# HG changeset patch # User Jaroslav Tulach # Date 1403819657 -7200 # Node ID 4b09a4b689a49160a48287879104fa8692c5d191 # Parent a84f511654ae5ab03b28cdb6c7d32af16f703bc2 Can parse JDK8 generated bytecode diff -r a84f511654ae -r 4b09a4b689a4 rt/pom.xml --- a/rt/pom.xml Thu Jun 26 07:42:54 2014 +0200 +++ b/rt/pom.xml Thu Jun 26 23:54:17 2014 +0200 @@ -19,4 +19,14 @@ vmtest aot + + + + 1.8 + + + vm8 + + + diff -r a84f511654ae -r 4b09a4b689a4 rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java Thu Jun 26 07:42:54 2014 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeParser.java Thu Jun 26 23:54:17 2014 +0200 @@ -58,6 +58,9 @@ public static final int CONSTANT_METHOD = 10; public static final int CONSTANT_INTERFACEMETHOD = 11; public static final int CONSTANT_NAMEANDTYPE = 12; + public static final int CONSTANT_METHODHANDLE = 15; + public static final int CONSTANT_METHODTYPE = 16; + public static final int CONSTANT_INVOKEDYNAMIC = 18; /* Access Flags */ public static final int ACC_PUBLIC = 0x00000001; @@ -668,7 +671,17 @@ case CONSTANT_NAMEANDTYPE: cpool[i] = new CPX2(in.readUnsignedShort(), in.readUnsignedShort()); break; - + case CONSTANT_METHODHANDLE: + in.readByte(); + in.readUnsignedShort(); + break; + case CONSTANT_METHODTYPE: + in.readUnsignedShort(); + break; + case CONSTANT_INVOKEDYNAMIC: + in.readUnsignedShort(); + in.readUnsignedShort(); + break; case 0: default: throw new ClassFormatError("invalid constant type: " + (int) tags[i]); diff -r a84f511654ae -r 4b09a4b689a4 rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java --- a/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Thu Jun 26 07:42:54 2014 +0200 +++ b/rt/vm/src/main/java/org/apidesign/vm4brwsr/ByteCodeToJavaScript.java Thu Jun 26 23:54:17 2014 +0200 @@ -19,7 +19,6 @@ import java.io.IOException; import java.io.InputStream; -import java.util.Locale; import static org.apidesign.vm4brwsr.ByteCodeParser.*; /** Translator of the code inside class files to JavaScript. @@ -1558,7 +1557,7 @@ sb.append(ch); } else { sb.append("_0"); - String hex = Integer.toHexString(ch).toLowerCase(Locale.ENGLISH); + String hex = Integer.toHexString(ch).toLowerCase(); for (int m = hex.length(); m < 4; m++) { sb.append("0"); } diff -r a84f511654ae -r 4b09a4b689a4 rt/vm8/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm8/pom.xml Thu Jun 26 23:54:17 2014 +0200 @@ -0,0 +1,39 @@ + + + 4.0.0 + + org.apidesign.bck2brwsr + rt + 1.0-SNAPSHOT + + vm8 + Bck2Brwsr on JDK8 + jar + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.8 + 1.8 + + + + + + + org.testng + testng + test + + + org.apidesign.bck2brwsr + vm4brwsr + ${project.version} + test + jar + + + \ No newline at end of file diff -r a84f511654ae -r 4b09a4b689a4 rt/vm8/src/test/java/org/apidesign/bck2brwsr/vm8/BytesLoader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm8/src/test/java/org/apidesign/bck2brwsr/vm8/BytesLoader.java Thu Jun 26 23:54:17 2014 +0200 @@ -0,0 +1,69 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.vm8; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +/** + * + * @author Jaroslav Tulach + */ +public final class BytesLoader { + public byte[] get(String name) throws IOException { + byte[] arr = readClass(name); + /* + System.err.print("loader['" + name + "'] = ["); + for (int i = 0; i < arr.length; i++) { + if (i > 0) { + System.err.print(", "); + } + System.err.print(arr[i]); + } + System.err.println("]"); + */ + return arr; + } + + static byte[] readClass(String name) throws IOException { + URL u = null; + Enumeration en = BytesLoader.class.getClassLoader().getResources(name); + while (en.hasMoreElements()) { + u = en.nextElement(); + } + if (u == null) { + throw new IOException("Can't find " + name); + } + try (InputStream is = u.openStream()) { + byte[] arr; + arr = new byte[is.available()]; + int offset = 0; + while (offset < arr.length) { + int len = is.read(arr, offset, arr.length - offset); + if (len == -1) { + throw new IOException("Can't read " + name); + } + offset += len; + } + return arr; + } + } + +} diff -r a84f511654ae -r 4b09a4b689a4 rt/vm8/src/test/java/org/apidesign/bck2brwsr/vm8/Lambdas.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm8/src/test/java/org/apidesign/bck2brwsr/vm8/Lambdas.java Thu Jun 26 23:54:17 2014 +0200 @@ -0,0 +1,36 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.vm8; + +/** + * + * @author Jaroslav Tulach + */ +public class Lambdas { + private static void fewTimes(Runnable r, int cnt) { + while (cnt-- > 0) { + r.run(); + } + } + + public static String compound() { + StringBuilder sb = new StringBuilder(); + fewTimes(() -> sb.append('X'), 10); + return sb.toString(); + } +} diff -r a84f511654ae -r 4b09a4b689a4 rt/vm8/src/test/java/org/apidesign/bck2brwsr/vm8/LambdasTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm8/src/test/java/org/apidesign/bck2brwsr/vm8/LambdasTest.java Thu Jun 26 23:54:17 2014 +0200 @@ -0,0 +1,53 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.vm8; + +import org.testng.annotations.BeforeClass; +import static org.testng.Assert.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public class LambdasTest { + private static TestVM code; + + @Test public void verifyJSTime() throws Exception { + String exp = Lambdas.compound(); + + Object js = code.execCode("Get js time", + Lambdas.class, "compound__Ljava_lang_String_2", + exp + ); + } + + + @BeforeClass + public static void compileTheCode() throws Exception { + code = TestVM.compileClass( + "org/apidesign/bck2brwsr/vm8/Lambdas"); + } + @AfterClass + public static void releaseTheCode() { + code = null; + } + +} + diff -r a84f511654ae -r 4b09a4b689a4 rt/vm8/src/test/java/org/apidesign/bck2brwsr/vm8/TestVM.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/vm8/src/test/java/org/apidesign/bck2brwsr/vm8/TestVM.java Thu Jun 26 23:54:17 2014 +0200 @@ -0,0 +1,310 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.vm8; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.script.Invocable; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import org.apidesign.vm4brwsr.Bck2Brwsr; +import org.apidesign.vm4brwsr.ObfuscationLevel; +import static org.testng.Assert.*; + +public final class TestVM { + private final Invocable code; + private final CharSequence codeSeq; + private final Object bck2brwsr; + private BytesLoader resources; + + + private TestVM(Invocable code, CharSequence codeSeq) throws ScriptException, NoSuchMethodException { + this.code = code; + this.codeSeq = codeSeq; + this.bck2brwsr = ((ScriptEngine)code).eval("bck2brwsr(function(n) { return loader.get(n); })"); + ((ScriptEngine)code).getContext().setAttribute("loader", this, ScriptContext.ENGINE_SCOPE); + } + + public void register(BytesLoader res) { + this.resources = res; + } + + public byte[] get(String res) throws IOException { + return resources != null ? resources.get(res) : null; + } + + public Object execCode( + String msg, Class clazz, String method, + Object expRes, Object... args + ) throws Exception { + Object ret = null; + try { + ret = code.invokeMethod(bck2brwsr, "loadClass", clazz.getName()); + List ma = new ArrayList<>(); + ma.add(method); + ma.addAll(Arrays.asList(args)); + ret = code.invokeMethod(ret, "invoke", ma.toArray()); + } catch (ScriptException ex) { + fail("Execution failed in " + dumpJS(codeSeq) + ": " + ex.getMessage(), ex); + } catch (NoSuchMethodException ex) { + fail("Cannot find method in " + dumpJS(codeSeq), ex); + } + if (ret == null && expRes == null) { + return null; + } + if (expRes != null && expRes.equals(ret)) { + return null; + } + if (expRes instanceof Number) { + // in case of Long it is necessary convert it to number + // since the Long is represented by two numbers in JavaScript + try { + final Object toFP = ((ScriptEngine)code).eval("Number.prototype.toFP"); + if (ret instanceof Long) { + ret = code.invokeMethod(toFP, "call", ret); + } + ret = code.invokeFunction("Number", ret); + } catch (ScriptException ex) { + fail("Conversion to number failed in " + dumpJS(codeSeq) + ": " + ex.getMessage(), ex); + } catch (NoSuchMethodException ex) { + fail("Cannot find global Number(x) function in " + dumpJS(codeSeq) + ": " + ex.getMessage(), ex); + } + } + return ret; + } + + void assertExec( + String msg, Class clazz, String method, Object expRes, Object... args + ) throws Exception { + Object ret = execCode(msg, clazz, method, expRes, args); + if (ret == null) { + return; + } + if (expRes instanceof Integer && ret instanceof Double) { + expRes = ((Integer)expRes).doubleValue(); + } + if (expRes != null && expRes.equals(ret)) { + return; + } + assertEquals(ret, expRes, msg + "was: " + ret + "\n" + dumpJS(codeSeq)); + } + + static TestVM compileClass(String... names) throws ScriptException, IOException { + return compileClass(null, names); + } + + static TestVM compileClass(StringBuilder sb, String... names) throws ScriptException, IOException { + return compileClass(sb, null, names); + } + + static TestVM compileClass(StringBuilder sb, ScriptEngine[] eng, String... names) throws ScriptException, IOException { + if (sb == null) { + sb = new StringBuilder(); + } + Bck2Brwsr.generate(sb, new EmulationResources(), names); + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine js = sem.getEngineByExtension("js"); + if (eng != null) { + eng[0] = js; + } + try { + Object res = js.eval(sb.toString()); + assertTrue(js instanceof Invocable, "It is invocable object: " + res); + return new TestVM((Invocable) js, sb); + } catch (Exception ex) { + if (sb.length() > 2000) { + sb = dumpJS(sb); + } + fail("Could not evaluate:" + ex.getClass() + ":" + ex.getMessage() + "\n" + sb, ex); + return null; + } + } + + static TestVM compileClassAsExtension( + StringBuilder sb, ScriptEngine[] eng, + String name, final String resourceName, final String resourceContent + ) throws ScriptException, IOException { + return compileClassesAsExtension(sb, eng, resourceName, resourceContent, name); + } + static TestVM compileClassesAsExtension( + StringBuilder sb, ScriptEngine[] eng, + final String resourceName, final String resourceContent, String... names + ) throws ScriptException, IOException { + if (sb == null) { + sb = new StringBuilder(); + } + if (eng[0] == null) { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine js = sem.getEngineByExtension("js"); + eng[0] = js; + Bck2Brwsr.generate(sb, new EmulationResources()); + } + Set exp = new HashSet(); + for (String n : names) { + int last = n.lastIndexOf('/'); + exp.add(n.substring(0, last + 1)); + } + Bck2Brwsr b2b = Bck2Brwsr.newCompiler(). + resources(new EmulationResources() { + @Override + public InputStream get(String name) throws IOException { + if (name.equals(resourceName)) { + return new ByteArrayInputStream(resourceContent.getBytes("UTF-8")); + } + return super.get(name); + } + }). + addClasses(names). + addResources("org/apidesign/vm4brwsr/obj.js"). + addExported(exp.toArray(new String[0])). + obfuscation(ObfuscationLevel.FULL). + library(); + if (resourceName != null) { + b2b = b2b.addResources(resourceName); + } + b2b.generate(sb); + try { + defineAtoB(eng[0]); + Object res = eng[0].eval(sb.toString()); + assertTrue(eng[0] instanceof Invocable, "It is invocable object: " + res); + return new TestVM((Invocable) eng[0], sb); + } catch (Exception ex) { + if (sb.length() > 2000) { + sb = dumpJS(sb); + } + fail("Could not evaluate:" + ex.getClass() + ":" + ex.getMessage() + "\n" + sb, ex); + return null; + } + } + + static TestVM compileClassAndResources(StringBuilder sb, ScriptEngine[] eng, String name, String... resources) throws ScriptException, IOException { + if (sb == null) { + sb = new StringBuilder(); + } + Bck2Brwsr b2b = Bck2Brwsr.newCompiler(). + resources(new EmulationResources()). + addRootClasses(name). + addResources(resources); + b2b.generate(sb); + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine js = sem.getEngineByExtension("js"); + if (eng != null) { + eng[0] = js; + } + try { + defineAtoB(js); + + Object res = js.eval(sb.toString()); + assertTrue(js instanceof Invocable, "It is invocable object: " + res); + return new TestVM((Invocable) js, sb); + } catch (Exception ex) { + if (sb.length() > 2000) { + sb = dumpJS(sb); + } + fail("Could not evaluate:" + ex.getClass() + ":" + ex.getMessage() + "\n" + sb, ex); + return null; + } + } + + private static void defineAtoB(ScriptEngine js) throws ScriptException { + js.eval("atob = function(s) { return new String(org.apidesign.vm4brwsr.ResourcesTest.parseBase64Binary(s)); }"); + } + +// Object loadClass(String loadClass, String name) throws ScriptException, NoSuchMethodException { +// return code.invokeMethod(bck2brwsr, "loadClass", Exceptions.class.getName()); +// } + + Object invokeMethod(Object obj, String method, Object... params) throws ScriptException, NoSuchMethodException { + return code.invokeMethod(obj, method, params); + } + + Object invokeFunction(String methodName, Object... args) throws ScriptException, NoSuchMethodException { + return code.invokeFunction(methodName, args); + } + + static StringBuilder dumpJS(CharSequence sb) throws IOException { + File f = File.createTempFile("execution", ".js"); + FileWriter w = new FileWriter(f); + w.append(sb); + w.close(); + return new StringBuilder(f.getPath()); + } + + @Override + public String toString() { + try { + return dumpJS(codeSeq).toString(); + } catch (IOException ex) { + return ex.toString(); + } + } + + final CharSequence codeSeq() { + return codeSeq; + } + + private static class EmulationResources implements Bck2Brwsr.Resources { + @Override + public InputStream get(String name) throws IOException { + if ("java/net/URI.class".equals(name)) { + // skip + return null; + } + if ("java/net/URLConnection.class".equals(name)) { + // skip + return null; + } + if ("java/lang/System.class".equals(name)) { + // skip + return null; + } + if ("java/io/PrintStream.class".equals(name)) { + // skip + return null; + } + if ("java/io/PrintWriter.class".equals(name)) { + // skip + return null; + } + Enumeration en = TestVM.class.getClassLoader().getResources(name); + URL u = null; + while (en.hasMoreElements()) { + u = en.nextElement(); + } + if (u == null) { + throw new IOException("Can't find " + name); + } + if (u.toExternalForm().contains("rt.jar!")) { + throw new IOException("No emulation for " + u); + } + return u.openStream(); + } + } +}