preparser-process.cc 11.5 KB
Newer Older
1
// Copyright 2011 the V8 project authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

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 37 38 39
#include "../src/preparse-data-format.h"

namespace i = v8::internal;

40 41
// This file is only used for testing the stand-alone preparser
// library.
42 43 44
// The first argument must be the path of a JavaScript source file, or
// the flags "-e" and the next argument is then the source of a JavaScript
// program.
45 46 47 48 49 50 51 52 53 54 55
// Optionally this can be followed by the word "throws" (case sensitive),
// which signals that the parsing is expected to throw - the default is
// to expect the parsing to not throw.
// The command line can further be followed by a message text (the
// *type* of the exception to throw), and even more optionally, the
// start and end position reported with the exception.
//
// This source file is preparsed and tested against the expectations, and if
// successful, the resulting preparser data is written to stdout.
// Diagnostic output is output on stderr.
// 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 60
// Adapts an ASCII string to the UnicodeInputStream interface.
class AsciiInputStream : public v8::UnicodeInputStream {
61
 public:
62
  AsciiInputStream(const uint8_t* buffer, size_t length)
63
      : buffer_(buffer),
64 65
        end_offset_(static_cast<int>(length)),
        offset_(0) { }
66

67
  virtual ~AsciiInputStream() { }
68 69

  virtual void PushBack(int32_t ch) {
70
    offset_--;
71
#ifdef DEBUG
72 73 74 75
    if (offset_ < 0 ||
        (ch != ((offset_ >= end_offset_) ? -1 : buffer_[offset_]))) {
      fprintf(stderr, "Invalid pushback: '%c' at offset %d.", ch, offset_);
      exit(1);
76
    }
77 78 79
#endif
  }

80
  virtual int32_t Next() {
81 82 83 84 85 86 87 88 89
    if (offset_ >= end_offset_) {
      offset_++;  // Increment anyway to allow symmetric pushbacks.
      return -1;
    }
    uint8_t next_char = buffer_[offset_];
#ifdef DEBUG
    if (next_char > 0x7fu) {
      fprintf(stderr, "Non-ASCII character in input: '%c'.", next_char);
      exit(1);
90
    }
91 92 93
#endif
    offset_++;
    return static_cast<int32_t>(next_char);
94 95 96 97
  }

 private:
  const uint8_t* buffer_;
98 99
  const int end_offset_;
  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 106 107 108
  return (actually_read == length);
}


109
bool WriteBuffer(FILE* dest, const void* buffer, size_t length) {
110 111 112 113
  size_t actually_written = fwrite(buffer, 1, length, dest);
  return (actually_written == length);
}

114

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
class PreparseDataInterpreter {
 public:
  PreparseDataInterpreter(const uint8_t* data, int length)
      : data_(data), length_(length), message_(NULL) { }

  ~PreparseDataInterpreter() {
    if (message_ != NULL) delete[] message_;
  }

  bool valid() {
    int header_length =
      i::PreparseDataConstants::kHeaderSize * sizeof(int);  // NOLINT
    return length_ >= header_length;
  }

  bool throws() {
    return valid() &&
        word(i::PreparseDataConstants::kHasErrorOffset) != 0;
  }

  const char* message() {
    if (message_ != NULL) return message_;
    if (!throws()) return NULL;
    int text_pos = i::PreparseDataConstants::kHeaderSize +
                   i::PreparseDataConstants::kMessageTextPos;
    int length = word(text_pos);
    char* buffer = new char[length + 1];
    for (int i = 1; i <= length; i++) {
      int character = word(text_pos + i);
      buffer[i - 1] = character;
    }
    buffer[length] = '\0';
    message_ = buffer;
    return buffer;
  }

  int beg_pos() {
    if (!throws()) return -1;
    return word(i::PreparseDataConstants::kHeaderSize +
                i::PreparseDataConstants::kMessageStartPos);
  }

  int end_pos() {
    if (!throws()) return -1;
    return word(i::PreparseDataConstants::kHeaderSize +
                i::PreparseDataConstants::kMessageEndPos);
  }

 private:
  int word(int offset) {
    const int* word_data = reinterpret_cast<const int*>(data_);
    if (word_data + offset < reinterpret_cast<const int*>(data_ + length_)) {
      return word_data[offset];
    }
    return -1;
  }

  const uint8_t* const data_;
  const int length_;
  const char* message_;
};


178 179 180
template <typename T>
class ScopedPointer {
 public:
181
  explicit ScopedPointer() : pointer_(NULL) {}
182
  explicit ScopedPointer(T* pointer) : pointer_(pointer) {}
183
  ~ScopedPointer() { if (pointer_ != NULL) delete[] pointer_; }
184 185
  T& operator[](int index) { return pointer_[index]; }
  T* operator*() { return pointer_ ;}
186
  T* operator=(T* new_value) {
187 188
    if (pointer_ != NULL) delete[] pointer_;
    pointer_ = new_value;
189
    return new_value;
190
  }
191 192 193 194 195
 private:
  T* pointer_;
};


196 197 198 199 200 201 202 203 204 205 206 207 208 209 210

void fail(v8::PreParserData* data, const char* message, ...) {
  va_list args;
  va_start(args, message);
  vfprintf(stderr, message, args);
  va_end(args);
  fflush(stderr);
  // Print preparser data to stdout.
  uint32_t size = data->size();
  fprintf(stderr, "LOG: data size: %u\n", size);
  if (!WriteBuffer(stdout, data->data(), size)) {
    perror("ERROR: Writing data");
    fflush(stderr);
  }
  exit(EXIT_FAILURE);
211
}
212 213


214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
bool IsFlag(const char* arg) {
  // Anything starting with '-' is considered a flag.
  // It's summarily ignored for now.
  return arg[0] == '-';
}


struct ExceptionExpectation {
  ExceptionExpectation()
      : throws(false), type(NULL), beg_pos(-1), end_pos(-1) { }
  bool throws;
  const char* type;
  int beg_pos;
  int end_pos;
};


231
void CheckException(v8::PreParserData* data,
232
                    ExceptionExpectation* expects) {
233
  PreparseDataInterpreter reader(data->data(), data->size());
234
  if (expects->throws) {
235
    if (!reader.throws()) {
236
      if (expects->type == NULL) {
237 238
        fail(data, "Didn't throw as expected\n");
      } else {
239
        fail(data, "Didn't throw \"%s\" as expected\n", expects->type);
240 241
      }
    }
242
    if (expects->type != NULL) {
243
      const char* actual_message = reader.message();
244
      if (strcmp(expects->type, actual_message)) {
245 246
        fail(data, "Wrong error message. Expected <%s>, found <%s> at %d..%d\n",
             expects->type, actual_message, reader.beg_pos(), reader.end_pos());
247 248
      }
    }
249 250
    if (expects->beg_pos >= 0) {
      if (expects->beg_pos != reader.beg_pos()) {
251
        fail(data, "Wrong error start position: Expected %i, found %i\n",
252
             expects->beg_pos, reader.beg_pos());
253 254
      }
    }
255 256
    if (expects->end_pos >= 0) {
      if (expects->end_pos != reader.end_pos()) {
257
        fail(data, "Wrong error end position: Expected %i, found %i\n",
258
             expects->end_pos, reader.end_pos());
259 260 261 262
      }
    }
  } else if (reader.throws()) {
    const char* message = reader.message();
263 264
    fail(data, "Throws unexpectedly with message: %s at location %d-%d\n",
         message, reader.beg_pos(), reader.end_pos());
265 266 267
  }
}

268 269 270 271 272 273 274 275 276 277 278 279

ExceptionExpectation ParseExpectation(int argc, const char* argv[]) {
  ExceptionExpectation expects;

  // Parse exception expectations from (the remainder of) the command line.
  int arg_index = 0;
  // Skip any flags.
  while (argc > arg_index && IsFlag(argv[arg_index])) arg_index++;
  if (argc > arg_index) {
    if (strncmp("throws", argv[arg_index], 7)) {
      // First argument after filename, if present, must be the verbatim
      // "throws", marking that the preparsing should fail with an exception.
280 281
      fail(NULL, "ERROR: Extra arguments not prefixed by \"throws\".\n");
    }
282 283 284 285 286 287 288 289 290 291 292
    expects.throws = true;
    do {
      arg_index++;
    } while (argc > arg_index && IsFlag(argv[arg_index]));
    if (argc > arg_index) {
      // Next argument is the exception type identifier.
      expects.type = argv[arg_index];
      do {
        arg_index++;
      } while (argc > arg_index && IsFlag(argv[arg_index]));
      if (argc > arg_index) {
293
        expects.beg_pos = atoi(argv[arg_index]);  // NOLINT
294 295 296 297
        do {
          arg_index++;
        } while (argc > arg_index && IsFlag(argv[arg_index]));
        if (argc > arg_index) {
298
          expects.end_pos = atoi(argv[arg_index]);  // NOLINT
299 300
        }
      }
301 302
    }
  }
303 304 305 306 307 308
  return expects;
}


int main(int argc, const char* argv[]) {
  // Parse command line.
309 310 311
  // Format:  preparser (<scriptfile> | -e "<source>")
  //                    ["throws" [<exn-type> [<start> [<end>]]]]
  // Any flags (except an initial -s) are ignored.
312 313 314 315 316 317

  // Check for mandatory filename argument.
  int arg_index = 1;
  if (argc <= arg_index) {
    fail(NULL, "ERROR: No filename on command line.\n");
  }
318
  const uint8_t* source = NULL;
319
  const char* filename = argv[arg_index];
320 321 322 323 324 325 326
  if (!strcmp(filename, "-e")) {
    arg_index++;
    if (argc <= arg_index) {
      fail(NULL, "ERROR: No source after -e on command line.\n");
    }
    source = reinterpret_cast<const uint8_t*>(argv[arg_index]);
  }
327 328 329 330
  // Check remainder of command line for exception expectations.
  arg_index++;
  ExceptionExpectation expects =
      ParseExpectation(argc - arg_index, argv + arg_index);
331

332 333
  ScopedPointer<uint8_t> buffer;
  size_t length;
334

335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
  if (source == NULL) {
    // Open JS file.
    FILE* input = fopen(filename, "rb");
    if (input == NULL) {
      perror("ERROR: Error opening file");
      fflush(stderr);
      return EXIT_FAILURE;
    }
    // Find length of JS file.
    if (fseek(input, 0, SEEK_END) != 0) {
      perror("ERROR: Error during seek");
      fflush(stderr);
      return EXIT_FAILURE;
    }
    length = static_cast<size_t>(ftell(input));
    rewind(input);
    // Read JS file into memory buffer.
    buffer = new uint8_t[length];
    if (!ReadBuffer(input, *buffer, length)) {
      perror("ERROR: Reading file");
      fflush(stderr);
      return EXIT_FAILURE;
    }
    fclose(input);
    source = *buffer;
  } else {
    length = strlen(reinterpret_cast<const char*>(source));
362
  }
363 364

  // Preparse input file.
365
  AsciiInputStream input_buffer(source, length);
366 367
  size_t kMaxStackSize = 64 * 1024 * sizeof(void*);  // NOLINT
  v8::PreParserData data = v8::Preparse(&input_buffer, kMaxStackSize);
368

369
  // Fail if stack overflow.
370
  if (data.stack_overflow()) {
371
    fail(&data, "ERROR: Stack overflow\n");
372 373
  }

374 375
  // Check that the expected exception is thrown, if an exception is
  // expected.
376
  CheckException(&data, &expects);
377

378
  return EXIT_SUCCESS;
379
}