diff --git a/js/narcissus/js.js b/js/narcissus/js.js index f7a116bc7fc..2147bd33a56 100644 --- a/js/narcissus/js.js +++ b/js/narcissus/js.js @@ -68,6 +68,7 @@ function my_load(filename) { } my_load('jsdefs.js'); +my_load('jslex.js'); my_load('jsparse.js'); my_load('jsexec.js'); diff --git a/js/narcissus/jsdefs.js b/js/narcissus/jsdefs.js index a1031d798d0..35414c3eb71 100644 --- a/js/narcissus/jsdefs.js +++ b/js/narcissus/jsdefs.js @@ -42,8 +42,6 @@ * separately to take advantage of the simple switch-case constant propagation * done by SpiderMonkey. */ -const GLOBAL = this; - var tokens = [ // End of source. "END", @@ -95,9 +93,9 @@ var tokens = [ ]; // Operator and punctuator mapping from token to tree node type name. -// NB: superstring tokens (e.g., ++) must come before their substring token -// counterparts (+ in the example), so that the opRegExp regular expression -// synthesized from this list makes the longest possible match. +// NB: because the lexer doesn't backtrack, all token prefixes must themselves +// be valid tokens (e.g. !== is acceptable because its prefixes are the valid +// tokens != and !). var opTypeNames = { '\n': "NEWLINE", ';': "SEMICOLON", @@ -144,18 +142,21 @@ var opTypeNames = { var keywords = {__proto__: null}; // Define const END, etc., based on the token names. Also map name to index. +var tokenIds = {}; var consts = "const "; for (var i = 0, j = tokens.length; i < j; i++) { if (i > 0) consts += ", "; var t = tokens[i]; + var name; if (/^[a-z]/.test(t)) { - consts += t.toUpperCase(); + name = t.toUpperCase(); keywords[t] = i; } else { - consts += (/^\W/.test(t) ? opTypeNames[t] : t); + name = (/^\W/.test(t) ? opTypeNames[t] : t); } - consts += " = " + i; + consts += name + " = " + i; + tokenIds[name] = i; tokens[t] = i; } eval(consts + ";"); diff --git a/js/narcissus/jsexec.js b/js/narcissus/jsexec.js index 74ce739e39b..acc56fb963c 100644 --- a/js/narcissus/jsexec.js +++ b/js/narcissus/jsexec.js @@ -110,9 +110,9 @@ var global = { var s = {object: global, parent: null}; return new FunctionObject(f, s); }, - Array: function Array(dummy) { + Array: function (dummy) { // Array when called as a function acts as a constructor. - return GLOBAL.Array.apply(this, arguments); + return Array.apply(this, arguments); }, String: function String(s) { // Called as function or constructor: convert argument to string type. diff --git a/js/narcissus/jslex.js b/js/narcissus/jslex.js new file mode 100644 index 00000000000..6e848691c75 --- /dev/null +++ b/js/narcissus/jslex.js @@ -0,0 +1,430 @@ +/* vim: set sw=4 ts=8 et tw=78: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Narcissus JavaScript engine. + * + * The Initial Developer of the Original Code is + * Brendan Eich . + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Narcissus - JS implemented in JS. + * + * Lexical scanner. + */ + +// Build up a trie of operator tokens. +var opTokens = {}; +for (var op in opTypeNames) { + if (op === '\n' || op === '.') + continue; + + var node = opTokens; + for (var i = 0; i < op.length; i++) { + var ch = op[i]; + if (!(ch in node)) + node[ch] = {}; + node = node[ch]; + node.op = op; + } +} + +function Tokenizer(s, f, l) { + this.cursor = 0; + this.source = String(s); + this.tokens = []; + this.tokenIndex = 0; + this.lookahead = 0; + this.scanNewlines = false; + this.scanOperand = true; + this.filename = f || ""; + this.lineno = l || 1; +} + +Tokenizer.prototype = { + get done() { + return this.peek() == END; + }, + + get token() { + return this.tokens[this.tokenIndex]; + }, + + match: function (tt) { + return this.get() == tt || this.unget(); + }, + + mustMatch: function (tt) { + if (!this.match(tt)) + throw this.newSyntaxError("Missing " + tokens[tt].toLowerCase()); + return this.token; + }, + + peek: function () { + var tt, next; + if (this.lookahead) { + next = this.tokens[(this.tokenIndex + this.lookahead) & 3]; + tt = (this.scanNewlines && next.lineno != this.lineno) + ? NEWLINE + : next.type; + } else { + tt = this.get(); + this.unget(); + } + return tt; + }, + + peekOnSameLine: function () { + this.scanNewlines = true; + var tt = this.peek(); + this.scanNewlines = false; + return tt; + }, + + // Eats comments and whitespace. + skip: function () { + var input = this.source; + for (;;) { + var ch = input[this.cursor++]; + var next = input[this.cursor]; + if (ch === '\n' && !this.scanNewlines) { + this.lineno++; + } else if (ch === '/' && next === '*') { + this.cursor++; + for (;;) { + ch = input[this.cursor++]; + if (ch === undefined) + throw this.newSyntaxError("Unterminated comment"); + + if (ch === '*') { + next = input[this.cursor]; + if (next === '/') { + this.cursor++; + break; + } + } else if (ch === '\n') { + this.lineno++; + } + } + } else if (ch === '/' && next === '/') { + this.cursor++; + for (;;) { + ch = input[this.cursor++]; + if (ch === undefined) + return; + + if (ch === '\n') { + this.lineno++; + break; + } + } + } else if (ch !== ' ' && ch !== '\t') { + this.cursor--; + return; + } + } + }, + + // Lexes the exponential part of a number, if present. Returns true iff an + // exponential part was found. + lexExponent: function() { + var input = this.source; + var next = input[this.cursor]; + if (next === 'e' || next === 'E') { + this.cursor++; + ch = input[this.cursor++]; + if (ch === '+' || ch === '-') + ch = input[this.cursor++]; + + if (ch < '0' || ch > '9') + throw this.newSyntaxError("Missing exponent"); + + do { + ch = input[this.cursor++]; + } while (ch >= '0' && ch <= '9'); + this.cursor--; + + return true; + } + + return false; + }, + + lexZeroNumber: function (ch) { + var token = this.token, input = this.source; + token.type = NUMBER; + + ch = input[this.cursor++]; + if (ch === '.') { + do { + ch = input[this.cursor++]; + } while (ch >= '0' && ch <= '9'); + this.cursor--; + + this.lexExponent(); + token.value = parseFloat(token.start, this.cursor); + } else if (ch === 'x' || ch === 'X') { + do { + ch = input[this.cursor++]; + } while ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || + (ch >= 'A' && ch <= 'F')); + this.cursor--; + + token.value = parseInt(input.substring(token.start, this.cursor)); + } else if (ch >= '0' && ch <= '7') { + do { + ch = input[this.cursor++]; + } while (ch >= '0' && ch <= '7'); + this.cursor--; + + token.value = parseInt(input.substring(token.start, this.cursor)); + } else { + this.cursor--; + this.lexExponent(); // 0E1, &c. + token.value = 0; + } + }, + + lexNumber: function (ch) { + var token = this.token, input = this.source; + token.type = NUMBER; + + var floating = false; + do { + ch = input[this.cursor++]; + if (ch === '.' && !floating) { + floating = true; + ch = input[this.cursor++]; + } + } while (ch >= '0' && ch <= '9'); + + this.cursor--; + + var exponent = this.lexExponent(); + floating = floating || exponent; + + var str = input.substring(token.start, this.cursor); + token.value = floating ? parseFloat(str) : parseInt(str); + }, + + lexDot: function (ch) { + var token = this.token, input = this.source; + var next = input[this.cursor]; + if (next >= '0' && next <= '9') { + do { + ch = input[this.cursor++]; + } while (ch >= '0' && ch <= '9'); + this.cursor--; + + this.lexExponent(); + + token.type = NUMBER; + token.value = parseFloat(token.start, this.cursor); + } else { + token.type = DOT; + token.assignOp = null; + token.value = '.'; + } + }, + + lexString: function (ch) { + var token = this.token, input = this.source; + token.type = STRING; + + var hasEscapes = false; + var delim = ch; + ch = input[this.cursor++]; + while (ch !== delim) { + if (ch === '\\') { + hasEscapes = true; + this.cursor++; + } + ch = input[this.cursor++]; + } + + token.value = (hasEscapes) + ? eval(input.substring(token.start, this.cursor)) + : input.substring(token.start + 1, this.cursor - 1); + }, + + lexRegExp: function (ch) { + var token = this.token, input = this.source; + token.type = REGEXP; + + do { + ch = input[this.cursor++]; + if (ch === '\\') { + this.cursor++; + } else if (ch === '[') { + do { + if (ch === undefined) + throw this.newSyntaxError("Unterminated character class"); + + if (ch === '\\') + this.cursor++; + + ch = input[this.cursor++]; + } while (ch !== ']'); + } else if (ch === undefined) { + throw this.newSyntaxError("Unterminated regex"); + } + } while (ch !== '/'); + + do { + ch = input[this.cursor++]; + } while (ch >= 'a' && ch <= 'z'); + + this.cursor--; + + token.value = eval(input.substring(token.start, this.cursor)); + }, + + lexOp: function (ch) { + var token = this.token, input = this.source; + + // A bit ugly, but it seems wasteful to write a trie lookup routine for + // only 3 characters... + var node = opTokens[ch]; + var next = input[this.cursor]; + if (next in node) { + node = node[next]; + this.cursor++; + next = input[this.cursor]; + if (next in node) { + node = node[next]; + this.cursor++; + next = input[this.cursor]; + } + } + + var op = node.op; + if (assignOps[op] && input[this.cursor] === '=') { + this.cursor++; + token.type = ASSIGN; + token.assignOp = tokenIds[opTypeNames[op]]; + op += '='; + } else { + token.type = tokenIds[opTypeNames[op]]; + if (this.scanOperand) { + switch (token.type) { + case PLUS: token.type = UNARY_PLUS; break; + case MINUS: token.type = UNARY_MINUS; break; + } + } + + token.assignOp = null; + } + + token.value = op; + }, + + // FIXME: Unicode escape sequences + // FIXME: Unicode identifiers + lexIdent: function (ch) { + var token = this.token, input = this.source; + + do { + ch = input[this.cursor++]; + } while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || ch === '$' || ch === '_'); + + this.cursor--; // Put the non-word character back. + + var id = input.substring(token.start, this.cursor); + token.type = keywords[id] || IDENTIFIER; + token.value = id; + }, + + get: function () { + var token; + while (this.lookahead) { + --this.lookahead; + this.tokenIndex = (this.tokenIndex + 1) & 3; + token = this.tokens[this.tokenIndex]; + if (token.type != NEWLINE || this.scanNewlines) + return token.type; + } + + this.skip(); + + this.tokenIndex = (this.tokenIndex + 1) & 3; + token = this.tokens[this.tokenIndex]; + if (!token) + this.tokens[this.tokenIndex] = token = {}; + + var input = this.source; + if (this.cursor === input.length) + return token.type = END; + + token.start = this.cursor; + token.lineno = this.lineno; + + var ch = input[this.cursor++]; + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + ch === '$' || ch === '_') { + this.lexIdent(ch); + } else if (this.scanOperand && ch === '/') { + this.lexRegExp(ch); + } else if (ch in opTokens) { + this.lexOp(ch); + } else if (ch === '.') { + this.lexDot(ch); + } else if (ch >= '1' && ch <= '9') { + this.lexNumber(ch); + } else if (ch === '0') { + this.lexZeroNumber(ch); + } else if (ch === '"' || ch === "'") { + this.lexString(ch); + } else if (this.scanNewlines && ch === '\n') { + token.type = NEWLINE; + token.value = '\n'; + this.lineno++; + } else { + throw this.newSyntaxError("Illegal token"); + } + + token.end = this.cursor; + return token.type; + }, + + unget: function () { + if (++this.lookahead == 4) throw "PANIC: too much lookahead!"; + this.tokenIndex = (this.tokenIndex - 1) & 3; + }, + + newSyntaxError: function (m) { + var e = new SyntaxError(m, this.filename, this.lineno); + e.source = this.source; + e.cursor = this.cursor; + return e; + } +}; + diff --git a/js/narcissus/jsparse.js b/js/narcissus/jsparse.js index 6720ecdc432..39e2a2b678b 100644 --- a/js/narcissus/jsparse.js +++ b/js/narcissus/jsparse.js @@ -38,179 +38,9 @@ /* * Narcissus - JS implemented in JS. * - * Lexical scanner and parser. + * Parser. */ -// Build a regexp that recognizes operators and punctuators (except newline). -var opRegExpSrc = "^"; -for (i in opTypeNames) { - if (i == '\n') - continue; - if (opRegExpSrc != "^") - opRegExpSrc += "|^"; - opRegExpSrc += i.replace(/[?|^&(){}\[\]+\-*\/\.]/g, "\\$&"); -} -var opRegExp = new RegExp(opRegExpSrc); - -// A regexp to match floating point literals (but not integer literals). -var fpRegExp = /^\d+\.\d*(?:[eE][-+]?\d+)?|^\d+(?:\.\d*)?[eE][-+]?\d+|^\.\d+(?:[eE][-+]?\d+)?/; - -// A regexp to match regexp literals. -var reRegExp = /^\/((?:\\.|\[(?:\\.|[^\]])*\]|[^\/])+)\/([gimy]*)/; - -function Tokenizer(s, f, l) { - this.cursor = 0; - this.source = String(s); - this.tokens = []; - this.tokenIndex = 0; - this.lookahead = 0; - this.scanNewlines = false; - this.scanOperand = true; - this.filename = f || ""; - this.lineno = l || 1; -} - -Tokenizer.prototype = { - get input() { - return this.source.substring(this.cursor); - }, - - get done() { - return this.peek() == END; - }, - - get token() { - return this.tokens[this.tokenIndex]; - }, - - match: function (tt) { - return this.get() == tt || this.unget(); - }, - - mustMatch: function (tt) { - if (!this.match(tt)) - throw this.newSyntaxError("Missing " + tokens[tt].toLowerCase()); - return this.token; - }, - - peek: function () { - var tt, next; - if (this.lookahead) { - next = this.tokens[(this.tokenIndex + this.lookahead) & 3]; - if (this.scanNewlines && next.lineno != this.lineno) - tt = NEWLINE; - else - tt = next.type; - } else { - tt = this.get(); - this.unget(); - } - return tt; - }, - - peekOnSameLine: function () { - this.scanNewlines = true; - var tt = this.peek(); - this.scanNewlines = false; - return tt; - }, - - get: function () { - var token; - while (this.lookahead) { - --this.lookahead; - this.tokenIndex = (this.tokenIndex + 1) & 3; - token = this.tokens[this.tokenIndex]; - if (token.type != NEWLINE || this.scanNewlines) - return token.type; - } - - for (;;) { - var input = this.input; - var match = (this.scanNewlines ? /^[ \t]+/ : /^\s+/)(input); - if (match) { - var spaces = match[0]; - this.cursor += spaces.length; - var newlines = spaces.match(/\n/g); - if (newlines) - this.lineno += newlines.length; - input = this.input; - } - - if (!(match = /^\/(?:\*(?:.|\n)*?\*\/|\/.*)/(input))) - break; - var comment = match[0]; - this.cursor += comment.length; - newlines = comment.match(/\n/g); - if (newlines) - this.lineno += newlines.length - } - - this.tokenIndex = (this.tokenIndex + 1) & 3; - token = this.tokens[this.tokenIndex]; - if (!token) - this.tokens[this.tokenIndex] = token = {}; - - if (!input) - return token.type = END; - - if ((match = fpRegExp(input))) { - token.type = NUMBER; - token.value = parseFloat(match[0]); - } else if ((match = /^0[xX][\da-fA-F]+|^0[0-7]*|^\d+/(input))) { - token.type = NUMBER; - token.value = parseInt(match[0]); - } else if ((match = /^[$_\w]+/(input))) { // FIXME no ES3 unicode - var id = match[0]; - token.type = keywords[id] || IDENTIFIER; - token.value = id; - } else if ((match = /^"(?:\\.|[^"])*"|^'(?:\\.|[^'])*'/(input))) { //"){ - token.type = STRING; - token.value = eval(match[0]); - } else if (this.scanOperand && (match = reRegExp(input))) { - token.type = REGEXP; - token.value = new RegExp(match[1], match[2]); - } else if ((match = opRegExp(input))) { - var op = match[0]; - if (assignOps[op] && input[op.length] == '=') { - token.type = ASSIGN; - token.assignOp = GLOBAL[opTypeNames[op]]; - match[0] += '='; - } else { - token.type = GLOBAL[opTypeNames[op]]; - if (this.scanOperand && - (token.type == PLUS || token.type == MINUS)) { - token.type += UNARY_PLUS - PLUS; - } - token.assignOp = null; - } - token.value = op; - } else if (this.scanNewlines && (match = /^\n/(input))) { - token.type = NEWLINE; - } else { - throw this.newSyntaxError("Illegal token"); - } - - token.start = this.cursor; - this.cursor += match[0].length; - token.end = this.cursor; - token.lineno = this.lineno; - return token.type; - }, - - unget: function () { - if (++this.lookahead == 4) throw "PANIC: too much lookahead!"; - this.tokenIndex = (this.tokenIndex - 1) & 3; - }, - - newSyntaxError: function (m) { - var e = new SyntaxError(m, this.filename, this.lineno); - e.source = this.source; - e.cursor = this.cursor; - return e; - } -}; - function CompilerContext(inFunction) { this.inFunction = inFunction; this.stmtStack = []; @@ -690,7 +520,7 @@ var opPrecedence = { // Map operator type code to precedence. for (i in opPrecedence) - opPrecedence[GLOBAL[i]] = opPrecedence[i]; + opPrecedence[tokenIds[i]] = opPrecedence[i]; var opArity = { COMMA: -2, @@ -715,7 +545,7 @@ var opArity = { // Map operator type code to arity. for (i in opArity) - opArity[GLOBAL[i]] = opArity[i]; + opArity[tokenIds[i]] = opArity[i]; function Expression(t, x, stop) { var n, id, tt, operators = [], operands = []; diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 6d656bb4801..2ece93fa204 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -2248,6 +2248,11 @@ class AutoValueRooter : private AutoGCRooter JS_GUARD_OBJECT_NOTIFIER_INIT; } + void set(jsval v) { + JS_ASSERT(tag == JSVAL); + val = v; + } + void setObject(JSObject *obj) { JS_ASSERT(tag == JSVAL); val = OBJECT_TO_JSVAL(obj); diff --git a/js/src/jshashtable.h b/js/src/jshashtable.h index 692ee5527c6..29a2a545024 100644 --- a/js/src/jshashtable.h +++ b/js/src/jshashtable.h @@ -159,7 +159,7 @@ class HashTable : AllocPolicy return cur == end; } - const T &front() const { + T &front() const { JS_ASSERT(!empty()); return cur->t; } diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 47befa3cc20..62f8276900a 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -248,8 +248,6 @@ MarkSharpObjects(JSContext *cx, JSObject *obj, JSIdArray **idap) jsid id; JSObject *obj2; JSProperty *prop; - uintN attrs; - jsval val; JS_CHECK_RECURSION(cx, return NULL); @@ -279,32 +277,37 @@ MarkSharpObjects(JSContext *cx, JSObject *obj, JSIdArray **idap) break; if (!prop) continue; - ok = obj2->getAttributes(cx, id, prop, &attrs); - if (ok) { - if (obj2->isNative() && - (attrs & (JSPROP_GETTER | JSPROP_SETTER))) { - JSScopeProperty *sprop = (JSScopeProperty *) prop; - val = JSVAL_VOID; - if (attrs & JSPROP_GETTER) - val = sprop->getterValue(); - if (attrs & JSPROP_SETTER) { - if (val != JSVAL_VOID) { - /* Mark the getter, then set val to setter. */ - ok = (MarkSharpObjects(cx, JSVAL_TO_OBJECT(val), - NULL) - != NULL); - } - val = sprop->setterValue(); - } - } else { - ok = obj->getProperty(cx, id, &val); - } + bool hasGetter, hasSetter; + AutoValueRooter v(cx, JSVAL_VOID); + AutoValueRooter setter(cx, JSVAL_VOID); + if (obj->isNative()) { + JSScopeProperty *sprop = (JSScopeProperty *) prop; + hasGetter = sprop->hasGetterValue(); + hasSetter = sprop->hasSetterValue(); + if (hasGetter) + v.set(sprop->getterValue()); + if (hasSetter) + setter.set(sprop->setterValue()); + JS_UNLOCK_OBJ(cx, obj2); + } else { + obj->dropProperty(cx, prop); + hasGetter = hasSetter = false; } - obj2->dropProperty(cx, prop); - if (!ok) - break; - if (!JSVAL_IS_PRIMITIVE(val) && - !MarkSharpObjects(cx, JSVAL_TO_OBJECT(val), NULL)) { + if (hasSetter) { + /* Mark the getter, then set val to setter. */ + if (hasGetter && !JSVAL_IS_PRIMITIVE(v.value())) { + ok = !!MarkSharpObjects(cx, JSVAL_TO_OBJECT(v.value()), NULL); + if (!ok) + break; + } + v.set(setter.value()); + } else if (!hasGetter) { + ok = obj->getProperty(cx, id, v.addr()); + if (!ok) + break; + } + if (!JSVAL_IS_PRIMITIVE(v.value()) && + !MarkSharpObjects(cx, JSVAL_TO_OBJECT(v.value()), NULL)) { ok = JS_FALSE; break; } @@ -873,7 +876,7 @@ obj_toString(JSContext *cx, uintN argc, jsval *vp) if (!obj) return JS_FALSE; if (obj->isProxy()) { - if (!JS_GetProxyObjectClass(cx, obj, &clazz)) + if (!GetProxyObjectClass(cx, obj, &clazz)) return false; } else { obj = js_GetWrappedObject(cx, obj); @@ -1118,14 +1121,6 @@ obj_eval(JSContext *cx, uintN argc, jsval *vp) return JS_TRUE; } - /* - * If the caller is a lightweight function and doesn't have a variables - * object, then we need to provide one for the compiler to stick any - * declared (var) variables into. - */ - if (caller->fun && !caller->callobj && !js_GetCallObject(cx, caller)) - return JS_FALSE; - /* Accept an optional trailing argument that overrides the scope object. */ JSObject *scopeobj = NULL; if (argc >= 2) { @@ -1196,6 +1191,7 @@ obj_eval(JSContext *cx, uintN argc, jsval *vp) * NB: This means that native callers (who reach this point through * the C API) must use the two parameter form. */ + JS_ASSERT_IF(caller->argv, caller->callobj); scopeobj = callerScopeChain; } #endif @@ -3199,6 +3195,14 @@ js_DefineBlockVariable(JSContext *cx, JSObject *obj, jsid id, intN index) JSScopeProperty::HAS_SHORTID, index, NULL); } +static size_t +GetObjectSize(JSObject *obj) +{ + return (obj->isFunction() && !obj->getPrivate()) + ? sizeof(JSFunction) + : sizeof(JSObject); +} + /* * Use this method with extreme caution. It trades the guts of two objects and updates * scope ownership. This operation is not thread-safe, just as fast array to slow array @@ -3212,11 +3216,17 @@ JSObject::swap(JSObject *other) bool thisOwns = this->isNative() && scope()->object == this; bool otherOwns = other->isNative() && other->scope()->object == other; + size_t size = GetObjectSize(this); + JS_ASSERT(size == GetObjectSize(other)); + /* Trade the guts of the objects. */ - JSObject tmp; - memcpy(&tmp, this, sizeof(JSObject)); - memcpy(this, other, sizeof(JSObject)); - memcpy(other, &tmp, sizeof(JSObject)); + union { + JSFunction fun; + JSObject obj; + } tmp; + memcpy(&tmp, this, size); + memcpy(this, other, size); + memcpy(other, &tmp, size); /* Fixup scope ownerships. */ if (otherOwns) diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp index 38e8c257778..bd7b780aa91 100644 --- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -52,6 +52,8 @@ using namespace js; +namespace js { + JSProxyHandler::~JSProxyHandler() { } @@ -173,6 +175,11 @@ JSProxyHandler::finalize(JSContext *cx, JSObject *proxy) { } +void +JSProxyHandler::trace(JSTracer *trc, JSObject *proxy) +{ +} + JSNoopProxyHandler::JSNoopProxyHandler(JSObject *obj) : mWrappedObject(obj) { } @@ -276,6 +283,13 @@ JSNoopProxyHandler::finalize(JSContext *cx, JSObject *proxy) delete this; } +void +JSNoopProxyHandler::trace(JSTracer *trc, JSObject *proxy) +{ + if (mWrappedObject) + JS_CALL_OBJECT_TRACER(trc, mWrappedObject, "wrappedObject"); +} + void * JSNoopProxyHandler::family() { @@ -591,8 +605,8 @@ JSProxy::enumerateOwn(JSContext *cx, JSObject *proxy, JSIdArray **idap) return TryHandlerTrap(cx, proxy, ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->enumerateOwn(cx, proxy, idap)); } -JS_PUBLIC_API(JSBool) -JS_GetProxyObjectClass(JSContext *cx, JSObject *proxy, const char **namep) +JS_FRIEND_API(JSBool) +GetProxyObjectClass(JSContext *cx, JSObject *proxy, const char **namep) { if (!proxy->isProxy()) { char *bytes = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, @@ -712,7 +726,11 @@ proxy_TraceObject(JSTracer *trc, JSObject *obj) obj->traceProtoAndParent(trc); - JS_CALL_VALUE_TRACER(trc, obj->fslots[JSSLOT_PROXY_HANDLER], "handler"); + jsval handler = obj->fslots[JSSLOT_PROXY_HANDLER]; + if (!JSVAL_IS_PRIMITIVE(handler)) + JS_CALL_OBJECT_TRACER(trc, JSVAL_TO_OBJECT(handler), "handler"); + else + ((JSProxyHandler *) JSVAL_TO_PRIVATE(handler))->trace(trc, obj); if (obj->isFunctionProxy()) { JS_CALL_VALUE_TRACER(trc, obj->fslots[JSSLOT_PROXY_CALL], "call"); JS_CALL_VALUE_TRACER(trc, obj->fslots[JSSLOT_PROXY_CONSTRUCT], "construct"); @@ -756,7 +774,7 @@ obj_proxy_getObjectOps(JSContext *cx, JSClass *clasp) return &js_ObjectProxyObjectOps; } -JS_FRIEND_API(JSClass) js_ObjectProxyClass = { +JS_FRIEND_API(JSClass) ObjectProxyClass = { "ObjectProxy", JSCLASS_HAS_RESERVED_SLOTS(3) | JSCLASS_NEW_ENUMERATE, @@ -835,7 +853,7 @@ fun_proxy_getObjectOps(JSContext *cx, JSClass *clasp) return &js_FunctionProxyObjectOps; } -JS_FRIEND_API(JSClass) js_FunctionProxyClass = { +JS_FRIEND_API(JSClass) FunctionProxyClass = { "FunctionProxy", JSCLASS_HAS_RESERVED_SLOTS(3) | JSCLASS_NEW_ENUMERATE, @@ -845,10 +863,10 @@ JS_FRIEND_API(JSClass) js_FunctionProxyClass = { NULL, NULL, NULL, NULL }; -JS_PUBLIC_API(JSObject *) -JS_NewObjectProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *parent, JSString *className) +JS_FRIEND_API(JSObject *) +NewObjectProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *parent, JSString *className) { - JSObject *obj = NewObject(cx, &js_ObjectProxyClass, proto, parent); + JSObject *obj = NewObjectWithGivenProto(cx, &ObjectProxyClass, proto, parent); if (!obj) return NULL; obj->fslots[JSSLOT_PROXY_HANDLER] = handler; @@ -857,11 +875,11 @@ JS_NewObjectProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *paren return obj; } -JS_PUBLIC_API(JSObject *) -JS_NewFunctionProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *parent, - JSObject *call, JSObject *construct) +JS_FRIEND_API(JSObject *) +NewFunctionProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *parent, + JSObject *call, JSObject *construct) { - JSObject *obj = NewObject(cx, &js_FunctionProxyClass, proto, parent); + JSObject *obj = NewObjectWithGivenProto(cx, &FunctionProxyClass, proto, parent); if (!obj) return NULL; obj->fslots[JSSLOT_PROXY_HANDLER] = handler; @@ -901,7 +919,7 @@ proxy_create(JSContext *cx, uintN argc, jsval *vp) parent = JSVAL_TO_OBJECT(vp[0])->getParent(); } JSString *className = (argc > 2 && JSVAL_IS_STRING(vp[4])) ? JSVAL_TO_STRING(vp[4]) : NULL; - JSObject *proxy = JS_NewObjectProxy(cx, OBJECT_TO_JSVAL(handler), proto, parent, className); + JSObject *proxy = NewObjectProxy(cx, OBJECT_TO_JSVAL(handler), proto, parent, className); if (!proxy) return false; @@ -936,7 +954,7 @@ proxy_createFunction(JSContext *cx, uintN argc, jsval *vp) return false; } - JSObject *proxy = JS_NewFunctionProxy(cx, OBJECT_TO_JSVAL(handler), proto, parent, call, construct); + JSObject *proxy = NewFunctionProxy(cx, OBJECT_TO_JSVAL(handler), proto, parent, call, construct); if (!proxy) return false; @@ -974,7 +992,7 @@ proxy_fix(JSContext *cx, uintN argc, jsval *vp) return false; if (obj->isProxy()) { JSBool flag; - if (!JS_FixProxy(cx, obj, &flag)) + if (!FixProxy(cx, obj, &flag)) return false; *vp = BOOLEAN_TO_JSVAL(flag); } else { @@ -995,22 +1013,7 @@ static JSFunctionSpec static_methods[] = { JS_FS_END }; -JS_FRIEND_API(JSObject *) -js_InitProxyClass(JSContext *cx, JSObject *obj) -{ - JSObject *module = NewObject(cx, &js_ObjectClass, NULL, obj); - if (!module) - return NULL; - if (!JS_DefineProperty(cx, obj, "Proxy", OBJECT_TO_JSVAL(module), - JS_PropertyStub, JS_PropertyStub, 0)) { - return NULL; - } - if (!JS_DefineFunctions(cx, module, static_methods)) - return NULL; - return obj; -} - -extern JSClass js_CallableObjectClass; +extern JSClass CallableObjectClass; static const uint32 JSSLOT_CALLABLE_CALL = JSSLOT_PRIVATE; static const uint32 JSSLOT_CALLABLE_CONSTRUCT = JSSLOT_PRIVATE + 1; @@ -1019,7 +1022,7 @@ static JSBool callable_Call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSObject *callable = JSVAL_TO_OBJECT(argv[-2]); - JS_ASSERT(callable->getClass() == &js_CallableObjectClass); + JS_ASSERT(callable->getClass() == &CallableObjectClass); jsval fval = callable->fslots[JSSLOT_CALLABLE_CALL]; return js_InternalCall(cx, obj, fval, argc, argv, rval); } @@ -1028,7 +1031,7 @@ static JSBool callable_Construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { JSObject *callable = JSVAL_TO_OBJECT(argv[-2]); - JS_ASSERT(callable->getClass() == &js_CallableObjectClass); + JS_ASSERT(callable->getClass() == &CallableObjectClass); jsval fval = callable->fslots[JSSLOT_CALLABLE_CONSTRUCT]; if (fval == JSVAL_VOID) { /* We don't have an explicit constructor so allocate a new object and use the call. */ @@ -1054,7 +1057,7 @@ callable_Construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval return js_InternalCall(cx, obj, fval, argc, argv, rval); } -JSClass js_CallableObjectClass = { +JSClass CallableObjectClass = { "CallableObject", JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_NEW_ENUMERATE, @@ -1064,8 +1067,8 @@ JSClass js_CallableObjectClass = { NULL, NULL, NULL, NULL }; -JS_PUBLIC_API(JSBool) -JS_FixProxy(JSContext *cx, JSObject *proxy, JSBool *bp) +JS_FRIEND_API(JSBool) +FixProxy(JSContext *cx, JSObject *proxy, JSBool *bp) { AutoValueRooter tvr(cx); if (!JSProxy::fix(cx, proxy, tvr.addr())) @@ -1081,7 +1084,7 @@ JS_FixProxy(JSContext *cx, JSObject *proxy, JSBool *bp) JSObject *proto = proxy->getProto(); JSObject *parent = proxy->getParent(); - JSClass *clasp = proxy->isFunctionProxy() ? &js_CallableObjectClass : &js_ObjectClass; + JSClass *clasp = proxy->isFunctionProxy() ? &CallableObjectClass : &js_ObjectClass; /* Make a blank object from the recipe fix provided to us. */ JSObject *newborn = NewObjectWithGivenProto(cx, clasp, proto, parent); @@ -1089,7 +1092,7 @@ JS_FixProxy(JSContext *cx, JSObject *proxy, JSBool *bp) return NULL; AutoValueRooter tvr2(cx, newborn); - if (clasp == &js_CallableObjectClass) { + if (clasp == &CallableObjectClass) { newborn->fslots[JSSLOT_CALLABLE_CALL] = proxy->fslots[JSSLOT_PROXY_CALL]; newborn->fslots[JSSLOT_CALLABLE_CONSTRUCT] = proxy->fslots[JSSLOT_PROXY_CONSTRUCT]; } @@ -1106,13 +1109,19 @@ JS_FixProxy(JSContext *cx, JSObject *proxy, JSBool *bp) return true; } -JS_PUBLIC_API(JSBool) -JS_Becomes(JSContext *cx, JSObject *obj, JSObject *obj2) -{ -#ifdef JS_THREADSAFE - JS_ASSERT_IF(obj->isNative(), obj->scope()->title.ownercx == cx); - JS_ASSERT_IF(obj2->isNative(), obj2->scope()->title.ownercx == cx); -#endif - obj->swap(obj2); - return true; +} + +JS_FRIEND_API(JSObject *) +js_InitProxyClass(JSContext *cx, JSObject *obj) +{ + JSObject *module = NewObject(cx, &js_ObjectClass, NULL, obj); + if (!module) + return NULL; + if (!JS_DefineProperty(cx, obj, "Proxy", OBJECT_TO_JSVAL(module), + JS_PropertyStub, JS_PropertyStub, 0)) { + return NULL; + } + if (!JS_DefineFunctions(cx, module, static_methods)) + return NULL; + return obj; } diff --git a/js/src/jsproxy.h b/js/src/jsproxy.h index 70a313752d5..629afb0ef58 100644 --- a/js/src/jsproxy.h +++ b/js/src/jsproxy.h @@ -45,6 +45,8 @@ #include "jsapi.h" #include "jsobj.h" +namespace js { + /* Base class for all C++ proxy handlers. */ class JSProxyHandler { public: @@ -68,6 +70,7 @@ class JSProxyHandler { /* Spidermonkey extensions. */ virtual void finalize(JSContext *cx, JSObject *proxy); + virtual void trace(JSTracer *trc, JSObject *proxy); virtual void *family() = 0; }; @@ -99,6 +102,7 @@ class JSNoopProxyHandler { /* Spidermonkey extensions. */ virtual JS_FRIEND_API(void) finalize(JSContext *cx, JSObject *proxy); + virtual JS_FRIEND_API(void) trace(JSTracer *trc, JSObject *proxy); virtual JS_FRIEND_API(void) *family(); static JSNoopProxyHandler singleton; @@ -143,20 +147,22 @@ const uint32 JSSLOT_PROXY_PRIVATE = JSSLOT_PRIVATE + 2; const uint32 JSSLOT_PROXY_CALL = JSSLOT_PRIVATE + 1; const uint32 JSSLOT_PROXY_CONSTRUCT = JSSLOT_PRIVATE + 2; -extern JS_FRIEND_API(JSClass) js_ObjectProxyClass; -extern JS_FRIEND_API(JSClass) js_FunctionProxyClass; -extern JSClass js_CallableObjectClass; +extern JS_FRIEND_API(JSClass) ObjectProxyClass; +extern JS_FRIEND_API(JSClass) FunctionProxyClass; +extern JSClass CallableObjectClass; + +} inline bool JSObject::isObjectProxy() const { - return getClass() == &js_ObjectProxyClass; + return getClass() == &js::ObjectProxyClass; } inline bool JSObject::isFunctionProxy() const { - return getClass() == &js_FunctionProxyClass; + return getClass() == &js::FunctionProxyClass; } inline bool @@ -169,7 +175,7 @@ inline jsval JSObject::getProxyHandler() const { JS_ASSERT(isProxy()); - jsval handler = fslots[JSSLOT_PROXY_HANDLER]; + jsval handler = fslots[js::JSSLOT_PROXY_HANDLER]; JS_ASSERT(JSVAL_IS_OBJECT(handler) || JSVAL_IS_INT(handler)); return handler; } @@ -178,30 +184,29 @@ inline jsval JSObject::getProxyPrivate() const { JS_ASSERT(isObjectProxy()); - return fslots[JSSLOT_PROXY_PRIVATE]; + return fslots[js::JSSLOT_PROXY_PRIVATE]; } inline void JSObject::setProxyPrivate(jsval priv) { JS_ASSERT(isObjectProxy()); - fslots[JSSLOT_PROXY_PRIVATE] = priv; + fslots[js::JSSLOT_PROXY_PRIVATE] = priv; } -JS_PUBLIC_API(JSObject *) -JS_NewObjectProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *parent, JSString *className); +namespace js { -JS_PUBLIC_API(JSObject *) -JS_NewFunctionProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *parent, JSObject *call, JSObject *construct); +JS_FRIEND_API(JSObject *) +NewObjectProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *parent, JSString *className); -JS_PUBLIC_API(JSBool) -JS_GetProxyObjectClass(JSContext *cx, JSObject *proxy, const char **namep); +JS_FRIEND_API(JSObject *) +NewFunctionProxy(JSContext *cx, jsval handler, JSObject *proto, JSObject *parent, JSObject *call, JSObject *construct); -JS_PUBLIC_API(JSBool) -JS_FixProxy(JSContext *cx, JSObject *proxy, JSBool *bp); +JS_FRIEND_API(JSBool) +GetProxyObjectClass(JSContext *cx, JSObject *proxy, const char **namep); -JS_PUBLIC_API(JSBool) -JS_Becomes(JSContext *cx, JSObject *obj, JSObject *obj2); +JS_FRIEND_API(JSBool) +FixProxy(JSContext *cx, JSObject *proxy, JSBool *bp); template JSObject * @@ -211,17 +216,19 @@ JSNoopProxyHandler::wrap(JSContext *cx, JSObject *obj, JSObject *proto, JSObject JSNoopProxyHandler *handler = new T(obj); if (!handler) return NULL; - JSObject *wrapper = JS_NewFunctionProxy(cx, PRIVATE_TO_JSVAL(handler), proto, parent, obj, NULL); + JSObject *wrapper = NewFunctionProxy(cx, PRIVATE_TO_JSVAL(handler), proto, parent, obj, NULL); if (!wrapper) delete handler; return wrapper; } - JSObject *wrapper = JS_NewObjectProxy(cx, PRIVATE_TO_JSVAL(&T::singleton), proto, parent, className); + JSObject *wrapper = NewObjectProxy(cx, PRIVATE_TO_JSVAL(&T::singleton), proto, parent, className); if (wrapper) wrapper->setProxyPrivate(OBJECT_TO_JSVAL(obj)); return wrapper; } +} + JS_BEGIN_EXTERN_C extern JS_FRIEND_API(JSObject *) diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index 7aac6fd87a8..c7d00755e7d 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -13946,6 +13946,8 @@ TraceRecorder::record_JSOP_BINDNAME() JSAtom *atom = atoms[GET_INDEX(cx->regs->pc)]; jsid id = ATOM_TO_JSID(atom); JSObject *obj2 = js_FindIdentifierBase(cx, fp->scopeChain, id); + if (!obj2) + RETURN_ERROR_A("error in js_FindIdentifierBase"); if (obj2 != globalObj && obj2->getClass() != &js_CallClass) RETURN_STOP_A("BINDNAME on non-global, non-call object"); @@ -14012,7 +14014,7 @@ TraceRecorder::record_JSOP_IN() if (!localtm.recorder) { if (prop) obj2->dropProperty(localcx, prop); - return ARECORD_STOP; + return ARECORD_ABORTED; } if (!ok)