Home | History | Annotate | Download | only in interpreter
      1 // Copyright 2016 the V8 project authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <cstring>
      6 #include <fstream>
      7 #include <vector>
      8 
      9 #include "test/cctest/interpreter/bytecode-expectations-printer.h"
     10 
     11 #include "include/libplatform/libplatform.h"
     12 #include "include/v8.h"
     13 
     14 #include "src/base/logging.h"
     15 #include "src/base/smart-pointers.h"
     16 #include "src/compiler.h"
     17 #include "src/interpreter/interpreter.h"
     18 
     19 #ifdef V8_OS_POSIX
     20 #include <dirent.h>
     21 #endif
     22 
     23 using v8::internal::interpreter::BytecodeExpectationsPrinter;
     24 
     25 #define REPORT_ERROR(MESSAGE) (((std::cerr << "ERROR: ") << MESSAGE) << '\n')
     26 
     27 namespace {
     28 
     29 #ifdef V8_OS_POSIX
     30 const char* kGoldenFilesPath = "test/cctest/interpreter/bytecode_expectations/";
     31 #endif
     32 
     33 class ProgramOptions final {
     34  public:
     35   static ProgramOptions FromCommandLine(int argc, char** argv);
     36 
     37   ProgramOptions()
     38       : parsing_failed_(false),
     39         print_help_(false),
     40         read_raw_js_snippet_(false),
     41         read_from_stdin_(false),
     42         rebaseline_(false),
     43         wrap_(true),
     44         execute_(true),
     45         top_level_(false),
     46         do_expressions_(false),
     47         ignition_generators_(false),
     48         verbose_(false),
     49         const_pool_type_(
     50             BytecodeExpectationsPrinter::ConstantPoolType::kMixed) {}
     51 
     52   bool Validate() const;
     53   void UpdateFromHeader(std::istream& stream);   // NOLINT
     54   void PrintHeader(std::ostream& stream) const;  // NOLINT
     55 
     56   bool parsing_failed() const { return parsing_failed_; }
     57   bool print_help() const { return print_help_; }
     58   bool read_raw_js_snippet() const { return read_raw_js_snippet_; }
     59   bool read_from_stdin() const { return read_from_stdin_; }
     60   bool write_to_stdout() const {
     61     return output_filename_.empty() && !rebaseline_;
     62   }
     63   bool rebaseline() const { return rebaseline_; }
     64   bool wrap() const { return wrap_; }
     65   bool execute() const { return execute_; }
     66   bool top_level() const { return top_level_; }
     67   bool do_expressions() const { return do_expressions_; }
     68   bool ignition_generators() const { return ignition_generators_; }
     69   bool verbose() const { return verbose_; }
     70   bool suppress_runtime_errors() const { return rebaseline_ && !verbose_; }
     71   BytecodeExpectationsPrinter::ConstantPoolType const_pool_type() const {
     72     return const_pool_type_;
     73   }
     74   std::vector<std::string> input_filenames() const { return input_filenames_; }
     75   std::string output_filename() const { return output_filename_; }
     76   std::string test_function_name() const { return test_function_name_; }
     77 
     78  private:
     79   bool parsing_failed_;
     80   bool print_help_;
     81   bool read_raw_js_snippet_;
     82   bool read_from_stdin_;
     83   bool rebaseline_;
     84   bool wrap_;
     85   bool execute_;
     86   bool top_level_;
     87   bool do_expressions_;
     88   bool ignition_generators_;
     89   bool verbose_;
     90   BytecodeExpectationsPrinter::ConstantPoolType const_pool_type_;
     91   std::vector<std::string> input_filenames_;
     92   std::string output_filename_;
     93   std::string test_function_name_;
     94 };
     95 
     96 class ArrayBufferAllocator final : public v8::ArrayBuffer::Allocator {
     97  public:
     98   void* Allocate(size_t length) override {
     99     void* data = AllocateUninitialized(length);
    100     if (data != nullptr) memset(data, 0, length);
    101     return data;
    102   }
    103   void* AllocateUninitialized(size_t length) override { return malloc(length); }
    104   void Free(void* data, size_t) override { free(data); }
    105 };
    106 
    107 class V8InitializationScope final {
    108  public:
    109   explicit V8InitializationScope(const char* exec_path);
    110   ~V8InitializationScope();
    111 
    112   v8::Platform* platform() const { return platform_.get(); }
    113   v8::Isolate* isolate() const { return isolate_; }
    114 
    115  private:
    116   v8::base::SmartPointer<v8::Platform> platform_;
    117   v8::Isolate* isolate_;
    118 
    119   DISALLOW_COPY_AND_ASSIGN(V8InitializationScope);
    120 };
    121 
    122 BytecodeExpectationsPrinter::ConstantPoolType ParseConstantPoolType(
    123     const char* type_string) {
    124   if (strcmp(type_string, "number") == 0) {
    125     return BytecodeExpectationsPrinter::ConstantPoolType::kNumber;
    126   } else if (strcmp(type_string, "string") == 0) {
    127     return BytecodeExpectationsPrinter::ConstantPoolType::kString;
    128   } else if (strcmp(type_string, "mixed") == 0) {
    129     return BytecodeExpectationsPrinter::ConstantPoolType::kMixed;
    130   }
    131   return BytecodeExpectationsPrinter::ConstantPoolType::kUnknown;
    132 }
    133 
    134 const char* ConstantPoolTypeToString(
    135     BytecodeExpectationsPrinter::ConstantPoolType type) {
    136   switch (type) {
    137     case BytecodeExpectationsPrinter::ConstantPoolType::kNumber:
    138       return "number";
    139     case BytecodeExpectationsPrinter::ConstantPoolType::kMixed:
    140       return "mixed";
    141     case BytecodeExpectationsPrinter::ConstantPoolType::kString:
    142       return "string";
    143     default:
    144       UNREACHABLE();
    145       return nullptr;
    146   }
    147 }
    148 
    149 bool ParseBoolean(const char* string) {
    150   if (strcmp(string, "yes") == 0) {
    151     return true;
    152   } else if (strcmp(string, "no") == 0) {
    153     return false;
    154   } else {
    155     UNREACHABLE();
    156     return false;
    157   }
    158 }
    159 
    160 const char* BooleanToString(bool value) { return value ? "yes" : "no"; }
    161 
    162 #ifdef V8_OS_POSIX
    163 
    164 bool StrEndsWith(const char* string, const char* suffix) {
    165   int string_size = i::StrLength(string);
    166   int suffix_size = i::StrLength(suffix);
    167   if (string_size < suffix_size) return false;
    168 
    169   return strcmp(string + (string_size - suffix_size), suffix) == 0;
    170 }
    171 
    172 bool CollectGoldenFiles(std::vector<std::string>* golden_file_list,
    173                         const char* directory_path) {
    174   DIR* directory = opendir(directory_path);
    175   if (!directory) return false;
    176 
    177   dirent entry_buffer;
    178   dirent* entry;
    179 
    180   while (readdir_r(directory, &entry_buffer, &entry) == 0 && entry) {
    181     if (StrEndsWith(entry->d_name, ".golden")) {
    182       std::string golden_filename(kGoldenFilesPath);
    183       golden_filename += entry->d_name;
    184       golden_file_list->push_back(golden_filename);
    185     }
    186   }
    187 
    188   closedir(directory);
    189 
    190   return true;
    191 }
    192 
    193 #endif  // V8_OS_POSIX
    194 
    195 // static
    196 ProgramOptions ProgramOptions::FromCommandLine(int argc, char** argv) {
    197   ProgramOptions options;
    198 
    199   for (int i = 1; i < argc; ++i) {
    200     if (strcmp(argv[i], "--help") == 0) {
    201       options.print_help_ = true;
    202     } else if (strcmp(argv[i], "--raw-js") == 0) {
    203       options.read_raw_js_snippet_ = true;
    204     } else if (strncmp(argv[i], "--pool-type=", 12) == 0) {
    205       options.const_pool_type_ = ParseConstantPoolType(argv[i] + 12);
    206     } else if (strcmp(argv[i], "--stdin") == 0) {
    207       options.read_from_stdin_ = true;
    208     } else if (strcmp(argv[i], "--rebaseline") == 0) {
    209       options.rebaseline_ = true;
    210     } else if (strcmp(argv[i], "--no-wrap") == 0) {
    211       options.wrap_ = false;
    212     } else if (strcmp(argv[i], "--no-execute") == 0) {
    213       options.execute_ = false;
    214     } else if (strcmp(argv[i], "--top-level") == 0) {
    215       options.top_level_ = true;
    216     } else if (strcmp(argv[i], "--do-expressions") == 0) {
    217       options.do_expressions_ = true;
    218     } else if (strcmp(argv[i], "--ignition-generators") == 0) {
    219       options.ignition_generators_ = true;
    220     } else if (strcmp(argv[i], "--verbose") == 0) {
    221       options.verbose_ = true;
    222     } else if (strncmp(argv[i], "--output=", 9) == 0) {
    223       options.output_filename_ = argv[i] + 9;
    224     } else if (strncmp(argv[i], "--test-function-name=", 21) == 0) {
    225       options.test_function_name_ = argv[i] + 21;
    226     } else if (strncmp(argv[i], "--", 2) != 0) {  // It doesn't start with --
    227       options.input_filenames_.push_back(argv[i]);
    228     } else {
    229       REPORT_ERROR("Unknown option " << argv[i]);
    230       options.parsing_failed_ = true;
    231       break;
    232     }
    233   }
    234 
    235   if (options.rebaseline_ && options.input_filenames_.empty()) {
    236 #ifdef V8_OS_POSIX
    237     if (options.verbose_) {
    238       std::cout << "Looking for golden files in " << kGoldenFilesPath << '\n';
    239     }
    240     if (!CollectGoldenFiles(&options.input_filenames_, kGoldenFilesPath)) {
    241       REPORT_ERROR("Golden files autodiscovery failed.");
    242       options.parsing_failed_ = true;
    243     }
    244 #else
    245     REPORT_ERROR("Golden files autodiscovery requires a POSIX OS, sorry.");
    246     options.parsing_failed_ = true;
    247 #endif
    248   }
    249 
    250   return options;
    251 }
    252 
    253 bool ProgramOptions::Validate() const {
    254   if (parsing_failed_) return false;
    255   if (print_help_) return true;
    256 
    257   if (const_pool_type_ ==
    258       BytecodeExpectationsPrinter::ConstantPoolType::kUnknown) {
    259     REPORT_ERROR("Unknown constant pool type.");
    260     return false;
    261   }
    262 
    263   if (!read_from_stdin_ && input_filenames_.empty()) {
    264     REPORT_ERROR("No input file specified.");
    265     return false;
    266   }
    267 
    268   if (read_from_stdin_ && !input_filenames_.empty()) {
    269     REPORT_ERROR("Reading from stdin, but input files supplied.");
    270     return false;
    271   }
    272 
    273   if (rebaseline_ && read_raw_js_snippet_) {
    274     REPORT_ERROR("Cannot use --rebaseline on a raw JS snippet.");
    275     return false;
    276   }
    277 
    278   if (rebaseline_ && !output_filename_.empty()) {
    279     REPORT_ERROR("Output file cannot be specified together with --rebaseline.");
    280     return false;
    281   }
    282 
    283   if (rebaseline_ && read_from_stdin_) {
    284     REPORT_ERROR("Cannot --rebaseline when input is --stdin.");
    285     return false;
    286   }
    287 
    288   if (input_filenames_.size() > 1 && !rebaseline_ && !read_raw_js_snippet()) {
    289     REPORT_ERROR(
    290         "Multiple input files, but no --rebaseline or --raw-js specified.");
    291     return false;
    292   }
    293 
    294   if (top_level_ && !test_function_name_.empty()) {
    295     REPORT_ERROR(
    296         "Test function name specified while processing top level code.");
    297     return false;
    298   }
    299 
    300   return true;
    301 }
    302 
    303 void ProgramOptions::UpdateFromHeader(std::istream& stream) {
    304   std::string line;
    305 
    306   // Skip to the beginning of the options header
    307   while (std::getline(stream, line)) {
    308     if (line == "---") break;
    309   }
    310 
    311   while (std::getline(stream, line)) {
    312     if (line.compare(0, 11, "pool type: ") == 0) {
    313       const_pool_type_ = ParseConstantPoolType(line.c_str() + 11);
    314     } else if (line.compare(0, 9, "execute: ") == 0) {
    315       execute_ = ParseBoolean(line.c_str() + 9);
    316     } else if (line.compare(0, 6, "wrap: ") == 0) {
    317       wrap_ = ParseBoolean(line.c_str() + 6);
    318     } else if (line.compare(0, 20, "test function name: ") == 0) {
    319       test_function_name_ = line.c_str() + 20;
    320     } else if (line.compare(0, 11, "top level: ") == 0) {
    321       top_level_ = ParseBoolean(line.c_str() + 11);
    322     } else if (line.compare(0, 16, "do expressions: ") == 0) {
    323       do_expressions_ = ParseBoolean(line.c_str() + 16);
    324     } else if (line.compare(0, 21, "ignition generators: ") == 0) {
    325       ignition_generators_ = ParseBoolean(line.c_str() + 21);
    326     } else if (line == "---") {
    327       break;
    328     } else if (line.empty()) {
    329       continue;
    330     } else {
    331       UNREACHABLE();
    332       return;
    333     }
    334   }
    335 }
    336 
    337 void ProgramOptions::PrintHeader(std::ostream& stream) const {  // NOLINT
    338   stream << "---"
    339             "\npool type: "
    340          << ConstantPoolTypeToString(const_pool_type_)
    341          << "\nexecute: " << BooleanToString(execute_)
    342          << "\nwrap: " << BooleanToString(wrap_);
    343 
    344   if (!test_function_name_.empty()) {
    345     stream << "\ntest function name: " << test_function_name_;
    346   }
    347 
    348   if (top_level_) stream << "\ntop level: yes";
    349   if (do_expressions_) stream << "\ndo expressions: yes";
    350   if (ignition_generators_) stream << "\nignition generators: yes";
    351 
    352   stream << "\n\n";
    353 }
    354 
    355 V8InitializationScope::V8InitializationScope(const char* exec_path)
    356     : platform_(v8::platform::CreateDefaultPlatform()) {
    357   i::FLAG_ignition = true;
    358   i::FLAG_always_opt = false;
    359   i::FLAG_allow_natives_syntax = true;
    360 
    361   v8::V8::InitializeICUDefaultLocation(exec_path);
    362   v8::V8::InitializeExternalStartupData(exec_path);
    363   v8::V8::InitializePlatform(platform_.get());
    364   v8::V8::Initialize();
    365 
    366   ArrayBufferAllocator allocator;
    367   v8::Isolate::CreateParams create_params;
    368   create_params.array_buffer_allocator = &allocator;
    369 
    370   isolate_ = v8::Isolate::New(create_params);
    371 }
    372 
    373 V8InitializationScope::~V8InitializationScope() {
    374   isolate_->Dispose();
    375   v8::V8::Dispose();
    376   v8::V8::ShutdownPlatform();
    377 }
    378 
    379 std::string ReadRawJSSnippet(std::istream& stream) {  // NOLINT
    380   std::stringstream body_buffer;
    381   CHECK(body_buffer << stream.rdbuf());
    382   return body_buffer.str();
    383 }
    384 
    385 bool ReadNextSnippet(std::istream& stream, std::string* string_out) {  // NOLINT
    386   std::string line;
    387   bool found_begin_snippet = false;
    388   string_out->clear();
    389   while (std::getline(stream, line)) {
    390     if (line == "snippet: \"") {
    391       found_begin_snippet = true;
    392       continue;
    393     }
    394     if (!found_begin_snippet) continue;
    395     if (line == "\"") return true;
    396     CHECK_GE(line.size(), 2u);  // We should have the indent
    397     string_out->append(line.begin() + 2, line.end());
    398     *string_out += '\n';
    399   }
    400   return false;
    401 }
    402 
    403 std::string UnescapeString(const std::string& escaped_string) {
    404   std::string unescaped_string;
    405   bool previous_was_backslash = false;
    406   for (char c : escaped_string) {
    407     if (previous_was_backslash) {
    408       // If it was not an escape sequence, emit the previous backslash
    409       if (c != '\\' && c != '"') unescaped_string += '\\';
    410       unescaped_string += c;
    411       previous_was_backslash = false;
    412     } else {
    413       if (c == '\\') {
    414         previous_was_backslash = true;
    415         // Defer emission to the point where we can check if it was an escape.
    416       } else {
    417         unescaped_string += c;
    418       }
    419     }
    420   }
    421   return unescaped_string;
    422 }
    423 
    424 void ExtractSnippets(std::vector<std::string>* snippet_list,
    425                      std::istream& body_stream,  // NOLINT
    426                      bool read_raw_js_snippet) {
    427   if (read_raw_js_snippet) {
    428     snippet_list->push_back(ReadRawJSSnippet(body_stream));
    429   } else {
    430     std::string snippet;
    431     while (ReadNextSnippet(body_stream, &snippet)) {
    432       snippet_list->push_back(UnescapeString(snippet));
    433     }
    434   }
    435 }
    436 
    437 void GenerateExpectationsFile(std::ostream& stream,  // NOLINT
    438                               const std::vector<std::string>& snippet_list,
    439                               const V8InitializationScope& platform,
    440                               const ProgramOptions& options) {
    441   v8::Isolate::Scope isolate_scope(platform.isolate());
    442   v8::HandleScope handle_scope(platform.isolate());
    443   v8::Local<v8::Context> context = v8::Context::New(platform.isolate());
    444   v8::Context::Scope context_scope(context);
    445 
    446   BytecodeExpectationsPrinter printer(platform.isolate(),
    447                                       options.const_pool_type());
    448   printer.set_wrap(options.wrap());
    449   printer.set_execute(options.execute());
    450   printer.set_top_level(options.top_level());
    451   if (!options.test_function_name().empty()) {
    452     printer.set_test_function_name(options.test_function_name());
    453   }
    454 
    455   if (options.do_expressions()) i::FLAG_harmony_do_expressions = true;
    456   if (options.ignition_generators()) i::FLAG_ignition_generators = true;
    457 
    458   stream << "#\n# Autogenerated by generate-bytecode-expectations.\n#\n\n";
    459   options.PrintHeader(stream);
    460   for (const std::string& snippet : snippet_list) {
    461     printer.PrintExpectation(stream, snippet);
    462   }
    463 
    464   i::FLAG_harmony_do_expressions = false;
    465 }
    466 
    467 bool WriteExpectationsFile(const std::vector<std::string>& snippet_list,
    468                            const V8InitializationScope& platform,
    469                            const ProgramOptions& options,
    470                            const std::string& output_filename) {
    471   std::ofstream output_file_handle;
    472   if (!options.write_to_stdout()) {
    473     output_file_handle.open(output_filename.c_str());
    474     if (!output_file_handle.is_open()) {
    475       REPORT_ERROR("Could not open " << output_filename << " for writing.");
    476       return false;
    477     }
    478   }
    479   std::ostream& output_stream =
    480       options.write_to_stdout() ? std::cout : output_file_handle;
    481 
    482   GenerateExpectationsFile(output_stream, snippet_list, platform, options);
    483 
    484   return true;
    485 }
    486 
    487 void PrintMessage(v8::Local<v8::Message> message, v8::Local<v8::Value>) {
    488   std::cerr << "INFO: " << *v8::String::Utf8Value(message->Get()) << '\n';
    489 }
    490 
    491 void DiscardMessage(v8::Local<v8::Message>, v8::Local<v8::Value>) {}
    492 
    493 void PrintUsage(const char* exec_path) {
    494   std::cerr
    495       << "\nUsage: " << exec_path
    496       << " [OPTIONS]... [INPUT FILES]...\n\n"
    497          "Options:\n"
    498          "  --help    Print this help message.\n"
    499          "  --verbose Emit messages about the progress of the tool.\n"
    500          "  --raw-js  Read raw JavaScript, instead of the output format.\n"
    501          "  --stdin   Read from standard input instead of file.\n"
    502          "  --rebaseline  Rebaseline input snippet file.\n"
    503          "  --no-wrap     Do not wrap the snippet in a function.\n"
    504          "  --no-execute  Do not execute after compilation.\n"
    505          "  --test-function-name=foo  "
    506          "Specify the name of the test function.\n"
    507          "  --top-level   Process top level code, not the top-level function.\n"
    508          "  --do-expressions  Enable harmony_do_expressions flag.\n"
    509          "  --ignition-generators  Enable ignition_generators flag.\n"
    510          "  --output=file.name\n"
    511          "      Specify the output file. If not specified, output goes to "
    512          "stdout.\n"
    513          "  --pool-type=(number|string|mixed)\n"
    514          "      Specify the type of the entries in the constant pool "
    515          "(default: mixed).\n"
    516          "\n"
    517          "When using --rebaseline, flags --no-wrap, --no-execute, "
    518          "--test-function-name\nand --pool-type will be overridden by the "
    519          "options specified in the input file\nheader.\n\n"
    520          "Each raw JavaScript file is interpreted as a single snippet.\n\n"
    521          "This tool is intended as a help in writing tests.\n"
    522          "Please, DO NOT blindly copy and paste the output "
    523          "into the test suite.\n";
    524 }
    525 
    526 }  // namespace
    527 
    528 int main(int argc, char** argv) {
    529   ProgramOptions options = ProgramOptions::FromCommandLine(argc, argv);
    530 
    531   if (!options.Validate() || options.print_help()) {
    532     PrintUsage(argv[0]);
    533     return options.print_help() ? 0 : 1;
    534   }
    535 
    536   V8InitializationScope platform(argv[0]);
    537   platform.isolate()->AddMessageListener(
    538       options.suppress_runtime_errors() ? DiscardMessage : PrintMessage);
    539 
    540   std::vector<std::string> snippet_list;
    541 
    542   if (options.read_from_stdin()) {
    543     // Rebaseline will never get here, so we will always take the
    544     // GenerateExpectationsFile at the end of this function.
    545     DCHECK(!options.rebaseline());
    546     ExtractSnippets(&snippet_list, std::cin, options.read_raw_js_snippet());
    547   } else {
    548     for (const std::string& input_filename : options.input_filenames()) {
    549       if (options.verbose()) {
    550         std::cerr << "Processing " << input_filename << '\n';
    551       }
    552 
    553       std::ifstream input_stream(input_filename.c_str());
    554       if (!input_stream.is_open()) {
    555         REPORT_ERROR("Could not open " << input_filename << " for reading.");
    556         return 2;
    557       }
    558 
    559       ProgramOptions updated_options = options;
    560       if (options.rebaseline()) {
    561         updated_options.UpdateFromHeader(input_stream);
    562         CHECK(updated_options.Validate());
    563       }
    564 
    565       ExtractSnippets(&snippet_list, input_stream,
    566                       options.read_raw_js_snippet());
    567 
    568       if (options.rebaseline()) {
    569         if (!WriteExpectationsFile(snippet_list, platform, updated_options,
    570                                    input_filename)) {
    571           return 3;
    572         }
    573         snippet_list.clear();
    574       }
    575     }
    576   }
    577 
    578   if (!options.rebaseline()) {
    579     if (!WriteExpectationsFile(snippet_list, platform, options,
    580                                options.output_filename())) {
    581       return 3;
    582     }
    583   }
    584 }
    585