diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index c73797f54c2..96335926dcd 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -3,6 +3,7 @@ FILE(GLOB fuzzing_HEADERS fuzzing/*h) set(fuzzing_SOURCES fuzzing/fuzzing.cpp fuzzing/heap-types.cpp + fuzzing/parameters.cpp fuzzing/random.cpp ${fuzzing_HEADERS} ) diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index b878b0ae615..63f399f949f 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -19,12 +19,6 @@ // This is helpful for fuzzing. // -/* -high chance for set at start of loop - high chance of get of a set local in the scope of that scope - high chance of a tee in that case => loop var -*/ - #include "ir/branch-utils.h" #include "ir/struct-utils.h" #include "support/insert_ordered.h" @@ -61,9 +55,68 @@ struct BinaryArgs { Expression* c; }; +// params + +struct FuzzParams { + // The maximum amount of params to each function. + int MAX_PARAMS; + + // The maximum amount of vars in each function. + int MAX_VARS; + + // The maximum number of globals in a module. + int MAX_GLOBALS; + + // The maximum number of tuple elements. + int MAX_TUPLE_SIZE; + + // The maximum number of struct fields. + int MAX_STRUCT_SIZE; + + // The maximum number of elements in an array. + int MAX_ARRAY_SIZE; + + // The number of nontrivial heap types to generate. + int MIN_HEAPTYPES; + int MAX_HEAPTYPES; + + // some things require luck, try them a few times + int TRIES; + + // beyond a nesting limit, greatly decrease the chance to continue to nest + int NESTING_LIMIT; + + // the maximum size of a block + int BLOCK_FACTOR; + + // the memory that we use, a small portion so that we have a good chance of + // looking at writes (we also look outside of this region with small + // probability) this should be a power of 2 + Address USABLE_MEMORY; + + // the number of runtime iterations (function calls, loop backbranches) we + // allow before we stop execution with a trap, to prevent hangs. 0 means + // no hang protection. + int HANG_LIMIT; + + // the maximum amount of new GC types (structs, etc.) to create + int MAX_NEW_GC_TYPES; + + // the maximum amount of catches in each try (not including a catch-all, if + // present). + int MAX_TRY_CATCHES; + + FuzzParams() { setDefaults(); } + + void setDefaults(); +}; + // main reader class TranslateToFuzzReader { + static constexpr size_t VeryImportant = 4; + static constexpr size_t Important = 2; + public: TranslateToFuzzReader(Module& wasm, std::vector&& input, @@ -179,6 +232,27 @@ class TranslateToFuzzReader { FunctionCreationContext* funcContext = nullptr; + // The fuzzing parameters we use. This may change from function to function or + // even in a more refined manner, so we use an RAII context to manage it. + struct FuzzParamsContext : public FuzzParams { + TranslateToFuzzReader& parent; + + FuzzParamsContext* old; + + FuzzParamsContext(TranslateToFuzzReader& parent) + : parent(parent), old(parent.fuzzParams) { + parent.fuzzParams = this; + } + + ~FuzzParamsContext() { parent.fuzzParams = old; } + }; + + FuzzParamsContext* fuzzParams = nullptr; + + // The default global context we use throughout the process (unless it is + // overridden using another context in an RAII manner). + std::unique_ptr globalParams; + public: int nesting = 0; diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 50191045fed..11fa12d5ba8 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -23,7 +23,6 @@ #include "ir/type-updating.h" #include "support/string.h" #include "tools/fuzzing/heap-types.h" -#include "tools/fuzzing/parameters.h" namespace wasm { @@ -49,6 +48,8 @@ TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, if (wasm.features.hasSIMD()) { loggableTypes.push_back(Type::v128); } + + globalParams = std::make_unique(*this); } TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, @@ -299,7 +300,7 @@ void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { } void TranslateToFuzzReader::build() { - if (HANG_LIMIT > 0) { + if (fuzzParams->HANG_LIMIT > 0) { prepareHangLimitSupport(); } if (allowMemory) { @@ -324,7 +325,7 @@ void TranslateToFuzzReader::build() { auto* func = addFunction(); addInvocations(func); } - if (HANG_LIMIT > 0) { + if (fuzzParams->HANG_LIMIT > 0) { addHangLimitSupport(); } if (allowMemory) { @@ -364,7 +365,7 @@ void TranslateToFuzzReader::setupMemory() { segment->setName(Names::getValidDataSegmentName(wasm, Name::fromInt(i)), false); segment->isPassive = bool(upTo(2)); - size_t segSize = upTo(USABLE_MEMORY * 2); + size_t segSize = upTo(fuzzParams->USABLE_MEMORY * 2); segment->data.resize(segSize); for (size_t j = 0; j < segSize; j++) { segment->data[j] = upTo(512); @@ -385,7 +386,7 @@ void TranslateToFuzzReader::setupMemory() { builder.makeConst(Literal::makeFromInt32(0, memory->addressType)); segment->setName(Names::getValidDataSegmentName(wasm, Name::fromInt(0)), false); - auto num = upTo(USABLE_MEMORY * 2); + auto num = upTo(fuzzParams->USABLE_MEMORY * 2); for (size_t i = 0; i < num; i++) { auto value = upTo(512); segment->data.push_back(value >= 256 ? 0 : (value & 0xff)); @@ -405,8 +406,8 @@ void TranslateToFuzzReader::setupHeapTypes() { // For GC, also generate random types. if (wasm.features.hasGC()) { - auto generator = - HeapTypeGenerator::create(random, wasm.features, upTo(MAX_NEW_GC_TYPES)); + auto generator = HeapTypeGenerator::create( + random, wasm.features, upTo(fuzzParams->MAX_NEW_GC_TYPES)); auto result = generator.builder.build(); if (auto* err = result.getError()) { Fatal() << "Failed to build heap types: " << err->reason << " at index " @@ -601,7 +602,7 @@ void TranslateToFuzzReader::setupGlobals() { } // Create new random globals. - for (size_t index = upTo(MAX_GLOBALS); index > 0; --index) { + for (size_t index = upTo(fuzzParams->MAX_GLOBALS); index > 0; --index) { auto type = getConcreteType(); // Prefer immutable ones as they can be used in global.gets in other // globals, for more interesting patterns. @@ -682,7 +683,7 @@ void TranslateToFuzzReader::finalizeMemory() { memory->initial, Address((maxOffset + Memory::kPageSize - 1) / Memory::kPageSize)); } - memory->initial = std::max(memory->initial, USABLE_MEMORY); + memory->initial = std::max(memory->initial, fuzzParams->USABLE_MEMORY); // Avoid an unlimited memory size, which would make fuzzing very difficult // as different VMs will run out of system memory in different ways. if (memory->max == Memory::kUnlimitedSize) { @@ -783,10 +784,11 @@ void TranslateToFuzzReader::prepareHangLimitSupport() { } void TranslateToFuzzReader::addHangLimitSupport() { - auto glob = builder.makeGlobal(HANG_LIMIT_GLOBAL, - Type::i32, - builder.makeConst(int32_t(HANG_LIMIT)), - Builder::Mutable); + auto glob = + builder.makeGlobal(HANG_LIMIT_GLOBAL, + Type::i32, + builder.makeConst(int32_t(fuzzParams->HANG_LIMIT)), + Builder::Mutable); wasm.addGlobal(std::move(glob)); } @@ -974,7 +976,7 @@ void TranslateToFuzzReader::addHashMemorySupport() { contents.push_back( builder.makeLocalSet(0, builder.makeConst(uint32_t(5381)))); auto zero = Literal::makeFromInt32(0, wasm.memories[0]->addressType); - for (Index i = 0; i < USABLE_MEMORY; i++) { + for (Index i = 0; i < fuzzParams->USABLE_MEMORY; i++) { contents.push_back(builder.makeLocalSet( 0, builder.makeBinary( @@ -1032,7 +1034,7 @@ TranslateToFuzzReader::FunctionCreationContext::~FunctionCreationContext() { // fixup to ensure we validate. TypeUpdating::handleNonDefaultableLocals(func, parent.wasm); - if (HANG_LIMIT > 0) { + if (parent.fuzzParams->HANG_LIMIT > 0) { parent.addHangLimitChecks(func); } assert(breakableStack.empty()); @@ -1048,10 +1050,10 @@ Expression* TranslateToFuzzReader::makeHangLimitCheck() { builder.makeIf( builder.makeUnary(UnaryOp::EqZInt32, builder.makeGlobalGet(HANG_LIMIT_GLOBAL, Type::i32)), - builder.makeSequence( - builder.makeGlobalSet(HANG_LIMIT_GLOBAL, - builder.makeConst(int32_t(HANG_LIMIT))), - builder.makeUnreachable())), + builder.makeSequence(builder.makeGlobalSet(HANG_LIMIT_GLOBAL, + builder.makeConst(int32_t( + fuzzParams->HANG_LIMIT))), + builder.makeUnreachable())), builder.makeGlobalSet( HANG_LIMIT_GLOBAL, builder.makeBinary(BinaryOp::SubInt32, @@ -1161,7 +1163,7 @@ Function* TranslateToFuzzReader::addFunction() { func->name = Names::getValidFunctionName(wasm, "func"); FunctionCreationContext context(*this, func); assert(funcContext->typeLocals.empty()); - Index numParams = upToSquared(MAX_PARAMS); + Index numParams = upToSquared(fuzzParams->MAX_PARAMS); std::vector params; params.reserve(numParams); for (Index i = 0; i < numParams; i++) { @@ -1171,7 +1173,7 @@ Function* TranslateToFuzzReader::addFunction() { auto paramType = Type(params); auto resultType = getControlFlowType(); func->type = Signature(paramType, resultType); - Index numVars = upToSquared(MAX_VARS); + Index numVars = upToSquared(fuzzParams->MAX_VARS); for (Index i = 0; i < numVars; i++) { auto type = getConcreteType(); if (!TypeUpdating::canHandleAsLocal(type)) { @@ -1803,8 +1805,9 @@ Expression* TranslateToFuzzReader::make(Type type) { return makeTrivial(type); } // When we should stop, emit something small (but not necessarily trivial). - if (random.finished() || nesting >= 5 * NESTING_LIMIT || // hard limit - (nesting >= NESTING_LIMIT && !oneIn(3))) { + if (random.finished() || + nesting >= 5 * fuzzParams->NESTING_LIMIT || // hard limit + (nesting >= fuzzParams->NESTING_LIMIT && !oneIn(3))) { if (type.isConcrete()) { if (oneIn(2)) { return makeConst(type); @@ -2031,11 +2034,11 @@ Expression* TranslateToFuzzReader::makeBlock(Type type) { ret->type = type; // so we have it during child creation ret->name = makeLabel(); funcContext->breakableStack.push_back(ret); - Index num = upToSquared(BLOCK_FACTOR - 1); // we add another later - if (nesting >= NESTING_LIMIT / 2) { + Index num = upToSquared(fuzzParams->BLOCK_FACTOR - 1); // we add another later + if (nesting >= fuzzParams->NESTING_LIMIT / 2) { // smaller blocks past the limit num /= 2; - if (nesting >= NESTING_LIMIT && oneIn(2)) { + if (nesting >= fuzzParams->NESTING_LIMIT && oneIn(2)) { // smaller blocks past the limit num /= 2; } @@ -2106,7 +2109,7 @@ Expression* TranslateToFuzzReader::makeCondition() { Expression* TranslateToFuzzReader::makeMaybeBlock(Type type) { // if past the limit, prefer not to emit blocks - if (nesting >= NESTING_LIMIT || oneIn(3)) { + if (nesting >= fuzzParams->NESTING_LIMIT || oneIn(3)) { return make(type); } else { return makeBlock(type); @@ -2153,7 +2156,7 @@ Expression* TranslateToFuzzReader::makeTry(Type type) { auto* body = make(type); std::vector catchTags; std::vector catchBodies; - auto numTags = upTo(MAX_TRY_CATCHES); + auto numTags = upTo(fuzzParams->MAX_TRY_CATCHES); std::unordered_set usedTags; for (Index i = 0; i < numTags; i++) { if (wasm.tags.empty()) { @@ -2215,7 +2218,7 @@ Expression* TranslateToFuzzReader::makeTryTable(Type type) { std::vector catchTags; std::vector catchDests; std::vector catchRefs; - auto numCatches = upTo(MAX_TRY_CATCHES); + auto numCatches = upTo(fuzzParams->MAX_TRY_CATCHES); for (Index i = 0; i <= numCatches; i++) { Name tagName; Type tagType; @@ -2240,7 +2243,7 @@ Expression* TranslateToFuzzReader::makeTryTable(Type type) { // also accept a target that is nullable. vec.push_back(Type(HeapType::exn, NonNullable)); auto tagTypeWithExn = Type(vec); - int tries = TRIES; + int tries = fuzzParams->TRIES; while (tries-- > 0) { auto* target = pick(funcContext->breakableStack); auto dest = getTargetName(target); @@ -2272,7 +2275,7 @@ Expression* TranslateToFuzzReader::makeBreak(Type type) { condition = makeCondition(); } // we need to find a proper target to break to; try a few times - int tries = TRIES; + int tries = fuzzParams->TRIES; while (tries-- > 0) { auto* target = pick(funcContext->breakableStack); auto name = getTargetName(target); @@ -2346,7 +2349,7 @@ Expression* TranslateToFuzzReader::makeBreak(Type type) { } Expression* TranslateToFuzzReader::makeCall(Type type) { - int tries = TRIES; + int tries = fuzzParams->TRIES; bool isReturn; while (tries-- > 0) { Function* target = funcContext->func; @@ -2420,9 +2423,9 @@ Expression* TranslateToFuzzReader::makeCallRef(Type type) { // look for a call target with the right type Function* target; bool isReturn; - size_t i = 0; + decltype(fuzzParams->TRIES) i = 0; while (1) { - if (i == TRIES || wasm.functions.empty()) { + if (i == fuzzParams->TRIES || wasm.functions.empty()) { // We can't find a proper target, give up. return makeTrivial(type); } @@ -2585,10 +2588,14 @@ Expression* TranslateToFuzzReader::makePointer() { if (!allowOOB || !oneIn(10)) { if (wasm.memories[0]->is64()) { ret = builder.makeBinary( - AndInt64, ret, builder.makeConst(int64_t(USABLE_MEMORY - 1))); + AndInt64, + ret, + builder.makeConst(int64_t(fuzzParams->USABLE_MEMORY - 1))); } else { ret = builder.makeBinary( - AndInt32, ret, builder.makeConst(int32_t(USABLE_MEMORY - 1))); + AndInt32, + ret, + builder.makeConst(int32_t(fuzzParams->USABLE_MEMORY - 1))); } } return ret; @@ -3316,7 +3323,7 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) { // will only stop here when we exceed the nesting and reach a nullable one. // (This assumes there is a nullable one, that is, that the types are // inhabitable.) - const auto LIMIT = NESTING_LIMIT + 1; + const auto LIMIT = fuzzParams->NESTING_LIMIT + 1; AutoNester nester(*this); if (type.isNullable() && (random.finished() || nesting >= LIMIT || oneIn(LIMIT - nesting + 1))) { @@ -3384,7 +3391,8 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) { if (!element.type.isDefaultable() || oneIn(2)) { init = makeChild(element.type); } - auto* count = builder.makeConst(int32_t(upTo(MAX_ARRAY_SIZE))); + auto* count = + builder.makeConst(int32_t(upTo(fuzzParams->MAX_ARRAY_SIZE))); return builder.makeArrayNew(type.getHeapType(), count, init); } case HeapTypeKind::Cont: @@ -4011,7 +4019,7 @@ Expression* TranslateToFuzzReader::makeSwitch(Type type) { return make(type); } // we need to find proper targets to break to; try a bunch - int tries = TRIES; + int tries = fuzzParams->TRIES; std::vector names; Type valueType = Type::unreachable; while (tries-- > 0) { @@ -4471,7 +4479,7 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) { // 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; + int tries = fuzzParams->TRIES; Name targetName; Type targetType; while (--tries >= 0) { @@ -4939,7 +4947,7 @@ Type TranslateToFuzzReader::getMVPType() { Type TranslateToFuzzReader::getTupleType() { std::vector elements; - size_t maxElements = 2 + upTo(MAX_TUPLE_SIZE - 1); + size_t maxElements = 2 + upTo(fuzzParams->MAX_TUPLE_SIZE - 1); for (size_t i = 0; i < maxElements; ++i) { auto type = getSingleConcreteType(); // Don't add a non-defaultable type into a tuple, as currently we can't diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index b4833475a2c..6255ab42799 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -19,8 +19,8 @@ #include "ir/gc-type-utils.h" #include "ir/subtypes.h" #include "support/insert_ordered.h" +#include "tools/fuzzing.h" #include "tools/fuzzing/heap-types.h" -#include "tools/fuzzing/parameters.h" namespace wasm { @@ -54,6 +54,8 @@ struct HeapTypeGeneratorImpl { // The index of the type we are currently generating. Index index = 0; + FuzzParams params; + HeapTypeGeneratorImpl(Random& rand, FeatureSet features, size_t n) : result{TypeBuilder(n), std::vector>(n), @@ -216,7 +218,7 @@ struct HeapTypeGeneratorImpl { } Type generateTupleType(Shareability share) { - std::vector types(2 + rand.upTo(MAX_TUPLE_SIZE - 1)); + std::vector types(2 + rand.upTo(params.MAX_TUPLE_SIZE - 1)); for (auto& type : types) { type = generateSingleType(share); } @@ -234,7 +236,7 @@ struct HeapTypeGeneratorImpl { } Signature generateSignature() { - std::vector types(rand.upToSquared(MAX_PARAMS)); + std::vector types(rand.upToSquared(params.MAX_PARAMS)); for (auto& type : types) { type = generateSingleType(Unshared); } @@ -252,7 +254,7 @@ struct HeapTypeGeneratorImpl { } Struct generateStruct(Shareability share) { - std::vector fields(rand.upTo(MAX_STRUCT_SIZE + 1)); + std::vector fields(rand.upTo(params.MAX_STRUCT_SIZE + 1)); for (auto& field : fields) { field = generateField(share); } @@ -595,7 +597,7 @@ struct HeapTypeGeneratorImpl { fields.push_back(generateSubField(field)); } // Width subtyping - Index extra = rand.upTo(MAX_STRUCT_SIZE + 1 - fields.size()); + Index extra = rand.upTo(params.MAX_STRUCT_SIZE + 1 - fields.size()); for (Index i = 0; i < extra; ++i) { fields.push_back(generateField(share)); } diff --git a/src/tools/fuzzing/parameters.cpp b/src/tools/fuzzing/parameters.cpp new file mode 100644 index 00000000000..3220f9625d3 --- /dev/null +++ b/src/tools/fuzzing/parameters.cpp @@ -0,0 +1,53 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "tools/fuzzing.h" +#include "wasm.h" + +namespace wasm { + +void FuzzParams::setDefaults() { + MAX_PARAMS = 10; + + MAX_VARS = 20; + + MAX_GLOBALS = 30; + + MAX_TUPLE_SIZE = 6; + + MAX_STRUCT_SIZE = 6; + + MAX_ARRAY_SIZE = 100; + + MIN_HEAPTYPES = 4; + MAX_HEAPTYPES = 20; + + TRIES = 10; + + NESTING_LIMIT = 11; + + BLOCK_FACTOR = 5; + + USABLE_MEMORY = 16; + + HANG_LIMIT = 100; + + MAX_NEW_GC_TYPES = 25; + + MAX_TRY_CATCHES = 4; +} + +} // namespace wasm diff --git a/src/tools/fuzzing/parameters.h b/src/tools/fuzzing/parameters.h deleted file mode 100644 index eede193a7f3..00000000000 --- a/src/tools/fuzzing/parameters.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2021 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Constants that control fuzzing. - -#ifndef wasm_tools_fuzzing_parameters_h -#define wasm_tools_fuzzing_parameters_h - -#include "wasm.h" - -namespace wasm { - -// The maximum amount of params to each function. -constexpr int MAX_PARAMS = 10; - -// The maximum amount of vars in each function. -constexpr int MAX_VARS = 20; - -// The maximum number of globals in a module. -constexpr int MAX_GLOBALS = 30; - -// The maximum number of tuple elements. -constexpr int MAX_TUPLE_SIZE = 6; - -// The maximum number of struct fields. -static const int MAX_STRUCT_SIZE = 6; - -// The maximum number of elements in an array. -static const int MAX_ARRAY_SIZE = 100; - -// The number of nontrivial heap types to generate. -constexpr int MIN_HEAPTYPES = 4; -constexpr int MAX_HEAPTYPES = 20; - -// some things require luck, try them a few times -constexpr int TRIES = 10; - -// beyond a nesting limit, greatly decrease the chance to continue to nest -constexpr int NESTING_LIMIT = 11; - -// the maximum size of a block -constexpr int BLOCK_FACTOR = 5; - -// the memory that we use, a small portion so that we have a good chance of -// looking at writes (we also look outside of this region with small -// probability) this should be a power of 2 -constexpr Address USABLE_MEMORY = 16; - -// the number of runtime iterations (function calls, loop backbranches) we -// allow before we stop execution with a trap, to prevent hangs. 0 means -// no hang protection. -constexpr int HANG_LIMIT = 100; - -// the maximum amount of new GC types (structs, etc.) to create -constexpr int MAX_NEW_GC_TYPES = 25; - -// the maximum amount of catches in each try (not including a catch-all, if -// present). -constexpr int MAX_TRY_CATCHES = 4; - -// -constexpr size_t VeryImportant = 4; -constexpr size_t Important = 2; - -} // namespace wasm - -#endif // wasm_tools_fuzzing_parameters_h