text: /*
   * Copyright 2016 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.
   */
  
  //
  // A WebAssembly optimizer, loads code, optionally runs passes on it,
  // then writes it.
  //
  
  #include <memory>
  
  #include "pass.h"
  #include "support/command-line.h"
  #include "support/file.h"
  #include "wasm-printing.h"
  #include "wasm-s-parser.h"
  #include "wasm-validator.h"
  #include "wasm-io.h"
  #include "wasm-interpreter.h"
  #include "wasm-binary.h"
  #include "shell-interface.h"
  #include "optimization-options.h"
  #include "execution-results.h"
  #include "fuzzing.h"
  #include "js-wrapper.h"
  #include "spec-wrapper.h"
  
  using namespace wasm;
  
  // runs a command and returns its output TODO: portability, return code checking
  std::string runCommand(std::string command) {
  #ifdef __linux__
    std::string output;
    const int MAX_BUFFER = 1024;
    char buffer[MAX_BUFFER];
    FILE *stream = popen(command.c_str(), "r");
    while (fgets(buffer, MAX_BUFFER, stream) != NULL) {
      output.append(buffer);
    }
    pclose(stream);
    return output;
  #else
    Fatal() << "TODO: portability for wasm-opt runCommand";
  #endif
  }
  
  //
  // main
  //
  
  int main(int argc, const char* argv[]) {
    Name entry;
    bool emitBinary = true;
    bool debugInfo = false;
    bool fuzzExec = false;
    bool fuzzBinary = false;
    std::string extraFuzzCommand;
    bool translateToFuzz = false;
    bool fuzzPasses = false;
    std::string emitJSWrapper;
    std::string emitSpecWrapper;
  
    OptimizationOptions options("wasm-opt", "Read, write, and optimize files");
    options
        .add("--output", "-o", "Output file (stdout if not specified)",
             Options::Arguments::One,
             [](Options* o, const std::string& argument) {
               o->extra["output"] = argument;
               Colors::disable();
             })
        .add("--emit-text", "-S", "Emit text instead of binary for the output file",
             Options::Arguments::Zero,
             [&](Options *o, const std::string& argument) { emitBinary = false; })
        .add("--debuginfo", "-g", "Emit names section and debug info",
             Options::Arguments::Zero,
             [&](Options *o, const std::string& arguments) { debugInfo = true; })
        .add("--fuzz-exec", "-fe", "Execute functions before and after optimization, helping fuzzing find bugs",
             Options::Arguments::Zero,
             [&](Options *o, const std::string& arguments) { fuzzExec = true; })
        .add("--fuzz-binary", "-fb", "Convert to binary and back after optimizations and before fuzz-exec, helping fuzzing find binary format bugs",
             Options::Arguments::Zero,
             [&](Options *o, const std::string& arguments) { fuzzBinary = true; })
        .add("--extra-fuzz-command", "-efc", "An extra command to run on the output before and after optimizing. The output is compared between the two, and an error occurs if they are not equal",
             Options::Arguments::One,
             [&](Options *o, const std::string& arguments) { extraFuzzCommand = arguments; })
        .add("--translate-to-fuzz", "-ttf", "Translate the input into a valid wasm module *somehow*, useful for fuzzing",
             Options::Arguments::Zero,
             [&](Options *o, const std::string& arguments) { translateToFuzz = true; })
        .add("--fuzz-passes", "-fp", "Pick a random set of passes to run, useful for fuzzing. this depends on translate-to-fuzz (it picks the passes from the input)",
             Options::Arguments::Zero,
             [&](Options *o, const std::string& arguments) { fuzzPasses = true; })
        .add("--emit-js-wrapper", "-ejw", "Emit a JavaScript wrapper file that can run the wasm with some test values, useful for fuzzing",
             Options::Arguments::One,
             [&](Options *o, const std::string& arguments) { emitJSWrapper = arguments; })
        .add("--emit-spec-wrapper", "-esw", "Emit a wasm spec interpreter wrapper file that can run the wasm with some test values, useful for fuzzing",
             Options::Arguments::One,
             [&](Options *o, const std::string& arguments) { emitSpecWrapper = arguments; })
        .add_positional("INFILE", Options::Arguments::One,
                        [](Options* o, const std::string& argument) {
                          o->extra["infile"] = argument;
                        });
    options.parse(argc, argv);
  
    Module wasm;
    // It should be safe to just always enable atomics in wasm-opt, because we
    // don't expect any passes to accidentally generate atomic ops
    FeatureSet features = Feature::Atomics;
  
    if (options.debug) std::cerr << "reading...\n";
  
    if (!translateToFuzz) {
      ModuleReader reader;
      reader.setDebug(options.debug);
      try {
        reader.read(options.extra["infile"], wasm);
      } catch (ParseException& p) {
        p.dump(std::cerr);
        Fatal() << "error in parsing input";
      } catch (std::bad_alloc& b) {
        Fatal() << "error in building module, std::bad_alloc (possibly invalid request for silly amounts of memory)";
      }
  
      if (!WasmValidator().validate(wasm, features)) {
        WasmPrinter::printModule(&wasm);
        Fatal() << "error in validating input";
      }
    } else {
      // translate-to-fuzz
      TranslateToFuzzReader reader(wasm, options.extra["infile"]);
      if (fuzzPasses) {
        reader.pickPasses(options);
      }
      reader.build();
      if (!WasmValidator().validate(wasm, features)) {
        WasmPrinter::printModule(&wasm);
        std::cerr << "translate-to-fuzz must always generate a valid module";
        abort();
      }
    }
  
    ExecutionResults results;
    if (fuzzExec) {
      results.get(wasm);
    }
  
    if (emitJSWrapper.size() > 0) {
      std::ofstream outfile;
      outfile.open(emitJSWrapper, std::ofstream::out);
      outfile << generateJSWrapper(wasm);
      outfile.close();
    }
  
    if (emitSpecWrapper.size() > 0) {
      std::ofstream outfile;
      outfile.open(emitSpecWrapper, std::ofstream::out);
      outfile << generateSpecWrapper(wasm);
      outfile.close();
    }
  
    std::string firstOutput;
  
    if (extraFuzzCommand.size() > 0 && options.extra.count("output") > 0) {
      if (options.debug) std::cerr << "writing binary before opts, for extra fuzz command..." << std::endl;
      ModuleWriter writer;
      writer.setDebug(options.debug);
      writer.setBinary(emitBinary);
      writer.setDebugInfo(debugInfo);
      writer.write(wasm, options.extra["output"]);
      firstOutput = runCommand(extraFuzzCommand);
      std::cout << "[extra-fuzz-command first output:]\n" << firstOutput << '\n';
    }
  
    Module* curr = &wasm;
    Module other;
  
    if (fuzzExec && fuzzBinary) {
      BufferWithRandomAccess buffer(false);
      // write the binary
      WasmBinaryWriter writer(&wasm, buffer, false);
      writer.write();
      // read the binary
      auto input = buffer.getAsChars();
      WasmBinaryBuilder parser(other, input, false);
      parser.read();
      bool valid = WasmValidator().validate(other, features);
      if (!valid) {
        WasmPrinter::printModule(&other);
      }
      assert(valid);
      curr = &other;
    }
  
    if (options.runningPasses()) {
      if (options.debug) std::cerr << "running passes...\n";
      options.runPasses(*curr);
      bool valid = WasmValidator().validate(*curr, features);
      if (!valid) {
        WasmPrinter::printModule(&*curr);
      }
      assert(valid);
    }
  
    if (fuzzExec) {
      results.check(*curr);
    }
  
    if (options.extra.count("output") > 0) {
      if (options.debug) std::cerr << "writing..." << std::endl;
      ModuleWriter writer;
      writer.setDebug(options.debug);
      writer.setBinary(emitBinary);
      writer.setDebugInfo(debugInfo);
      writer.write(*curr, options.extra["output"]);
  
      if (extraFuzzCommand.size() > 0) {
        auto secondOutput = runCommand(extraFuzzCommand);
        std::cout << "[extra-fuzz-command second output:]\n" << firstOutput << '\n';
        if (firstOutput != secondOutput) {
          std::cerr << "extra fuzz command output differs\n";
          abort();
        }
      }
    }
  }
  