#include <optional>
#include <random>
#include <string>
#include "analysis/lattice.h"
#include "analysis/liveness-transfer-function.h"
#include "analysis/reaching-definitions-transfer-function.h"
#include "analysis/stack-lattice.h"
#include "support/command-line.h"
#include "tools/fuzzing.h"
#include "tools/fuzzing/random.h"
namespace wasm {
using RandEngine = std::mt19937_64;
using namespace analysis;
std::string LatticeComparisonNames[4] = {
"No Relation", "Equal", "Less", "Greater"};
std::string LatticeComparisonSymbols[4] = {"?", "=", "<", ">"};
uint64_t getSeed() {
std::random_device rand;
return std::uniform_int_distribution<uint64_t>{}(rand);
}
template<typename Lattice, typename TransferFunction> struct AnalysisChecker {
Lattice& lattice;
TransferFunction& transferFunction;
std::string latticeName;
std::string transferFunctionName;
uint64_t latticeElementSeed;
Name funcName;
AnalysisChecker(Lattice& lattice,
TransferFunction& transferFunction,
std::string latticeName,
std::string transferFunctionName,
uint64_t latticeElementSeed,
Name funcName)
: lattice(lattice), transferFunction(transferFunction),
latticeName(latticeName), transferFunctionName(transferFunctionName),
latticeElementSeed(latticeElementSeed), funcName(funcName) {}
void printFailureInfo(std::ostream& os) {
os << "Error for " << transferFunctionName << " and " << latticeName
<< " at lattice element seed " << latticeElementSeed << " and function "
<< funcName << ".\n";
}
void printVerboseFunctionCase(std::ostream& os,
typename Lattice::Element& x,
typename Lattice::Element& y,
typename Lattice::Element& z) {
os << "Using lattice element seed " << latticeElementSeed << "\nGenerated "
<< latticeName << " elements:\n";
x.print(os);
os << ",\n";
y.print(os);
os << ",\n";
z.print(os);
os << "\nfor " << funcName << " to test " << transferFunctionName
<< ".\n\n";
}
void checkReflexivity(typename Lattice::Element& element) {
LatticeComparison result = lattice.compare(element, element);
if (result != LatticeComparison::EQUAL) {
std::stringstream ss;
printFailureInfo(ss);
ss << "Element ";
element.print(ss);
ss << " is not reflexive.\n";
Fatal() << ss.str();
}
}
void checkAntiSymmetry(typename Lattice::Element& x,
typename Lattice::Element& y) {
LatticeComparison result = lattice.compare(x, y);
LatticeComparison reverseResult = lattice.compare(y, x);
if (reverseComparison(result) != reverseResult) {
std::stringstream ss;
printFailureInfo(ss);
x.print(ss);
ss << " " << LatticeComparisonNames[result] << " ";
y.print(ss);
ss << " but reverse direction comparison is "
<< LatticeComparisonNames[reverseResult] << ".\n";
Fatal() << ss.str();
}
}
private:
void printTransitivityError(std::ostream& os,
typename Lattice::Element& a,
typename Lattice::Element& b,
typename Lattice::Element& c,
LatticeComparison ab,
LatticeComparison bc,
LatticeComparison ac) {
printFailureInfo(os);
os << "Elements a = ";
a.print(os);
os << ", b = ";
b.print(os);
os << ", and c = ";
c.print(os);
os << " are not transitive. a" << LatticeComparisonSymbols[ab] << "b and b"
<< LatticeComparisonSymbols[bc] << "c, but a"
<< LatticeComparisonSymbols[ac] << "c.\n";
}
bool violatesTransitivity(LatticeComparison ab,
LatticeComparison bc,
LatticeComparison ac) {
if (ab != LatticeComparison::NO_RELATION &&
(bc == LatticeComparison::EQUAL || bc == ab) && ab != ac) {
return true;
} else if (bc != LatticeComparison::NO_RELATION &&
(ab == LatticeComparison::EQUAL || ab == bc) && bc != ac) {
return true;
}
return false;
}
public:
void checkTransitivity(typename Lattice::Element& x,
typename Lattice::Element& y,
typename Lattice::Element& z) {
LatticeComparison xy = lattice.compare(x, y);
LatticeComparison yz = lattice.compare(y, z);
LatticeComparison xz = lattice.compare(x, z);
LatticeComparison yx = reverseComparison(xy);
LatticeComparison zy = reverseComparison(yz);
if (violatesTransitivity(xy, yz, xz)) {
std::stringstream ss;
printTransitivityError(ss, x, y, z, xy, yz, xz);
Fatal() << ss.str();
} else if (violatesTransitivity(yx, xz, yz)) {
std::stringstream ss;
printTransitivityError(ss, y, x, z, yx, xz, yz);
Fatal() << ss.str();
} else if (violatesTransitivity(xz, zy, xy)) {
std::stringstream ss;
printTransitivityError(ss, x, z, y, xz, zy, xy);
Fatal() << ss.str();
}
}
void checkMonotonicity(const BasicBlock* cfgBlock,
typename Lattice::Element& first,
typename Lattice::Element& second,
typename Lattice::Element& firstResult,
typename Lattice::Element& secondResult) {
LatticeComparison beforeCmp = lattice.compare(first, second);
LatticeComparison afterCmp = lattice.compare(firstResult, secondResult);
if (beforeCmp == LatticeComparison::NO_RELATION) {
return;
} else if (beforeCmp == LatticeComparison::LESS &&
(afterCmp == LatticeComparison::LESS ||
afterCmp == LatticeComparison::EQUAL)) {
return;
} else if (beforeCmp == LatticeComparison::GREATER &&
(afterCmp == LatticeComparison::GREATER ||
afterCmp == LatticeComparison::EQUAL)) {
return;
} else if (beforeCmp == LatticeComparison::EQUAL &&
afterCmp == LatticeComparison::EQUAL) {
return;
}
std::stringstream ss;
printFailureInfo(ss);
ss << "Elements ";
first.print(ss);
ss << " -> ";
firstResult.print(ss);
ss << " and ";
second.print(ss);
ss << " -> ";
secondResult.print(ss);
ss << "\n show that the transfer function is not monotone when given the "
"input:\n";
cfgBlock->print(ss);
ss << "\n";
Fatal() << ss.str();
}
void checkLatticeElements(typename Lattice::Element x,
typename Lattice::Element y,
typename Lattice::Element z) {
checkReflexivity(x);
checkReflexivity(y);
checkReflexivity(z);
checkAntiSymmetry(x, y);
checkAntiSymmetry(x, z);
checkAntiSymmetry(y, z);
checkTransitivity(x, y, z);
}
void checkTransferFunction(CFG& cfg,
typename Lattice::Element x,
typename Lattice::Element y,
typename Lattice::Element z) {
for (auto cfgIter = cfg.begin(); cfgIter != cfg.end(); ++cfgIter) {
typename Lattice::Element xResult = x;
transferFunction.transfer(&(*cfgIter), xResult);
typename Lattice::Element yResult = y;
transferFunction.transfer(&(*cfgIter), yResult);
typename Lattice::Element zResult = z;
transferFunction.transfer(&(*cfgIter), zResult);
checkMonotonicity(&(*cfgIter), x, y, xResult, yResult);
checkMonotonicity(&(*cfgIter), x, z, xResult, zResult);
checkMonotonicity(&(*cfgIter), y, z, yResult, zResult);
}
}
};
struct LivenessChecker {
LivenessTransferFunction transferFunction;
FiniteIntPowersetLattice lattice;
AnalysisChecker<FiniteIntPowersetLattice, LivenessTransferFunction> checker;
LivenessChecker(Function* func, uint64_t latticeElementSeed, Name funcName)
: lattice(func->getNumLocals()), checker(lattice,
transferFunction,
"FiniteIntPowersetLattice",
"LivenessTransferFunction",
latticeElementSeed,
funcName) {}
FiniteIntPowersetLattice::Element getRandomElement(Random& rand) {
FiniteIntPowersetLattice::Element result = lattice.getBottom();
for (size_t i = 0; i < lattice.getSetSize(); ++i) {
result.set(i, rand.oneIn(2));
}
return result;
}
void runChecks(CFG& cfg, Random& rand, bool verbose) {
FiniteIntPowersetLattice::Element x = getRandomElement(rand);
FiniteIntPowersetLattice::Element y = getRandomElement(rand);
FiniteIntPowersetLattice::Element z = getRandomElement(rand);
if (verbose) {
checker.printVerboseFunctionCase(std::cout, x, y, z);
}
checker.checkLatticeElements(x, y, z);
checker.checkTransferFunction(cfg, x, y, z);
}
};
struct ReachingDefinitionsChecker {
LocalGraph::GetSetses getSetses;
LocalGraph::Locations locations;
ReachingDefinitionsTransferFunction transferFunction;
AnalysisChecker<FinitePowersetLattice<LocalSet*>,
ReachingDefinitionsTransferFunction>
checker;
ReachingDefinitionsChecker(Function* func,
uint64_t latticeElementSeed,
Name funcName)
: transferFunction(func, getSetses, locations),
checker(transferFunction.lattice,
transferFunction,
"FinitePowersetLattice<LocalSet*>",
"ReachingDefinitionsTransferFunction",
latticeElementSeed,
funcName) {}
FinitePowersetLattice<LocalSet*>::Element getRandomElement(Random& rand) {
FinitePowersetLattice<LocalSet*>::Element result =
transferFunction.lattice.getBottom();
for (size_t i = 0; i < transferFunction.lattice.getSetSize(); ++i) {
result.set(i, rand.oneIn(2));
}
return result;
}
void runChecks(CFG& cfg, Random& rand, bool verbose) {
FinitePowersetLattice<LocalSet*>::Element x = getRandomElement(rand);
FinitePowersetLattice<LocalSet*>::Element y = getRandomElement(rand);
FinitePowersetLattice<LocalSet*>::Element z = getRandomElement(rand);
if (verbose) {
checker.printVerboseFunctionCase(std::cout, x, y, z);
}
checker.checkLatticeElements(x, y, z);
checker.checkTransferFunction(cfg, x, y, z);
}
};
struct StackLatticeChecker {
FiniteIntPowersetLattice contentLattice;
StackLattice<FiniteIntPowersetLattice> stackLattice;
LivenessTransferFunction transferFunction;
AnalysisChecker<StackLattice<FiniteIntPowersetLattice>,
LivenessTransferFunction>
checker;
StackLatticeChecker(Function* func,
uint64_t latticeElementSeed,
Name funcName)
: contentLattice(func->getNumLocals()), stackLattice(contentLattice),
checker(stackLattice,
transferFunction,
"StackLattice<FiniteIntPowersetLattice>",
"LivenessTransferFunction",
latticeElementSeed,
funcName) {}
StackLattice<FiniteIntPowersetLattice>::Element
getRandomElement(Random& rand) {
StackLattice<FiniteIntPowersetLattice>::Element result =
stackLattice.getBottom();
size_t stackHeight = rand.upTo(15);
for (size_t j = 0; j < stackHeight; ++j) {
FiniteIntPowersetLattice::Element content = contentLattice.getBottom();
for (size_t i = 0; i < contentLattice.getSetSize(); ++i) {
content.set(i, rand.oneIn(2));
}
result.push(std::move(content));
}
return result;
}
void runChecks(Random& rand, bool verbose) {
StackLattice<FiniteIntPowersetLattice>::Element x = getRandomElement(rand);
StackLattice<FiniteIntPowersetLattice>::Element y = getRandomElement(rand);
StackLattice<FiniteIntPowersetLattice>::Element z = getRandomElement(rand);
if (verbose) {
checker.printVerboseFunctionCase(std::cout, x, y, z);
}
checker.checkLatticeElements(x, y, z);
}
};
struct Fuzzer {
bool verbose;
Fuzzer(bool verbose) : verbose(verbose) {}
void runOnFunction(Function* func, uint64_t latticeElementSeed) {
RandEngine getFuncRand(latticeElementSeed);
std::vector<char> funcBytes(128);
for (size_t i = 0; i < funcBytes.size(); i += sizeof(uint64_t)) {
*(uint64_t*)(funcBytes.data() + i) = getFuncRand();
}
Random rand(std::move(funcBytes));
CFG cfg = CFG::fromFunction(func);
switch (rand.upTo(3)) {
case 0: {
LivenessChecker livenessChecker(func, latticeElementSeed, func->name);
livenessChecker.runChecks(cfg, rand, verbose);
break;
}
case 1: {
ReachingDefinitionsChecker reachingDefinitionsChecker(
func, latticeElementSeed, func->name);
reachingDefinitionsChecker.runChecks(cfg, rand, verbose);
break;
}
default: {
StackLatticeChecker stackLatticeChecker(
func, latticeElementSeed, func->name);
stackLatticeChecker.runChecks(rand, verbose);
}
}
}
void run(uint64_t seed,
uint64_t* latticeElementSeed = nullptr,
std::string* funcName = nullptr) {
RandEngine getRand(seed);
std::cout << "Running with seed " << seed << "\n";
std::vector<char> bytes(4096);
for (size_t i = 0; i < bytes.size(); i += sizeof(uint64_t)) {
*(uint64_t*)(bytes.data() + i) = getRand();
}
Module testModule;
TranslateToFuzzReader reader(testModule, std::move(bytes));
reader.build();
if (verbose) {
std::cout << "Generated test module: \n";
std::cout << testModule;
std::cout << "\n";
}
if (latticeElementSeed && funcName) {
runOnFunction(testModule.getFunction(*funcName), *latticeElementSeed);
return;
}
ModuleUtils::iterDefinedFunctions(testModule, [&](Function* func) {
uint64_t funcSeed = getRand();
runOnFunction(func, funcSeed);
});
}
};
}
int main(int argc, const char* argv[]) {
using namespace wasm;
const std::string WasmFuzzTypesOption = "wasm-fuzz-lattices options";
Options options("wasm-fuzz-lattices",
"Fuzz lattices for reflexivity, transitivity, and "
"anti-symmetry, and tranfer functions for monotonicity.");
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));
});
std::optional<uint64_t> latticeElementSeed;
options.add("--lattice-element-seed",
"",
"Seed which generated the lattice elements to be checked.",
WasmFuzzTypesOption,
Options::Arguments::One,
[&](Options*, const std::string& arg) {
latticeElementSeed = uint64_t(std::stoull(arg));
});
std::optional<std::string> functionName;
options.add(
"--function-name",
"",
"Name of the function in the module generated by --seed to be checked.",
WasmFuzzTypesOption,
Options::Arguments::One,
[&](Options*, const std::string& arg) { functionName = arg; });
bool verbose = false;
options.add("--verbose",
"-v",
"Print extra information",
WasmFuzzTypesOption,
Options::Arguments::Zero,
[&](Options*, const std::string& arg) { verbose = true; });
options.parse(argc, argv);
Fuzzer fuzzer{verbose};
if (seed) {
if (latticeElementSeed && functionName) {
fuzzer.run(*seed, &(*latticeElementSeed), &(*functionName));
} else {
fuzzer.run(*seed);
}
} else {
size_t i = 0;
RandEngine nextSeed(getSeed());
while (true) {
std::cout << "Iteration " << ++i << "\n";
fuzzer.run(nextSeed());
}
}
return 0;
}