Skip to content

Commit 0ba1c2e

Browse files
avanhatttedinski
authored andcommitted
Handle copy on empty str/length zero data (rust-lang#235)
For consistency with LLVM semantics, only codegen an implicit memcpy when the length is nonzero.
1 parent 3d0d12a commit 0ba1c2e

File tree

6 files changed

+118
-4
lines changed

6 files changed

+118
-4
lines changed

compiler/rustc_codegen_llvm/src/gotoc/cbmc/goto_program/expr.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,13 @@ impl Expr {
11051105
self.lt(typ.zero())
11061106
}
11071107

1108+
/// `self == 0`
1109+
pub fn is_zero(self) -> Self {
1110+
assert!(self.typ.is_numeric());
1111+
let typ = self.typ.clone();
1112+
self.eq(typ.zero())
1113+
}
1114+
11081115
/// `self != NULL`
11091116
pub fn is_nonnull(self) -> Self {
11101117
assert!(self.typ.is_pointer());

compiler/rustc_codegen_llvm/src/gotoc/intrinsic.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,16 @@ impl<'tcx> GotocCtx<'tcx> {
6161
}
6262
};
6363
let n = sz.mul(count);
64-
let e = BuiltinFn::$f.call(vec![dst, src, n], loc);
65-
self.codegen_expr_to_place(p, e)
64+
let call_memcopy = BuiltinFn::$f.call(vec![dst.clone(), src, n.clone()], loc);
65+
66+
// The C implementation of memcpy does not allow an invalid pointer for
67+
// the src/dst, but the LLVM implementation specifies that a copy with
68+
// length zero is a no-op. This comes up specifically when handling
69+
// the empty string; CBMC will fail on passing a reference to empty
70+
// string unless we codegen this zero check.
71+
// https://llvm.org/docs/LangRef.html#llvm-memcpy-intrinsic
72+
let copy_if_nontrivial = n.is_zero().ternary(dst, call_memcopy);
73+
self.codegen_expr_to_place(p, copy_if_nontrivial)
6674
}};
6775
}
6876

compiler/rustc_codegen_llvm/src/gotoc/statement.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -507,8 +507,20 @@ impl<'tcx> GotocCtx<'tcx> {
507507
let sz = Expr::int_constant(sz, Type::size_t());
508508
let n = sz.mul(count);
509509
let dst = dst.cast_to(Type::void_pointer());
510-
let e = BuiltinFn::Memcpy.call(vec![dst, src, n], Location::none());
511-
e.as_stmt(Location::none())
510+
let e = BuiltinFn::Memcpy.call(vec![dst, src, n.clone()], Location::none());
511+
512+
// The C implementation of memcpy does not allow an invalid pointer for
513+
// the src/dst, but the LLVM implementation specifies that a copy with
514+
// length zero is a no-op. This comes up specifically when handling
515+
// the empty string; CBMC will fail on passing a reference to empty
516+
// string unless we codegen this zero check.
517+
// https://llvm.org/docs/LangRef.html#llvm-memcpy-intrinsic
518+
Stmt::if_then_else(
519+
n.is_zero().not(),
520+
e.as_stmt(Location::none()),
521+
None,
522+
Location::none(),
523+
)
512524
}
513525
StatementKind::FakeRead(_)
514526
| StatementKind::Retag(_, _)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Make sure we can handle explicit copy_nonoverlapping on empty string
5+
6+
#![feature(rustc_private)]
7+
8+
extern crate libc;
9+
10+
use std::mem::size_of;
11+
use std::ptr::copy_nonoverlapping;
12+
use std::slice::from_raw_parts;
13+
use std::str;
14+
15+
fn copy_string(s: &str, l: usize) {
16+
unsafe {
17+
// Unsafe buffer
18+
let size: libc::size_t = size_of::<u8>();
19+
let dest: *mut u8 = libc::malloc(size * l) as *mut u8;
20+
21+
// Copy
22+
let src = from_raw_parts(s.as_ptr(), l).as_ptr();
23+
copy_nonoverlapping(src, dest, l);
24+
}
25+
}
26+
27+
fn main() {
28+
copy_string("x", 1);
29+
copy_string("", 0);
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Make sure we can handle explicit copy_nonoverlapping on empty string
5+
6+
// TODO: https://github.com./model-checking/rmc/issues/241
7+
// The copy_nonoverlapping succeeds, but the final copy back to a slice
8+
// fails:
9+
// [...copy_empty_string_by_intrinsic.assertion.2] line 1035 unreachable code: FAILURE
10+
// [...copy_empty_string_by_intrinsic.assertion.1] line 1037 a panicking function std::result::unwrap_failed is invoked: FAILURE
11+
// [...copy_string.assertion.2] line 28 assertion failed: dest_as_str.len() == l: FAILURE
12+
13+
#![feature(rustc_private)]
14+
15+
extern crate libc;
16+
17+
use std::mem::size_of;
18+
use std::ptr::copy_nonoverlapping;
19+
use std::slice::from_raw_parts;
20+
use std::str;
21+
22+
fn copy_string(s: &str, l: usize) {
23+
unsafe {
24+
// Unsafe buffer
25+
let size: libc::size_t = size_of::<u8>();
26+
let dest: *mut u8 = libc::malloc(size * l) as *mut u8;
27+
28+
// Copy
29+
let src = from_raw_parts(s.as_ptr(), l).as_ptr();
30+
copy_nonoverlapping(src, dest, l);
31+
32+
// The chunk below causes the 3 failures at the top of the file
33+
// Back to str, check length
34+
let dest_slice: &[u8] = from_raw_parts(dest, l);
35+
let dest_as_str: &str = str::from_utf8(dest_slice).unwrap();
36+
assert!(dest_as_str.len() == l);
37+
}
38+
}
39+
40+
fn main() {
41+
// Verification fails for both of these cases.
42+
copy_string("x", 1);
43+
copy_string("", 0);
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Make sure we can handle implicit memcpy on the empty string
5+
6+
fn take_string_ref(s: &str, l: usize) {
7+
assert!(s.len() == l)
8+
}
9+
10+
fn main() {
11+
take_string_ref(&"x".to_string(), 1);
12+
take_string_ref(&"".to_string(), 0);
13+
}

0 commit comments

Comments
 (0)