1 // Copyright 2011 the V8 project authors. All rights reserved. 2 // Redistribution and use in source and binary forms, with or without 3 // modification, are permitted provided that the following conditions are 4 // met: 5 // 6 // * Redistributions of source code must retain the above copyright 7 // notice, this list of conditions and the following disclaimer. 8 // * Redistributions in binary form must reproduce the above 9 // copyright notice, this list of conditions and the following 10 // disclaimer in the documentation and/or other materials provided 11 // with the distribution. 12 // * Neither the name of Google Inc. nor the names of its 13 // contributors may be used to endorse or promote products derived 14 // from this software without specific prior written permission. 15 // 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 #include <stdlib.h> 29 #include <stdarg.h> 30 #include <stdio.h> 31 #include <string.h> 32 33 #include "../include/v8stdint.h" 34 #include "../include/v8-preparser.h" 35 36 #include "../src/preparse-data-format.h" 37 38 namespace i = v8::internal; 39 40 // This file is only used for testing the stand-alone preparser 41 // library. 42 // The first argument must be the path of a JavaScript source file, or 43 // the flags "-e" and the next argument is then the source of a JavaScript 44 // program. 45 // Optionally this can be followed by the word "throws" (case sensitive), 46 // which signals that the parsing is expected to throw - the default is 47 // to expect the parsing to not throw. 48 // The command line can further be followed by a message text (the 49 // *type* of the exception to throw), and even more optionally, the 50 // start and end position reported with the exception. 51 // 52 // This source file is preparsed and tested against the expectations, and if 53 // successful, the resulting preparser data is written to stdout. 54 // Diagnostic output is output on stderr. 55 // The source file must contain only ASCII characters (UTF-8 isn't supported). 56 // The file is read into memory, so it should have a reasonable size. 57 58 59 // Adapts an ASCII string to the UnicodeInputStream interface. 60 class AsciiInputStream : public v8::UnicodeInputStream { 61 public: 62 AsciiInputStream(const uint8_t* buffer, size_t length) 63 : buffer_(buffer), 64 end_offset_(static_cast<int>(length)), 65 offset_(0) { } 66 67 virtual ~AsciiInputStream() { } 68 69 virtual void PushBack(int32_t ch) { 70 offset_--; 71 #ifdef DEBUG 72 if (offset_ < 0 || 73 (ch != ((offset_ >= end_offset_) ? -1 : buffer_[offset_]))) { 74 fprintf(stderr, "Invalid pushback: '%c' at offset %d.", ch, offset_); 75 exit(1); 76 } 77 #endif 78 } 79 80 virtual int32_t Next() { 81 if (offset_ >= end_offset_) { 82 offset_++; // Increment anyway to allow symmetric pushbacks. 83 return -1; 84 } 85 uint8_t next_char = buffer_[offset_]; 86 #ifdef DEBUG 87 if (next_char > 0x7fu) { 88 fprintf(stderr, "Non-ASCII character in input: '%c'.", next_char); 89 exit(1); 90 } 91 #endif 92 offset_++; 93 return static_cast<int32_t>(next_char); 94 } 95 96 private: 97 const uint8_t* buffer_; 98 const int end_offset_; 99 int offset_; 100 }; 101 102 103 bool ReadBuffer(FILE* source, void* buffer, size_t length) { 104 size_t actually_read = fread(buffer, 1, length, source); 105 return (actually_read == length); 106 } 107 108 109 bool WriteBuffer(FILE* dest, const void* buffer, size_t length) { 110 size_t actually_written = fwrite(buffer, 1, length, dest); 111 return (actually_written == length); 112 } 113 114 115 class PreparseDataInterpreter { 116 public: 117 PreparseDataInterpreter(const uint8_t* data, int length) 118 : data_(data), length_(length), message_(NULL) { } 119 120 ~PreparseDataInterpreter() { 121 if (message_ != NULL) delete[] message_; 122 } 123 124 bool valid() { 125 int header_length = 126 i::PreparseDataConstants::kHeaderSize * sizeof(int); // NOLINT 127 return length_ >= header_length; 128 } 129 130 bool throws() { 131 return valid() && 132 word(i::PreparseDataConstants::kHasErrorOffset) != 0; 133 } 134 135 const char* message() { 136 if (message_ != NULL) return message_; 137 if (!throws()) return NULL; 138 int text_pos = i::PreparseDataConstants::kHeaderSize + 139 i::PreparseDataConstants::kMessageTextPos; 140 int length = word(text_pos); 141 char* buffer = new char[length + 1]; 142 for (int i = 1; i <= length; i++) { 143 int character = word(text_pos + i); 144 buffer[i - 1] = character; 145 } 146 buffer[length] = '\0'; 147 message_ = buffer; 148 return buffer; 149 } 150 151 int beg_pos() { 152 if (!throws()) return -1; 153 return word(i::PreparseDataConstants::kHeaderSize + 154 i::PreparseDataConstants::kMessageStartPos); 155 } 156 157 int end_pos() { 158 if (!throws()) return -1; 159 return word(i::PreparseDataConstants::kHeaderSize + 160 i::PreparseDataConstants::kMessageEndPos); 161 } 162 163 private: 164 int word(int offset) { 165 const int* word_data = reinterpret_cast<const int*>(data_); 166 if (word_data + offset < reinterpret_cast<const int*>(data_ + length_)) { 167 return word_data[offset]; 168 } 169 return -1; 170 } 171 172 const uint8_t* const data_; 173 const int length_; 174 const char* message_; 175 }; 176 177 178 template <typename T> 179 class ScopedPointer { 180 public: 181 explicit ScopedPointer() : pointer_(NULL) {} 182 explicit ScopedPointer(T* pointer) : pointer_(pointer) {} 183 ~ScopedPointer() { if (pointer_ != NULL) delete[] pointer_; } 184 T& operator[](int index) { return pointer_[index]; } 185 T* operator*() { return pointer_ ;} 186 T* operator=(T* new_value) { 187 if (pointer_ != NULL) delete[] pointer_; 188 pointer_ = new_value; 189 return new_value; 190 } 191 private: 192 T* pointer_; 193 }; 194 195 196 197 void fail(v8::PreParserData* data, const char* message, ...) { 198 va_list args; 199 va_start(args, message); 200 vfprintf(stderr, message, args); 201 va_end(args); 202 fflush(stderr); 203 if (data != NULL) { 204 // Print preparser data to stdout. 205 uint32_t size = data->size(); 206 fprintf(stderr, "LOG: data size: %u\n", size); 207 if (!WriteBuffer(stdout, data->data(), size)) { 208 perror("ERROR: Writing data"); 209 fflush(stderr); 210 } 211 } 212 exit(EXIT_FAILURE); 213 } 214 215 216 bool IsFlag(const char* arg) { 217 // Anything starting with '-' is considered a flag. 218 // It's summarily ignored for now. 219 return arg[0] == '-'; 220 } 221 222 223 struct ExceptionExpectation { 224 ExceptionExpectation() 225 : throws(false), type(NULL), beg_pos(-1), end_pos(-1) { } 226 bool throws; 227 const char* type; 228 int beg_pos; 229 int end_pos; 230 }; 231 232 233 void CheckException(v8::PreParserData* data, 234 ExceptionExpectation* expects) { 235 PreparseDataInterpreter reader(data->data(), data->size()); 236 if (expects->throws) { 237 if (!reader.throws()) { 238 if (expects->type == NULL) { 239 fail(data, "Didn't throw as expected\n"); 240 } else { 241 fail(data, "Didn't throw \"%s\" as expected\n", expects->type); 242 } 243 } 244 if (expects->type != NULL) { 245 const char* actual_message = reader.message(); 246 if (strcmp(expects->type, actual_message)) { 247 fail(data, "Wrong error message. Expected <%s>, found <%s> at %d..%d\n", 248 expects->type, actual_message, reader.beg_pos(), reader.end_pos()); 249 } 250 } 251 if (expects->beg_pos >= 0) { 252 if (expects->beg_pos != reader.beg_pos()) { 253 fail(data, "Wrong error start position: Expected %i, found %i\n", 254 expects->beg_pos, reader.beg_pos()); 255 } 256 } 257 if (expects->end_pos >= 0) { 258 if (expects->end_pos != reader.end_pos()) { 259 fail(data, "Wrong error end position: Expected %i, found %i\n", 260 expects->end_pos, reader.end_pos()); 261 } 262 } 263 } else if (reader.throws()) { 264 const char* message = reader.message(); 265 fail(data, "Throws unexpectedly with message: %s at location %d-%d\n", 266 message, reader.beg_pos(), reader.end_pos()); 267 } 268 } 269 270 271 ExceptionExpectation ParseExpectation(int argc, const char* argv[]) { 272 // Parse ["throws" [<exn-type> [<start> [<end>]]]]. 273 ExceptionExpectation expects; 274 int arg_index = 0; 275 while (argc > arg_index && strncmp("throws", argv[arg_index], 7)) { 276 arg_index++; 277 } 278 if (argc > arg_index) { 279 expects.throws = true; 280 arg_index++; 281 if (argc > arg_index && !IsFlag(argv[arg_index])) { 282 expects.type = argv[arg_index]; 283 arg_index++; 284 if (argc > arg_index && !IsFlag(argv[arg_index])) { 285 expects.beg_pos = atoi(argv[arg_index]); // NOLINT 286 arg_index++; 287 if (argc > arg_index && !IsFlag(argv[arg_index])) { 288 expects.end_pos = atoi(argv[arg_index]); // NOLINT 289 } 290 } 291 } 292 } 293 return expects; 294 } 295 296 297 int main(int argc, const char* argv[]) { 298 // Parse command line. 299 // Format: preparser (<scriptfile> | -e "<source>") 300 // ["throws" [<exn-type> [<start> [<end>]]]] 301 // Any flags (except an initial -e) are ignored. 302 // Flags must not separate "throws" and its arguments. 303 304 // Check for mandatory filename argument. 305 int arg_index = 1; 306 if (argc <= arg_index) { 307 fail(NULL, "ERROR: No filename on command line.\n"); 308 } 309 const uint8_t* source = NULL; 310 const char* filename = argv[arg_index]; 311 if (!strcmp(filename, "-e")) { 312 arg_index++; 313 if (argc <= arg_index) { 314 fail(NULL, "ERROR: No source after -e on command line.\n"); 315 } 316 source = reinterpret_cast<const uint8_t*>(argv[arg_index]); 317 } 318 // Check remainder of command line for exception expectations. 319 arg_index++; 320 ExceptionExpectation expects = 321 ParseExpectation(argc - arg_index, argv + arg_index); 322 323 ScopedPointer<uint8_t> buffer; 324 size_t length; 325 326 if (source == NULL) { 327 // Open JS file. 328 FILE* input = fopen(filename, "rb"); 329 if (input == NULL) { 330 perror("ERROR: Error opening file"); 331 fflush(stderr); 332 return EXIT_FAILURE; 333 } 334 // Find length of JS file. 335 if (fseek(input, 0, SEEK_END) != 0) { 336 perror("ERROR: Error during seek"); 337 fflush(stderr); 338 return EXIT_FAILURE; 339 } 340 length = static_cast<size_t>(ftell(input)); 341 rewind(input); 342 // Read JS file into memory buffer. 343 buffer = new uint8_t[length]; 344 if (!ReadBuffer(input, *buffer, length)) { 345 perror("ERROR: Reading file"); 346 fflush(stderr); 347 return EXIT_FAILURE; 348 } 349 fclose(input); 350 source = *buffer; 351 } else { 352 length = strlen(reinterpret_cast<const char*>(source)); 353 } 354 355 // Preparse input file. 356 AsciiInputStream input_buffer(source, length); 357 size_t kMaxStackSize = 64 * 1024 * sizeof(void*); // NOLINT 358 v8::PreParserData data = v8::Preparse(&input_buffer, kMaxStackSize); 359 360 // Fail if stack overflow. 361 if (data.stack_overflow()) { 362 fail(&data, "ERROR: Stack overflow\n"); 363 } 364 365 // Check that the expected exception is thrown, if an exception is 366 // expected. 367 CheckException(&data, &expects); 368 369 return EXIT_SUCCESS; 370 } 371