Skip to content

Commit b8b9cab

Browse files
committed
Implement build-time error and assertion
Signed-off-by: Gary Guo <gary@garyguo.net>
1 parent 478bc95 commit b8b9cab

File tree

7 files changed

+173
-1
lines changed

7 files changed

+173
-1
lines changed

Diff for: lib/Kconfig.debug

+37
Original file line numberDiff line numberDiff line change
@@ -2641,6 +2641,43 @@ config RUST_OPT_LEVEL_Z
26412641

26422642
endchoice
26432643

2644+
choice
2645+
prompt "Build-time assertions"
2646+
default RUST_BUILD_ASSERT_ALLOW if RUST_OPT_LEVEL_0
2647+
default RUST_BUILD_ASSERT_DENY if !RUST_OPT_LEVEL_0
2648+
help
2649+
Controls how are `build_error!` and `build_assert!` handled during build.
2650+
2651+
If calls to them exist in the binary, it may indicate a violated invariant
2652+
or that the optimizer failed to verify the invariant during compilation.
2653+
You can choose to abort compilation or ignore them during build and let the
2654+
check be carried to runtime.
2655+
2656+
If optimizations are turned off, you cannot select "Deny".
2657+
2658+
If unsure, say "Deny".
2659+
2660+
config RUST_BUILD_ASSERT_ALLOW
2661+
bool "Allow"
2662+
help
2663+
Unoptimized calls to `build_error!` will be converted to `panic!`
2664+
and checked at runtime.
2665+
2666+
config RUST_BUILD_ASSERT_WARN
2667+
bool "Warn"
2668+
help
2669+
Unoptimized calls to `build_error!` will be converted to `panic!`
2670+
and checked at runtime, but warnings will be generated when building.
2671+
2672+
config RUST_BUILD_ASSERT_DENY
2673+
bool "Deny"
2674+
depends on !RUST_OPT_LEVEL_0
2675+
help
2676+
Unoptimized calls to `build_error!` will abort compilation.
2677+
2678+
endchoice
2679+
2680+
26442681
endmenu # "Rust"
26452682

26462683
source "Documentation/Kconfig"

Diff for: rust/Makefile

+11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ extra-$(CONFIG_RUST) += bindings_generated.rs
99
obj-$(CONFIG_RUST) += alloc.o kernel.o
1010
extra-$(CONFIG_RUST) += exports_alloc_generated.h exports_kernel_generated.h
1111

12+
ifndef CONFIG_RUST_BUILD_ASSERT_DENY
13+
obj-$(CONFIG_RUST) += build_error.o
14+
endif
15+
1216
obj-$(CONFIG_RUST) += exports.o
1317

1418
RUSTDOC = rustdoc
@@ -40,6 +44,7 @@ rustdoc-compiler_builtins: $(srctree)/rust/compiler_builtins.rs FORCE
4044
$(call if_changed,rustdoc)
4145

4246
rustdoc-kernel: private rustdoc_target_flags = --extern alloc \
47+
--extern build_error \
4348
--extern module=$(objtree)/rust/libmodule.so
4449
rustdoc-kernel: $(srctree)/rust/kernel/lib.rs rustdoc-module \
4550
$(objtree)/rust/libmodule.so $(objtree)/rust/bindings_generated.rs FORCE
@@ -155,9 +160,15 @@ $(objtree)/rust/alloc.o: $$(RUST_LIB_SRC)/alloc/src/lib.rs \
155160
$(objtree)/rust/compiler_builtins.o FORCE
156161
$(call if_changed_dep,rustc_library)
157162

163+
$(objtree)/rust/build_error.o: $(srctree)/rust/build_error.rs \
164+
$(objtree)/rust/compiler_builtins.o FORCE
165+
$(call if_changed_dep,rustc_library)
166+
158167
# ICE on `--extern module`: https://github.com/rust-lang/rust/issues/56935
159168
$(objtree)/rust/kernel.o: private rustc_target_flags = --extern alloc \
169+
--extern build_error \
160170
--extern module=$(objtree)/rust/libmodule.so
161171
$(objtree)/rust/kernel.o: $(srctree)/rust/kernel/lib.rs $(objtree)/rust/alloc.o \
172+
$(objtree)/rust/build_error.o \
162173
$(objtree)/rust/libmodule.so $(objtree)/rust/bindings_generated.rs FORCE
163174
$(call if_changed_dep,rustc_library)

Diff for: rust/build_error.rs

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Build-time error.
4+
//!
5+
//! This crate provides a function `build_error`, which will panic in
6+
//! compile-time if executed in const context, and will cause a build error
7+
//! if not executed at compile time and the optimizer does not optimise away the
8+
//! call.
9+
//!
10+
//! It is used by `build_assert!` in the kernel crate, allowing checking of
11+
//! conditions that could be checked statically, but could not be enforced in
12+
//! Rust yet (e.g. perform some checks in const functions, but those
13+
//! functions could still be called in the runtime).
14+
15+
#![no_std]
16+
#![feature(const_panic, core_panic)]
17+
18+
/// Panics if executed in const context, or triggers a build error if not.
19+
#[inline(never)]
20+
#[cold]
21+
#[no_mangle]
22+
#[track_caller]
23+
pub const fn build_error(msg: &'static str) -> ! {
24+
// Could also be `panic!(msg)` to avoid using unstable feature `core_panic`,
25+
// but it is not allowed in Rust 2021, while `panic!("{}", msg)` could not
26+
// yet be used in const context.
27+
core::panicking::panic(msg);
28+
}
29+
30+
#[cfg(CONFIG_RUST_BUILD_ASSERT_WARN)]
31+
#[link_section = ".gnu.warning.build_error"]
32+
#[used]
33+
static BUILD_ERROR_WARNING: [u8; 45] = *b"call to build_error present after compilation";

Diff for: rust/kernel/build_assert.rs

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Build-time assert.
4+
5+
/// Fails the build if the code path calling `build_error!` can possibly be executed.
6+
///
7+
/// If the macro is executed in const context, `build_error!` will panic.
8+
/// If the compiler or optimizer cannot guarantee that `build_error!` can never
9+
/// be called, a build error will be triggered.
10+
///
11+
/// # Examples
12+
/// ```no_run
13+
/// #[inline]
14+
/// fn foo(a: usize) -> usize {
15+
/// a.checked_add(1).unwrap_or_else(|| build_error!("overflow"))
16+
/// }
17+
/// ```
18+
#[macro_export]
19+
macro_rules! build_error {
20+
() => {{
21+
$crate::build_error("")
22+
}};
23+
($msg:expr) => {{
24+
$crate::build_error($msg)
25+
}};
26+
}
27+
28+
/// Asserts that a boolean expression is `true` at compile time.
29+
///
30+
/// If the condition is evaluated to `false` in const context, `build_assert!`
31+
/// will panic. If the compiler or optimizer cannot guarantee the condition will
32+
/// be evaluated to `true`, a build error will be triggered.
33+
///
34+
/// [`static_assert!`] should be preferred to `build_assert!` whenever possible.
35+
///
36+
/// # Examples
37+
///
38+
/// These examples show that different types of [`assert!`] will trigger errors
39+
/// at different stage of compilation. It is preferred to err as early as
40+
/// possible, so [`static_assert!`] should be used whenever possible.
41+
/// ```no_run
42+
/// fn foo() {
43+
/// static_assert!(1 > 1); // Compile-time error
44+
/// build_assert!(1 > 1); // Build-time error
45+
/// assert!(1 > 1); // Run-time error
46+
/// }
47+
/// ```
48+
///
49+
/// When the condition refers to generic parameters or parameters of an inline function,
50+
/// [`static_assert!`] cannot be used. Use `build_assert!` in this scenario.
51+
/// ```no_run
52+
/// fn foo<const N: usize>() {
53+
/// // `static_assert!(N > 1);` is not allowed
54+
/// build_assert!(N > 1); // Build-time check
55+
/// assert!(N > 1); // Run-time check
56+
/// }
57+
///
58+
/// #[inline]
59+
/// fn bar(n: usize) {
60+
/// // `static_assert!(n > 1);` is not allowed
61+
/// build_assert!(n > 1); // Build-time check
62+
/// assert!(n > 1); // Run-time check
63+
/// }
64+
/// ```
65+
#[macro_export]
66+
macro_rules! build_assert {
67+
($cond:expr $(,)?) => {{
68+
if !$cond {
69+
$crate::build_error(concat!("assertion failed: ", stringify!($cond)));
70+
}
71+
}};
72+
($cond:expr, $msg:expr) => {{
73+
if !$cond {
74+
$crate::build_error($msg);
75+
}
76+
}};
77+
}

Diff for: rust/kernel/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ mod raw_list;
5151
#[doc(hidden)]
5252
pub mod module_param;
5353

54+
mod build_assert;
5455
pub mod prelude;
5556
pub mod print;
5657
pub mod random;
@@ -65,6 +66,9 @@ pub mod iov_iter;
6566
mod types;
6667
pub mod user_ptr;
6768

69+
#[doc(hidden)]
70+
pub use build_error::build_error;
71+
6872
pub use crate::error::{Error, Result};
6973
pub use crate::types::{CStr, Mode};
7074

Diff for: rust/kernel/prelude.rs

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
1414
pub use alloc::{borrow::ToOwned, string::String};
1515

16+
pub use super::build_assert;
17+
1618
pub use module::{module, module_misc_device};
1719

1820
pub use super::{pr_alert, pr_cont, pr_crit, pr_emerg, pr_err, pr_info, pr_notice, pr_warn};

Diff for: scripts/generate_rust_analyzer.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,19 @@ def append_crate(display_name, root_module, is_workspace_member, deps, cfg):
7171
)
7272
crates[-1]["proc_macro_dylib_path"] = "rust/libmodule.so"
7373

74+
append_crate(
75+
"build_error",
76+
srctree / "rust" / "build_error.rs",
77+
True,
78+
["core", "compiler_builtins"],
79+
[],
80+
)
81+
7482
append_crate(
7583
"kernel",
7684
srctree / "rust" / "kernel" / "lib.rs",
7785
True,
78-
["core", "alloc", "module"],
86+
["core", "alloc", "module", "build_error"],
7987
cfg,
8088
)
8189
crates[-1]["env"]["RUST_BINDINGS_FILE"] = str(bindings_file.resolve(True))

0 commit comments

Comments
 (0)