Skip to content

Commit b2c17a1

Browse files
authored
Merge pull request #1087 from WebAssembly/fuzz-2
Fuzz fixes
2 parents 25cbf64 + 6159fb4 commit b2c17a1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1254
-370
lines changed

src/asm2wasm.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "pass.h"
3333
#include "parsing.h"
3434
#include "ast_utils.h"
35+
#include "ast/branch-utils.h"
3536
#include "wasm-builder.h"
3637
#include "wasm-emscripten.h"
3738
#include "wasm-module-building.h"
@@ -1983,9 +1984,9 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
19831984
// No wasm support, so use a temp local
19841985
ensureI32Temp();
19851986
auto set = allocator.alloc<SetLocal>();
1986-
set->setTee(false);
19871987
set->index = function->getLocalIndex(I32_TEMP);
19881988
set->value = value;
1989+
set->setTee(false);
19891990
set->finalize();
19901991
auto get = [&]() {
19911992
auto ret = allocator.alloc<GetLocal>();
@@ -2335,9 +2336,9 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) {
23352336
nameMapper.popLabelName(more);
23362337
nameMapper.popLabelName(stop);
23372338
// if we never continued, we don't need a loop
2338-
BreakSeeker breakSeeker(more);
2339-
breakSeeker.walk(child);
2340-
if (breakSeeker.found == 0) {
2339+
BranchUtils::BranchSeeker seeker(more);
2340+
seeker.walk(child);
2341+
if (seeker.found == 0) {
23412342
auto block = allocator.alloc<Block>();
23422343
block->list.push_back(child);
23432344
if (isConcreteWasmType(child->type)) {

src/ast/block-utils.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919

2020
#include "literal.h"
2121
#include "wasm.h"
22-
#include "ast_utils.h"
22+
#include "ast/branch-utils.h"
23+
#include "ast/effects.h"
2324

2425
namespace wasm {
2526

@@ -29,7 +30,7 @@ namespace BlockUtils {
2930
template<typename T>
3031
inline Expression* simplifyToContents(Block* block, T* parent, bool allowTypeChange = false) {
3132
auto& list = block->list;
32-
if (list.size() == 1 && !BreakSeeker::has(list[0], block->name)) {
33+
if (list.size() == 1 && !BranchUtils::BranchSeeker::hasNamed(list[0], block->name)) {
3334
// just one element. try to replace the block
3435
auto* singleton = list[0];
3536
auto sideEffects = EffectAnalyzer(parent->getPassOptions(), singleton).hasSideEffects();

src/ast/branch-utils.h

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ inline bool isBranchTaken(Switch* sw) {
3737
sw->condition->type != unreachable;
3838
}
3939

40+
inline bool isBranchTaken(Expression* expr) {
41+
if (auto* br = expr->dynCast<Break>()) {
42+
return isBranchTaken(br);
43+
} else if (auto* sw = expr->dynCast<Switch>()) {
44+
return isBranchTaken(sw);
45+
}
46+
WASM_UNREACHABLE();
47+
}
48+
4049
// returns the set of targets to which we branch that are
4150
// outside of a node
4251
inline std::set<Name> getExitingBranches(Expression* ast) {
@@ -91,6 +100,81 @@ inline std::set<Name> getBranchTargets(Expression* ast) {
91100
return scanner.targets;
92101
}
93102

103+
// Finds if there are branches targeting a name. Note that since names are
104+
// unique in our IR, we just need to look for the name, and do not need
105+
// to analyze scoping.
106+
// By default we ignore untaken branches. You can set named to
107+
// note those as well, then any named branch is noted, even if untaken
108+
struct BranchSeeker : public PostWalker<BranchSeeker> {
109+
Name target;
110+
bool named = false;
111+
112+
Index found;
113+
WasmType valueType;
114+
115+
BranchSeeker(Name target) : target(target), found(0) {}
116+
117+
void noteFound(Expression* value) {
118+
found++;
119+
if (found == 1) valueType = unreachable;
120+
if (!value) valueType = none;
121+
else if (value->type != unreachable) valueType = value->type;
122+
}
123+
124+
void visitBreak(Break *curr) {
125+
if (!named) {
126+
// ignore an unreachable break
127+
if (curr->condition && curr->condition->type == unreachable) return;
128+
if (curr->value && curr->value->type == unreachable) return;
129+
}
130+
// check the break
131+
if (curr->name == target) noteFound(curr->value);
132+
}
133+
134+
void visitSwitch(Switch *curr) {
135+
if (!named) {
136+
// ignore an unreachable switch
137+
if (curr->condition->type == unreachable) return;
138+
if (curr->value && curr->value->type == unreachable) return;
139+
}
140+
// check the switch
141+
for (auto name : curr->targets) {
142+
if (name == target) noteFound(curr->value);
143+
}
144+
if (curr->default_ == target) noteFound(curr->value);
145+
}
146+
147+
static bool has(Expression* tree, Name target) {
148+
if (!target.is()) return false;
149+
BranchSeeker seeker(target);
150+
seeker.walk(tree);
151+
return seeker.found > 0;
152+
}
153+
154+
static Index count(Expression* tree, Name target) {
155+
if (!target.is()) return 0;
156+
BranchSeeker seeker(target);
157+
seeker.walk(tree);
158+
return seeker.found;
159+
}
160+
161+
static bool hasNamed(Expression* tree, Name target) {
162+
if (!target.is()) return false;
163+
BranchSeeker seeker(target);
164+
seeker.named = true;
165+
seeker.walk(tree);
166+
return seeker.found > 0;
167+
}
168+
169+
static Index countNamed(Expression* tree, Name target) {
170+
if (!target.is()) return 0;
171+
BranchSeeker seeker(target);
172+
seeker.named = true;
173+
seeker.walk(tree);
174+
return seeker.found;
175+
}
176+
};
177+
94178
} // namespace BranchUtils
95179

96180
} // namespace wasm

src/ast/effects.h

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/*
2+
* Copyright 2017 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef wasm_ast_effects_h
18+
#define wasm_ast_effects_h
19+
20+
namespace wasm {
21+
22+
// Look for side effects, including control flow
23+
// TODO: optimize
24+
25+
struct EffectAnalyzer : public PostWalker<EffectAnalyzer> {
26+
EffectAnalyzer(PassOptions& passOptions, Expression *ast = nullptr) {
27+
ignoreImplicitTraps = passOptions.ignoreImplicitTraps;
28+
debugInfo = passOptions.debugInfo;
29+
if (ast) analyze(ast);
30+
}
31+
32+
bool ignoreImplicitTraps;
33+
bool debugInfo;
34+
35+
void analyze(Expression *ast) {
36+
breakNames.clear();
37+
walk(ast);
38+
// if we are left with breaks, they are external
39+
if (breakNames.size() > 0) branches = true;
40+
}
41+
42+
bool branches = false; // branches out of this expression
43+
bool calls = false;
44+
std::set<Index> localsRead;
45+
std::set<Index> localsWritten;
46+
std::set<Name> globalsRead;
47+
std::set<Name> globalsWritten;
48+
bool readsMemory = false;
49+
bool writesMemory = false;
50+
bool implicitTrap = false; // a load or div/rem, which may trap. we ignore trap
51+
// differences, so it is ok to reorder these, and we
52+
// also allow reordering them with other effects
53+
// (so a trap may occur later or earlier, if it is
54+
// going to occur anyhow), but we can't remove them,
55+
// they count as side effects
56+
57+
bool accessesLocal() { return localsRead.size() + localsWritten.size() > 0; }
58+
bool accessesGlobal() { return globalsRead.size() + globalsWritten.size() > 0; }
59+
bool accessesMemory() { return calls || readsMemory || writesMemory; }
60+
bool hasSideEffects() { return calls || localsWritten.size() > 0 || writesMemory || branches || globalsWritten.size() > 0 || implicitTrap; }
61+
bool hasAnything() { return branches || calls || accessesLocal() || readsMemory || writesMemory || accessesGlobal() || implicitTrap; }
62+
63+
// checks if these effects would invalidate another set (e.g., if we write, we invalidate someone that reads, they can't be moved past us)
64+
bool invalidates(EffectAnalyzer& other) {
65+
if (branches || other.branches
66+
|| ((writesMemory || calls) && other.accessesMemory())
67+
|| (accessesMemory() && (other.writesMemory || other.calls))) {
68+
return true;
69+
}
70+
for (auto local : localsWritten) {
71+
if (other.localsWritten.count(local) || other.localsRead.count(local)) {
72+
return true;
73+
}
74+
}
75+
for (auto local : localsRead) {
76+
if (other.localsWritten.count(local)) return true;
77+
}
78+
if ((accessesGlobal() && other.calls) || (other.accessesGlobal() && calls)) {
79+
return true;
80+
}
81+
for (auto global : globalsWritten) {
82+
if (other.globalsWritten.count(global) || other.globalsRead.count(global)) {
83+
return true;
84+
}
85+
}
86+
for (auto global : globalsRead) {
87+
if (other.globalsWritten.count(global)) return true;
88+
}
89+
// we are ok to reorder implicit traps, but not conditionalize them
90+
if ((implicitTrap && other.branches) || (other.implicitTrap && branches)) {
91+
return true;
92+
}
93+
return false;
94+
}
95+
96+
void mergeIn(EffectAnalyzer& other) {
97+
branches = branches || other.branches;
98+
calls = calls || other.calls;
99+
readsMemory = readsMemory || other.readsMemory;
100+
writesMemory = writesMemory || other.writesMemory;
101+
for (auto i : other.localsRead) localsRead.insert(i);
102+
for (auto i : other.localsWritten) localsWritten.insert(i);
103+
for (auto i : other.globalsRead) globalsRead.insert(i);
104+
for (auto i : other.globalsWritten) globalsWritten.insert(i);
105+
}
106+
107+
// the checks above happen after the node's children were processed, in the order of execution
108+
// we must also check for control flow that happens before the children, i.e., loops
109+
bool checkPre(Expression* curr) {
110+
if (curr->is<Loop>()) {
111+
branches = true;
112+
return true;
113+
}
114+
return false;
115+
}
116+
117+
bool checkPost(Expression* curr) {
118+
visit(curr);
119+
if (curr->is<Loop>()) {
120+
branches = true;
121+
}
122+
return hasAnything();
123+
}
124+
125+
std::set<Name> breakNames;
126+
127+
void visitBreak(Break *curr) {
128+
breakNames.insert(curr->name);
129+
}
130+
void visitSwitch(Switch *curr) {
131+
for (auto name : curr->targets) {
132+
breakNames.insert(name);
133+
}
134+
breakNames.insert(curr->default_);
135+
}
136+
void visitBlock(Block* curr) {
137+
if (curr->name.is()) breakNames.erase(curr->name); // these were internal breaks
138+
}
139+
void visitLoop(Loop* curr) {
140+
if (curr->name.is()) breakNames.erase(curr->name); // these were internal breaks
141+
}
142+
143+
void visitCall(Call *curr) { calls = true; }
144+
void visitCallImport(CallImport *curr) {
145+
calls = true;
146+
if (debugInfo) {
147+
// debugInfo call imports must be preserved very strongly, do not
148+
// move code around them
149+
branches = true; // !
150+
}
151+
}
152+
void visitCallIndirect(CallIndirect *curr) { calls = true; }
153+
void visitGetLocal(GetLocal *curr) {
154+
localsRead.insert(curr->index);
155+
}
156+
void visitSetLocal(SetLocal *curr) {
157+
localsWritten.insert(curr->index);
158+
}
159+
void visitGetGlobal(GetGlobal *curr) {
160+
globalsRead.insert(curr->name);
161+
}
162+
void visitSetGlobal(SetGlobal *curr) {
163+
globalsWritten.insert(curr->name);
164+
}
165+
void visitLoad(Load *curr) {
166+
readsMemory = true;
167+
if (!ignoreImplicitTraps) implicitTrap = true;
168+
}
169+
void visitStore(Store *curr) {
170+
writesMemory = true;
171+
if (!ignoreImplicitTraps) implicitTrap = true;
172+
}
173+
void visitUnary(Unary *curr) {
174+
if (!ignoreImplicitTraps) {
175+
switch (curr->op) {
176+
case TruncSFloat32ToInt32:
177+
case TruncSFloat32ToInt64:
178+
case TruncUFloat32ToInt32:
179+
case TruncUFloat32ToInt64:
180+
case TruncSFloat64ToInt32:
181+
case TruncSFloat64ToInt64:
182+
case TruncUFloat64ToInt32:
183+
case TruncUFloat64ToInt64: {
184+
implicitTrap = true;
185+
}
186+
default: {}
187+
}
188+
}
189+
}
190+
void visitBinary(Binary *curr) {
191+
if (!ignoreImplicitTraps) {
192+
switch (curr->op) {
193+
case DivSInt32:
194+
case DivUInt32:
195+
case RemSInt32:
196+
case RemUInt32:
197+
case DivSInt64:
198+
case DivUInt64:
199+
case RemSInt64:
200+
case RemUInt64: {
201+
implicitTrap = true;
202+
}
203+
default: {}
204+
}
205+
}
206+
}
207+
void visitReturn(Return *curr) { branches = true; }
208+
void visitHost(Host *curr) { calls = true; }
209+
void visitUnreachable(Unreachable *curr) { branches = true; }
210+
};
211+
212+
} // namespace wasm
213+
214+
#endif // wasm_ast_effects_h
215+

0 commit comments

Comments
 (0)