Skip to content
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ required to lift unsafe Rust into safe Rust types.
However, we are building a [refactoring tool](c2rust-refactor) that reduces the tedium of doing so.
This work is still in the early stages; please get in touch if you're interested!

You can also [cross-check](cross-checks) the translated code against the original ([tutorial](docs/cross-check-tutorial.md)).

Here's the big picture:

![C2Rust overview](docs/c2rust-overview.png "C2Rust overview")
Expand Down
14 changes: 14 additions & 0 deletions c2rust-transpile/src/build_files/Cargo.toml.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,18 @@ name = "{{name}}"
{{/if}}
{{/each}}

{{#if cross_checks~}}
[dependencies.c2rust-xcheck-plugin]
version = "*"

[dependencies.c2rust-xcheck-derive]
version = "*"

[dependencies.c2rust-xcheck-runtime]
version = "*"
features = ["libc-hash", "fixed-length-array-hash"]

[dependencies.c2rust-xcheck-backend-{{cross_check_backend}}]
version = "*"
Comment on lines +44 to +55
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
[dependencies.c2rust-xcheck-plugin]
version = "*"
[dependencies.c2rust-xcheck-derive]
version = "*"
[dependencies.c2rust-xcheck-runtime]
version = "*"
features = ["libc-hash", "fixed-length-array-hash"]
[dependencies.c2rust-xcheck-backend-{{cross_check_backend}}]
version = "*"
c2rust-xcheck-plugin = "*"
c2rust-xcheck-derive = "*"
c2rust-xcheck-runtime = { version = "*", features = ["libc-hash", "fixed-length-array-hash"] }
c2rust-xcheck-backend-{{cross_check_backend}} = "*"

Also, what's the reason for "*" instead of a specific version?

{{~/if}}
{{~/if}}
10 changes: 10 additions & 0 deletions c2rust-transpile/src/build_files/lib.rs.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
extern crate {{this.ident}};
{{~/each}}

{{#if cross_checks~}}
#![plugin(c2rust_xcheck_plugin({{plugin_args}}))]
#[macro_use] extern crate c2rust_xcheck_derive;
#[macro_use] extern crate c2rust_xcheck_runtime;
extern crate c2rust_xcheck_backend_{{cross_check_backend}};

#[global_allocator]
static C2RUST_ALLOC: ::std::alloc::System = ::std::alloc::System;
{{~/if}}

{{#each modules~}}
{{~#if this.path~}}
#[path = "{{this.path}}"]
Expand Down
13 changes: 13 additions & 0 deletions c2rust-transpile/src/build_files/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,13 +250,24 @@ fn emit_lib_rs(
pragmas: PragmaSet,
crates: &CrateSet,
) -> Option<PathBuf> {
let plugin_args = tcfg
.cross_check_configs
.iter()
.map(|ccc| format!("config_file = \"{}\"", ccc))
.collect::<Vec<String>>()
.join(", ");

let modules = convert_module_list(tcfg, build_dir, modules, ModuleSubset::Libraries);
let crates = convert_dependencies_list(crates.clone(), tcfg.c2rust_dir.as_deref());
let file_name = get_lib_rs_file_name(tcfg);
let rs_xcheck_backend = tcfg.cross_check_backend.replace("-", "_");
let json = json!({
"lib_rs_file": file_name,
"reorganize_definitions": tcfg.reorganize_definitions,
"translate_valist": tcfg.translate_valist,
"cross_checks": tcfg.cross_checks,
"cross_check_backend": rs_xcheck_backend,
"plugin_args": plugin_args,
"modules": modules,
"pragmas": pragmas,
"crates": crates,
Expand Down Expand Up @@ -311,6 +322,8 @@ fn emit_cargo_toml<'lcmd>(
"is_library": ccfg.link_cmd.r#type.is_library(),
"lib_rs_file": get_lib_rs_file_name(tcfg),
"binaries": binaries,
"cross_checks": tcfg.cross_checks,
"cross_check_backend": tcfg.cross_check_backend,
"dependencies": dependencies,
});
json.as_object_mut().unwrap().extend(
Expand Down
3 changes: 3 additions & 0 deletions c2rust-transpile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ pub struct TranspilerConfig {
pub fail_on_multiple: bool,
pub filter: Option<Regex>,
pub debug_relooper_labels: bool,
pub cross_checks: bool,
pub cross_check_backend: String,
pub cross_check_configs: Vec<String>,
pub prefix_function_names: Option<String>,
pub translate_asm: bool,
pub use_c_loop_info: bool,
Expand Down
3 changes: 2 additions & 1 deletion c2rust-transpile/src/translator/main_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ impl<'c> Translation<'c> {
};

let block = mk().block(stmts);
Ok(mk().pub_().fn_item(decl, block))
let main_attributes = self.mk_cross_check(mk(), vec!["none"]);
Ok(main_attributes.pub_().fn_item(decl, block))
} else {
Err(TranslationError::generic(
"Cannot translate non-function main entry point",
Expand Down
55 changes: 53 additions & 2 deletions c2rust-transpile/src/translator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1228,6 +1228,20 @@ fn arrange_header(t: &Translation, is_binary: bool) -> (Vec<syn::Attribute>, Vec
out_attrs.push(attr);
}

if t.tcfg.cross_checks {
let xcheck_plugin_args = t
.tcfg
.cross_check_configs
.iter()
.map(|config_file| mk().meta_namevalue("config_file", mk().str_lit(config_file)))
.collect::<Vec<_>>();
let xcheck_plugin_item = mk().meta_list("c2rust_xcheck_plugin", xcheck_plugin_args);
let plugin_args = vec![xcheck_plugin_item];
let plugin_item = mk().meta_list("plugin", plugin_args);
let attr = mk().attribute(AttrStyle::Inner(Default::default()), plugin_item);
out_attrs.push(attr);
}

if t.tcfg.emit_no_std {
let meta = mk().meta_path("no_std");
let attr = mk().attribute(AttrStyle::Inner(Default::default()), meta);
Expand All @@ -1247,6 +1261,24 @@ fn arrange_header(t: &Translation, is_binary: bool) -> (Vec<syn::Attribute>, Vec
}
}

if t.tcfg.cross_checks {
out_items.push(
mk().single_attr("macro_use")
.extern_crate_item("c2rust_xcheck_derive", None),
);
out_items.push(
mk().single_attr("macro_use")
.extern_crate_item("c2rust_xcheck_runtime", None),
);
Comment on lines +1265 to +1272
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need #[macro_use]s anymore.

// When cross-checking, always use the system allocator
let sys_alloc_path = vec!["", "std", "alloc", "System"];
out_items.push(mk().single_attr("global_allocator").static_item(
"C2RUST_ALLOC",
mk().path_ty(sys_alloc_path.clone()),
mk().path_expr(sys_alloc_path),
));
}

// TODO: switch to `#[expect(unused_imports, reason = ...)]` once
// we upgrade to a newer nightly (Rust 1.81) that supports it.
out_items.push(
Expand Down Expand Up @@ -1432,6 +1464,10 @@ impl<'c> Translation<'c> {
"unused_assignments",
],
)];
if self.tcfg.cross_checks {
features.append(&mut vec!["plugin"]);
pragmas.push(("cross_check", vec!["yes"]));
}

if self.features.borrow().contains("register_tool") {
pragmas.push(("register_tool", vec!["c2rust"]));
Expand Down Expand Up @@ -1465,6 +1501,14 @@ impl<'c> Translation<'c> {
))
}

fn mk_cross_check(&self, mk: Builder, args: Vec<&str>) -> Builder {
if self.tcfg.cross_checks {
mk.call_attr("cross_check", args)
} else {
mk
}
}

fn static_initializer_is_unsafe(&self, expr_id: Option<CExprId>, qty: CQualTypeId) -> bool {
// SIMD types are always unsafe in statics
match self.ast_context.resolve_type(qty.ctype).kind {
Expand Down Expand Up @@ -1660,7 +1704,11 @@ impl<'c> Translation<'c> {
let fn_decl = mk().fn_decl(fn_name.clone(), vec![], None, fn_ty.clone());
let fn_bare_decl = (vec![], None, fn_ty);
let fn_block = mk().block(sectioned_static_initializers);
let fn_item = mk().unsafe_().extern_("C").fn_item(fn_decl, fn_block);
let fn_attributes = self.mk_cross_check(mk(), vec!["none"]);
let fn_item = fn_attributes
.unsafe_()
.extern_("C")
.fn_item(fn_decl, fn_block);

let static_attributes = mk()
.single_attr("used")
Expand Down Expand Up @@ -2418,7 +2466,10 @@ impl<'c> Translation<'c> {

// Only add linkage attributes if the function is `extern`
let mut mk_ = if is_main {
mk()
// Cross-check this function as if it was called `main`
// FIXME: pass in a vector of NestedMetaItem elements,
// but strings have to do for now
self.mk_cross_check(mk(), vec!["entry(djb2=\"main\")", "exit(djb2=\"main\")"])
} else if (is_global && !is_inline) || is_extern_inline {
mk_linkage(false, new_name, name).extern_("C").pub_()
} else if self.cur_file.get().is_some() {
Expand Down
3 changes: 3 additions & 0 deletions c2rust-transpile/tests/snapshots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ fn config() -> TranspilerConfig {
fail_on_multiple: false,
filter: None,
debug_relooper_labels: false,
cross_checks: false,
cross_check_backend: Default::default(),
cross_check_configs: Default::default(),
prefix_function_names: None,
translate_asm: true,
use_c_loop_info: true,
Expand Down
36 changes: 36 additions & 0 deletions c2rust/src/bin/c2rust-transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@ struct Args {
/// Fail when the control-flow graph generates branching constructs
#[clap(long)]
fail_on_multiple: bool,

#[clap(long, short = 'x')]
cross_checks: bool,

#[clap(long, short = 'X', multiple = true)]
cross_check_config: Vec<String>,

#[clap(long, value_enum, default_value_t)]
cross_check_backend: CrossCheckBackend,
}

// TODO Eventually move this code into `c2rust-transpile`
Expand Down Expand Up @@ -200,6 +209,30 @@ impl From<TranslateMacros> for c2rust_transpile::TranslateMacros {
}
}

#[derive(Default, Debug, ValueEnum, Clone)]
pub enum CrossCheckBackend {
DynamicDlsym,

#[default]
ZstdLogging,

LibclevrbufSys,

LibfakechecksSys,
}

impl From<CrossCheckBackend> for String {
fn from(x: CrossCheckBackend) -> String {
let s = match x {
CrossCheckBackend::DynamicDlsym => "dynamic-dlsym",
CrossCheckBackend::ZstdLogging => "zstd-logging",
CrossCheckBackend::LibclevrbufSys => "libclevrbuf-sys",
CrossCheckBackend::LibfakechecksSys => "libfakechecks-sys",
};
s.to_string()
}
}

#[derive(Debug, PartialEq, Eq, ValueEnum, Clone)]
#[clap(rename_all = "snake_case")]
enum InvalidCodes {
Expand Down Expand Up @@ -228,6 +261,9 @@ fn main() {
fail_on_multiple: args.fail_on_multiple,
filter: args.filter,
debug_relooper_labels: args.debug_labels,
cross_checks: args.cross_checks,
cross_check_backend: args.cross_check_backend.into(),
cross_check_configs: args.cross_check_config,
prefix_function_names: args.prefix_function_names,

// We used to guard asm translation with a command-line
Expand Down
11 changes: 11 additions & 0 deletions cross-checks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
This is the top-level directory for all cross-checking components, and contains the following:

* A clang plugin that automatically inserts cross-check instrumentation into C code.

* An equivalent rustc compiler plugin for Rust.

* The `libfakechecks` cross-checking backend library that prints out all cross-checks to standard output.
This library is supported by both the C and Rust compiler plugins.

* Our experimental fork of the `ReMon` MVEE modified for C/Rust side-by-side checking,
along with the `mvee-configs` directory that contains some MVEE configuration examples.
33 changes: 33 additions & 0 deletions cross-checks/c-checks/clang-plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.8)

find_package(Clang REQUIRED CONFIG)

add_definitions(${CLANG_DEFINITIONS} ${LLVM_DEFINITIONS})
include_directories(${CLANG_INCLUDE_DIRS} ${LLVM_INCLUDE_DIRS})

find_program(LLVM_TABLEGEN_EXE "llvm-tblgen" ${LLVM_TOOLS_BINARY_DIR}
NO_DEFAULT_PATH)

# TableGen needs this
# Find "llvm/Option/OptParser.td" for Options.td,
# since LLVM_MAIN_INCLUDE_DIR needs to be a single path
find_path(LLVM_OPT_PARSER_PATH OptParser.td
PATHS ${LLVM_INCLUDE_DIRS}
PATH_SUFFIXES llvm/Option
NO_DEFAULT_PATH
)
get_filename_component(LLVM_INCLUDE_LLVM_DIR
${LLVM_OPT_PARSER_PATH} DIRECTORY)
get_filename_component(LLVM_MAIN_INCLUDE_DIR
${LLVM_INCLUDE_LLVM_DIR} DIRECTORY)
set(CMAKE_INCLUDE_CURRENT_DIR ON)

# Needed for add_llvm_loadable_module
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
include(AddLLVM)
include(TableGen)
include(HandleLLVMOptions)

add_subdirectory(plugin)
add_subdirectory(runtime)
add_subdirectory(test)
35 changes: 35 additions & 0 deletions cross-checks/c-checks/clang-plugin/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

TEST_CFLAGS=-g -O2

#PLUGIN_PATH := $(shell find . -name CrossChecks.so)
#RUNTIME_PATH := $(shell find . -name libruntime.a)
PLUGIN_PATH := $(realpath ../../../build/clang-xcheck-plugin.$(shell uname -n)/plugin/CrossChecks.so)
RUNTIME_PATH := $(realpath ../../../build/clang-xcheck-plugin.$(shell uname -n)/runtime/libruntime.a)

# Override this to change the path to the clang binary
#PLUGIN_CC := clang
PLUGIN_CC := $(realpath ../../../build/llvm-6.0.0/build.$(shell uname -n)/bin/clang)

PLUGIN_CC_ARGS := -Xclang -plugin-arg-crosschecks -Xclang -Ctest.c2r

ifneq ($(filter yes true,$(DUMP_AST)),)
PLUGIN_CC_ARGS += -Xclang -ast-dump
endif

ifneq ($(filter yes true,$(DUMP_LLVM)),)
PLUGIN_CC_ARGS += -S -emit-llvm
endif

FAKECHECKS_PATH=`pwd`/../../libfakechecks

.PHONY: clean all test.bin
all: test.bin

clean:
rm -f test.bin

test.bin: test.c
@echo Building test...
./cc_wrapper.sh $(PLUGIN_CC) $(PLUGIN_PATH) $(PLUGIN_CC_ARGS) -ffunction-sections -fuse-ld=gold -Wl,--gc-sections,--icf=safe,-rpath,$(FAKECHECKS_PATH) -L$(FAKECHECKS_PATH) -lfakechecks -std=c11 -Iinclude $(TEST_CFLAGS) -o test.bin $< $(RUNTIME_PATH)
@echo Running test...
./test.bin
31 changes: 31 additions & 0 deletions cross-checks/c-checks/clang-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Clang plugin for crosschecking on C programs

This is a cross-check inserter for C programs implemented as a clang compiler plugin.

## Building and running the plugin

1. Build `libfakechecks` (optional, useful for testing):
```bash
$ cd ../../libfakechecks
$ make all
```

2. Build the clang plugin using the build script:
```bash
$ ../../../scripts/build_cross_checks.py
```

3. To compile code using the plugin, either wrap the compilation command with the `cc_wrapper.sh` script from this directory:
```bash
$ cc_wrapper.sh <path/to/clang> .../CrossChecks.so <rest of command line...>
```
or add the following arguments manually to the clang command line, e.g., using `CFLAGS`:
```
-Xclang -load -Xclang .../CrossChecks.so -Xclang -add-plugin -Xclang crosschecks
```
and link against `libruntime.a`.
In both cases, the target binary must then be linked against one of the `rb_xcheck` implementation libraries: `libfakechecks.so` or `libclevrbuf.so`.

## Testing

This plugin can be tested in this directory by running `make test`.
Loading