Skip to content

Commit 2f84b32

Browse files
avanhatttedinski
authored andcommitted
Handle boxed closures as arguments (fix RMC std lib crash) (rust-lang#278)
* Support boxed closures * More comments * Review comments * Whitespace and test typo * Cleanup
1 parent 1e251e3 commit 2f84b32

File tree

9 files changed

+140
-76
lines changed

9 files changed

+140
-76
lines changed

Diff for: compiler/rustc_codegen_llvm/src/gotoc/intrinsic.rs

-1
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,6 @@ impl<'tcx> GotocCtx<'tcx> {
453453
"prefetch_read_instruction" => unimplemented!(),
454454
"prefetch_write_data" => unimplemented!(),
455455
"prefetch_write_instruction" => unimplemented!(),
456-
"try" => unimplemented!(),
457456
"unaligned_volatile_store" => unimplemented!(),
458457
"va_arg" => unimplemented!(),
459458
"va_copy" => unimplemented!(),

Diff for: compiler/rustc_codegen_llvm/src/gotoc/mod.rs

-6
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,8 @@ impl<'tcx> GotocCtx<'tcx> {
105105
x if x.ends_with("__getit") => true,
106106
// https://github.com/model-checking/rmc/issues/205
107107
"panic::Location::<'a>::caller" => true,
108-
// https://github.com/model-checking/rmc/issues/206
109-
"core::sync::atomic::atomic_swap" => true,
110108
// https://github.com/model-checking/rmc/issues/207
111109
"core::slice::<impl [T]>::split_first" => true,
112-
// https://github.com/model-checking/rmc/issues/208
113-
"panicking::take_hook" => true,
114-
// https://github.com/model-checking/rmc/issues/209
115-
"panicking::r#try" => true,
116110
_ => false,
117111
}
118112
}

Diff for: compiler/rustc_codegen_llvm/src/gotoc/operand.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,16 @@ impl<'tcx> GotocCtx<'tcx> {
2828
Operand::Copy(d) | Operand::Move(d) =>
2929
// TODO: move shouldn't be the same as copy
3030
{
31-
self.codegen_place(d).goto_expr
31+
let projection = self.codegen_place(d);
32+
// If the operand itself is a Dynamic (like when passing a boxed closure),
33+
// we need to pull off the fat pointer. In that case, the rustc kind() on
34+
// both the operand and the inner type are Dynamic.
35+
// Consider moving this check elsewhere in:
36+
// https://github.com/model-checking/rmc/issues/277
37+
match self.operand_ty(o).kind() {
38+
ty::Dynamic(..) => projection.fat_ptr_goto_expr.unwrap(),
39+
_ => projection.goto_expr,
40+
}
3241
}
3342
Operand::Constant(c) => self.codegen_constant(&c),
3443
}

Diff for: compiler/rustc_codegen_llvm/src/gotoc/place.rs

+18-8
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_middle::{
1313
ty::{self, Ty, TyS, VariantDef},
1414
};
1515
use rustc_target::abi::{LayoutOf, TagEncoding, Variants};
16-
use tracing::debug;
16+
use tracing::{debug, warn};
1717

1818
/// A projection in RMC can either be to a type (the normal case),
1919
/// or a variant in the case of a downcast.
@@ -63,7 +63,7 @@ impl<'tcx> ProjectedPlace<'tcx> {
6363

6464
/// Constructor
6565
impl<'tcx> ProjectedPlace<'tcx> {
66-
fn _check_expr_typ(expr: &Expr, typ: &TypeOrVariant<'tcx>, ctx: &mut GotocCtx<'tcx>) -> bool {
66+
fn check_expr_typ(expr: &Expr, typ: &TypeOrVariant<'tcx>, ctx: &mut GotocCtx<'tcx>) -> bool {
6767
match typ {
6868
TypeOrVariant::Type(t) => &ctx.codegen_ty(t) == expr.typ(),
6969
TypeOrVariant::Variant(_) => true, //TODO, what to do here?
@@ -102,12 +102,14 @@ impl<'tcx> ProjectedPlace<'tcx> {
102102
}
103103
// TODO: these assertions fail on a few regressions. Figure out why.
104104
// I think it may have to do with boxed fat pointers.
105-
// assert!(
106-
// Self::check_expr_typ(&goto_expr, &mir_typ_or_variant, ctx),
107-
// "\n{:?}\n{:?}",
108-
// &goto_expr,
109-
// &mir_typ_or_variant
110-
// );
105+
// https://github.com/model-checking/rmc/issues/277
106+
if !Self::check_expr_typ(&goto_expr, &mir_typ_or_variant, ctx) {
107+
warn!(
108+
"Unexpected type mismatch in projection: \n{:?}\n{:?}",
109+
&goto_expr, &mir_typ_or_variant
110+
);
111+
};
112+
111113
assert!(
112114
Self::check_fat_ptr_typ(&fat_ptr_goto_expr, &fat_ptr_mir_typ, ctx),
113115
"\n{:?}\n{:?}",
@@ -252,6 +254,14 @@ impl<'tcx> GotocCtx<'tcx> {
252254
before.fat_ptr_goto_expr
253255
};
254256

257+
// Check that we have a valid trait or slice fat pointer
258+
if let Some(fat_ptr) = fat_ptr_goto_expr.clone() {
259+
assert!(
260+
fat_ptr.typ().is_rust_trait_fat_ptr(&self.symbol_table)
261+
|| fat_ptr.typ().is_rust_slice_fat_ptr(&self.symbol_table)
262+
);
263+
};
264+
255265
let inner_mir_typ = inner_mir_typ_and_mut.ty;
256266
let expr = match inner_mir_typ.kind() {
257267
ty::Slice(_) | ty::Str | ty::Dynamic(..) => {

Diff for: compiler/rustc_codegen_llvm/src/gotoc/statement.rs

+80-57
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use super::cbmc::goto_program::{BuiltinFn, Expr, Location, Stmt, Type};
44
use super::metadata::*;
55
use super::typ::FN_RETURN_VOID_VAR_NAME;
6+
use rustc_hir::def_id::DefId;
67
use rustc_middle::mir;
78
use rustc_middle::mir::{
89
BasicBlock, Operand, Place, Statement, StatementKind, SwitchTargets, Terminator, TerminatorKind,
@@ -276,79 +277,101 @@ impl<'tcx> GotocCtx<'tcx> {
276277

277278
let (p, target) = destination.unwrap();
278279

279-
if let ty::InstanceDef::DropGlue(_, None) = instance.def {
280-
// here an empty drop glue is invoked. we just ignore it.
281-
return Stmt::goto(self.current_fn().find_label(&target), Location::none());
282-
}
283-
284-
// Handle the case of a virtual function call via vtable lookup.
285-
let mut stmts = if let InstanceDef::Virtual(def_id, size) = instance.def {
286-
debug!(
287-
"Codegen a call through a virtual function. def_id: {:?} size: {:?}",
288-
def_id, size
289-
);
290-
291-
// The first argument to a virtual function is a fat pointer for the trait
292-
let trait_fat_ptr = fargs[0].to_owned();
293-
let vtable_field_name = self.vtable_field_name(def_id);
294-
295-
// Now that we have all the stuff we need, we can actually build the dynamic call
296-
// If the original call was of the form
297-
// f(arg0, arg1);
298-
// The new call should be of the form
299-
// arg0.vtable->f(arg0.data,arg1);
300-
let vtable_ref = trait_fat_ptr.to_owned().member("vtable", &self.symbol_table);
301-
let vtable = vtable_ref.dereference();
302-
let fn_ptr = vtable.member(&vtable_field_name, &self.symbol_table);
303-
304-
// Update the argument from arg0 to arg0.data
305-
fargs[0] = trait_fat_ptr.to_owned().member("data", &self.symbol_table);
280+
let mut stmts: Vec<Stmt> = match instance.def {
281+
// Here an empty drop glue is invoked; we just ignore it.
282+
InstanceDef::DropGlue(_, None) => {
283+
return Stmt::goto(self.current_fn().find_label(&target), Location::none());
284+
}
285+
// Handle a virtual function call via a vtable lookup
286+
InstanceDef::Virtual(def_id, _) => {
287+
// We must have at least one argument, and the first one
288+
// should be a fat pointer for the trait
289+
let trait_fat_ptr = fargs[0].to_owned();
306290

307-
// For soundness, add an assertion that the vtable function call is not null.
308-
// Otherwise, CBMC might treat this as an assert(0) and later user-added assertions
309-
// could be vacuously true.
310-
let call_is_nonnull = fn_ptr.clone().is_nonnull();
311-
let assert_msg =
312-
format!("Non-null virtual function call for {:?}", vtable_field_name);
313-
let assert_nonnull = Stmt::assert(call_is_nonnull, &assert_msg, loc.clone());
291+
//Check the Gotoc-level fat pointer type
292+
assert!(trait_fat_ptr.typ().is_rust_trait_fat_ptr(&self.symbol_table));
314293

315-
// Virtual function call and corresponding nonnull assertion.
316-
let func_exp: Expr = fn_ptr.dereference();
317-
vec![
318-
assert_nonnull,
319-
self.codegen_expr_to_place(&p, func_exp.call(fargs))
320-
.with_location(loc.clone()),
321-
]
322-
} else {
323-
// Non-virtual function call.
324-
let func_exp = self.codegen_operand(func);
325-
vec![
326-
self.codegen_expr_to_place(&p, func_exp.call(fargs))
327-
.with_location(loc.clone()),
328-
]
294+
self.codegen_virtual_funcall(
295+
trait_fat_ptr,
296+
def_id,
297+
&p,
298+
&mut fargs,
299+
loc.clone(),
300+
)
301+
}
302+
// Normal, non-virtual function calls
303+
InstanceDef::Item(..)
304+
| InstanceDef::DropGlue(_, Some(_))
305+
| InstanceDef::Intrinsic(..)
306+
| InstanceDef::FnPtrShim(.., _)
307+
| InstanceDef::VtableShim(..)
308+
| InstanceDef::ReifyShim(..)
309+
| InstanceDef::ClosureOnceShim { call_once: _ }
310+
| InstanceDef::CloneShim(..) => {
311+
let func_exp = self.codegen_operand(func);
312+
vec![
313+
self.codegen_expr_to_place(&p, func_exp.call(fargs))
314+
.with_location(loc.clone()),
315+
]
316+
}
329317
};
330-
331-
// Add return jump.
332318
stmts.push(Stmt::goto(self.current_fn().find_label(&target), loc.clone()));
333-
334-
// Produce the full function call block.
335-
Stmt::block(stmts, loc)
319+
return Stmt::block(stmts, loc);
336320
}
321+
// Function call through a pointer
337322
ty::FnPtr(_) => {
338323
let (p, target) = destination.unwrap();
339324
let func_expr = self.codegen_operand(func).dereference();
340-
// Actually generate the function call, and store the return value, if any.
341-
Stmt::block(
325+
// Actually generate the function call and return.
326+
return Stmt::block(
342327
vec![
343328
self.codegen_expr_to_place(&p, func_expr.call(fargs))
344329
.with_location(loc.clone()),
345330
Stmt::goto(self.current_fn().find_label(&target), loc.clone()),
346331
],
347332
loc,
348-
)
333+
);
349334
}
350335
x => unreachable!("Function call where the function was of unexpected type: {:?}", x),
351-
}
336+
};
337+
}
338+
339+
fn codegen_virtual_funcall(
340+
&mut self,
341+
trait_fat_ptr: Expr,
342+
def_id: DefId,
343+
place: &Place<'tcx>,
344+
fargs: &mut Vec<Expr>,
345+
loc: Location,
346+
) -> Vec<Stmt> {
347+
let vtable_field_name = self.vtable_field_name(def_id);
348+
349+
// Now that we have all the stuff we need, we can actually build the dynamic call
350+
// If the original call was of the form
351+
// f(arg0, arg1);
352+
// The new call should be of the form
353+
// arg0.vtable->f(arg0.data,arg1);
354+
let vtable_ref = trait_fat_ptr.to_owned().member("vtable", &self.symbol_table);
355+
let vtable = vtable_ref.dereference();
356+
let fn_ptr = vtable.member(&vtable_field_name, &self.symbol_table);
357+
358+
// Update the argument from arg0 to arg0.data
359+
fargs[0] = trait_fat_ptr.to_owned().member("data", &self.symbol_table);
360+
361+
// For soundness, add an assertion that the vtable function call is not null.
362+
// Otherwise, CBMC might treat this as an assert(0) and later user-added assertions
363+
// could be vacuously true.
364+
let call_is_nonnull = fn_ptr.clone().is_nonnull();
365+
let assert_msg = format!("Non-null virtual function call for {:?}", vtable_field_name);
366+
let assert_nonnull = Stmt::assert(call_is_nonnull, &assert_msg, loc.clone());
367+
368+
// Virtual function call and corresponding nonnull assertion.
369+
let func_exp: Expr = fn_ptr.dereference();
370+
vec![
371+
assert_nonnull,
372+
self.codegen_expr_to_place(place, func_exp.call(fargs.to_vec()))
373+
.with_location(loc.clone()),
374+
]
352375
}
353376

354377
/// A place is similar to the C idea of a LHS. For example, the returned value of a function call is stored to a place.

Diff for: compiler/rustc_codegen_llvm/src/gotoc/typ.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -970,12 +970,11 @@ impl<'tcx> GotocCtx<'tcx> {
970970
}
971971
}
972972

973-
/// whether a variable of type ty should be ignored
973+
/// Whether a variable of type ty should be ignored as a parameter to a function
974974
pub fn ignore_var_ty(&self, ty: Ty<'tcx>) -> bool {
975975
match ty.kind() {
976976
ty::Tuple(substs) if substs.is_empty() => true,
977977
ty::FnDef(_, _) => true,
978-
ty::Dynamic(_, _) => true, //DSN understand why
979978
_ => false,
980979
}
981980
}

Diff for: src/test/cbmc/DynTrait/boxed_closure.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Check that we can codegen a boxed dyn closure
5+
6+
fn main() {
7+
// Create a boxed once-callable closure
8+
let f: Box<dyn FnOnce(i32)> = Box::new(|x| assert!(x == 1));
9+
10+
// Call it
11+
f(1);
12+
}

Diff for: src/test/cbmc/DynTrait/boxed_closure_fail_fixme.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Check that we can codegen a boxed dyn closure and fail an inner assertion
5+
6+
// This current verifies "successfully" because the closure is not actually
7+
// called in the resulting CotoC code.
8+
// https://github.com/model-checking/rmc/issues/240
9+
10+
fn main() {
11+
// Create a boxed once-callable closure
12+
let f: Box<dyn FnOnce()> = Box::new(|x| {
13+
__VERIFIER_expect_fail(x == 2, "Wrong int");
14+
});
15+
16+
// Call it
17+
f(1);
18+
}

Diff for: src/test/cbmc/DynTrait/dyn_fn_param_closure_capture_fail.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ fn takes_dyn_fun(fun: &dyn Fn() -> i32) {
1313
let x = fun();
1414
__VERIFIER_expect_fail(x != 5, "Wrong return");
1515
/* The closure captures `a` and thus is sized */
16-
__VERIFIER_expect_fail(size_from_vtable(vtable!(fun)) == 8, "Wrong size");
16+
__VERIFIER_expect_fail(size_from_vtable(vtable!(fun)) != 8, "Wrong size");
1717
}
1818

1919
fn main() {

0 commit comments

Comments
 (0)