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 {