diff options
author | Alon Zakai <azakai@google.com> | 2024-10-16 09:51:50 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-10-16 09:51:50 -0700 |
commit | 33abf08976264700e6ab5815a73537a5fb6b7dcf (patch) | |
tree | 916c344c4395b1b80c84b1994061323aca2a27b6 /src/tools | |
parent | c7e42ae04ffd45b0da3e281be03d753516083803 (diff) | |
download | binaryen-33abf08976264700e6ab5815a73537a5fb6b7dcf.tar.gz binaryen-33abf08976264700e6ab5815a73537a5fb6b7dcf.tar.bz2 binaryen-33abf08976264700e6ab5815a73537a5fb6b7dcf.zip |
[Wasm GC] Fuzz BrOn (#7006)
Diffstat (limited to 'src/tools')
-rw-r--r-- | src/tools/fuzzing.h | 1 | ||||
-rw-r--r-- | src/tools/fuzzing/fuzzing.cpp | 124 |
2 files changed, 119 insertions, 6 deletions
diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index f4400e39e..92e997913 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -366,6 +366,7 @@ private: Expression* makeRefEq(Type type); Expression* makeRefTest(Type type); Expression* makeRefCast(Type type); + Expression* makeBrOn(Type type); // Decide to emit a signed Struct/ArrayGet sometimes, when the field is // packed. diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index abc6a63d5..d3f52d3b0 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1367,7 +1367,8 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { &Self::makeCallIndirect) .add(FeatureSet::ExceptionHandling, &Self::makeTry) .add(FeatureSet::ExceptionHandling, &Self::makeTryTable) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef); + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef) + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeBrOn); } if (type.isSingle()) { options @@ -1454,10 +1455,11 @@ Expression* TranslateToFuzzReader::_makenone() { .add(FeatureSet::Atomics, &Self::makeAtomic) .add(FeatureSet::ExceptionHandling, &Self::makeTry) .add(FeatureSet::ExceptionHandling, &Self::makeTryTable) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeStructSet) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeArraySet) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef) + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeStructSet) + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeArraySet) + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeBrOn) + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeArrayBulkMemoryOp); return (this->*pick(options))(Type::none); } @@ -1484,7 +1486,7 @@ Expression* TranslateToFuzzReader::_makeunreachable() { &Self::makeDrop, &Self::makeReturn) .add(FeatureSet::ExceptionHandling, &Self::makeThrow) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef); + .add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeCallRef); return (this->*pick(options))(Type::unreachable); } @@ -3944,6 +3946,116 @@ Expression* TranslateToFuzzReader::makeRefCast(Type type) { return builder.makeRefCast(make(refType), type); } +Expression* TranslateToFuzzReader::makeBrOn(Type type) { + if (funcContext->breakableStack.empty()) { + return makeTrivial(type); + } + // We need to find a proper target to break to; try a few times. Finding the + // target is harder than flowing out the proper type, so focus on the target, + // and fix up the flowing type later. That is, once we find a target to break + // to, we can then either drop ourselves or wrap ourselves in a block + + // another value, so that we return the proper thing here (which is done below + // in fixFlowingType). + int tries = TRIES; + Name targetName; + Type targetType; + while (--tries >= 0) { + auto* target = pick(funcContext->breakableStack); + targetName = getTargetName(target); + targetType = getTargetType(target); + // We can send any reference type, or no value at all, but nothing else. + if (targetType.isRef() || targetType == Type::none) { + break; + } + } + if (tries < 0) { + return makeTrivial(type); + } + + auto fixFlowingType = [&](Expression* brOn) -> Expression* { + if (Type::isSubType(brOn->type, type)) { + // Already of the proper type. + return brOn; + } + if (type == Type::none) { + // We just need to drop whatever it is. + return builder.makeDrop(brOn); + } + // We need to replace the type with something else. Drop the BrOn if we need + // to, and append a value with the proper type. + if (brOn->type != Type::none) { + brOn = builder.makeDrop(brOn); + } + return builder.makeSequence(brOn, make(type)); + }; + + // We found something to break to. Figure out which BrOn variants we can + // send. + if (targetType == Type::none) { + // BrOnNull is the only variant that sends no value. + return fixFlowingType( + builder.makeBrOn(BrOnNull, targetName, make(getReferenceType()))); + } + + // We are sending a reference type to the target. All other BrOn variants can + // do that. + assert(targetType.isRef()); + auto op = pick(BrOnNonNull, BrOnCast, BrOnCastFail); + Type castType = Type::none; + Type refType; + switch (op) { + case BrOnNonNull: { + // The sent type is the non-nullable version of the reference, so any ref + // of that type is ok, nullable or not. + refType = Type(targetType.getHeapType(), getNullability()); + break; + } + case BrOnCast: { + // The sent type is the heap type we cast to, with the input type's + // nullability, so the combination of the two must be a subtype of + // targetType. + castType = getSubType(targetType); + // The ref's type must be castable to castType, or we'd not validate. But + // it can also be a subtype, which will trivially also succeed (so do that + // more rarely). Pick subtypes rarely, as they make the cast trivial. + refType = oneIn(5) ? getSubType(castType) : getSuperType(castType); + if (targetType.isNonNullable()) { + // And it must have the right nullability for the target, as mentioned + // above: if the target type is non-nullable then either the ref or the + // cast types must be. + if (!refType.isNonNullable() && !castType.isNonNullable()) { + // Pick one to make non-nullable. + if (oneIn(2)) { + refType = Type(refType.getHeapType(), NonNullable); + } else { + castType = Type(castType.getHeapType(), NonNullable); + } + } + } + break; + } + case BrOnCastFail: { + // The sent type is the ref's type, with adjusted nullability (if the cast + // allows nulls then no null can fail the cast, and what is sent is non- + // nullable). First, pick a ref type that we can send to the target. + refType = getSubType(targetType); + // See above on BrOnCast, but flipped. + castType = oneIn(5) ? getSuperType(refType) : getSubType(refType); + // There is no nullability to adjust: if targetType is non-nullable then + // both refType and castType are as well, as subtypes of it. But we can + // also allow castType to be nullable (it is not sent to the target). + if (castType.isNonNullable() && oneIn(2)) { + castType = Type(castType.getHeapType(), Nullable); + } + } break; + default: { + WASM_UNREACHABLE("bad br_on op"); + } + } + return fixFlowingType( + builder.makeBrOn(op, targetName, make(refType), castType)); +} + bool TranslateToFuzzReader::maybeSignedGet(const Field& field) { if (field.isPacked()) { return oneIn(2); |