1 /*
2  * Copyright 2016 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 //
18 // A WebAssembly optimizer, loads code, optionally runs passes on it,
19 // then writes it.
20 //
21 
22 #include <memory>
23 
24 #include "execution-results.h"
25 #include "fuzzing.h"
26 #include "js-wrapper.h"
27 #include "optimization-options.h"
28 #include "pass.h"
29 #include "shell-interface.h"
30 #include "spec-wrapper.h"
31 #include "support/command-line.h"
32 #include "support/debug.h"
33 #include "support/file.h"
34 #include "wasm-binary.h"
35 #include "wasm-interpreter.h"
36 #include "wasm-io.h"
37 #include "wasm-printing.h"
38 #include "wasm-s-parser.h"
39 #include "wasm-validator.h"
40 #include "wasm2c-wrapper.h"
41 
42 #define DEBUG_TYPE "opt"
43 
44 using namespace wasm;
45 
46 // runs a command and returns its output TODO: portability, return code checking
runCommand(std::string command)47 static std::string runCommand(std::string command) {
48 #ifdef __linux__
49   std::string output;
50   const int MAX_BUFFER = 1024;
51   char buffer[MAX_BUFFER];
52   FILE* stream = popen(command.c_str(), "r");
53   while (fgets(buffer, MAX_BUFFER, stream) != NULL) {
54     output.append(buffer);
55   }
56   pclose(stream);
57   return output;
58 #else
59   Fatal() << "TODO: portability for wasm-opt runCommand";
60 #endif
61 }
62 
willRemoveDebugInfo(const std::vector<std::string> & passes)63 static bool willRemoveDebugInfo(const std::vector<std::string>& passes) {
64   for (auto& pass : passes) {
65     if (pass == "strip" || pass == "strip-debug" || pass == "strip-dwarf") {
66       return true;
67     }
68   }
69   return false;
70 }
71 
72 //
73 // main
74 //
75 
main(int argc,const char * argv[])76 int main(int argc, const char* argv[]) {
77   Name entry;
78   bool emitBinary = true;
79   bool converge = false;
80   bool fuzzExecBefore = false;
81   bool fuzzExecAfter = false;
82   std::string extraFuzzCommand;
83   bool translateToFuzz = false;
84   bool fuzzPasses = false;
85   bool fuzzMemory = true;
86   bool fuzzOOB = true;
87   std::string emitJSWrapper;
88   std::string emitSpecWrapper;
89   std::string emitWasm2CWrapper;
90   std::string inputSourceMapFilename;
91   std::string outputSourceMapFilename;
92   std::string outputSourceMapUrl;
93 
94   OptimizationOptions options("wasm-opt", "Read, write, and optimize files");
95   options
96     .add("--output",
97          "-o",
98          "Output file (stdout if not specified)",
99          Options::Arguments::One,
100          [](Options* o, const std::string& argument) {
101            o->extra["output"] = argument;
102            Colors::setEnabled(false);
103          })
104     .add("--emit-text",
105          "-S",
106          "Emit text instead of binary for the output file",
107          Options::Arguments::Zero,
108          [&](Options* o, const std::string& argument) { emitBinary = false; })
109     .add("--converge",
110          "-c",
111          "Run passes to convergence, continuing while binary size decreases",
112          Options::Arguments::Zero,
113          [&](Options* o, const std::string& arguments) { converge = true; })
114     .add(
115       "--fuzz-exec-before",
116       "-feh",
117       "Execute functions before optimization, helping fuzzing find bugs",
118       Options::Arguments::Zero,
119       [&](Options* o, const std::string& arguments) { fuzzExecBefore = true; })
120     .add("--fuzz-exec",
121          "-fe",
122          "Execute functions before and after optimization, helping fuzzing "
123          "find bugs",
124          Options::Arguments::Zero,
125          [&](Options* o, const std::string& arguments) {
126            fuzzExecBefore = fuzzExecAfter = true;
127          })
128     .add("--extra-fuzz-command",
129          "-efc",
130          "An extra command to run on the output before and after optimizing. "
131          "The output is compared between the two, and an error occurs if they "
132          "are not equal",
133          Options::Arguments::One,
134          [&](Options* o, const std::string& arguments) {
135            extraFuzzCommand = arguments;
136          })
137     .add(
138       "--translate-to-fuzz",
139       "-ttf",
140       "Translate the input into a valid wasm module *somehow*, useful for "
141       "fuzzing",
142       Options::Arguments::Zero,
143       [&](Options* o, const std::string& arguments) { translateToFuzz = true; })
144     .add("--fuzz-passes",
145          "-fp",
146          "Pick a random set of passes to run, useful for fuzzing. this depends "
147          "on translate-to-fuzz (it picks the passes from the input)",
148          Options::Arguments::Zero,
149          [&](Options* o, const std::string& arguments) { fuzzPasses = true; })
150     .add("--no-fuzz-memory",
151          "",
152          "don't emit memory ops when fuzzing",
153          Options::Arguments::Zero,
154          [&](Options* o, const std::string& arguments) { fuzzMemory = false; })
155     .add("--no-fuzz-oob",
156          "",
157          "don't emit out-of-bounds loads/stores/indirect calls when fuzzing",
158          Options::Arguments::Zero,
159          [&](Options* o, const std::string& arguments) { fuzzOOB = false; })
160     .add("--emit-js-wrapper",
161          "-ejw",
162          "Emit a JavaScript wrapper file that can run the wasm with some test "
163          "values, useful for fuzzing",
164          Options::Arguments::One,
165          [&](Options* o, const std::string& arguments) {
166            emitJSWrapper = arguments;
167          })
168     .add("--emit-spec-wrapper",
169          "-esw",
170          "Emit a wasm spec interpreter wrapper file that can run the wasm with "
171          "some test values, useful for fuzzing",
172          Options::Arguments::One,
173          [&](Options* o, const std::string& arguments) {
174            emitSpecWrapper = arguments;
175          })
176     .add("--emit-wasm2c-wrapper",
177          "-esw",
178          "Emit a C wrapper file that can run the wasm after it is compiled "
179          "with wasm2c, useful for fuzzing",
180          Options::Arguments::One,
181          [&](Options* o, const std::string& arguments) {
182            emitWasm2CWrapper = arguments;
183          })
184     .add("--input-source-map",
185          "-ism",
186          "Consume source map from the specified file",
187          Options::Arguments::One,
188          [&inputSourceMapFilename](Options* o, const std::string& argument) {
189            inputSourceMapFilename = argument;
190          })
191     .add("--output-source-map",
192          "-osm",
193          "Emit source map to the specified file",
194          Options::Arguments::One,
195          [&outputSourceMapFilename](Options* o, const std::string& argument) {
196            outputSourceMapFilename = argument;
197          })
198     .add("--output-source-map-url",
199          "-osu",
200          "Emit specified string as source map URL",
201          Options::Arguments::One,
202          [&outputSourceMapUrl](Options* o, const std::string& argument) {
203            outputSourceMapUrl = argument;
204          })
205     .add_positional("INFILE",
206                     Options::Arguments::One,
207                     [](Options* o, const std::string& argument) {
208                       o->extra["infile"] = argument;
209                     });
210   options.parse(argc, argv);
211 
212   Module wasm;
213 
214   BYN_TRACE("reading...\n");
215 
216   auto exitOnInvalidWasm = [&](const char* message) {
217     // If the user asked to print the module, print it even if invalid,
218     // as otherwise there is no way to print the broken module (the pass
219     // to print would not be reached).
220     if (std::find(options.passes.begin(), options.passes.end(), "print") !=
221         options.passes.end()) {
222       WasmPrinter::printModule(&wasm);
223     }
224     Fatal() << message;
225   };
226 
227   if (!translateToFuzz) {
228     ModuleReader reader;
229     // Enable DWARF parsing if we were asked for debug info, and were not
230     // asked to remove it.
231     reader.setDWARF(options.passOptions.debugInfo &&
232                     !willRemoveDebugInfo(options.passes));
233     reader.setProfile(options.profile);
234     try {
235       reader.read(options.extra["infile"], wasm, inputSourceMapFilename);
236     } catch (ParseException& p) {
237       p.dump(std::cerr);
238       std::cerr << '\n';
239       Fatal() << "error parsing wasm";
240     } catch (MapParseException& p) {
241       p.dump(std::cerr);
242       std::cerr << '\n';
243       Fatal() << "error parsing wasm source map";
244     } catch (std::bad_alloc&) {
245       Fatal() << "error building module, std::bad_alloc (possibly invalid "
246                  "request for silly amounts of memory)";
247     }
248 
249     options.applyFeatures(wasm);
250 
251     if (options.passOptions.validate) {
252       if (!WasmValidator().validate(wasm)) {
253         exitOnInvalidWasm("error validating input");
254       }
255     }
256   } else {
257     // translate-to-fuzz
258     options.applyFeatures(wasm);
259     TranslateToFuzzReader reader(wasm, options.extra["infile"]);
260     if (fuzzPasses) {
261       reader.pickPasses(options);
262     }
263     reader.setAllowMemory(fuzzMemory);
264     reader.setAllowOOB(fuzzOOB);
265     reader.build();
266     if (options.passOptions.validate) {
267       if (!WasmValidator().validate(wasm)) {
268         WasmPrinter::printModule(&wasm);
269         Fatal() << "error after translate-to-fuzz";
270       }
271     }
272   }
273 
274   if (emitJSWrapper.size() > 0) {
275     // As the code will run in JS, we must legalize it.
276     PassRunner runner(&wasm);
277     runner.add("legalize-js-interface");
278     runner.run();
279   }
280 
281   ExecutionResults results;
282   if (fuzzExecBefore) {
283     results.get(wasm);
284   }
285 
286   if (emitJSWrapper.size() > 0) {
287     std::ofstream outfile;
288     outfile.open(emitJSWrapper, std::ofstream::out);
289     outfile << generateJSWrapper(wasm);
290     outfile.close();
291   }
292   if (emitSpecWrapper.size() > 0) {
293     std::ofstream outfile;
294     outfile.open(emitSpecWrapper, std::ofstream::out);
295     outfile << generateSpecWrapper(wasm);
296     outfile.close();
297   }
298   if (emitWasm2CWrapper.size() > 0) {
299     std::ofstream outfile;
300     outfile.open(emitWasm2CWrapper, std::ofstream::out);
301     outfile << generateWasm2CWrapper(wasm);
302     outfile.close();
303   }
304 
305   std::string firstOutput;
306 
307   if (extraFuzzCommand.size() > 0 && options.extra.count("output") > 0) {
308     BYN_TRACE("writing binary before opts, for extra fuzz command...\n");
309     ModuleWriter writer;
310     writer.setBinary(emitBinary);
311     writer.setDebugInfo(options.passOptions.debugInfo);
312     writer.write(wasm, options.extra["output"]);
313     firstOutput = runCommand(extraFuzzCommand);
314     std::cout << "[extra-fuzz-command first output:]\n" << firstOutput << '\n';
315   }
316 
317   if (!options.runningPasses()) {
318     if (!options.quiet) {
319       std::cerr << "warning: no passes specified, not doing any work\n";
320     }
321   } else {
322     BYN_TRACE("running passes...\n");
323     auto runPasses = [&]() {
324       options.runPasses(wasm);
325       if (options.passOptions.validate) {
326         bool valid = WasmValidator().validate(wasm);
327         if (!valid) {
328           exitOnInvalidWasm("error after opts");
329         }
330       }
331     };
332     runPasses();
333     if (converge) {
334       // Keep on running passes to convergence, defined as binary
335       // size no longer decreasing.
336       auto getSize = [&]() {
337         BufferWithRandomAccess buffer;
338         WasmBinaryWriter writer(&wasm, buffer);
339         writer.write();
340         return buffer.size();
341       };
342       auto lastSize = getSize();
343       while (1) {
344         BYN_TRACE("running iteration for convergence (" << lastSize << ")..\n");
345         runPasses();
346         auto currSize = getSize();
347         if (currSize >= lastSize) {
348           break;
349         }
350         lastSize = currSize;
351       }
352     }
353   }
354 
355   if (fuzzExecAfter) {
356     results.check(wasm);
357   }
358 
359   if (options.extra.count("output") == 0) {
360     if (!options.quiet) {
361       std::cerr << "warning: no output file specified, not emitting output\n";
362     }
363     return 0;
364   }
365 
366   BYN_TRACE("writing...\n");
367   ModuleWriter writer;
368   writer.setBinary(emitBinary);
369   writer.setDebugInfo(options.passOptions.debugInfo);
370   if (outputSourceMapFilename.size()) {
371     writer.setSourceMapFilename(outputSourceMapFilename);
372     writer.setSourceMapUrl(outputSourceMapUrl);
373   }
374   writer.write(wasm, options.extra["output"]);
375 
376   if (extraFuzzCommand.size() > 0) {
377     auto secondOutput = runCommand(extraFuzzCommand);
378     std::cout << "[extra-fuzz-command second output:]\n" << firstOutput << '\n';
379     if (firstOutput != secondOutput) {
380       Fatal() << "extra fuzz command output differs\n";
381     }
382   }
383   return 0;
384 }
385