From f5329f629e6bc61a75d40b45b6a09440d801d0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Wed, 21 Mar 2012 08:00:00 +0100 Subject: [PATCH] - support special GetValue and PutValue on primitives in call expressions - handle primitive thisObject in NativeString, NativeBoolean and NativeNumber functions --- src/org/mozilla/javascript/Context.java | 2 +- src/org/mozilla/javascript/Interpreter.java | 17 +- src/org/mozilla/javascript/NativeBoolean.java | 9 +- src/org/mozilla/javascript/NativeNumber.java | 9 +- src/org/mozilla/javascript/NativeString.java | 15 +- src/org/mozilla/javascript/ScriptRuntime.java | 160 ++++++++++-------- .../mozilla/javascript/optimizer/Codegen.java | 13 +- .../javascript/optimizer/OptRuntime.java | 8 +- 8 files changed, 135 insertions(+), 98 deletions(-) diff --git a/src/org/mozilla/javascript/Context.java b/src/org/mozilla/javascript/Context.java index dafa975e6b..49813c5216 100644 --- a/src/org/mozilla/javascript/Context.java +++ b/src/org/mozilla/javascript/Context.java @@ -2665,7 +2665,7 @@ public void removeActivationName(String name) long scratchUint32; // It can be used to return the second Scriptable result from function - Scriptable scratchScriptable; + Object scratchThis; // Generate an observer count on compiled code public boolean generateObserverCount = false; diff --git a/src/org/mozilla/javascript/Interpreter.java b/src/org/mozilla/javascript/Interpreter.java index 5b2fcea9cf..579f3ec2b8 100644 --- a/src/org/mozilla/javascript/Interpreter.java +++ b/src/org/mozilla/javascript/Interpreter.java @@ -1540,7 +1540,7 @@ private static Object interpretLoop(Context cx, CallFrame frame, stack[stackTop] = ScriptRuntime.getNameObjectAndThis(stringReg, cx, frame.scope); ++stackTop; - Scriptable thisObj = ScriptRuntime.lastStoredScriptable(cx); + Object thisObj = ScriptRuntime.lastStoredThis(cx); stack[stackTop] = (thisObj != null ? thisObj : Undefined.instance); continue Loop; } @@ -1551,7 +1551,9 @@ private static Object interpretLoop(Context cx, CallFrame frame, stack[stackTop] = ScriptRuntime.getPropObjectAndThis(obj, stringReg, cx, frame.scope); ++stackTop; - stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx); + // ignore stored this + ScriptRuntime.lastStoredThis(cx); + stack[stackTop] = obj; continue Loop; } case Icode_ELEM_AND_THIS: { @@ -1559,8 +1561,11 @@ private static Object interpretLoop(Context cx, CallFrame frame, if (obj == DBL_MRK) obj = ScriptRuntime.wrapNumber(sDbl[stackTop - 1]); Object id = stack[stackTop]; if (id == DBL_MRK) id = ScriptRuntime.wrapNumber(sDbl[stackTop]); - stack[stackTop - 1] = ScriptRuntime.getElemObjectAndThis(obj, id, cx); - stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx); + stack[stackTop - 1] = ScriptRuntime.getElemObjectAndThis(obj, id, cx, + frame.scope); + // ignore stored this + ScriptRuntime.lastStoredThis(cx); + stack[stackTop] = obj; continue Loop; } case Icode_VALUE_AND_THIS : { @@ -1568,8 +1573,8 @@ private static Object interpretLoop(Context cx, CallFrame frame, if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]); stack[stackTop] = ScriptRuntime.getValueObjectAndThis(value, cx); ++stackTop; - // ignore stored scriptable - ScriptRuntime.lastStoredScriptable(cx); + // ignore stored this + ScriptRuntime.lastStoredThis(cx); stack[stackTop] = Undefined.instance; continue Loop; } diff --git a/src/org/mozilla/javascript/NativeBoolean.java b/src/org/mozilla/javascript/NativeBoolean.java index f59fb88c49..7a6f4ea9c2 100644 --- a/src/org/mozilla/javascript/NativeBoolean.java +++ b/src/org/mozilla/javascript/NativeBoolean.java @@ -121,9 +121,14 @@ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, // The rest of Boolean.prototype methods require thisObj to be Boolean - if (!(thisObj instanceof NativeBoolean)) + boolean value; + if (thisObj instanceof Boolean) { + value = ((Boolean)thisObj).booleanValue(); + } else if (thisObj instanceof NativeBoolean) { + value = ((NativeBoolean)thisObj).booleanValue; + } else { throw incompatibleCallError(f); - boolean value = ((NativeBoolean)thisObj).booleanValue; + } switch (id) { diff --git a/src/org/mozilla/javascript/NativeNumber.java b/src/org/mozilla/javascript/NativeNumber.java index b6cfe92028..53ec4c03d4 100644 --- a/src/org/mozilla/javascript/NativeNumber.java +++ b/src/org/mozilla/javascript/NativeNumber.java @@ -136,9 +136,14 @@ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, // The rest of Number.prototype methods require thisObj to be Number - if (!(thisObj instanceof NativeNumber)) + double value; + if (thisObj instanceof Number) { + value = ((Number)thisObj).doubleValue(); + } else if (thisObj instanceof NativeNumber) { + value = ((NativeNumber)thisObj).doubleValue; + } else { throw incompatibleCallError(f); - double value = ((NativeNumber)thisObj).doubleValue; + } switch (id) { diff --git a/src/org/mozilla/javascript/NativeString.java b/src/org/mozilla/javascript/NativeString.java index bcff5ff6aa..c160edc0e4 100644 --- a/src/org/mozilla/javascript/NativeString.java +++ b/src/org/mozilla/javascript/NativeString.java @@ -272,11 +272,11 @@ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, case Id_toString: case Id_valueOf: // ECMA 15.5.4.2: 'the toString function is not generic. - CharSequence cs = realThis(thisObj, f).string; + CharSequence cs = realThis(thisObj, f); return cs instanceof String ? cs : cs.toString(); case Id_toSource: { - CharSequence s = realThis(thisObj, f).string; + CharSequence s = realThis(thisObj, f); return "(new String(\""+ScriptRuntime.escapeString(s.toString())+"\"))"; } @@ -452,11 +452,14 @@ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, } } - private static NativeString realThis(Object thisObj, IdFunctionObject f) + private static CharSequence realThis(Object thisObj, IdFunctionObject f) { - if (!(thisObj instanceof NativeString)) - throw incompatibleCallError(f); - return (NativeString)thisObj; + if (thisObj instanceof CharSequence) { + return (CharSequence)thisObj; + } else if (thisObj instanceof NativeString) { + return ((NativeString)thisObj).string; + } + throw incompatibleCallError(f); } /* diff --git a/src/org/mozilla/javascript/ScriptRuntime.java b/src/org/mozilla/javascript/ScriptRuntime.java index b4d6201b72..fa92647582 100644 --- a/src/org/mozilla/javascript/ScriptRuntime.java +++ b/src/org/mozilla/javascript/ScriptRuntime.java @@ -1513,7 +1513,9 @@ private static Object getPrimitiveValue(Object base, Scriptable obj, String property, Context cx, Scriptable scope) { // TODO: could need some optimization - assert obj instanceof ScriptableObject; + assert obj instanceof NativeString + || obj instanceof NativeNumber + || obj instanceof NativeBoolean; ScriptableObject sobj = (ScriptableObject) obj; PropertyDescriptor desc = sobj.$getProperty(property); if (desc == null) { @@ -1537,7 +1539,9 @@ private static void putPrimitiveValue(Object base, Scriptable obj, Object value, Context cx, Scriptable scope) { // TODO: could need some optimization - assert obj instanceof ScriptableObject; + assert obj instanceof NativeString + || obj instanceof NativeNumber + || obj instanceof NativeBoolean; ScriptableObject sobj = (ScriptableObject) obj; if (!sobj.$canPut(property)) { if (checked) { @@ -1563,6 +1567,7 @@ private static void putPrimitiveValue(Object base, Scriptable obj, return; } Object setter = desc.getSetter(); + assert setter instanceof Callable; ((Callable) setter).call(cx, scope, base, new Object[]{ value }); } @@ -2154,7 +2159,7 @@ private static Object nameOrFunction(Context cx, Scriptable scope, result = topScopeName(cx, scope, name); if (result == Scriptable.NOT_FOUND) { if (asFunctionCall) { - storeScriptable(cx, BAD_SCRIPTABLE); + storeThis(cx, BAD_SCRIPTABLE); return createNotFoundError(scope, name); } else if (firstXMLObject == null) { throw notFoundError(scope, name); @@ -2173,10 +2178,10 @@ private static Object nameOrFunction(Context cx, Scriptable scope, if (asFunctionCall) { if (!(result instanceof Callable)) { - storeScriptable(cx, BAD_SCRIPTABLE); + storeThis(cx, BAD_SCRIPTABLE); return notFunctionError(result, name); } - storeScriptable(cx, thisObj); + storeThis(cx, thisObj); } return result; @@ -2547,8 +2552,8 @@ public static Callable ensureCallable(Object value) throws RuntimeException { */ private static Callable ensureCallable(Context cx, Object value) { if (!(value instanceof Callable)) { - // clear stored scriptable before throwing - lastStoredScriptable(cx); + // clear stored this before throwing + lastStoredThis(cx); throw (RuntimeException)value; } return (Callable)value; @@ -2567,11 +2572,11 @@ public static Callable getNameFunctionAndThis(String name, { Object value = getNameObjectAndThis(name, cx, scope); // restore old behaviour - scope = lastStoredScriptable(cx); - if (scope == null) { - scope = cx.topCallScope; + Object thisObj = lastStoredThis(cx); + if (thisObj == null) { + thisObj = cx.topCallScope; } - storeScriptable(cx, scope); + storeThis(cx, thisObj); return ensureCallable(cx, value); } @@ -2586,7 +2591,7 @@ public static Callable getElemFunctionAndThis(Object obj, Object elem, Context cx) { - Object value = getElemObjectAndThis(obj, elem, cx); + Object value = getElemObjectAndThis(obj, elem, cx, getTopCallScope(cx)); return ensureCallable(cx, value); } @@ -2603,7 +2608,7 @@ public static Callable getPropFunctionAndThis(Object obj, String property, Context cx) { - Object value = getPropObjectAndThis(obj, property, cx); + Object value = getPropObjectAndThis(obj, property, cx, getTopCallScope(cx)); return ensureCallable(cx, value); } @@ -2616,7 +2621,7 @@ public static Callable getPropFunctionAndThis(Object obj, */ public static Callable getPropFunctionAndThis(Object obj, String property, - Context cx, final Scriptable scope) + Context cx, Scriptable scope) { Object value = getPropObjectAndThis(obj, property, cx, scope); return ensureCallable(cx, value); @@ -2676,7 +2681,7 @@ public static Object getNameObjectAndThis(String name, if (parent == null) { Object result = topScopeName(cx, scope, name); if (!(result instanceof Callable)) { - storeScriptable(cx, BAD_SCRIPTABLE); + storeThis(cx, BAD_SCRIPTABLE); if (result == Scriptable.NOT_FOUND) { return createNotFoundError(scope, name); } else { @@ -2684,7 +2689,7 @@ public static Object getNameObjectAndThis(String name, } } // Top scope is not NativeWith or NativeCall => thisObj == scope - storeScriptable(cx, null); + storeThis(cx, null); return result; } @@ -2701,44 +2706,15 @@ public static Object getNameObjectAndThis(String name, */ public static Object getElemObjectAndThis(Object obj, Object elem, - Context cx) + Context cx, Scriptable scope) { String str = toStringIdOrIndex(cx, elem); if (str != null) { - return getPropObjectAndThis(obj, str, cx); - } - int index = lastIndexResult(cx); - - Scriptable thisObj = toObjectOrNull(cx, obj); - if (thisObj == null) { - throw undefCallError(obj, String.valueOf(index)); - } - - Object value = ScriptableObject.getProperty(thisObj, index); - if (!(value instanceof Callable)) { - storeScriptable(cx, BAD_SCRIPTABLE); - return notFunctionError(value, elem); + return getObjectAndThisHelper(obj, str, -1, cx, scope); + } else { + int index = lastIndexResult(cx); + return getObjectAndThisHelper(obj, null, index, cx, scope); } - - storeScriptable(cx, thisObj); - return value; - } - - /** - * Prepare for calling obj.property(...): return function corresponding to - * obj.property and make obj properly converted to Scriptable available - * as ScriptRuntime.lastStoredScriptable() for consumption as thisObj. - * The caller must call ScriptRuntime.lastStoredScriptable() immediately - * after calling this method. - * Warning: this doesn't allow to resolve primitive prototype properly when - * many top scopes are involved. - */ - public static Object getPropObjectAndThis(Object obj, - String property, - Context cx) - { - Scriptable thisObj = toObjectOrNull(cx, obj); - return getPropFunctionAndThisHelper(obj, property, cx, thisObj); } /** @@ -2750,32 +2726,60 @@ public static Object getPropObjectAndThis(Object obj, */ public static Object getPropObjectAndThis(Object obj, String property, - Context cx, final Scriptable scope) + Context cx, Scriptable scope) { - Scriptable thisObj = toObjectOrNull(cx, obj, scope); - return getPropFunctionAndThisHelper(obj, property, cx, thisObj); + assert property != null; + return getObjectAndThisHelper(obj, property, -1, cx, scope); } - private static Object getPropFunctionAndThisHelper(Object obj, - String property, Context cx, Scriptable thisObj) - { - if (thisObj == null) { + private static Object getObjectAndThisHelper(Object obj, String property, + int index, Context cx, + Scriptable scope) { + Scriptable sobj; + Object value; + // spidermonkey also tries noSuchMethod for index properties, rhino bug? + boolean tryNoSuchMethod = property != null; + if (obj == null || obj == Undefined.instance) { throw undefCallError(obj, property); + } else if (obj instanceof Scriptable) { + sobj = (Scriptable) obj; + if (property == null) { + value = ScriptableObject.getProperty(sobj, index); + } else { + value = ScriptableObject.getProperty(sobj, property); + } + } else if ((sobj = tryPrimitive(cx, scope, obj)) != null) { + // follow spidermonkey (possible bug?) + tryNoSuchMethod = false; + if (property == null) { property = toString(index); } + value = getPrimitiveValue(obj, sobj, property, cx, scope); + } else if ((sobj = tryWrap(cx, scope, obj)) != null) { + if (property == null) { + value = ScriptableObject.getProperty(sobj, index); + } else { + value = ScriptableObject.getProperty(sobj, property); + } + } else { + throw errorWithClassName("msg.invalid.type", obj); } - Object value = ScriptableObject.getProperty(thisObj, property); - if (!(value instanceof Callable)) { - Object noSuchMethod = ScriptableObject.getProperty(thisObj, "__noSuchMethod__"); - if (noSuchMethod instanceof Callable) - value = new NoSuchMethodShim((Callable)noSuchMethod, property); + boolean valueCallable = value instanceof Callable; + if (tryNoSuchMethod && !valueCallable) { + Object noSuchMethod = ScriptableObject.getProperty(sobj, "__noSuchMethod__"); + if (noSuchMethod instanceof Callable) { + if (property == null) { property = toString(index); } + valueCallable = true; + value = new NoSuchMethodShim((Callable) noSuchMethod, property); + } } - if (!(value instanceof Callable)) { - storeScriptable(cx, BAD_SCRIPTABLE); - return notFunctionError(thisObj, value, property); + if (!valueCallable) { + if (property == null) { property = toString(index); } + storeThis(cx, BAD_SCRIPTABLE); + return notFunctionError(sobj, value, property); } - storeScriptable(cx, thisObj); + storeThis(cx, sobj); return value; } @@ -2789,7 +2793,7 @@ private static Object getPropFunctionAndThisHelper(Object obj, public static Object getValueObjectAndThis(Object value, Context cx) { if (!(value instanceof Callable)) { - storeScriptable(cx, BAD_SCRIPTABLE); + storeThis(cx, BAD_SCRIPTABLE); return notFunctionError(value); } Scriptable thisObj = null; @@ -2809,7 +2813,7 @@ public static Object getValueObjectAndThis(Object value, Context cx) thisObj = ScriptableObject.getTopLevelScope(thisObj); } } - storeScriptable(cx, thisObj); + storeThis(cx, thisObj); return value; } @@ -4555,21 +4559,27 @@ public static long lastUint32Result(Context cx) return value; } - private static void storeScriptable(Context cx, Scriptable value) + private static void storeThis(Context cx, Object value) { - // The previously stored scratchScriptable should be consumed - if (cx.scratchScriptable != null) + // The previously stored scratchThis should be consumed + if (cx.scratchThis != null) throw new IllegalStateException(); - cx.scratchScriptable = value; + cx.scratchThis = value; } - public static Scriptable lastStoredScriptable(Context cx) + public static Object lastStoredThis(Context cx) { - Scriptable result = cx.scratchScriptable; - cx.scratchScriptable = null; + Object result = cx.scratchThis; + cx.scratchThis = null; return result; } + @Deprecated + public static Scriptable lastStoredScriptable(Context cx) + { + return toObjectOrNull(cx, lastStoredThis(cx)); + } + static String makeUrlForGeneratedScript (boolean isEval, String masterScriptUrl, int masterScriptLine) { diff --git a/src/org/mozilla/javascript/optimizer/Codegen.java b/src/org/mozilla/javascript/optimizer/Codegen.java index cffe5071a7..9cbc1033ba 100644 --- a/src/org/mozilla/javascript/optimizer/Codegen.java +++ b/src/org/mozilla/javascript/optimizer/Codegen.java @@ -3645,6 +3645,7 @@ private void generateObjectAndThisObj(Node node, Node parent) case Token.GETELEM: { Node target = node.getFirstChild(); generateExpression(target, node); + cfw.add(ByteCode.DUP); // dup b/c we ignore lastStoredThis Node id = target.getNext(); if (type == Token.GETPROP) { String property = id.getString(); @@ -3664,11 +3665,13 @@ private void generateObjectAndThisObj(Node node, Node parent) throw Codegen.badTree(); generateExpression(id, node); // id cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); addScriptRuntimeInvoke( "getElemObjectAndThis", "(Ljava/lang/Object;" +"Ljava/lang/Object;" +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" +")Ljava/lang/Object;"); } break; @@ -3701,13 +3704,17 @@ private void generateObjectAndThisObj(Node node, Node parent) // Get thisObj prepared by get(Name|Prop|Elem|Value)ObjectAndThis cfw.addALoad(contextLocal); addScriptRuntimeInvoke( - "lastStoredScriptable", + "lastStoredThis", "(Lorg/mozilla/javascript/Context;" - +")Lorg/mozilla/javascript/Scriptable;"); + +")Ljava/lang/Object;"); switch (type) { case Token.GETPROP: case Token.GETELEM: - // no further changes needed + // ignore stored scriptable, obj is still on stack (see DUP above) + // stack: ... obj value stored -> ... obj value + cfw.add(ByteCode.POP); + // stack: ... obj value -> ... value obj + cfw.add(ByteCode.SWAP); break; case Token.NAME: { // replace null with undefined diff --git a/src/org/mozilla/javascript/optimizer/OptRuntime.java b/src/org/mozilla/javascript/optimizer/OptRuntime.java index a8e354692a..6098fb12fa 100644 --- a/src/org/mozilla/javascript/optimizer/OptRuntime.java +++ b/src/org/mozilla/javascript/optimizer/OptRuntime.java @@ -97,7 +97,7 @@ public static Object callName(Object[] args, String name, Context cx, Scriptable scope) { Object f = getNameObjectAndThis(name, cx, scope); - Object thisObj = lastStoredScriptable(cx); + Object thisObj = lastStoredThis(cx); thisObj = (thisObj != null ? thisObj : Undefined.instance); Callable c = ensureCallable(f); return c.call(cx, scope, thisObj, args); @@ -110,7 +110,7 @@ public static Object callName0(String name, Context cx, Scriptable scope) { Object f = getNameObjectAndThis(name, cx, scope); - Object thisObj = lastStoredScriptable(cx); + Object thisObj = lastStoredThis(cx); thisObj = (thisObj != null ? thisObj : Undefined.instance); Callable c = ensureCallable(f); return c.call(cx, scope, thisObj, ScriptRuntime.emptyArgs); @@ -123,7 +123,9 @@ public static Object callProp0(Object value, String property, Context cx, Scriptable scope) { Object f = getPropObjectAndThis(value, property, cx, scope); - Scriptable thisObj = lastStoredScriptable(cx); + // ignore stored this + Object thisObj = lastStoredThis(cx); + thisObj = value; Callable c = ensureCallable(f); return c.call(cx, scope, thisObj, ScriptRuntime.emptyArgs); }