Skip to content

Commit 50a5862

Browse files
committed
[GR-40422] GraalJSEngineFactory should return a compliant engine when js is not available.
PullRequest: js/2586
2 parents 51f9783 + a15a931 commit 50a5862

File tree

3 files changed

+205
-10
lines changed

3 files changed

+205
-10
lines changed

graal-js/src/com.oracle.truffle.js.scriptengine.test/src/com/oracle/truffle/js/scriptengine/test/ScriptEngineArrayTypeMappingTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ public class ScriptEngineArrayTypeMappingTest {
6464
*/
6565
@Test
6666
public void testJavaScriptArrayViaScriptEngine() throws ScriptException {
67-
try (GraalJSScriptEngine engine = new GraalJSEngineFactory().getScriptEngine()) {
67+
GraalJSEngineFactory factory = new GraalJSEngineFactory();
68+
try (GraalJSScriptEngine engine = (GraalJSScriptEngine) factory.getScriptEngine()) {
6869
Object result = engine.eval("['a', 'b', 'c']");
6970

7071
Assert.assertTrue(result instanceof List);

graal-js/src/com.oracle.truffle.js.scriptengine/src/com/oracle/truffle/js/scriptengine/GraalJSEngineFactory.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
package com.oracle.truffle.js.scriptengine;
4242

4343
import java.lang.ref.WeakReference;
44+
import java.util.Collections;
4445
import java.util.List;
4546
import java.util.Objects;
4647

@@ -61,6 +62,16 @@ public final class GraalJSEngineFactory implements ScriptEngineFactory {
6162
private static final List<String> MIME_TYPES = List.of("application/javascript", "application/ecmascript", "text/javascript", "text/ecmascript");
6263
private static final List<String> EXTENSIONS = List.of("js", "mjs");
6364

65+
private static final boolean JS_AVAILABLE;
66+
private static final String PLACEHOLDER_NAME = "placeholder";
67+
private static final String PLACEHOLDER_VERSION = "1";
68+
69+
static {
70+
try (Engine engine = Engine.newBuilder().useSystemProperties(false).build()) {
71+
JS_AVAILABLE = engine.getLanguages().containsKey("js");
72+
}
73+
}
74+
6475
private WeakReference<Engine> defaultEngine;
6576
private final Engine userDefinedEngine;
6677

@@ -95,7 +106,7 @@ public Engine getPolyglotEngine() {
95106

96107
@Override
97108
public String getEngineName() {
98-
return ENGINE_NAME;
109+
return JS_AVAILABLE ? ENGINE_NAME : PLACEHOLDER_NAME;
99110
}
100111

101112
@Override
@@ -105,27 +116,27 @@ public String getEngineVersion() {
105116

106117
@Override
107118
public List<String> getExtensions() {
108-
return EXTENSIONS;
119+
return JS_AVAILABLE ? EXTENSIONS : Collections.emptyList();
109120
}
110121

111122
@Override
112123
public List<String> getMimeTypes() {
113-
return MIME_TYPES;
124+
return JS_AVAILABLE ? MIME_TYPES : Collections.emptyList();
114125
}
115126

116127
@Override
117128
public List<String> getNames() {
118-
return NAMES;
129+
return JS_AVAILABLE ? NAMES : Collections.singletonList(NAME);
119130
}
120131

121132
@Override
122133
public String getLanguageName() {
123-
return LANGUAGE;
134+
return JS_AVAILABLE ? LANGUAGE : PLACEHOLDER_NAME;
124135
}
125136

126137
@Override
127138
public String getLanguageVersion() {
128-
return LANGUAGE_VERSION;
139+
return JS_AVAILABLE ? LANGUAGE_VERSION : PLACEHOLDER_VERSION;
129140
}
130141

131142
@Override
@@ -147,8 +158,8 @@ public Object getParameter(String key) {
147158
}
148159

149160
@Override
150-
public GraalJSScriptEngine getScriptEngine() {
151-
return new GraalJSScriptEngine(this);
161+
public ScriptEngine getScriptEngine() {
162+
return JS_AVAILABLE ? new GraalJSScriptEngine(this) : new PlaceholderScriptEngine(this);
152163
}
153164

154165
@Override
@@ -173,7 +184,7 @@ public String getMethodCallSyntax(final String obj, final String method, final S
173184

174185
@Override
175186
public String getOutputStatement(final String toDisplay) {
176-
return "print(" + toDisplay + ")";
187+
return "print(" + toDisplay + ")" + (JS_AVAILABLE ? "" : ";");
177188
}
178189

179190
@Override
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.truffle.js.scriptengine;
42+
43+
import java.io.IOException;
44+
import java.io.Reader;
45+
import java.lang.reflect.InvocationTargetException;
46+
import java.lang.reflect.Method;
47+
import java.util.Objects;
48+
import javax.script.AbstractScriptEngine;
49+
import javax.script.Bindings;
50+
import javax.script.ScriptContext;
51+
import javax.script.ScriptEngineFactory;
52+
import javax.script.ScriptException;
53+
import javax.script.SimpleBindings;
54+
55+
/**
56+
* ScriptEngine implementation returned by {@code GraalJSEngineFactory} when JavaScript support is
57+
* not installed. Unfortunately, there is no JavaDoc-compliant way for {@code ScriptEngineFactory}
58+
* to claim that it is not able to provide {@code ScriptEngine} (because of a missing dependency).
59+
* Hence, we provide this minimal compliant implementation instead. An encounter of this engine is a
60+
* sign of a misconfiguration. Users should not see this engine when JavaScript support is installed
61+
* properly.
62+
*/
63+
public final class PlaceholderScriptEngine extends AbstractScriptEngine {
64+
private static final String OUTPUT_PREFIX = "print(";
65+
66+
private final GraalJSEngineFactory factory;
67+
68+
PlaceholderScriptEngine(GraalJSEngineFactory factory) {
69+
this.factory = factory;
70+
setBindings(new PlaceholderBindings(), ScriptContext.ENGINE_SCOPE);
71+
}
72+
73+
@Override
74+
public void setBindings(Bindings bindings, int scope) {
75+
// engine bindings use global bindings as a fallback
76+
if (scope == ScriptContext.ENGINE_SCOPE) {
77+
if (bindings instanceof PlaceholderBindings) {
78+
((PlaceholderBindings) bindings).setGlobalBindings(getBindings(ScriptContext.GLOBAL_SCOPE));
79+
}
80+
} else if (scope == ScriptContext.GLOBAL_SCOPE) {
81+
Bindings engineBindings = getBindings(ScriptContext.ENGINE_SCOPE);
82+
if (engineBindings instanceof PlaceholderBindings) {
83+
((PlaceholderBindings) engineBindings).setGlobalBindings(bindings);
84+
}
85+
}
86+
super.setBindings(bindings, scope);
87+
}
88+
89+
@Override
90+
public Object eval(String script, ScriptContext ctx) throws ScriptException {
91+
Objects.requireNonNull(ctx);
92+
for (String statement : script.split(";")) {
93+
if (statement.isEmpty()) {
94+
continue;
95+
}
96+
if (!statement.endsWith(")")) {
97+
throw error();
98+
}
99+
if (statement.startsWith(OUTPUT_PREFIX)) {
100+
try {
101+
String output = statement.substring(OUTPUT_PREFIX.length(), statement.length() - 1);
102+
ctx.getWriter().write(output);
103+
} catch (IOException ioex) {
104+
throw new ScriptException(ioex);
105+
}
106+
} else {
107+
int dot = statement.indexOf('.');
108+
if (dot == -1) {
109+
throw error();
110+
}
111+
int argsStart = statement.indexOf('(', dot);
112+
if (argsStart == -1) {
113+
throw error();
114+
}
115+
116+
String objectName = statement.substring(0, dot);
117+
String methodName = statement.substring(dot + 1, argsStart);
118+
String argsString = statement.substring(argsStart + 1, statement.length() - 1);
119+
String[] argNames = argsString.isEmpty() ? new String[0] : argsString.split(",");
120+
121+
Bindings bindings = ctx.getBindings(ScriptContext.ENGINE_SCOPE);
122+
Object object = bindings.get(objectName);
123+
if (object == null) {
124+
throw error();
125+
}
126+
127+
Object[] args = new Object[argNames.length];
128+
Class<?>[] argClasses = new Class<?>[argNames.length];
129+
for (int i = 0; i < argNames.length; i++) {
130+
Object arg = bindings.get(argNames[i]);
131+
if (arg == null) {
132+
throw error();
133+
}
134+
argClasses[i] = arg.getClass();
135+
args[i] = arg;
136+
}
137+
138+
try {
139+
Method method = object.getClass().getMethod(methodName, argClasses);
140+
return method.invoke(object, args);
141+
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
142+
throw error();
143+
}
144+
}
145+
}
146+
return null;
147+
}
148+
149+
private static ScriptException error() {
150+
return new ScriptException("JavaScript implementation not found!");
151+
}
152+
153+
@Override
154+
public Object eval(Reader reader, ScriptContext ctx) throws ScriptException {
155+
return eval(GraalJSScriptEngine.read(reader), ctx);
156+
}
157+
158+
@Override
159+
public Bindings createBindings() {
160+
return new PlaceholderBindings();
161+
}
162+
163+
@Override
164+
public ScriptEngineFactory getFactory() {
165+
return factory;
166+
}
167+
168+
private static final class PlaceholderBindings extends SimpleBindings {
169+
170+
private Bindings globalBindings;
171+
172+
void setGlobalBindings(Bindings bindings) {
173+
this.globalBindings = bindings;
174+
}
175+
176+
@Override
177+
public Object get(Object key) {
178+
return (globalBindings == null || containsKey(key)) ? super.get(key) : globalBindings.get(key);
179+
}
180+
181+
}
182+
183+
}

0 commit comments

Comments
 (0)