messages.js 44.7 KB
Newer Older
1
// Copyright 2012 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 28
// 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.

// -------------------------------------------------------------------
29
//
30 31 32
// If this object gets passed to an error constructor the error will
// get an accessor for .message that constructs a descriptive error
// message on access.
33
var kAddMessageAccessorsMarker = { };
34

35 36
// This will be lazily initialized when first needed (and forcibly
// overwritten even though it's const).
37
var kMessages = 0;
38

39 40
function FormatString(format, message) {
  var args = %MessageGetArguments(message);
41 42 43 44
  var result = "";
  var arg_num = 0;
  for (var i = 0; i < format.length; i++) {
    var str = format[i];
45 46 47 48 49
    if (str.length == 2 && %_StringCharCodeAt(str, 0) == 0x25) {
      // Two-char string starts with "%".
      var arg_num = (%_StringCharCodeAt(str, 1) - 0x30) >>> 0;
      if (arg_num < 4) {
        // str is one of %0, %1, %2 or %3.
50 51 52 53 54
        try {
          str = ToDetailString(args[arg_num]);
        } catch (e) {
          str = "#<error>";
        }
55
      }
56
    }
57
    result += str;
58 59
  }
  return result;
60
}
61 62


63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
// To check if something is a native error we need to check the
// concrete native error types. It is not enough to check "obj
// instanceof $Error" because user code can replace
// NativeError.prototype.__proto__. User code cannot replace
// NativeError.prototype though and therefore this is a safe test.
function IsNativeErrorObject(obj) {
  return (obj instanceof $Error) ||
      (obj instanceof $EvalError) ||
      (obj instanceof $RangeError) ||
      (obj instanceof $ReferenceError) ||
      (obj instanceof $SyntaxError) ||
      (obj instanceof $TypeError) ||
      (obj instanceof $URIError);
}


79 80 81 82 83
// When formatting internally created error messages, do not
// invoke overwritten error toString methods but explicitly use
// the error to string method. This is to avoid leaking error
// objects between script tags in a browser setting.
function ToStringCheckErrorObject(obj) {
84
  if (IsNativeErrorObject(obj)) {
85
    return %_CallFunction(obj, ErrorToString);
86 87 88 89 90 91
  } else {
    return ToString(obj);
  }
}


92
function ToDetailString(obj) {
93
  if (obj != null && IS_OBJECT(obj) && obj.toString === ObjectToString) {
94
    var constructor = obj.constructor;
95 96 97 98 99
    if (typeof constructor == "function") {
      var constructorName = constructor.name;
      if (IS_STRING(constructorName) && constructorName !== "") {
        return "#<" + constructorName + ">";
      }
100
    }
101
  }
102
  return ToStringCheckErrorObject(obj);
103
}
104 105 106


function MakeGenericError(constructor, type, args) {
107
  if (IS_UNDEFINED(args)) {
108
    args = [];
109
  }
110
  var e = new constructor(kAddMessageAccessorsMarker);
111 112 113
  e.type = type;
  e.arguments = args;
  return e;
114
}
115 116 117


/**
118
 * Set up the Script function and constructor.
119 120
 */
%FunctionSetInstanceClassName(Script, 'Script');
121 122
%SetProperty(Script.prototype, 'constructor', Script,
             DONT_ENUM | DONT_DELETE | READ_ONLY);
123 124 125 126 127 128 129 130
%SetCode(Script, function(x) {
  // Script objects can only be created by the VM.
  throw new $Error("Not supported");
});


// Helper functions; called from the runtime system.
function FormatMessage(message) {
131
  if (kMessages === 0) {
132
    var messagesDictionary = [
133
      // Error
134 135
      "cyclic_proto",                 ["Cyclic __proto__ value"],
      "code_gen_from_strings",        ["Code generation from strings disallowed for this context"],
136
      // TypeError
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 178 179 180 181 182 183 184 185 186
      "unexpected_token",             ["Unexpected token ", "%0"],
      "unexpected_token_number",      ["Unexpected number"],
      "unexpected_token_string",      ["Unexpected string"],
      "unexpected_token_identifier",  ["Unexpected identifier"],
      "unexpected_reserved",          ["Unexpected reserved word"],
      "unexpected_strict_reserved",   ["Unexpected strict mode reserved word"],
      "unexpected_eos",               ["Unexpected end of input"],
      "malformed_regexp",             ["Invalid regular expression: /", "%0", "/: ", "%1"],
      "unterminated_regexp",          ["Invalid regular expression: missing /"],
      "regexp_flags",                 ["Cannot supply flags when constructing one RegExp from another"],
      "incompatible_method_receiver", ["Method ", "%0", " called on incompatible receiver ", "%1"],
      "invalid_lhs_in_assignment",    ["Invalid left-hand side in assignment"],
      "invalid_lhs_in_for_in",        ["Invalid left-hand side in for-in"],
      "invalid_lhs_in_postfix_op",    ["Invalid left-hand side expression in postfix operation"],
      "invalid_lhs_in_prefix_op",     ["Invalid left-hand side expression in prefix operation"],
      "multiple_defaults_in_switch",  ["More than one default clause in switch statement"],
      "newline_after_throw",          ["Illegal newline after throw"],
      "redeclaration",                ["%0", " '", "%1", "' has already been declared"],
      "no_catch_or_finally",          ["Missing catch or finally after try"],
      "unknown_label",                ["Undefined label '", "%0", "'"],
      "uncaught_exception",           ["Uncaught ", "%0"],
      "stack_trace",                  ["Stack Trace:\n", "%0"],
      "called_non_callable",          ["%0", " is not a function"],
      "undefined_method",             ["Object ", "%1", " has no method '", "%0", "'"],
      "property_not_function",        ["Property '", "%0", "' of object ", "%1", " is not a function"],
      "cannot_convert_to_primitive",  ["Cannot convert object to primitive value"],
      "not_constructor",              ["%0", " is not a constructor"],
      "not_defined",                  ["%0", " is not defined"],
      "non_object_property_load",     ["Cannot read property '", "%0", "' of ", "%1"],
      "non_object_property_store",    ["Cannot set property '", "%0", "' of ", "%1"],
      "non_object_property_call",     ["Cannot call method '", "%0", "' of ", "%1"],
      "with_expression",              ["%0", " has no properties"],
      "illegal_invocation",           ["Illegal invocation"],
      "no_setter_in_callback",        ["Cannot set property ", "%0", " of ", "%1", " which has only a getter"],
      "apply_non_function",           ["Function.prototype.apply was called on ", "%0", ", which is a ", "%1", " and not a function"],
      "apply_wrong_args",             ["Function.prototype.apply: Arguments list has wrong type"],
      "invalid_in_operator_use",      ["Cannot use 'in' operator to search for '", "%0", "' in ", "%1"],
      "instanceof_function_expected", ["Expecting a function in instanceof check, but got ", "%0"],
      "instanceof_nonobject_proto",   ["Function has non-object prototype '", "%0", "' in instanceof check"],
      "null_to_object",               ["Cannot convert null to object"],
      "reduce_no_initial",            ["Reduce of empty array with no initial value"],
      "getter_must_be_callable",      ["Getter must be a function: ", "%0"],
      "setter_must_be_callable",      ["Setter must be a function: ", "%0"],
      "value_and_accessor",           ["Invalid property.  A property cannot both have accessors and be writable or have a value, ", "%0"],
      "proto_object_or_null",         ["Object prototype may only be an Object or null"],
      "property_desc_object",         ["Property description must be an object: ", "%0"],
      "redefine_disallowed",          ["Cannot redefine property: ", "%0"],
      "define_disallowed",            ["Cannot define property:", "%0", ", object is not extensible."],
      "non_extensible_proto",         ["%0", " is not extensible"],
      "handler_non_object",           ["Proxy.", "%0", " called with non-object as handler"],
187
      "proto_non_object",             ["Proxy.", "%0", " called with non-object as prototype"],
188
      "trap_function_expected",       ["Proxy.", "%0", " called with non-function for '", "%1", "' trap"],
189 190
      "handler_trap_missing",         ["Proxy handler ", "%0", " has no '", "%1", "' trap"],
      "handler_trap_must_be_callable", ["Proxy handler ", "%0", " has non-callable '", "%1", "' trap"],
191 192 193 194 195
      "handler_returned_false",       ["Proxy handler ", "%0", " returned false from '", "%1", "' trap"],
      "handler_returned_undefined",   ["Proxy handler ", "%0", " returned undefined from '", "%1", "' trap"],
      "proxy_prop_not_configurable",  ["Proxy handler ", "%0", " returned non-configurable descriptor for property '", "%2", "' from '", "%1", "' trap"],
      "proxy_non_object_prop_names",  ["Trap '", "%1", "' returned non-object ", "%0"],
      "proxy_repeated_prop_name",     ["Trap '", "%1", "' returned repeated property name '", "%2", "'"],
196
      "invalid_weakmap_key",          ["Invalid value used as weak map key"],
197
      // RangeError
198 199
      "invalid_array_length",         ["Invalid array length"],
      "stack_overflow",               ["Maximum call stack size exceeded"],
200
      "invalid_time_value",           ["Invalid time value"],
201
      // SyntaxError
202 203 204 205 206 207
      "unable_to_parse",              ["Parse error"],
      "invalid_regexp_flags",         ["Invalid flags supplied to RegExp constructor '", "%0", "'"],
      "invalid_regexp",               ["Invalid RegExp pattern /", "%0", "/"],
      "illegal_break",                ["Illegal break statement"],
      "illegal_continue",             ["Illegal continue statement"],
      "illegal_return",               ["Illegal return statement"],
208
      "illegal_let",                  ["Illegal let declaration outside extended mode"],
209 210 211 212
      "error_loading_debugger",       ["Error loading debugger"],
      "no_input_to_regexp",           ["No input to ", "%0"],
      "invalid_json",                 ["String '", "%0", "' is not valid JSON"],
      "circular_structure",           ["Converting circular structure to JSON"],
213
      "called_on_non_object",         ["%0", " called on non-object"],
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
      "called_on_null_or_undefined",  ["%0", " called on null or undefined"],
      "array_indexof_not_defined",    ["Array.getIndexOf: Argument undefined"],
      "object_not_extensible",        ["Can't add property ", "%0", ", object is not extensible"],
      "illegal_access",               ["Illegal access"],
      "invalid_preparser_data",       ["Invalid preparser data for function ", "%0"],
      "strict_mode_with",             ["Strict mode code may not include a with statement"],
      "strict_catch_variable",        ["Catch variable may not be eval or arguments in strict mode"],
      "too_many_arguments",           ["Too many arguments in function call (only 32766 allowed)"],
      "too_many_parameters",          ["Too many parameters in function definition (only 32766 allowed)"],
      "too_many_variables",           ["Too many variables declared (only 32767 allowed)"],
      "strict_param_name",            ["Parameter name eval or arguments is not allowed in strict mode"],
      "strict_param_dupe",            ["Strict mode function may not have duplicate parameter names"],
      "strict_var_name",              ["Variable name may not be eval or arguments in strict mode"],
      "strict_function_name",         ["Function name may not be eval or arguments in strict mode"],
      "strict_octal_literal",         ["Octal literals are not allowed in strict mode."],
      "strict_duplicate_property",    ["Duplicate data property in object literal not allowed in strict mode"],
      "accessor_data_property",       ["Object literal may not have data and accessor property with the same name"],
      "accessor_get_set",             ["Object literal may not have multiple get/set accessors with the same name"],
      "strict_lhs_assignment",        ["Assignment to eval or arguments is not allowed in strict mode"],
      "strict_lhs_postfix",           ["Postfix increment/decrement may not have eval or arguments operand in strict mode"],
      "strict_lhs_prefix",            ["Prefix increment/decrement may not have eval or arguments operand in strict mode"],
      "strict_reserved_word",         ["Use of future reserved word in strict mode"],
      "strict_delete",                ["Delete of an unqualified identifier in strict mode."],
      "strict_delete_property",       ["Cannot delete property '", "%0", "' of ", "%1"],
      "strict_const",                 ["Use of const in strict mode."],
      "strict_function",              ["In strict mode code, functions can only be declared at top level or immediately within another function." ],
      "strict_read_only_property",    ["Cannot assign to read only property '", "%0", "' of ", "%1"],
      "strict_cannot_assign",         ["Cannot assign to read only '", "%0", "' in strict mode"],
      "strict_poison_pill",           ["'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them"],
      "strict_caller",                ["Illegal access to a strict mode caller function."],
      "unprotected_let",              ["Illegal let declaration in unprotected statement context."],
245
      "unprotected_const",            ["Illegal const declaration in unprotected statement context."],
246 247
      "cant_prevent_ext_external_array_elements", ["Cannot prevent extension of an object with external array elements"],
      "redef_external_array_element", ["Cannot redefine a property of an object with external array elements"],
248
      "harmony_const_assign",         ["Assignment to constant variable."],
249 250
      "invalid_module_path",          ["Module does not export '", "%0", "', or export is not itself a module"],
      "module_type_error",            ["Module '", "%0", "' used improperly"],
251 252 253 254 255
    ];
    var messages = { __proto__ : null };
    for (var i = 0; i < messagesDictionary.length; i += 2) {
      var key = messagesDictionary[i];
      var format = messagesDictionary[i + 1];
256 257 258 259 260 261 262 263 264 265 266 267

      for (var j = 0; j < format.length; j++) {
        %IgnoreAttributesAndSetProperty(format, %_NumberToString(j), format[j],
                                        DONT_DELETE | READ_ONLY | DONT_ENUM);
      }
      %IgnoreAttributesAndSetProperty(format, 'length', format.length,
                                      DONT_DELETE | READ_ONLY | DONT_ENUM);
      %PreventExtensions(format);
      %IgnoreAttributesAndSetProperty(messages,
                                      key,
                                      format,
                                      DONT_DELETE | DONT_ENUM | READ_ONLY);
268 269 270 271 272
    }
    %PreventExtensions(messages);
    %IgnoreAttributesAndSetProperty(builtins, "kMessages",
                                    messages,
                                    DONT_DELETE | DONT_ENUM | READ_ONLY);
273
  }
274 275 276 277
  var message_type = %MessageGetType(message);
  var format = kMessages[message_type];
  if (!format) return "<unknown message " + message_type + ">";
  return FormatString(format, message);
278
}
279 280 281


function GetLineNumber(message) {
282 283 284 285
  var start_position = %MessageGetStartPosition(message);
  if (start_position == -1) return kNoLineNumberInfo;
  var script = %MessageGetScript(message);
  var location = script.locationFromPosition(start_position, true);
286
  if (location == null) return kNoLineNumberInfo;
287
  return location.line + 1;
288
}
289 290 291 292 293


// Returns the source code line containing the given source
// position, or the empty string if the position is invalid.
function GetSourceLine(message) {
294 295 296
  var script = %MessageGetScript(message);
  var start_position = %MessageGetStartPosition(message);
  var location = script.locationFromPosition(start_position, true);
297 298 299
  if (location == null) return "";
  location.restrict();
  return location.sourceText();
300
}
301 302 303 304


function MakeTypeError(type, args) {
  return MakeGenericError($TypeError, type, args);
305
}
306 307 308 309


function MakeRangeError(type, args) {
  return MakeGenericError($RangeError, type, args);
310
}
311 312 313 314


function MakeSyntaxError(type, args) {
  return MakeGenericError($SyntaxError, type, args);
315
}
316 317 318 319


function MakeReferenceError(type, args) {
  return MakeGenericError($ReferenceError, type, args);
320
}
321 322 323 324


function MakeEvalError(type, args) {
  return MakeGenericError($EvalError, type, args);
325
}
326 327 328 329


function MakeError(type, args) {
  return MakeGenericError($Error, type, args);
330
}
331

332 333 334 335 336 337
/**
 * Find a line number given a specific source position.
 * @param {number} position The source position.
 * @return {number} 0 if input too small, -1 if input too large,
       else the line number.
 */
338
function ScriptLineFromPosition(position) {
339 340
  var lower = 0;
  var upper = this.lineCount() - 1;
341
  var line_ends = this.line_ends;
342 343

  // We'll never find invalid positions so bail right away.
344
  if (position > line_ends[upper]) {
345 346 347 348
    return -1;
  }

  // This means we don't have to safe-guard indexing line_ends[i - 1].
349
  if (position <= line_ends[0]) {
350 351 352 353 354 355 356
    return 0;
  }

  // Binary search to find line # from position range.
  while (upper >= 1) {
    var i = (lower + upper) >> 1;

357
    if (position > line_ends[i]) {
358
      lower = i + 1;
359
    } else if (position <= line_ends[i - 1]) {
360 361 362 363 364
      upper = i - 1;
    } else {
      return i;
    }
  }
365

366 367
  return -1;
}
368 369 370 371

/**
 * Get information on a specific source position.
 * @param {number} position The source position
372 373
 * @param {boolean} include_resource_offset Set to true to have the resource
 *     offset added to the location
374 375 376
 * @return {SourceLocation}
 *     If line is negative or not in the source null is returned.
 */
377 378
function ScriptLocationFromPosition(position,
                                    include_resource_offset) {
379
  var line = this.lineFromPosition(position);
380
  if (line == -1) return null;
381

382
  // Determine start, end and column.
383 384 385
  var line_ends = this.line_ends;
  var start = line == 0 ? 0 : line_ends[line - 1] + 1;
  var end = line_ends[line];
386 387 388
  if (end > 0 && %_CallFunction(this.source, end - 1, StringCharAt) == '\r') {
    end--;
  }
389 390 391
  var column = position - start;

  // Adjust according to the offset within the resource.
392 393 394 395 396
  if (include_resource_offset) {
    line += this.line_offset;
    if (line == this.line_offset) {
      column += this.column_offset;
    }
397 398 399
  }

  return new SourceLocation(this, position, line, column, start, end);
400
}
401 402 403 404 405 406 407 408 409 410 411 412


/**
 * Get information on a specific source line and column possibly offset by a
 * fixed source position. This function is used to find a source position from
 * a line and column position. The fixed source position offset is typically
 * used to find a source position in a function based on a line and column in
 * the source for the function alone. The offset passed will then be the
 * start position of the source for the function within the full script source.
 * @param {number} opt_line The line within the source. Default value is 0
 * @param {number} opt_column The column in within the line. Default value is 0
 * @param {number} opt_offset_position The offset from the begining of the
413 414
 *     source from where the line and column calculation starts.
 *     Default value is 0
415 416 417
 * @return {SourceLocation}
 *     If line is negative or not in the source null is returned.
 */
418
function ScriptLocationFromLine(opt_line, opt_column, opt_offset_position) {
419 420 421 422 423 424
  // Default is the first line in the script. Lines in the script is relative
  // to the offset within the resource.
  var line = 0;
  if (!IS_UNDEFINED(opt_line)) {
    line = opt_line - this.line_offset;
  }
425

426 427 428 429
  // Default is first column. If on the first line add the offset within the
  // resource.
  var column = opt_column || 0;
  if (line == 0) {
430
    column -= this.column_offset;
431 432 433 434 435
  }

  var offset_position = opt_offset_position || 0;
  if (line < 0 || column < 0 || offset_position < 0) return null;
  if (line == 0) {
436
    return this.locationFromPosition(offset_position + column, false);
437
  } else {
438 439 440 441 442
    // Find the line where the offset position is located.
    var offset_line = this.lineFromPosition(offset_position);

    if (offset_line == -1 || offset_line + line >= this.lineCount()) {
      return null;
443
    }
444

445 446
    return this.locationFromPosition(
        this.line_ends[offset_line + line - 1] + 1 + column);  // line > 0 here.
447 448 449 450 451 452 453 454 455 456 457 458 459 460
  }
}


/**
 * Get a slice of source code from the script. The boundaries for the slice is
 * specified in lines.
 * @param {number} opt_from_line The first line (zero bound) in the slice.
 *     Default is 0
 * @param {number} opt_to_column The last line (zero bound) in the slice (non
 *     inclusive). Default is the number of lines in the script
 * @return {SourceSlice} The source slice or null of the parameters where
 *     invalid
 */
461
function ScriptSourceSlice(opt_from_line, opt_to_line) {
462 463 464 465
  var from_line = IS_UNDEFINED(opt_from_line) ? this.line_offset
                                              : opt_from_line;
  var to_line = IS_UNDEFINED(opt_to_line) ? this.line_offset + this.lineCount()
                                          : opt_to_line;
466 467 468 469 470 471 472

  // Adjust according to the offset within the resource.
  from_line -= this.line_offset;
  to_line -= this.line_offset;
  if (from_line < 0) from_line = 0;
  if (to_line > this.lineCount()) to_line = this.lineCount();

473
  // Check parameters.
474 475 476 477 478 479
  if (from_line >= this.lineCount() ||
      to_line < 0 ||
      from_line > to_line) {
    return null;
  }

480 481 482
  var line_ends = this.line_ends;
  var from_position = from_line == 0 ? 0 : line_ends[from_line - 1] + 1;
  var to_position = to_line == 0 ? 0 : line_ends[to_line - 1] + 1;
483 484

  // Return a source slice with line numbers re-adjusted to the resource.
485 486 487 488
  return new SourceSlice(this,
                         from_line + this.line_offset,
                         to_line + this.line_offset,
                          from_position, to_position);
489 490 491
}


492
function ScriptSourceLine(opt_line) {
493 494 495 496 497 498
  // Default is the first line in the script. Lines in the script are relative
  // to the offset within the resource.
  var line = 0;
  if (!IS_UNDEFINED(opt_line)) {
    line = opt_line - this.line_offset;
  }
499 500

  // Check parameter.
501 502 503 504
  if (line < 0 || this.lineCount() <= line) {
    return null;
  }

505
  // Return the source line.
506 507 508
  var line_ends = this.line_ends;
  var start = line == 0 ? 0 : line_ends[line - 1] + 1;
  var end = line_ends[line];
509
  return %_CallFunction(this.source, start, end, StringSubstring);
510 511 512 513 514 515 516 517
}


/**
 * Returns the number of source lines.
 * @return {number}
 *     Number of source lines.
 */
518
function ScriptLineCount() {
519 520
  // Return number of source lines.
  return this.line_ends.length;
521
}
522 523


524 525
/**
 * Returns the name of script if available, contents of sourceURL comment
526
 * otherwise. See
527 528 529
 * http://fbug.googlecode.com/svn/branches/firebug1.1/docs/ReleaseNotes_1.1.txt
 * for details on using //@ sourceURL comment to identify scritps that don't
 * have name.
530
 *
531 532 533
 * @return {?string} script name if present, value for //@ sourceURL comment
 * otherwise.
 */
534 535
function ScriptNameOrSourceURL() {
  if (this.name) {
536
    return this.name;
537
  }
538 539 540 541 542 543 544

  // The result is cached as on long scripts it takes noticable time to search
  // for the sourceURL.
  if (this.hasCachedNameOrSourceURL)
      return this.cachedNameOrSourceURL;
  this.hasCachedNameOrSourceURL = true;

545
  // TODO(608): the spaces in a regexp below had to be escaped as \040
546 547 548
  // because this file is being processed by js2c whose handling of spaces
  // in regexps is broken. Also, ['"] are excluded from allowed URLs to
  // avoid matches against sources that invoke evals with sourceURL.
549 550 551 552
  // A better solution would be to detect these special comments in
  // the scanner/parser.
  var source = ToString(this.source);
  var sourceUrlPos = %StringIndexOf(source, "sourceURL=", 0);
553
  this.cachedNameOrSourceURL = this.name;
554 555 556 557 558 559 560 561 562 563
  if (sourceUrlPos > 4) {
    var sourceUrlPattern =
        /\/\/@[\040\t]sourceURL=[\040\t]*([^\s\'\"]*)[\040\t]*$/gm;
    // Don't reuse lastMatchInfo here, so we create a new array with room
    // for four captures (array with length one longer than the index
    // of the fourth capture, where the numbering is zero-based).
    var matchInfo = new InternalArray(CAPTURE(3) + 1);
    var match =
        %_RegExpExec(sourceUrlPattern, source, sourceUrlPos - 4, matchInfo);
    if (match) {
564 565
      this.cachedNameOrSourceURL =
          SubString(source, matchInfo[CAPTURE(2)], matchInfo[CAPTURE(3)]);
566 567
    }
  }
568
  return this.cachedNameOrSourceURL;
569 570 571
}


572
SetUpLockedPrototype(Script,
573 574
  $Array("source", "name", "line_ends", "line_offset", "column_offset",
         "cachedNameOrSourceURL", "hasCachedNameOrSourceURL" ),
575 576 577 578 579 580 581 582 583 584 585 586
  $Array(
    "lineFromPosition", ScriptLineFromPosition,
    "locationFromPosition", ScriptLocationFromPosition,
    "locationFromLine", ScriptLocationFromLine,
    "sourceSlice", ScriptSourceSlice,
    "sourceLine", ScriptSourceLine,
    "lineCount", ScriptLineCount,
    "nameOrSourceURL", ScriptNameOrSourceURL
  )
);


587 588 589 590 591 592 593 594 595
/**
 * Class for source location. A source location is a position within some
 * source with the following properties:
 *   script   : script object for the source
 *   line     : source line number
 *   column   : source column within the line
 *   position : position within the source
 *   start    : position of start of source context (inclusive)
 *   end      : position of end of source context (not inclusive)
596 597 598 599
 * Source text for the source context is the character interval
 * [start, end[. In most cases end will point to a newline character.
 * It might point just past the final position of the source if the last
 * source line does not end with a newline character.
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
 * @param {Script} script The Script object for which this is a location
 * @param {number} position Source position for the location
 * @param {number} line The line number for the location
 * @param {number} column The column within the line for the location
 * @param {number} start Source position for start of source context
 * @param {number} end Source position for end of source context
 * @constructor
 */
function SourceLocation(script, position, line, column, start, end) {
  this.script = script;
  this.position = position;
  this.line = line;
  this.column = column;
  this.start = start;
  this.end = end;
615
}
616

617
var kLineLengthLimit = 78;
618 619 620 621 622 623 624 625 626

/**
 * Restrict source location start and end positions to make the source slice
 * no more that a certain number of characters wide.
 * @param {number} opt_limit The with limit of the source text with a default
 *     of 78
 * @param {number} opt_before The number of characters to prefer before the
 *     position with a default value of 10 less that the limit
 */
627
function SourceLocationRestrict(opt_limit, opt_before) {
628 629 630 631 632 633 634 635 636 637 638 639 640 641
  // Find the actual limit to use.
  var limit;
  var before;
  if (!IS_UNDEFINED(opt_limit)) {
    limit = opt_limit;
  } else {
    limit = kLineLengthLimit;
  }
  if (!IS_UNDEFINED(opt_before)) {
    before = opt_before;
  } else {
    // If no before is specified center for small limits and perfer more source
    // before the the position that after for longer limits.
    if (limit <= 20) {
642
      before = $floor(limit / 2);
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
    } else {
      before = limit - 10;
    }
  }
  if (before >= limit) {
    before = limit - 1;
  }

  // If the [start, end[ interval is too big we restrict
  // it in one or both ends. We make sure to always produce
  // restricted intervals of maximum allowed size.
  if (this.end - this.start > limit) {
    var start_limit = this.position - before;
    var end_limit = this.position + limit - before;
    if (this.start < start_limit && end_limit < this.end) {
      this.start = start_limit;
      this.end = end_limit;
    } else if (this.start < start_limit) {
      this.start = this.end - limit;
    } else {
      this.end = this.start + limit;
    }
  }
666
}
667 668 669 670 671 672 673


/**
 * Get the source text for a SourceLocation
 * @return {String}
 *     Source text for this location.
 */
674
function SourceLocationSourceText() {
675 676 677 678 679
  return %_CallFunction(this.script.source,
                        this.start,
                        this.end,
                        StringSubstring);
}
680 681


682 683 684 685 686
SetUpLockedPrototype(SourceLocation,
  $Array("script", "position", "line", "column", "start", "end"),
  $Array(
    "restrict", SourceLocationRestrict,
    "sourceText", SourceLocationSourceText
687
 )
688 689 690
);


691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
/**
 * Class for a source slice. A source slice is a part of a script source with
 * the following properties:
 *   script        : script object for the source
 *   from_line     : line number for the first line in the slice
 *   to_line       : source line number for the last line in the slice
 *   from_position : position of the first character in the slice
 *   to_position   : position of the last character in the slice
 * The to_line and to_position are not included in the slice, that is the lines
 * in the slice are [from_line, to_line[. Likewise the characters in the slice
 * are [from_position, to_position[.
 * @param {Script} script The Script object for the source slice
 * @param {number} from_line
 * @param {number} to_line
 * @param {number} from_position
 * @param {number} to_position
 * @constructor
 */
function SourceSlice(script, from_line, to_line, from_position, to_position) {
  this.script = script;
  this.from_line = from_line;
  this.to_line = to_line;
  this.from_position = from_position;
  this.to_position = to_position;
}

/**
 * Get the source text for a SourceSlice
 * @return {String} Source text for this slice. The last line will include
 *     the line terminating characters (if any)
 */
722
function SourceSliceSourceText() {
723 724 725 726
  return %_CallFunction(this.script.source,
                        this.from_position,
                        this.to_position,
                        StringSubstring);
727
}
728

729 730 731 732 733
SetUpLockedPrototype(SourceSlice,
  $Array("script", "from_line", "to_line", "from_position", "to_position"),
  $Array("sourceText", SourceSliceSourceText)
);

734 735 736 737

// Returns the offset of the given position within the containing
// line.
function GetPositionInLine(message) {
738 739 740
  var script = %MessageGetScript(message);
  var start_position = %MessageGetStartPosition(message);
  var location = script.locationFromPosition(start_position, false);
741 742
  if (location == null) return -1;
  location.restrict();
743
  return start_position - location.start;
744
}
745 746 747


function GetStackTraceLine(recv, fun, pos, isGlobal) {
748
  return FormatSourcePosition(new CallSite(recv, fun, pos));
749
}
750 751 752 753

// ----------------------------------------------------------------------------
// Error implementation

754
// Defines accessors for a property that is calculated the first time
755
// the property is read.
756
function DefineOneShotAccessor(obj, name, fun) {
757 758 759
  // Note that the accessors consistently operate on 'obj', not 'this'.
  // Since the object may occur in someone else's prototype chain we
  // can't rely on 'this' being the same as 'obj'.
760 761
  var hasBeenSet = false;
  var value;
762
  var getter = function() {
763 764 765 766 767
    if (hasBeenSet) {
      return value;
    }
    hasBeenSet = true;
    value = fun(obj);
768
    return value;
769 770
  };
  var setter = function(v) {
771 772
    hasBeenSet = true;
    value = v;
773
  };
774
  %DefineOrRedefineAccessorProperty(obj, name, getter, setter, DONT_ENUM);
775 776
}

777 778 779 780 781 782
function CallSite(receiver, fun, pos) {
  this.receiver = receiver;
  this.fun = fun;
  this.pos = pos;
}

783
function CallSiteGetThis() {
784
  return this.receiver;
785
}
786

787
function CallSiteGetTypeName() {
788
  var constructor = this.receiver.constructor;
789
  if (!constructor) {
790
    return %_CallFunction(this.receiver, ObjectToString);
791
  }
792
  var constructorName = constructor.name;
793
  if (!constructorName) {
794
    return %_CallFunction(this.receiver, ObjectToString);
795
  }
796
  return constructorName;
797
}
798

799
function CallSiteIsToplevel() {
800
  if (this.receiver == null) {
801
    return true;
802
  }
803
  return IS_GLOBAL(this.receiver);
804
}
805

806
function CallSiteIsEval() {
807
  var script = %FunctionGetScript(this.fun);
808
  return script && script.compilation_type == COMPILATION_TYPE_EVAL;
809
}
810

811
function CallSiteGetEvalOrigin() {
812
  var script = %FunctionGetScript(this.fun);
813
  return FormatEvalOrigin(script);
814
}
815

816
function CallSiteGetScriptNameOrSourceURL() {
817 818
  var script = %FunctionGetScript(this.fun);
  return script ? script.nameOrSourceURL() : null;
819
}
820

821
function CallSiteGetFunction() {
822
  return this.fun;
823
}
824

825
function CallSiteGetFunctionName() {
826 827
  // See if the function knows its own name
  var name = this.fun.name;
828
  if (name) {
829
    return name;
830 831 832 833 834
  } else {
    return %FunctionGetInferredName(this.fun);
  }
  // Maybe this is an evaluation?
  var script = %FunctionGetScript(this.fun);
835
  if (script && script.compilation_type == COMPILATION_TYPE_EVAL) {
836
    return "eval";
837
  }
838
  return null;
839
}
840

841
function CallSiteGetMethodName() {
842 843
  // See if we can find a unique property on the receiver that holds
  // this function.
844
  var ownName = this.fun.name;
845
  if (ownName && this.receiver &&
846 847 848 849 850 851
      (%_CallFunction(this.receiver,
                      ownName,
                      ObjectLookupGetter) === this.fun ||
       %_CallFunction(this.receiver,
                      ownName,
                      ObjectLookupSetter) === this.fun ||
852
       this.receiver[ownName] === this.fun)) {
853 854 855
    // To handle DontEnum properties we guess that the method has
    // the same name as the function.
    return ownName;
856
  }
857
  var name = null;
858
  for (var prop in this.receiver) {
859 860
    if (this.receiver.__lookupGetter__(prop) === this.fun ||
        this.receiver.__lookupSetter__(prop) === this.fun ||
861 862
        (!this.receiver.__lookupGetter__(prop) &&
         this.receiver[prop] === this.fun)) {
863
      // If we find more than one match bail out to avoid confusion.
864
      if (name) {
865
        return null;
866
      }
867 868 869
      name = prop;
    }
  }
870
  if (name) {
871
    return name;
872
  }
873
  return null;
874
}
875

876
function CallSiteGetFileName() {
877 878
  var script = %FunctionGetScript(this.fun);
  return script ? script.name : null;
879
}
880

881
function CallSiteGetLineNumber() {
882
  if (this.pos == -1) {
883
    return null;
884
  }
885 886 887 888 889 890
  var script = %FunctionGetScript(this.fun);
  var location = null;
  if (script) {
    location = script.locationFromPosition(this.pos, true);
  }
  return location ? location.line + 1 : null;
891
}
892

893
function CallSiteGetColumnNumber() {
894
  if (this.pos == -1) {
895
    return null;
896
  }
897 898 899 900 901
  var script = %FunctionGetScript(this.fun);
  var location = null;
  if (script) {
    location = script.locationFromPosition(this.pos, true);
  }
902
  return location ? location.column + 1: null;
903
}
904

905
function CallSiteIsNative() {
906
  var script = %FunctionGetScript(this.fun);
907
  return script ? (script.type == TYPE_NATIVE) : false;
908
}
909

910
function CallSiteGetPosition() {
911
  return this.pos;
912
}
913

914
function CallSiteIsConstructor() {
915
  var constructor = this.receiver ? this.receiver.constructor : null;
916
  if (!constructor) {
917
    return false;
918
  }
919
  return this.fun === constructor;
920
}
921

922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940
SetUpLockedPrototype(CallSite, $Array("receiver", "fun", "pos"), $Array(
  "getThis", CallSiteGetThis,
  "getTypeName", CallSiteGetTypeName,
  "isToplevel", CallSiteIsToplevel,
  "isEval", CallSiteIsEval,
  "getEvalOrigin", CallSiteGetEvalOrigin,
  "getScriptNameOrSourceURL", CallSiteGetScriptNameOrSourceURL,
  "getFunction", CallSiteGetFunction,
  "getFunctionName", CallSiteGetFunctionName,
  "getMethodName", CallSiteGetMethodName,
  "getFileName", CallSiteGetFileName,
  "getLineNumber", CallSiteGetLineNumber,
  "getColumnNumber", CallSiteGetColumnNumber,
  "isNative", CallSiteIsNative,
  "getPosition", CallSiteGetPosition,
  "isConstructor", CallSiteIsConstructor
));


941
function FormatEvalOrigin(script) {
942
  var sourceURL = script.nameOrSourceURL();
943
  if (sourceURL) {
944
    return sourceURL;
945
  }
946 947

  var eval_origin = "eval at ";
948 949 950 951 952
  if (script.eval_from_function_name) {
    eval_origin += script.eval_from_function_name;
  } else {
    eval_origin +=  "<anonymous>";
  }
953

954 955
  var eval_from_script = script.eval_from_script;
  if (eval_from_script) {
956
    if (eval_from_script.compilation_type == COMPILATION_TYPE_EVAL) {
957
      // eval script originated from another eval.
958
      eval_origin += " (" + FormatEvalOrigin(eval_from_script) + ")";
959
    } else {
960
      // eval script originated from "real" source.
961 962
      if (eval_from_script.name) {
        eval_origin += " (" + eval_from_script.name;
963 964
        var location = eval_from_script.locationFromPosition(
            script.eval_from_script_position, true);
965 966 967 968
        if (location) {
          eval_origin += ":" + (location.line + 1);
          eval_origin += ":" + (location.column + 1);
        }
969
        eval_origin += ")";
970 971 972 973 974
      } else {
        eval_origin += " (unknown source)";
      }
    }
  }
975

976
  return eval_origin;
977
}
978

979
function FormatSourcePosition(frame) {
980
  var fileName;
981 982 983 984
  var fileLocation = "";
  if (frame.isNative()) {
    fileLocation = "native";
  } else if (frame.isEval()) {
985
    fileName = frame.getScriptNameOrSourceURL();
986
    if (!fileName) {
987
      fileLocation = frame.getEvalOrigin();
988
    }
989
  } else {
990 991 992 993 994 995 996 997 998 999 1000
    fileName = frame.getFileName();
  }

  if (fileName) {
    fileLocation += fileName;
    var lineNumber = frame.getLineNumber();
    if (lineNumber != null) {
      fileLocation += ":" + lineNumber;
      var columnNumber = frame.getColumnNumber();
      if (columnNumber) {
        fileLocation += ":" + columnNumber;
1001 1002 1003
      }
    }
  }
1004

1005 1006 1007 1008
  if (!fileLocation) {
    fileLocation = "unknown source";
  }
  var line = "";
1009
  var functionName = frame.getFunction().name;
1010
  var addPrefix = true;
1011 1012 1013
  var isConstructor = frame.isConstructor();
  var isMethodCall = !(frame.isToplevel() || isConstructor);
  if (isMethodCall) {
1014
    var methodName = frame.getMethodName();
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025
    line += frame.getTypeName() + ".";
    if (functionName) {
      line += functionName;
      if (methodName && (methodName != functionName)) {
        line += " [as " + methodName + "]";
      }
    } else {
      line += methodName || "<anonymous>";
    }
  } else if (isConstructor) {
    line += "new " + (functionName || "<anonymous>");
1026 1027
  } else if (functionName) {
    line += functionName;
1028 1029
  } else {
    line += fileLocation;
1030 1031 1032
    addPrefix = false;
  }
  if (addPrefix) {
1033
    line += " (" + fileLocation + ")";
1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050
  }
  return line;
}

function FormatStackTrace(error, frames) {
  var lines = [];
  try {
    lines.push(error.toString());
  } catch (e) {
    try {
      lines.push("<error: " + e + ">");
    } catch (ee) {
      lines.push("<error>");
    }
  }
  for (var i = 0; i < frames.length; i++) {
    var frame = frames[i];
1051
    var line;
1052
    try {
1053
      line = FormatSourcePosition(frame);
1054 1055
    } catch (e) {
      try {
1056
        line = "<error: " + e + ">";
1057 1058
      } catch (ee) {
        // Any code that reaches this point is seriously nasty!
1059
        line = "<error>";
1060 1061 1062 1063 1064 1065 1066 1067 1068
      }
    }
    lines.push("    at " + line);
  }
  return lines.join("\n");
}

function FormatRawStackTrace(error, raw_stack) {
  var frames = [ ];
1069
  for (var i = 0; i < raw_stack.length; i += 4) {
1070
    var recv = raw_stack[i];
1071 1072 1073 1074
    var fun = raw_stack[i + 1];
    var code = raw_stack[i + 2];
    var pc = raw_stack[i + 3];
    var pos = %FunctionGetPositionForOffset(code, pc);
1075 1076 1077 1078 1079 1080 1081 1082 1083
    frames.push(new CallSite(recv, fun, pos));
  }
  if (IS_FUNCTION($Error.prepareStackTrace)) {
    return $Error.prepareStackTrace(error, frames);
  } else {
    return FormatStackTrace(error, frames);
  }
}

1084

1085 1086
function captureStackTrace(obj, cons_opt) {
  var stackTraceLimit = $Error.stackTraceLimit;
1087
  if (!stackTraceLimit || !IS_NUMBER(stackTraceLimit)) return;
1088
  if (stackTraceLimit < 0 || stackTraceLimit > 10000) {
1089
    stackTraceLimit = 10000;
1090
  }
1091 1092 1093
  var raw_stack = %CollectStackTrace(obj,
                                     cons_opt ? cons_opt : captureStackTrace,
                                     stackTraceLimit);
1094 1095 1096
  DefineOneShotAccessor(obj, 'stack', function (obj) {
    return FormatRawStackTrace(obj, raw_stack);
  });
1097
}
1098

1099

1100
function SetUpError() {
1101 1102
  // Define special error type constructors.

1103
  var DefineError = function(f) {
1104 1105 1106 1107 1108 1109 1110 1111
    // Store the error function in both the global object
    // and the runtime object. The function is fetched
    // from the runtime object when throwing errors from
    // within the runtime system to avoid strange side
    // effects when overwriting the error functions from
    // user code.
    var name = f.name;
    %SetProperty(global, name, f, DONT_ENUM);
1112
    %SetProperty(builtins, '$' + name, f, DONT_ENUM | DONT_DELETE | READ_ONLY);
1113 1114 1115 1116 1117 1118
    // Configure the error function.
    if (name == 'Error') {
      // The prototype of the Error object must itself be an error.
      // However, it can't be an instance of the Error object because
      // it hasn't been properly configured yet.  Instead we create a
      // special not-a-true-error-but-close-enough object.
1119
      var ErrorPrototype = function() {};
1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159
      %FunctionSetPrototype(ErrorPrototype, $Object.prototype);
      %FunctionSetInstanceClassName(ErrorPrototype, 'Error');
      %FunctionSetPrototype(f, new ErrorPrototype());
    } else {
      %FunctionSetPrototype(f, new $Error());
    }
    %FunctionSetInstanceClassName(f, 'Error');
    %SetProperty(f.prototype, 'constructor', f, DONT_ENUM);
    // The name property on the prototype of error objects is not
    // specified as being read-one and dont-delete. However, allowing
    // overwriting allows leaks of error objects between script blocks
    // in the same context in a browser setting. Therefore we fix the
    // name.
    %SetProperty(f.prototype, "name", name,
                 DONT_ENUM | DONT_DELETE | READ_ONLY)  ;
    %SetCode(f, function(m) {
      if (%_IsConstructCall()) {
        // Define all the expected properties directly on the error
        // object. This avoids going through getters and setters defined
        // on prototype objects.
        %IgnoreAttributesAndSetProperty(this, 'stack', void 0, DONT_ENUM);
        %IgnoreAttributesAndSetProperty(this, 'arguments', void 0, DONT_ENUM);
        %IgnoreAttributesAndSetProperty(this, 'type', void 0, DONT_ENUM);
        if (m === kAddMessageAccessorsMarker) {
          // DefineOneShotAccessor always inserts a message property and
          // ignores setters.
          DefineOneShotAccessor(this, 'message', function (obj) {
              return FormatMessage(%NewMessageObject(obj.type, obj.arguments));
          });
        } else if (!IS_UNDEFINED(m)) {
          %IgnoreAttributesAndSetProperty(this,
                                          'message',
                                          ToString(m),
                                          DONT_ENUM);
        }
        captureStackTrace(this, f);
      } else {
        return new f(m);
      }
    });
1160
    %SetNativeFlag(f);
1161
  };
1162

1163 1164 1165 1166 1167 1168 1169
  DefineError(function Error() { });
  DefineError(function TypeError() { });
  DefineError(function RangeError() { });
  DefineError(function SyntaxError() { });
  DefineError(function ReferenceError() { });
  DefineError(function EvalError() { });
  DefineError(function URIError() { });
1170 1171 1172
}

SetUpError();
1173

1174
$Error.captureStackTrace = captureStackTrace;
1175

1176
%SetProperty($Error.prototype, 'message', '', DONT_ENUM);
1177

1178
// Global list of error objects visited during ErrorToString. This is
1179
// used to detect cycles in error toString formatting.
1180 1181
var visited_errors = new InternalArray();
var cyclic_error_marker = new $Object();
1182

1183
function ErrorToStringDetectCycle(error) {
1184
  if (!%PushIfAbsent(visited_errors, error)) throw cyclic_error_marker;
1185
  try {
1186
    var type = error.type;
1187
    var name = error.name;
1188 1189
    name = IS_UNDEFINED(name) ? "Error" : TO_STRING_INLINE(name);
    var message = error.message;
1190 1191
    var hasMessage = %_CallFunction(error, "message", ObjectHasOwnProperty);
    if (type && !hasMessage) {
1192
      message = FormatMessage(%NewMessageObject(type, error.arguments));
1193
    }
1194 1195 1196 1197
    message = IS_UNDEFINED(message) ? "" : TO_STRING_INLINE(message);
    if (name === "") return message;
    if (message === "") return name;
    return name + ": " + message;
1198
  } finally {
1199
    visited_errors.length = visited_errors.length - 1;
1200 1201 1202
  }
}

1203
function ErrorToString() {
1204 1205
  if (!IS_SPEC_OBJECT(this)) {
    throw MakeTypeError("called_on_non_object", ["Error.prototype.toString"]);
1206
  }
1207 1208

  try {
1209
    return ErrorToStringDetectCycle(this);
1210
  } catch(e) {
1211 1212
    // If this error message was encountered already return the empty
    // string for it instead of recursively formatting it.
1213
    if (e === cyclic_error_marker) {
1214 1215 1216
      return '';
    }
    throw e;
1217
  }
1218
}
1219

1220

1221
InstallFunctions($Error.prototype, DONT_ENUM, ['toString', ErrorToString]);
1222 1223

// Boilerplate for exceptions for stack overflows. Used from
1224
// Isolate::StackOverflow().
1225
var kStackOverflowBoilerplate = MakeRangeError('stack_overflow', []);