Fix Bug 370400 - Rhino should support const keyword

Patch provided by Bob Jervis (bjervis@google.com)
This commit is contained in:
nboyd%atg.com 2007-04-04 20:52:13 +00:00
Родитель a4385f9f64
Коммит 3f6be5befa
18 изменённых файлов: 743 добавлений и 61 удалений

Просмотреть файл

@ -0,0 +1,116 @@
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* 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 Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
// API class
package org.mozilla.javascript;
/**
* Created by IntelliJ IDEA.
* User: bjervis
* Date: Feb 14, 2007
* Time: 11:04:59 AM
* To change this template use File | Settings | File Templates.
*/
public interface ConstProperties {
/**
* Sets a named const property in this object.
* <p>
* The property is specified by a string name
* as defined for <code>Scriptable.get</code>.
* <p>
* The possible values that may be passed in are as defined for
* <code>Scriptable.get</code>. A class that implements this method may choose
* to ignore calls to set certain properties, in which case those
* properties are effectively read-only.<p>
* For properties defined in a prototype chain,
* use <code>putProperty</code> in ScriptableObject. <p>
* Note that if a property <i>a</i> is defined in the prototype <i>p</i>
* of an object <i>o</i>, then evaluating <code>o.a = 23</code> will cause
* <code>set</code> to be called on the prototype <i>p</i> with
* <i>o</i> as the <i>start</i> parameter.
* To preserve JavaScript semantics, it is the Scriptable
* object's responsibility to modify <i>o</i>. <p>
* This design allows properties to be defined in prototypes and implemented
* in terms of getters and setters of Java values without consuming slots
* in each instance.<p>
* <p>
* The values that may be set are limited to the following:
* <UL>
* <LI>java.lang.Boolean objects</LI>
* <LI>java.lang.String objects</LI>
* <LI>java.lang.Number objects</LI>
* <LI>org.mozilla.javascript.Scriptable objects</LI>
* <LI>null</LI>
* <LI>The value returned by Context.getUndefinedValue()</LI>
* </UL><p>
* Arbitrary Java objects may be wrapped in a Scriptable by first calling
* <code>Context.toObject</code>. This allows the property of a JavaScript
* object to contain an arbitrary Java object as a value.<p>
* Note that <code>has</code> will be called by the runtime first before
* <code>set</code> is called to determine in which object the
* property is defined.
* Note that this method is not expected to traverse the prototype chain,
* which is different from the ECMA [[Put]] operation.
* @param name the name of the property
* @param start the object whose property is being set
* @param value value to set the property to
* @see org.mozilla.javascript.Scriptable#has
* @see org.mozilla.javascript.Scriptable#get
* @see org.mozilla.javascript.ScriptableObject#putProperty
* @see org.mozilla.javascript.Context#toObject
*/
public void putConst(String name, Scriptable start, Object value);
/**
* Reserves a definition spot for a const. This will set up a definition
* of the const property, but set its value to undefined. The semantics of
* the start parameter is the same as for putConst.
* @param name The name of the property.
* @param start The object whose property is being reserved.
*/
public void defineConst(String name, Scriptable start);
/**
* Returns true if the named property is defined as a const on this object.
* @param name
* @return true if the named property is defined as a const, false
* otherwise.
*/
public boolean isConst(String name);
}

Просмотреть файл

@ -744,6 +744,10 @@ public class Decompiler
result.append("void ");
break;
case Token.CONST:
result.append("const ");
break;
case Token.NOT:
result.append('!');
break;
@ -806,7 +810,8 @@ public class Decompiler
default:
// If we don't know how to decompile it, raise an exception.
throw new RuntimeException();
throw new RuntimeException("Token: " +
Token.name(source.charAt(i)));
}
++i;
}

Просмотреть файл

@ -25,6 +25,7 @@
* Norris Boyd
* Igor Bukanov
* Ethan Hugg
* Bob Jervis
* Terry Lucas
* Milen Nankov
*
@ -177,9 +178,9 @@ final class IRFactory
switchBlock.addChildToBack(switchBreakTarget);
}
Node createVariables(int lineno)
Node createVariables(int token, int lineno)
{
return new Node(Token.VAR, lineno);
return new Node(token, lineno);
}
Node createExprStatement(Node expr, int lineno)
@ -379,7 +380,8 @@ final class IRFactory
// See ECMA Ch. 13. We add code to the beginning of the
// function to initialize a local variable of the
// function's name to the function value.
fnNode.addVar(name);
if (!fnNode.addVar(name))
parser.addError("msg.const.redecl", name);
Node setFn = new Node(Token.EXPR_VOID,
new Node(Token.SETNAME,
Node.newString(Token.BINDNAME, name),

Просмотреть файл

@ -23,6 +23,7 @@
*
* Contributor(s):
* Igor Bukanov
* Bob Jervis
* Roger Lawrence
*
* Alternatively, the contents of this file may be used under the terms of
@ -147,8 +148,8 @@ final class InterpretedFunction extends NativeFunction implements Script
* Calls the function.
* @param cx the current context
* @param scope the scope used for the call
* @param the value of "this"
* @param function arguments. Must not be null. You can use
* @param thisObj the value of "this"
* @param args function arguments. Must not be null. You can use
* {@link ScriptRuntime#emptyArgs} to pass empty arguments.
* @return the result of the function call.
*/
@ -206,5 +207,9 @@ final class InterpretedFunction extends NativeFunction implements Script
return idata.argNames[index];
}
protected boolean getParamOrVarConst(int index)
{
return idata.argIsConst[index];
}
}

Просмотреть файл

@ -167,8 +167,13 @@ public class Interpreter
Icode_LITERAL_GETTER = -57,
Icode_LITERAL_SETTER = -58,
// const
Icode_SETCONST = -59,
Icode_SETCONSTVAR = -60,
Icode_SETCONSTVAR1 = -61,
// Last icode
MIN_ICODE = -58;
MIN_ICODE = -61;
// data for parsing
@ -231,6 +236,7 @@ public class Interpreter
// sDbl[i]: if stack[i] is UniqueTag.DOUBLE_MARK, sDbl[i] holds the number value
Object[] stack;
int[] stackAttributes;
double[] sDbl;
CallFrame varSource; // defaults to this unless continuation frame
int localShift;
@ -269,6 +275,7 @@ public class Interpreter
// from this frame to share variables.
copy.stack = (Object[])stack.clone();
copy.stackAttributes = (int[])stackAttributes.clone();
copy.sDbl = (double[])sDbl.clone();
copy.frozen = false;
@ -419,6 +426,9 @@ public class Interpreter
case Icode_LOCAL_CLEAR: return "LOCAL_CLEAR";
case Icode_LITERAL_GETTER: return "LITERAL_GETTER";
case Icode_LITERAL_SETTER: return "LITERAL_SETTER";
case Icode_SETCONST: return "SETCONST";
case Icode_SETCONSTVAR: return "SETCONSTVAR";
case Icode_SETCONSTVAR1: return "SETCONSTVAR1";
}
// icode without name
@ -568,6 +578,7 @@ public class Interpreter
+ itsData.itsMaxStack;
itsData.argNames = scriptOrFn.getParamAndVarNames();
itsData.argIsConst = scriptOrFn.getParamAndVarConst();
itsData.argCount = scriptOrFn.getParamCount();
itsData.encodedSourceStart = scriptOrFn.getEncodedSourceStart();
@ -657,9 +668,9 @@ public class Interpreter
}
}
// For function statements or function expression statements
// in scripts, we need to ensure that the result of the script
// in scripts, we need to ensure that the result of the script
// is the function if it is the last statement in the script.
// For example, eval("function () {}") should return a
// For example, eval("function () {}") should return a
// function, not undefined.
if (!itsInFunctionFlag) {
addIndexOp(Icode_CLOSURE_EXPR, fnIndex);
@ -1137,6 +1148,17 @@ public class Interpreter
}
break;
case Token.SETCONST:
{
String name = child.getString();
visitExpression(child, 0);
child = child.getNext();
visitExpression(child, 0);
addStringOp(Icode_SETCONST, name);
stackChange(-1);
}
break;
case Token.TYPEOFNAME:
{
String name = node.getString();
@ -1218,6 +1240,17 @@ public class Interpreter
}
break;
case Token.SETCONSTVAR:
{
if (itsData.itsNeedsActivation) Kit.codeBug();
String name = child.getString();
child = child.getNext();
visitExpression(child, 0);
int index = scriptOrFn.getParamOrVarIndex(name);
addVarOp(Token.SETCONSTVAR, index);
}
break;
case Token.NULL:
case Token.THIS:
case Token.THISFN:
@ -1635,6 +1668,14 @@ public class Interpreter
private void addVarOp(int op, int varIndex)
{
switch (op) {
case Token.SETCONSTVAR:
if (varIndex < 128) {
addIcode(Icode_SETCONSTVAR1);
addUint8(varIndex);
return;
}
addIndexOp(Icode_SETCONSTVAR, varIndex);
return;
case Token.GETVAR:
case Token.SETVAR:
if (varIndex < 128) {
@ -1985,6 +2026,7 @@ public class Interpreter
}
case Icode_GETVAR1:
case Icode_SETVAR1:
case Icode_SETCONSTVAR1:
indexReg = iCode[pc];
out.println(tname+" "+indexReg);
++pc;
@ -2084,6 +2126,7 @@ public class Interpreter
case Icode_GETVAR1:
case Icode_SETVAR1:
case Icode_SETCONSTVAR1:
// byte var index
return 1 + 1;
@ -2459,6 +2502,7 @@ public class Interpreter
double[] sDbl = frame.sDbl;
Object[] vars = frame.varSource.stack;
double[] varDbls = frame.varSource.sDbl;
int[] varAttributes = frame.varSource.stackAttributes;
byte[] iCode = frame.idata.itsICode;
String[] strings = frame.idata.itsStringTable;
@ -2830,6 +2874,14 @@ switch (op) {
frame.scope, stringReg);
continue Loop;
}
case Icode_SETCONST: {
Object rhs = stack[stackTop];
if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
--stackTop;
Scriptable lhs = (Scriptable)stack[stackTop];
stack[stackTop] = ScriptRuntime.setConst(lhs, rhs, cx, stringReg);
continue Loop;
}
case Token.DELPROP : {
Object rhs = stack[stackTop];
if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
@ -3198,13 +3250,42 @@ switch (op) {
cx, iCode[frame.pc]);
++frame.pc;
continue Loop;
case Icode_SETCONSTVAR1:
indexReg = iCode[frame.pc++];
// fallthrough
case Token.SETCONSTVAR :
if (!frame.useActivation) {
if ((varAttributes[indexReg] & ScriptableObject.READONLY) == 0) {
throw Context.reportRuntimeError1("msg.var.redecl",
frame.idata.argNames[indexReg]);
}
if ((varAttributes[indexReg] & ScriptableObject.UNINITIALIZED_CONST)
!= 0)
{
vars[indexReg] = stack[stackTop];
varAttributes[indexReg] &= ~ScriptableObject.UNINITIALIZED_CONST;
varDbls[indexReg] = sDbl[stackTop];
}
} else {
Object val = stack[stackTop];
if (val == DBL_MRK) val = ScriptRuntime.wrapNumber(sDbl[stackTop]);
stringReg = frame.idata.argNames[indexReg];
if (frame.scope instanceof ConstProperties) {
ConstProperties cp = (ConstProperties)frame.scope;
cp.putConst(stringReg, frame.scope, val);
} else
throw Kit.codeBug();
}
continue Loop;
case Icode_SETVAR1:
indexReg = iCode[frame.pc++];
// fallthrough
case Token.SETVAR :
if (!frame.useActivation) {
vars[indexReg] = stack[stackTop];
varDbls[indexReg] = sDbl[stackTop];
if ((varAttributes[indexReg] & ScriptableObject.READONLY) == 0) {
vars[indexReg] = stack[stackTop];
varDbls[indexReg] = sDbl[stackTop];
}
} else {
Object val = stack[stackTop];
if (val == DBL_MRK) val = ScriptRuntime.wrapNumber(sDbl[stackTop]);
@ -3822,19 +3903,27 @@ switch (op) {
Kit.codeBug();
Object[] stack;
int[] stackAttributes;
double[] sDbl;
boolean stackReuse;
if (frame.stack != null && maxFrameArray <= frame.stack.length) {
// Reuse stacks from old frame
stackReuse = true;
stack = frame.stack;
stackAttributes = frame.stackAttributes;
sDbl = frame.sDbl;
} else {
stackReuse = false;
stack = new Object[maxFrameArray];
stackAttributes = new int[maxFrameArray];
sDbl = new double[maxFrameArray];
}
int varCount = idata.getParamAndVarCount();
for (int i = 0; i < varCount; i++) {
if (idata.getParamOrVarConst(i))
stackAttributes[i] = ScriptableObject.CONST;
}
int definedArgs = idata.argCount;
if (definedArgs > argCount) { definedArgs = argCount; }
@ -3853,6 +3942,7 @@ switch (op) {
frame.idata = idata;
frame.stack = stack;
frame.stackAttributes = stackAttributes;
frame.sDbl = sDbl;
frame.varSource = frame;
frame.localShift = idata.itsMaxVars;
@ -4015,6 +4105,7 @@ switch (op) {
for (int i = x.savedStackTop + 1; i != x.stack.length; ++i) {
// Allow to GC unused stack space
x.stack[i] = null;
x.stackAttributes[i] = ScriptableObject.EMPTY;
}
if (x.savedCallOp == Token.CALL) {
// the call will always overwrite the stack top with the result

Просмотреть файл

@ -23,6 +23,7 @@
*
* Contributor(s):
* Norris Boyd
* Bob Jervis
* Roger Lawrence
*
* Alternatively, the contents of this file may be used under the terms of
@ -98,6 +99,7 @@ final class InterpreterData implements Serializable, DebuggableScript
// see comments in NativeFuncion for definition of argNames and argCount
String[] argNames;
boolean[] argIsConst;
int argCount;
int itsMaxCalleeArgs;
@ -152,6 +154,11 @@ final class InterpreterData implements Serializable, DebuggableScript
return argNames[index];
}
public boolean getParamOrVarConst(int index)
{
return argIsConst[index];
}
public String getSourceName()
{
return itsSourceFile;

Просмотреть файл

@ -23,6 +23,7 @@
*
* Contributor(s):
* Norris Boyd
* Bob Jervis
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
@ -73,7 +74,7 @@ public final class NativeCall extends IdScriptableObject
int paramAndVarCount = function.getParamAndVarCount();
int paramCount = function.getParamCount();
if (paramAndVarCount != 0) {
for (int i = 0; i != paramCount; ++i) {
for (int i = 0; i < paramCount; ++i) {
String name = function.getParamOrVarName(i);
Object val = i < args.length ? args[i]
: Undefined.instance;
@ -88,10 +89,13 @@ public final class NativeCall extends IdScriptableObject
}
if (paramAndVarCount != 0) {
for (int i = paramCount; i != paramAndVarCount; ++i) {
for (int i = paramCount; i < paramAndVarCount; ++i) {
String name = function.getParamOrVarName(i);
if (!super.has(name, this)) {
defineProperty(name, Undefined.instance, PERMANENT);
if (function.getParamOrVarConst(i))
defineProperty(name, Undefined.instance, CONST);
else
defineProperty(name, Undefined.instance, PERMANENT);
}
}
}

Просмотреть файл

@ -24,6 +24,7 @@
* Contributor(s):
* Norris Boyd
* Igor Bukanov
* Bob Jervis
* Roger Lawrence
* Mike McCabe
*
@ -134,5 +135,13 @@ public abstract class NativeFunction extends BaseFunction
* corresponding parameter. Otherwise returm the name of variable.
*/
protected abstract String getParamOrVarName(int index);
/**
* Get parameter or variable const-ness.
* If <tt>index < {@link #getParamCount()}</tt>, then return the const-ness
* of the corresponding parameter. Otherwise returm whether the variable is
* const.
*/
protected abstract boolean getParamOrVarConst(int index);
}

Просмотреть файл

@ -24,6 +24,7 @@
* Contributor(s):
* Norris Boyd
* Igor Bukanov
* Bob Jervis
* Roger Lawrence
* Mike McCabe
*
@ -236,6 +237,7 @@ public class NodeTransformer
visitNew(node, tree);
break;
case Token.CONST:
case Token.VAR:
{
Node result = new Node(Token.BLOCK);
@ -250,7 +252,10 @@ public class NodeTransformer
Node init = n.getFirstChild();
n.removeChild(init);
n.setType(Token.BINDNAME);
n = new Node(Token.SETNAME, n, init);
n = new Node(type == Token.VAR ?
Token.SETNAME :
Token.SETCONST,
n, init);
Node pop = new Node(Token.EXPR_VOID, n, node.getLineno());
result.addChildToBack(pop);
}
@ -260,6 +265,7 @@ public class NodeTransformer
case Token.NAME:
case Token.SETNAME:
case Token.SETCONST:
case Token.DELPROP:
{
// Turn name to var for faster access if possible
@ -287,6 +293,9 @@ public class NodeTransformer
} else if (type == Token.SETNAME) {
node.setType(Token.SETVAR);
nameSource.setType(Token.STRING);
} else if (type == Token.SETCONST) {
node.setType(Token.SETCONSTVAR);
nameSource.setType(Token.STRING);
} else if (type == Token.DELPROP) {
// Local variables are by definition permanent
Node n = new Node(Token.FALSE);

Просмотреть файл

@ -128,6 +128,14 @@ public class Parser
ts.getLine(), ts.getOffset());
}
void addError(String messageId, String messageArg)
{
++syntaxErrorCount;
String message = ScriptRuntime.getMessage1(messageId, messageArg);
errorReporter.error(message, sourceURI, ts.getLineno(),
ts.getLine(), ts.getOffset());
}
RuntimeException reportError(String messageId)
{
addError(messageId);
@ -815,7 +823,7 @@ public class Parser
if (tt == Token.VAR) {
// set init to a var list or initial
consumeToken(); // consume the 'var' token
init = variables(true);
init = variables(Token.FOR);
}
else {
init = expr(true);
@ -1017,9 +1025,10 @@ public class Parser
return pn;
}
case Token.CONST:
case Token.VAR: {
consumeToken();
pn = variables(false);
pn = variables(tt);
break;
}
@ -1181,13 +1190,28 @@ public class Parser
return pn;
}
private Node variables(boolean inForInit)
/**
* Parse a 'var' or 'const' statement, or a 'var' init list in a for
* statement.
* @param context A token value: either VAR, CONST or FOR depending on
* context.
* @return The parsed statement
* @throws IOException
* @throws ParserException
*/
private Node variables(int context)
throws IOException, ParserException
{
Node pn = nf.createVariables(ts.getLineno());
Node pn;
boolean first = true;
decompiler.addToken(Token.VAR);
if (context == Token.CONST){
pn = nf.createVariables(Token.CONST, ts.getLineno());
decompiler.addToken(Token.CONST);
} else {
pn = nf.createVariables(Token.VAR, ts.getLineno());
decompiler.addToken(Token.VAR);
}
for (;;) {
Node name;
@ -1200,7 +1224,19 @@ public class Parser
first = false;
decompiler.addName(s);
currentScriptOrFn.addVar(s);
if (context == Token.CONST) {
if (!currentScriptOrFn.addConst(s)) {
// We know it's already defined, since addVar passes if
// it's a var.
if (currentScriptOrFn.addVar(s))
addError("msg.var.redecl", s);
else
addError("msg.const.redecl", s);
}
} else {
if (!currentScriptOrFn.addVar(s))
addError("msg.const.redecl", s);
}
name = nf.createName(s);
// omitted check for argument hiding
@ -1208,7 +1244,7 @@ public class Parser
if (matchToken(Token.ASSIGN)) {
decompiler.addToken(Token.ASSIGN);
init = assignExpr(inForInit);
init = assignExpr(context == Token.FOR);
nf.addChildToBack(name, init);
}
nf.addChildToBack(pn, name);

Просмотреть файл

@ -23,6 +23,7 @@
*
* Contributor(s):
* Igor Bukanov
* Bob Jervis
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
@ -142,24 +143,53 @@ public class ScriptOrFnNode extends Node {
return array;
}
public final boolean[] getParamAndVarConst() {
int N = itsVariables.size();
boolean[] array = new boolean[N];
for (int i = 0; i < N; i++)
if (itsConst.get(i) != null)
array[i] = true;
return array;
}
public final void addParam(String name) {
// Check addparam is not called after addLocal
if (varStart != itsVariables.size()) Kit.codeBug();
// Allow non-unique parameter names: use the last occurrence
int index = varStart++;
itsVariables.add(name);
itsConst.add(null);
itsVariableNames.put(name, index);
}
public final void addVar(String name) {
public final boolean addVar(String name) {
int vIndex = itsVariableNames.get(name, -1);
if (vIndex != -1) {
// There's already a variable or parameter with this name.
return;
Object v = itsConst.get(vIndex);
if (v == null)
return true;
else
return false;
}
int index = itsVariables.size();
itsVariables.add(name);
itsConst.add(null);
itsVariableNames.put(name, index);
return true;
}
public final boolean addConst(String name) {
int vIndex = itsVariableNames.get(name, -1);
if (vIndex != -1) {
// There's already a variable or parameter with this name.
return false;
}
int index = itsVariables.size();
itsVariables.add(name);
itsConst.add(name);
itsVariableNames.put(name, index);
return true;
}
public final void removeParamOrVar(String name) {
@ -202,6 +232,7 @@ public class ScriptOrFnNode extends Node {
// a list of the formal parameters and local variables
private ObjArray itsVariables = new ObjArray();
private ObjArray itsConst = new ObjArray();
// mapping from name to index in list
private ObjToIntMap itsVariableNames = new ObjToIntMap(11);

Просмотреть файл

@ -1789,6 +1789,18 @@ public class ScriptRuntime {
return value;
}
public static Object setConst(Scriptable bound, Object value,
Context cx, String id)
{
if (bound instanceof XMLObject) {
XMLObject xmlObject = (XMLObject)bound;
xmlObject.ecmaPut(cx, id, value);
} else {
ScriptableObject.putConstProperty(bound, id, value);
}
return value;
}
/**
* This is the enumeration needed by the for..in statement.
*
@ -2862,17 +2874,23 @@ public class ScriptRuntime {
for (int i = varCount; i-- != 0;) {
String name = funObj.getParamOrVarName(i);
boolean isConst = funObj.getParamOrVarConst(i);
// Don't overwrite existing def if already defined in object
// or prototypes of object.
if (!ScriptableObject.hasProperty(scope, name)) {
if (!evalScript) {
// Global var definitions are supposed to be DONTDELETE
ScriptableObject.defineProperty(
varScope, name, Undefined.instance,
ScriptableObject.PERMANENT);
if (isConst)
ScriptableObject.defineConstProperty(varScope, name);
else
ScriptableObject.defineProperty(
varScope, name, Undefined.instance,
ScriptableObject.PERMANENT);
} else {
varScope.put(name, varScope, Undefined.instance);
}
} else {
ScriptableObject.redefineProperty(scope, name, isConst);
}
}
}

Просмотреть файл

@ -64,7 +64,8 @@ import org.mozilla.javascript.debug.DebuggableObject;
*/
public abstract class ScriptableObject implements Scriptable, Serializable,
DebuggableObject
DebuggableObject,
ConstProperties
{
/**
@ -106,6 +107,14 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
*/
public static final int PERMANENT = 0x04;
/**
* Property attribute indicating that this is a const property that has not
* been assigned yet. The first 'const' assignment to the property will
* clear this bit.
*/
public static final int UNINITIALIZED_CONST = 0x08;
public static final int CONST = PERMANENT|READONLY|UNINITIALIZED_CONST;
/**
* The prototype of this object.
*/
@ -138,6 +147,7 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
private static final int SLOT_MODIFY = 2;
private static final int SLOT_REMOVE = 3;
private static final int SLOT_MODIFY_GETTER_SETTER = 4;
private static final int SLOT_MODIFY_CONST = 5;
private static class Slot implements Serializable
{
@ -202,7 +212,7 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
static void checkValidAttributes(int attributes)
{
final int mask = READONLY | DONTENUM | PERMANENT;
final int mask = READONLY | DONTENUM | PERMANENT | UNINITIALIZED_CONST;
if ((attributes & ~mask) != 0) {
throw new IllegalArgumentException(String.valueOf(attributes));
}
@ -298,7 +308,7 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
*/
public void put(String name, Scriptable start, Object value)
{
if (putImpl(name, 0, start, value))
if (putImpl(name, 0, start, value, EMPTY))
return;
if (start == this) throw Kit.codeBug();
@ -314,7 +324,7 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
*/
public void put(int index, Scriptable start, Object value)
{
if (putImpl(null, index, start, value))
if (putImpl(null, index, start, value, EMPTY))
return;
if (start == this) throw Kit.codeBug();
@ -349,6 +359,58 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
accessSlot(null, index, SLOT_REMOVE);
}
/**
* Sets the value of the named const property, creating it if need be.
*
* If the property was created using defineProperty, the
* appropriate setter method is called. <p>
*
* If the property's attributes include READONLY, no action is
* taken.
* This method will actually set the property in the start
* object.
*
* @param name the name of the property
* @param start the object whose property is being set
* @param value value to set the property to
*/
public void putConst(String name, Scriptable start, Object value)
{
if (putImpl(name, 0, start, value, READONLY))
return;
if (start == this) throw Kit.codeBug();
if (start instanceof ConstProperties)
((ConstProperties)start).putConst(name, start, value);
else
start.put(name, start, value);
}
public void defineConst(String name, Scriptable start)
{
if (putImpl(name, 0, start, Undefined.instance, UNINITIALIZED_CONST))
return;
if (start == this) throw Kit.codeBug();
if (start instanceof ConstProperties)
((ConstProperties)start).defineConst(name, start);
}
/**
* Returns true if the named property is defined as a const on this object.
* @param name
* @return true if the named property is defined as a const, false
* otherwise.
*/
public boolean isConst(String name)
{
Slot slot = getSlot(name, 0, SLOT_QUERY);
if (slot == null) {
return false;
}
return (slot.getAttributes() & (PERMANENT|READONLY)) ==
(PERMANENT|READONLY);
}
/**
* @deprecated Use {@link #getAttributes(String name)}. The engine always
* ignored the start argument.
@ -1136,6 +1198,22 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
so.defineProperty(propertyName, value, attributes);
}
/**
* Utility method to add properties to arbitrary Scriptable object.
* If destination is instance of ScriptableObject, calls
* defineProperty there, otherwise calls put in destination
* ignoring attributes
*/
public static void defineConstProperty(Scriptable destination,
String propertyName)
{
if (destination instanceof ConstProperties) {
ConstProperties cp = (ConstProperties)destination;
cp.defineConst(propertyName, destination);
} else
defineProperty(destination, propertyName, Undefined.instance, CONST);
}
/**
* Define a JavaScript property with getter and setter side effects.
*
@ -1516,6 +1594,30 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
return null != getBase(obj, name);
}
/**
* If hasProperty(obj, name) would return true, then if the property that
* was found is compatible with the new property, this method just returns.
* If the property is not compatible, then an exception is thrown.
*
* A property redefinition is incompatible if the first definition was a
* const declaration or if this one is. They are compatible only if neither
* was const.
*/
public static void redefineProperty(Scriptable obj, String name,
boolean isConst)
{
Scriptable base = getBase(obj, name);
if (base == null)
return;
if (base instanceof ConstProperties) {
ConstProperties cp = (ConstProperties)base;
if (cp.isConst(name))
throw Context.reportRuntimeError1("msg.const.redecl", name);
}
if (isConst)
throw Context.reportRuntimeError1("msg.var.redecl", name);
}
/**
* Returns whether an indexed property is defined in an object or any object
* in its prototype chain.
@ -1537,11 +1639,11 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
* <p>
* Searches for the named property in the prototype chain. If it is found,
* the value of the property in <code>obj</code> is changed through a call
* to {@link Scriptable#put(String, Scriptable, Object)} on the prototype
* passing <code>obj</code> as the <code>start</code> argument. This allows
* the prototype to veto the property setting in case the prototype defines
* the property with [[ReadOnly]] attribute. If the property is not found,
* it is added in <code>obj</code>.
* to {@link Scriptable#put(String, Scriptable, Object)} on the
* prototype passing <code>obj</code> as the <code>start</code> argument.
* This allows the prototype to veto the property setting in case the
* prototype defines the property with [[ReadOnly]] attribute. If the
* property is not found, it is added in <code>obj</code>.
* @param obj a JavaScript object
* @param name a property name
* @param value any JavaScript value accepted by Scriptable.put
@ -1555,6 +1657,30 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
base.put(name, obj, value);
}
/**
* Puts a named property in an object or in an object in its prototype chain.
* <p>
* Searches for the named property in the prototype chain. If it is found,
* the value of the property in <code>obj</code> is changed through a call
* to {@link Scriptable#put(String, Scriptable, Object)} on the
* prototype passing <code>obj</code> as the <code>start</code> argument.
* This allows the prototype to veto the property setting in case the
* prototype defines the property with [[ReadOnly]] attribute. If the
* property is not found, it is added in <code>obj</code>.
* @param obj a JavaScript object
* @param name a property name
* @param value any JavaScript value accepted by Scriptable.put
* @since 1.5R2
*/
public static void putConstProperty(Scriptable obj, String name, Object value)
{
Scriptable base = getBase(obj, name);
if (base == null)
base = obj;
if (base instanceof ConstProperties)
((ConstProperties)base).putConst(name, obj, value);
}
/**
* Puts an indexed property in an object or in an object in its prototype chain.
* <p>
@ -1842,8 +1968,19 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
return value;
}
/**
*
* @param name
* @param index
* @param start
* @param value
* @param constFlag EMPTY means normal put. UNINITIALIZED_CONST means
* defineConstProperty. READONLY means const initialization expression.
* @return false if this != start and no slot was found. true if this == start
* or this != start and a READONLY slot was found.
*/
private boolean putImpl(String name, int index, Scriptable start,
Object value)
Object value, int constFlag)
{
Slot slot;
if (this != start) {
@ -1853,6 +1990,20 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
}
} else {
checkNotSealed(name, index);
// either const hoisted declaration or initialization
if (constFlag != EMPTY) {
slot = getSlot(name, index, SLOT_MODIFY_CONST);
int attr = slot.getAttributes();
if ((attr & READONLY) == 0)
throw Context.reportRuntimeError1("msg.var.redecl", name);
if ((attr & UNINITIALIZED_CONST) != 0) {
slot.value = value;
// clear the bit on const initialization
if (constFlag != UNINITIALIZED_CONST)
slot.setAttributes(attr & ~UNINITIALIZED_CONST);
}
return true;
}
slot = getSlot(name, index, SLOT_MODIFY);
}
if ((slot.getAttributes() & READONLY) != 0)
@ -1952,7 +2103,9 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
{
int indexOrHash = (name != null ? name.hashCode() : index);
if (accessType == SLOT_QUERY || accessType == SLOT_MODIFY ||
if (accessType == SLOT_QUERY ||
accessType == SLOT_MODIFY ||
accessType == SLOT_MODIFY_CONST ||
accessType == SLOT_MODIFY_GETTER_SETTER)
{
// Check the hashtable without using synchronization
@ -2003,6 +2156,9 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
} else if (accessType == SLOT_MODIFY_GETTER_SETTER) {
if (slot instanceof GetterSlot)
return slot;
} else if (accessType == SLOT_MODIFY_CONST) {
if (slot != null)
return slot;
} else {
Kit.codeBug();
}
@ -2051,7 +2207,7 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
if (slot != null) {
// Another thread just added a slot with same
// name/index before this one enetered synchronized
// name/index before this one entered synchronized
// block. This is a race in application code and
// probably indicates bug there. But for the hashtable
// implementation it is harmless with the only
@ -2070,6 +2226,8 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
lastAccess = REMOVED;
}
slot = newSlot;
} else if (accessType == SLOT_MODIFY_CONST) {
return null;
}
return slot;
}
@ -2087,6 +2245,8 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
Slot newSlot = (accessType == SLOT_MODIFY_GETTER_SETTER
? new GetterSlot(name, indexOrHash, 0)
: new Slot(name, indexOrHash, 0));
if (accessType == SLOT_MODIFY_CONST)
newSlot.setAttributes(CONST);
++count;
slotsLocalRef[insertPos] = newSlot;
return newSlot;

Просмотреть файл

@ -247,7 +247,10 @@ public class Token
GET = 147, // JS 1.5 get pseudo keyword
SET = 148, // JS 1.5 set pseudo keyword
LAST_TOKEN = 148;
CONST = 149,
SETCONST = 150,
SETCONSTVAR = 151,
LAST_TOKEN = 152;
public static String name(int token)
{
@ -404,6 +407,8 @@ public class Token
case TO_DOUBLE: return "TO_DOUBLE";
case GET: return "GET";
case SET: return "SET";
case CONST: return "CONST";
case SETCONST: return "SETCONST";
}
// Token without name

Просмотреть файл

@ -26,6 +26,7 @@
* Mike McCabe
* Igor Bukanov
* Ethan Hugg
* Bob Jervis
* Terry Lucas
* Milen Nankov
*
@ -151,7 +152,7 @@ class TokenStream
Id_catch = Token.CATCH,
Id_char = Token.RESERVED,
Id_class = Token.RESERVED,
Id_const = Token.RESERVED,
Id_const = Token.CONST,
Id_debugger = Token.RESERVED,
Id_double = Token.RESERVED,
Id_enum = Token.RESERVED,

Просмотреть файл

@ -669,7 +669,8 @@ public class Codegen extends Interpreter
final int Do_getParamAndVarCount = 2;
final int Do_getParamOrVarName = 3;
final int Do_getEncodedSource = 4;
final int SWITCH_COUNT = 5;
final int Do_getParamOrVarConst = 5;
final int SWITCH_COUNT = 6;
for (int methodIndex = 0; methodIndex != SWITCH_COUNT; ++methodIndex) {
if (methodIndex == Do_getEncodedSource && encodedSource == null) {
@ -703,6 +704,11 @@ public class Codegen extends Interpreter
cfw.startMethod("getParamOrVarName", "(I)Ljava/lang/String;",
ClassFileWriter.ACC_PUBLIC);
break;
case Do_getParamOrVarConst:
metodLocals = 1 + 1 + 1; // this + paramOrVarName
cfw.startMethod("getParamOrVarConst", "(I)Z",
ClassFileWriter.ACC_PUBLIC);
break;
case Do_getEncodedSource:
metodLocals = 1; // Only this
cfw.startMethod("getEncodedSource", "()Ljava/lang/String;",
@ -802,6 +808,43 @@ public class Codegen extends Interpreter
}
break;
case Do_getParamOrVarConst:
// Push name of parameter using another switch
// over paramAndVarCount
paramAndVarCount = n.getParamAndVarCount();
boolean [] constness = n.getParamAndVarConst();
if (paramAndVarCount == 0) {
// The runtime should never call the method in this
// case but to make bytecode verifier happy return null
// as throwing execption takes more code
cfw.add(ByteCode.ICONST_0);
cfw.add(ByteCode.IRETURN);
} else if (paramAndVarCount == 1) {
// As above do not check for valid index but always
// return the name of the first param
cfw.addPush(constness[0]);
cfw.add(ByteCode.IRETURN);
} else {
// Do switch over getParamOrVarName
cfw.addILoad(1); // param or var index
// do switch from 1 .. paramAndVarCount - 1 mapping 0
// to the default case
int paramSwitchStart = cfw.addTableSwitch(
1, paramAndVarCount - 1);
for (int j = 0; j != paramAndVarCount; ++j) {
if (cfw.getStackTop() != 0) Kit.codeBug();
if (j == 0) {
cfw.markTableSwitchDefault(paramSwitchStart);
} else {
cfw.markTableSwitchCase(paramSwitchStart, j - 1,
0);
}
cfw.addPush(constness[j]);
cfw.add(ByteCode.IRETURN);
}
}
break;
case Do_getEncodedSource:
// Push number encoded source start and end
// to prepare for encodedSource.substring(start, end)
@ -1310,6 +1353,7 @@ class BodyCodegen
int paramCount = fnCurrent.fnode.getParamCount();
int varCount = fnCurrent.fnode.getParamAndVarCount();
boolean [] constDeclarations = fnCurrent.fnode.getParamAndVarConst();
// REMIND - only need to initialize the vars that don't get a value
// before the next call and are used in the function
@ -1325,11 +1369,11 @@ class BodyCodegen
cfw.addAStore(reg);
}
} else if (fnCurrent.isNumberVar(i)) {
reg = getNewWordPairLocal();
reg = getNewWordPairLocal(constDeclarations[i]);
cfw.addPush(0.0);
cfw.addDStore(reg);
} else {
reg = getNewWordLocal();
reg = getNewWordLocal(constDeclarations[i]);
if (firstUndefVar == -1) {
Codegen.pushUndefined(cfw);
firstUndefVar = reg;
@ -1339,6 +1383,10 @@ class BodyCodegen
cfw.addAStore(reg);
}
if (reg >= 0) {
if (constDeclarations[i]) {
cfw.addPush(0);
cfw.addIStore(reg + (fnCurrent.isNumberVar(i) ? 2 : 1));
}
varRegisters[i] = reg;
}
@ -1654,6 +1702,11 @@ class BodyCodegen
load's & pop's */
visitSetVar(child, child.getFirstChild(), false);
}
else if (child.getType() == Token.SETCONSTVAR) {
/* special case this so as to avoid unnecessary
load's & pop's */
visitSetConstVar(child, child.getFirstChild(), false);
}
else {
generateExpression(child, node);
if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1)
@ -2111,6 +2164,14 @@ class BodyCodegen
visitSetName(node, child);
break;
case Token.SETCONST:
visitSetConst(node, child);
break;
case Token.SETCONSTVAR:
visitSetConstVar(node, child, true);
break;
case Token.SETPROP:
case Token.SETPROP_OP:
visitSetProp(type, node, child);
@ -3647,6 +3708,24 @@ Else pass the JS object in the aReg and 0.0 in the dReg.
+")Ljava/lang/Object;");
}
private void visitSetConst(Node node, Node child)
{
String name = node.getFirstChild().getString();
while (child != null) {
generateExpression(child, node);
child = child.getNext();
}
cfw.addALoad(contextLocal);
cfw.addPush(name);
addScriptRuntimeInvoke(
"setConst",
"(Lorg/mozilla/javascript/Scriptable;"
+"Ljava/lang/Object;"
+"Lorg/mozilla/javascript/Context;"
+"Ljava/lang/String;"
+")Ljava/lang/Object;");
}
private void visitGetVar(Node node)
{
if (!hasVarsInRegs) Kit.codeBug();
@ -3676,7 +3755,16 @@ Else pass the JS object in the aReg and 0.0 in the dReg.
generateExpression(child.getNext(), node);
boolean isNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1);
short reg = varRegisters[varIndex];
if (varIsDirectCallParameter(varIndex)) {
boolean [] constDeclarations = fnCurrent.fnode.getParamAndVarConst();
if (constDeclarations[varIndex]) {
if (!needValue) {
if (isNumber)
cfw.add(ByteCode.POP2);
else
cfw.add(ByteCode.POP);
}
}
else if (varIsDirectCallParameter(varIndex)) {
if (isNumber) {
if (needValue) cfw.add(ByteCode.DUP2);
cfw.addALoad(reg);
@ -3701,8 +3789,8 @@ Else pass the JS object in the aReg and 0.0 in the dReg.
}
} else {
if (isNumber) {
cfw.addDStore(reg);
if (needValue) cfw.addDLoad(reg);
cfw.addDStore(reg);
if (needValue) cfw.addDLoad(reg);
}
else {
cfw.addAStore(reg);
@ -3711,6 +3799,50 @@ Else pass the JS object in the aReg and 0.0 in the dReg.
}
}
private void visitSetConstVar(Node node, Node child, boolean needValue)
{
if (!hasVarsInRegs) Kit.codeBug();
int varIndex = fnCurrent.getVarIndex(node);
generateExpression(child.getNext(), node);
boolean isNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1);
short reg = varRegisters[varIndex];
int beyond = cfw.acquireLabel();
int noAssign = cfw.acquireLabel();
if (isNumber) {
cfw.addILoad(reg + 2);
cfw.add(ByteCode.IFNE, noAssign);
short stack = cfw.getStackTop();
cfw.addPush(1);
cfw.addIStore(reg + 2);
cfw.addDStore(reg);
if (needValue) {
cfw.addDLoad(reg);
cfw.markLabel(noAssign, stack);
} else {
cfw.add(ByteCode.GOTO, beyond);
cfw.markLabel(noAssign, stack);
cfw.add(ByteCode.POP2);
}
}
else {
cfw.addILoad(reg + 1);
cfw.add(ByteCode.IFNE, noAssign);
short stack = cfw.getStackTop();
cfw.addPush(1);
cfw.addIStore(reg + 1);
cfw.addAStore(reg);
if (needValue) {
cfw.addALoad(reg);
cfw.markLabel(noAssign, stack);
} else {
cfw.add(ByteCode.GOTO, beyond);
cfw.markLabel(noAssign, stack);
cfw.add(ByteCode.POP);
}
}
cfw.markLabel(beyond);
}
private void visitGetProp(Node node, Node child)
{
generateExpression(child, node); //object
@ -3987,20 +4119,45 @@ Else pass the JS object in the aReg and 0.0 in the dReg.
addOptRuntimeInvoke("wrapDouble", "(D)Ljava/lang/Double;");
}
private short getNewWordPairLocal()
/**
* Const locals use an extra slot to hold the has-been-assigned-once flag at
* runtime.
* @param isConst
* @return
*/
private short getNewWordPairLocal(boolean isConst)
{
short result = firstFreeLocal;
while (true) {
if (result >= (MAX_LOCALS - 1))
break;
if (!locals[result]
&& !locals[result + 1])
break;
result++;
}
short result = getConsecutiveSlots(2, isConst);
if (result < (MAX_LOCALS - 1)) {
locals[result] = true;
locals[result + 1] = true;
if (isConst)
locals[result + 2] = true;
if (result == firstFreeLocal) {
for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) {
if (!locals[i]) {
firstFreeLocal = (short) i;
if (localsMax < firstFreeLocal)
localsMax = firstFreeLocal;
return result;
}
}
}
else {
return result;
}
}
throw Context.reportRuntimeError("Program too complex " +
"(out of locals)");
}
private short getNewWordLocal(boolean isConst)
{
short result = getConsecutiveSlots(1, isConst);
if (result < (MAX_LOCALS - 1)) {
locals[result] = true;
if (isConst)
locals[result + 1] = true;
if (result == firstFreeLocal) {
for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) {
if (!locals[i]) {
@ -4035,6 +4192,24 @@ Else pass the JS object in the aReg and 0.0 in the dReg.
"(out of locals)");
}
private short getConsecutiveSlots(int count, boolean isConst) {
if (isConst)
count++;
short result = firstFreeLocal;
while (true) {
if (result >= (MAX_LOCALS - 1))
break;
int i;
for (i = 0; i < count; i++)
if (locals[result + i])
break;
if (i >= count)
break;
result++;
}
return result;
}
private void releaseWordLocal(short local)
{
if (local < firstFreeLocal)

Просмотреть файл

@ -21,6 +21,7 @@
*
* Contributor(s):
* Norris Boyd
* Bob Jervis
* Roger Lawrence
*
* Alternatively, the contents of this file may be used under the terms of
@ -126,7 +127,8 @@ final class OptFunctionNode
int type = n.getType();
if (type == Token.GETVAR) {
name = n.getString();
} else if (type == Token.SETVAR) {
} else if (type == Token.SETVAR ||
type == Token.SETCONSTVAR) {
name = n.getFirstChild().getString();
} else {
throw Kit.codeBug();

Просмотреть файл

@ -24,6 +24,7 @@
#
# Contributor(s):
# Norris Boyd
# Bob Jervis
#
# Alternatively, the contents of this file may be used under the terms of
# the GNU General Public License Version 2 or later (the "GPL"), in which
@ -245,12 +246,18 @@ msg.bad.regexp.compile =\
msg.got.syntax.errors = \
Compilation produced {0} syntax errors.
msg.var.redecl =\
Redeclaration of var {0}.
msg.const.redecl =\
Redeclaration of const {0}.
# NodeTransformer
msg.dup.label =\
duplicatet label
duplicated label
msg.undef.label =\
undefined labe
undefined label
msg.bad.break =\
unlabelled break must be inside loop or switch
@ -532,7 +539,6 @@ msg.setter.parms =\
msg.setter.bad.type =\
Unsupported parameter type "{0}" in setter "{1}".
msg.add.sealed =\
Cannot add a property to a sealed object: {0}.