blob: 1da6a6745206ff15d95804af2296f2ca022f2292 [file] [log] [blame]
/*
* 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.
*/
#include <optional>
#include <random>
#include <string>
#include "support/command-line.h"
#include "tools/fuzzing/heap-types.h"
#include "tools/fuzzing/random.h"
namespace wasm {
using RandEngine = std::mt19937_64;
uint64_t getSeed() {
// Return a (truly) random 64-bit value.
std::random_device rand;
return std::uniform_int_distribution<uint64_t>{}(rand);
}
struct Fuzzer {
bool verbose;
void run(uint64_t seed) {
// TODO: Reset the global type state to avoid monotonically increasing
// memory use.
RandEngine getRand(seed);
std::cout << "Running with seed " << seed << "\n";
// 4kb of random bytes should be enough for anyone!
std::vector<char> bytes(4096);
for (size_t i = 0; i < bytes.size(); i += sizeof(uint64_t)) {
*(uint64_t*)(bytes.data() + i) = getRand();
}
Random rand(std::move(bytes));
// TODO: Options to control the size or set it randomly.
HeapTypeGenerator generator =
HeapTypeGenerator::create(rand, FeatureSet::All, 20);
auto result = generator.builder.build();
if (auto* err = result.getError()) {
Fatal() << "Failed to build types: " << err->reason << " at index "
<< err->index;
}
auto types = *result;
if (verbose) {
printTypes(types);
}
checkSubtypes(types, generator.subtypeIndices);
checkLUBs(types);
}
void printTypes(const std::vector<HeapType>& types) {
std::cout << "Built " << types.size() << " types:\n";
// Record indices to use as names.
std::unordered_map<HeapType, size_t> typeIndices;
for (size_t i = 0; i < types.size(); ++i) {
typeIndices.insert({types[i], i});
}
for (size_t i = 0; i < types.size(); ++i) {
auto type = types[i];
std::cout << "(type $" << i << ' ';
if (auto prev = typeIndices[type]; !type.isBasic() && prev != i) {
std::cout << "identical to $" << prev;
} else {
std::cout << type.print([&](HeapType t) -> TypeNames {
if (auto it = typeIndices.find(t); it != typeIndices.end()) {
return {std::to_string(it->second), {}};
} else {
Fatal() << "trying to print unknown heap type";
}
});
}
std::cout << ")\n";
}
}
void checkSubtypes(const std::vector<HeapType>& types,
const std::vector<std::vector<Index>>& subtypeIndices) {
for (size_t super = 0; super < types.size(); ++super) {
for (auto sub : subtypeIndices[super]) {
if (!HeapType::isSubType(types[sub], types[super])) {
Fatal() << "HeapType " << sub << " should be a subtype of HeapType "
<< super << " but is not!\n"
<< sub << ": " << types[sub] << "\n"
<< super << ": " << types[super] << "\n";
}
}
}
}
void checkLUBs(const std::vector<HeapType>& types) {
// For each unordered pair of types...
for (size_t i = 0; i < types.size(); ++i) {
for (size_t j = i; j < types.size(); ++j) {
HeapType a = types[i], b = types[j];
// Check that their LUB is stable when calculated multiple times and in
// reverse order.
HeapType lub = HeapType::getLeastUpperBound(a, b);
if (lub != HeapType::getLeastUpperBound(b, a) ||
lub != HeapType::getLeastUpperBound(a, b)) {
Fatal() << "Could not calculate a stable LUB of HeapTypes " << i
<< " and " << j << "!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n";
}
// Check that each type is a subtype of the LUB.
if (!HeapType::isSubType(a, lub)) {
Fatal() << "HeapType " << i
<< " is not a subtype of its LUB with HeapType " << j << "!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n"
<< "lub: " << lub << "\n";
}
if (!HeapType::isSubType(b, lub)) {
Fatal() << "HeapType " << j
<< " is not a subtype of its LUB with HeapType " << i << "!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n"
<< "lub: " << lub << "\n";
}
// Check that the LUB of each type and the original LUB is still the
// original LUB.
if (lub != HeapType::getLeastUpperBound(a, lub)) {
Fatal() << "The LUB of HeapType " << i << " and HeapType " << j
<< " should be the LUB of itself and HeapType " << i
<< ", but it is not!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n"
<< "lub: " << lub << "\n";
}
if (lub != HeapType::getLeastUpperBound(lub, b)) {
Fatal() << "The LUB of HeapType " << i << " and HeapType " << j
<< " should be the LUB of itself and HeapType " << j
<< ", but it is not!\n"
<< i << ": " << a << "\n"
<< j << ": " << b << "\n"
<< "lub: " << lub << "\n";
}
}
}
}
};
} // namespace wasm
int main(int argc, const char* argv[]) {
using namespace wasm;
const std::string WasmFuzzTypesOption = "wasm-fuzz-types options";
Options options("wasm-fuzz-types",
"Fuzz type construction, canonicalization, and operations");
std::optional<uint64_t> seed;
options.add("--seed",
"",
"Run a single workload generated by the given seed",
WasmFuzzTypesOption,
Options::Arguments::One,
[&](Options*, const std::string& arg) {
seed = uint64_t(std::stoull(arg));
});
bool verbose = false;
options.add("--verbose",
"-v",
"Print extra information",
WasmFuzzTypesOption,
Options::Arguments::Zero,
[&](Options*, const std::string& arg) { verbose = true; });
TypeSystem system = TypeSystem::Nominal;
options.add(
"--nominal",
"",
"Use the nominal type system (default)",
WasmFuzzTypesOption,
Options::Arguments::Zero,
[&](Options*, const std::string& arg) { system = TypeSystem::Nominal; });
options.add("--structural",
"",
"Use the equirecursive type system",
WasmFuzzTypesOption,
Options::Arguments::Zero,
[&](Options*, const std::string& arg) {
system = TypeSystem::Equirecursive;
});
options.add("--hybrid",
"",
"Use the isorecursive hybrid type system",
WasmFuzzTypesOption,
Options::Arguments::Zero,
[&](Options*, const std::string& arg) {
system = TypeSystem::Isorecursive;
});
options.parse(argc, argv);
setTypeSystem(system);
Fuzzer fuzzer{verbose};
if (seed) {
// Run just a single workload with the given seed.
fuzzer.run(*seed);
} else {
// Continuously run workloads with new randomly generated seeds.
size_t i = 0;
RandEngine nextSeed(getSeed());
while (true) {
std::cout << "Iteration " << ++i << "\n";
fuzzer.run(nextSeed());
}
}
return 0;
}