diff options
-rw-r--r-- | src/wasm/wasm-validator.cpp | 63 | ||||
-rw-r--r-- | test/lit/validation/intrinsics.wast | 23 |
2 files changed, 78 insertions, 8 deletions
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 572a3b4ba..8cf09e747 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -446,15 +446,20 @@ private: "return_call* requires tail calls to be enabled"); } + // |printable| is the expression to print in case of an error. That may differ + // from |curr| which we are validating. template<typename T> - void validateCallParamsAndResult(T* curr, HeapType sigType) { - if (!shouldBeTrue( - sigType.isSignature(), curr, "Heap type must be a signature type")) { + void validateCallParamsAndResult(T* curr, + HeapType sigType, + Expression* printable) { + if (!shouldBeTrue(sigType.isSignature(), + printable, + "Heap type must be a signature type")) { return; } auto sig = sigType.getSignature(); if (!shouldBeTrue(curr->operands.size() == sig.params.size(), - curr, + printable, "call* param number must match")) { return; } @@ -462,7 +467,7 @@ private: for (const auto& param : sig.params) { if (!shouldBeSubType(curr->operands[i]->type, param, - curr, + printable, "call param types must match") && !info.quiet) { getStream() << "(on argument " << i << ")\n"; @@ -472,22 +477,28 @@ private: if (curr->isReturn) { shouldBeEqual(curr->type, Type(Type::unreachable), - curr, + printable, "return_call* should have unreachable type"); shouldBeSubType( sig.results, getFunction()->getResults(), - curr, + printable, "return_call* callee return type must match caller return type"); } else { shouldBeEqualOrFirstIsUnreachable( curr->type, sig.results, - curr, + printable, "call* type must match callee return type"); } } + // In the common case, we use |curr| as |printable|. + template<typename T> + void validateCallParamsAndResult(T* curr, HeapType sigType) { + validateCallParamsAndResult(curr, sigType, curr); + } + Type indexType() { return getModule()->memory.indexType; } }; @@ -799,6 +810,42 @@ void FunctionValidator::visitCall(Call* curr) { return; } validateCallParamsAndResult(curr, target->type); + + if (Intrinsics(*getModule()).isCallWithoutEffects(curr)) { + // call.without.effects has the specific form of the last argument being a + // function reference, which will be called with all the previous arguments. + // The type must be consistent with that. This, for example, is not: + // + // (call $call.without.effects + // (i32.const 1) + // (.. some function reference that expects an f64 param and not i32 ..) + // ) + if (shouldBeTrue(!curr->operands.empty(), + curr, + "call.without.effects must have a target operand")) { + auto* target = curr->operands.back(); + // Validate only in the case that the target is a function. If it isn't, + // it might be unreachable (which is fine, and we can ignore this), or if + // the call.without.effects import doesn't have a function as the last + // parameter, then validateImports() will handle that later (and it's + // better to emit a single error there than one per callsite here). + if (target->type.isFunction()) { + // Copy the original call and remove the reference. It must then match + // the expected signature. + struct Copy { + std::vector<Expression*> operands; + bool isReturn; + Type type; + } copy; + for (Index i = 0; i < curr->operands.size() - 1; i++) { + copy.operands.push_back(curr->operands[i]); + } + copy.isReturn = curr->isReturn; + copy.type = curr->type; + validateCallParamsAndResult(©, target->type.getHeapType(), curr); + } + } + } } void FunctionValidator::visitCallIndirect(CallIndirect* curr) { diff --git a/test/lit/validation/intrinsics.wast b/test/lit/validation/intrinsics.wast new file mode 100644 index 000000000..6437b2217 --- /dev/null +++ b/test/lit/validation/intrinsics.wast @@ -0,0 +1,23 @@ +;; Test for a validation error on bad usage of call.without.effects + +;; RUN: not wasm-opt -all %s 2>&1 | filecheck %s + +;; CHECK: param number must match + +(module + (import "binaryen-intrinsics" "call.without.effects" (func $cwe (param i32 funcref) (result i32))) + + (func "get-ref" (result i32) + ;; This call-without-effects is done to a $func, but $func has the wrong + ;; signature - it lacks the i32 parameter. + (call $cwe + (i32.const 41) + (ref.func $func) + ) + ) + + (func $func (result i32) + (i32.const 1) + ) +) + |