@@ -64,7 +64,7 @@ TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm,
6464 std::vector<char >&& input,
6565 bool closedWorld)
6666 : wasm(wasm), closedWorld(closedWorld), builder(wasm),
67- random (std::move(input), wasm.features),
67+ random (std::move(input), wasm.features), intrinsics(wasm),
6868 loggableTypes(getLoggableTypes(wasm.features)),
6969 atomicMemoryOrders(getMemoryOrders(wasm.features)),
7070
@@ -1000,25 +1000,6 @@ void TranslateToFuzzReader::addImportCallingSupport() {
10001000 return ;
10011001 }
10021002
1003- if (wasm.features .hasReferenceTypes () && closedWorld) {
1004- // In closed world mode we must *remove* the call-ref* imports, if they
1005- // exist in the initial content. These are not valid to call in closed-world
1006- // mode as they call function references. (Another solution here would be to
1007- // make closed-world issue validation errors on these imports, but that
1008- // would require changes to the general-purpose validator.)
1009- for (auto & func : wasm.functions ) {
1010- if (func->imported () && func->module == " fuzzing-support" &&
1011- func->base .startsWith (" call-ref" )) {
1012- // Make it non-imported, and with a simple body.
1013- func->module = func->base = Name ();
1014- func->type = func->type .with (Exact);
1015- auto results = func->getResults ();
1016- func->body =
1017- results.isConcrete () ? makeConst (results) : makeNop (Type::none);
1018- }
1019- }
1020- }
1021-
10221003 // Only add these some of the time, as they inhibit some fuzzing (things like
10231004 // wasm-ctor-eval and wasm-merge are sensitive to the wasm being able to call
10241005 // its own exports, and to care about the indexes of the exports).
@@ -1058,9 +1039,7 @@ void TranslateToFuzzReader::addImportCallingSupport() {
10581039 wasm.addFunction (std::move (func));
10591040 }
10601041
1061- // If the wasm will be used for closed-world testing, we cannot use the
1062- // call-ref variants, as mentioned before.
1063- if (wasm.features .hasReferenceTypes () && !closedWorld) {
1042+ if (wasm.features .hasReferenceTypes ()) {
10641043 if (choice & 4 ) {
10651044 // Given an funcref, call it from JS.
10661045 callRefImportName = Names::getValidFunctionName (wasm, " call-ref" );
@@ -1638,6 +1617,16 @@ void TranslateToFuzzReader::processFunctions() {
16381617 }
16391618 }
16401619 }
1620+
1621+ // Also fix up closed world, if we need to. We must do this at the end, so
1622+ // nothing can break the closed world assumptions after.
1623+ if (closedWorld) {
1624+ for (auto & func : wasm.functions ) {
1625+ if (!func->imported ()) {
1626+ fixClosedWorld (func.get ());
1627+ }
1628+ }
1629+ }
16411630}
16421631
16431632// TODO: return std::unique_ptr<Function>
@@ -1695,8 +1684,8 @@ Function* TranslateToFuzzReader::addFunction() {
16951684 func->body = make (bodyType);
16961685 }
16971686
1698- // Add hang limit checks after all other operations on the function body.
16991687 wasm.addFunction (std::move (allocation));
1688+
17001689 // Export some functions, but not all (to allow inlining etc.). Try to export
17011690 // at least one, though, to keep each testcase interesting. Avoid non-
17021691 // nullable params, as those cannot be constructed by the fuzzer on the
@@ -1713,7 +1702,10 @@ Function* TranslateToFuzzReader::addFunction() {
17131702 wasm.addExport (
17141703 Builder::makeExport (func->name , func->name , ExternalKind::Function));
17151704 }
1716- // add some to an elem segment TODO we could do this for imported funcs too
1705+
1706+ // Add some to an elem segment TODO we could do this for imported funcs too,
1707+ // but in closed world must be careful of callRef*, see the jsCalled logic
1708+ // and isValidRefFuncTarget.
17171709 while (oneIn (3 ) && !random.finished ()) {
17181710 auto type = func->type ;
17191711 std::vector<ElementSegment*> compatibleSegments;
@@ -1725,6 +1717,23 @@ Function* TranslateToFuzzReader::addFunction() {
17251717 auto & randomElem = compatibleSegments[upTo (compatibleSegments.size ())];
17261718 randomElem->data .push_back (builder.makeRefFunc (func->name ));
17271719 }
1720+
1721+ // Mark some functions as jsCalled. This allows them to be called by
1722+ // reference even in closed world, using the callRef* imports.
1723+ // TODO: We could do this, and exporting above, to initial content too.
1724+ if (oneIn (4 )) {
1725+ auto annotations = intrinsics.getAnnotations (func);
1726+ annotations.jsCalled = true ;
1727+ intrinsics.setAnnotations (func, annotations);
1728+
1729+ // We cannot actually use this as jsCalled if it does not have a type
1730+ // compatible with the callRef* imports. They send a funcref, so we must
1731+ // only send non-shared functions.
1732+ if (!func->type .getHeapType ().isShared ()) {
1733+ jsCalled.push_back (func->name );
1734+ }
1735+ }
1736+
17281737 numAddedFunctions++;
17291738 return func;
17301739}
@@ -2125,6 +2134,60 @@ void TranslateToFuzzReader::mutate(Function* func) {
21252134 modder.walkFunctionInModule (func, &wasm);
21262135}
21272136
2137+ void TranslateToFuzzReader::fixClosedWorld (Function* func) {
2138+ assert (closedWorld);
2139+
2140+ struct Fixer
2141+ : public ExpressionStackWalker<Fixer, UnifiedExpressionVisitor<Fixer>> {
2142+ TranslateToFuzzReader& parent;
2143+
2144+ Fixer (TranslateToFuzzReader& parent) : parent(parent) {}
2145+
2146+ void visitCall (Call* curr) {
2147+ // In closed world, the callRef* imports can cause misoptimization later:
2148+ // they send a funcref to JS to call, and in closed world we assume such
2149+ // calls do not happen unless the function is annotated as jsCalled. We
2150+ // must therefore ensure that calls to these imports only send a jsCalled
2151+ // method and nothing else.
2152+ if (!parent.isCallRefImport (curr->target )) {
2153+ return ;
2154+ }
2155+
2156+ // These imports take a funcref as the first param.
2157+ assert (!curr->operands .empty ());
2158+ if (curr->operands [0 ]->type == Type::unreachable) {
2159+ // This call is not executed anyhow, and we shouldn't replace it, as
2160+ // that could change the type.
2161+ return ;
2162+ }
2163+
2164+ if (parent.jsCalled .empty ()) {
2165+ // There is nothing valid to call at all. Keep the children (we may
2166+ // need them to validate, e.g. if there is a `pop`), but remove the
2167+ // call.
2168+ std::vector<Expression*> list;
2169+ for (auto * child : curr->operands ) {
2170+ list.push_back (parent.builder .makeDrop (child));
2171+ }
2172+ list.push_back (parent.makeTrivial (curr->type ));
2173+ replaceCurrent (parent.builder .makeBlock (list));
2174+ return ;
2175+ }
2176+
2177+ // Set something valid. As above, we must keep the old child for
2178+ // validation reasons.
2179+ auto old = parent.builder .makeDrop (curr->operands [0 ]);
2180+ auto target = parent.pick (parent.jsCalled );
2181+ assert (parent.isValidRefFuncTarget (target));
2182+ auto new_ = parent.builder .makeRefFunc (target);
2183+ curr->operands [0 ] = parent.builder .makeSequence (old, new_);
2184+ }
2185+ } fixer (*this );
2186+
2187+ FunctionCreationContext context (*this , func);
2188+ fixer.walk (func->body );
2189+ }
2190+
21282191void TranslateToFuzzReader::fixAfterChanges (Function* func) {
21292192 struct Fixer
21302193 : public ExpressionStackWalker<Fixer, UnifiedExpressionVisitor<Fixer>> {
@@ -3002,6 +3065,10 @@ Expression* TranslateToFuzzReader::makeCallRef(Type type) {
30023065 target = wasm.functions [upTo (wasm.functions .size ())].get ();
30033066 isReturn = type == Type::unreachable && wasm.features .hasTailCall () &&
30043067 funcContext->func ->getResults () == target->getResults ();
3068+ if (!isValidRefFuncTarget (target->name )) {
3069+ i++;
3070+ continue ;
3071+ }
30053072 if (target->getResults () == type || isReturn) {
30063073 break ;
30073074 }
@@ -3638,7 +3705,9 @@ Expression* TranslateToFuzzReader::makeRefFuncConst(Type type) {
36383705 funcContext->func ->type .getHeapType ().getShared () == share &&
36393706 !oneIn (4 )) {
36403707 auto * target = funcContext->func ;
3641- return builder.makeRefFunc (target->name , target->type );
3708+ if (isValidRefFuncTarget (target->name )) {
3709+ return builder.makeRefFunc (target->name , target->type );
3710+ }
36423711 }
36433712 }
36443713 // Look for a proper function starting from a random location, and loop from
@@ -3648,7 +3717,8 @@ Expression* TranslateToFuzzReader::makeRefFuncConst(Type type) {
36483717 Index i = start;
36493718 do {
36503719 auto & func = wasm.functions [i];
3651- if (Type::isSubType (func->type , type)) {
3720+ if (Type::isSubType (func->type , type) &&
3721+ isValidRefFuncTarget (func->name )) {
36523722 return builder.makeRefFunc (func->name );
36533723 }
36543724 i = (i + 1 ) % wasm.functions .size ();
@@ -3688,6 +3758,7 @@ Expression* TranslateToFuzzReader::makeRefFuncConst(Type type) {
36883758 Type (heapType, NonNullable, Exact),
36893759 {},
36903760 body));
3761+ assert (isValidRefFuncTarget (func->name ));
36913762 return builder.makeRefFunc (func->name );
36923763}
36933764
@@ -6028,4 +6099,31 @@ Type TranslateToFuzzReader::getTargetType(Expression* target) {
60286099 WASM_UNREACHABLE (" unexpected expr type" );
60296100}
60306101
6102+ bool TranslateToFuzzReader::isValidRefFuncTarget (Name func) {
6103+ // In closed world, we must not take callRef* by reference: if we do, then we
6104+ // might end up calling them indirectly, and with any possible function
6105+ // reference, but in that mode we must only pass in jsCalled functions. We
6106+ // handle direct calls in fixClosedWorld, but cannot handle indirect ones
6107+ // easily, so just disallow taking references of those functions.
6108+ if (!closedWorld) {
6109+ return true ;
6110+ }
6111+ return !isCallRefImport (func);
6112+ }
6113+
6114+ bool TranslateToFuzzReader::isCallRefImport (Name target) {
6115+ // Check the import module and base. Note we do not just compare to
6116+ // callRefImportName / callRefCatchImportName because those are the names of
6117+ // the methods we added, but the initial content may import those methods
6118+ // under other internal names.
6119+ auto * func = wasm.getFunctionOrNull (target);
6120+ if (!func) {
6121+ // We are called while a function is still being constructed. Such a new
6122+ // defined function can never be an import.
6123+ return false ;
6124+ }
6125+ return func->imported () && func->module == " fuzzing-support" &&
6126+ func->base .startsWith (" call-ref" );
6127+ }
6128+
60316129} // namespace wasm
0 commit comments