Skip to content

Commit 8001582

Browse files
authored
transpile: fix out-of-range literals (#1442)
* Fixes #1435. * Supersedes #1436. This first reverts the part of 7eaa39f that broke #1435, which is omitting casts from literals where the type already matches without checking if the literal is within that type's range. Then tests are added and then `fn literal_kind_matches_ty` is fixed to also check the type's range. If the literal may be out of range on any possible platform, then a cast remains. I didn't add a float to these tests since floats go through a different code path (there's no cast generated for them). So for them, we need to explicitly generate a cast if it's out of range.
2 parents df2af7f + bc89539 commit 8001582

File tree

9 files changed

+252
-16
lines changed

9 files changed

+252
-16
lines changed

c2rust-transpile/src/c_ast/mod.rs

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2183,6 +2183,190 @@ impl CTypeKind {
21832183
_ => unimplemented!("Printer::print_type({:?})", self),
21842184
}
21852185
}
2186+
2187+
/// Whether `value` is guaranteed to be in this integer type's range.
2188+
/// Thus, the narrowest possible range is used.
2189+
///
2190+
/// For example, for [`Self::Long`], [`i32`]'s range is used,
2191+
/// as on Linux and macOS (LP64), it's an [`i64`],
2192+
/// but on Windows (LLP64), it's only an [`i32`].
2193+
///
2194+
/// This should only be called on integer types.
2195+
/// Other types will return `false`.
2196+
pub fn guaranteed_integer_in_range(&self, value: u64) -> bool {
2197+
fn in_range<T: TryFrom<u64>>(value: u64) -> bool {
2198+
T::try_from(value).is_ok()
2199+
}
2200+
2201+
use CTypeKind::*;
2202+
match *self {
2203+
Void => false,
2204+
2205+
// Kind of an integer type, but would definitely need an explicit cast.
2206+
Bool => false,
2207+
2208+
// Can be signed or unsigned, so choose the minimum range of each.
2209+
Char => (u8::MIN as u64..=i8::MAX as u64).contains(&value),
2210+
WChar => in_range::<i32>(value),
2211+
2212+
// `int` is at least `i16` and `long` is at least `i32`.
2213+
SChar => in_range::<i8>(value),
2214+
Short => in_range::<i16>(value),
2215+
Int => in_range::<i16>(value),
2216+
Long => in_range::<i32>(value),
2217+
LongLong => in_range::<i64>(value),
2218+
2219+
// `unsigned int` is at least `u16` and `unsigned long` is at least `u32`.
2220+
UChar => in_range::<u8>(value),
2221+
UShort => in_range::<u16>(value),
2222+
UInt => in_range::<u16>(value),
2223+
ULong => in_range::<u32>(value),
2224+
ULongLong => in_range::<u64>(value),
2225+
2226+
Int8 => in_range::<i8>(value),
2227+
Int16 => in_range::<i16>(value),
2228+
Int32 => in_range::<i32>(value),
2229+
Int64 => in_range::<i64>(value),
2230+
Int128 => in_range::<i128>(value),
2231+
2232+
UInt8 => in_range::<u8>(value),
2233+
UInt16 => in_range::<u16>(value),
2234+
UInt32 => in_range::<u32>(value),
2235+
UInt64 => in_range::<u64>(value),
2236+
UInt128 => in_range::<u128>(value),
2237+
2238+
// There's no guarantee on pointer size, but `NULL` should work.
2239+
IntPtr => value == 0,
2240+
UIntPtr => value == 0,
2241+
2242+
IntMax => in_range::<i64>(value),
2243+
UIntMax => in_range::<u64>(value),
2244+
2245+
// `size_t` is at least a `u16`, and similar for `ssize_t` and `ptrdiff_t`.
2246+
Size => in_range::<u16>(value),
2247+
SSize => in_range::<i16>(value),
2248+
PtrDiff => in_range::<i16>(value),
2249+
2250+
// Floats, see `Self::guaranteed_float_in_range`.
2251+
Float => false,
2252+
Double => false,
2253+
LongDouble => false,
2254+
Half => false,
2255+
BFloat16 => false,
2256+
Float128 => false,
2257+
2258+
// Non-scalars.
2259+
Complex(_) => false,
2260+
Pointer(_) => false,
2261+
Reference(_) => false,
2262+
ConstantArray(_, _) => false,
2263+
IncompleteArray(_) => false,
2264+
VariableArray(_, _) => false,
2265+
TypeOf(_) => false,
2266+
TypeOfExpr(_) => false,
2267+
Function(_, _, _, _, _) => false,
2268+
Typedef(_) => false,
2269+
Decayed(_) => false,
2270+
Elaborated(_) => false,
2271+
Paren(_) => false,
2272+
Struct(_) => false,
2273+
Union(_) => false,
2274+
Enum(_) => false,
2275+
BuiltinFn => false,
2276+
Attributed(_, _) => false,
2277+
BlockPointer(_) => false,
2278+
Vector(_, _) => false,
2279+
UnhandledSveType => false,
2280+
Atomic(_) => false,
2281+
}
2282+
}
2283+
2284+
/// See [`Self::guaranteed_integer_in_range`].
2285+
/// This is the same, but for floats.
2286+
///
2287+
/// This should only be called on float types.
2288+
/// Other types will return `false`.
2289+
pub fn guaranteed_float_in_range(&self, value: f64) -> bool {
2290+
fn in_range<T: TryFrom<f64>>(value: f64) -> bool {
2291+
T::try_from(value).is_ok()
2292+
}
2293+
2294+
use CTypeKind::*;
2295+
match *self {
2296+
// `f32: TryFrom<f64>` is not implemented.
2297+
// C `float`s are not guaranteed to be `f32`,
2298+
// but Rust (namely `libc`) doesn't support any platform where this isn't the case.
2299+
Float => value >= f32::MIN as f64 && value <= f32::MAX as f64,
2300+
2301+
// Similarly to `float`, `double` is not guaranteed to be `f64`,
2302+
// but `libc` doesn't support any platform where this isn't the case.
2303+
Double => in_range::<f64>(value),
2304+
2305+
// `long double` (not `f128`) is only guaranteed to be at least as precise as a `double`.
2306+
LongDouble => in_range::<f64>(value),
2307+
2308+
// All `f64`s are valid `f128`s.
2309+
Float128 => in_range::<f64>(value),
2310+
2311+
// TODO Would like to depend on `half`.
2312+
Half => todo!("f16 range"),
2313+
BFloat16 => todo!("bf16 range"),
2314+
2315+
Void => false,
2316+
Bool => false,
2317+
Char => false,
2318+
SChar => false,
2319+
Short => false,
2320+
Int => false,
2321+
Long => false,
2322+
LongLong => false,
2323+
UChar => false,
2324+
UShort => false,
2325+
UInt => false,
2326+
ULong => false,
2327+
ULongLong => false,
2328+
Int128 => false,
2329+
UInt128 => false,
2330+
Complex(_) => false,
2331+
Pointer(_) => false,
2332+
Reference(_) => false,
2333+
ConstantArray(_, _) => false,
2334+
IncompleteArray(_) => false,
2335+
VariableArray(_, _) => false,
2336+
TypeOf(_) => false,
2337+
TypeOfExpr(_) => false,
2338+
Function(_, _, _, _, _) => false,
2339+
Typedef(_) => false,
2340+
Decayed(_) => false,
2341+
Elaborated(_) => false,
2342+
Paren(_) => false,
2343+
Struct(_) => false,
2344+
Union(_) => false,
2345+
Enum(_) => false,
2346+
BuiltinFn => false,
2347+
Attributed(_, _) => false,
2348+
BlockPointer(_) => false,
2349+
Vector(_, _) => false,
2350+
UnhandledSveType => false,
2351+
Atomic(_) => false,
2352+
Int8 => false,
2353+
Int16 => false,
2354+
Int32 => false,
2355+
Int64 => false,
2356+
IntPtr => false,
2357+
UInt8 => false,
2358+
UInt16 => false,
2359+
UInt32 => false,
2360+
UInt64 => false,
2361+
UIntPtr => false,
2362+
IntMax => false,
2363+
UIntMax => false,
2364+
Size => false,
2365+
SSize => false,
2366+
PtrDiff => false,
2367+
WChar => false,
2368+
}
2369+
}
21862370
}
21872371

21882372
impl Display for CTypeKind {
@@ -2286,7 +2470,7 @@ impl CTypeKind {
22862470

22872471
pub fn is_floating_type(&self) -> bool {
22882472
use CTypeKind::*;
2289-
matches!(self, Float | Double | LongDouble)
2473+
matches!(self, Float | Double | LongDouble | Half | BFloat16)
22902474
}
22912475

22922476
pub fn as_underlying_decl(&self) -> Option<CDeclId> {

c2rust-transpile/src/translator/literals.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,18 @@ impl<'c> Translation<'c> {
7070
mk().cast_expr(value, target_ty)
7171
}
7272

73-
/// Return whether the literal can be directly translated as this type. This does not check
74-
/// that the literal fits into the type's range of values (as doing so is in general dependent
75-
/// on the target platform), just that it is the appropriate kind for the type.
76-
pub fn literal_kind_matches_ty(&self, lit: &CLiteral, ty: CQualTypeId) -> bool {
73+
/// Return whether the literal can be directly translated as this type.
74+
pub fn literal_matches_ty(&self, lit: &CLiteral, ty: CQualTypeId) -> bool {
7775
let ty_kind = &self.ast_context.resolve_type(ty.ctype).kind;
7876
match *lit {
79-
CLiteral::Integer(..) if ty_kind.is_integral_type() && !ty_kind.is_bool() => true,
77+
CLiteral::Integer(value, _) if ty_kind.is_integral_type() && !ty_kind.is_bool() => {
78+
ty_kind.guaranteed_integer_in_range(value)
79+
}
8080
// `convert_literal` always casts these to i32.
81-
CLiteral::Character(..) => matches!(ty_kind, CTypeKind::Int32),
82-
CLiteral::Floating(..) if ty_kind.is_floating_type() => true,
81+
CLiteral::Character(_value) => matches!(ty_kind, CTypeKind::Int32),
82+
CLiteral::Floating(value, _) if ty_kind.is_floating_type() => {
83+
ty_kind.guaranteed_float_in_range(value)
84+
}
8385
_ => false,
8486
}
8587
}

c2rust-transpile/src/translator/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3699,7 +3699,7 @@ impl<'c> Translation<'c> {
36993699
if let (Some(ty), CExprKind::Literal(_ty, lit)) =
37003700
(override_ty, &self.ast_context[expr].kind)
37013701
{
3702-
if self.literal_kind_matches_ty(lit, ty) {
3702+
if self.literal_matches_ty(lit, ty) {
37033703
return self.convert_expr(ctx, expr, override_ty);
37043704
}
37053705
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include <stdint.h>
2+
3+
void f() {
4+
int32_t a = 0x80000000U;
5+
int32_t b = 0x80000000;
6+
int32_t c = 0x8000000000000000;
7+
8+
int32_t d = (unsigned int)0x80000000U; // Test with explicit cast.
9+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
source: c2rust-transpile/tests/snapshots.rs
3+
expression: cat tests/snapshots/out_of_range_lit.rs
4+
input_file: c2rust-transpile/tests/snapshots/out_of_range_lit.c
5+
---
6+
#![allow(
7+
dead_code,
8+
non_camel_case_types,
9+
non_snake_case,
10+
non_upper_case_globals,
11+
unused_assignments,
12+
unused_mut
13+
)]
14+
pub type __int32_t = i32;
15+
pub type int32_t = __int32_t;
16+
#[no_mangle]
17+
pub unsafe extern "C" fn f() {
18+
let mut a: int32_t = 0x80000000 as ::core::ffi::c_uint as int32_t;
19+
let mut b: int32_t = 0x80000000 as ::core::ffi::c_uint as int32_t;
20+
let mut c: int32_t = 0x8000000000000000 as ::core::ffi::c_ulong as int32_t;
21+
let mut d: int32_t = 0x80000000 as ::core::ffi::c_uint as int32_t;
22+
}

c2rust-transpile/tests/snapshots/[email protected]

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
---
22
source: c2rust-transpile/tests/snapshots.rs
3-
assertion_line: 69
4-
expression: cat tests/snapshots/platform-specific/types.rs
5-
input_file: c2rust-transpile/tests/snapshots/platform-specific/types.c
3+
expression: cat tests/snapshots/os-specific/types.linux.rs
4+
input_file: c2rust-transpile/tests/snapshots/os-specific/types.c
65
---
76
#![allow(
87
dead_code,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
source: c2rust-transpile/tests/snapshots.rs
3+
expression: cat tests/snapshots/out_of_range_lit.rs
4+
input_file: c2rust-transpile/tests/snapshots/out_of_range_lit.c
5+
---
6+
#![allow(
7+
dead_code,
8+
non_camel_case_types,
9+
non_snake_case,
10+
non_upper_case_globals,
11+
unused_assignments,
12+
unused_mut
13+
)]
14+
pub type int32_t = i32;
15+
#[no_mangle]
16+
pub unsafe extern "C" fn f() {
17+
let mut a: int32_t = 0x80000000 as ::core::ffi::c_uint as int32_t;
18+
let mut b: int32_t = 0x80000000 as ::core::ffi::c_uint as int32_t;
19+
let mut c: int32_t = 0x8000000000000000 as ::core::ffi::c_ulong as int32_t;
20+
let mut d: int32_t = 0x80000000 as ::core::ffi::c_uint as int32_t;
21+
}

c2rust-transpile/tests/snapshots/[email protected]

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
---
22
source: c2rust-transpile/tests/snapshots.rs
3-
assertion_line: 69
4-
expression: cat tests/snapshots/platform-specific/types.rs
5-
input_file: c2rust-transpile/tests/snapshots/platform-specific/types.c
3+
expression: cat tests/snapshots/os-specific/types.macos.rs
4+
input_file: c2rust-transpile/tests/snapshots/os-specific/types.c
65
---
76
#![allow(
87
dead_code,

c2rust-transpile/tests/snapshots/[email protected]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ pub unsafe extern "C" fn use_local_value() -> ::core::ffi::c_int {
427427
}
428428
#[no_mangle]
429429
pub unsafe extern "C" fn use_portable_type(mut len: uintptr_t) -> bool {
430-
return len <= (UINTPTR_MAX as uintptr_t).wrapping_div(2 as uintptr_t);
430+
return len <= (UINTPTR_MAX as uintptr_t).wrapping_div(2 as ::core::ffi::c_int as uintptr_t);
431431
}
432432
#[no_mangle]
433433
pub unsafe extern "C" fn ntlm_v2_blob_len(mut ntlm: *mut ntlmdata) -> ::core::ffi::c_uint {

0 commit comments

Comments
 (0)