@@ -26,7 +26,10 @@ high chance for set at start of loop
2626*/
2727
2828#include < wasm-builder.h>
29+ #include < ir/find_all.h>
2930#include < ir/literal-utils.h>
31+ #include < ir/manipulation.h>
32+ #include < ir/utils.h>
3033
3134namespace wasm {
3235
@@ -392,11 +395,18 @@ class TranslateToFuzzReader {
392395 } else {
393396 func->body = make (bodyType);
394397 }
398+ // Recombinations create duplicate code patterns.
399+ recombine (func);
400+ // Mutations add random small changes, which can subtly break duplicate code
401+ // patterns.
402+ mutate (func);
403+ // TODO: liveness operations on gets, with some prob alter a get to one with
404+ // more possible sets
405+ // Recombination, mutation, etc. can break validation; fix things up after.
406+ fixLabels (func);
407+ // Add hang limit checks after all other operations on the function body.
395408 if (HANG_LIMIT > 0 ) {
396- func->body = builder.makeSequence (
397- makeHangLimitCheck (),
398- func->body
399- );
409+ addHangLimitChecks (func);
400410 }
401411 assert (breakableStack.empty ());
402412 assert (hangStack.empty ());
@@ -420,6 +430,181 @@ class TranslateToFuzzReader {
420430 return func;
421431 }
422432
433+ void addHangLimitChecks (Function* func) {
434+ // loop limit
435+ FindAll<Loop> loops (func->body );
436+ for (auto * loop : loops.list ) {
437+ loop->body = builder.makeSequence (
438+ makeHangLimitCheck (),
439+ loop->body
440+ );
441+ }
442+ // recursion limit
443+ func->body = builder.makeSequence (
444+ makeHangLimitCheck (),
445+ func->body
446+ );
447+ }
448+
449+ void recombine (Function* func) {
450+ // Don't always do this.
451+ if (oneIn (2 )) return ;
452+ // First, scan and group all expressions by type.
453+ struct Scanner : public PostWalker <Scanner, UnifiedExpressionVisitor<Scanner>> {
454+ // A map of all expressions, categorized by type.
455+ std::map<Type, std::vector<Expression*>> exprsByType;
456+
457+ void visitExpression (Expression* curr) {
458+ exprsByType[curr->type ].push_back (curr);
459+ }
460+ };
461+ Scanner scanner;
462+ scanner.walk (func->body );
463+ // Potentially trim the list of possible picks, so replacements are more likely
464+ // to collide.
465+ for (auto & pair : scanner.exprsByType ) {
466+ if (oneIn (2 )) continue ;
467+ auto & list = pair.second ;
468+ std::vector<Expression*> trimmed;
469+ size_t num = upToSquared (list.size ());
470+ for (size_t i = 0 ; i < num; i++) {
471+ trimmed.push_back (vectorPick (list));
472+ }
473+ if (trimmed.empty ()) {
474+ trimmed.push_back (vectorPick (list));
475+ }
476+ list.swap (trimmed);
477+ }
478+ // Replace them with copies, to avoid a copy into one altering another copy
479+ for (auto & pair : scanner.exprsByType ) {
480+ for (auto *& item : pair.second ) {
481+ item = ExpressionManipulator::copy (item, wasm);
482+ }
483+ }
484+ // Second, with some probability replace an item with another item having
485+ // the same type. (This is not always valid due to nesting of labels, but
486+ // we'll fix that up later.)
487+ struct Modder : public PostWalker <Modder, UnifiedExpressionVisitor<Modder>> {
488+ Module& wasm;
489+ Scanner& scanner;
490+ TranslateToFuzzReader& parent;
491+
492+ Modder (Module& wasm, Scanner& scanner, TranslateToFuzzReader& parent) : wasm(wasm), scanner(scanner), parent(parent) {}
493+
494+ void visitExpression (Expression* curr) {
495+ if (parent.oneIn (10 )) {
496+ // Replace it!
497+ auto & candidates = scanner.exprsByType [curr->type ];
498+ assert (!candidates.empty ()); // this expression itself must be there
499+ replaceCurrent (ExpressionManipulator::copy (parent.vectorPick (candidates), wasm));
500+ }
501+ }
502+ };
503+ Modder modder (wasm, scanner, *this );
504+ modder.walk (func->body );
505+ }
506+
507+ void mutate (Function* func) {
508+ // Don't always do this.
509+ if (oneIn (2 )) return ;
510+ struct Modder : public PostWalker <Modder, UnifiedExpressionVisitor<Modder>> {
511+ Module& wasm;
512+ TranslateToFuzzReader& parent;
513+
514+ Modder (Module& wasm, TranslateToFuzzReader& parent) : wasm(wasm), parent(parent) {}
515+
516+ void visitExpression (Expression* curr) {
517+ if (parent.oneIn (10 )) {
518+ // Replace it!
519+ // (This is not always valid due to nesting of labels, but
520+ // we'll fix that up later.)
521+ replaceCurrent (parent.make (curr->type ));
522+ }
523+ }
524+ };
525+ Modder modder (wasm, *this );
526+ modder.walk (func->body );
527+ }
528+
529+ // Fix up changes that may have broken validation - types are correct in our
530+ // modding, but not necessarily labels.
531+ void fixLabels (Function* func) {
532+ struct Fixer : public ControlFlowWalker <Fixer> {
533+ Module& wasm;
534+ TranslateToFuzzReader& parent;
535+
536+ Fixer (Module& wasm, TranslateToFuzzReader& parent) : wasm(wasm), parent(parent) {}
537+
538+ // Track seen names to find duplication, which is invalid.
539+ std::set<Name> seen;
540+
541+ void visitBlock (Block* curr) {
542+ if (curr->name .is ()) {
543+ if (seen.count (curr->name )) {
544+ replace ();
545+ } else {
546+ seen.insert (curr->name );
547+ }
548+ }
549+ }
550+
551+ void visitLoop (Loop* curr) {
552+ if (curr->name .is ()) {
553+ if (seen.count (curr->name )) {
554+ replace ();
555+ } else {
556+ seen.insert (curr->name );
557+ }
558+ }
559+ }
560+
561+ void visitSwitch (Switch* curr) {
562+ for (auto name : curr->targets ) {
563+ if (replaceIfInvalid (name)) return ;
564+ }
565+ replaceIfInvalid (curr->default_ );
566+ }
567+
568+ void visitBreak (Break* curr) {
569+ replaceIfInvalid (curr->name );
570+ }
571+
572+ bool replaceIfInvalid (Name target) {
573+ if (!hasBreakTarget (target)) {
574+ // There is no valid parent, replace with something trivially safe.
575+ replace ();
576+ return true ;
577+ }
578+ return false ;
579+ }
580+
581+ void replace () {
582+ replaceCurrent (parent.makeTrivial (getCurrent ()->type ));
583+ }
584+
585+ bool hasBreakTarget (Name name) {
586+ if (controlFlowStack.empty ()) return false ;
587+ Index i = controlFlowStack.size () - 1 ;
588+ while (1 ) {
589+ auto * curr = controlFlowStack[i];
590+ if (Block* block = curr->template dynCast <Block>()) {
591+ if (name == block->name ) return true ;
592+ } else if (Loop* loop = curr->template dynCast <Loop>()) {
593+ if (name == loop->name ) return true ;
594+ } else {
595+ // an if, ignorable
596+ assert (curr->template is <If>());
597+ }
598+ if (i == 0 ) return false ;
599+ i--;
600+ }
601+ }
602+ };
603+ Fixer fixer (wasm, *this );
604+ fixer.walk (func->body );
605+ ReFinalize ().walkFunctionInModule (func, &wasm);
606+ }
607+
423608 // the fuzzer external interface sends in zeros (simpler to compare
424609 // across invocations from JS or wasm-opt etc.). Add invocations in
425610 // the wasm, so they run everywhere
@@ -651,12 +836,6 @@ class TranslateToFuzzReader {
651836 }
652837 breakableStack.pop_back ();
653838 hangStack.pop_back ();
654- if (HANG_LIMIT > 0 ) {
655- ret->body = builder.makeSequence (
656- makeHangLimitCheck (),
657- ret->body
658- );
659- }
660839 ret->finalize ();
661840 return ret;
662841 }
0 commit comments