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
23using v8::internal::interpreter::BytecodeExpectationsPrinter;
24
25#define REPORT_ERROR(MESSAGE) (((std::cerr << "ERROR: ") << MESSAGE) << '\n')
26
27namespace {
28
29#ifdef V8_OS_POSIX
30const char* kGoldenFilesPath = "test/cctest/interpreter/bytecode_expectations/";
31#endif
32
33class 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
96class 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
107class 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
122BytecodeExpectationsPrinter::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
134const 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
149bool 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
160const char* BooleanToString(bool value) { return value ? "yes" : "no"; }
161
162#ifdef V8_OS_POSIX
163
164bool 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
172bool 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
196ProgramOptions 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
253bool 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
303void 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
337void 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
355V8InitializationScope::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
373V8InitializationScope::~V8InitializationScope() {
374  isolate_->Dispose();
375  v8::V8::Dispose();
376  v8::V8::ShutdownPlatform();
377}
378
379std::string ReadRawJSSnippet(std::istream& stream) {  // NOLINT
380  std::stringstream body_buffer;
381  CHECK(body_buffer << stream.rdbuf());
382  return body_buffer.str();
383}
384
385bool 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
403std::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
424void 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
437void 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
467bool 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
487void PrintMessage(v8::Local<v8::Message> message, v8::Local<v8::Value>) {
488  std::cerr << "INFO: " << *v8::String::Utf8Value(message->Get()) << '\n';
489}
490
491void DiscardMessage(v8::Local<v8::Message>, v8::Local<v8::Value>) {}
492
493void 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
528int 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