Skip to content

Build-time assertion #257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/kernel-arm-debug.config
Original file line number Diff line number Diff line change
Expand Up @@ -1708,5 +1708,8 @@ CONFIG_RUST_OPT_LEVEL_2=y
# CONFIG_RUST_OPT_LEVEL_3 is not set
# CONFIG_RUST_OPT_LEVEL_S is not set
# CONFIG_RUST_OPT_LEVEL_Z is not set
# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set
# CONFIG_RUST_BUILD_ASSERT_WARN is not set
CONFIG_RUST_BUILD_ASSERT_DENY=y
# end of Rust hacking
# end of Kernel hacking
3 changes: 3 additions & 0 deletions .github/workflows/kernel-arm-release.config
Original file line number Diff line number Diff line change
Expand Up @@ -1666,5 +1666,8 @@ CONFIG_RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C=y
# CONFIG_RUST_OPT_LEVEL_3 is not set
# CONFIG_RUST_OPT_LEVEL_S is not set
# CONFIG_RUST_OPT_LEVEL_Z is not set
# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set
# CONFIG_RUST_BUILD_ASSERT_WARN is not set
CONFIG_RUST_BUILD_ASSERT_DENY=y
# end of Rust hacking
# end of Kernel hacking
3 changes: 3 additions & 0 deletions .github/workflows/kernel-arm64-debug.config
Original file line number Diff line number Diff line change
Expand Up @@ -1466,5 +1466,8 @@ CONFIG_RUST_OPT_LEVEL_1=y
# CONFIG_RUST_OPT_LEVEL_3 is not set
# CONFIG_RUST_OPT_LEVEL_S is not set
# CONFIG_RUST_OPT_LEVEL_Z is not set
# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set
# CONFIG_RUST_BUILD_ASSERT_WARN is not set
CONFIG_RUST_BUILD_ASSERT_DENY=y
# end of Rust hacking
# end of Kernel hacking
3 changes: 3 additions & 0 deletions .github/workflows/kernel-arm64-release.config
Original file line number Diff line number Diff line change
Expand Up @@ -1383,5 +1383,8 @@ CONFIG_RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C=y
# CONFIG_RUST_OPT_LEVEL_3 is not set
# CONFIG_RUST_OPT_LEVEL_S is not set
# CONFIG_RUST_OPT_LEVEL_Z is not set
# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set
# CONFIG_RUST_BUILD_ASSERT_WARN is not set
CONFIG_RUST_BUILD_ASSERT_DENY=y
# end of Rust hacking
# end of Kernel hacking
2 changes: 2 additions & 0 deletions .github/workflows/kernel-ppc64le-debug.config
Original file line number Diff line number Diff line change
Expand Up @@ -1588,5 +1588,7 @@ CONFIG_RUST_OPT_LEVEL_0=y
# CONFIG_RUST_OPT_LEVEL_3 is not set
# CONFIG_RUST_OPT_LEVEL_S is not set
# CONFIG_RUST_OPT_LEVEL_Z is not set
CONFIG_RUST_BUILD_ASSERT_ALLOW=y
# CONFIG_RUST_BUILD_ASSERT_WARN is not set
# end of Rust hacking
# end of Kernel hacking
3 changes: 3 additions & 0 deletions .github/workflows/kernel-ppc64le-release.config
Original file line number Diff line number Diff line change
Expand Up @@ -1491,5 +1491,8 @@ CONFIG_RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C=y
# CONFIG_RUST_OPT_LEVEL_3 is not set
# CONFIG_RUST_OPT_LEVEL_S is not set
# CONFIG_RUST_OPT_LEVEL_Z is not set
# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set
# CONFIG_RUST_BUILD_ASSERT_WARN is not set
CONFIG_RUST_BUILD_ASSERT_DENY=y
# end of Rust hacking
# end of Kernel hacking
2 changes: 2 additions & 0 deletions .github/workflows/kernel-riscv64-debug.config
Original file line number Diff line number Diff line change
Expand Up @@ -1317,5 +1317,7 @@ CONFIG_RUST_OPT_LEVEL_0=y
# CONFIG_RUST_OPT_LEVEL_3 is not set
# CONFIG_RUST_OPT_LEVEL_S is not set
# CONFIG_RUST_OPT_LEVEL_Z is not set
CONFIG_RUST_BUILD_ASSERT_ALLOW=y
# CONFIG_RUST_BUILD_ASSERT_WARN is not set
# end of Rust hacking
# end of Kernel hacking
3 changes: 3 additions & 0 deletions .github/workflows/kernel-riscv64-release.config
Original file line number Diff line number Diff line change
Expand Up @@ -1231,5 +1231,8 @@ CONFIG_RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C=y
# CONFIG_RUST_OPT_LEVEL_3 is not set
# CONFIG_RUST_OPT_LEVEL_S is not set
# CONFIG_RUST_OPT_LEVEL_Z is not set
# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set
# CONFIG_RUST_BUILD_ASSERT_WARN is not set
CONFIG_RUST_BUILD_ASSERT_DENY=y
# end of Rust hacking
# end of Kernel hacking
2 changes: 2 additions & 0 deletions .github/workflows/kernel-x86_64-debug.config
Original file line number Diff line number Diff line change
Expand Up @@ -1542,5 +1542,7 @@ CONFIG_RUST_OPT_LEVEL_0=y
# CONFIG_RUST_OPT_LEVEL_3 is not set
# CONFIG_RUST_OPT_LEVEL_S is not set
# CONFIG_RUST_OPT_LEVEL_Z is not set
CONFIG_RUST_BUILD_ASSERT_ALLOW=y
# CONFIG_RUST_BUILD_ASSERT_WARN is not set
# end of Rust hacking
# end of Kernel hacking
3 changes: 3 additions & 0 deletions .github/workflows/kernel-x86_64-release.config
Original file line number Diff line number Diff line change
Expand Up @@ -1433,5 +1433,8 @@ CONFIG_RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C=y
# CONFIG_RUST_OPT_LEVEL_3 is not set
# CONFIG_RUST_OPT_LEVEL_S is not set
# CONFIG_RUST_OPT_LEVEL_Z is not set
# CONFIG_RUST_BUILD_ASSERT_ALLOW is not set
# CONFIG_RUST_BUILD_ASSERT_WARN is not set
CONFIG_RUST_BUILD_ASSERT_DENY=y
# end of Rust hacking
# end of Kernel hacking
37 changes: 37 additions & 0 deletions lib/Kconfig.debug
Original file line number Diff line number Diff line change
Expand Up @@ -2641,6 +2641,43 @@ config RUST_OPT_LEVEL_Z

endchoice

choice
prompt "Build-time assertions"
default RUST_BUILD_ASSERT_ALLOW if RUST_OPT_LEVEL_0
default RUST_BUILD_ASSERT_DENY if !RUST_OPT_LEVEL_0
help
Controls how are `build_error!` and `build_assert!` handled during build.

If calls to them exist in the binary, it may indicate a violated invariant
or that the optimizer failed to verify the invariant during compilation.
You can choose to abort compilation or ignore them during build and let the
check be carried to runtime.

If optimizations are turned off, you cannot select "Deny".

If unsure, say "Deny".

config RUST_BUILD_ASSERT_ALLOW
bool "Allow"
help
Unoptimized calls to `build_error!` will be converted to `panic!`
and checked at runtime.

config RUST_BUILD_ASSERT_WARN
bool "Warn"
help
Unoptimized calls to `build_error!` will be converted to `panic!`
and checked at runtime, but warnings will be generated when building.

config RUST_BUILD_ASSERT_DENY
bool "Deny"
depends on !RUST_OPT_LEVEL_0
help
Unoptimized calls to `build_error!` will abort compilation.

endchoice


endmenu # "Rust"

source "Documentation/Kconfig"
Expand Down
13 changes: 12 additions & 1 deletion rust/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ extra-$(CONFIG_RUST) += bindings_generated.rs
obj-$(CONFIG_RUST) += alloc.o kernel.o
extra-$(CONFIG_RUST) += exports_alloc_generated.h exports_kernel_generated.h

ifndef CONFIG_RUST_BUILD_ASSERT_DENY
obj-$(CONFIG_RUST) += build_error.o
endif

obj-$(CONFIG_RUST) += exports.o

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

rustdoc-kernel: private rustdoc_target_flags = --extern alloc \
--extern build_error \
--extern module=$(objtree)/rust/libmodule.so
rustdoc-kernel: $(srctree)/rust/kernel/lib.rs rustdoc-module \
$(objtree)/rust/libmodule.so $(objtree)/rust/bindings_generated.rs FORCE
Expand Down Expand Up @@ -77,7 +82,7 @@ bindgen_c_flags = $(filter-out $(bindgen_skip_c_flags), $(c_flags)) \
$(bindgen_extra_c_flags)
endif

bindgen_opaque_types := xregs_state desc_struct arch_lbr_state
bindgen_opaque_types := xregs_state desc_struct arch_lbr_state local_apic
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this one related or is another cleanup?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is in a separate commit. I couldn't build on my machine without this change, but it is trivial enough that I think it might not worth a separate PR.


# To avoid several recompilations in PowerPC, which inserts `-D_TASK_CPU`
bindgen_c_flags_final = $(filter-out -D_TASK_CPU=%, $(bindgen_c_flags))
Expand Down Expand Up @@ -155,9 +160,15 @@ $(objtree)/rust/alloc.o: $$(RUST_LIB_SRC)/alloc/src/lib.rs \
$(objtree)/rust/compiler_builtins.o FORCE
$(call if_changed_dep,rustc_library)

$(objtree)/rust/build_error.o: $(srctree)/rust/build_error.rs \
$(objtree)/rust/compiler_builtins.o FORCE
$(call if_changed_dep,rustc_library)

# ICE on `--extern module`: https://github.com/rust-lang/rust/issues/56935
$(objtree)/rust/kernel.o: private rustc_target_flags = --extern alloc \
--extern build_error \
--extern module=$(objtree)/rust/libmodule.so
$(objtree)/rust/kernel.o: $(srctree)/rust/kernel/lib.rs $(objtree)/rust/alloc.o \
$(objtree)/rust/build_error.o \
$(objtree)/rust/libmodule.so $(objtree)/rust/bindings_generated.rs FORCE
$(call if_changed_dep,rustc_library)
33 changes: 33 additions & 0 deletions rust/build_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0

//! Build-time error.
//!
//! This crate provides a function `build_error`, which will panic in
//! compile-time if executed in const context, and will cause a build error
//! if not executed at compile time and the optimizer does not optimise away the
//! call.
//!
//! It is used by `build_assert!` in the kernel crate, allowing checking of
//! conditions that could be checked statically, but could not be enforced in
//! Rust yet (e.g. perform some checks in const functions, but those
//! functions could still be called in the runtime).

#![no_std]
#![feature(const_panic, core_panic)]

/// Panics if executed in const context, or triggers a build error if not.
#[inline(never)]
#[cold]
#[no_mangle]
#[track_caller]
pub const fn build_error(msg: &'static str) -> ! {
// Could also be `panic!(msg)` to avoid using unstable feature `core_panic`,
// but it is not allowed in Rust 2021, while `panic!("{}", msg)` could not
// yet be used in const context.
core::panicking::panic(msg);
}

#[cfg(CONFIG_RUST_BUILD_ASSERT_WARN)]
#[link_section = ".gnu.warning.build_error"]
#[used]
static BUILD_ERROR_WARNING: [u8; 45] = *b"call to build_error present after compilation";
77 changes: 77 additions & 0 deletions rust/kernel/build_assert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: GPL-2.0

//! Build-time assert.

/// Fails the build if the code path calling `build_error!` can possibly be executed.
///
/// If the macro is executed in const context, `build_error!` will panic.
/// If the compiler or optimizer cannot guarantee that `build_error!` can never
/// be called, a build error will be triggered.
///
/// # Examples
/// ```no_run
/// #[inline]
/// fn foo(a: usize) -> usize {
/// a.checked_add(1).unwrap_or_else(|| build_error!("overflow"))
/// }
/// ```
#[macro_export]
macro_rules! build_error {
() => {{
$crate::build_error("")
}};
($msg:expr) => {{
$crate::build_error($msg)
}};
}

/// Asserts that a boolean expression is `true` at compile time.
///
/// If the condition is evaluated to `false` in const context, `build_assert!`
/// will panic. If the compiler or optimizer cannot guarantee the condition will
/// be evaluated to `true`, a build error will be triggered.
///
/// [`static_assert!`] should be preferred to `build_assert!` whenever possible.
///
/// # Examples
///
/// These examples show that different types of [`assert!`] will trigger errors
/// at different stage of compilation. It is preferred to err as early as
/// possible, so [`static_assert!`] should be used whenever possible.
/// ```no_run
/// fn foo() {
/// static_assert!(1 > 1); // Compile-time error
/// build_assert!(1 > 1); // Build-time error
/// assert!(1 > 1); // Run-time error
/// }
/// ```
///
/// When the condition refers to generic parameters or parameters of an inline function,
/// [`static_assert!`] cannot be used. Use `build_assert!` in this scenario.
/// ```no_run
/// fn foo<const N: usize>() {
/// // `static_assert!(N > 1);` is not allowed
/// build_assert!(N > 1); // Build-time check
/// assert!(N > 1); // Run-time check
/// }
///
/// #[inline]
/// fn bar(n: usize) {
/// // `static_assert!(n > 1);` is not allowed
/// build_assert!(n > 1); // Build-time check
/// assert!(n > 1); // Run-time check
/// }
/// ```
#[macro_export]
macro_rules! build_assert {
($cond:expr $(,)?) => {{
if !$cond {
$crate::build_error(concat!("assertion failed: ", stringify!($cond)));
}
}};
($cond:expr, $msg:expr) => {{
if !$cond {
$crate::build_error($msg);
}
}};
}
4 changes: 4 additions & 0 deletions rust/kernel/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ mod raw_list;
#[doc(hidden)]
pub mod module_param;

mod build_assert;
pub mod prelude;
pub mod print;
pub mod random;
Expand All @@ -65,6 +66,9 @@ pub mod iov_iter;
mod types;
pub mod user_ptr;

#[doc(hidden)]
pub use build_error::build_error;

pub use crate::error::{Error, Result};
pub use crate::types::{CStr, Mode};

Expand Down
2 changes: 2 additions & 0 deletions rust/kernel/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

pub use alloc::{borrow::ToOwned, string::String};

pub use super::build_assert;

pub use module::{module, module_misc_device};

pub use super::{pr_alert, pr_cont, pr_crit, pr_emerg, pr_err, pr_info, pr_notice, pr_warn};
Expand Down
10 changes: 9 additions & 1 deletion scripts/generate_rust_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,19 @@ def append_crate(display_name, root_module, is_workspace_member, deps, cfg):
)
crates[-1]["proc_macro_dylib_path"] = "rust/libmodule.so"

append_crate(
"build_error",
srctree / "rust" / "build_error.rs",
True,
["core", "compiler_builtins"],
[],
)

append_crate(
"kernel",
srctree / "rust" / "kernel" / "lib.rs",
True,
["core", "alloc", "module"],
["core", "alloc", "module", "build_error"],
cfg,
)
crates[-1]["env"]["RUST_BINDINGS_FILE"] = str(bindings_file.resolve(True))
Expand Down