Skip to content

Commit

Permalink
Resolving bug 255891: new approach to dynamic scoping
Browse files Browse the repository at this point in the history
  • Loading branch information
ibukanov committed Sep 1, 2004
1 parent b76364b commit 526b436
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 54 deletions.
5 changes: 2 additions & 3 deletions docs/scopes.html
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,8 @@ <h2> Dynamic Scopes</h2>
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.
<p>With Rhino 1.5, it is possible to compile functions to use <i>dynamic
scope</i>. With dynamic scope, functions look at the top-level scope of the
calling function rather than their lexical scope. So we can store information
<p>With Rhino 1.6, it is possible to use <i>dynamic scope</i>. 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. </p>
<p>The <a href="http://lxr.mozilla.org/mozilla/source/js/rhino/examples/DynamicScopes.java">
Expand Down
97 changes: 65 additions & 32 deletions examples/DynamicScopes.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
* <pre>
* sharedScope
* nested:sharedScope
* sharedScope
* nested:sharedScope
* sharedScope
* nested:sharedScope
* thread0
* nested:thread0
* thread1
* nested:thread1
* thread2
* nested:thread2
* </pre>
* 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:
// <pre>
// ------------------
// | shared scope |
// ------------------
// ------------------ ------------------
// | per-thread scope | -prototype-> | shared scope |
// ------------------ ------------------
// ^
// |
// ------------------
// | per-thread scope |
// ------------------
// ^
// parentScope
// |
// ------------------
// | f's activation |
Expand All @@ -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++)
Expand All @@ -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;
}

Expand All @@ -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
Expand All @@ -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
Expand All @@ -163,8 +196,8 @@ public void run() {
Context.exit();
}
}
private Scriptable scope;
private String script;
private Scriptable sharedScope;
private String source;
private String x;
}

Expand Down
7 changes: 1 addition & 6 deletions src/org/mozilla/javascript/CompilerEnvirons.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -112,11 +112,6 @@ public final boolean isUseDynamicScope()
return useDynamicScope;
}

public void setUseDynamicScope(boolean flag)
{
this.useDynamicScope = flag;
}

public final boolean isReservedKeywordAsIdentifier()
{
return reservedKeywordAsIdentifier;
Expand Down
39 changes: 26 additions & 13 deletions src/org/mozilla/javascript/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* This is useful to define shared scope containing functions that can
* be called from scripts and functions using private scopes.
* <p>
* 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";

Expand Down Expand Up @@ -1913,25 +1931,19 @@ public final void removeThreadLocal(Object key)
}

/**
* Return whether functions are compiled by this context using
* dynamic scope.
* <p>
* 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()
{
return compileFunctionsWithDynamicScopeFlag;
}

/**
* Set whether functions compiled by this context should use
* dynamic scope.
* <p>
* @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)
{
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/org/mozilla/javascript/ContextFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
31 changes: 31 additions & 0 deletions src/org/mozilla/javascript/ScriptRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
}
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down

0 comments on commit 526b436

Please sign in to comment.