diff options
author | Alon Zakai <azakai@google.com> | 2023-04-12 10:39:43 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-12 10:39:43 -0700 |
commit | 1d1d60c6d9803edb2e3684c69c0298d7e5b2d8cb (patch) | |
tree | eea211ebb6ad7840197777147e441f4c094b65c6 /src | |
parent | 18898347e7c4ceb86988bdb8594160883f7886f0 (diff) | |
download | binaryen-1d1d60c6d9803edb2e3684c69c0298d7e5b2d8cb.tar.gz binaryen-1d1d60c6d9803edb2e3684c69c0298d7e5b2d8cb.tar.bz2 binaryen-1d1d60c6d9803edb2e3684c69c0298d7e5b2d8cb.zip |
[Wasm GC] Fuzz struct.set and array.set (#5655)
Diffstat (limited to 'src')
-rw-r--r-- | src/tools/fuzzing.h | 8 | ||||
-rw-r--r-- | src/tools/fuzzing/fuzzing.cpp | 66 |
2 files changed, 73 insertions, 1 deletions
diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 24f08dc53..f055d7f44 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -124,6 +124,12 @@ private: // Type => list of array types that have that type. std::unordered_map<Type, std::vector<HeapType>> typeArrays; + // All struct fields that are mutable. + std::vector<StructField> mutableStructFields; + + // All arrays that are mutable. + std::vector<HeapType> mutableArrays; + Index numAddedFunctions = 0; // RAII helper for managing the state used to create a single function. @@ -334,7 +340,9 @@ private: Expression* makeRefTest(Type type); Expression* makeRefCast(Type type); Expression* makeStructGet(Type type); + Expression* makeStructSet(Type type); Expression* makeArrayGet(Type type); + Expression* makeArraySet(Type type); Expression* makeI31Get(Type type); Expression* makeMemoryInit(); Expression* makeDataDrop(); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index fd8722224..1c2b818df 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -287,10 +287,22 @@ void TranslateToFuzzReader::setupHeapTypes() { interestingHeapSubTypes[HeapType::struct_].push_back(type); interestingHeapSubTypes[HeapType::eq].push_back(type); interestingHeapSubTypes[HeapType::any].push_back(type); + + // Note the mutable fields. + auto& fields = type.getStruct().fields; + for (Index i = 0; i < fields.size(); i++) { + if (fields[i].mutable_) { + mutableStructFields.push_back(StructField{type, i}); + } + } } else if (type.isArray()) { interestingHeapSubTypes[HeapType::array].push_back(type); interestingHeapSubTypes[HeapType::eq].push_back(type); interestingHeapSubTypes[HeapType::any].push_back(type); + + if (type.getArray().element.mutable_) { + mutableArrays.push_back(type); + } } else if (type.isSignature()) { interestingHeapSubTypes[HeapType::func].push_back(type); } @@ -1182,7 +1194,9 @@ Expression* TranslateToFuzzReader::_makenone() { &Self::makeGlobalSet) .add(FeatureSet::BulkMemory, &Self::makeBulkMemory) .add(FeatureSet::Atomics, &Self::makeAtomic) - .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef); + .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeCallRef) + .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeStructSet) + .add(FeatureSet::GC | FeatureSet::ReferenceTypes, &Self::makeArraySet); return (this->*pick(options))(Type::none); } @@ -3251,11 +3265,28 @@ Expression* TranslateToFuzzReader::makeStructGet(Type type) { assert(!structFields.empty()); auto [structType, fieldIndex] = pick(structFields); // TODO: also nullable ones? that would increase the risk of traps + // TODO: Ensure a good chance to use a local.get or tee here, as we want to + // test the same reference having multiple sets/gets on it, and not + // gets/sets of struct.news everywhere. Also in struct.set, array.get, + // array.set. auto* ref = make(Type(structType, NonNullable)); // TODO: fuzz signed and unsigned return builder.makeStructGet(fieldIndex, ref, type); } +Expression* TranslateToFuzzReader::makeStructSet(Type type) { + assert(type == Type::none); + if (mutableStructFields.empty()) { + return makeTrivial(type); + } + auto [structType, fieldIndex] = pick(mutableStructFields); + auto fieldType = structType.getStruct().fields[fieldIndex].type; + // TODO: also nullable ones? that would increase the risk of traps + auto* ref = make(Type(structType, NonNullable)); + auto* value = make(fieldType); + return builder.makeStructSet(fieldIndex, ref, value); +} + Expression* TranslateToFuzzReader::makeArrayGet(Type type) { auto& arrays = typeArrays[type]; assert(!arrays.empty()); @@ -3286,6 +3317,39 @@ Expression* TranslateToFuzzReader::makeArrayGet(Type type) { return builder.makeIf(condition, get, fallback); } +Expression* TranslateToFuzzReader::makeArraySet(Type type) { + assert(type == Type::none); + if (mutableArrays.empty()) { + return makeTrivial(type); + } + auto arrayType = pick(mutableArrays); + auto elementType = arrayType.getArray().element.type; + auto* index = make(Type::i32); + // TODO: also nullable ones? that would increase the risk of traps + auto* ref = make(Type(arrayType, NonNullable)); + auto* value = make(elementType); + // Only rarely emit a plain get which might trap. See related logic in + // ::makePointer(). + if (allowOOB && oneIn(10)) { + // TODO: fuzz signed and unsigned, and also below + return builder.makeArraySet(ref, index, value); + } + // To avoid a trap, check the length dynamically using this pattern: + // + // if (index < array.len) array[index] = value; + // + auto tempRef = builder.addVar(funcContext->func, ref->type); + auto tempIndex = builder.addVar(funcContext->func, index->type); + auto* teeRef = builder.makeLocalTee(tempRef, ref, ref->type); + auto* teeIndex = builder.makeLocalTee(tempIndex, index, index->type); + auto* getSize = builder.makeArrayLen(teeRef); + auto* condition = builder.makeBinary(LtUInt32, teeIndex, getSize); + auto* refGet = builder.makeLocalGet(tempRef, ref->type); + auto* indexGet = builder.makeLocalGet(tempIndex, index->type); + auto* set = builder.makeArraySet(refGet, indexGet, value); + return builder.makeIf(condition, set); +} + Expression* TranslateToFuzzReader::makeI31Get(Type type) { assert(type == Type::i32); assert(wasm.features.hasReferenceTypes() && wasm.features.hasGC()); |