Skip to content

Commit da09374

Browse files
authored
Relax panics in async/futures to traps/errors (#12688)
* Relax panics in async/futures to traps/errors This commit is an admittance that I don't believe we're going to get to a point where we are confident enough in the fuzzing of component-model-async such that we could confidently say we're exercising the vast majority of possible panics. Development of component-model-async has shown a steady trickle of panics over the course of the development of the feature, and this trend has been persistent over time as well. An attempt was made in #12119 to add a fuzzer dedicated to async events but that didn't actually find anything in development and it has missed a number of panics present before and discovered after its introduction. Overall I do not know how to improve the fuzzer to the point that it would find pretty much all of the existing async-related panics over time. To help address this concern of the `concurrent.rs` implementation this commit goes through and replaces things like `unwrap()`, `assert!`, `panic!`, and `unreachable!` with an error-producing form. The benefit of this is that a bug in the implementation is less likely to result in a panic and instead just results in a non-spec-compliant trap. The downside of doing this though is that it can become unclear what errors are "first class traps", or expected to be guest reachable, and which are expected to be bugs in Wasmtime. To help address this I've performed a few refactorings here as well. * Some traps previously present as error strings are now promoted to using `Trap::Foo` instead. This has some refactoring of the Rust/C side as well to make it easier to define new variants. Tests were additionally added for any trap messages that weren't previously tested as being reachable. * A new `bail_bug!` macro was added (internally) for Wasmtime. This is coupled with a concrete `WasmtimeBug` error type (exported as `wasmtime::WasmtimeBug`). The intention is that `bail!` continues to be "here's a string and I'm a bit too lazy to make a concrete error" while `bail_bug!` indicates "this is a bug in wasmtime please report this if you see it". The rough vision is that if an error condition is reached, and the system is not broken in such a way that panicking is required, then `bail_bug!` can be used to indicate a bug in Wasmtime as opposed to panicking. This reduces the real-world impact of hitting these scenarios by downgrading a CVE-worthy `panic!` into a bug-worthy non-spec-compliant trap. Not all panics are able to be transitioned to this as some are load bearing from a safety perspective or similar (or indicate something equally broken), but the vast majority of cases are suitable for "return a trap, lock down the store, and let destructors take care of everything else". This change additionally has resulted in API changes for `FutureReader` and `StreamReader`. For example creation of these types now returns a `Result` for when the `ResourceTable` is full, for example, instead of panicking. * Fix CI build * Translate `WasmtimeBug` to panics in debug mode * Review comments * Refactor some stream methods for fewer panics
1 parent 550a07c commit da09374

29 files changed

Lines changed: 1475 additions & 960 deletions

File tree

crates/c-api/include/wasmtime/trap.h

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,90 +25,112 @@ typedef uint8_t wasmtime_trap_code_t;
2525
*/
2626
enum wasmtime_trap_code_enum {
2727
/// The current stack space was exhausted.
28-
WASMTIME_TRAP_CODE_STACK_OVERFLOW,
28+
WASMTIME_TRAP_CODE_STACK_OVERFLOW = 0,
2929
/// An out-of-bounds memory access.
30-
WASMTIME_TRAP_CODE_MEMORY_OUT_OF_BOUNDS,
30+
WASMTIME_TRAP_CODE_MEMORY_OUT_OF_BOUNDS = 1,
3131
/// A wasm atomic operation was presented with a not-naturally-aligned
3232
/// linear-memory address.
33-
WASMTIME_TRAP_CODE_HEAP_MISALIGNED,
33+
WASMTIME_TRAP_CODE_HEAP_MISALIGNED = 2,
3434
/// An out-of-bounds access to a table.
35-
WASMTIME_TRAP_CODE_TABLE_OUT_OF_BOUNDS,
35+
WASMTIME_TRAP_CODE_TABLE_OUT_OF_BOUNDS = 3,
3636
/// Indirect call to a null table entry.
37-
WASMTIME_TRAP_CODE_INDIRECT_CALL_TO_NULL,
37+
WASMTIME_TRAP_CODE_INDIRECT_CALL_TO_NULL = 4,
3838
/// Signature mismatch on indirect call.
39-
WASMTIME_TRAP_CODE_BAD_SIGNATURE,
39+
WASMTIME_TRAP_CODE_BAD_SIGNATURE = 5,
4040
/// An integer arithmetic operation caused an overflow.
41-
WASMTIME_TRAP_CODE_INTEGER_OVERFLOW,
41+
WASMTIME_TRAP_CODE_INTEGER_OVERFLOW = 6,
4242
/// An integer division by zero.
43-
WASMTIME_TRAP_CODE_INTEGER_DIVISION_BY_ZERO,
43+
WASMTIME_TRAP_CODE_INTEGER_DIVISION_BY_ZERO = 7,
4444
/// Failed float-to-int conversion.
45-
WASMTIME_TRAP_CODE_BAD_CONVERSION_TO_INTEGER,
45+
WASMTIME_TRAP_CODE_BAD_CONVERSION_TO_INTEGER = 8,
4646
/// Code that was supposed to have been unreachable was reached.
47-
WASMTIME_TRAP_CODE_UNREACHABLE_CODE_REACHED,
47+
WASMTIME_TRAP_CODE_UNREACHABLE_CODE_REACHED = 9,
4848
/// Execution has potentially run too long and may be interrupted.
49-
WASMTIME_TRAP_CODE_INTERRUPT,
49+
WASMTIME_TRAP_CODE_INTERRUPT = 10,
5050
/// Execution has run out of the configured fuel amount.
51-
WASMTIME_TRAP_CODE_OUT_OF_FUEL,
51+
WASMTIME_TRAP_CODE_OUT_OF_FUEL = 11,
5252
/// Used to indicate that a trap was raised by atomic wait operations on non
5353
/// shared memory.
54-
WASMTIME_TRAP_CODE_ATOMIC_WAIT_NON_SHARED_MEMORY,
54+
WASMTIME_TRAP_CODE_ATOMIC_WAIT_NON_SHARED_MEMORY = 12,
5555
/// Call to a null reference.
56-
WASMTIME_TRAP_CODE_NULL_REFERENCE,
56+
WASMTIME_TRAP_CODE_NULL_REFERENCE = 13,
5757
/// Attempt to access beyond the bounds of an array.
58-
WASMTIME_TRAP_CODE_ARRAY_OUT_OF_BOUNDS,
58+
WASMTIME_TRAP_CODE_ARRAY_OUT_OF_BOUNDS = 14,
5959
/// Attempted an allocation that was too large to succeed.
60-
WASMTIME_TRAP_CODE_ALLOCATION_TOO_LARGE,
60+
WASMTIME_TRAP_CODE_ALLOCATION_TOO_LARGE = 15,
6161
/// Attempted to cast a reference to a type that it is not an instance of.
62-
WASMTIME_TRAP_CODE_CAST_FAILURE,
62+
WASMTIME_TRAP_CODE_CAST_FAILURE = 16,
6363
/// When the `component-model` feature is enabled this trap represents a
6464
/// scenario where one component tried to call another component but it
6565
/// would have violated the reentrance rules of the component model,
6666
/// triggering a trap instead.
67-
WASMTIME_TRAP_CODE_CANNOT_ENTER_COMPONENT,
67+
WASMTIME_TRAP_CODE_CANNOT_ENTER_COMPONENT = 17,
6868
/// Async-lifted export failed to produce a result by calling `task.return`
6969
/// before returning `STATUS_DONE` and/or after all host tasks completed.
70-
WASMTIME_TRAP_CODE_NO_ASYNC_RESULT,
70+
WASMTIME_TRAP_CODE_NO_ASYNC_RESULT = 18,
7171
/// We are suspending to a tag for which there is no active handler.
72-
WASMTIME_TRAP_CODE_UNHANDLED_TAG,
72+
WASMTIME_TRAP_CODE_UNHANDLED_TAG = 19,
7373
/// Attempt to resume a continuation twice.
74-
WASMTIME_TRAP_CODE_CONTINUATION_ALREADY_CONSUMED,
74+
WASMTIME_TRAP_CODE_CONTINUATION_ALREADY_CONSUMED = 20,
7575
/// A Pulley opcode was executed at runtime when the opcode was disabled at
7676
/// compile time.
77-
WASMTIME_TRAP_CODE_DISABLED_OPCODE,
77+
WASMTIME_TRAP_CODE_DISABLED_OPCODE = 21,
7878
/// Async event loop deadlocked; i.e. it cannot make further progress given
7979
/// that all host tasks have completed and any/all host-owned stream/future
8080
/// handles have been dropped.
81-
WASMTIME_TRAP_CODE_ASYNC_DEADLOCK,
81+
WASMTIME_TRAP_CODE_ASYNC_DEADLOCK = 22,
8282
/// When the `component-model` feature is enabled this trap represents a
8383
/// scenario where a component instance tried to call an import or intrinsic
8484
/// when it wasn't allowed to, e.g. from a post-return function.
85-
WASMTIME_TRAP_CODE_CANNOT_LEAVE_COMPONENT,
85+
WASMTIME_TRAP_CODE_CANNOT_LEAVE_COMPONENT = 23,
8686
/// A synchronous task attempted to make a potentially blocking call prior
8787
/// to returning.
88-
WASMTIME_TRAP_CODE_CANNOT_BLOCK_SYNC_TASK,
88+
WASMTIME_TRAP_CODE_CANNOT_BLOCK_SYNC_TASK = 24,
8989
/// A component tried to lift a `char` with an invalid bit pattern.
90-
WASMTIME_TRAP_CODE_INVALID_CHAR,
90+
WASMTIME_TRAP_CODE_INVALID_CHAR = 25,
9191
/// Debug assertion generated for a fused adapter regarding the expected
9292
/// completion of a string encoding operation.
93-
WASMTIME_TRAP_CODE_DEBUG_ASSERT_STRING_ENCODING_FINISHED,
93+
WASMTIME_TRAP_CODE_DEBUG_ASSERT_STRING_ENCODING_FINISHED = 26,
9494
/// Debug assertion generated for a fused adapter regarding a string
9595
/// encoding operation.
96-
WASMTIME_TRAP_CODE_DEBUG_ASSERT_EQUAL_CODE_UNITS,
96+
WASMTIME_TRAP_CODE_DEBUG_ASSERT_EQUAL_CODE_UNITS = 27,
9797
/// Debug assertion generated for a fused adapter regarding the alignment of
9898
/// a pointer.
99-
WASMTIME_TRAP_CODE_DEBUG_ASSERT_POINTER_ALIGNED,
99+
WASMTIME_TRAP_CODE_DEBUG_ASSERT_POINTER_ALIGNED = 28,
100100
/// Debug assertion generated for a fused adapter regarding the upper bits
101101
/// of a 64-bit value.
102-
WASMTIME_TRAP_CODE_DEBUG_ASSERT_UPPER_BITS_UNSET,
102+
WASMTIME_TRAP_CODE_DEBUG_ASSERT_UPPER_BITS_UNSET = 29,
103103
/// A component tried to lift or lower a string past the end of its memory.
104-
WASMTIME_TRAP_CODE_STRING_OUT_OF_BOUNDS,
104+
WASMTIME_TRAP_CODE_STRING_OUT_OF_BOUNDS = 30,
105105
/// A component tried to lift or lower a list past the end of its memory.
106-
WASMTIME_TRAP_CODE_LIST_OUT_OF_BOUNDS,
106+
WASMTIME_TRAP_CODE_LIST_OUT_OF_BOUNDS = 31,
107107
/// A component used an invalid discriminant when lowering a variant value.
108-
WASMTIME_TRAP_CODE_INVALID_DISCRIMINANT,
108+
WASMTIME_TRAP_CODE_INVALID_DISCRIMINANT = 32,
109109
/// A component passed an unaligned pointer when lifting or lowering a
110110
/// value.
111-
WASMTIME_TRAP_CODE_UNALIGNED_POINTER,
111+
WASMTIME_TRAP_CODE_UNALIGNED_POINTER = 33,
112+
/// `task.cancel` invoked in an invalid way.
113+
WASMTIME_TRAP_CODE_TASK_CANCEL_NOT_CANCELLED = 34,
114+
/// `task.cancel` or `task.return` called too many times
115+
WASMTIME_TRAP_CODE_TASK_CANCEL_OR_RETURN_TWICE = 35,
116+
/// `subtask.cancel` invoked after it already finished.
117+
WASMTIME_TRAP_CODE_SUBTASK_CANCEL_AFTER_TERMINAL = 36,
118+
/// `task.return` invoked with an invalid type.
119+
WASMTIME_TRAP_CODE_TASK_RETURN_INVALID = 37,
120+
/// `waitable-set.drop` invoked on a waitable set with waiters.
121+
WASMTIME_TRAP_CODE_WAITABLE_SET_DROP_HAS_WAITERS = 38,
122+
/// `subtask.drop` invoked on a subtask that hasn't resolved yet.
123+
WASMTIME_TRAP_CODE_SUBTASK_DROP_NOT_RESOLVED = 39,
124+
/// `thread.new-indirect` invoked with a function that has an invalid type.
125+
WASMTIME_TRAP_CODE_THREAD_NEW_INDIRECT_INVALID_TYPE = 40,
126+
/// `thread.new-indirect` invoked with an uninitialized function reference.
127+
WASMTIME_TRAP_CODE_THREAD_NEW_INDIRECT_UNINITIALIZED = 41,
128+
/// Backpressure-related intrinsics overflowed the built-in counter.
129+
WASMTIME_TRAP_CODE_BACKPRESSURE_OVERFLOW = 42,
130+
/// Invalid code returned from `callback` of `async`-lifted function.
131+
WASMTIME_TRAP_CODE_UNSUPPORTED_CALLBACK_CODE = 43,
132+
/// Cannot resume a thread which is not suspended.
133+
WASMTIME_TRAP_CODE_CANNOT_RESUME_THREAD = 44,
112134
};
113135

114136
/**

crates/c-api/src/trap.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ const _: () = {
4040
assert!(Trap::ListOutOfBounds as u8 == 31);
4141
assert!(Trap::InvalidDiscriminant as u8 == 32);
4242
assert!(Trap::UnalignedPointer as u8 == 33);
43+
assert!(Trap::TaskCancelNotCancelled as u8 == 34);
44+
assert!(Trap::TaskCancelOrReturnTwice as u8 == 35);
45+
assert!(Trap::SubtaskCancelAfterTerminal as u8 == 36);
46+
assert!(Trap::TaskReturnInvalid as u8 == 37);
47+
assert!(Trap::WaitableSetDropHasWaiters as u8 == 38);
48+
assert!(Trap::SubtaskDropNotResolved as u8 == 39);
49+
assert!(Trap::ThreadNewIndirectInvalidType as u8 == 40);
50+
assert!(Trap::ThreadNewIndirectUninitialized as u8 == 41);
51+
assert!(Trap::BackpressureOverflow as u8 == 42);
52+
assert!(Trap::UnsupportedCallbackCode as u8 == 43);
53+
assert!(Trap::CannotResumeThread as u8 == 44);
4354
};
4455

4556
#[repr(C)]

0 commit comments

Comments
 (0)