[Miscompilation] C Statement Expression results in E0308 (type mismatch)
I encountered a miscompilation bug when transpiling a C program that uses Statement Expressions (({ ... })) containing internal control flow (goto).
c2rust correctly identifies the need for a block, but fails to ensure the block evaluates to a value. The generated Rust block ends with a statement (assignment) instead of an expression, causing a unit type () to be returned instead of the expected i32.
Reproduction C Code
#include <stdio.h>
int main(void) {
int input = 10;
// C Statement Expression with internal goto
int result = ({
int val;
if (input > 5) {
val = 42;
goto early_exit;
}
val = 0;
early_exit:
val; // The return value of the expression
});
printf("Result: %d\n", result);
return 0;
}
Generated Rust Code
#![allow(
dead_code,
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
unused_assignments,
unused_mut
)]
#![feature(label_break_value)]
extern "C" {
fn printf(__format: *const ::core::ffi::c_char, ...) -> ::core::ffi::c_int;
}
unsafe fn main_0() -> ::core::ffi::c_int {
let mut input: ::core::ffi::c_int = 10 as ::core::ffi::c_int;
let mut val: ::core::ffi::c_int = 0;
if input > 5 as ::core::ffi::c_int {
val = 42 as ::core::ffi::c_int;
} else {
val = 0 as ::core::ffi::c_int;
}
let mut result: ::core::ffi::c_int = 'c_646: {
let mut val: ::core::ffi::c_int = 0;
if input > 5 as ::core::ffi::c_int {
val = 42 as ::core::ffi::c_int;
} else {
val = 0 as ::core::ffi::c_int;
}
};
printf(b"Result: %d\n\0" as *const u8 as *const ::core::ffi::c_char, result);
return 0 as ::core::ffi::c_int;
}
pub fn main() {
unsafe { ::std::process::exit(main_0() as i32) }
}
Compilation Errors Using rustc (Stable channel):
error[E0308]: mismatched types
--> test.rs:23:44
|
23 | if input > 5 as ::core::ffi::c_int {
| ____________________________________________^
24 | | val = 42 as ::core::ffi::c_int;
25 | | } else {
| |_________^ expected `i32`, found `()`
error[E0308]: mismatched types
--> test.rs:25:16
|
25 | } else {
| ________________^
26 | | val = 0 as ::core::ffi::c_int;
27 | | }
| |_________^ expected `i32`, found `()`
error: aborting due to 3 previous errors; 2 warnings emitted
Some errors have detailed explanations: E0308, E0554.
For more information about an error, try `rustc --explain E0308`.
Environment:
- c2rust version: v0.21.0
- rustc version: 1.91.1
I can confirm this issue. I was able to minimize the reproducer to the following:
int main(void) {
// C Statement Expression with internal label
int result = ({
early_exit:
0; // The return value of the expression
});
return 0;
}
Cc @randomPoison as the person most likely to have our control-flow processing fresh in their head.
The issue here is happening before we get to relooper, something is going wrong when we create the CFG that's causing us to eat the final return of a statement expr. The issue specifically happens if the final statement of the statement expr, the thing that gets returned, is labeled. i.e. this is fine:
int result = ({
label:
x++;
0;
});
But this breaks:
int result = ({
x++;
label:
0;
});
Looking at the CFG we are feeding into relooper, it looks like we're doing something wrong with the return statement:
{
"entries": "s_0",
"nodes": {
"s_0": {
"body": [
[
"x += 1;"
]
],
"terminator": {
"Jump": [
"s_6"
]
}
},
"s_6": {
"body": [
[
"return;"
]
],
"terminator": "End"
}
}
}
Note that the return statement doesn't return a value. I'm not familiar with the code for building the CFG though, so I'm not sure why this is happening.