This is an automated email from the ASF dual-hosted git repository. jtulach pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-netbeans-html4j.git
The following commit(s) were added to refs/heads/master by this push: new 5de8d00 Make sure Java Object methods aren't exposed 5de8d00 is described below commit 5de8d00308ae2ea4c05574f079920f58912961cc Author: Jaroslav Tulach <jaroslav.tul...@apidesign.org> AuthorDate: Sun Dec 16 15:17:42 2018 +0100 Make sure Java Object methods aren't exposed --- .../netbeans/html/boot/fx/AbstractFXPresenter.java | 32 +-- .../net/java/html/boot/script/ScriptPresenter.java | 214 ++++++++++++++------- .../net/java/html/boot/script/impl/Callback.java | 24 +++ .../main/java/net/java/html/js/tests/Bodies.java | 25 ++- .../net/java/html/js/tests/JavaScriptBodyTest.java | 153 ++++++++++++++- 5 files changed, 350 insertions(+), 98 deletions(-) diff --git a/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java b/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java index d9752df..965d337 100644 --- a/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java +++ b/boot-fx/src/main/java/org/netbeans/html/boot/fx/AbstractFXPresenter.java @@ -22,11 +22,9 @@ import java.io.BufferedReader; import java.io.Closeable; import java.io.IOException; import java.io.Reader; -import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Array; import java.net.URL; -import java.net.URLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -192,16 +190,6 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { abstract WebView findView(final URL resource); - final JSObject convertArrays(Object[] arr) { - for (int i = 0; i < arr.length; i++) { - if (arr[i] instanceof Object[]) { - arr[i] = convertArrays((Object[]) arr[i]); - } - } - final JSObject wrapArr = (JSObject)wrapArrFn().call("array", arr); // NOI18N - return wrapArr; - } - private final JavaValues values() { if (values == null) { values = new JavaValues(); @@ -231,9 +219,11 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { newPOJOImpl = (JSObject) defineJSFn( "var k = {};\n" + "k.fxBrwsrId = function(hash, id) {\n" + - " return {\n" + - " 'fxBrwsrId' : function(callback) { callback.hashAndId(hash, id); }\n" + - " }\n" + + " var obj = {};\n" + + " Object.defineProperty(obj, 'fxBrwsrId', {\n" + + " value : function(callback) { callback.hashAndId(hash, id) }\n" + + " });\n" + + " return obj;\n" + "};\n" + "return k;\n", new String[] { "callback" }, null ).invokeImpl(null, false); @@ -336,8 +326,8 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { if (value instanceof Enum) { return value; } - int len = isArray(value); - if (len >= 0) { + if (value.getClass().isArray()) { + int len = Array.getLength(value); Object[] copy = new Object[len]; for (int i = 0; i < len; i++) { copy[i] = toJavaScript(Array.get(value, i)); @@ -439,14 +429,6 @@ Fn.KeepAlive, Fn.ToJavaScript, Fn.FromJavaScript, Executor, Cloneable { } } - protected int isArray(Object value) { - try { - return Array.getLength(value); - } catch (IllegalArgumentException ex) { - return -1; - } - } - private interface Ref extends Comparable<Ref> { Object value(); int id(); diff --git a/boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java b/boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java index e004f46..af9b2ec 100644 --- a/boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java +++ b/boot-script/src/main/java/net/java/html/boot/script/ScriptPresenter.java @@ -23,9 +23,12 @@ import java.io.IOException; import java.io.ObjectOutput; import java.io.Reader; import java.lang.ref.WeakReference; +import java.lang.reflect.Array; import java.net.URL; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; @@ -33,18 +36,19 @@ import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import net.java.html.boot.script.impl.Callback; import org.netbeans.html.boot.spi.Fn; import org.netbeans.html.boot.spi.Fn.Presenter; /** Implementation of {@link Presenter} that delegates * to Java {@link ScriptEngine scripting} API. The presenter runs headless * without appropriate simulation of browser APIs. Its primary usefulness - * is inside testing environments. + * is inside testing environments. * <p> - * One can load in browser simulation for example from + * One can load in browser simulation for example from * <a href="http://www.envjs.com/">env.js</a>. The best way to achieve so, * is to wait until JDK-8046013 gets fixed.... - * + * * * @author Jaroslav Tulach */ @@ -65,6 +69,8 @@ Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { private final ScriptEngine eng; private final Executor exc; private final Object undefined; + private final Set<Class<?>> jsReady; + private final CallbackImpl callback; ScriptPresenter(Executor exc) { this(new ScriptEngineManager().getEngineByName("javascript"), exc); @@ -87,6 +93,8 @@ Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { } catch (ScriptException ex) { throw new IllegalStateException(ex); } + this.jsReady = new HashSet<>(); + this.callback = new CallbackImpl(); } @Override @@ -97,7 +105,7 @@ Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { @Override public Fn defineFn(String code, String[] names, boolean[] keepAlive) { return defineImpl(code, names, keepAlive); - } + } private FnImpl defineImpl(String code, String[] names, boolean[] keepAlive) { StringBuilder sb = new StringBuilder(); sb.append("(function() {\n"); @@ -137,18 +145,19 @@ Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { public void loadScript(Reader code) throws Exception { eng.eval(code); } - + // // array conversions // - - final Object convertArrays(Object[] arr) throws Exception { - for (int i = 0; i < arr.length; i++) { - if (arr[i] instanceof Object[]) { - arr[i] = convertArrays((Object[]) arr[i]); - } + + private Object convertArrays(Object anyArr) throws Exception { + int len = Array.getLength(anyArr); + Object[] arr = new Object[len]; + for (int i = 0; i < len; i++) { + final Object ith = Array.get(anyArr, i); + arr[i] = toJavaScript(ith, true, true); } - final Object wrapArr = wrapArrFn().invokeImpl(null, false, arr); // NOI18N + final Object wrapArr = wrapArrFn().invokeImpl(null, false, arr); return wrapArr; } @@ -164,7 +173,7 @@ Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { return wrapArrImpl; } - final Object checkArray(Object val) throws Exception { + private Object checkArray(Object val) throws Exception { if (val instanceof Boolean || val instanceof Number || val instanceof String) { return val; } @@ -202,12 +211,70 @@ Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { return arraySize; } + private FnImpl wrapJavaObject; + private FnImpl wrapJavaObject() { + if (wrapJavaObject == null) { + try { + wrapJavaObject = defineImpl("\n" + + "var obj = {};\n" + + "Object.defineProperty(obj, 'javaObj', {\n" + + " value : function() { callback.callback(java); }\n" + + "});\n" + + " if (str) {\n" + + " Object.defineProperty(obj, 'toString', {\n" + + " value : function() { return str; }\n" + + " });\n" + + " }\n" + + "return obj;\n" + + "", new String[] { "java", "callback", "str" }, null + ); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + return wrapJavaObject; + } + + private FnImpl extractJavaObject; + private FnImpl extractJavaObject() { + if (extractJavaObject == null) { + try { + extractJavaObject = defineImpl("\n" + + "var fn = obj && obj['javaObj'];\n" + + "if (typeof fn === 'function') {\n" + + " fn();\n" + + "};\n" + + "", new String[] { "obj" }, null + ); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + return extractJavaObject; + } + @Override public Object toJava(Object toJS) { + if (toJS == undefined || toJS == null) { + return null; + } + if (toJS instanceof String || toJS instanceof Number || toJS instanceof Boolean || toJS instanceof Character) { + return toJS; + } + jsReady.add(toJS.getClass()); + try { + callback.last = this; + extractJavaObject().invokeImpl(null, false, toJS); + if (callback.last != this) { + toJS = callback.last; + } + } catch (Exception ex) { + throw new IllegalStateException(ex); + } if (toJS instanceof Weak) { toJS = ((Weak)toJS).get(); } - if (toJS == undefined) { + if (toJS == undefined || toJS == null) { return null; } try { @@ -216,12 +283,19 @@ Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { throw new IllegalStateException(ex); } } - + @Override public Object toJavaScript(Object toReturn) { - if (toReturn instanceof Object[]) { + return toJavaScript(toReturn, true, true); + } + + final Object toJavaScript(Object toReturn, boolean arrayChecks, boolean keepAlive) { + if (toReturn == null || !arrayChecks) { + return toReturn; + } + if (toReturn.getClass().isArray()) { try { - return convertArrays((Object[])toReturn); + return convertArrays(toReturn); } catch (Exception ex) { throw new IllegalStateException(ex); } @@ -231,7 +305,30 @@ Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { return ((Boolean)toReturn) ? true : null; } } - return toReturn; + if (toReturn.getClass().getSimpleName().equals("$JsCallbacks$")) { // NOI18N + return toReturn; + } + if (toReturn instanceof Character) { + return (int) (Character) toReturn; + } + if ( + toReturn instanceof Boolean || toReturn instanceof String || + toReturn instanceof Number + ) { + return toReturn; + } + if (isJSReady(toReturn)) { + return toReturn; + } + if (!keepAlive) { + toReturn = new Weak(toReturn); + } + String name = toReturn instanceof Enum ? toReturn.toString() : null; + try { + return wrapJavaObject().invokeImpl(null, false, toReturn, callback, name); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } } } @@ -241,8 +338,9 @@ Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { command.run(); return; } - + class Wrap implements Runnable { + @Override public void run() { try (Closeable c = Fn.activate(ScriptPresenter.this)) { command.run(); @@ -275,43 +373,30 @@ Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { return invokeImpl(thiz, true, args); } - final Object invokeImpl(Object thiz, boolean arrayChecks, Object... args) throws Exception { - List<Object> all = new ArrayList<>(args.length + 1); - all.add(thiz == null ? fn : thiz); - for (int i = 0; i < args.length; i++) { - Object conv = args[i]; - if (arrayChecks) { - if (args[i] instanceof Object[]) { - Object[] arr = (Object[]) args[i]; - conv = ((ScriptPresenter) presenter()).convertArrays(arr); - } - if (conv != null && keepAlive != null - && !keepAlive[i] && !isJSReady(conv) - && !conv.getClass().getSimpleName().equals("$JsCallbacks$") // NOI18N - ) { - conv = new Weak(conv); - } - if (conv instanceof Character) { - conv = (int)(Character)conv; - } - } - all.add(conv); - } - Object ret = ((Invocable)eng).invokeMethod(fn, "call", all.toArray()); // NOI18N - if (ret instanceof Weak) { - ret = ((Weak)ret).get(); - } - if (ret == fn) { - return null; - } - if (!arrayChecks) { - return ret; - } - return ((ScriptPresenter)presenter()).checkArray(ret); + final Object invokeImpl(Object thiz, boolean arrayChecks, Object... args) throws Exception { + List<Object> all = new ArrayList<>(args.length + 1); + ScriptPresenter sp = (ScriptPresenter) presenter(); + if (thiz == null) { + all.add(fn); + } else { + all.add(sp.toJavaScript(thiz, true, true)); + } + for (int i = 0; i < args.length; i++) { + Object conv = sp.toJavaScript(args[i], arrayChecks, keepAlive == null || keepAlive[i]); + all.add(conv); } + Object ret = ((Invocable)eng).invokeMethod(fn, "call", all.toArray()); // NOI18N + if (ret == fn) { + return null; + } + if (!arrayChecks) { + return ret; + } + return ((ScriptPresenter)presenter()).toJava(ret); + } } - - private static boolean isJSReady(Object obj) { + + private boolean isJSReady(Object obj) { if (obj == null) { return true; } @@ -321,20 +406,12 @@ Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { if (obj instanceof Number) { return true; } - final String cn = obj.getClass().getName(); - if ( - cn.startsWith("com.oracle.truffle") || // NOI18N - cn.startsWith("jdk.nashorn") || // NOI18N - (cn.contains(".mozilla.") && cn.contains(".Native")) // NOI18N - ) { - return true; - } if (obj instanceof Character) { return true; } - return false; - } - + return jsReady.contains(obj.getClass()); + } + private static final class Weak extends WeakReference<Object> { public Weak(Object referent) { super(referent); @@ -424,5 +501,12 @@ Presenter, Fn.FromJavaScript, Fn.ToJavaScript, Executor { return undefined; } } + private static final class CallbackImpl extends Callback { + Object last; + @Override + public void callback(Object obj) { + last = obj; + } + } } diff --git a/boot-script/src/main/java/net/java/html/boot/script/impl/Callback.java b/boot-script/src/main/java/net/java/html/boot/script/impl/Callback.java new file mode 100644 index 0000000..fe7bac1 --- /dev/null +++ b/boot-script/src/main/java/net/java/html/boot/script/impl/Callback.java @@ -0,0 +1,24 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package net.java.html.boot.script.impl; + +public abstract class Callback { + public abstract void callback(Object obj); +} diff --git a/json-tck/src/main/java/net/java/html/js/tests/Bodies.java b/json-tck/src/main/java/net/java/html/js/tests/Bodies.java index 4633133..2a99e2e 100644 --- a/json-tck/src/main/java/net/java/html/js/tests/Bodies.java +++ b/json-tck/src/main/java/net/java/html/js/tests/Bodies.java @@ -165,14 +165,15 @@ final class Bodies { ) static native int incAsync(); - @JavaScriptBody(args = { "arr" }, body = + @JavaScriptBody(args = { "obj" }, body = "var ret = [];\n" + - "for (var i in arr) {\n" + - " ret.push(arr[i]);\n" + + "for (var i in obj) {\n" + + " ret.push(i);\n" + + " ret.push(obj[i]);\n" + "}\n" + "return ret;\n" ) - static native Object[] forIn(Object[] in); + static native Object[] forIn(Object obj); @JavaScriptBody(args = { "max" }, body = "var arr = [];\n" @@ -242,4 +243,20 @@ final class Bodies { " } \n" + "}"; } + + @JavaScriptBody(args = { "o", "n" }, body = "return o[n];") + static native Object get(Object o, String n); + + @JavaScriptBody(args = { "o", "n", "arg" }, body = "\n" + + "try {\n" + + " if (arg == null) {\n" + + " return o[n]();\n" + + " } else {\n" + + " return o[n](arg);\n" + + " }\n" + + "} catch (e) {\n" + + " return n;\n" + + "}\n" + ) + static native Object invoke(Object o, String n, Object arg); } diff --git a/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java b/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java index 338a9e8..247d8ae 100644 --- a/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java +++ b/json-tck/src/main/java/net/java/html/js/tests/JavaScriptBodyTest.java @@ -19,6 +19,9 @@ package net.java.html.js.tests; import java.io.StringReader; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.Callable; import net.java.html.json.Models; import org.netbeans.html.boot.spi.Fn; @@ -130,6 +133,22 @@ public class JavaScriptBodyTest { assertEquals("number", doubleType, "Expecting number type: " + doubleType); } + private static void assertNoProp(Object obj, String name, Object arg) { + Object prop = Bodies.get(obj, name); + assertNull(prop, "Expecting no value for property " + name + ", but was " + Bodies.typeof(prop, false)); + + try { + Object res = Bodies.invoke(obj, name, arg); + if (name.equals(res)) { + return; + } + fail("Invoking " + name + " on " + obj + " returned " + res); + } catch (Exception e) { + e.printStackTrace(); + } + + } + enum Two { ONE, TWO; } @@ -332,11 +351,11 @@ public class JavaScriptBodyTest { @KOTest public void iterateArray() { String[] arr = { "Ahoj", "Hi", "Ciao" }; Object[] ret = Bodies.forIn(arr); - assertEquals(ret.length, 3, "Three elements returned: " + ret.length); + assertEquals(ret.length, 6, "Three elements returned: " + ret.length); assertNotEquals(ret, arr, "Different arrays"); - assertEquals(ret[0], "Ahoj", "Expecting Ahoj: " + ret[0]); - assertEquals(ret[1], "Hi", "Expecting Hi: " + ret[1]); - assertEquals(ret[2], "Ciao", "Expecting Ciao: " + ret[2]); + assertEquals(ret[1], "Ahoj", "Expecting Ahoj: " + ret[0]); + assertEquals(ret[3], "Hi", "Expecting Hi: " + ret[1]); + assertEquals(ret[5], "Ciao", "Expecting Ciao: " + ret[2]); } @KOTest public void primitiveTypes() { @@ -379,6 +398,132 @@ public class JavaScriptBodyTest { assertEquals(nullAndUnknown, 1, "Only one slot"); } + @KOTest + public void exposedPropertiesOfAJavaObject() { + Sum s = new Sum(); + Object[] props = Bodies.forIn(s); + + Set<Object> all = new HashSet<>(Arrays.asList(props)); + assertEquals(0, all.size(), "No own properties: " + all); + } + + + @KOTest + public void exposedEqualsOfAJavaObject() { + Sum s = new Sum(); + assertNoProp(s, "equals", s); + } + + @KOTest + public void exposedHashCodeOfAJavaObject() { + Sum s = new Sum(); + + assertNoProp(s, "hashCode", null); + } + + @KOTest + public void exposedWaitOfAJavaObject() { + Sum s = new Sum(); + + assertNoProp(s, "wait", null); + } + + @KOTest + public void exposedGetClassOfAJavaObject() { + Sum s = new Sum(); + + assertNoProp(s, "getClass", null); + } + + @KOTest + public void exposedNotifyOfAJavaObject() { + Sum s = new Sum(); + + assertNoProp(s, "notify", null); + } + + @KOTest + public void exposedNotifyAllOfAJavaObject() { + Sum s = new Sum(); + + assertNoProp(s, "notifyAll", null); + } + + @KOTest + public void exposedEqualsOfAJavaArray() { + Object s = new Object[5]; + assertNoProp(s, "equals", s); + } + + @KOTest + public void exposedHashCodeOfAJavaArray() { + Object s = new Object[5]; + + assertNoProp(s, "hashCode", null); + } + + @KOTest + public void exposedWaitOfAJavaArray() { + Object s = new Object[5]; + + assertNoProp(s, "wait", null); + } + + @KOTest + public void exposedGetClassOfAJavaArray() { + Object s = new Object[5]; + + assertNoProp(s, "getClass", null); + } + + @KOTest + public void exposedNotifyOfAJavaArray() { + Object s = new Object[5]; + + assertNoProp(s, "notify", null); + } + + @KOTest + public void exposedNotifyAllOfAJavaArray() { + Object s = new Object[5]; + + assertNoProp(s, "notifyAll", null); + } + + @KOTest + public void exposedEqualsOfAJavaPrimitiveArray() { + Object s = new int[5]; + assertNoProp(s, "equals", s); + } + + @KOTest + public void exposedHashCodeOfAJavaPrimitiveArray() { + Object s = new int[5]; + + assertNoProp(s, "hashCode", null); + } + + @KOTest + public void exposedWaitOfAJavaPrimitiveArray() { + Object s = new int[5]; + + assertNoProp(s, "wait", null); + } + + @KOTest + public void exposedGetClassOfAJavaPrimitiveArray() { + Object s = new int[5]; + + assertNoProp(s, "getClass", null); + } + + @KOTest + public void exposedNotifyOfAJavaPrimitiveArray() { + Object s = new int[5]; + + assertNoProp(s, "notify", null); + } + @KOTest public void problematicString() { String orig = Bodies.problematicString(); String js = Bodies.problematicCallback(); --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists