@@ -771,22 +771,33 @@ void TranslateToFuzzReader::addImportLoggingSupport() {
771771}
772772
773773void TranslateToFuzzReader::addImportCallingSupport () {
774+ if (wasm.features .hasReferenceTypes () && closedWorld) {
775+ // In closed world mode we must *remove* the call-ref* imports, if they
776+ // exist in the initial content. These are not valid to call in closed-world
777+ // mode as they call function references. (Another solution here would be to
778+ // make closed-world issue validation errors on these imports, but that
779+ // would require changes to the general-purpose validator.)
780+ for (auto & func : wasm.functions ) {
781+ if (func->imported () && func->module == " fuzzing-support" &&
782+ func->base .startsWith (" call-ref" )) {
783+ // Make it non-imported, and with a simple body.
784+ func->module = func->base = Name ();
785+ auto results = func->getResults ();
786+ func->body =
787+ results.isConcrete () ? makeConst (results) : makeNop (Type::none);
788+ }
789+ }
790+ }
791+
774792 // Only add these some of the time, as they inhibit some fuzzing (things like
775793 // wasm-ctor-eval and wasm-merge are sensitive to the wasm being able to call
776- // its own exports, and to care about the indexes of the exports):
777- //
778- // 0 - none
779- // 1 - call-export
780- // 2 - call-export-catch
781- // 3 - call-export & call-export-catch
782- // 4 - none
783- // 5 - none
784- //
785- auto choice = upTo (6 );
786- if (choice >= 4 ) {
794+ // its own exports, and to care about the indexes of the exports).
795+ if (oneIn (2 )) {
787796 return ;
788797 }
789798
799+ auto choice = upTo (16 );
800+
790801 if (choice & 1 ) {
791802 // Given an export index, call it from JS.
792803 callExportImportName = Names::getValidFunctionName (wasm, " call-export" );
@@ -811,6 +822,34 @@ void TranslateToFuzzReader::addImportCallingSupport() {
811822 func->type = Signature (Type::i32 , Type::i32 );
812823 wasm.addFunction (std::move (func));
813824 }
825+
826+ // If the wasm will be used for closed-world testing, we cannot use the
827+ // call-ref variants, as mentioned before.
828+ if (wasm.features .hasReferenceTypes () && !closedWorld) {
829+ if (choice & 4 ) {
830+ // Given an funcref, call it from JS.
831+ callRefImportName = Names::getValidFunctionName (wasm, " call-ref" );
832+ auto func = std::make_unique<Function>();
833+ func->name = callRefImportName;
834+ func->module = " fuzzing-support" ;
835+ func->base = " call-ref" ;
836+ func->type = Signature ({Type (HeapType::func, Nullable)}, Type::none);
837+ wasm.addFunction (std::move (func));
838+ }
839+
840+ if (choice & 8 ) {
841+ // Given an funcref, call it from JS and catch all exceptions (similar
842+ // to callExportCatch), return 1 if we caught).
843+ callRefCatchImportName =
844+ Names::getValidFunctionName (wasm, " call-ref-catch" );
845+ auto func = std::make_unique<Function>();
846+ func->name = callRefCatchImportName;
847+ func->module = " fuzzing-support" ;
848+ func->base = " call-ref-catch" ;
849+ func->type = Signature (Type (HeapType::func, Nullable), Type::i32 );
850+ wasm.addFunction (std::move (func));
851+ }
852+ }
814853}
815854
816855void TranslateToFuzzReader::addImportThrowingSupport () {
@@ -998,27 +1037,48 @@ Expression* TranslateToFuzzReader::makeImportTableSet(Type type) {
9981037 Type::none);
9991038}
10001039
1001- Expression* TranslateToFuzzReader::makeImportCallExport (Type type) {
1002- // The none-returning variant just does the call. The i32-returning one
1003- // catches any errors and returns 1 when it saw an error. Based on the
1004- // variant, pick which to call, and the maximum index to call.
1005- Name target;
1040+ Expression* TranslateToFuzzReader::makeImportCallCode (Type type) {
1041+ // Call code: either an export or a ref. Each has a catching and non-catching
1042+ // variant. The catching variants return i32, the others none.
1043+ assert (type == Type::none || type == Type::i32 );
1044+ auto catching = type == Type::i32 ;
1045+ auto exportTarget =
1046+ catching ? callExportCatchImportName : callExportImportName;
1047+ auto refTarget = catching ? callRefCatchImportName : callRefImportName;
1048+
1049+ // We want to call a ref less often, as refs are more likely to error (a
1050+ // function reference can have arbitrary params and results, including things
1051+ // that error on the JS boundary; an export is already filtered for such
1052+ // things in some cases - when we legalize the boundary - and even if not, we
1053+ // emit lots of void(void) functions - all the invoke_foo functions - that are
1054+ // safe to call).
1055+ if (refTarget) {
1056+ // This matters a lot more in the variants that do *not* catch (in the
1057+ // catching ones, we just get a result of 1, but when not caught it halts
1058+ // execution).
1059+ if ((catching && (!exportTarget || oneIn (2 ))) || (!catching && oneIn (4 ))) {
1060+ // Most of the time make a non-nullable funcref, to avoid errors.
1061+ auto refType = Type (HeapType::func, oneIn (10 ) ? Nullable : NonNullable);
1062+ return builder.makeCall (refTarget, {make (refType)}, type);
1063+ }
1064+ }
1065+
1066+ if (!exportTarget) {
1067+ // We decided not to emit a call-ref here, due to fear of erroring, and
1068+ // there is no call-export, so just emit something trivial.
1069+ return makeTrivial (type);
1070+ }
1071+
1072+ // Pick the maximum export index to call.
10061073 Index maxIndex = wasm.exports .size ();
1007- if (type == Type::none) {
1008- target = callExportImportName;
1009- } else if (type == Type::i32 ) {
1010- target = callExportCatchImportName;
1011- // This never traps, so we can be less careful, but we do still want to
1012- // avoid trapping a lot as executing code is more interesting. (Note that
1074+ if (type == Type::i32 ) {
1075+ // This swallows errors, so we can be less careful, but we do still want to
1076+ // avoid swallowing a lot as executing code is more interesting. (Note that
10131077 // even though we double here, the risk is not that great: we are still
10141078 // adding functions as we go, so the first half of functions/exports can
10151079 // double here and still end up in bounds by the time we've added them all.)
10161080 maxIndex = (maxIndex + 1 ) * 2 ;
1017- } else {
1018- WASM_UNREACHABLE (" bad import.call" );
10191081 }
1020- // We must have set up the target function.
1021- assert (target);
10221082
10231083 // Most of the time, call a valid export index in the range we picked, but
10241084 // sometimes allow anything at all.
@@ -1027,7 +1087,7 @@ Expression* TranslateToFuzzReader::makeImportCallExport(Type type) {
10271087 index = builder.makeBinary (
10281088 RemUInt32, index, builder.makeConst (int32_t (maxIndex)));
10291089 }
1030- return builder.makeCall (target , {index}, type);
1090+ return builder.makeCall (exportTarget , {index}, type);
10311091}
10321092
10331093Expression* TranslateToFuzzReader::makeMemoryHashLogging () {
@@ -1705,8 +1765,8 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) {
17051765 options.add (FeatureSet::Atomics, &Self::makeAtomic);
17061766 }
17071767 if (type == Type::i32 ) {
1708- if (callExportCatchImportName) {
1709- options.add (FeatureSet::MVP, &Self::makeImportCallExport );
1768+ if (callExportCatchImportName || callRefCatchImportName ) {
1769+ options.add (FeatureSet::MVP, &Self::makeImportCallCode );
17101770 }
17111771 options.add (FeatureSet::ReferenceTypes, &Self::makeRefIsNull);
17121772 options.add (FeatureSet::ReferenceTypes | FeatureSet::GC,
@@ -1787,8 +1847,8 @@ Expression* TranslateToFuzzReader::_makenone() {
17871847 if (tableSetImportName) {
17881848 options.add (FeatureSet::ReferenceTypes, &Self::makeImportTableSet);
17891849 }
1790- if (callExportImportName) {
1791- options.add (FeatureSet::MVP, &Self::makeImportCallExport );
1850+ if (callExportImportName || callRefImportName ) {
1851+ options.add (FeatureSet::MVP, &Self::makeImportCallCode );
17921852 }
17931853 return (this ->*pick (options))(Type::none);
17941854}
0 commit comments