blob: 567aa41a8eb5834eae8f14d81f74a4bae888ba38 [file] [log] [blame]
Ben Murdoch097c5b22016-05-18 11:27:45 +01001// 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
8#include "test/cctest/interpreter/bytecode-expectations-printer.h"
9
10#include "include/libplatform/libplatform.h"
11#include "include/v8.h"
12
13#include "src/base/logging.h"
14#include "src/base/smart-pointers.h"
15#include "src/compiler.h"
16#include "src/interpreter/interpreter.h"
17
18using v8::internal::interpreter::BytecodeExpectationsPrinter;
19
20namespace {
21
22class ProgramOptions final {
23 public:
24 static ProgramOptions FromCommandLine(int argc, char** argv);
25
26 ProgramOptions()
27 : parsing_failed_(false),
28 print_help_(false),
29 read_raw_js_snippet_(false),
30 read_from_stdin_(false),
31 rebaseline_(false),
32 wrap_(true),
33 execute_(true),
34 top_level_(false),
35 legacy_const_(false),
36 do_expressions_(false),
37 const_pool_type_(
38 BytecodeExpectationsPrinter::ConstantPoolType::kMixed) {}
39
40 bool Validate() const;
41 void UpdateFromHeader(std::istream& stream); // NOLINT
42 void PrintHeader(std::ostream& stream) const; // NOLINT
43
44 bool parsing_failed() const { return parsing_failed_; }
45 bool print_help() const { return print_help_; }
46 bool read_raw_js_snippet() const { return read_raw_js_snippet_; }
47 bool read_from_stdin() const { return read_from_stdin_; }
48 bool write_to_stdout() const {
49 return output_filename_.empty() && !rebaseline_;
50 }
51 bool rebaseline() const { return rebaseline_; }
52 bool wrap() const { return wrap_; }
53 bool execute() const { return execute_; }
54 bool top_level() const { return top_level_; }
55 bool legacy_const() const { return legacy_const_; }
56 bool do_expressions() const { return do_expressions_; }
57 BytecodeExpectationsPrinter::ConstantPoolType const_pool_type() const {
58 return const_pool_type_;
59 }
60 std::string input_filename() const { return input_filename_; }
61 std::string output_filename() const { return output_filename_; }
62 std::string test_function_name() const { return test_function_name_; }
63
64 private:
65 bool parsing_failed_;
66 bool print_help_;
67 bool read_raw_js_snippet_;
68 bool read_from_stdin_;
69 bool rebaseline_;
70 bool wrap_;
71 bool execute_;
72 bool top_level_;
73 bool legacy_const_;
74 bool do_expressions_;
75 BytecodeExpectationsPrinter::ConstantPoolType const_pool_type_;
76 std::string input_filename_;
77 std::string output_filename_;
78 std::string test_function_name_;
79};
80
81class ArrayBufferAllocator final : public v8::ArrayBuffer::Allocator {
82 public:
83 void* Allocate(size_t length) override {
84 void* data = AllocateUninitialized(length);
85 if (data != nullptr) memset(data, 0, length);
86 return data;
87 }
88 void* AllocateUninitialized(size_t length) override { return malloc(length); }
89 void Free(void* data, size_t) override { free(data); }
90};
91
92class V8InitializationScope final {
93 public:
94 explicit V8InitializationScope(const char* exec_path);
95 ~V8InitializationScope();
96
97 v8::Platform* platform() const { return platform_.get(); }
98 v8::Isolate* isolate() const { return isolate_; }
99
100 private:
101 v8::base::SmartPointer<v8::Platform> platform_;
102 v8::Isolate* isolate_;
103
104 DISALLOW_COPY_AND_ASSIGN(V8InitializationScope);
105};
106
107BytecodeExpectationsPrinter::ConstantPoolType ParseConstantPoolType(
108 const char* type_string) {
109 if (strcmp(type_string, "number") == 0) {
110 return BytecodeExpectationsPrinter::ConstantPoolType::kNumber;
111 } else if (strcmp(type_string, "string") == 0) {
112 return BytecodeExpectationsPrinter::ConstantPoolType::kString;
113 } else if (strcmp(type_string, "mixed") == 0) {
114 return BytecodeExpectationsPrinter::ConstantPoolType::kMixed;
115 }
116 return BytecodeExpectationsPrinter::ConstantPoolType::kUnknown;
117}
118
119const char* ConstantPoolTypeToString(
120 BytecodeExpectationsPrinter::ConstantPoolType type) {
121 switch (type) {
122 case BytecodeExpectationsPrinter::ConstantPoolType::kNumber:
123 return "number";
124 case BytecodeExpectationsPrinter::ConstantPoolType::kMixed:
125 return "mixed";
126 case BytecodeExpectationsPrinter::ConstantPoolType::kString:
127 return "string";
128 default:
129 UNREACHABLE();
130 return nullptr;
131 }
132}
133
134bool ParseBoolean(const char* string) {
135 if (strcmp(string, "yes") == 0) {
136 return true;
137 } else if (strcmp(string, "no") == 0) {
138 return false;
139 } else {
140 UNREACHABLE();
141 return false;
142 }
143}
144
145const char* BooleanToString(bool value) { return value ? "yes" : "no"; }
146
147// static
148ProgramOptions ProgramOptions::FromCommandLine(int argc, char** argv) {
149 ProgramOptions options;
150
151 for (int i = 1; i < argc; ++i) {
152 if (strcmp(argv[i], "--help") == 0) {
153 options.print_help_ = true;
154 } else if (strcmp(argv[i], "--raw-js") == 0) {
155 options.read_raw_js_snippet_ = true;
156 } else if (strncmp(argv[i], "--pool-type=", 12) == 0) {
157 options.const_pool_type_ = ParseConstantPoolType(argv[i] + 12);
158 } else if (strcmp(argv[i], "--stdin") == 0) {
159 options.read_from_stdin_ = true;
160 } else if (strcmp(argv[i], "--rebaseline") == 0) {
161 options.rebaseline_ = true;
162 } else if (strcmp(argv[i], "--no-wrap") == 0) {
163 options.wrap_ = false;
164 } else if (strcmp(argv[i], "--no-execute") == 0) {
165 options.execute_ = false;
166 } else if (strcmp(argv[i], "--top-level") == 0) {
167 options.top_level_ = true;
168 } else if (strcmp(argv[i], "--legacy-const") == 0) {
169 options.legacy_const_ = true;
170 } else if (strcmp(argv[i], "--do-expressions") == 0) {
171 options.do_expressions_ = true;
172 } else if (strncmp(argv[i], "--output=", 9) == 0) {
173 options.output_filename_ = argv[i] + 9;
174 } else if (strncmp(argv[i], "--test-function-name=", 21) == 0) {
175 options.test_function_name_ = argv[i] + 21;
176 } else if (strncmp(argv[i], "--", 2) != 0) { // It doesn't start with --
177 if (!options.input_filename_.empty()) {
178 std::cerr << "ERROR: More than one input file specified\n";
179 options.parsing_failed_ = true;
180 break;
181 }
182 options.input_filename_ = argv[i];
183 } else {
184 std::cerr << "ERROR: Unknonwn option " << argv[i] << "\n";
185 options.parsing_failed_ = true;
186 break;
187 }
188 }
189
190 return options;
191}
192
193bool ProgramOptions::Validate() const {
194 if (parsing_failed_) return false;
195 if (print_help_) return true;
196
197 if (const_pool_type_ ==
198 BytecodeExpectationsPrinter::ConstantPoolType::kUnknown) {
199 std::cerr << "ERROR: Unknown constant pool type.\n";
200 return false;
201 }
202
203 if (!read_from_stdin_ && input_filename_.empty()) {
204 std::cerr << "ERROR: No input file specified.\n";
205 return false;
206 }
207
208 if (read_from_stdin_ && !input_filename_.empty()) {
209 std::cerr << "ERROR: Reading from stdin, but input files supplied.\n";
210 return false;
211 }
212
213 if (rebaseline_ && read_raw_js_snippet_) {
214 std::cerr << "ERROR: Cannot use --rebaseline on a raw JS snippet.\n";
215 return false;
216 }
217
218 if (top_level_ && !test_function_name_.empty()) {
219 std::cerr << "ERROR: test function name specified while processing "
220 "top level code.\n";
221 return false;
222 }
223
224 return true;
225}
226
227void ProgramOptions::UpdateFromHeader(std::istream& stream) {
228 std::string line;
229
230 // Skip to the beginning of the options header
231 while (std::getline(stream, line)) {
232 if (line == "---") break;
233 }
234
235 while (std::getline(stream, line)) {
236 if (line.compare(0, 11, "pool type: ") == 0) {
237 const_pool_type_ = ParseConstantPoolType(line.c_str() + 11);
238 } else if (line.compare(0, 9, "execute: ") == 0) {
239 execute_ = ParseBoolean(line.c_str() + 9);
240 } else if (line.compare(0, 6, "wrap: ") == 0) {
241 wrap_ = ParseBoolean(line.c_str() + 6);
242 } else if (line.compare(0, 20, "test function name: ") == 0) {
243 test_function_name_ = line.c_str() + 20;
244 } else if (line.compare(0, 11, "top level: ") == 0) {
245 top_level_ = ParseBoolean(line.c_str() + 11);
246 } else if (line.compare(0, 14, "legacy const: ") == 0) {
247 legacy_const_ = ParseBoolean(line.c_str() + 14);
248 } else if (line.compare(0, 16, "do expressions: ") == 0) {
249 do_expressions_ = ParseBoolean(line.c_str() + 16);
250 } else if (line == "---") {
251 break;
252 } else if (line.empty()) {
253 continue;
254 } else {
255 UNREACHABLE();
256 return;
257 }
258 }
259}
260
261void ProgramOptions::PrintHeader(std::ostream& stream) const { // NOLINT
262 stream << "---"
263 "\npool type: "
264 << ConstantPoolTypeToString(const_pool_type_)
265 << "\nexecute: " << BooleanToString(execute_)
266 << "\nwrap: " << BooleanToString(wrap_);
267
268 if (!test_function_name_.empty()) {
269 stream << "\ntest function name: " << test_function_name_;
270 }
271
272 if (top_level_) stream << "\ntop level: yes";
273 if (legacy_const_) stream << "\nlegacy const: yes";
274 if (do_expressions_) stream << "\ndo expressions: yes";
275
276 stream << "\n\n";
277}
278
279V8InitializationScope::V8InitializationScope(const char* exec_path)
280 : platform_(v8::platform::CreateDefaultPlatform()) {
281 i::FLAG_ignition = true;
282 i::FLAG_always_opt = false;
283 i::FLAG_allow_natives_syntax = true;
284
285 v8::V8::InitializeICU();
286 v8::V8::InitializeExternalStartupData(exec_path);
287 v8::V8::InitializePlatform(platform_.get());
288 v8::V8::Initialize();
289
290 ArrayBufferAllocator allocator;
291 v8::Isolate::CreateParams create_params;
292 create_params.array_buffer_allocator = &allocator;
293
294 isolate_ = v8::Isolate::New(create_params);
295}
296
297V8InitializationScope::~V8InitializationScope() {
298 isolate_->Dispose();
299 v8::V8::Dispose();
300 v8::V8::ShutdownPlatform();
301}
302
303std::string ReadRawJSSnippet(std::istream& stream) { // NOLINT
304 std::stringstream body_buffer;
305 CHECK(body_buffer << stream.rdbuf());
306 return body_buffer.str();
307}
308
309bool ReadNextSnippet(std::istream& stream, std::string* string_out) { // NOLINT
310 std::string line;
311 bool found_begin_snippet = false;
312 string_out->clear();
313 while (std::getline(stream, line)) {
314 if (line == "snippet: \"") {
315 found_begin_snippet = true;
316 continue;
317 }
318 if (!found_begin_snippet) continue;
319 if (line == "\"") return true;
320 CHECK_GE(line.size(), 2u); // We should have the indent
321 string_out->append(line.begin() + 2, line.end());
322 *string_out += '\n';
323 }
324 return false;
325}
326
327std::string UnescapeString(const std::string& escaped_string) {
328 std::string unescaped_string;
329 bool previous_was_backslash = false;
330 for (char c : escaped_string) {
331 if (previous_was_backslash) {
332 // If it was not an escape sequence, emit the previous backslash
333 if (c != '\\' && c != '"') unescaped_string += '\\';
334 unescaped_string += c;
335 previous_was_backslash = false;
336 } else {
337 if (c == '\\') {
338 previous_was_backslash = true;
339 // Defer emission to the point where we can check if it was an escape.
340 } else {
341 unescaped_string += c;
342 }
343 }
344 }
345 return unescaped_string;
346}
347
348void ExtractSnippets(std::vector<std::string>* snippet_list,
349 std::istream& body_stream, // NOLINT
350 bool read_raw_js_snippet) {
351 if (read_raw_js_snippet) {
352 snippet_list->push_back(ReadRawJSSnippet(body_stream));
353 } else {
354 std::string snippet;
355 while (ReadNextSnippet(body_stream, &snippet)) {
356 snippet_list->push_back(UnescapeString(snippet));
357 }
358 }
359}
360
361void GenerateExpectationsFile(std::ostream& stream, // NOLINT
362 const std::vector<std::string>& snippet_list,
363 const ProgramOptions& options,
364 const char* exec_path) {
365 V8InitializationScope platform(exec_path);
366 {
367 v8::Isolate::Scope isolate_scope(platform.isolate());
368 v8::HandleScope handle_scope(platform.isolate());
369 v8::Local<v8::Context> context = v8::Context::New(platform.isolate());
370 v8::Context::Scope context_scope(context);
371
372 BytecodeExpectationsPrinter printer(platform.isolate(),
373 options.const_pool_type());
374 printer.set_wrap(options.wrap());
375 printer.set_execute(options.execute());
376 printer.set_top_level(options.top_level());
377 if (!options.test_function_name().empty()) {
378 printer.set_test_function_name(options.test_function_name());
379 }
380
381 if (options.legacy_const()) i::FLAG_legacy_const = true;
382 if (options.do_expressions()) i::FLAG_harmony_do_expressions = true;
383
384 stream << "#\n# Autogenerated by generate-bytecode-expectations\n#\n\n";
385 options.PrintHeader(stream);
386 for (const std::string& snippet : snippet_list) {
387 printer.PrintExpectation(stream, snippet);
388 }
389 }
390}
391
392void PrintUsage(const char* exec_path) {
393 std::cerr
394 << "\nUsage: " << exec_path
395 << " [OPTIONS]... [INPUT FILE]\n\n"
396 "Options:\n"
397 " --help Print this help message.\n"
398 " --raw-js Read raw JavaScript, instead of the output format.\n"
399 " --stdin Read from standard input instead of file.\n"
400 " --rebaseline Rebaseline input snippet file.\n"
401 " --no-wrap Do not wrap the snippet in a function.\n"
402 " --no-execute Do not execute after compilation.\n"
403 " --test-function-name=foo "
404 "Specify the name of the test function.\n"
405 " --top-level Process top level code, not the top-level function."
406 " --legacy-const Enable legacy_const flag.\n"
407 " --do-expressions Enable harmony_do_expressions flag.\n"
408 " --output=file.name\n"
409 " Specify the output file. If not specified, output goes to "
410 "stdout.\n"
411 " --pool-type=(number|string|mixed)\n"
412 " Specify the type of the entries in the constant pool "
413 "(default: mixed).\n"
414 "\n"
415 "When using --rebaseline, flags --no-wrap, --no-execute, "
416 "--test-function-name\nand --pool-type will be overridden by the "
417 "options specified in the input file\nheader.\n\n"
418 "Each raw JavaScript file is interpreted as a single snippet.\n\n"
419 "This tool is intended as a help in writing tests.\n"
420 "Please, DO NOT blindly copy and paste the output "
421 "into the test suite.\n";
422}
423
424} // namespace
425
426int main(int argc, char** argv) {
427 ProgramOptions options = ProgramOptions::FromCommandLine(argc, argv);
428
429 if (!options.Validate() || options.print_help()) {
430 PrintUsage(argv[0]);
431 return options.print_help() ? 0 : 1;
432 }
433
434 std::ifstream input_file_handle;
435 if (!options.read_from_stdin()) {
436 input_file_handle.open(options.input_filename().c_str());
437 if (!input_file_handle.is_open()) {
438 std::cerr << "ERROR: Could not open '" << options.input_filename()
439 << "' for reading.\n";
440 return 2;
441 }
442 }
443 std::istream& input_stream =
444 options.read_from_stdin() ? std::cin : input_file_handle;
445
446 if (options.rebaseline()) {
447 options.UpdateFromHeader(input_stream);
448 CHECK(options.Validate());
449 }
450
451 std::vector<std::string> snippet_list;
452 ExtractSnippets(&snippet_list, input_stream, options.read_raw_js_snippet());
453
454 std::ofstream output_file_handle;
455 if (!options.write_to_stdout()) {
456 output_file_handle.open(options.rebaseline()
457 ? options.input_filename().c_str()
458 : options.output_filename().c_str());
459 if (!output_file_handle.is_open()) {
460 std::cerr << "ERROR: Could not open '" << options.output_filename()
461 << "' for writing.\n";
462 return 3;
463 }
464 }
465 std::ostream& output_stream =
466 options.write_to_stdout() ? std::cout : output_file_handle;
467
468 GenerateExpectationsFile(output_stream, snippet_list, options, argv[0]);
469}