Skip to content

Commit 4b866e8

Browse files
authored
Fuzzer: Use SubtypingDiscoverer to mutate (#7933)
Rather than mutate by replacing X with something of X's type (or a subtype), find which types fit in X's place - possibly a supertype - and use that.
1 parent 7b18dd1 commit 4b866e8

File tree

4 files changed

+277
-207
lines changed

4 files changed

+277
-207
lines changed

src/tools/fuzzing/fuzzing.cpp

Lines changed: 182 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "ir/iteration.h"
2020
#include "ir/local-structural-dominance.h"
2121
#include "ir/module-utils.h"
22+
#include "ir/subtype-exprs.h"
2223
#include "ir/subtypes.h"
2324
#include "ir/type-updating.h"
2425
#include "support/string.h"
@@ -1733,153 +1734,219 @@ void TranslateToFuzzReader::mutate(Function* func) {
17331734
// reasonable chance of making some changes.
17341735
percentChance = std::max(percentChance, Index(3));
17351736

1737+
// First, find things to replace and their types. SubtypingDiscoverer needs to
1738+
// do this in a single, full walk (as types of children depend on parents, and
1739+
// even block targets).
1740+
struct Finder
1741+
: public ControlFlowWalker<Finder, SubtypingDiscoverer<Finder>> {
1742+
// Maps children we can replace to the types we can replace them with. We
1743+
// only store nontrivial ones (i.e., where the type is not just the child's
1744+
// type).
1745+
std::unordered_map<Expression*, Type> childTypes;
1746+
1747+
// We only care about constraints on Expression* things.
1748+
void noteSubtype(Type sub, Type super) {}
1749+
void noteSubtype(HeapType sub, HeapType super) {}
1750+
1751+
void noteSubtype(Type sub, Expression* super) {
1752+
// The expression must be a supertype of a fixed type. Nothing to do.
1753+
}
1754+
void noteSubtype(Expression* sub, Type super) {
1755+
if (super.isRef() && sub->type != super) {
1756+
// This is a nontrivial opportunity to replace sub with a given type.
1757+
childTypes[sub] = super;
1758+
}
1759+
}
1760+
void noteSubtype(Expression* sub, Expression* super) {
1761+
noteSubtype(sub, super->type);
1762+
}
1763+
void noteNonFlowSubtype(Expression* sub, Type super) {
1764+
noteSubtype(sub, super);
1765+
}
1766+
1767+
// TODO: Many casts can accept the top type. We may need to use visit*(), to
1768+
// handle each expression class separately.
1769+
void noteCast(HeapType src, HeapType dst) {}
1770+
void noteCast(Expression* src, Type dst) {}
1771+
void noteCast(Expression* src, Expression* dst) {}
1772+
} finder;
1773+
1774+
if (oneIn(2)) {
1775+
// |finder| reads the IR, and it must be totally valid - e.g. breaks have a
1776+
// proper break target - or else we'd hit internal errors. Fix it up first.
1777+
// (Otherwise, fixing it up is done once after mutation, and in that case
1778+
// we can mutate the IR in simple ways but not read it using
1779+
// useSubtypingDiscoverer). We avoid always doing this second fixup as it
1780+
// may bias the code in some ways.
1781+
fixAfterChanges(func);
1782+
finder.walkFunctionInModule(func, &wasm);
1783+
}
1784+
1785+
// Next, modify things.
17361786
struct Modder : public PostWalker<Modder, UnifiedExpressionVisitor<Modder>> {
17371787
TranslateToFuzzReader& parent;
17381788
Index percentChance;
1789+
Finder& finder;
17391790

17401791
// Whether to replace with unreachable. This can lead to less code getting
17411792
// executed, so we don't want to do it all the time even in a big function.
17421793
bool allowUnreachable;
17431794

1744-
Modder(TranslateToFuzzReader& parent, Index percentChance)
1745-
: parent(parent), percentChance(percentChance) {
1795+
Modder(TranslateToFuzzReader& parent, Index percentChance, Finder& finder)
1796+
: parent(parent), percentChance(percentChance), finder(finder) {
17461797
// If the parent allows it then sometimes replace with an unreachable, and
17471798
// sometimes not. Even if we allow it, only do it in certain functions
17481799
// (half the time) and only do it rarely (see below).
17491800
allowUnreachable = parent.allowAddingUnreachableCode && parent.oneIn(2);
17501801
}
17511802

17521803
void visitExpression(Expression* curr) {
1753-
if (parent.upTo(100) < percentChance &&
1754-
parent.canBeArbitrarilyReplaced(curr)) {
1755-
// We can replace in various modes, see below. Generate a random number
1756-
// up to 100 to help us there.
1757-
int mode = parent.upTo(100);
1758-
1759-
if (allowUnreachable && mode < 5) {
1760-
replaceCurrent(parent.make(Type::unreachable));
1761-
return;
1804+
// See if we want to replace it.
1805+
if (!parent.canBeArbitrarilyReplaced(curr) ||
1806+
parent.upTo(100) >= percentChance) {
1807+
return;
1808+
}
1809+
1810+
// Find the type to replace with.
1811+
auto type = curr->type;
1812+
if (type.isRef()) {
1813+
auto iter = finder.childTypes.find(curr);
1814+
if (iter != finder.childTypes.end()) {
1815+
type = iter->second;
1816+
// We can only be given a less-refined type (certainly we can replace
1817+
// curr with its own type).
1818+
assert(Type::isSubType(curr->type, type));
1819+
// We only store an interesting non-trivial type.
1820+
assert(type != curr->type);
17621821
}
1822+
}
17631823

1764-
// For constants, perform only a small tweaking in some cases.
1765-
// TODO: more minor tweaks to immediates, like making a load atomic or
1766-
// not, changing an offset, etc.
1767-
if (auto* c = curr->dynCast<Const>()) {
1768-
if (mode < 50) {
1769-
c->value = parent.tweak(c->value);
1770-
} else {
1771-
// Just replace the entire thing.
1772-
replaceCurrent(parent.make(curr->type));
1773-
}
1774-
return;
1824+
// We can replace in various modes, see below. Generate a random number
1825+
// up to 100 to help us there.
1826+
int mode = parent.upTo(100);
1827+
1828+
if (allowUnreachable && mode < 5) {
1829+
replaceCurrent(parent.make(Type::unreachable));
1830+
return;
1831+
}
1832+
1833+
// For constants, perform only a small tweaking in some cases.
1834+
// TODO: more minor tweaks to immediates, like making a load atomic or
1835+
// not, changing an offset, etc.
1836+
if (auto* c = curr->dynCast<Const>()) {
1837+
if (mode < 50) {
1838+
c->value = parent.tweak(c->value);
1839+
} else {
1840+
// Just replace the entire thing.
1841+
replaceCurrent(parent.make(type));
17751842
}
1843+
return;
1844+
}
17761845

1777-
// Generate a replacement for the expression, and by default replace all
1778-
// of |curr| (including children) with that replacement, but in some
1779-
// cases we can do more subtle things.
1846+
// Generate a replacement for the expression, and by default replace all
1847+
// of |curr| (including children) with that replacement, but in some
1848+
// cases we can do more subtle things.
1849+
//
1850+
// Note that such a replacement is not always valid due to nesting of
1851+
// labels, but we'll fix that up later. Note also that make() picks a
1852+
// subtype, so this has a chance to replace us with anything that is
1853+
// valid to put here.
1854+
auto* rep = parent.make(type);
1855+
if (mode < 33 && rep->type != Type::none) {
1856+
// This has a non-none type. Replace the output, keeping the
1857+
// expression and its children in a drop. This "interposes" between
1858+
// this expression and its parent, something like this:
17801859
//
1781-
// Note that such a replacement is not always valid due to nesting of
1782-
// labels, but we'll fix that up later. Note also that make() picks a
1783-
// subtype, so this has a chance to replace us with anything that is
1784-
// valid to put here.
1785-
auto* rep = parent.make(curr->type);
1786-
if (mode < 33 && rep->type != Type::none) {
1787-
// This has a non-none type. Replace the output, keeping the
1788-
// expression and its children in a drop. This "interposes" between
1789-
// this expression and its parent, something like this:
1790-
//
1791-
// (D
1792-
// (A
1793-
// (B)
1794-
// (C)
1795-
// )
1796-
// )
1797-
////
1798-
// => ;; keep A, replace it in the parent
1860+
// (D
1861+
// (A
1862+
// (B)
1863+
// (C)
1864+
// )
1865+
// )
1866+
////
1867+
// => ;; keep A, replace it in the parent
1868+
//
1869+
// (D
1870+
// (block
1871+
// (drop
1872+
// (A
1873+
// (B)
1874+
// (C)
1875+
// )
1876+
// )
1877+
// (NEW)
1878+
// )
1879+
// )
1880+
//
1881+
// We also sometimes try to insert A as a child of NEW, so we actually
1882+
// interpose directly:
1883+
//
1884+
// (D
1885+
// (NEW
1886+
// (A
1887+
// (B)
1888+
// (C)
1889+
// )
1890+
// )
1891+
// )
1892+
//
1893+
// We do not do that all the time, as inserting a drop is actually an
1894+
// important situation to test: the drop makes the output of A unused,
1895+
// which may let optimizations remove it.
1896+
if ((mode & 1) && replaceChildWith(rep, curr)) {
1897+
// We managed to replace one of the children with curr, and have
1898+
// nothing more to do.
1899+
} else {
1900+
// Drop curr and append.
1901+
rep = parent.builder.makeSequence(parent.builder.makeDrop(curr), rep);
1902+
}
1903+
} else if (mode >= 66 && !Properties::isControlFlowStructure(curr)) {
1904+
ChildIterator children(curr);
1905+
auto numChildren = children.getNumChildren();
1906+
if (numChildren > 0 && numChildren < 5) {
1907+
// This is a normal (non-control-flow) expression with at least one
1908+
// child (and not an excessive amount of them; see the processing
1909+
// below). "Interpose" between the children and this expression by
1910+
// keeping them and replacing the parent |curr|. We do this by
1911+
// generating drops of the children, like this:
17991912
//
1800-
// (D
1801-
// (block
1802-
// (drop
1803-
// (A
1804-
// (B)
1805-
// (C)
1806-
// )
1807-
// )
1808-
// (NEW)
1809-
// )
1810-
// )
1913+
// (A
1914+
// (B)
1915+
// (C)
1916+
// )
18111917
//
1812-
// We also sometimes try to insert A as a child of NEW, so we actually
1813-
// interpose directly:
1918+
// => ;; keep children, replace A
18141919
//
1815-
// (D
1816-
// (NEW
1817-
// (A
1818-
// (B)
1819-
// (C)
1820-
// )
1821-
// )
1822-
// )
1920+
// (block
1921+
// (drop (B))
1922+
// (drop (C))
1923+
// (NEW)
1924+
// )
18231925
//
1824-
// We do not do that all the time, as inserting a drop is actually an
1825-
// important situation to test: the drop makes the output of A unused,
1826-
// which may let optimizations remove it.
1827-
if ((mode & 1) && replaceChildWith(rep, curr)) {
1828-
// We managed to replace one of the children with curr, and have
1829-
// nothing more to do.
1830-
} else {
1831-
// Drop curr and append.
1832-
rep =
1833-
parent.builder.makeSequence(parent.builder.makeDrop(curr), rep);
1834-
}
1835-
} else if (mode >= 66 && !Properties::isControlFlowStructure(curr)) {
1836-
ChildIterator children(curr);
1837-
auto numChildren = children.getNumChildren();
1838-
if (numChildren > 0 && numChildren < 5) {
1839-
// This is a normal (non-control-flow) expression with at least one
1840-
// child (and not an excessive amount of them; see the processing
1841-
// below). "Interpose" between the children and this expression by
1842-
// keeping them and replacing the parent |curr|. We do this by
1843-
// generating drops of the children, like this:
1844-
//
1845-
// (A
1846-
// (B)
1847-
// (C)
1848-
// )
1849-
//
1850-
// => ;; keep children, replace A
1851-
//
1852-
// (block
1853-
// (drop (B))
1854-
// (drop (C))
1855-
// (NEW)
1856-
// )
1857-
//
1858-
auto* block = parent.builder.makeBlock();
1859-
for (auto* child : children) {
1860-
// Only drop the child if we can't replace it as one of NEW's
1861-
// children. This does a linear scan of |rep| which is the reason
1862-
// for the above limit on the number of children.
1863-
if (!replaceChildWith(rep, child)) {
1864-
block->list.push_back(parent.builder.makeDrop(child));
1865-
}
1926+
auto* block = parent.builder.makeBlock();
1927+
for (auto* child : children) {
1928+
// Only drop the child if we can't replace it as one of NEW's
1929+
// children. This does a linear scan of |rep| which is the reason
1930+
// for the above limit on the number of children.
1931+
if (!replaceChildWith(rep, child)) {
1932+
block->list.push_back(parent.builder.makeDrop(child));
18661933
}
1934+
}
18671935

1868-
if (!block->list.empty()) {
1869-
// We need the block, that is, we did not find a place for all the
1870-
// children.
1871-
block->list.push_back(rep);
1872-
block->finalize();
1873-
rep = block;
1874-
}
1936+
if (!block->list.empty()) {
1937+
// We need the block, that is, we did not find a place for all the
1938+
// children.
1939+
block->list.push_back(rep);
1940+
block->finalize();
1941+
rep = block;
18751942
}
18761943
}
1877-
replaceCurrent(rep);
18781944
}
1945+
replaceCurrent(rep);
18791946
}
18801947
};
18811948

1882-
Modder modder(*this, percentChance);
1949+
Modder modder(*this, percentChance, finder);
18831950
modder.walkFunctionInModule(func, &wasm);
18841951
}
18851952

0 commit comments

Comments
 (0)