@@ -362,11 +362,7 @@ void TranslateToFuzzReader::build() {
362
362
addImportCallingSupport ();
363
363
addImportSleepSupport ();
364
364
modifyInitialFunctions ();
365
- // keep adding functions until we run out of input
366
- while (!random.finished ()) {
367
- auto * func = addFunction ();
368
- addInvocations (func);
369
- }
365
+ processFunctions ();
370
366
if (fuzzParams->HANG_LIMIT > 0 ) {
371
367
addHangLimitSupport ();
372
368
}
@@ -1125,6 +1121,41 @@ void TranslateToFuzzReader::addHashMemorySupport() {
1125
1121
}
1126
1122
}
1127
1123
1124
+ TranslateToFuzzReader::FunctionCreationContext::FunctionCreationContext (
1125
+ TranslateToFuzzReader& parent, Function* func)
1126
+ : parent(parent), func(func) {
1127
+ parent.funcContext = this ;
1128
+
1129
+ // Note the types of all locals.
1130
+ computeTypeLocals ();
1131
+
1132
+ // Find the right index for labelIndex: we emit names like label$5, so we need
1133
+ // the index to be larger than all currently existing.
1134
+ if (!func->body ) {
1135
+ return ;
1136
+ }
1137
+
1138
+ struct Finder : public PostWalker <Finder, UnifiedExpressionVisitor<Finder>> {
1139
+ Index maxIndex = 0 ;
1140
+
1141
+ void visitExpression (Expression* curr) {
1142
+ // Note all scope names, and fix up all uses.
1143
+ BranchUtils::operateOnScopeNameDefs (curr, [&](Name& name) {
1144
+ if (name.is ()) {
1145
+ if (name.startsWith (" label$" )) {
1146
+ auto str = name.toString ();
1147
+ str = str.substr (6 );
1148
+ Index index = atoi (str.c_str ());
1149
+ maxIndex = std::max (maxIndex, index + 1 );
1150
+ }
1151
+ }
1152
+ });
1153
+ }
1154
+ } finder;
1155
+ finder.walk (func->body );
1156
+ labelIndex = finder.maxIndex ;
1157
+ }
1158
+
1128
1159
TranslateToFuzzReader::FunctionCreationContext::~FunctionCreationContext () {
1129
1160
// We must ensure non-nullable locals validate. Later down we'll run
1130
1161
// TypeUpdating::handleNonDefaultableLocals which will make them validate by
@@ -1151,9 +1182,6 @@ TranslateToFuzzReader::FunctionCreationContext::~FunctionCreationContext() {
1151
1182
// fixup to ensure we validate.
1152
1183
TypeUpdating::handleNonDefaultableLocals (func, parent.wasm );
1153
1184
1154
- if (parent.fuzzParams ->HANG_LIMIT > 0 ) {
1155
- parent.addHangLimitChecks (func);
1156
- }
1157
1185
assert (breakableStack.empty ());
1158
1186
assert (hangStack.empty ());
1159
1187
parent.funcContext = nullptr ;
@@ -1296,14 +1324,78 @@ Expression* TranslateToFuzzReader::makeMemoryHashLogging() {
1296
1324
return builder.makeCall (logImportNames[Type::i32 ], {hash}, Type::none);
1297
1325
}
1298
1326
1327
+ void TranslateToFuzzReader::processFunctions () {
1328
+ // Functions that are eligible for being modded. We only do so once to each
1329
+ // function, at most, so once we do we remove it from here.
1330
+ std::vector<Function*> moddable;
1331
+
1332
+ // Defined initial functions are moddable.
1333
+ for (auto & func : wasm.functions ) {
1334
+ if (!func->imported ()) {
1335
+ moddable.push_back (func.get ());
1336
+ }
1337
+ }
1338
+
1339
+ // Add invocations, which can help execute the code here even if the function
1340
+ // was not exported (or was exported but with a signature that traps
1341
+ // immediately, like receiving a non-nullable ref, that the fuzzer can't
1342
+ // provide from JS). Note we cannot iterate on wasm.functions because
1343
+ // addInvocations modifies that.
1344
+ for (auto * func : moddable) {
1345
+ addInvocations (func);
1346
+ }
1347
+
1348
+ // We do not want to always mod in the same frequency. Pick a chance to mod a
1349
+ // function. When the chance is maximal we will mod every single function, and
1350
+ // immediately after creating it; when the chance is minimal we will not mod
1351
+ // anything; values in the middle will end up randomly modding some functions,
1352
+ // at random times (random times are useful because we might create function
1353
+ // A, then B, then mod A, and since B has already been created, the modding of
1354
+ // A may lead to calls to B).
1355
+ const int RESOLUTION = 10 ;
1356
+ auto chance = upTo (RESOLUTION + 1 );
1357
+
1358
+ // Keep working while we have random data.
1359
+ while (!random.finished ()) {
1360
+ if (!moddable.empty () && upTo (RESOLUTION) < chance) {
1361
+ // Mod an existing function.
1362
+ auto index = upTo (moddable.size ());
1363
+ auto * func = moddable[index];
1364
+ modFunction (func);
1365
+
1366
+ // Remove this function from the vector by swapping the last item to its
1367
+ // place, and truncating.
1368
+ moddable[index] = moddable.back ();
1369
+ moddable.pop_back ();
1370
+ } else {
1371
+ // Add a new function
1372
+ auto * func = addFunction ();
1373
+ addInvocations (func);
1374
+
1375
+ // It may be modded later, if we allow out-of-bounds: we emit OOB checks
1376
+ // in the code we just generated, and any changes could break that.
1377
+ if (allowOOB) {
1378
+ moddable.push_back (func);
1379
+ }
1380
+ }
1381
+ }
1382
+
1383
+ // At the very end, add hang limit checks (so no modding can override them).
1384
+ if (fuzzParams->HANG_LIMIT > 0 ) {
1385
+ for (auto & func : wasm.functions ) {
1386
+ if (!func->imported ()) {
1387
+ addHangLimitChecks (func.get ());
1388
+ }
1389
+ }
1390
+ }
1391
+ }
1392
+
1299
1393
// TODO: return std::unique_ptr<Function>
1300
1394
Function* TranslateToFuzzReader::addFunction () {
1301
1395
LOGGING_PERCENT = upToSquared (100 );
1302
1396
auto allocation = std::make_unique<Function>();
1303
1397
auto * func = allocation.get ();
1304
1398
func->name = Names::getValidFunctionName (wasm, " func" );
1305
- FunctionCreationContext context (*this , func);
1306
- assert (funcContext->typeLocals .empty ());
1307
1399
Index numParams = upToSquared (fuzzParams->MAX_PARAMS );
1308
1400
std::vector<Type> params;
1309
1401
params.reserve (numParams);
@@ -1322,7 +1414,9 @@ Function* TranslateToFuzzReader::addFunction() {
1322
1414
}
1323
1415
func->vars .push_back (type);
1324
1416
}
1325
- context.computeTypeLocals ();
1417
+ // Generate the function creation context after we filled in locals, which it
1418
+ // will scan.
1419
+ FunctionCreationContext context (*this , func);
1326
1420
// with small chance, make the body unreachable
1327
1421
auto bodyType = func->getResults ();
1328
1422
if (oneIn (10 )) {
@@ -1334,29 +1428,6 @@ Function* TranslateToFuzzReader::addFunction() {
1334
1428
} else {
1335
1429
func->body = make (bodyType);
1336
1430
}
1337
- // Our OOB checks are already in the code, and if we recombine/mutate we
1338
- // may end up breaking them. TODO: do them after the fact, like with the
1339
- // hang limit checks.
1340
- if (allowOOB) {
1341
- // Notice the locals and their types again, as more may have been added
1342
- // during generation of the body. We want to be able to local.get from those
1343
- // as well.
1344
- // TODO: We could also add a "localize" phase here to stash even more things
1345
- // in locals, so that they can be reused. But we would need to be
1346
- // careful with non-nullable locals (which error if used before being
1347
- // set, or trap if we make them nullable, both of which are bad).
1348
- context.computeTypeLocals ();
1349
- // Recombinations create duplicate code patterns.
1350
- recombine (func);
1351
- // Mutations add random small changes, which can subtly break duplicate
1352
- // code patterns.
1353
- mutate (func);
1354
- // TODO: liveness operations on gets, with some prob alter a get to one
1355
- // with more possible sets.
1356
- // Recombination, mutation, etc. can break validation; fix things up
1357
- // after.
1358
- fixAfterChanges (func);
1359
- }
1360
1431
1361
1432
// Add hang limit checks after all other operations on the function body.
1362
1433
wasm.addFunction (std::move (allocation));
@@ -1392,6 +1463,20 @@ Function* TranslateToFuzzReader::addFunction() {
1392
1463
return func;
1393
1464
}
1394
1465
1466
+ void TranslateToFuzzReader::modFunction (Function* func) {
1467
+ FunctionCreationContext context (*this , func);
1468
+
1469
+ dropToLog (func);
1470
+ // TODO: if we add OOB checks after creation, then we can do it on
1471
+ // initial contents too, and it may be nice to *not* run these
1472
+ // passes, like we don't run them on new functions. But, we may
1473
+ // still want to run them some of the time, at least, so that we
1474
+ // check variations on initial testcases even at the risk of OOB.
1475
+ recombine (func);
1476
+ mutate (func);
1477
+ fixAfterChanges (func);
1478
+ }
1479
+
1395
1480
void TranslateToFuzzReader::addHangLimitChecks (Function* func) {
1396
1481
// loop limit
1397
1482
for (auto * loop : FindAll<Loop>(func->body ).list ) {
@@ -1822,9 +1907,6 @@ void TranslateToFuzzReader::modifyInitialFunctions() {
1822
1907
if (wasm.functions .empty ()) {
1823
1908
return ;
1824
1909
}
1825
- // Pick a chance to fuzz the contents of a function.
1826
- const int RESOLUTION = 10 ;
1827
- auto chance = upTo (RESOLUTION + 1 );
1828
1910
// Do not iterate directly on wasm.functions itself (that is, avoid
1829
1911
// for (x : wasm.functions)
1830
1912
// ) as we may add to it as we go through the functions - make() can add new
@@ -1840,43 +1922,11 @@ void TranslateToFuzzReader::modifyInitialFunctions() {
1840
1922
(func->module == " fuzzing-support" || preserveImportsAndExports)) {
1841
1923
continue ;
1842
1924
}
1843
- FunctionCreationContext context (*this , func);
1844
1925
if (func->imported ()) {
1926
+ FunctionCreationContext context (*this , func);
1845
1927
func->module = func->base = Name ();
1846
1928
func->body = make (func->getResults ());
1847
1929
}
1848
- // Optionally, fuzz the function contents.
1849
- if (upTo (RESOLUTION) >= chance) {
1850
- dropToLog (func);
1851
- // Notice params as well as any locals generated above.
1852
- // TODO add some locals? and the rest of addFunction's operations?
1853
- context.computeTypeLocals ();
1854
- // TODO: if we add OOB checks after creation, then we can do it on
1855
- // initial contents too, and it may be nice to *not* run these
1856
- // passes, like we don't run them on new functions. But, we may
1857
- // still want to run them some of the time, at least, so that we
1858
- // check variations on initial testcases even at the risk of OOB.
1859
- recombine (func);
1860
- mutate (func);
1861
- fixAfterChanges (func);
1862
- // TODO: This triad of functions appears in another place as well, and
1863
- // could be handled by a single function. That function could also
1864
- // decide to reorder recombine and mutate or even run more cycles of
1865
- // them.
1866
- }
1867
- }
1868
-
1869
- // Add invocations, which can help execute the code here even if the function
1870
- // was not exported (or was exported but with a signature that traps
1871
- // immediately, like receiving a non-nullable ref, that the fuzzer can't
1872
- // provide from JS). Note we need to use a temp vector for iteration, as
1873
- // addInvocations modifies wasm.functions.
1874
- std::vector<Function*> funcs;
1875
- for (auto & func : wasm.functions ) {
1876
- funcs.push_back (func.get ());
1877
- }
1878
- for (auto * func : funcs) {
1879
- addInvocations (func);
1880
1930
}
1881
1931
1882
1932
// Remove a start function - the fuzzing harness expects code to run only
0 commit comments