Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ static final Context enter(Context cx, ContextFactory factory) {
Context old = currentContext.get();
if (old != null) {
cx = old;
throw new UnsupportedOperationException("NESTED"); // for testing only
} else {
if (cx == null) {
cx = factory.makeContext();
Expand Down Expand Up @@ -604,9 +605,46 @@ public static Object call(

/** The method implements {@link ContextFactory#call(ContextAction)} logic. */
static <T> T call(ContextFactory factory, ContextAction<T> action) {
try (Context cx = enter(null, factory)) {
Context cx = currentContext.get();
if (cx != null) {
if (cx.isSealed()) {
return action.run(cx);
}
cx.seal("SECRET");
try {
return action.run(cx);
} finally {
cx.unseal("SECRET");
}
}
// do not nest calls
try (Context newCx = enter(null, factory)) {
return action.run(newCx);
}
}

static <T> T callExplicit(ContextFactory factory, ContextAction<T> action) {
Context old = currentContext.get();
currentContext.set(null);
// do not nest calls
try (Context newCx = enter(null, factory)) {
return action.run(newCx);
} finally {
currentContext.set(old);
}
}

static <T> T call(ContextFactory factory, Consumer<Context> config, ContextAction<T> action) {
Context cx = currentContext.get();
if (cx != null) {
return action.run(cx);
}
// do not nest calls
try (Context newCx = enter(null, factory)) {
config.accept(newCx);
newCx.seal(null);
return action.run(newCx);
}
}

/**
Expand Down
17 changes: 17 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/ContextFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.function.Consumer;

/**
* Factory class that Rhino runtime uses to create new {@link Context} instances. A <code>
Expand Down Expand Up @@ -439,6 +440,22 @@ public final <T> T call(ContextAction<T> action) {
return Context.call(this, action);
}

/** Configure new context and seal. Reuse existing */
public final <T> T call(Consumer<Context> configuration, ContextAction<T> action) {
return Context.call(this, configuration, action);
}

/**
* @param action
* @return
* @param <T>
*/
public final <T> T callExplicit(ContextAction<T> action) {
return Context.callExplicit(this, action);
}

public void callExplicit(Object action) {}

/**
* Get a context associated with the current thread, creating one if need be. The Context stores
* the execution state of the JavaScript engine, so it is required that the context be entered
Expand Down
27 changes: 16 additions & 11 deletions rhino/src/main/java/org/mozilla/javascript/JavaMembers.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,22 @@ class JavaMembers {
}

JavaMembers(Scriptable scope, Class<?> cl, boolean includeProtected) {
try (Context cx = ContextFactory.getGlobal().enterContext()) {
ClassShutter shutter = cx.getClassShutter();
if (shutter != null && !shutter.visibleToScripts(cl.getName())) {
throw Context.reportRuntimeErrorById("msg.access.prohibited", cl.getName());
}
this.members = new HashMap<>();
this.staticMembers = new HashMap<>();
this.cl = cl;
boolean includePrivate = cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS);
reflect(cx, scope, includeProtected, includePrivate);
}
ContextFactory.getGlobal()
.call(
cx -> {
ClassShutter shutter = cx.getClassShutter();
if (shutter != null && !shutter.visibleToScripts(cl.getName())) {
throw Context.reportRuntimeErrorById(
"msg.access.prohibited", cl.getName());
}
this.members = new HashMap<>();
this.staticMembers = new HashMap<>();
this.cl = cl;
boolean includePrivate =
cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS);
reflect(cx, scope, includeProtected, includePrivate);
return Undefined.instance;
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
import org.junit.Test;
import org.mozilla.javascript.ConsString;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ContinuationPending;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.WrappedException;
import org.mozilla.javascript.serialize.ScriptableInputStream;
import org.mozilla.javascript.serialize.ScriptableOutputStream;
Expand All @@ -42,43 +44,57 @@ public static class MyClass implements Serializable {
private static final long serialVersionUID = 4189002778806232070L;

public int f(int a) {
try (Context cx = Context.enter()) {
ContinuationPending pending = cx.captureContinuation();
pending.setApplicationState(a);
throw pending;
}
return ContextFactory.getGlobal()
.call(
cx -> {
ContinuationPending pending = cx.captureContinuation();
pending.setApplicationState(a);
throw pending;
});
}

public int g(int a) {
try (Context cx = Context.enter()) {
ContinuationPending pending = cx.captureContinuation();
pending.setApplicationState(2 * a);
throw pending;
}
return ContextFactory.getGlobal()
.call(
cx -> {
ContinuationPending pending = cx.captureContinuation();
pending.setApplicationState(2 * a);
throw pending;
});
}

public String h() {
try (Context cx = Context.enter()) {
ContinuationPending pending = cx.captureContinuation();
pending.setApplicationState("2*3");
throw pending;
}
return ContextFactory.getGlobal()
.call(
cx -> {
ContinuationPending pending = cx.captureContinuation();
pending.setApplicationState("2*3");
throw pending;
});
}

public void directThrow() {
try (Context cx = Context.enter()) {
throw cx.captureContinuation();
}
ContextFactory.getGlobal()
.call(
cx -> {
throw cx.captureContinuation();
});
}
}

@Before
public void setUp() {
try (Context cx = Context.enter()) {
globalScope = cx.initStandardObjects();
cx.setInterpretedMode(true); // must use interpreter mode
globalScope.put("myObject", globalScope, Context.javaToJS(new MyClass(), globalScope));
}
ContextFactory.getGlobal()
.call(
cx -> {
globalScope = cx.initStandardObjects();
cx.setInterpretedMode(true); // must use interpreter mode
globalScope.put(
"myObject",
globalScope,
Context.javaToJS(new MyClass(), globalScope));
return Undefined.instance;
});
}

@Test
Expand All @@ -102,32 +118,41 @@ public void scriptWithContinuations() {

@Test
public void scriptWithMultipleContinuations() {
try (Context cx = Context.enter()) {
try {
cx.setInterpretedMode(true); // must use interpreter mode
Script script =
cx.compileString(
"myObject.f(3) + myObject.g(3) + 2;", "test source", 1, null);
cx.executeScriptWithContinuations(script, globalScope);
fail("Should throw ContinuationPending");
} catch (ContinuationPending pending) {
try {
Object applicationState = pending.getApplicationState();
assertEquals(Integer.valueOf(3), applicationState);
int saved = (Integer) applicationState;
cx.resumeContinuation(pending.getContinuation(), globalScope, saved + 1);
fail("Should throw another ContinuationPending");
} catch (ContinuationPending pending2) {
Object applicationState2 = pending2.getApplicationState();
assertEquals(Integer.valueOf(6), applicationState2);
int saved2 = (Integer) applicationState2;
Object result2 =
cx.resumeContinuation(
pending2.getContinuation(), globalScope, saved2 + 1);
assertEquals(13, ((Number) result2).intValue());
}
}
}
ContextFactory.getGlobal()
.call(
cx -> {
try {
cx.setInterpretedMode(true); // must use interpreter mode
Script script =
cx.compileString(
"myObject.f(3) + myObject.g(3) + 2;",
"test source",
1,
null);
cx.executeScriptWithContinuations(script, globalScope);
fail("Should throw ContinuationPending");
} catch (ContinuationPending pending) {
try {
Object applicationState = pending.getApplicationState();
assertEquals(Integer.valueOf(3), applicationState);
int saved = (Integer) applicationState;
cx.resumeContinuation(
pending.getContinuation(), globalScope, saved + 1);
fail("Should throw another ContinuationPending");
} catch (ContinuationPending pending2) {
Object applicationState2 = pending2.getApplicationState();
assertEquals(Integer.valueOf(6), applicationState2);
int saved2 = (Integer) applicationState2;
Object result2 =
cx.resumeContinuation(
pending2.getContinuation(),
globalScope,
saved2 + 1);
assertEquals(13, ((Number) result2).intValue());
}
}
return Void.TYPE;
});
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.junit.Before;
import org.junit.Test;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.json.JsonParser;
Expand Down Expand Up @@ -155,18 +156,28 @@ public void shouldParseJsonObject() throws Exception {

@Test
public void testECMAKeyOrdering() throws Exception {
try (Context cx = Context.enter()) {
cx.setLanguageVersion(Context.VERSION_ES6);
String json =
"{\"foo\": \"a\", \"bar\": \"b\", \"1\": \"c\", \"-1\": \"d\", \"x\": \"e\"}";
NativeObject actual = (NativeObject) parser.parseValue(json);
// Ensure that modern ECMAScript property ordering works, which depends on
// valid index values being treated as numbers and not as strings.
assertArrayEquals(
"Property ordering should match",
new Object[] {1, "foo", "bar", "-1", "x"},
actual.getIds());
}
ContextFactory.getGlobal()
.callExplicit(
cx -> {
cx.setLanguageVersion(Context.VERSION_ES6);
String json =
"{\"foo\": \"a\", \"bar\": \"b\", \"1\": \"c\", \"-1\": \"d\", \"x\": \"e\"}";

NativeObject actual = null;
try {
actual = (NativeObject) parser.parseValue(json);
} catch (ParseException e) {
throw new RuntimeException(e);
}
// Ensure that modern ECMAScript property ordering works, which depends
// on
// valid index values being treated as numbers and not as strings.
assertArrayEquals(
"Property ordering should match",
new Object[] {1, "foo", "bar", "-1", "x"},
actual.getIds());
return Void.TYPE;
});
}

@Test(expected = ParseException.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ public static Object eval(Context cx, String source, String id, Scriptable objec
}

public static Object eval(String source, Map<String, Scriptable> bindings) {
try (Context cx = ContextFactory.getGlobal().enterContext()) {
return eval(cx, source, bindings);
}
return ContextFactory.getGlobal().call(cx -> eval(cx, source, bindings));
}

public static Object eval(Context cx, String source, Map<String, Scriptable> bindings) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
import org.junit.Assert;
import org.junit.Test;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ContinuationPending;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;

// Tests that continuations work across arrow function, bound function, and apply/call invocations.
public class InterpreterFunctionPeelingTest {
public static final Runnable CAPTURER =
() -> {
try (var cx = Context.enter()) {
throw cx.captureContinuation();
}
};
() ->
ContextFactory.getGlobal()
.call(
cx -> {
throw cx.captureContinuation();
});

public static void executeScript(String script) {
try (var cx = Context.enter()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.junit.Assert;
import org.junit.Test;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.NativeConsole;
import org.mozilla.javascript.NativeConsole.Level;
import org.mozilla.javascript.ScriptStackElement;
Expand Down Expand Up @@ -545,10 +546,13 @@ public void printErrorProperty() {
}

private static void assertFormat(Object[] args, String expected) {
try (Context cx = Context.enter()) {
Scriptable scope = cx.initStandardObjects();
assertEquals(expected, NativeConsole.format(cx, scope, args));
}
ContextFactory.getGlobal()
.call(
cx -> {
Scriptable scope = cx.initStandardObjects();
assertEquals(expected, NativeConsole.format(cx, scope, args));
return Void.TYPE;
});
}

private static void assertPrintCalls(String source, List<PrinterCall> expectedCalls) {
Expand Down
Loading