// Copyright 2006-2007 Google Inc. All Rights Reserved. // 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. // ------------------------------------------------------------------- const kVowelSounds = {a: true, e: true, i: true, o: true, u: true, y: true}; const kCapitalVowelSounds = {a: true, e: true, i: true, o: true, u: true, h: true, f: true, l: true, m: true, n: true, r: true, s: true, x: true, y: true}; function GetInstanceName(cons) { if (cons.length == 0) { return ""; } var first = cons.charAt(0).toLowerCase(); var mapping = kVowelSounds; if (cons.length > 1 && (cons.charAt(0) != first)) { // First char is upper case var second = cons.charAt(1).toLowerCase(); // Second char is upper case if (cons.charAt(1) != second) mapping = kCapitalVowelSounds; } var s = mapping[first] ? "an " : "a "; return s + cons; }; const kMessages = { // Error cyclic_proto: "Cyclic __proto__ value", // TypeError unexpected_token: "Unexpected token %0", unexpected_token_number: "Unexpected number", unexpected_token_string: "Unexpected string", unexpected_token_identifier: "Unexpected identifier", unexpected_eos: "Unexpected end of input", expected_label: "Expected label", malformed_regexp: "Invalid regular expression: /%0/: %1", unterminated_regexp: "Invalid regular expression: missing /", pcre_error: "PCRE function %0, error code %1", regexp_flags: "Cannot supply flags when constructing one RegExp from another", 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'", invalid_break: "Invalid break statement", invalid_continue: "Invalid continue statement", 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", null_or_undefined: "Cannot access property of null or undefined", 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", illegal_eval: "Unsupported indirect eval() call", 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", // RangeError invalid_array_length: "Invalid array length", invalid_array_apply_length: "Function.prototype.apply supports only up to 1024 arguments", stack_overflow: "Maximum call stack size exceeded", apply_overflow: "Function.prototype.apply cannot support %0 arguments", // SyntaxError unable_to_parse: "Parse error", duplicate_regexp_flag: "Duplicate RegExp flag %0", unrecognized_regexp_flag: "Unrecognized RegExp flag %0", invalid_regexp: "Invalid RegExp pattern /%0/", illegal_break: "Illegal break statement", illegal_continue: "Illegal continue statement", illegal_return: "Illegal return statement", error_loading_debugger: "Error loading debugger %0", }; function FormatString(format, args) { var result = format; for (var i = 0; i < args.length; i++) { var str; try { str = ToDetailString(args[i]); } catch (e) { str = "#"; } result = result.split("%" + i).join(str); } return result; }; function ToDetailString(obj) { if (obj != null && IS_OBJECT(obj) && obj.toString === $Object.prototype.toString) { var constructor = obj.constructor; if (!constructor) return ToString(obj); var constructorName = constructor.name; if (!constructorName) return ToString(obj); return "#<" + GetInstanceName(constructorName) + ">"; } else { return ToString(obj); } }; function MakeGenericError(constructor, type, args) { if (args instanceof $Array) { for (var i = 0; i < args.length; i++) { var elem = args[i]; if (elem instanceof $Array && elem.length > 100) { // arbitrary limit, grab a reasonable slice to report args[i] = elem.slice(0,20).concat("..."); } } } var e = new constructor(); e.type = type; e.arguments = args; return e; }; /** * Setup the Script function and constructor. */ %FunctionSetInstanceClassName(Script, 'Script'); %AddProperty(Script.prototype, 'constructor', Script, DONT_ENUM); %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) { var format = kMessages[message.type]; if (!format) return ""; return FormatString(format, message.args); }; function GetLineNumber(message) { if (message.startPos == -1) return -1; var location = message.script.locationFromPosition(message.startPos); if (location == null) return -1; return location.line + 1; }; // Returns the source code line containing the given source // position, or the empty string if the position is invalid. function GetSourceLine(message) { var location = message.script.locationFromPosition(message.startPos); if (location == null) return ""; location.restrict(); return location.sourceText(); }; function MakeTypeError(type, args) { return MakeGenericError($TypeError, type, args); }; function MakeRangeError(type, args) { return MakeGenericError($RangeError, type, args); }; function MakeSyntaxError(type, args) { return MakeGenericError($SyntaxError, type, args); }; function MakeReferenceError(type, args) { return MakeGenericError($ReferenceError, type, args); }; function MakeEvalError(type, args) { return MakeGenericError($EvalError, type, args); }; function MakeError(type, args) { return MakeGenericError($Error, type, args); }; /** * Initialize the cached source information in a script. Currently all line * end positions are cached. */ Script.prototype.initSourceInfo_ = function () { // Just return if initialized. if (this.lineEnds_) return; // Collect all line endings. this.lineEnds_ = []; for (var i = 0; i < this.source.length; i++) { var current = this.source.charAt(i); if (current == '\n') { this.lineEnds_.push(i); } } // If the script does not end with a line ending add the final end position // as just past the last line ending. if (this.lineEnds_[this.lineEnds_.length - 1] != this.source.length - 1) { this.lineEnds_.push(this.source.length); } }; /** * Get information on a specific source position. * @param {number} position The source position * @return {SourceLocation} * If line is negative or not in the source null is returned. */ Script.prototype.locationFromPosition = function (position) { // Make sure source info has been initialized. this.initSourceInfo_(); var lineCount = this.lineCount(); var line = -1; if (position <= this.lineEnds_[0]) { line = 0; } else { for (var i = 1; i < lineCount; i++) { if (this.lineEnds_[i - 1] < position && position <= this.lineEnds_[i]) { line = i; break; } } } if (line == -1) return null; // Determine start, end and column. var start = line == 0 ? 0 : this.lineEnds_[line - 1] + 1; var end = this.lineEnds_[line]; if (end > 0 && this.source.charAt(end - 1) == '\r') end--; var column = position - start; // Adjust according to the offset within the resource. line += this.line_offset; if (line == this.line_offset) { column += this.column_offset; } return new SourceLocation(this, position, line, column, start, end); }; /** * 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 * source from where the line and column calculation starts. Default value is 0 * @return {SourceLocation} * If line is negative or not in the source null is returned. */ Script.prototype.locationFromLine = function (opt_line, opt_column, opt_offset_position) { // Make soure source info has been initialized. this.initSourceInfo_(); // 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; } // Default is first column. If on the first line add the offset within the // resource. var column = opt_column || 0; if (line == 0) { column -= this.column_offset } var offset_position = opt_offset_position || 0; if (line < 0 || column < 0 || offset_position < 0) return null; if (line == 0) { return this.locationFromPosition(offset_position + column); } else { // Find the line where the offset position is located var lineCount = this.lineCount(); var offset_line; for (var i = 0; i < lineCount; i++) { if (offset_position <= this.lineEnds_[i]) { offset_line = i; break; } } if (offset_line + line >= lineCount) return null; return this.locationFromPosition(this.lineEnds_[offset_line + line - 1] + 1 + column); // line > 0 here. } } /** * 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 */ Script.prototype.sourceSlice = function (opt_from_line, opt_to_line) { // Make soure source info has been initialized. this.initSourceInfo_(); 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 // 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(); // Check parameters. if (from_line >= this.lineCount() || to_line < 0 || from_line > to_line) { return null; } var from_position = from_line == 0 ? 0 : this.lineEnds_[from_line - 1] + 1; var to_position = to_line == 0 ? 0 : this.lineEnds_[to_line - 1] + 1; // Return a source slice with line numbers re-adjusted to the resource. return new SourceSlice(this, from_line + this.line_offset, to_line + this.line_offset, from_position, to_position); } Script.prototype.sourceLine = function (opt_line) { // 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; } // Check parameter. if (line < 0 || this.lineCount() <= line) { return null; } // Return the source line. var start = line == 0 ? 0 : this.lineEnds_[line - 1] + 1; var end = this.lineEnds_[line]; return this.source.substring(start, end); } /** * Returns the number of source lines. * @return {number} * Number of source lines. */ Script.prototype.lineCount = function() { // Make soure source info has been initialized. this.initSourceInfo_(); // Return number of source lines. return this.lineEnds_.length; }; /** * 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) * 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. * @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; }; const kLineLengthLimit = 78; /** * 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 */ SourceLocation.prototype.restrict = function (opt_limit, opt_before) { // 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) { before = $Math_floor(limit / 2); } 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; } } }; /** * Get the source text for a SourceLocation * @return {String} * Source text for this location. */ SourceLocation.prototype.sourceText = function () { return this.script.source.substring(this.start, this.end); }; /** * 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) */ SourceSlice.prototype.sourceText = function () { return this.script.source.substring(this.from_position, this.to_position); }; // Returns the offset of the given position within the containing // line. function GetPositionInLine(message) { var location = message.script.locationFromPosition(message.startPos); if (location == null) return -1; location.restrict(); return message.startPos - location.start; }; function ErrorMessage(type, args, startPos, endPos, script) { this.startPos = startPos; this.endPos = endPos; this.type = type; this.args = args; this.script = script; }; function MakeMessage(type, args, startPos, endPos, script) { return new ErrorMessage(type, args, startPos, endPos, script); }; function GetStackTraceLine(recv, fun, pos, isGlobal) { try { return UnsafeGetStackTraceLine(recv, fun, pos, isGlobal); } catch (e) { return ""; } }; function GetFunctionName(fun, recv) { var name = %FunctionGetName(fun); if (name) return name; for (var prop in recv) { if (recv[prop] === fun) return prop; } return "[anonymous]"; }; function UnsafeGetStackTraceLine(recv, fun, pos, isTopLevel) { var result = ""; // The global frame has no meaningful function or receiver if (!isTopLevel) { // If the receiver is not the global object then prefix the // message send if (recv !== global) result += ToDetailString(recv) + "."; result += GetFunctionName(fun, recv); } if (pos != -1) { var script = %FunctionGetScript(fun); var file; if (script) { file = %FunctionGetScript(fun).data; } if (file) { var location = %FunctionGetScript(fun).locationFromPosition(pos); if (!isTopLevel) result += "("; result += file; if (location != null) { result += ":" + (location.line + 1) + ":" + (location.column + 1); } if (!isTopLevel) result += ")"; } } return (result) ? " at " + result : result; }; // ---------------------------------------------------------------------------- // Error implementation function DefineError(name) { var f = function(msg) {}; // 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. %AddProperty(global, name, f, DONT_ENUM); this['$' + name] = f; // Configure the error function. // prototype of 'Error' must be as default: new Object(). if (name != 'Error') %FunctionSetPrototype(f, new $Error()); %FunctionSetInstanceClassName(f, 'Error'); f.prototype.name = name; f.prototype.constructor = f; %SetCode(f, function(m) { if (%IsConstructCall(this)) { if (!IS_UNDEFINED(m)) this.message = ToString(m); } else { return new f(m); } }); }; $Math.__proto__ = global.Object.prototype; DefineError('Error'); DefineError('TypeError'); DefineError('RangeError'); DefineError('SyntaxError'); DefineError('ReferenceError'); DefineError('EvalError'); DefineError('URIError'); // Setup extra properties of the Error.prototype object. $Error.prototype.message = ''; %AddProperty($Error.prototype, 'toString', function() { var type = this.type; if (type && !this.hasOwnProperty("message")) { return this.name + ": " + FormatMessage({ type: type, args: this.arguments }); } var message = this.message; return this.name + (message ? (": " + message) : ""); }, DONT_ENUM); // Boilerplate for exceptions for stack overflows. Used from // Top::StackOverflow(). const kStackOverflowBoilerplate = MakeRangeError('stack_overflow', []);