Skip to content

Commit

Permalink
Fix a case of using the wrong stack map during gcs
Browse files Browse the repository at this point in the history
This commit fixes an issue where when looking up the stack map for a pc
within a function we might end up reading the *previous* function's
stack maps. This then later caused asserts to trip because we started
interpreting random data as a `VMExternRef` when it wasn't. The fix was
to add `None` markers for "this range has no stack map" in the function
ranges map.

Closes #2386
  • Loading branch information
alexcrichton committed Nov 11, 2020
1 parent 997b654 commit af72028
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 4 deletions.
18 changes: 14 additions & 4 deletions crates/runtime/src/externref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -779,8 +779,9 @@ struct ModuleStackMaps {
range: std::ops::Range<usize>,

/// A map from a PC in this module (that is a GC safepoint) to its
/// associated stack map.
pc_to_stack_map: Vec<(usize, Rc<StackMap>)>,
/// associated stack map. If `None` then it means that the PC is the start
/// of a range which has no stack map.
pc_to_stack_map: Vec<(usize, Option<Rc<StackMap>>)>,
}

impl StackMapRegistry {
Expand All @@ -804,11 +805,20 @@ impl StackMapRegistry {
min = std::cmp::min(min, range.start);
max = std::cmp::max(max, range.end);

// Add a marker between functions indicating that this function's pc
// starts with no stack map so when our binary search later on finds
// a pc between the start of the function and the function's first
// stack map it doesn't think the previous stack map is our stack
// map.
if pc_to_stack_map.len() > 0 {
pc_to_stack_map.push((range.start, None));
}

for info in infos {
assert!((info.code_offset as usize) < len);
pc_to_stack_map.push((
range.start + (info.code_offset as usize),
Rc::new(info.stack_map.clone()),
Some(Rc::new(info.stack_map.clone())),
));
}
}
Expand Down Expand Up @@ -911,7 +921,7 @@ impl StackMapRegistry {
Err(n) => n - 1,
};

let stack_map = stack_maps.pc_to_stack_map[index].1.clone();
let stack_map = stack_maps.pc_to_stack_map[index].1.as_ref()?.clone();
Some(stack_map)
}
}
Expand Down
49 changes: 49 additions & 0 deletions tests/misc_testsuite/reference-types/no-mixup-stack-maps.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
(module
(global $g (mut externref) (ref.null extern))

;; This function will have a stack map, notably one that's a bit
;; different than the one below.
(func $has_a_stack_map
(local externref)
global.get $g
local.tee 0
global.set $g

local.get 0
global.set $g
ref.null extern
global.set $g
)

;; This function also has a stack map, but it's only applicable after
;; the call to the `$gc` import, so when we gc during that we shouldn't
;; accidentally read the previous function's stack maps and use that
;; for our own.
(func (export "run") (result i32)
call $gc

ref.null extern
global.set $g
i32.const 0
)

(func (export "init") (param externref)
local.get 0
global.set $g
)

;; A small function which when run triggers a gc in wasmtime
(func $gc
(local $i i32)
i32.const 10000
local.set $i
(loop $continue
(global.set $g (global.get $g))
(local.tee $i (i32.sub (local.get $i) (i32.const 1)))
br_if $continue
)
)
)

(invoke "init" (ref.extern 1))
(assert_return (invoke "run") (i32.const 0))

0 comments on commit af72028

Please sign in to comment.