1 // Copyright 2006-2008 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 29 // ------------------------------------------------------------------- 30 // 31 // Matches Script::Type from objects.h 32 var TYPE_NATIVE = 0; 33 var TYPE_EXTENSION = 1; 34 var TYPE_NORMAL = 2; 35 36 // Matches Script::CompilationType from objects.h 37 var COMPILATION_TYPE_HOST = 0; 38 var COMPILATION_TYPE_EVAL = 1; 39 var COMPILATION_TYPE_JSON = 2; 40 41 // Matches Messages::kNoLineNumberInfo from v8.h 42 var kNoLineNumberInfo = 0; 43 44 // If this object gets passed to an error constructor the error will 45 // get an accessor for .message that constructs a descriptive error 46 // message on access. 47 var kAddMessageAccessorsMarker = { }; 48 49 var kMessages = 0; 50 51 var kReplacementMarkers = [ "%0", "%1", "%2", "%3" ]; 52 53 function FormatString(format, message) { 54 var args = %MessageGetArguments(message); 55 var result = ""; 56 var arg_num = 0; 57 for (var i = 0; i < format.length; i++) { 58 var str = format[i]; 59 for (arg_num = 0; arg_num < kReplacementMarkers.length; arg_num++) { 60 if (format[i] !== kReplacementMarkers[arg_num]) continue; 61 try { 62 str = ToDetailString(args[arg_num]); 63 } catch (e) { 64 str = "#<error>"; 65 } 66 } 67 result += str; 68 } 69 return result; 70 } 71 72 73 // To check if something is a native error we need to check the 74 // concrete native error types. It is not enough to check "obj 75 // instanceof $Error" because user code can replace 76 // NativeError.prototype.__proto__. User code cannot replace 77 // NativeError.prototype though and therefore this is a safe test. 78 function IsNativeErrorObject(obj) { 79 return (obj instanceof $Error) || 80 (obj instanceof $EvalError) || 81 (obj instanceof $RangeError) || 82 (obj instanceof $ReferenceError) || 83 (obj instanceof $SyntaxError) || 84 (obj instanceof $TypeError) || 85 (obj instanceof $URIError); 86 } 87 88 89 // When formatting internally created error messages, do not 90 // invoke overwritten error toString methods but explicitly use 91 // the error to string method. This is to avoid leaking error 92 // objects between script tags in a browser setting. 93 function ToStringCheckErrorObject(obj) { 94 if (IsNativeErrorObject(obj)) { 95 return %_CallFunction(obj, errorToString); 96 } else { 97 return ToString(obj); 98 } 99 } 100 101 102 function ToDetailString(obj) { 103 if (obj != null && IS_OBJECT(obj) && obj.toString === $Object.prototype.toString) { 104 var constructor = obj.constructor; 105 if (!constructor) return ToStringCheckErrorObject(obj); 106 var constructorName = constructor.name; 107 if (!constructorName || !IS_STRING(constructorName)) { 108 return ToStringCheckErrorObject(obj); 109 } 110 return "#<" + constructorName + ">"; 111 } else { 112 return ToStringCheckErrorObject(obj); 113 } 114 } 115 116 117 function MakeGenericError(constructor, type, args) { 118 if (IS_UNDEFINED(args)) { 119 args = []; 120 } 121 var e = new constructor(kAddMessageAccessorsMarker); 122 e.type = type; 123 e.arguments = args; 124 return e; 125 } 126 127 128 /** 129 * Setup the Script function and constructor. 130 */ 131 %FunctionSetInstanceClassName(Script, 'Script'); 132 %SetProperty(Script.prototype, 'constructor', Script, DONT_ENUM); 133 %SetCode(Script, function(x) { 134 // Script objects can only be created by the VM. 135 throw new $Error("Not supported"); 136 }); 137 138 139 // Helper functions; called from the runtime system. 140 function FormatMessage(message) { 141 if (kMessages === 0) { 142 kMessages = { 143 // Error 144 cyclic_proto: ["Cyclic __proto__ value"], 145 // TypeError 146 unexpected_token: ["Unexpected token ", "%0"], 147 unexpected_token_number: ["Unexpected number"], 148 unexpected_token_string: ["Unexpected string"], 149 unexpected_token_identifier: ["Unexpected identifier"], 150 unexpected_strict_reserved: ["Unexpected strict mode reserved word"], 151 unexpected_eos: ["Unexpected end of input"], 152 malformed_regexp: ["Invalid regular expression: /", "%0", "/: ", "%1"], 153 unterminated_regexp: ["Invalid regular expression: missing /"], 154 regexp_flags: ["Cannot supply flags when constructing one RegExp from another"], 155 incompatible_method_receiver: ["Method ", "%0", " called on incompatible receiver ", "%1"], 156 invalid_lhs_in_assignment: ["Invalid left-hand side in assignment"], 157 invalid_lhs_in_for_in: ["Invalid left-hand side in for-in"], 158 invalid_lhs_in_postfix_op: ["Invalid left-hand side expression in postfix operation"], 159 invalid_lhs_in_prefix_op: ["Invalid left-hand side expression in prefix operation"], 160 multiple_defaults_in_switch: ["More than one default clause in switch statement"], 161 newline_after_throw: ["Illegal newline after throw"], 162 redeclaration: ["%0", " '", "%1", "' has already been declared"], 163 no_catch_or_finally: ["Missing catch or finally after try"], 164 unknown_label: ["Undefined label '", "%0", "'"], 165 uncaught_exception: ["Uncaught ", "%0"], 166 stack_trace: ["Stack Trace:\n", "%0"], 167 called_non_callable: ["%0", " is not a function"], 168 undefined_method: ["Object ", "%1", " has no method '", "%0", "'"], 169 property_not_function: ["Property '", "%0", "' of object ", "%1", " is not a function"], 170 cannot_convert_to_primitive: ["Cannot convert object to primitive value"], 171 not_constructor: ["%0", " is not a constructor"], 172 not_defined: ["%0", " is not defined"], 173 non_object_property_load: ["Cannot read property '", "%0", "' of ", "%1"], 174 non_object_property_store: ["Cannot set property '", "%0", "' of ", "%1"], 175 non_object_property_call: ["Cannot call method '", "%0", "' of ", "%1"], 176 with_expression: ["%0", " has no properties"], 177 illegal_invocation: ["Illegal invocation"], 178 no_setter_in_callback: ["Cannot set property ", "%0", " of ", "%1", " which has only a getter"], 179 apply_non_function: ["Function.prototype.apply was called on ", "%0", ", which is a ", "%1", " and not a function"], 180 apply_wrong_args: ["Function.prototype.apply: Arguments list has wrong type"], 181 invalid_in_operator_use: ["Cannot use 'in' operator to search for '", "%0", "' in ", "%1"], 182 instanceof_function_expected: ["Expecting a function in instanceof check, but got ", "%0"], 183 instanceof_nonobject_proto: ["Function has non-object prototype '", "%0", "' in instanceof check"], 184 null_to_object: ["Cannot convert null to object"], 185 reduce_no_initial: ["Reduce of empty array with no initial value"], 186 getter_must_be_callable: ["Getter must be a function: ", "%0"], 187 setter_must_be_callable: ["Setter must be a function: ", "%0"], 188 value_and_accessor: ["Invalid property. A property cannot both have accessors and be writable or have a value: ", "%0"], 189 proto_object_or_null: ["Object prototype may only be an Object or null"], 190 property_desc_object: ["Property description must be an object: ", "%0"], 191 redefine_disallowed: ["Cannot redefine property: ", "%0"], 192 define_disallowed: ["Cannot define property, object is not extensible: ", "%0"], 193 non_extensible_proto: ["%0", " is not extensible"], 194 // RangeError 195 invalid_array_length: ["Invalid array length"], 196 stack_overflow: ["Maximum call stack size exceeded"], 197 // SyntaxError 198 unable_to_parse: ["Parse error"], 199 duplicate_regexp_flag: ["Duplicate RegExp flag ", "%0"], 200 invalid_regexp: ["Invalid RegExp pattern /", "%0", "/"], 201 illegal_break: ["Illegal break statement"], 202 illegal_continue: ["Illegal continue statement"], 203 illegal_return: ["Illegal return statement"], 204 error_loading_debugger: ["Error loading debugger"], 205 no_input_to_regexp: ["No input to ", "%0"], 206 invalid_json: ["String '", "%0", "' is not valid JSON"], 207 circular_structure: ["Converting circular structure to JSON"], 208 obj_ctor_property_non_object: ["Object.", "%0", " called on non-object"], 209 array_indexof_not_defined: ["Array.getIndexOf: Argument undefined"], 210 object_not_extensible: ["Can't add property ", "%0", ", object is not extensible"], 211 illegal_access: ["Illegal access"], 212 invalid_preparser_data: ["Invalid preparser data for function ", "%0"], 213 strict_mode_with: ["Strict mode code may not include a with statement"], 214 strict_catch_variable: ["Catch variable may not be eval or arguments in strict mode"], 215 too_many_arguments: ["Too many arguments in function call (only 32766 allowed)"], 216 too_many_parameters: ["Too many parameters in function definition (only 32766 allowed)"], 217 too_many_variables: ["Too many variables declared (only 32767 allowed)"], 218 strict_param_name: ["Parameter name eval or arguments is not allowed in strict mode"], 219 strict_param_dupe: ["Strict mode function may not have duplicate parameter names"], 220 strict_var_name: ["Variable name may not be eval or arguments in strict mode"], 221 strict_function_name: ["Function name may not be eval or arguments in strict mode"], 222 strict_octal_literal: ["Octal literals are not allowed in strict mode."], 223 strict_duplicate_property: ["Duplicate data property in object literal not allowed in strict mode"], 224 accessor_data_property: ["Object literal may not have data and accessor property with the same name"], 225 accessor_get_set: ["Object literal may not have multiple get/set accessors with the same name"], 226 strict_lhs_assignment: ["Assignment to eval or arguments is not allowed in strict mode"], 227 strict_lhs_postfix: ["Postfix increment/decrement may not have eval or arguments operand in strict mode"], 228 strict_lhs_prefix: ["Prefix increment/decrement may not have eval or arguments operand in strict mode"], 229 strict_reserved_word: ["Use of future reserved word in strict mode"], 230 strict_delete: ["Delete of an unqualified identifier in strict mode."], 231 strict_delete_property: ["Cannot delete property '", "%0", "' of ", "%1"], 232 strict_const: ["Use of const in strict mode."], 233 strict_function: ["In strict mode code, functions can only be declared at top level or immediately within another function." ], 234 strict_read_only_property: ["Cannot assign to read only property '", "%0", "' of ", "%1"], 235 strict_cannot_assign: ["Cannot assign to read only '", "%0", "' in strict mode"], 236 strict_arguments_callee: ["Cannot access property 'callee' of strict mode arguments"], 237 strict_arguments_caller: ["Cannot access property 'caller' of strict mode arguments"], 238 strict_function_caller: ["Cannot access property 'caller' of a strict mode function"], 239 strict_function_arguments: ["Cannot access property 'arguments' of a strict mode function"], 240 strict_caller: ["Illegal access to a strict mode caller function."], 241 }; 242 } 243 var message_type = %MessageGetType(message); 244 var format = kMessages[message_type]; 245 if (!format) return "<unknown message " + message_type + ">"; 246 return FormatString(format, message); 247 } 248 249 250 function GetLineNumber(message) { 251 var start_position = %MessageGetStartPosition(message); 252 if (start_position == -1) return kNoLineNumberInfo; 253 var script = %MessageGetScript(message); 254 var location = script.locationFromPosition(start_position, true); 255 if (location == null) return kNoLineNumberInfo; 256 return location.line + 1; 257 } 258 259 260 // Returns the source code line containing the given source 261 // position, or the empty string if the position is invalid. 262 function GetSourceLine(message) { 263 var script = %MessageGetScript(message); 264 var start_position = %MessageGetStartPosition(message); 265 var location = script.locationFromPosition(start_position, true); 266 if (location == null) return ""; 267 location.restrict(); 268 return location.sourceText(); 269 } 270 271 272 function MakeTypeError(type, args) { 273 return MakeGenericError($TypeError, type, args); 274 } 275 276 277 function MakeRangeError(type, args) { 278 return MakeGenericError($RangeError, type, args); 279 } 280 281 282 function MakeSyntaxError(type, args) { 283 return MakeGenericError($SyntaxError, type, args); 284 } 285 286 287 function MakeReferenceError(type, args) { 288 return MakeGenericError($ReferenceError, type, args); 289 } 290 291 292 function MakeEvalError(type, args) { 293 return MakeGenericError($EvalError, type, args); 294 } 295 296 297 function MakeError(type, args) { 298 return MakeGenericError($Error, type, args); 299 } 300 301 /** 302 * Find a line number given a specific source position. 303 * @param {number} position The source position. 304 * @return {number} 0 if input too small, -1 if input too large, 305 else the line number. 306 */ 307 Script.prototype.lineFromPosition = function(position) { 308 var lower = 0; 309 var upper = this.lineCount() - 1; 310 var line_ends = this.line_ends; 311 312 // We'll never find invalid positions so bail right away. 313 if (position > line_ends[upper]) { 314 return -1; 315 } 316 317 // This means we don't have to safe-guard indexing line_ends[i - 1]. 318 if (position <= line_ends[0]) { 319 return 0; 320 } 321 322 // Binary search to find line # from position range. 323 while (upper >= 1) { 324 var i = (lower + upper) >> 1; 325 326 if (position > line_ends[i]) { 327 lower = i + 1; 328 } else if (position <= line_ends[i - 1]) { 329 upper = i - 1; 330 } else { 331 return i; 332 } 333 } 334 335 return -1; 336 } 337 338 /** 339 * Get information on a specific source position. 340 * @param {number} position The source position 341 * @param {boolean} include_resource_offset Set to true to have the resource 342 * offset added to the location 343 * @return {SourceLocation} 344 * If line is negative or not in the source null is returned. 345 */ 346 Script.prototype.locationFromPosition = function (position, 347 include_resource_offset) { 348 var line = this.lineFromPosition(position); 349 if (line == -1) return null; 350 351 // Determine start, end and column. 352 var line_ends = this.line_ends; 353 var start = line == 0 ? 0 : line_ends[line - 1] + 1; 354 var end = line_ends[line]; 355 if (end > 0 && %_CallFunction(this.source, end - 1, StringCharAt) == '\r') end--; 356 var column = position - start; 357 358 // Adjust according to the offset within the resource. 359 if (include_resource_offset) { 360 line += this.line_offset; 361 if (line == this.line_offset) { 362 column += this.column_offset; 363 } 364 } 365 366 return new SourceLocation(this, position, line, column, start, end); 367 }; 368 369 370 /** 371 * Get information on a specific source line and column possibly offset by a 372 * fixed source position. This function is used to find a source position from 373 * a line and column position. The fixed source position offset is typically 374 * used to find a source position in a function based on a line and column in 375 * the source for the function alone. The offset passed will then be the 376 * start position of the source for the function within the full script source. 377 * @param {number} opt_line The line within the source. Default value is 0 378 * @param {number} opt_column The column in within the line. Default value is 0 379 * @param {number} opt_offset_position The offset from the begining of the 380 * source from where the line and column calculation starts. Default value is 0 381 * @return {SourceLocation} 382 * If line is negative or not in the source null is returned. 383 */ 384 Script.prototype.locationFromLine = function (opt_line, opt_column, opt_offset_position) { 385 // Default is the first line in the script. Lines in the script is relative 386 // to the offset within the resource. 387 var line = 0; 388 if (!IS_UNDEFINED(opt_line)) { 389 line = opt_line - this.line_offset; 390 } 391 392 // Default is first column. If on the first line add the offset within the 393 // resource. 394 var column = opt_column || 0; 395 if (line == 0) { 396 column -= this.column_offset 397 } 398 399 var offset_position = opt_offset_position || 0; 400 if (line < 0 || column < 0 || offset_position < 0) return null; 401 if (line == 0) { 402 return this.locationFromPosition(offset_position + column, false); 403 } else { 404 // Find the line where the offset position is located. 405 var offset_line = this.lineFromPosition(offset_position); 406 407 if (offset_line == -1 || offset_line + line >= this.lineCount()) { 408 return null; 409 } 410 411 return this.locationFromPosition(this.line_ends[offset_line + line - 1] + 1 + column); // line > 0 here. 412 } 413 } 414 415 416 /** 417 * Get a slice of source code from the script. The boundaries for the slice is 418 * specified in lines. 419 * @param {number} opt_from_line The first line (zero bound) in the slice. 420 * Default is 0 421 * @param {number} opt_to_column The last line (zero bound) in the slice (non 422 * inclusive). Default is the number of lines in the script 423 * @return {SourceSlice} The source slice or null of the parameters where 424 * invalid 425 */ 426 Script.prototype.sourceSlice = function (opt_from_line, opt_to_line) { 427 var from_line = IS_UNDEFINED(opt_from_line) ? this.line_offset : opt_from_line; 428 var to_line = IS_UNDEFINED(opt_to_line) ? this.line_offset + this.lineCount() : opt_to_line 429 430 // Adjust according to the offset within the resource. 431 from_line -= this.line_offset; 432 to_line -= this.line_offset; 433 if (from_line < 0) from_line = 0; 434 if (to_line > this.lineCount()) to_line = this.lineCount(); 435 436 // Check parameters. 437 if (from_line >= this.lineCount() || 438 to_line < 0 || 439 from_line > to_line) { 440 return null; 441 } 442 443 var line_ends = this.line_ends; 444 var from_position = from_line == 0 ? 0 : line_ends[from_line - 1] + 1; 445 var to_position = to_line == 0 ? 0 : line_ends[to_line - 1] + 1; 446 447 // Return a source slice with line numbers re-adjusted to the resource. 448 return new SourceSlice(this, from_line + this.line_offset, to_line + this.line_offset, 449 from_position, to_position); 450 } 451 452 453 Script.prototype.sourceLine = function (opt_line) { 454 // Default is the first line in the script. Lines in the script are relative 455 // to the offset within the resource. 456 var line = 0; 457 if (!IS_UNDEFINED(opt_line)) { 458 line = opt_line - this.line_offset; 459 } 460 461 // Check parameter. 462 if (line < 0 || this.lineCount() <= line) { 463 return null; 464 } 465 466 // Return the source line. 467 var line_ends = this.line_ends; 468 var start = line == 0 ? 0 : line_ends[line - 1] + 1; 469 var end = line_ends[line]; 470 return %_CallFunction(this.source, start, end, StringSubstring); 471 } 472 473 474 /** 475 * Returns the number of source lines. 476 * @return {number} 477 * Number of source lines. 478 */ 479 Script.prototype.lineCount = function() { 480 // Return number of source lines. 481 return this.line_ends.length; 482 }; 483 484 485 /** 486 * Returns the name of script if available, contents of sourceURL comment 487 * otherwise. See 488 * http://fbug.googlecode.com/svn/branches/firebug1.1/docs/ReleaseNotes_1.1.txt 489 * for details on using //@ sourceURL comment to identify scritps that don't 490 * have name. 491 * 492 * @return {?string} script name if present, value for //@ sourceURL comment 493 * otherwise. 494 */ 495 Script.prototype.nameOrSourceURL = function() { 496 if (this.name) 497 return this.name; 498 // TODO(608): the spaces in a regexp below had to be escaped as \040 499 // because this file is being processed by js2c whose handling of spaces 500 // in regexps is broken. Also, ['"] are excluded from allowed URLs to 501 // avoid matches against sources that invoke evals with sourceURL. 502 // A better solution would be to detect these special comments in 503 // the scanner/parser. 504 var source = ToString(this.source); 505 var sourceUrlPos = %StringIndexOf(source, "sourceURL=", 0); 506 if (sourceUrlPos > 4) { 507 var sourceUrlPattern = 508 /\/\/@[\040\t]sourceURL=[\040\t]*([^\s\'\"]*)[\040\t]*$/gm; 509 // Don't reuse lastMatchInfo here, so we create a new array with room 510 // for four captures (array with length one longer than the index 511 // of the fourth capture, where the numbering is zero-based). 512 var matchInfo = new InternalArray(CAPTURE(3) + 1); 513 var match = 514 %_RegExpExec(sourceUrlPattern, source, sourceUrlPos - 4, matchInfo); 515 if (match) { 516 return SubString(source, matchInfo[CAPTURE(2)], matchInfo[CAPTURE(3)]); 517 } 518 } 519 return this.name; 520 } 521 522 523 /** 524 * Class for source location. A source location is a position within some 525 * source with the following properties: 526 * script : script object for the source 527 * line : source line number 528 * column : source column within the line 529 * position : position within the source 530 * start : position of start of source context (inclusive) 531 * end : position of end of source context (not inclusive) 532 * Source text for the source context is the character interval [start, end[. In 533 * most cases end will point to a newline character. It might point just past 534 * the final position of the source if the last source line does not end with a 535 * newline character. 536 * @param {Script} script The Script object for which this is a location 537 * @param {number} position Source position for the location 538 * @param {number} line The line number for the location 539 * @param {number} column The column within the line for the location 540 * @param {number} start Source position for start of source context 541 * @param {number} end Source position for end of source context 542 * @constructor 543 */ 544 function SourceLocation(script, position, line, column, start, end) { 545 this.script = script; 546 this.position = position; 547 this.line = line; 548 this.column = column; 549 this.start = start; 550 this.end = end; 551 } 552 553 554 const kLineLengthLimit = 78; 555 556 /** 557 * Restrict source location start and end positions to make the source slice 558 * no more that a certain number of characters wide. 559 * @param {number} opt_limit The with limit of the source text with a default 560 * of 78 561 * @param {number} opt_before The number of characters to prefer before the 562 * position with a default value of 10 less that the limit 563 */ 564 SourceLocation.prototype.restrict = function (opt_limit, opt_before) { 565 // Find the actual limit to use. 566 var limit; 567 var before; 568 if (!IS_UNDEFINED(opt_limit)) { 569 limit = opt_limit; 570 } else { 571 limit = kLineLengthLimit; 572 } 573 if (!IS_UNDEFINED(opt_before)) { 574 before = opt_before; 575 } else { 576 // If no before is specified center for small limits and perfer more source 577 // before the the position that after for longer limits. 578 if (limit <= 20) { 579 before = $floor(limit / 2); 580 } else { 581 before = limit - 10; 582 } 583 } 584 if (before >= limit) { 585 before = limit - 1; 586 } 587 588 // If the [start, end[ interval is too big we restrict 589 // it in one or both ends. We make sure to always produce 590 // restricted intervals of maximum allowed size. 591 if (this.end - this.start > limit) { 592 var start_limit = this.position - before; 593 var end_limit = this.position + limit - before; 594 if (this.start < start_limit && end_limit < this.end) { 595 this.start = start_limit; 596 this.end = end_limit; 597 } else if (this.start < start_limit) { 598 this.start = this.end - limit; 599 } else { 600 this.end = this.start + limit; 601 } 602 } 603 }; 604 605 606 /** 607 * Get the source text for a SourceLocation 608 * @return {String} 609 * Source text for this location. 610 */ 611 SourceLocation.prototype.sourceText = function () { 612 return %_CallFunction(this.script.source, this.start, this.end, StringSubstring); 613 }; 614 615 616 /** 617 * Class for a source slice. A source slice is a part of a script source with 618 * the following properties: 619 * script : script object for the source 620 * from_line : line number for the first line in the slice 621 * to_line : source line number for the last line in the slice 622 * from_position : position of the first character in the slice 623 * to_position : position of the last character in the slice 624 * The to_line and to_position are not included in the slice, that is the lines 625 * in the slice are [from_line, to_line[. Likewise the characters in the slice 626 * are [from_position, to_position[. 627 * @param {Script} script The Script object for the source slice 628 * @param {number} from_line 629 * @param {number} to_line 630 * @param {number} from_position 631 * @param {number} to_position 632 * @constructor 633 */ 634 function SourceSlice(script, from_line, to_line, from_position, to_position) { 635 this.script = script; 636 this.from_line = from_line; 637 this.to_line = to_line; 638 this.from_position = from_position; 639 this.to_position = to_position; 640 } 641 642 643 /** 644 * Get the source text for a SourceSlice 645 * @return {String} Source text for this slice. The last line will include 646 * the line terminating characters (if any) 647 */ 648 SourceSlice.prototype.sourceText = function () { 649 return %_CallFunction(this.script.source, 650 this.from_position, 651 this.to_position, 652 StringSubstring); 653 }; 654 655 656 // Returns the offset of the given position within the containing 657 // line. 658 function GetPositionInLine(message) { 659 var script = %MessageGetScript(message); 660 var start_position = %MessageGetStartPosition(message); 661 var location = script.locationFromPosition(start_position, false); 662 if (location == null) return -1; 663 location.restrict(); 664 return start_position - location.start; 665 } 666 667 668 function GetStackTraceLine(recv, fun, pos, isGlobal) { 669 return FormatSourcePosition(new CallSite(recv, fun, pos)); 670 } 671 672 // ---------------------------------------------------------------------------- 673 // Error implementation 674 675 // Defines accessors for a property that is calculated the first time 676 // the property is read. 677 function DefineOneShotAccessor(obj, name, fun) { 678 // Note that the accessors consistently operate on 'obj', not 'this'. 679 // Since the object may occur in someone else's prototype chain we 680 // can't rely on 'this' being the same as 'obj'. 681 var hasBeenSet = false; 682 var value; 683 obj.__defineGetter__(name, function () { 684 if (hasBeenSet) { 685 return value; 686 } 687 hasBeenSet = true; 688 value = fun(obj); 689 return value; 690 }); 691 obj.__defineSetter__(name, function (v) { 692 hasBeenSet = true; 693 value = v; 694 }); 695 } 696 697 function CallSite(receiver, fun, pos) { 698 this.receiver = receiver; 699 this.fun = fun; 700 this.pos = pos; 701 } 702 703 CallSite.prototype.getThis = function () { 704 return this.receiver; 705 }; 706 707 CallSite.prototype.getTypeName = function () { 708 var constructor = this.receiver.constructor; 709 if (!constructor) 710 return %_CallFunction(this.receiver, ObjectToString); 711 var constructorName = constructor.name; 712 if (!constructorName) 713 return %_CallFunction(this.receiver, ObjectToString); 714 return constructorName; 715 }; 716 717 CallSite.prototype.isToplevel = function () { 718 if (this.receiver == null) 719 return true; 720 return IS_GLOBAL(this.receiver); 721 }; 722 723 CallSite.prototype.isEval = function () { 724 var script = %FunctionGetScript(this.fun); 725 return script && script.compilation_type == COMPILATION_TYPE_EVAL; 726 }; 727 728 CallSite.prototype.getEvalOrigin = function () { 729 var script = %FunctionGetScript(this.fun); 730 return FormatEvalOrigin(script); 731 }; 732 733 CallSite.prototype.getScriptNameOrSourceURL = function () { 734 var script = %FunctionGetScript(this.fun); 735 return script ? script.nameOrSourceURL() : null; 736 }; 737 738 CallSite.prototype.getFunction = function () { 739 return this.fun; 740 }; 741 742 CallSite.prototype.getFunctionName = function () { 743 // See if the function knows its own name 744 var name = this.fun.name; 745 if (name) { 746 return name; 747 } else { 748 return %FunctionGetInferredName(this.fun); 749 } 750 // Maybe this is an evaluation? 751 var script = %FunctionGetScript(this.fun); 752 if (script && script.compilation_type == COMPILATION_TYPE_EVAL) 753 return "eval"; 754 return null; 755 }; 756 757 CallSite.prototype.getMethodName = function () { 758 // See if we can find a unique property on the receiver that holds 759 // this function. 760 var ownName = this.fun.name; 761 if (ownName && this.receiver && 762 (%_CallFunction(this.receiver, ownName, ObjectLookupGetter) === this.fun || 763 %_CallFunction(this.receiver, ownName, ObjectLookupSetter) === this.fun || 764 this.receiver[ownName] === this.fun)) { 765 // To handle DontEnum properties we guess that the method has 766 // the same name as the function. 767 return ownName; 768 } 769 var name = null; 770 for (var prop in this.receiver) { 771 if (this.receiver.__lookupGetter__(prop) === this.fun || 772 this.receiver.__lookupSetter__(prop) === this.fun || 773 (!this.receiver.__lookupGetter__(prop) && this.receiver[prop] === this.fun)) { 774 // If we find more than one match bail out to avoid confusion. 775 if (name) 776 return null; 777 name = prop; 778 } 779 } 780 if (name) 781 return name; 782 return null; 783 }; 784 785 CallSite.prototype.getFileName = function () { 786 var script = %FunctionGetScript(this.fun); 787 return script ? script.name : null; 788 }; 789 790 CallSite.prototype.getLineNumber = function () { 791 if (this.pos == -1) 792 return null; 793 var script = %FunctionGetScript(this.fun); 794 var location = null; 795 if (script) { 796 location = script.locationFromPosition(this.pos, true); 797 } 798 return location ? location.line + 1 : null; 799 }; 800 801 CallSite.prototype.getColumnNumber = function () { 802 if (this.pos == -1) 803 return null; 804 var script = %FunctionGetScript(this.fun); 805 var location = null; 806 if (script) { 807 location = script.locationFromPosition(this.pos, true); 808 } 809 return location ? location.column + 1: null; 810 }; 811 812 CallSite.prototype.isNative = function () { 813 var script = %FunctionGetScript(this.fun); 814 return script ? (script.type == TYPE_NATIVE) : false; 815 }; 816 817 CallSite.prototype.getPosition = function () { 818 return this.pos; 819 }; 820 821 CallSite.prototype.isConstructor = function () { 822 var constructor = this.receiver ? this.receiver.constructor : null; 823 if (!constructor) 824 return false; 825 return this.fun === constructor; 826 }; 827 828 function FormatEvalOrigin(script) { 829 var sourceURL = script.nameOrSourceURL(); 830 if (sourceURL) 831 return sourceURL; 832 833 var eval_origin = "eval at "; 834 if (script.eval_from_function_name) { 835 eval_origin += script.eval_from_function_name; 836 } else { 837 eval_origin += "<anonymous>"; 838 } 839 840 var eval_from_script = script.eval_from_script; 841 if (eval_from_script) { 842 if (eval_from_script.compilation_type == COMPILATION_TYPE_EVAL) { 843 // eval script originated from another eval. 844 eval_origin += " (" + FormatEvalOrigin(eval_from_script) + ")"; 845 } else { 846 // eval script originated from "real" source. 847 if (eval_from_script.name) { 848 eval_origin += " (" + eval_from_script.name; 849 var location = eval_from_script.locationFromPosition(script.eval_from_script_position, true); 850 if (location) { 851 eval_origin += ":" + (location.line + 1); 852 eval_origin += ":" + (location.column + 1); 853 } 854 eval_origin += ")" 855 } else { 856 eval_origin += " (unknown source)"; 857 } 858 } 859 } 860 861 return eval_origin; 862 }; 863 864 function FormatSourcePosition(frame) { 865 var fileName; 866 var fileLocation = ""; 867 if (frame.isNative()) { 868 fileLocation = "native"; 869 } else if (frame.isEval()) { 870 fileName = frame.getScriptNameOrSourceURL(); 871 if (!fileName) 872 fileLocation = frame.getEvalOrigin(); 873 } else { 874 fileName = frame.getFileName(); 875 } 876 877 if (fileName) { 878 fileLocation += fileName; 879 var lineNumber = frame.getLineNumber(); 880 if (lineNumber != null) { 881 fileLocation += ":" + lineNumber; 882 var columnNumber = frame.getColumnNumber(); 883 if (columnNumber) { 884 fileLocation += ":" + columnNumber; 885 } 886 } 887 } 888 889 if (!fileLocation) { 890 fileLocation = "unknown source"; 891 } 892 var line = ""; 893 var functionName = frame.getFunction().name; 894 var addPrefix = true; 895 var isConstructor = frame.isConstructor(); 896 var isMethodCall = !(frame.isToplevel() || isConstructor); 897 if (isMethodCall) { 898 var methodName = frame.getMethodName(); 899 line += frame.getTypeName() + "."; 900 if (functionName) { 901 line += functionName; 902 if (methodName && (methodName != functionName)) { 903 line += " [as " + methodName + "]"; 904 } 905 } else { 906 line += methodName || "<anonymous>"; 907 } 908 } else if (isConstructor) { 909 line += "new " + (functionName || "<anonymous>"); 910 } else if (functionName) { 911 line += functionName; 912 } else { 913 line += fileLocation; 914 addPrefix = false; 915 } 916 if (addPrefix) { 917 line += " (" + fileLocation + ")"; 918 } 919 return line; 920 } 921 922 function FormatStackTrace(error, frames) { 923 var lines = []; 924 try { 925 lines.push(error.toString()); 926 } catch (e) { 927 try { 928 lines.push("<error: " + e + ">"); 929 } catch (ee) { 930 lines.push("<error>"); 931 } 932 } 933 for (var i = 0; i < frames.length; i++) { 934 var frame = frames[i]; 935 var line; 936 try { 937 line = FormatSourcePosition(frame); 938 } catch (e) { 939 try { 940 line = "<error: " + e + ">"; 941 } catch (ee) { 942 // Any code that reaches this point is seriously nasty! 943 line = "<error>"; 944 } 945 } 946 lines.push(" at " + line); 947 } 948 return lines.join("\n"); 949 } 950 951 function FormatRawStackTrace(error, raw_stack) { 952 var frames = [ ]; 953 for (var i = 0; i < raw_stack.length; i += 4) { 954 var recv = raw_stack[i]; 955 var fun = raw_stack[i + 1]; 956 var code = raw_stack[i + 2]; 957 var pc = raw_stack[i + 3]; 958 var pos = %FunctionGetPositionForOffset(code, pc); 959 frames.push(new CallSite(recv, fun, pos)); 960 } 961 if (IS_FUNCTION($Error.prepareStackTrace)) { 962 return $Error.prepareStackTrace(error, frames); 963 } else { 964 return FormatStackTrace(error, frames); 965 } 966 } 967 968 function DefineError(f) { 969 // Store the error function in both the global object 970 // and the runtime object. The function is fetched 971 // from the runtime object when throwing errors from 972 // within the runtime system to avoid strange side 973 // effects when overwriting the error functions from 974 // user code. 975 var name = f.name; 976 %SetProperty(global, name, f, DONT_ENUM); 977 this['$' + name] = f; 978 // Configure the error function. 979 if (name == 'Error') { 980 // The prototype of the Error object must itself be an error. 981 // However, it can't be an instance of the Error object because 982 // it hasn't been properly configured yet. Instead we create a 983 // special not-a-true-error-but-close-enough object. 984 function ErrorPrototype() {} 985 %FunctionSetPrototype(ErrorPrototype, $Object.prototype); 986 %FunctionSetInstanceClassName(ErrorPrototype, 'Error'); 987 %FunctionSetPrototype(f, new ErrorPrototype()); 988 } else { 989 %FunctionSetPrototype(f, new $Error()); 990 } 991 %FunctionSetInstanceClassName(f, 'Error'); 992 %SetProperty(f.prototype, 'constructor', f, DONT_ENUM); 993 // The name property on the prototype of error objects is not 994 // specified as being read-one and dont-delete. However, allowing 995 // overwriting allows leaks of error objects between script blocks 996 // in the same context in a browser setting. Therefore we fix the 997 // name. 998 %SetProperty(f.prototype, "name", name, READ_ONLY | DONT_DELETE); 999 %SetCode(f, function(m) { 1000 if (%_IsConstructCall()) { 1001 // Define all the expected properties directly on the error 1002 // object. This avoids going through getters and setters defined 1003 // on prototype objects. 1004 %IgnoreAttributesAndSetProperty(this, 'stack', void 0); 1005 %IgnoreAttributesAndSetProperty(this, 'arguments', void 0); 1006 %IgnoreAttributesAndSetProperty(this, 'type', void 0); 1007 if (m === kAddMessageAccessorsMarker) { 1008 // DefineOneShotAccessor always inserts a message property and 1009 // ignores setters. 1010 DefineOneShotAccessor(this, 'message', function (obj) { 1011 return FormatMessage(%NewMessageObject(obj.type, obj.arguments)); 1012 }); 1013 } else if (!IS_UNDEFINED(m)) { 1014 %IgnoreAttributesAndSetProperty(this, 'message', ToString(m)); 1015 } 1016 captureStackTrace(this, f); 1017 } else { 1018 return new f(m); 1019 } 1020 }); 1021 } 1022 1023 function captureStackTrace(obj, cons_opt) { 1024 var stackTraceLimit = $Error.stackTraceLimit; 1025 if (!stackTraceLimit || !IS_NUMBER(stackTraceLimit)) return; 1026 if (stackTraceLimit < 0 || stackTraceLimit > 10000) 1027 stackTraceLimit = 10000; 1028 var raw_stack = %CollectStackTrace(cons_opt 1029 ? cons_opt 1030 : captureStackTrace, stackTraceLimit); 1031 DefineOneShotAccessor(obj, 'stack', function (obj) { 1032 return FormatRawStackTrace(obj, raw_stack); 1033 }); 1034 }; 1035 1036 $Math.__proto__ = global.Object.prototype; 1037 1038 DefineError(function Error() { }); 1039 DefineError(function TypeError() { }); 1040 DefineError(function RangeError() { }); 1041 DefineError(function SyntaxError() { }); 1042 DefineError(function ReferenceError() { }); 1043 DefineError(function EvalError() { }); 1044 DefineError(function URIError() { }); 1045 1046 $Error.captureStackTrace = captureStackTrace; 1047 1048 // Setup extra properties of the Error.prototype object. 1049 $Error.prototype.message = ''; 1050 1051 // Global list of error objects visited during errorToString. This is 1052 // used to detect cycles in error toString formatting. 1053 var visited_errors = new $Array(); 1054 var cyclic_error_marker = new $Object(); 1055 1056 function errorToStringDetectCycle() { 1057 if (!%PushIfAbsent(visited_errors, this)) throw cyclic_error_marker; 1058 try { 1059 var type = this.type; 1060 if (type && !%_CallFunction(this, "message", ObjectHasOwnProperty)) { 1061 var formatted = FormatMessage(%NewMessageObject(type, this.arguments)); 1062 return this.name + ": " + formatted; 1063 } 1064 var message = %_CallFunction(this, "message", ObjectHasOwnProperty) 1065 ? (": " + this.message) 1066 : ""; 1067 return this.name + message; 1068 } finally { 1069 visited_errors.length = visited_errors.length - 1; 1070 } 1071 } 1072 1073 function errorToString() { 1074 // This helper function is needed because access to properties on 1075 // the builtins object do not work inside of a catch clause. 1076 function isCyclicErrorMarker(o) { return o === cyclic_error_marker; } 1077 1078 try { 1079 return %_CallFunction(this, errorToStringDetectCycle); 1080 } catch(e) { 1081 // If this error message was encountered already return the empty 1082 // string for it instead of recursively formatting it. 1083 if (isCyclicErrorMarker(e)) return ''; 1084 else throw e; 1085 } 1086 } 1087 1088 1089 InstallFunctions($Error.prototype, DONT_ENUM, ['toString', errorToString]); 1090 1091 // Boilerplate for exceptions for stack overflows. Used from 1092 // Isolate::StackOverflow(). 1093 const kStackOverflowBoilerplate = MakeRangeError('stack_overflow', []); 1094