Skip to content

Commit c60512f

Browse files
committed
Warn against clippy::cast_possible_truncation in Wasmtime
This commit explicitly enables the `clippy::cast_possible_truncation` lint in Clippy for just the `wasmtime::runtime` module. This does not enable it for the entire workspace since it's a very noisy lint and in general has a low signal value. For the domain that `wasmtime::runtime` is working in, however, this is a much more useful lint. We in general want to be very careful about casting between `usize`, `u32`, and `u64` and the purpose of this module-targeted lint is to help with just that. I was inspired to do this after reading over bytecodealliance#9206 where especially when refactoring code and changing types I think it would be useful to have locations flagged as "truncation may happen here" which previously weren't truncating. The failure mode for this lint is that panics might be introduced where truncation is explicitly intended. Most of the time though this isn't actually desired so the more practical consequence of this lint is to probably slow down wasmtime ever so slightly and bloat it ever so slightly by having a few more checks in a few places. This is likely best addressed in a more comprehensive manner, however, rather than specifically for just this one case. This problem isn't unique to just casts, but to many other forms of `.unwrap()` for example.
1 parent 459378c commit c60512f

20 files changed

Lines changed: 118 additions & 53 deletions

File tree

crates/wasmtime/src/runtime.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,31 @@
1+
// Wasmtime's runtime has lots of fiddly bits where we're doing operations like
2+
// casting between wasm i32/i64 and host `usize` values. There's also in general
3+
// just lots of pieces of low-level manipulation of memory and internals of VM
4+
// runtime state. To help keep all the integer casts correct be a bit more
5+
// strict than the default settings to help weed out bugs ahead of time.
6+
//
7+
// This inevitably leads to wordier code than might otherwise be used because,
8+
// for example, `u64 as usize` is warned against and will be an error on CI.
9+
// This happens pretty frequently and needs to be replaced with `val.try_into()`
10+
// or `usize::try_from(val)` where the error is handled. In some cases the
11+
// correct thing to do is to `.unwrap()` the error to indicate a fatal mistake,
12+
// but in some cases the correct thing is to propagate the error.
13+
//
14+
// Some niche cases that explicitly want truncation are recommended to have a
15+
// function along the lines of
16+
//
17+
// #[allow(clippy::cast_possible_truncation)]
18+
// fn truncate_i32_to_i8(a: i32) -> i8 { a as i8 }
19+
//
20+
// as this explicitly indicates the intent of truncation is desired. Other
21+
// locations should use fallible conversions.
22+
//
23+
// If performance is absolutely critical then it's recommended to use `#[allow]`
24+
// with a comment indicating why performance is critical as well as a short
25+
// explanation of why truncation shouldn't be happening at runtime. This
26+
// situation should be pretty rare though.
27+
#![warn(clippy::cast_possible_truncation)]
28+
129
#[macro_use]
230
pub(crate) mod func;
331

crates/wasmtime/src/runtime/component/func/typed.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -837,7 +837,7 @@ macro_rules! integers {
837837

838838
unsafe impl Lift for $primitive {
839839
#[inline]
840-
#[allow(trivial_numeric_casts)]
840+
#[allow(trivial_numeric_casts, clippy::cast_possible_truncation)]
841841
fn lift(_cx: &mut LiftContext<'_>, ty: InterfaceType, src: &Self::Lower) -> Result<Self> {
842842
debug_assert!(matches!(ty, InterfaceType::$ty));
843843
Ok(src.$get() as $primitive)
@@ -1110,8 +1110,8 @@ unsafe impl Lower for str {
11101110
debug_assert!(offset % (Self::ALIGN32 as usize) == 0);
11111111
let (ptr, len) = lower_string(cx, self)?;
11121112
// FIXME: needs memory64 handling
1113-
*cx.get(offset + 0) = (ptr as i32).to_le_bytes();
1114-
*cx.get(offset + 4) = (len as i32).to_le_bytes();
1113+
*cx.get(offset + 0) = u32::try_from(ptr).unwrap().to_le_bytes();
1114+
*cx.get(offset + 4) = u32::try_from(len).unwrap().to_le_bytes();
11151115
Ok(())
11161116
}
11171117
}
@@ -1457,8 +1457,8 @@ where
14571457
};
14581458
debug_assert!(offset % (Self::ALIGN32 as usize) == 0);
14591459
let (ptr, len) = lower_list(cx, elem, self)?;
1460-
*cx.get(offset + 0) = (ptr as i32).to_le_bytes();
1461-
*cx.get(offset + 4) = (len as i32).to_le_bytes();
1460+
*cx.get(offset + 0) = u32::try_from(ptr).unwrap().to_le_bytes();
1461+
*cx.get(offset + 4) = u32::try_from(len).unwrap().to_le_bytes();
14621462
Ok(())
14631463
}
14641464
}

crates/wasmtime/src/runtime/component/resource_table.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ impl ResourceTable {
161161
fn push_(&mut self, e: TableEntry) -> Result<u32, ResourceTableError> {
162162
if let Some(free) = self.pop_free_list() {
163163
self.entries[free] = Entry::Occupied { entry: e };
164-
Ok(free as u32)
164+
Ok(free.try_into().unwrap())
165165
} else {
166166
let ix = self
167167
.entries

crates/wasmtime/src/runtime/component/values.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,11 @@ impl Val {
187187
let u32_count = cx.types.canonical_abi(&ty).flat_count(usize::MAX).unwrap();
188188
let ty = &cx.types[i];
189189
let mut flags = Vec::new();
190-
for i in 0..u32_count {
190+
for i in 0..u32::try_from(u32_count).unwrap() {
191191
push_flags(
192192
ty,
193193
&mut flags,
194-
(i as u32) * 32,
194+
i * 32,
195195
u32::lift(cx, InterfaceType::U32, next(src))?,
196196
);
197197
}
@@ -480,8 +480,8 @@ impl Val {
480480
let ty = &cx.types[ty];
481481
let (ptr, len) = lower_list(cx, ty.element, values)?;
482482
// FIXME: needs memory64 handling
483-
*cx.get(offset + 0) = (ptr as i32).to_le_bytes();
484-
*cx.get(offset + 4) = (len as i32).to_le_bytes();
483+
*cx.get(offset + 0) = u32::try_from(ptr).unwrap().to_le_bytes();
484+
*cx.get(offset + 4) = u32::try_from(len).unwrap().to_le_bytes();
485485
Ok(())
486486
}
487487
(InterfaceType::List(_), _) => unexpected(ty, self),
@@ -947,7 +947,7 @@ fn get_enum_discriminant(ty: &TypeEnum, n: &str) -> Result<u32> {
947947
ty.names
948948
.get_index_of(n)
949949
.ok_or_else(|| anyhow::anyhow!("enum variant name `{n}` is not valid"))
950-
.map(|i| i as u32)
950+
.map(|i| i.try_into().unwrap())
951951
}
952952

953953
fn get_variant_discriminant<'a>(
@@ -958,7 +958,7 @@ fn get_variant_discriminant<'a>(
958958
.cases
959959
.get_full(name)
960960
.ok_or_else(|| anyhow::anyhow!("unknown variant case: `{name}`"))?;
961-
Ok((i as u32, ty))
961+
Ok((i.try_into().unwrap(), ty))
962962
}
963963

964964
fn next<'a>(src: &mut Iter<'a, ValRaw>) -> &'a ValRaw {

crates/wasmtime/src/runtime/coredump.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,16 +137,18 @@ impl WasmCoreDump {
137137
// to balance these conflicting desires, we break the memory up
138138
// into reasonably-sized chunks and then trim runs of zeroes
139139
// from the start and end of each chunk.
140-
const CHUNK_SIZE: u32 = 4096;
141-
for (i, chunk) in mem
142-
.data(&store)
143-
.chunks_exact(CHUNK_SIZE as usize)
144-
.enumerate()
145-
{
140+
const CHUNK_SIZE: usize = 4096;
141+
for (i, chunk) in mem.data(&store).chunks_exact(CHUNK_SIZE).enumerate() {
146142
if let Some(start) = chunk.iter().position(|byte| *byte != 0) {
147143
let end = chunk.iter().rposition(|byte| *byte != 0).unwrap() + 1;
148-
let offset = (i as u32) * CHUNK_SIZE + (start as u32);
149-
let offset = wasm_encoder::ConstExpr::i32_const(offset as i32);
144+
let offset = i * CHUNK_SIZE + start;
145+
let offset = if ty.is_64() {
146+
let offset = u64::try_from(offset).unwrap();
147+
wasm_encoder::ConstExpr::i64_const(offset as i64)
148+
} else {
149+
let offset = u32::try_from(offset).unwrap();
150+
wasm_encoder::ConstExpr::i32_const(offset as i32)
151+
};
150152
data.active(memory_idx, &offset, chunk[start..end].iter().copied());
151153
}
152154
}

crates/wasmtime/src/runtime/debug.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,11 @@ fn relocate_dwarf_sections(bytes: &mut [u8], code_region: (*const u8, usize)) ->
6363
}
6464

6565
for (offset, value) in relocations {
66-
let (loc, _) = object::from_bytes_mut::<U64Bytes<NE>>(&mut bytes[offset as usize..])
67-
.map_err(|()| anyhow!("invalid dwarf relocations"))?;
66+
let (loc, _) = offset
67+
.try_into()
68+
.ok()
69+
.and_then(|offset| object::from_bytes_mut::<U64Bytes<NE>>(&mut bytes[offset..]).ok())
70+
.ok_or_else(|| anyhow!("invalid dwarf relocations"))?;
6871
loc.set(NE, value);
6972
}
7073
Ok(())
@@ -126,7 +129,8 @@ fn convert_object_elf_to_loadable_file<E: Endian>(
126129
let text_range = match sections.section_by_name(e, b".text") {
127130
Some((i, text)) => {
128131
let range = text.file_range(e);
129-
let off = header.e_shoff.get(e) as usize + i.0 * header.e_shentsize.get(e) as usize;
132+
let e_shoff = usize::try_from(header.e_shoff.get(e)).unwrap();
133+
let off = e_shoff + i.0 * header.e_shentsize.get(e) as usize;
130134

131135
let section: &mut SectionHeader64<E> =
132136
object::from_bytes_mut(&mut bytes[off..]).unwrap().0;
@@ -160,6 +164,8 @@ fn convert_object_elf_to_loadable_file<E: Endian>(
160164
let header: &mut FileHeader64<E> = object::from_bytes_mut(bytes).unwrap().0;
161165
header.e_type.set(e, ET_DYN);
162166
header.e_phoff.set(e, ph_off as u64);
163-
header.e_phentsize.set(e, e_phentsize as u16);
164-
header.e_phnum.set(e, e_phnum as u16);
167+
header
168+
.e_phentsize
169+
.set(e, u16::try_from(e_phentsize).unwrap());
170+
header.e_phnum.set(e, u16::try_from(e_phnum).unwrap());
165171
}

crates/wasmtime/src/runtime/instantiate.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,12 @@ impl CompiledModule {
274274
.meta
275275
.dwarf
276276
.binary_search_by_key(&(id as u8), |(id, _)| *id)
277-
.map(|i| {
277+
.ok()
278+
.and_then(|i| {
278279
let (_, range) = &self.meta.dwarf[i];
279-
&self.code_memory().dwarf()[range.start as usize..range.end as usize]
280+
let start = range.start.try_into().ok()?;
281+
let end = range.end.try_into().ok()?;
282+
self.code_memory().dwarf().get(start..end)
280283
})
281284
.unwrap_or(&[]);
282285
Ok(EndianSlice::new(data, gimli::LittleEndian))

crates/wasmtime/src/runtime/trap.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ use crate::prelude::*;
44
use crate::store::StoreOpaque;
55
use crate::{AsContext, Module};
66
use core::fmt;
7-
use wasmtime_environ::{
8-
demangle_function_name, demangle_function_name_or_index, EntityRef, FilePos,
9-
};
7+
use wasmtime_environ::{demangle_function_name, demangle_function_name_or_index, FilePos};
108

119
/// Representation of a WebAssembly trap and what caused it to occur.
1210
///
@@ -451,7 +449,7 @@ impl FrameInfo {
451449
text_offset,
452450
);
453451
let index = compiled_module.module().func_index(index);
454-
let func_index = index.index() as u32;
452+
let func_index = index.as_u32();
455453
let func_name = compiled_module.func_name(index).map(|s| s.to_string());
456454

457455
// In debug mode for now assert that we found a mapping for `pc` within

crates/wasmtime/src/runtime/vm/component.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ use sptr::Strict;
2323
use wasmtime_environ::component::*;
2424
use wasmtime_environ::{HostPtr, PrimaryMap, VMSharedTypeIndex};
2525

26+
#[allow(clippy::cast_possible_truncation)] // it's intended this is truncated on
27+
// 32-bit platforms
2628
const INVALID_PTR: usize = 0xdead_dead_beef_beef_u64 as usize;
2729

2830
mod libcalls;

crates/wasmtime/src/runtime/vm/cow.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,13 @@ impl MemoryImage {
7272
data: &[u8],
7373
mmap: Option<&MmapVec>,
7474
) -> Result<Option<MemoryImage>> {
75+
let assert_page_aligned = |val: usize| {
76+
assert_eq!(val % (page_size as usize), 0);
77+
};
7578
// Sanity-check that various parameters are page-aligned.
7679
let len = data.len();
7780
assert_eq!(offset % u64::from(page_size), 0);
78-
assert_eq!((len as u32) % page_size, 0);
81+
assert_page_aligned(len);
7982
let linear_memory_offset = match usize::try_from(offset) {
8083
Ok(offset) => offset,
8184
Err(_) => return Ok(None),
@@ -101,10 +104,10 @@ impl MemoryImage {
101104
let data_start = data.as_ptr() as usize;
102105
let data_end = data_start + data.len();
103106
assert!(start <= data_start && data_end <= end);
104-
assert_eq!((start as u32) % page_size, 0);
105-
assert_eq!((data_start as u32) % page_size, 0);
106-
assert_eq!((data_end as u32) % page_size, 0);
107-
assert_eq!((mmap.original_offset() as u32) % page_size, 0);
107+
assert_page_aligned(start);
108+
assert_page_aligned(data_start);
109+
assert_page_aligned(data_end);
110+
assert_page_aligned(mmap.original_offset());
108111

109112
#[cfg(feature = "std")]
110113
if let Some(file) = mmap.original_file() {
@@ -167,7 +170,8 @@ impl ModuleMemoryImages {
167170
_ => return Ok(None),
168171
};
169172
let mut memories = PrimaryMap::with_capacity(map.len());
170-
let page_size = crate::runtime::vm::host_page_size() as u32;
173+
let page_size = crate::runtime::vm::host_page_size();
174+
let page_size = u32::try_from(page_size).unwrap();
171175
for (memory_index, init) in map {
172176
// mmap-based-initialization only works for defined memories with a
173177
// known starting point of all zeros, so bail out if the mmeory is

0 commit comments

Comments
 (0)