1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/rt/emul/compact/src/test/java/org/apidesign/vm4brwsr/dynamic/InvokeDynamic.java Sat Aug 09 12:15:18 2014 +0200
1.3 @@ -0,0 +1,46 @@
1.4 +/**
1.5 + * Back 2 Browser Bytecode Translator
1.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
1.7 + *
1.8 + * This program is free software: you can redistribute it and/or modify
1.9 + * it under the terms of the GNU General Public License as published by
1.10 + * the Free Software Foundation, version 2 of the License.
1.11 + *
1.12 + * This program is distributed in the hope that it will be useful,
1.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.15 + * GNU General Public License for more details.
1.16 + *
1.17 + * You should have received a copy of the GNU General Public License
1.18 + * along with this program. Look for COPYING file in the top folder.
1.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
1.20 + */
1.21 +package org.apidesign.vm4brwsr.dynamic;
1.22 +
1.23 +import java.lang.invoke.CallSite;
1.24 +import java.lang.invoke.ConstantCallSite;
1.25 +import java.lang.invoke.MethodHandles;
1.26 +import java.lang.invoke.MethodType;
1.27 +
1.28 +public class InvokeDynamic {
1.29 +
1.30 + public static String dynamicSay() {
1.31 + return TEST_dynamic_boot1(new InvokeDynamic());
1.32 + }
1.33 +
1.34 + private static String TEST_dynamic_boot1(InvokeDynamic instance) {
1.35 + throw new IllegalStateException("Can't touch this");
1.36 + }
1.37 +
1.38 + public static CallSite boot1(MethodHandles.Lookup lookup, String name, MethodType type) {
1.39 + try {
1.40 + return new ConstantCallSite(lookup.findVirtual(InvokeDynamic.class, "instance_sayHello", MethodType.methodType(String.class)));
1.41 + } catch (NoSuchMethodException | IllegalAccessException e) {
1.42 + throw new IllegalStateException(e);
1.43 + }
1.44 + }
1.45 +
1.46 + public String instance_sayHello() {
1.47 + return "Hello from Dynamic!";
1.48 + }
1.49 +}
1.50 \ No newline at end of file
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/rt/emul/compact/src/test/java/org/apidesign/vm4brwsr/dynamic/InvokeDynamicTest.java Sat Aug 09 12:15:18 2014 +0200
2.3 @@ -0,0 +1,175 @@
2.4 +/**
2.5 + * Back 2 Browser Bytecode Translator
2.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
2.7 + *
2.8 + * This program is free software: you can redistribute it and/or modify
2.9 + * it under the terms of the GNU General Public License as published by
2.10 + * the Free Software Foundation, version 2 of the License.
2.11 + *
2.12 + * This program is distributed in the hope that it will be useful,
2.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2.15 + * GNU General Public License for more details.
2.16 + *
2.17 + * You should have received a copy of the GNU General Public License
2.18 + * along with this program. Look for COPYING file in the top folder.
2.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
2.20 + */
2.21 +package org.apidesign.vm4brwsr.dynamic;
2.22 +
2.23 +import java.io.ByteArrayInputStream;
2.24 +import java.io.IOException;
2.25 +import java.io.InputStream;
2.26 +import java.lang.invoke.CallSite;
2.27 +import java.lang.invoke.MethodHandles;
2.28 +import java.lang.invoke.MethodType;
2.29 +import java.lang.reflect.Method;
2.30 +import java.net.URL;
2.31 +import java.util.Enumeration;
2.32 +import org.apidesign.vm4brwsr.Bck2Brwsr;
2.33 +import org.objectweb.asm.ClassReader;
2.34 +import org.objectweb.asm.ClassVisitor;
2.35 +import org.objectweb.asm.ClassWriter;
2.36 +import org.objectweb.asm.Handle;
2.37 +import org.objectweb.asm.MethodVisitor;
2.38 +import org.objectweb.asm.Opcodes;
2.39 +import static org.objectweb.asm.Opcodes.ASM4;
2.40 +import static org.objectweb.asm.Opcodes.INVOKESTATIC;
2.41 +import static org.testng.Assert.*;
2.42 +import org.testng.annotations.AfterClass;
2.43 +import org.testng.annotations.BeforeClass;
2.44 +import org.testng.annotations.Test;
2.45 +
2.46 +/**
2.47 + *
2.48 + * @author Jaroslav Tulach <jtulach@netbeans.org>
2.49 + */
2.50 +public class InvokeDynamicTest {
2.51 + private static Class<?> invokeDynamicClass;
2.52 + private static byte[] invokeDynamicBytes;
2.53 + private static TestVM code;
2.54 +
2.55 + @Test public void simpleDynamicInJava() throws Exception {
2.56 + Method m = invokeDynamicClass.getMethod("dynamicSay");
2.57 + Object ret = m.invoke(m);
2.58 + assertEquals(ret, "Hello from Dynamic!");
2.59 + }
2.60 +
2.61 + @Test public void simpleDynamicInJS() throws Exception {
2.62 + code.assertExec(
2.63 + "Invoke dynamic can return a value", InvokeDynamic.class,
2.64 + "dynamic__Ljava_lang_String_2",
2.65 + "Hello from Dynamic!"
2.66 + );
2.67 + }
2.68 +
2.69 +
2.70 + @AfterClass
2.71 + public static void releaseTheCode() {
2.72 + code = null;
2.73 + }
2.74 +
2.75 + //
2.76 + // the following code is inspired by
2.77 + // https://code.google.com/p/indy-maven-plugin/
2.78 + // which I don't want to use, as it is not in a public repository
2.79 + //
2.80 + @BeforeClass
2.81 + public static void prepareClass() throws Exception {
2.82 + InputStream is = InvokeDynamic.class.getResourceAsStream("InvokeDynamic.class");
2.83 + assertNotNull(is, "Class found");
2.84 +
2.85 + ClassReader reader = new ClassReader(is);
2.86 + ClassWriter writer = new ClassWriter(reader, 0);
2.87 +
2.88 + reader.accept(
2.89 + new ClassVisitor(ASM4, writer) {
2.90 + @Override
2.91 + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions
2.92 + ) {
2.93 + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
2.94 + return new InvokeDynamicProcessor(mv);
2.95 + }
2.96 + },
2.97 + 0);
2.98 + is.close();
2.99 + invokeDynamicBytes = writer.toByteArray();
2.100 + ClassLoader l = new ClassLoader() {
2.101 + @Override
2.102 + public Class<?> loadClass(String name) throws ClassNotFoundException {
2.103 + if (name.equals(InvokeDynamic.class.getName())) {
2.104 + return defineClass(name, invokeDynamicBytes, 0, invokeDynamicBytes.length);
2.105 + }
2.106 + return super.loadClass(name);
2.107 + }
2.108 + };
2.109 + invokeDynamicClass = l.loadClass(InvokeDynamic.class.getName());
2.110 +
2.111 + code = TestVM.compileClass(
2.112 + null, null, new EmulationResourcesWithException(),
2.113 + InvokeDynamic.class.getName().replace('.', '/')
2.114 + );
2.115 + }
2.116 +
2.117 +
2.118 + private static class InvokeDynamicProcessor extends MethodVisitor {
2.119 + InvokeDynamicProcessor(MethodVisitor mv) {
2.120 + super(ASM4, mv);
2.121 + }
2.122 +
2.123 + @Override
2.124 + public void visitMethodInsn(int opcode, String owner, String name, String desc) {
2.125 + if (opcode == INVOKESTATIC) {
2.126 + if (name.startsWith("TEST_dynamic_")) {
2.127 + final String shortName = name.substring(13);
2.128 + Handle mh = new Handle(
2.129 + Opcodes.H_INVOKESTATIC, owner, shortName,
2.130 + MethodType.methodType(
2.131 + CallSite.class,
2.132 + MethodHandles.Lookup.class,
2.133 + String.class,
2.134 + MethodType.class
2.135 + ).toMethodDescriptorString()
2.136 + );
2.137 + super.visitInvokeDynamicInsn(shortName, desc, mh);
2.138 + return;
2.139 + }
2.140 + }
2.141 + super.visitMethodInsn(opcode, owner, name, desc);
2.142 + }
2.143 + }
2.144 +
2.145 + private static class EmulationResourcesWithException implements Bck2Brwsr.Resources {
2.146 + @Override
2.147 + public InputStream get(String name) throws IOException {
2.148 + if ("org/apidesign/vm4brwsr/InvokeDynamic.class".equals(name)) {
2.149 + return new ByteArrayInputStream(invokeDynamicBytes);
2.150 + }
2.151 + if ("java/net/URI.class".equals(name)) {
2.152 + // skip
2.153 + return null;
2.154 + }
2.155 + if ("java/net/URLConnection.class".equals(name)) {
2.156 + // skip
2.157 + return null;
2.158 + }
2.159 + if ("java/lang/System.class".equals(name)) {
2.160 + // skip
2.161 + return null;
2.162 + }
2.163 + Enumeration<URL> en = InvokeDynamicTest.class.getClassLoader().getResources(name);
2.164 + URL u = null;
2.165 + while (en.hasMoreElements()) {
2.166 + u = en.nextElement();
2.167 + }
2.168 + if (u == null) {
2.169 + throw new IOException("Can't find " + name);
2.170 + }
2.171 + if (u.toExternalForm().contains("rt.jar!")) {
2.172 + throw new IOException("No emulation for " + u);
2.173 + }
2.174 + return u.openStream();
2.175 + }
2.176 + }
2.177 +
2.178 +}
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/rt/emul/compact/src/test/java/org/apidesign/vm4brwsr/dynamic/TestVM.java Sat Aug 09 12:15:18 2014 +0200
3.3 @@ -0,0 +1,297 @@
3.4 +/**
3.5 + * Back 2 Browser Bytecode Translator
3.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
3.7 + *
3.8 + * This program is free software: you can redistribute it and/or modify
3.9 + * it under the terms of the GNU General Public License as published by
3.10 + * the Free Software Foundation, version 2 of the License.
3.11 + *
3.12 + * This program is distributed in the hope that it will be useful,
3.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
3.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3.15 + * GNU General Public License for more details.
3.16 + *
3.17 + * You should have received a copy of the GNU General Public License
3.18 + * along with this program. Look for COPYING file in the top folder.
3.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
3.20 + */
3.21 +package org.apidesign.vm4brwsr.dynamic;
3.22 +
3.23 +import java.io.ByteArrayInputStream;
3.24 +import java.io.File;
3.25 +import java.io.FileWriter;
3.26 +import java.io.IOException;
3.27 +import java.io.InputStream;
3.28 +import java.net.URL;
3.29 +import java.util.ArrayList;
3.30 +import java.util.Arrays;
3.31 +import java.util.Enumeration;
3.32 +import java.util.HashSet;
3.33 +import java.util.List;
3.34 +import java.util.Set;
3.35 +import javax.script.Invocable;
3.36 +import javax.script.ScriptContext;
3.37 +import javax.script.ScriptEngine;
3.38 +import javax.script.ScriptEngineManager;
3.39 +import javax.script.ScriptException;
3.40 +import org.apidesign.vm4brwsr.Bck2Brwsr;
3.41 +import org.apidesign.vm4brwsr.ObfuscationLevel;
3.42 +import static org.testng.Assert.*;
3.43 +
3.44 +public final class TestVM {
3.45 + private final Invocable code;
3.46 + private final CharSequence codeSeq;
3.47 + private final Object bck2brwsr;
3.48 +
3.49 +
3.50 + private TestVM(Invocable code, CharSequence codeSeq) throws ScriptException, NoSuchMethodException {
3.51 + this.code = code;
3.52 + this.codeSeq = codeSeq;
3.53 + this.bck2brwsr = ((ScriptEngine)code).eval("bck2brwsr(function(n) { return loader.get(n); })");
3.54 + ((ScriptEngine)code).getContext().setAttribute("loader", this, ScriptContext.ENGINE_SCOPE);
3.55 + }
3.56 +
3.57 + public Object execCode(
3.58 + String msg, Class<?> clazz, String method,
3.59 + Object expRes, Object... args
3.60 + ) throws Exception {
3.61 + Object ret = null;
3.62 + try {
3.63 + ret = code.invokeMethod(bck2brwsr, "loadClass", clazz.getName());
3.64 + List<Object> ma = new ArrayList<>();
3.65 + ma.add(method);
3.66 + ma.addAll(Arrays.asList(args));
3.67 + ret = code.invokeMethod(ret, "invoke", ma.toArray());
3.68 + } catch (ScriptException ex) {
3.69 + fail("Execution failed in " + dumpJS(codeSeq) + ": " + ex.getMessage(), ex);
3.70 + } catch (NoSuchMethodException ex) {
3.71 + fail("Cannot find method in " + dumpJS(codeSeq), ex);
3.72 + }
3.73 + if (ret == null && expRes == null) {
3.74 + return null;
3.75 + }
3.76 + if (expRes != null && expRes.equals(ret)) {
3.77 + return null;
3.78 + }
3.79 + if (expRes instanceof Number) {
3.80 + // in case of Long it is necessary convert it to number
3.81 + // since the Long is represented by two numbers in JavaScript
3.82 + try {
3.83 + final Object toFP = ((ScriptEngine)code).eval("Number.prototype.toFP");
3.84 + if (ret instanceof Long) {
3.85 + ret = code.invokeMethod(toFP, "call", ret);
3.86 + }
3.87 + ret = code.invokeFunction("Number", ret);
3.88 + } catch (ScriptException ex) {
3.89 + fail("Conversion to number failed in " + dumpJS(codeSeq) + ": " + ex.getMessage(), ex);
3.90 + } catch (NoSuchMethodException ex) {
3.91 + fail("Cannot find global Number(x) function in " + dumpJS(codeSeq) + ": " + ex.getMessage(), ex);
3.92 + }
3.93 + }
3.94 + return ret;
3.95 + }
3.96 +
3.97 + void assertExec(
3.98 + String msg, Class clazz, String method, Object expRes, Object... args
3.99 + ) throws Exception {
3.100 + Object ret = execCode(msg, clazz, method, expRes, args);
3.101 + if (ret == null) {
3.102 + return;
3.103 + }
3.104 + if (expRes instanceof Integer && ret instanceof Double) {
3.105 + expRes = ((Integer)expRes).doubleValue();
3.106 + }
3.107 + if (expRes != null && expRes.equals(ret)) {
3.108 + return;
3.109 + }
3.110 + assertEquals(ret, expRes, msg + "was: " + ret + "\n" + dumpJS(codeSeq));
3.111 + }
3.112 +
3.113 + static TestVM compileClass(String... names) throws ScriptException, IOException {
3.114 + return compileClass(null, names);
3.115 + }
3.116 +
3.117 + static TestVM compileClass(StringBuilder sb, String... names) throws ScriptException, IOException {
3.118 + return compileClass(sb, null, names);
3.119 + }
3.120 +
3.121 + static TestVM compileClass(StringBuilder sb, ScriptEngine[] eng, String... names) throws ScriptException, IOException {
3.122 + return compileClass(sb, eng, new EmulationResources(), names);
3.123 + }
3.124 + static TestVM compileClass(
3.125 + StringBuilder sb,
3.126 + ScriptEngine[] eng,
3.127 + Bck2Brwsr.Resources resources,
3.128 + String... names
3.129 + ) throws ScriptException, IOException {
3.130 + if (sb == null) {
3.131 + sb = new StringBuilder();
3.132 + }
3.133 + Bck2Brwsr.generate(sb, resources, names);
3.134 + ScriptEngineManager sem = new ScriptEngineManager();
3.135 + ScriptEngine js = sem.getEngineByExtension("js");
3.136 + if (eng != null) {
3.137 + eng[0] = js;
3.138 + }
3.139 + try {
3.140 + Object res = js.eval(sb.toString());
3.141 + assertTrue(js instanceof Invocable, "It is invocable object: " + res);
3.142 + return new TestVM((Invocable) js, sb);
3.143 + } catch (Exception ex) {
3.144 + if (sb.length() > 2000) {
3.145 + sb = dumpJS(sb);
3.146 + }
3.147 + fail("Could not evaluate:" + ex.getClass() + ":" + ex.getMessage() + "\n" + sb, ex);
3.148 + return null;
3.149 + }
3.150 + }
3.151 +
3.152 + static TestVM compileClassAsExtension(
3.153 + StringBuilder sb, ScriptEngine[] eng,
3.154 + String name, final String resourceName, final String resourceContent
3.155 + ) throws ScriptException, IOException {
3.156 + return compileClassesAsExtension(sb, eng, resourceName, resourceContent, name);
3.157 + }
3.158 + static TestVM compileClassesAsExtension(
3.159 + StringBuilder sb, ScriptEngine[] eng,
3.160 + final String resourceName, final String resourceContent, String... names
3.161 + ) throws ScriptException, IOException {
3.162 + if (sb == null) {
3.163 + sb = new StringBuilder();
3.164 + }
3.165 + if (eng[0] == null) {
3.166 + ScriptEngineManager sem = new ScriptEngineManager();
3.167 + ScriptEngine js = sem.getEngineByExtension("js");
3.168 + eng[0] = js;
3.169 + Bck2Brwsr.generate(sb, new EmulationResources());
3.170 + }
3.171 + Set<String> exp = new HashSet<String>();
3.172 + for (String n : names) {
3.173 + int last = n.lastIndexOf('/');
3.174 + exp.add(n.substring(0, last + 1));
3.175 + }
3.176 + Bck2Brwsr b2b = Bck2Brwsr.newCompiler().
3.177 + resources(new EmulationResources() {
3.178 + @Override
3.179 + public InputStream get(String name) throws IOException {
3.180 + if (name.equals(resourceName)) {
3.181 + return new ByteArrayInputStream(resourceContent.getBytes("UTF-8"));
3.182 + }
3.183 + return super.get(name);
3.184 + }
3.185 + }).
3.186 + addClasses(names).
3.187 + addResources("org/apidesign/vm4brwsr/obj.js").
3.188 + addExported(exp.toArray(new String[0])).
3.189 + obfuscation(ObfuscationLevel.FULL).
3.190 + library();
3.191 + if (resourceName != null) {
3.192 + b2b = b2b.addResources(resourceName);
3.193 + }
3.194 + b2b.generate(sb);
3.195 + try {
3.196 + defineAtoB(eng[0]);
3.197 + Object res = eng[0].eval(sb.toString());
3.198 + assertTrue(eng[0] instanceof Invocable, "It is invocable object: " + res);
3.199 + return new TestVM((Invocable) eng[0], sb);
3.200 + } catch (Exception ex) {
3.201 + if (sb.length() > 2000) {
3.202 + sb = dumpJS(sb);
3.203 + }
3.204 + fail("Could not evaluate:" + ex.getClass() + ":" + ex.getMessage() + "\n" + sb, ex);
3.205 + return null;
3.206 + }
3.207 + }
3.208 +
3.209 + static TestVM compileClassAndResources(StringBuilder sb, ScriptEngine[] eng, String name, String... resources) throws ScriptException, IOException {
3.210 + if (sb == null) {
3.211 + sb = new StringBuilder();
3.212 + }
3.213 + Bck2Brwsr b2b = Bck2Brwsr.newCompiler().
3.214 + resources(new EmulationResources()).
3.215 + addRootClasses(name).
3.216 + addResources(resources);
3.217 + b2b.generate(sb);
3.218 + ScriptEngineManager sem = new ScriptEngineManager();
3.219 + ScriptEngine js = sem.getEngineByExtension("js");
3.220 + if (eng != null) {
3.221 + eng[0] = js;
3.222 + }
3.223 + try {
3.224 + defineAtoB(js);
3.225 +
3.226 + Object res = js.eval(sb.toString());
3.227 + assertTrue(js instanceof Invocable, "It is invocable object: " + res);
3.228 + return new TestVM((Invocable) js, sb);
3.229 + } catch (Exception ex) {
3.230 + if (sb.length() > 2000) {
3.231 + sb = dumpJS(sb);
3.232 + }
3.233 + fail("Could not evaluate:" + ex.getClass() + ":" + ex.getMessage() + "\n" + sb, ex);
3.234 + return null;
3.235 + }
3.236 + }
3.237 +
3.238 + private static void defineAtoB(ScriptEngine js) throws ScriptException {
3.239 + js.eval("atob = function(s) { return new String(org.apidesign.vm4brwsr.ResourcesTest.parseBase64Binary(s)); }");
3.240 + }
3.241 +
3.242 + Object invokeMethod(Object obj, String method, Object... params) throws ScriptException, NoSuchMethodException {
3.243 + return code.invokeMethod(obj, method, params);
3.244 + }
3.245 +
3.246 + Object invokeFunction(String methodName, Object... args) throws ScriptException, NoSuchMethodException {
3.247 + return code.invokeFunction(methodName, args);
3.248 + }
3.249 +
3.250 + static StringBuilder dumpJS(CharSequence sb) throws IOException {
3.251 + File f = File.createTempFile("execution", ".js");
3.252 + FileWriter w = new FileWriter(f);
3.253 + w.append(sb);
3.254 + w.close();
3.255 + return new StringBuilder(f.getPath());
3.256 + }
3.257 +
3.258 + @Override
3.259 + public String toString() {
3.260 + try {
3.261 + return dumpJS(codeSeq).toString();
3.262 + } catch (IOException ex) {
3.263 + return ex.toString();
3.264 + }
3.265 + }
3.266 +
3.267 + final CharSequence codeSeq() {
3.268 + return codeSeq;
3.269 + }
3.270 +
3.271 + private static class EmulationResources implements Bck2Brwsr.Resources {
3.272 + @Override
3.273 + public InputStream get(String name) throws IOException {
3.274 + if ("java/net/URI.class".equals(name)) {
3.275 + // skip
3.276 + return null;
3.277 + }
3.278 + if ("java/net/URLConnection.class".equals(name)) {
3.279 + // skip
3.280 + return null;
3.281 + }
3.282 + if ("java/lang/System.class".equals(name)) {
3.283 + // skip
3.284 + return null;
3.285 + }
3.286 + Enumeration<URL> en = TestVM.class.getClassLoader().getResources(name);
3.287 + URL u = null;
3.288 + while (en.hasMoreElements()) {
3.289 + u = en.nextElement();
3.290 + }
3.291 + if (u == null) {
3.292 + throw new IOException("Can't find " + name);
3.293 + }
3.294 + if (u.toExternalForm().contains("rt.jar!")) {
3.295 + throw new IOException("No emulation for " + u);
3.296 + }
3.297 + return u.openStream();
3.298 + }
3.299 + }
3.300 +}