diff --git a/js/src/Makefile.in b/js/src/Makefile.in index c06bacf06aec..e036c30c6cdc 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -452,6 +452,8 @@ HOST_CPPSRCS += jsoplengen.cpp HOST_SIMPLE_PROGRAMS += host_jsoplengen$(HOST_BIN_SUFFIX) GARBAGE += jsautooplen.h host_jsoplengen$(HOST_BIN_SUFFIX) +GARBAGE += selfhosted.out.h + USE_HOST_CXX = 1 ifdef HAVE_DTRACE @@ -822,6 +824,24 @@ ifdef HAVE_LINUX_PERF_EVENT_H pm_linux.$(OBJ_SUFFIX): CXXFLAGS += $(LINUX_HEADERS_INCLUDES) endif +# Prepare self-hosted JS code for embedding +export:: selfhosted.out.h + +selfhosting_srcs := \ + $(srcdir)/builtin/array.js \ + $(NULL) + +selfhosted_out_h_deps := \ + $(selfhosting_srcs) \ + $(srcdir)/js.msg \ + $(srcdir)/builtin/macros.py \ + $(srcdir)/builtin/js2c.py \ + $(srcdir)/builtin/embedjs.py + +selfhosted.out.h: $(selfhosted_out_h_deps) + $(PYTHON) $(srcdir)/builtin/embedjs.py $@ $(srcdir)/js.msg \ + $(srcdir)/builtin/macros.py $(selfhosting_srcs) + ############################################### # BEGIN kludges for the Nitro assembler # diff --git a/js/src/builtin/array.js b/js/src/builtin/array.js new file mode 100644 index 000000000000..fd24f7d7dac1 --- /dev/null +++ b/js/src/builtin/array.js @@ -0,0 +1 @@ +//this space intentionally left blank \ No newline at end of file diff --git a/js/src/builtin/embedjs.py b/js/src/builtin/embedjs.py new file mode 100644 index 000000000000..c182d26e6c8a --- /dev/null +++ b/js/src/builtin/embedjs.py @@ -0,0 +1,49 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# This utility converts JS files containing self-hosted builtins into a C +# header file that can be embedded into SpiderMonkey. +# +# It expects error messages in the JS code to be referenced by their C enum +# keys as literals. + +from __future__ import with_statement +import re, sys, os, js2c, fileinput + +def replaceErrorMsgs(source_files, messages_file, output_file): + messages = buildMessagesTable(messages_file) + with open(output_file, 'w') as output: + if len(source_files) == 0: + return + for line in fileinput.input(source_files): + output.write(replaceMessages(line, messages)) + +def buildMessagesTable(messages_file): + table = {} + pattern = re.compile(r"MSG_DEF\(([\w_]+),\s*(\d+)") + for line in fileinput.input(messages_file): + match = pattern.match(line) + if match: + table[match.group(1)] = match.group(2) + return table + +def replaceMessages(line, messages): + if not 'JSMSG_' in line: + return line + for message_str, message_num in messages.iteritems(): + line = line.replace(message_str, message_num) + return line + +def main(): + output_file = sys.argv[1] + messages_file = sys.argv[2] + macros_file = sys.argv[3] + source_files = sys.argv[4:] + combined_file = 'combined.js' + replaceErrorMsgs(source_files, messages_file, combined_file) + js2c.JS2C([combined_file, macros_file], [output_file], { 'TYPE': 'CORE', 'COMPRESSION': 'off' }) + os.remove(combined_file) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/js/src/builtin/js2c.py b/js/src/builtin/js2c.py new file mode 100644 index 000000000000..434dbe950cf4 --- /dev/null +++ b/js/src/builtin/js2c.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python +# +# Copyright 2012 the V8 project authors. 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. + +# This is a utility for converting JavaScript source code into C-style +# char arrays. It is used for embedded JavaScript code in the V8 +# library. + +import os, re, sys, string +import jsmin +import bz2 + + +def ToCAsciiArray(lines): + result = [] + for chr in lines: + value = ord(chr) + assert value < 128 + result.append(str(value)) + return ", ".join(result) + + +def ToCArray(lines): + result = [] + for chr in lines: + result.append(str(ord(chr))) + return ", ".join(result) + + +def RemoveCommentsAndTrailingWhitespace(lines): + lines = re.sub(r'//.*\n', '\n', lines) # end-of-line comments + lines = re.sub(re.compile(r'/\*.*?\*/', re.DOTALL), '', lines) # comments. + lines = re.sub(r'\s+\n+', '\n', lines) # trailing whitespace + return lines + + +def ReadFile(filename): + file = open(filename, "rt") + try: + lines = file.read() + finally: + file.close() + return lines + + +def ReadLines(filename): + result = [] + for line in open(filename, "rt"): + if '#' in line: + line = line[:line.index('#')] + line = line.strip() + if len(line) > 0: + result.append(line) + return result + + +def LoadConfigFrom(name): + import ConfigParser + config = ConfigParser.ConfigParser() + config.read(name) + return config + + +def ParseValue(string): + string = string.strip() + if string.startswith('[') and string.endswith(']'): + return string.lstrip('[').rstrip(']').split() + else: + return string + + +EVAL_PATTERN = re.compile(r'\beval\s*\(') +WITH_PATTERN = re.compile(r'\bwith\s*\(') + + +def Validate(lines, file): + lines = RemoveCommentsAndTrailingWhitespace(lines) + # Because of simplified context setup, eval and with is not + # allowed in the natives files. + eval_match = EVAL_PATTERN.search(lines) + if eval_match: + raise ("Eval disallowed in natives: %s" % file) + with_match = WITH_PATTERN.search(lines) + if with_match: + raise ("With statements disallowed in natives: %s" % file) + + +def ExpandConstants(lines, constants): + for key, value in constants: + lines = key.sub(str(value), lines) + return lines + + +def ExpandMacros(lines, macros): + # We allow macros to depend on the previously declared macros, but + # we don't allow self-dependecies or recursion. + for name_pattern, macro in reversed(macros): + pattern_match = name_pattern.search(lines, 0) + while pattern_match is not None: + # Scan over the arguments + height = 1 + start = pattern_match.start() + end = pattern_match.end() + assert lines[end - 1] == '(' + last_match = end + arg_index = [0] # Wrap state into array, to work around Python "scoping" + mapping = { } + def add_arg(str): + # Remember to expand recursively in the arguments + replacement = ExpandMacros(str.strip(), macros) + mapping[macro.args[arg_index[0]]] = replacement + arg_index[0] += 1 + while end < len(lines) and height > 0: + # We don't count commas at higher nesting levels. + if lines[end] == ',' and height == 1: + add_arg(lines[last_match:end]) + last_match = end + 1 + elif lines[end] in ['(', '{', '[']: + height = height + 1 + elif lines[end] in [')', '}', ']']: + height = height - 1 + end = end + 1 + # Remember to add the last match. + add_arg(lines[last_match:end-1]) + result = macro.expand(mapping) + # Replace the occurrence of the macro with the expansion + lines = lines[:start] + result + lines[end:] + pattern_match = name_pattern.search(lines, start + len(result)) + return lines + +class TextMacro: + def __init__(self, args, body): + self.args = args + self.body = body + def expand(self, mapping): + result = self.body + for key, value in mapping.items(): + result = result.replace(key, value) + return result + +class PythonMacro: + def __init__(self, args, fun): + self.args = args + self.fun = fun + def expand(self, mapping): + args = [] + for arg in self.args: + args.append(mapping[arg]) + return str(self.fun(*args)) + +CONST_PATTERN = re.compile(r'^const\s+([a-zA-Z0-9_]+)\s*=\s*([^;]*);$') +MACRO_PATTERN = re.compile(r'^macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*=\s*([^;]*);$') +PYTHON_MACRO_PATTERN = re.compile(r'^python\s+macro\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*=\s*([^;]*);$') + + +def ReadMacros(lines): + constants = [] + macros = [] + for line in lines: + hash = line.find('#') + if hash != -1: line = line[:hash] + line = line.strip() + if len(line) is 0: continue + const_match = CONST_PATTERN.match(line) + if const_match: + name = const_match.group(1) + value = const_match.group(2).strip() + constants.append((re.compile("\\b%s\\b" % name), value)) + else: + macro_match = MACRO_PATTERN.match(line) + if macro_match: + name = macro_match.group(1) + args = [match.strip() for match in macro_match.group(2).split(',')] + body = macro_match.group(3).strip() + macros.append((re.compile("\\b%s\\(" % name), TextMacro(args, body))) + else: + python_match = PYTHON_MACRO_PATTERN.match(line) + if python_match: + name = python_match.group(1) + args = [match.strip() for match in python_match.group(2).split(',')] + body = python_match.group(3).strip() + fun = eval("lambda " + ",".join(args) + ': ' + body) + macros.append((re.compile("\\b%s\\(" % name), PythonMacro(args, fun))) + else: + raise ("Illegal line: " + line) + return (constants, macros) + + +HEADER_TEMPLATE = """\ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +namespace js { +namespace selfhosted { + static const char sources[] = { %(sources_data)s }; + +%(raw_sources_declaration)s\ + + uint32_t GetRawScriptsSize() { + return %(raw_total_length)i; + } +} // selfhosted +} // js +""" + + +RAW_SOURCES_COMPRESSION_DECLARATION = """\ + static const char* raw_sources = NULL; +""" + + +RAW_SOURCES_DECLARATION = """\ + static const char* raw_sources = reinterpret_cast(sources); +""" + + +GET_INDEX_CASE = """\ + if (strcmp(name, "%(id)s") == 0) return %(i)i; +""" + + +GET_RAW_SCRIPT_SOURCE_CASE = """\ + if (index == %(i)i) return Vector(raw_sources + %(offset)i, %(raw_length)i); +""" + + +GET_SCRIPT_NAME_CASE = """\ + if (index == %(i)i) return Vector("%(name)s", %(length)i); +""" + +def JS2C(source, target, env): + ids = [] + debugger_ids = [] + modules = [] + # Locate the macros file name. + consts = [] + macros = [] + for s in source: + if 'macros.py' == (os.path.split(str(s))[1]): + (consts, macros) = ReadMacros(ReadLines(str(s))) + else: + modules.append(s) + + minifier = jsmin.JavaScriptMinifier() + + module_offset = 0 + all_sources = [] + for module in modules: + filename = str(module) + debugger = filename.endswith('-debugger.js') + lines = ReadFile(filename) + lines = ExpandConstants(lines, consts) + lines = ExpandMacros(lines, macros) + Validate(lines, filename) + lines = minifier.JSMinify(lines) + id = (os.path.split(filename)[1])[:-3] + if debugger: id = id[:-9] + raw_length = len(lines) + if debugger: + debugger_ids.append((id, raw_length, module_offset)) + else: + ids.append((id, raw_length, module_offset)) + all_sources.append(lines) + module_offset += raw_length + total_length = raw_total_length = module_offset + + if env['COMPRESSION'] == 'off': + raw_sources_declaration = RAW_SOURCES_DECLARATION + sources_data = ToCAsciiArray("".join(all_sources)) + else: + raw_sources_declaration = RAW_SOURCES_COMPRESSION_DECLARATION + if env['COMPRESSION'] == 'bz2': + all_sources = bz2.compress("".join(all_sources)) + total_length = len(all_sources) + sources_data = ToCArray(all_sources) + + # Build debugger support functions + get_index_cases = [ ] + get_raw_script_source_cases = [ ] + get_script_name_cases = [ ] + + i = 0 + for (id, raw_length, module_offset) in debugger_ids + ids: + native_name = "native %s.js" % id + get_index_cases.append(GET_INDEX_CASE % { 'id': id, 'i': i }) + get_raw_script_source_cases.append(GET_RAW_SCRIPT_SOURCE_CASE % { + 'offset': module_offset, + 'raw_length': raw_length, + 'i': i + }) + get_script_name_cases.append(GET_SCRIPT_NAME_CASE % { + 'name': native_name, + 'length': len(native_name), + 'i': i + }) + i = i + 1 + + # Emit result + output = open(str(target[0]), "w") + output.write(HEADER_TEMPLATE % { + 'builtin_count': len(ids) + len(debugger_ids), + 'debugger_count': len(debugger_ids), + 'sources_data': sources_data, + 'raw_sources_declaration': raw_sources_declaration, + 'raw_total_length': raw_total_length, + 'total_length': total_length, + 'get_index_cases': "".join(get_index_cases), + 'get_raw_script_source_cases': "".join(get_raw_script_source_cases), + 'get_script_name_cases': "".join(get_script_name_cases), + 'type': env['TYPE'] + }) + output.close() + +def main(): + natives = sys.argv[1] + type = sys.argv[2] + compression = sys.argv[3] + source_files = sys.argv[4:] + JS2C(source_files, [natives], { 'TYPE': type, 'COMPRESSION': compression }) + +if __name__ == "__main__": + main() diff --git a/js/src/builtin/jsmin.py b/js/src/builtin/jsmin.py new file mode 100644 index 000000000000..250dea9d72bf --- /dev/null +++ b/js/src/builtin/jsmin.py @@ -0,0 +1,282 @@ +#!/usr/bin/python2.4 + +# Copyright 2012 the V8 project authors. 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. + +"""A JavaScript minifier. + +It is far from being a complete JS parser, so there are many valid +JavaScript programs that will be ruined by it. Another strangeness is that +it accepts $ and % as parts of identifiers. It doesn't merge lines or strip +out blank lines in order to ease debugging. Variables at the top scope are +properties of the global object so we can't rename them. It is assumed that +you introduce variables with var as if JavaScript followed C++ scope rules +around curly braces, so the declaration must be above the first use. + +Use as: +import jsmin +minifier = JavaScriptMinifier() +program1 = minifier.JSMinify(program1) +program2 = minifier.JSMinify(program2) +""" + +import re + + +class JavaScriptMinifier(object): + """An object that you can feed code snippets to to get them minified.""" + + def __init__(self): + # We prepopulate the list of identifiers that shouldn't be used. These + # short language keywords could otherwise be used by the script as variable + # names. + self.seen_identifiers = {"do": True, "in": True} + self.identifier_counter = 0 + self.in_comment = False + self.map = {} + self.nesting = 0 + + def LookAtIdentifier(self, m): + """Records identifiers or keywords that we see in use. + + (So we can avoid renaming variables to these strings.) + Args: + m: The match object returned by re.search. + + Returns: + Nothing. + """ + identifier = m.group(1) + self.seen_identifiers[identifier] = True + + def Push(self): + """Called when we encounter a '{'.""" + self.nesting += 1 + + def Pop(self): + """Called when we encounter a '}'.""" + self.nesting -= 1 + # We treat each top-level opening brace as a single scope that can span + # several sets of nested braces. + if self.nesting == 0: + self.map = {} + self.identifier_counter = 0 + + def Declaration(self, m): + """Rewrites bits of the program selected by a regexp. + + These can be curly braces, literal strings, function declarations and var + declarations. (These last two must be on one line including the opening + curly brace of the function for their variables to be renamed). + + Args: + m: The match object returned by re.search. + + Returns: + The string that should replace the match in the rewritten program. + """ + matched_text = m.group(0) + if matched_text == "{": + self.Push() + return matched_text + if matched_text == "}": + self.Pop() + return matched_text + if re.match("[\"'/]", matched_text): + return matched_text + m = re.match(r"var ", matched_text) + if m: + var_names = matched_text[m.end():] + var_names = re.split(r",", var_names) + return "var " + ",".join(map(self.FindNewName, var_names)) + m = re.match(r"(function\b[^(]*)\((.*)\)\{$", matched_text) + if m: + up_to_args = m.group(1) + args = m.group(2) + args = re.split(r",", args) + self.Push() + return up_to_args + "(" + ",".join(map(self.FindNewName, args)) + "){" + + if matched_text in self.map: + return self.map[matched_text] + + return matched_text + + def CharFromNumber(self, number): + """A single-digit base-52 encoding using a-zA-Z.""" + if number < 26: + return chr(number + 97) + number -= 26 + return chr(number + 65) + + def FindNewName(self, var_name): + """Finds a new 1-character or 2-character name for a variable. + + Enters it into the mapping table for this scope. + + Args: + var_name: The name of the variable before renaming. + + Returns: + The new name of the variable. + """ + new_identifier = "" + # Variable names that end in _ are member variables of the global object, + # so they can be visible from code in a different scope. We leave them + # alone. + if var_name in self.map: + return self.map[var_name] + if self.nesting == 0: + return var_name + while True: + identifier_first_char = self.identifier_counter % 52 + identifier_second_char = self.identifier_counter // 52 + new_identifier = self.CharFromNumber(identifier_first_char) + if identifier_second_char != 0: + new_identifier = ( + self.CharFromNumber(identifier_second_char - 1) + new_identifier) + self.identifier_counter += 1 + if not new_identifier in self.seen_identifiers: + break + + self.map[var_name] = new_identifier + return new_identifier + + def RemoveSpaces(self, m): + """Returns literal strings unchanged, replaces other inputs with group 2. + + Other inputs are replaced with the contents of capture 1. This is either + a single space or an empty string. + + Args: + m: The match object returned by re.search. + + Returns: + The string that should be inserted instead of the matched text. + """ + entire_match = m.group(0) + replacement = m.group(1) + if re.match(r"'.*'$", entire_match): + return entire_match + if re.match(r'".*"$', entire_match): + return entire_match + if re.match(r"/.+/$", entire_match): + return entire_match + return replacement + + def JSMinify(self, text): + """The main entry point. Takes a text and returns a compressed version. + + The compressed version hopefully does the same thing. Line breaks are + preserved. + + Args: + text: The text of the code snippet as a multiline string. + + Returns: + The compressed text of the code snippet as a multiline string. + """ + new_lines = [] + for line in re.split(r"\n", text): + line = line.replace("\t", " ") + if self.in_comment: + m = re.search(r"\*/", line) + if m: + line = line[m.end():] + self.in_comment = False + else: + new_lines.append("") + continue + + if not self.in_comment: + line = re.sub(r"/\*.*?\*/", " ", line) + line = re.sub(r"//.*", "", line) + m = re.search(r"/\*", line) + if m: + line = line[:m.start()] + self.in_comment = True + + # Strip leading and trailing spaces. + line = re.sub(r"^ +", "", line) + line = re.sub(r" +$", "", line) + # A regexp that matches a literal string surrounded by "double quotes". + # This regexp can handle embedded backslash-escaped characters including + # embedded backslash-escaped double quotes. + double_quoted_string = r'"(?:[^"\\]|\\.)*"' + # A regexp that matches a literal string surrounded by 'double quotes'. + single_quoted_string = r"'(?:[^'\\]|\\.)*'" + # A regexp that matches a regexp literal surrounded by /slashes/. + # Don't allow a regexp to have a ) before the first ( since that's a + # syntax error and it's probably just two unrelated slashes. + # Also don't allow it to come after anything that can only be the + # end of a primary expression. + slash_quoted_regexp = r"(?> 0)); +macro TO_UINT32(arg) = (arg >>> 0); +macro TO_STRING_INLINE(arg) = (IS_STRING(%IS_VAR(arg)) ? arg : NonStringToString(arg)); +macro TO_NUMBER_INLINE(arg) = (IS_NUMBER(%IS_VAR(arg)) ? arg : NonNumberToNumber(arg)); +macro TO_OBJECT_INLINE(arg) = (IS_SPEC_OBJECT(%IS_VAR(arg)) ? arg : ToObject(arg)); +macro JSON_NUMBER_TO_STRING(arg) = ((%_IsSmi(%IS_VAR(arg)) || arg - arg == 0) ? %_NumberToString(arg) : "null"); + +# Macros implemented in Python. +python macro CHAR_CODE(str) = ord(str[1]); + +# Constants used on an array to implement the properties of the RegExp object. +const REGEXP_NUMBER_OF_CAPTURES = 0; +const REGEXP_FIRST_CAPTURE = 3; + +# We can't put macros in macros so we use constants here. +# REGEXP_NUMBER_OF_CAPTURES +macro NUMBER_OF_CAPTURES(array) = ((array)[0]); + +# Limit according to ECMA 262 15.9.1.1 +const MAX_TIME_MS = 8640000000000000; +# Limit which is MAX_TIME_MS + msPerMonth. +const MAX_TIME_BEFORE_UTC = 8640002592000000; + +# Gets the value of a Date object. If arg is not a Date object +# a type error is thrown. +macro CHECK_DATE(arg) = if (%_ClassOf(arg) !== 'Date') ThrowDateTypeError(); +macro LOCAL_DATE_VALUE(arg) = (%_DateField(arg, 0) + %_DateField(arg, 21)); +macro UTC_DATE_VALUE(arg) = (%_DateField(arg, 0)); + +macro LOCAL_YEAR(arg) = (%_DateField(arg, 1)); +macro LOCAL_MONTH(arg) = (%_DateField(arg, 2)); +macro LOCAL_DAY(arg) = (%_DateField(arg, 3)); +macro LOCAL_WEEKDAY(arg) = (%_DateField(arg, 4)); +macro LOCAL_HOUR(arg) = (%_DateField(arg, 5)); +macro LOCAL_MIN(arg) = (%_DateField(arg, 6)); +macro LOCAL_SEC(arg) = (%_DateField(arg, 7)); +macro LOCAL_MS(arg) = (%_DateField(arg, 8)); +macro LOCAL_DAYS(arg) = (%_DateField(arg, 9)); +macro LOCAL_TIME_IN_DAY(arg) = (%_DateField(arg, 10)); + +macro UTC_YEAR(arg) = (%_DateField(arg, 11)); +macro UTC_MONTH(arg) = (%_DateField(arg, 12)); +macro UTC_DAY(arg) = (%_DateField(arg, 13)); +macro UTC_WEEKDAY(arg) = (%_DateField(arg, 14)); +macro UTC_HOUR(arg) = (%_DateField(arg, 15)); +macro UTC_MIN(arg) = (%_DateField(arg, 16)); +macro UTC_SEC(arg) = (%_DateField(arg, 17)); +macro UTC_MS(arg) = (%_DateField(arg, 18)); +macro UTC_DAYS(arg) = (%_DateField(arg, 19)); +macro UTC_TIME_IN_DAY(arg) = (%_DateField(arg, 20)); + +macro TIMEZONE_OFFSET(arg) = (%_DateField(arg, 21)); + +macro SET_UTC_DATE_VALUE(arg, value) = (%DateSetValue(arg, value, 1)); +macro SET_LOCAL_DATE_VALUE(arg, value) = (%DateSetValue(arg, value, 0)); + +# Last input and last subject of regexp matches. +const LAST_SUBJECT_INDEX = 1; +macro LAST_SUBJECT(array) = ((array)[1]); +macro LAST_INPUT(array) = ((array)[2]); + +# REGEXP_FIRST_CAPTURE +macro CAPTURE(index) = (3 + (index)); +const CAPTURE0 = 3; +const CAPTURE1 = 4; + +# For the regexp capture override array. This has the same +# format as the arguments to a function called from +# String.prototype.replace. +macro OVERRIDE_MATCH(override) = ((override)[0]); +macro OVERRIDE_POS(override) = ((override)[(override).length - 2]); +macro OVERRIDE_SUBJECT(override) = ((override)[(override).length - 1]); +# 1-based so index of 1 returns the first capture +macro OVERRIDE_CAPTURE(override, index) = ((override)[(index)]); + +# PropertyDescriptor return value indices - must match +# PropertyDescriptorIndices in runtime.cc. +const IS_ACCESSOR_INDEX = 0; +const VALUE_INDEX = 1; +const GETTER_INDEX = 2; +const SETTER_INDEX = 3; +const WRITABLE_INDEX = 4; +const ENUMERABLE_INDEX = 5; +const CONFIGURABLE_INDEX = 6; + +# For messages.js +# Matches Script::Type from objects.h +const TYPE_NATIVE = 0; +const TYPE_EXTENSION = 1; +const TYPE_NORMAL = 2; + +# Matches Script::CompilationType from objects.h +const COMPILATION_TYPE_HOST = 0; +const COMPILATION_TYPE_EVAL = 1; +const COMPILATION_TYPE_JSON = 2; + +# Matches Messages::kNoLineNumberInfo from v8.h +const kNoLineNumberInfo = 0; diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 20deda20fbfb..7a176dbc74d2 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -111,7 +111,7 @@ frontend::CompileScript(JSContext *cx, HandleObject scopeChain, StackFrame *call JS_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass())); BytecodeEmitter bce(/* parent = */ NULL, &parser, &sc, script, callerFrame, !!globalScope, - options.lineno); + options.lineno, options.selfHostingMode); if (!bce.init()) return NULL; diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 8d0be031a634..2869ef50ab4b 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -103,7 +103,7 @@ struct frontend::StmtInfoBCE : public StmtInfoBase BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent, Parser *parser, SharedContext *sc, HandleScript script, StackFrame *callerFrame, bool hasGlobalScope, - unsigned lineno) + unsigned lineno, bool selfHostingMode) : sc(sc), parent(parent), script(sc->context, script), @@ -122,7 +122,8 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent, Parser *parser, Shared typesetCount(0), hasSingletons(false), inForInit(false), - hasGlobalScope(hasGlobalScope) + hasGlobalScope(hasGlobalScope), + selfHostingMode(selfHostingMode) { JS_ASSERT_IF(callerFrame, callerFrame->isScriptFrame()); memset(&prolog, 0, sizeof prolog); @@ -1150,7 +1151,7 @@ EmitEnterBlock(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSOp op) * Try to convert a *NAME op to a *GNAME op, which optimizes access to * undeclared globals. Return true if a conversion was made. * - * This conversion is not made if we are in strict mode. In eval code nested + * This conversion is not made if we are in strict mode. In eval code nested * within (strict mode) eval code, access to an undeclared "global" might * merely be to a binding local to that outer eval: * @@ -1166,15 +1167,26 @@ EmitEnterBlock(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSOp op) * undeclared = 17; // throws ReferenceError * } * foo(); + * + * In self-hosting mode, JSOP_NAME is unconditionally converted to + * JSOP_INTRINSICNAME. This causes the lookup to be redirected to the special + * intrinsics holder in the global object, into which any missing objects are + * cloned lazily upon first access. */ static bool TryConvertToGname(BytecodeEmitter *bce, ParseNode *pn, JSOp *op) { + if (bce->selfHostingMode) { + JS_ASSERT(*op == JSOP_NAME); + *op = JSOP_INTRINSICNAME; + return true; + } if (bce->script->compileAndGo && bce->hasGlobalScope && !bce->sc->funMightAliasLocals() && !pn->isDeoptimized() && - !bce->sc->inStrictMode()) { + !bce->sc->inStrictMode()) + { switch (*op) { case JSOP_NAME: *op = JSOP_GETGNAME; break; case JSOP_SETNAME: *op = JSOP_SETGNAME; break; @@ -4865,7 +4877,7 @@ EmitFunc(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn) script->bindings = funbox->bindings; BytecodeEmitter bce2(bce, bce->parser, &sc, script, bce->callerFrame, bce->hasGlobalScope, - pn->pn_pos.begin.lineno); + pn->pn_pos.begin.lineno, bce->selfHostingMode); if (!bce2.init()) return false; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 629cadde1159..91d3df7e6682 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -115,9 +115,14 @@ struct BytecodeEmitter const bool hasGlobalScope:1; /* frontend::CompileScript's scope chain is the global object */ + const bool selfHostingMode:1; /* Emit JSOP_CALLINTRINSIC instead of JSOP_NAME + and assert that JSOP_NAME and JSOP_*GNAME + don't ever get emitted. See the comment for + the field |selfHostingMode| in Parser.h for details. */ + BytecodeEmitter(BytecodeEmitter *parent, Parser *parser, SharedContext *sc, HandleScript script, StackFrame *callerFrame, bool hasGlobalScope, - unsigned lineno); + unsigned lineno, bool selfHostingMode = false); bool init(); /* diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 627380b554df..d21cdfcd31cd 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -118,7 +118,7 @@ Parser::Parser(JSContext *cx, const CompileOptions &options, keepAtoms(cx->runtime), foldConstants(foldConstants), compileAndGo(options.compileAndGo), - allowIntrinsicsCalls(options.allowIntrinsicsCalls) + selfHostingMode(options.selfHostingMode) { cx->activeCompilations++; } @@ -837,9 +837,10 @@ Parser::newFunction(TreeContext *tc, JSAtom *atom, FunctionSyntaxKind kind) parent = tc->sc->inFunction() ? NULL : tc->sc->scopeChain(); RootedFunction fun(context); - fun = js_NewFunction(context, NULL, NULL, 0, - JSFUN_INTERPRETED | (kind == Expression ? JSFUN_LAMBDA : 0), - parent, atom); + uint32_t flags = JSFUN_INTERPRETED | (kind == Expression ? JSFUN_LAMBDA : 0); + if (selfHostingMode) + flags |= JSFUN_SELF_HOSTED; + fun = js_NewFunction(context, NULL, NULL, 0, flags, parent, atom); if (fun && !compileAndGo) { if (!JSObject::clearParent(context, fun)) return NULL; @@ -6773,7 +6774,7 @@ Parser::primaryExpr(TokenKind tt, bool afterDoubleDot) return new_(tokenStream.currentToken().pos); case TOK_MOD: - if (allowIntrinsicsCalls) + if (selfHostingMode) return intrinsicName(); else goto syntaxerror; diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 643b36bed114..e8974aced7c9 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -55,10 +55,20 @@ struct Parser : private AutoGCRooter const bool compileAndGo:1; /* - * Self-hosted scripts can use the special syntax %funName(..args) to call - * internal functions. + * In self-hosting mode, scripts emit JSOP_CALLINTRINSIC instead of + * JSOP_NAME or JSOP_GNAME to access unbound variables. JSOP_CALLINTRINSIC + * does a name lookup in a special object that contains properties + * installed during global initialization and that properties from + * self-hosted scripts get copied into lazily upon first access in a + * global. + * As that object is inaccessible to client code, the lookups are + * guaranteed to return the original objects, ensuring safe implementation + * of self-hosted builtins. + * Additionally, the special syntax %_CallName(receiver, ...args, fun) is + * supported, for which bytecode is emitted that invokes |fun| with + * |receiver| as the this-object and ...args as the arguments.. */ - const bool allowIntrinsicsCalls:1; + const bool selfHostingMode:1; public: Parser(JSContext *cx, const CompileOptions &options, diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index fd127178e188..4eb636ea5bf3 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -733,6 +733,7 @@ JSRuntime::JSRuntime() #ifdef JS_METHODJIT jaegerRuntime_(NULL), #endif + selfHostedGlobal_(NULL), nativeStackBase(0), nativeStackQuota(0), interpreterFrames(NULL), @@ -890,7 +891,8 @@ JSRuntime::init(uint32_t maxbytes) if (!(atomsCompartment = this->new_(this)) || !atomsCompartment->init(NULL) || - !compartments.append(atomsCompartment)) { + !compartments.append(atomsCompartment)) + { Foreground::delete_(atomsCompartment); return false; } @@ -4882,22 +4884,19 @@ JS_CloneFunctionObject(JSContext *cx, JSObject *funobjArg, JSRawObject parentArg } /* - * If a function was compiled as compile-and-go or was compiled to be - * lexically nested inside some other script, we cannot clone it without - * breaking the compiler's assumptions. + * If a function was compiled to be lexically nested inside some other + * script, we cannot clone it without breaking the compiler's assumptions. */ RootedFunction fun(cx, funobj->toFunction()); - if (fun->isInterpreted() && - (fun->script()->compileAndGo || fun->script()->enclosingStaticScope())) + if (fun->isInterpreted() && (fun->script()->enclosingStaticScope() || + (fun->script()->compileAndGo && !parent->isGlobal()))) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, - JSMSG_BAD_CLONE_FUNOBJ_SCOPE); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_CLONE_FUNOBJ_SCOPE); return NULL; } if (fun->isBoundFunction()) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, - JSMSG_CANT_CLONE_OBJECT); + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CLONE_OBJECT); return NULL; } @@ -5019,7 +5018,7 @@ JS_DefineFunctions(JSContext *cx, JSObject *objArg, JSFunctionSpec *fs) flags &= ~JSFUN_GENERIC_NATIVE; fun = js_DefineFunction(cx, ctor, id, js_generic_native_method_dispatcher, - fs->nargs + 1, flags, JSFunction::ExtendedFinalizeKind); + fs->nargs + 1, flags, NULL, JSFunction::ExtendedFinalizeKind); if (!fun) return JS_FALSE; @@ -5030,7 +5029,7 @@ JS_DefineFunctions(JSContext *cx, JSObject *objArg, JSFunctionSpec *fs) fun->setExtendedSlot(0, PrivateValue(fs)); } - fun = js_DefineFunction(cx, obj, id, fs->call.op, fs->nargs, flags); + fun = js_DefineFunction(cx, obj, id, fs->call.op, fs->nargs, flags, fs->selfHostedName); if (!fun) return JS_FALSE; if (fs->call.info) @@ -5196,7 +5195,7 @@ JS::CompileOptions::CompileOptions(JSContext *cx) lineno(1), compileAndGo(cx->hasRunOption(JSOPTION_COMPILE_N_GO)), noScriptRval(cx->hasRunOption(JSOPTION_NO_SCRIPT_RVAL)), - allowIntrinsicsCalls(false), + selfHostingMode(false), sourcePolicy(SAVE_SOURCE) { } diff --git a/js/src/jsapi.h b/js/src/jsapi.h index e484b82f80e4..bdc5338a26b0 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2540,6 +2540,11 @@ class AutoIdRooter : private AutoGCRooter /* Function flags, internal use only, returned by JS_GetFunctionFlags. */ #define JSFUN_LAMBDA 0x08 /* expressed, not declared, function */ + +#define JSFUN_SELF_HOSTED 0x40 /* function is self-hosted native and + must not be decompilable nor + constructible. */ + #define JSFUN_HEAVYWEIGHT 0x80 /* activation requires a Call object */ #define JSFUN_HEAVYWEIGHT_TEST(f) ((f) & JSFUN_HEAVYWEIGHT) @@ -4414,11 +4419,17 @@ struct JSPropertySpec { JSStrictPropertyOpWrapper setter; }; +/* + * To define a native function, set call to a JSNativeWrapper. To define a + * self-hosted function, set selfHostedName to the name of a function + * compiled during JSRuntime::initSelfHosting. + */ struct JSFunctionSpec { const char *name; JSNativeWrapper call; uint16_t nargs; uint16_t flags; + const char *selfHostedName; }; /* @@ -5147,7 +5158,7 @@ struct JS_PUBLIC_API(CompileOptions) { unsigned lineno; bool compileAndGo; bool noScriptRval; - bool allowIntrinsicsCalls; + bool selfHostingMode; enum SourcePolicy { NO_SOURCE, LAZY_SOURCE, @@ -5164,7 +5175,7 @@ struct JS_PUBLIC_API(CompileOptions) { } CompileOptions &setCompileAndGo(bool cng) { compileAndGo = cng; return *this; } CompileOptions &setNoScriptRval(bool nsr) { noScriptRval = nsr; return *this; } - CompileOptions &setAllowIntrinsicsCalls(bool aic) { allowIntrinsicsCalls = aic; return *this; } + CompileOptions &setSelfHostingMode(bool shm) { selfHostingMode = shm; return *this; } CompileOptions &setSourcePolicy(SourcePolicy sp) { sourcePolicy = sp; return *this; } }; diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index bf3877456da7..73513529d933 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -58,6 +58,8 @@ #include "jscompartment.h" #include "jsobjinlines.h" +#include "selfhosted.out.h" + using namespace js; using namespace js::gc; @@ -201,6 +203,84 @@ JSRuntime::createJaegerRuntime(JSContext *cx) } #endif + +static JSClass self_hosting_global_class = { + "self-hosting-global", JSCLASS_GLOBAL_FLAGS, + JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, JS_StrictPropertyStub, + JS_EnumerateStub, JS_ResolveStub, + JS_ConvertStub, NULL +}; +bool +JSRuntime::initSelfHosting(JSContext *cx) +{ + JS_ASSERT(!selfHostedGlobal_); + RootedObject savedGlobal(cx, JS_GetGlobalObject(cx)); + if (!(selfHostedGlobal_ = JS_NewGlobalObject(cx, &self_hosting_global_class, NULL))) + return false; + JS_SetGlobalObject(cx, selfHostedGlobal_); + + JSAutoEnterCompartment ac; + if (!ac.enter(cx, cx->global())) + return false; + + const char *src = selfhosted::raw_sources; + uint32_t srcLen = selfhosted::GetRawScriptsSize(); + + CompileOptions options(cx); + options.setFileAndLine("self-hosted", 1); + options.setSelfHostingMode(true); + + RootedObject shg(cx, selfHostedGlobal_); + Value rv; + if (!Evaluate(cx, shg, options, src, srcLen, &rv)) + return false; + + JS_SetGlobalObject(cx, savedGlobal); + return true; +} + +void +JSRuntime::markSelfHostedGlobal(JSTracer *trc) +{ + MarkObjectRoot(trc, &selfHostedGlobal_, "self-hosting global"); +} + +JSFunction * +JSRuntime::getSelfHostedFunction(JSContext *cx, const char *name) +{ + RootedObject holder(cx, cx->global()->getIntrinsicsHolder()); + JSAtom *atom = Atomize(cx, name, strlen(name)); + if (!atom) + return NULL; + Value funVal = NullValue(); + JSAutoByteString bytes; + if (!cloneSelfHostedValueById(cx, AtomToId(atom), holder, &funVal)) + return NULL; + return funVal.toObject().toFunction(); +} + +bool +JSRuntime::cloneSelfHostedValueById(JSContext *cx, jsid id, HandleObject holder, Value *vp) +{ + Value funVal; + { + RootedObject shg(cx, selfHostedGlobal_); + JSAutoEnterCompartment ac; + if (!ac.enter(cx, shg) || !JS_GetPropertyById(cx, shg, id, &funVal) || !funVal.isObject()) + return false; + } + + RootedObject clone(cx, JS_CloneFunctionObject(cx, &funVal.toObject(), cx->global())); + if (!clone) + return false; + + vp->setObjectOrNull(clone); + DebugOnly ok = JS_DefinePropertyById(cx, holder, id, *vp, NULL, NULL, 0); + JS_ASSERT(ok); + return true; +} + JSScript * js_GetCurrentScript(JSContext *cx) { @@ -234,11 +314,11 @@ js::NewContext(JSRuntime *rt, size_t stackChunkSize) /* * If cx is the first context on this runtime, initialize well-known atoms, - * keywords, numbers, and strings. If one of these steps should fail, the - * runtime will be left in a partially initialized state, with zeroes and - * nulls stored in the default-initialized remainder of the struct. We'll - * clean the runtime up under DestroyContext, because cx will be "last" - * as well as "first". + * keywords, numbers, strings and self-hosted scripts. If one of these + * steps should fail, the runtime will be left in a partially initialized + * state, with zeroes and nulls stored in the default-initialized remainder + * of the struct. We'll clean the runtime up under DestroyContext, because + * cx will be "last" as well as "first". */ if (first) { #ifdef JS_THREADSAFE @@ -247,6 +327,8 @@ js::NewContext(JSRuntime *rt, size_t stackChunkSize) bool ok = rt->staticStrings.init(cx); if (ok) ok = InitCommonAtoms(cx); + if (ok) + ok = rt->initSelfHosting(cx); #ifdef JS_THREADSAFE JS_EndRequest(cx); @@ -384,10 +466,10 @@ static void PopulateReportBlame(JSContext *cx, JSErrorReport *report) { /* - * Walk stack until we find a frame that is associated with some script - * rather than a native frame. + * Walk stack until we find a frame that is associated with a non-builtin + * rather than a builtin frame. */ - ScriptFrameIter iter(cx); + NonBuiltinScriptFrameIter iter(cx); if (iter.done()) return; diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 62f784269cda..efb31c7573c1 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -407,6 +407,8 @@ struct JSRuntime : js::RuntimeFriendFields js::mjit::JaegerRuntime *jaegerRuntime_; #endif + JSObject *selfHostedGlobal_; + JSC::ExecutableAllocator *createExecutableAllocator(JSContext *cx); WTF::BumpPointerAllocator *createBumpPointerAllocator(JSContext *cx); js::mjit::JaegerRuntime *createJaegerRuntime(JSContext *cx); @@ -435,6 +437,11 @@ struct JSRuntime : js::RuntimeFriendFields } #endif + bool initSelfHosting(JSContext *cx); + void markSelfHostedGlobal(JSTracer *trc); + JSFunction *getSelfHostedFunction(JSContext *cx, const char *name); + bool cloneSelfHostedValueById(JSContext *cx, jsid id, js::HandleObject holder, js::Value *vp); + /* Base address of the native stack for the current thread. */ uintptr_t nativeStackBase; diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index 75d85ad7228b..db9968f01060 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -260,7 +260,7 @@ InitExnPrivate(JSContext *cx, HandleObject exnObject, HandleString message, Vector frames(cx); { SuppressErrorsGuard seg(cx); - for (ScriptFrameIter i(cx); !i.done(); ++i) { + for (NonBuiltinScriptFrameIter i(cx); !i.done(); ++i) { StackFrame *fp = i.fp(); /* @@ -560,7 +560,7 @@ Exception(JSContext *cx, unsigned argc, Value *vp) } /* Find the scripted caller. */ - ScriptFrameIter iter(cx); + NonBuiltinScriptFrameIter iter(cx); /* XXX StackIter should not point directly to scripts. */ SkipRoot skip(cx, &iter); diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index a91a15bfe675..8df83c4582ed 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -364,7 +364,7 @@ js::DefineFunctionWithReserved(JSContext *cx, JSObject *objArg, const char *name if (!atom) return NULL; Rooted id(cx, AtomToId(atom)); - return js_DefineFunction(cx, obj, id, call, nargs, attrs, JSFunction::ExtendedFinalizeKind); + return js_DefineFunction(cx, obj, id, call, nargs, attrs, NULL, JSFunction::ExtendedFinalizeKind); } JS_FRIEND_API(JSFunction *) diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index fd224d2a5212..8a2664440b81 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -606,7 +606,7 @@ js::FunctionToString(JSContext *cx, HandleFunction fun, bool bodyOnly, bool lamb return NULL; } } - bool haveSource = fun->isInterpreted(); + bool haveSource = fun->isInterpreted() && !fun->isSelfHostedBuiltin(); if (haveSource && !fun->script()->scriptSource()->hasSourceData() && !fun->script()->loadSource(cx, &haveSource)) { @@ -709,7 +709,7 @@ js::FunctionToString(JSContext *cx, HandleFunction fun, bool bodyOnly, bool lamb if (!out.append(")")) return NULL; } - } else if (fun->isInterpreted()) { + } else if (fun->isInterpreted() && !fun->isSelfHostedBuiltin()) { if ((!bodyOnly && !out.append("() {\n ")) || !out.append("[sourceless code]") || (!bodyOnly && !out.append("\n}"))) @@ -1451,7 +1451,7 @@ js_CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent, JS_ASSERT(script); JS_ASSERT(script->compartment() == fun->compartment()); JS_ASSERT_IF(script->compartment() != cx->compartment, - !script->enclosingStaticScope() && !script->compileAndGo); + !script->enclosingStaticScope()); RootedObject scope(cx, script->enclosingStaticScope()); @@ -1475,7 +1475,7 @@ js_CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent, JSFunction * js_DefineFunction(JSContext *cx, HandleObject obj, HandleId id, Native native, - unsigned nargs, unsigned attrs, AllocKind kind) + unsigned nargs, unsigned attrs, const char *selfHostedName, AllocKind kind) { PropertyOp gop; StrictPropertyOp sop; @@ -1497,11 +1497,24 @@ js_DefineFunction(JSContext *cx, HandleObject obj, HandleId id, Native native, sop = NULL; } - fun = js_NewFunction(cx, NULL, native, nargs, - attrs & (JSFUN_FLAGS_MASK), - obj, - JSID_IS_ATOM(id) ? JSID_TO_ATOM(id) : NULL, - kind); + /* + * To support specifying both native and self-hosted functions using + * JSFunctionSpec, js_DefineFunction can be invoked with either native + * or selfHostedName set. It is assumed that selfHostedName is set if + * native isn't. + */ + if (native) { + JS_ASSERT(!selfHostedName); + fun = js_NewFunction(cx, NULL, native, nargs, + attrs & (JSFUN_FLAGS_MASK), + obj, + JSID_IS_ATOM(id) ? JSID_TO_ATOM(id) : NULL, + kind); + } else { + JS_ASSERT(attrs & JSFUN_INTERPRETED); + fun = cx->runtime->getSelfHostedFunction(cx, selfHostedName); + fun->atom = JSID_TO_ATOM(id); + } if (!fun) return NULL; diff --git a/js/src/jsfun.h b/js/src/jsfun.h index b3d0d9a9c56e..dcd6302d2c05 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -74,10 +74,13 @@ struct JSFunction : public JSObject bool hasRest() const { return flags & JSFUN_HAS_REST; } bool isInterpreted() const { return flags & JSFUN_INTERPRETED; } bool isNative() const { return !isInterpreted(); } + bool isSelfHostedBuiltin() const { return flags & JSFUN_SELF_HOSTED; } bool isNativeConstructor() const { return flags & JSFUN_CONSTRUCTOR; } bool isHeavyweight() const { return JSFUN_HEAVYWEIGHT_TEST(flags); } bool isFunctionPrototype() const { return flags & JSFUN_PROTOTYPE; } - bool isInterpretedConstructor() const { return isInterpreted() && !isFunctionPrototype(); } + bool isInterpretedConstructor() const { + return isInterpreted() && !isFunctionPrototype() && !isSelfHostedBuiltin(); + } bool isNamedLambda() const { return (flags & JSFUN_LAMBDA) && atom; } /* Returns the strictness of this function, which must be interpreted. */ @@ -228,7 +231,7 @@ js_CloneFunctionObject(JSContext *cx, js::HandleFunction fun, extern JSFunction * js_DefineFunction(JSContext *cx, js::HandleObject obj, js::HandleId id, JSNative native, - unsigned nargs, unsigned flags, + unsigned nargs, unsigned flags, const char *selfHostedName = NULL, js::gc::AllocKind kind = JSFunction::FinalizeKind); namespace js { diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 22db8d39d047..35576bc02c68 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -2496,6 +2496,7 @@ MarkRuntime(JSTracer *trc, bool useSavedRoots = false) #else MarkConservativeStackRoots(trc, useSavedRoots); #endif + rt->markSelfHostedGlobal(trc); } for (RootRange r = rt->gcRootsHash.all(); !r.empty(); r.popFront()) diff --git a/js/src/jsinterpinlines.h b/js/src/jsinterpinlines.h index 046cb9bb8fef..f309c6eb1b2e 100644 --- a/js/src/jsinterpinlines.h +++ b/js/src/jsinterpinlines.h @@ -80,7 +80,7 @@ ComputeThis(JSContext *cx, StackFrame *fp) if (thisv.isObject()) return true; if (fp->isFunctionFrame()) { - if (fp->fun()->inStrictMode()) + if (fp->fun()->inStrictMode() || fp->fun()->isSelfHostedBuiltin()) return true; /* * Eval function frames have their own |this| slot, which is a copy of the function's @@ -364,8 +364,7 @@ IntrinsicNameOperation(JSContext *cx, JSScript *script, jsbytecode *pc, Value *v JSOp op = JSOp(*pc); RootedPropertyName name(cx); name = GetNameFromBytecode(cx, script, pc, op); - JSFunction *fun = cx->global()->getIntrinsicFunction(cx, name); - vp->setObject(*fun); + cx->global()->getIntrinsicValue(cx, name, vp); return true; } diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 534361918817..cffc5a1dc928 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -5569,7 +5569,7 @@ js_DecompileFunctionBody(JSPrinter *jp) JS_ASSERT(jp->fun); JS_ASSERT(!jp->script); - if (!jp->fun->isInterpreted()) { + if (jp->fun->isNative() || jp->fun->isSelfHostedBuiltin()) { js_printf(jp, native_code_str); return JS_TRUE; } @@ -5604,7 +5604,7 @@ js_DecompileFunction(JSPrinter *jp) return JS_FALSE; js_puts(jp, "("); - if (!fun->isInterpreted()) { + if (fun->isNative() || fun->isSelfHostedBuiltin()) { js_printf(jp, ") {\n"); jp->indent += 4; js_printf(jp, native_code_str); @@ -5738,7 +5738,7 @@ static JSObject * GetBlockChainAtPC(JSContext *cx, JSScript *script, jsbytecode *pc) { jsbytecode *start = script->main(); - + JS_ASSERT(pc >= start && pc < script->code + script->length); JSObject *blockChain = NULL; @@ -5773,7 +5773,7 @@ GetBlockChainAtPC(JSContext *cx, JSScript *script, jsbytecode *pc) break; } } - + return blockChain; } @@ -6131,7 +6131,8 @@ ExpressionDecompiler::getOutput(char **res) } static bool -FindStartPC(JSContext *cx, JSScript *script, int spindex, Value v, jsbytecode **valuepc) +FindStartPC(JSContext *cx, JSScript *script, int spindex, int skipStackHits, + Value v, jsbytecode **valuepc) { jsbytecode *current = *valuepc; @@ -6150,10 +6151,11 @@ FindStartPC(JSContext *cx, JSScript *script, int spindex, Value v, jsbytecode ** // exception. Value *stackBase = cx->regs().spForStackDepth(0); Value *sp = cx->regs().sp; + int stackHits = 0; do { if (sp == stackBase) return true; - } while (*--sp != v); + } while (*--sp != v || stackHits++ != skipStackHits); if (sp < stackBase + pcstack.depth()) *valuepc = pcstack[sp - stackBase]; } else { @@ -6163,7 +6165,7 @@ FindStartPC(JSContext *cx, JSScript *script, int spindex, Value v, jsbytecode ** } static bool -DecompileExpressionFromStack(JSContext *cx, int spindex, Value v, char **res) +DecompileExpressionFromStack(JSContext *cx, int spindex, int skipStackHits, Value v, char **res) { JS_ASSERT(spindex < 0 || spindex == JSDVG_IGNORE_STACK || @@ -6183,7 +6185,7 @@ DecompileExpressionFromStack(JSContext *cx, int spindex, Value v, char **res) if (valuepc < script->main()) return true; - if (!FindStartPC(cx, script, spindex, v, &valuepc)) + if (!FindStartPC(cx, script, spindex, skipStackHits, v, &valuepc)) return false; if (!valuepc) return true; @@ -6199,12 +6201,12 @@ DecompileExpressionFromStack(JSContext *cx, int spindex, Value v, char **res) char * js::DecompileValueGenerator(JSContext *cx, int spindex, HandleValue v, - HandleString fallbackArg) + HandleString fallbackArg, int skipStackHits) { RootedString fallback(cx, fallbackArg); { char *result; - if (!DecompileExpressionFromStack(cx, spindex, v, &result)) + if (!DecompileExpressionFromStack(cx, spindex, skipStackHits, v, &result)) return NULL; if (result) { if (strcmp(result, "(intermediate value)")) diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index a057d5a12ed1..7262795267ff 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -355,10 +355,16 @@ namespace js { * spindex is the negative index of v, measured from cx->fp->sp, or from a * lower frame's sp if cx->fp is native. * + * The optional argument skipStackHits can be used to skip a hit in the stack + * frame. This can be useful in self-hosted code that wants to report value + * errors containing decompiled values that are useful for the user, instead of + * values used internally by the self-hosted code. + * * The caller must call JS_free on the result after a succsesful call. */ char * -DecompileValueGenerator(JSContext *cx, int spindex, HandleValue v, HandleString fallback); +DecompileValueGenerator(JSContext *cx, int spindex, HandleValue v, + HandleString fallback, int skipStackHits = 0); /* * Sprintf, but with unlimited and automatically allocated buffering. diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl index 124bcb6a64a7..2eda2c688eb2 100644 --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -353,7 +353,7 @@ OPDEF(JSOP_ALIASEDVARDEC, 142,"aliasedvardec",NULL, 10, 0, 1, 15, JOF_SCOPEC /* * Intrinsic names have the syntax %name and can only be used when the - * CompileOptions flag "allowIntrinsicsCalls" is set. + * CompileOptions flag "selfHostingMode" is set. * * They are used to access intrinsic functions the runtime doesn't give * client JS code access to from self-hosted code. diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 9aedd0662c8c..fdfc01e813b6 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1999,7 +1999,7 @@ void CurrentScriptFileLineOriginSlow(JSContext *cx, const char **file, unsigned *linenop, JSPrincipals **origin) { - ScriptFrameIter iter(cx); + NonBuiltinScriptFrameIter iter(cx); if (iter.done()) { *file = NULL; diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 411daa7b6ada..5c1fc36bb64d 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -5602,8 +5602,9 @@ mjit::Compiler::jsop_setprop(PropertyName *name, bool popGuaranteed) void mjit::Compiler::jsop_intrinsicname(PropertyName *name, JSValueType type) { - JSFunction *fun = cx->global().get()->getIntrinsicFunction(cx, name); - frame.push(ObjectValue(*fun)); + Value vp = NullValue(); + cx->global().get()->getIntrinsicValue(cx, name, &vp); + frame.push(vp); } void @@ -5908,11 +5909,13 @@ mjit::Compiler::jsop_this() frame.pushThis(); /* - * In strict mode code, we don't wrap 'this'. + * In strict mode and self-hosted code, we don't wrap 'this'. * In direct-call eval code, we wrapped 'this' before entering the eval. * In global code, 'this' is always an object. */ - if (script->function() && !script->strictModeCode) { + if (script->function() && !script->strictModeCode && + !script->function()->isSelfHostedBuiltin()) + { FrameEntry *thisFe = frame.peek(-1); if (!thisFe->isType(JSVAL_TYPE_OBJECT)) { diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 2511ce3fba3b..76f12706de93 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -173,8 +173,69 @@ ProtoSetter(JSContext *cx, unsigned argc, Value *vp) return CallNonGenericMethod(cx, TestProtoSetterThis, ProtoSetterImpl, args); } +static JSBool +intrinsic_ToObject(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedValue val(cx, args[0]); + RootedObject obj(cx, ToObject(cx, val)); + if (!obj) + return false; + args.rval().set(OBJECT_TO_JSVAL(obj)); + return true; +} + +static JSBool +intrinsic_ToInteger(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + double result; + if (!ToInteger(cx, args[0], &result)) + return false; + args.rval().set(DOUBLE_TO_JSVAL(result)); + return true; +} + +static JSBool +intrinsic_IsCallable(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + Value val = args[0]; + bool isCallable = val.isObject() && val.toObject().isCallable(); + args.rval().set(BOOLEAN_TO_JSVAL(isCallable)); + return true; +} + +static JSBool +intrinsic_ThrowError(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + JS_ASSERT(args.length() >= 1); + uint32_t errorNumber = args[0].toInt32(); + + char *errorArgs[3] = {NULL, NULL, NULL}; + for (int i = 1; i < 3 && i < args.length(); i++) { + RootedValue val(cx, args[i]); + if (val.isInt32() || val.isString()) { + errorArgs[i - 1] = JS_EncodeString(cx, ToString(cx, val)); + } else { + ptrdiff_t spIndex = cx->stack.spIndexOf(val.address()); + errorArgs[i - 1] = DecompileValueGenerator(cx, spIndex, val, NullPtr(), 1); + } + } + + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, errorNumber, + errorArgs[0], errorArgs[1], errorArgs[2]); + for (uint32_t i = 0; i < 3; i++) + cx->free_(errorArgs[i]); + return false; +} + JSFunctionSpec intrinsic_functions[] = { - JS_FN("ThrowTypeError", ThrowTypeError, 0,0), + JS_FN("ToObject", intrinsic_ToObject, 1,0), + JS_FN("ToInteger", intrinsic_ToInteger, 1,0), + JS_FN("IsCallable", intrinsic_IsCallable, 1,0), + JS_FN("ThrowError", intrinsic_ThrowError, 4,0), JS_FS_END }; JSObject * diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index f241d8b55877..fd9cc6abf014 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -320,6 +320,11 @@ class GlobalObject : public JSObject return &getPrototype(JSProto_Iterator).toObject(); } + JSObject *getIntrinsicsHolder() { + JS_ASSERT(!getSlotRef(INTRINSICS).isUndefined()); + return &getSlotRef(INTRINSICS).toObject(); + } + private: typedef bool (*ObjectInitOp)(JSContext *cx, Handle global); @@ -369,12 +374,18 @@ class GlobalObject : public JSObject return HasDataProperty(cx, holder, NameToId(name), &fun); } - JSFunction *getIntrinsicFunction(JSContext *cx, PropertyName *name) { + bool getIntrinsicValue(JSContext *cx, PropertyName *name, Value *vp) { RootedObject holder(cx, &getSlotRef(INTRINSICS).toObject()); - Value fun = NullValue(); - DebugOnly ok = HasDataProperty(cx, holder, NameToId(name), &fun); + jsid id = NameToId(name); + if (HasDataProperty(cx, holder, id, vp)) + return true; + bool ok = cx->runtime->cloneSelfHostedValueById(cx, id, holder, vp); + if (!ok) + return false; + + ok = JS_DefinePropertyById(cx, holder, id, *vp, NULL, NULL, 0); JS_ASSERT(ok); - return fun.toObject().toFunction(); + return true; } inline RegExpStatics *getRegExpStatics() const; diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index 8a11e73767f2..a51e1f9f42c3 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -1743,6 +1743,21 @@ class ScriptFrameIter : public StackIter ScriptFrameIter &operator++() { StackIter::operator++(); settle(); return *this; } }; +/* A filtering of the StackIter to only stop at non-self-hosted scripts. */ +class NonBuiltinScriptFrameIter : public StackIter +{ + void settle() { + while (!done() && (!isScript() || (isFunctionFrame() && fp()->fun()->isSelfHostedBuiltin()))) + StackIter::operator++(); + } + + public: + NonBuiltinScriptFrameIter(JSContext *cx, StackIter::SavedOption opt = StackIter::STOP_AT_SAVED) + : StackIter(cx, opt) { settle(); } + + NonBuiltinScriptFrameIter &operator++() { StackIter::operator++(); settle(); return *this; } +}; + /*****************************************************************************/ /* diff --git a/toolkit/content/license.html b/toolkit/content/license.html index fd90c6e0a601..c9e77ea62491 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -2839,10 +2839,10 @@ Additional Contributors:

V8 License

-

This license applies to certain files in the directory - js/src/v8-dtoa.

+

This license applies to certain files in the directories + js/src/v8-dtoa and js/src/builtin.

-Copyright 2006-2008 the V8 project authors. All rights reserved.
+Copyright 2006-2012 the V8 project authors. All rights reserved.
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
 met