summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/tools/fuzzing.h6
-rw-r--r--src/tools/fuzzing/fuzzing.cpp73
-rw-r--r--test/passes/translate-to-fuzz_all-features_metrics_noprint.txt63
3 files changed, 87 insertions, 55 deletions
diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h
index c0f6ab335..abc0a4248 100644
--- a/src/tools/fuzzing.h
+++ b/src/tools/fuzzing.h
@@ -302,6 +302,7 @@ private:
Expression* makeLoad(Type type);
Expression* makeNonAtomicStore(Type type);
Expression* makeStore(Type type);
+
// Makes a small change to a constant value.
Literal tweak(Literal value);
Literal makeLiteral(Type type);
@@ -318,6 +319,11 @@ private:
Expression* makeBasicRef(Type type);
Expression* makeCompoundRef(Type type);
+ // Similar to makeBasic/CompoundRef, but indicates that this value will be
+ // used in a place that will trap on null. For example, the reference of a
+ // struct.get or array.set would use this.
+ Expression* makeTrappingRefUse(HeapType type);
+
Expression* buildUnary(const UnaryArgs& args);
Expression* makeUnary(Type type);
Expression* buildBinary(const BinaryArgs& args);
diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp
index 062e66260..c88b2f13d 100644
--- a/src/tools/fuzzing/fuzzing.cpp
+++ b/src/tools/fuzzing/fuzzing.cpp
@@ -2316,11 +2316,19 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) {
return builder.makeRefNull(heapType);
}
- // If the type is non-nullable, and we've run out of input, emit a cast to
- // make this validate, but it will trap at runtime which is not ideal. This at
- // least avoids infinite recursion here, and we emit a valid (but not that
- // useful) wasm.
+ // If the type is non-nullable, but we've run out of input, then we need to do
+ // something here to avoid infinite recursion. In the worse case we'll emit a
+ // cast to non-null of a null, which validates, but it will trap at runtime
+ // which is not ideal.
if (type.isNonNullable() && (random.finished() || nesting >= LIMIT)) {
+ // If we have a function context then we can at least emit a local.get,
+ // perhaps, which is less bad. Note that we need to check typeLocals
+ // manually here to avoid infinite recursion (as makeLocalGet will fall back
+ // to us, if there is no local).
+ // TODO: we could also look for locals containing subtypes
+ if (funcContext && !funcContext->typeLocals[type].empty()) {
+ return makeLocalGet(type);
+ }
return builder.makeRefAs(RefAsNonNull, builder.makeRefNull(heapType));
}
@@ -2369,6 +2377,43 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) {
}
}
+Expression* TranslateToFuzzReader::makeTrappingRefUse(HeapType type) {
+ auto percent = upTo(100);
+ // Only give a low probability to emit a nullable reference.
+ if (percent < 5) {
+ return make(Type(type, Nullable));
+ }
+ // Otherwise, usually emit a non-nullable one.
+ auto nonNull = Type(type, NonNullable);
+ if (percent < 70 || !funcContext) {
+ return make(nonNull);
+ }
+ // With significant probability, try to use an existing value. it is better to
+ // have patterns like this:
+ //
+ // (local.set $ref (struct.new $..
+ // (struct.get (local.get $ref))
+ //
+ // Rather than constantly operating on new data each time:
+ //
+ // (local.set $ref (struct.new $..
+ // (struct.get (struct.new $..
+ //
+ // By using local values more, we get more coverage of interesting sequences
+ // of reads and writes to the same objects.
+ auto& typeLocals = funcContext->typeLocals[nonNull];
+ if (!typeLocals.empty()) {
+ return builder.makeLocalGet(pick(typeLocals), nonNull);
+ }
+ // Add a new local and tee it, so later operations can use it.
+ auto index = builder.addVar(funcContext->func, nonNull);
+ // Note we must create the child ref here before adding the local to
+ // typeLocals (or else we might end up using it prematurely).
+ auto* tee = builder.makeLocalTee(index, make(nonNull), nonNull);
+ funcContext->typeLocals[nonNull].push_back(index);
+ return tee;
+}
+
Expression* TranslateToFuzzReader::buildUnary(const UnaryArgs& args) {
return builder.makeUnary(args.a, args.b);
}
@@ -3270,12 +3315,7 @@ Expression* TranslateToFuzzReader::makeStructGet(Type type) {
auto& structFields = typeStructFields[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));
+ auto* ref = makeTrappingRefUse(structType);
// TODO: fuzz signed and unsigned
return builder.makeStructGet(fieldIndex, ref, type);
}
@@ -3287,8 +3327,7 @@ Expression* TranslateToFuzzReader::makeStructSet(Type 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* ref = makeTrappingRefUse(structType);
auto* value = make(fieldType);
return builder.makeStructSet(fieldIndex, ref, value);
}
@@ -3321,8 +3360,7 @@ Expression* TranslateToFuzzReader::makeArrayGet(Type type) {
auto& arrays = typeArrays[type];
assert(!arrays.empty());
auto arrayType = pick(arrays);
- // TODO: also nullable ones? that would increase the risk of traps
- auto* ref = make(Type(arrayType, NonNullable));
+ auto* ref = makeTrappingRefUse(arrayType);
auto* index = make(Type::i32);
// Only rarely emit a plain get which might trap. See related logic in
// ::makePointer().
@@ -3348,8 +3386,7 @@ Expression* TranslateToFuzzReader::makeArraySet(Type 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* ref = makeTrappingRefUse(arrayType);
auto* value = make(elementType);
// Only rarely emit a plain get which might trap. See related logic in
// ::makePointer().
@@ -3369,9 +3406,7 @@ Expression* TranslateToFuzzReader::makeArraySet(Type type) {
Expression* TranslateToFuzzReader::makeI31Get(Type type) {
assert(type == Type::i32);
assert(wasm.features.hasReferenceTypes() && wasm.features.hasGC());
- // TODO: Maybe this should be nullable?
- // https://github.com/WebAssembly/gc/issues/312
- auto* i31 = make(Type(HeapType::i31, NonNullable));
+ auto* i31 = makeTrappingRefUse(HeapType::i31);
return builder.makeI31Get(i31, bool(oneIn(2)));
}
diff --git a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt
index 92b1fc834..e9a93af72 100644
--- a/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt
+++ b/test/passes/translate-to-fuzz_all-features_metrics_noprint.txt
@@ -1,45 +1,36 @@
total
[exports] : 2
- [funcs] : 10
+ [funcs] : 7
[globals] : 16
[imports] : 5
[memories] : 1
[memory-data] : 20
- [table-data] : 5
+ [table-data] : 1
[tables] : 1
[tags] : 2
- [total] : 500
- [vars] : 9
- ArrayNew : 5
- AtomicCmpxchg : 1
- AtomicFence : 2
- AtomicNotify : 2
- Binary : 66
- Block : 49
- Break : 5
- Call : 4
- Const : 121
- Drop : 1
- GlobalGet : 25
- GlobalSet : 24
- I31New : 2
- If : 16
- Load : 17
+ [total] : 350
+ [vars] : 10
+ ArrayLen : 1
+ ArrayNew : 2
+ ArraySet : 1
+ Binary : 61
+ Block : 27
+ Const : 87
+ GlobalGet : 16
+ GlobalSet : 16
+ I31New : 1
+ If : 11
+ Load : 18
LocalGet : 40
- LocalSet : 28
- Loop : 3
- Nop : 9
- RefAs : 2
- RefEq : 2
- RefFunc : 10
- RefNull : 8
- RefTest : 1
- Return : 2
- SIMDExtract : 2
- Select : 4
- StructNew : 8
- StructSet : 1
- TupleExtract : 1
- TupleMake : 8
- Unary : 16
- Unreachable : 15
+ LocalSet : 21
+ Loop : 2
+ Nop : 1
+ RefFunc : 6
+ RefIsNull : 1
+ RefNull : 4
+ Return : 4
+ Store : 1
+ StructNew : 2
+ TupleMake : 7
+ Unary : 9
+ Unreachable : 11