diff --git a/docs/scopes.html b/docs/scopes.html index a8e6ba567d..712266ac7b 100644 --- a/docs/scopes.html +++ b/docs/scopes.html @@ -158,9 +158,8 @@

Dynamic Scopes

up in the function and then, if not found there, in the lexically enclosing scope. This causes problems if functions you define in your shared scope need access to variables you define in your instance scope. -

With Rhino 1.5, it is possible to compile functions to use dynamic -scope. With dynamic scope, functions look at the top-level scope of the -calling function rather than their lexical scope. So we can store information +

With Rhino 1.6, it is possible to use dynamic scope. With dynamic scope, functions look at the top-level scope of the currently executed script +rather than their lexical scope. So we can store information that varies across scopes in the instance scope yet still share functions that manipulate that information reside in the shared scope.

The diff --git a/examples/DynamicScopes.java b/examples/DynamicScopes.java index aba5c72833..37bff10922 100644 --- a/examples/DynamicScopes.java +++ b/examples/DynamicScopes.java @@ -40,65 +40,94 @@ */ public class DynamicScopes { + static boolean useDynamicScope; + + static class MyFactory extends ContextFactory + { + protected boolean hasFeature(Context cx, int featureIndex) + { + if (featureIndex == Context.FEATURE_DYNAMIC_SCOPE) { + return useDynamicScope; + } + return super.hasFeature(cx, featureIndex); + } + } + + static { + ContextFactory.initGlobal(new MyFactory()); + } + + /** * Main entry point. * * Set up the shared scope and then spawn new threads that execute - * relative to that shared scope. Try compiling functions with and + * relative to that shared scope. Try to run functions with and * without dynamic scope to see the effect. * * The expected output is *

      * sharedScope
+     * nested:sharedScope
      * sharedScope
+     * nested:sharedScope
      * sharedScope
+     * nested:sharedScope
      * thread0
+     * nested:thread0
      * thread1
+     * nested:thread1
      * thread2
+     * nested:thread2
      * 
* The final three lines may be permuted in any order depending on * thread scheduling. */ public static void main(String[] args) - throws JavaScriptException { Context cx = Context.enter(); try { - cx.setCompileFunctionsWithDynamicScope(false); - runScripts(cx); - cx.setCompileFunctionsWithDynamicScope(true); - runScripts(cx); + // Precompile source only once + String source = "" + +"var x = 'sharedScope';\n" + +"function f() { return x; }\n" + // Dynamic scope works with nested function too + +"function initClosure(prefix) {\n" + +" return function test() { return prefix+x; }\n" + +"}\n" + +"var closure = initClosure('nested:');\n" + +""; + Script script = cx.compileString(source, "sharedScript", 1, null); + + useDynamicScope = false; + runScripts(cx, script); + useDynamicScope = true; + runScripts(cx, script); } finally { cx.exit(); } } - static void runScripts(Context cx) - throws JavaScriptException + static void runScripts(Context cx, Script script) { // Initialize the standard objects (Object, Function, etc.) // This must be done before scripts can be executed. The call // returns a new scope that we will share. - ScriptableObject scope = cx.initStandardObjects(null, true); + ScriptableObject sharedScope = cx.initStandardObjects(null, true); - // Now we can evaluate a script and functions will be compiled to - // use dynamic scope if the Context is so initialized. - String source = "var x = 'sharedScope';" + - "function f() { return x; }"; - cx.evaluateString(scope, source, "MySource", 1, null); + // Now we can execute the precompiled script against the scope + // to define x variable and f function in the shared scope. + script.exec(cx, sharedScope); // Now we spawn some threads that execute a script that calls the // function 'f'. The scope chain looks like this: //
-        //            ------------------
-        //           |   shared scope   |
-        //            ------------------
+        //            ------------------                ------------------
+        //           | per-thread scope | -prototype-> |   shared scope   |
+        //            ------------------                ------------------
         //                    ^
         //                    |
-        //            ------------------
-        //           | per-thread scope |
-        //            ------------------
-        //                    ^
+        //               parentScope
         //                    |
         //            ------------------
         //           | f's activation   |
@@ -112,9 +141,13 @@ static void runScripts(Context cx)
         final int threadCount = 3;
         Thread[] t = new Thread[threadCount];
         for (int i=0; i < threadCount; i++) {
-            String script = "function g() { var x = 'local'; return f(); }" +
-                            "java.lang.System.out.println(g());";
-            t[i] = new Thread(new PerThread(scope, script,
+            String source2 = ""
+                +"function g() { var x = 'local'; return f(); }\n"
+                +"java.lang.System.out.println(g());\n"
+                +"function g2() { var x = 'local'; return closure(); }\n"
+                +"java.lang.System.out.println(g2());\n"
+                +"";
+            t[i] = new Thread(new PerThread(sharedScope, source2,
                                             "thread" + i));
         }
         for (int i=0; i < threadCount; i++)
@@ -131,9 +164,9 @@ static void runScripts(Context cx)
 
     static class PerThread implements Runnable {
 
-        PerThread(Scriptable scope, String script, String x) {
-            this.scope = scope;
-            this.script = script;
+        PerThread(Scriptable sharedScope, String source, String x) {
+            this.sharedScope = sharedScope;
+            this.source = source;
             this.x = x;
         }
 
@@ -142,8 +175,8 @@ public void run() {
             Context cx = Context.enter();
             try {
                 // We can share the scope.
-                Scriptable threadScope = cx.newObject(scope);
-                threadScope.setPrototype(scope);
+                Scriptable threadScope = cx.newObject(sharedScope);
+                threadScope.setPrototype(sharedScope);
 
                 // We want "threadScope" to be a new top-level
                 // scope, so set its parent scope to null. This
@@ -154,7 +187,7 @@ public void run() {
                 // Create a JavaScript property of the thread scope named
                 // 'x' and save a value for it.
                 threadScope.put("x", threadScope, x);
-                cx.evaluateString(threadScope, script, "threadScript", 1, null);
+                cx.evaluateString(threadScope, source, "threadScript", 1, null);
             }
             catch (JavaScriptException jse) {
                 // ignore
@@ -163,8 +196,8 @@ public void run() {
                 Context.exit();
             }
         }
-        private Scriptable scope;
-        private String script;
+        private Scriptable sharedScope;
+        private String source;
         private String x;
     }
 
diff --git a/src/org/mozilla/javascript/CompilerEnvirons.java b/src/org/mozilla/javascript/CompilerEnvirons.java
index 5917af4a0b..784fdcc145 100644
--- a/src/org/mozilla/javascript/CompilerEnvirons.java
+++ b/src/org/mozilla/javascript/CompilerEnvirons.java
@@ -59,7 +59,7 @@ public void initFromContext(Context cx)
     {
         setErrorReporter(cx.getErrorReporter());
         this.languageVersion = cx.getLanguageVersion();
-        useDynamicScope = cx.hasCompileFunctionsWithDynamicScope();
+        useDynamicScope = cx.compileFunctionsWithDynamicScopeFlag;
         generateDebugInfo = (!cx.isGeneratingDebugChanged()
                              || cx.isGeneratingDebug());
         reservedKeywordAsIdentifier
@@ -112,11 +112,6 @@ public final boolean isUseDynamicScope()
         return useDynamicScope;
     }
 
-    public void setUseDynamicScope(boolean flag)
-    {
-        this.useDynamicScope = flag;
-    }
-
     public final boolean isReservedKeywordAsIdentifier()
     {
         return reservedKeywordAsIdentifier;
diff --git a/src/org/mozilla/javascript/Context.java b/src/org/mozilla/javascript/Context.java
index 4fb326fc7a..6562132a29 100644
--- a/src/org/mozilla/javascript/Context.java
+++ b/src/org/mozilla/javascript/Context.java
@@ -211,9 +211,27 @@ public class Context
      * By default {@link #hasFeature(int)} returns true if
      * the current JS version is set to {@link #VERSION_DEFAULT}
      * or is greater then {@link #VERSION_1_6}.
+     * @since 1.6 Release 1
      */
     public static final int FEATURE_E4X = 6;
 
+    /**
+     * Control if dynamic scope should be used for name access.
+     * If hasFeature(FEATURE_DYNAMIC_SCOPE) returns true, then the name lookup
+     * during name resolution will use the top scope of the script or function
+     * which is at the top of JS execution stack instead of the top scope of the
+     * script or function from the current stack frame if the top scope of
+     * the top stack frame contains the top scope of the current stack frame
+     * on its prototype chain.
+     * 

+ * This is useful to define shared scope containing functions that can + * be called from scripts and functions using private scopes. + *

+ * By default {@link #hasFeature(int)} returns false. + * @since 1.6 Release 1 + */ + public static final int FEATURE_DYNAMIC_SCOPE = 7; + public static final String languageVersionProperty = "language version"; public static final String errorReporterProperty = "error reporter"; @@ -1913,13 +1931,9 @@ public final void removeThreadLocal(Object key) } /** - * Return whether functions are compiled by this context using - * dynamic scope. - *

- * If functions are compiled with dynamic scope, then they execute - * in the scope of their caller, rather than in their parent scope. - * This is useful for sharing functions across multiple scopes. - * @since 1.5 Release 1 + * @deprecated Use {@link #hasFeature(int)} and + * {@link #FEATURE_DYNAMIC_SCOPE} to control dynamic scoping that also works + * with nested functions defined by functions in the shared scope. */ public final boolean hasCompileFunctionsWithDynamicScope() { @@ -1927,11 +1941,9 @@ public final boolean hasCompileFunctionsWithDynamicScope() } /** - * Set whether functions compiled by this context should use - * dynamic scope. - *

- * @param flag if true, compile functions with dynamic scope - * @since 1.5 Release 1 + * @deprecated Use {@link #hasFeature(int)} and + * {@link #FEATURE_DYNAMIC_SCOPE} to control dynamic scoping that also works + * with nested functions defined by functions in the shared scope. */ public final void setCompileFunctionsWithDynamicScope(boolean flag) { @@ -2586,7 +2598,8 @@ public void removeActivationName(String name) private boolean generatingDebug; private boolean generatingDebugChanged; private boolean generatingSource=true; - private boolean compileFunctionsWithDynamicScopeFlag; + boolean compileFunctionsWithDynamicScopeFlag; + boolean useDynamicScope; private int optimizationLevel; private WrapFactory wrapFactory; Debugger debugger; diff --git a/src/org/mozilla/javascript/ContextFactory.java b/src/org/mozilla/javascript/ContextFactory.java index 833841a246..59459e79aa 100644 --- a/src/org/mozilla/javascript/ContextFactory.java +++ b/src/org/mozilla/javascript/ContextFactory.java @@ -229,6 +229,9 @@ protected boolean hasFeature(Context cx, int featureIndex) version = cx.getLanguageVersion(); return (version == Context.VERSION_DEFAULT || version >= Context.VERSION_1_6); + + case Context.FEATURE_DYNAMIC_SCOPE: + return false; } // It is a bug to call the method with unknown featureIndex throw new IllegalArgumentException(String.valueOf(featureIndex)); diff --git a/src/org/mozilla/javascript/ScriptRuntime.java b/src/org/mozilla/javascript/ScriptRuntime.java index 5de0920064..dfd477b40f 100644 --- a/src/org/mozilla/javascript/ScriptRuntime.java +++ b/src/org/mozilla/javascript/ScriptRuntime.java @@ -1758,6 +1758,9 @@ private static Object nameOrFunction(Context cx, Scriptable scope, private static Object topScopeName(Context cx, Scriptable scope, String name) { + if (cx.useDynamicScope) { + scope = locateDynamicScope(cx, scope); + } return ScriptableObject.getProperty(scope, name); } @@ -1813,6 +1816,10 @@ public static Scriptable bind(Context cx, Scriptable scope, String id) } } } + // scope here is top scope + if (cx.useDynamicScope) { + scope = locateDynamicScope(cx, scope); + } if (ScriptableObject.hasProperty(scope, id)) { return scope; } @@ -1837,6 +1844,9 @@ public static Object setName(Scriptable bound, Object value, // global object. Find the global object by // walking up the scope chain. bound = ScriptableObject.getTopLevelScope(scope); + if (cx.useDynamicScope) { + bound = locateDynamicScope(cx, bound); + } bound.put(id, bound, value); /* This code is causing immense performance problems in @@ -2848,6 +2858,26 @@ public static boolean hasTopCall(Context cx) return (cx.topCallScope != null); } + private static Scriptable locateDynamicScope(Context cx, Scriptable scope) + { + // Return cx.topCallScope is scope is present on its prototype chain + // and return scope otherwise. + // Should only be called when scope is top scope. + if (cx.topCallScope == scope) { + return scope; + } + Scriptable proto = cx.topCallScope; + for (;;) { + proto = proto.getPrototype(); + if (proto == scope) { + return cx.topCallScope; + } + if (proto == null) { + return scope; + } + } + } + public static Object doTopCall(Callable callable, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) @@ -2857,6 +2887,7 @@ public static Object doTopCall(Callable callable, Object result; cx.topCallScope = ScriptableObject.getTopLevelScope(scope); + cx.useDynamicScope = cx.hasFeature(Context.FEATURE_DYNAMIC_SCOPE); try { result = callable.call(cx, scope, thisObj, args); } finally {