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