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
38namespace 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.
60class 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
103bool 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
109bool 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
115class 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
178template <typename T>
179class 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
197void 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
216bool 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
223struct 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
233void 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
271ExceptionExpectation 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
297int 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