- Native methods are now changed to non-native thunks via bytecode rewriting, as part of other rewrites. - JsniInjector no longer performs this function at the source level. - JsniInjector still parses out the actual method bodies and changes them into class-level annotations. - This change is en route to do away with source code rewriting altogether. Patch by: tobyr (+me minor cleanup) Review by: me git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2652 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java index 9dfc01e..2359e09 100644 --- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java +++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -33,12 +33,15 @@ import org.apache.commons.collections.map.ReferenceIdentityMap; import org.apache.commons.collections.map.ReferenceMap; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -341,12 +344,43 @@ private static final Class<?>[] BRIDGE_CLASSES = new Class<?>[] { ShellJavaScriptHost.class, JsniMethods.class, JsniMethod.class}; + private static final boolean CLASS_DUMP = false; + + private static final String CLASS_DUMP_PATH = "rewritten-classes"; + static { for (Class<?> c : BRIDGE_CLASSES) { BRIDGE_CLASS_NAMES.put(c.getName(), c); } } + private static void classDump(String name, byte[] bytes) { + String packageName, className; + int pos = name.lastIndexOf('.'); + if (pos < 0) { + packageName = ""; + className = name; + } else { + packageName = name.substring(0, pos); + className = name.substring(pos + 1); + } + + File dir = new File(CLASS_DUMP_PATH + File.separator + + packageName.replace('.', File.separatorChar)); + if (!dir.exists()) { + dir.mkdirs(); + } + + File file = new File(dir, className + ".class"); + try { + FileOutputStream fileOutput = new FileOutputStream(file); + fileOutput.write(bytes); + fileOutput.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + private final HostedModeClassRewriter classRewriter; private final ByteCodeCompiler compiler; @@ -554,7 +588,13 @@ } classBytes = compiler.getClassBytes(logger, lookupClassName); if (classRewriter != null) { - classBytes = classRewriter.rewrite(className, classBytes); + byte[] newBytes = classRewriter.rewrite(className, classBytes); + if (CLASS_DUMP) { + if (!Arrays.equals(classBytes, newBytes)) { + classDump(className, classBytes); + } + } + classBytes = newBytes; } } Class<?> newClass = defineClass(className, classBytes, 0,
diff --git a/dev/core/src/com/google/gwt/dev/shell/JsniInjector.java b/dev/core/src/com/google/gwt/dev/shell/JsniInjector.java index bd1c3dd..34d2917 100644 --- a/dev/core/src/com/google/gwt/dev/shell/JsniInjector.java +++ b/dev/core/src/com/google/gwt/dev/shell/JsniInjector.java
@@ -21,8 +21,6 @@ import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.JParameter; -import com.google.gwt.core.ext.typeinfo.JPrimitiveType; -import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.dev.jdt.CompilationUnitProviderWithAlternateSource; import com.google.gwt.dev.js.ast.JsBlock; @@ -84,8 +82,6 @@ private final Map<JClassType, List<JsniMethod>> jsniMethodMap = new IdentityHashMap<JClassType, List<JsniMethod>>(); private final TypeOracle oracle; - private final Map<JMethod, JsBlock> parsedJsByMethod = new IdentityHashMap<JMethod, JsBlock>(); - public JsniInjector(TypeOracle oracle) { this.oracle = oracle; } @@ -135,8 +131,45 @@ } } - private JsniMethod createJsniMethod(JMethod method, final String file, - char[] source) { + private void collectJsniMethods(TreeLogger logger, char[] source, + JClassType type) throws UnableToCompleteException { + + // Locate the nearest non-local type; don't try to annotate local types. + JClassType targetType = type; + while (targetType.isLocalType()) { + targetType = targetType.getEnclosingType(); + } + List<JsniMethod> jsniMethods = jsniMethodMap.get(targetType); + String loc = type.getCompilationUnit().getLocation(); + + for (JMethod method : type.getMethods()) { + if (!method.isNative()) { + continue; + } + Jsni.Interval interval = Jsni.findJsniSource(method); + if (interval == null) { + String msg = "No JavaScript body found for native method '" + method + + "' in type '" + type + "'"; + logger.log(TreeLogger.ERROR, msg, null); + throw new UnableToCompleteException(); + } + // Parse it. + String js = String.valueOf(source, interval.start, interval.end + - interval.start); + int startLine = Jsni.countNewlines(source, 0, interval.start) + 1; + JsBlock body = Jsni.parseAsFunctionBody(logger, js, loc, startLine); + + // Add JsniMethod annotations to the target type. + if (jsniMethods == null) { + jsniMethods = new ArrayList<JsniMethod>(); + jsniMethodMap.put(targetType, jsniMethods); + } + jsniMethods.add(createJsniMethod(method, body, loc, source)); + } + } + + private JsniMethod createJsniMethod(JMethod method, JsBlock jsniBody, + final String file, char[] source) { final int line = Jsni.countNewlines(source, 0, method.getBodyStart()) + 1; @@ -154,8 +187,6 @@ * itself will print curly braces, so we don't need them around the * try/catch. */ - JsBlock jsniBody = parsedJsByMethod.get(method); - assert (jsniBody != null); String jsTry = "try "; String jsCatch = " catch (e) {\n __static[\"@" + Jsni.JAVASCRIPTHOST_NAME + "::exceptionCaught(Ljava/lang/Object;)\"](e == null ? null : e);\n" @@ -248,206 +279,26 @@ return sb.toString().toCharArray(); } - /** - * Create a legal Java method call that will result in a JSNI invocation. - * - * @param method - * @param expectedHeaderLines - * @param expectedBodyLines - * @param prettyPrint true if the output should be prettier - * @return a String of the Java code to call a JSNI method, using - * JavaScriptHost.invokeNative* - */ - private String genNonNativeVersionOfJsniMethod(JMethod method, - int expectedHeaderLines, int expectedBodyLines, boolean pretty) { - StringBuffer sb = new StringBuffer(); - String nl = pretty ? "\n " : ""; - - // Add extra lines at the start to match comments + declaration - if (!pretty) { - for (int i = 0; i < expectedHeaderLines; ++i) { - sb.append('\n'); - } - } - - String methodDecl = method.getReadableDeclaration(false, true, false, - false, false); - - sb.append(methodDecl + " {" + nl); - // wrap the call in a try-catch block - sb.append("try {" + nl); - - // Write the Java call to the property invoke method, adding - // downcasts where necessary. - JType returnType = method.getReturnType(); - JPrimitiveType primType = returnType.isPrimitive(); - if (primType != null) { - // Primitives have special overloads. - char[] primTypeSuffix = primType.getSimpleSourceName().toCharArray(); - primTypeSuffix[0] = Character.toUpperCase(primTypeSuffix[0]); - String invokeMethodName = "invokeNative" + String.valueOf(primTypeSuffix); - if (primType != JPrimitiveType.VOID) { - sb.append("return "); - } - sb.append(Jsni.JAVASCRIPTHOST_NAME); - sb.append("."); - sb.append(invokeMethodName); - } else { - // Some reference type. - // We need to add a downcast to the originally-declared type. - String returnTypeName = returnType.getParameterizedQualifiedSourceName(); - sb.append("return ("); - sb.append(returnTypeName); - sb.append(")"); - sb.append(Jsni.JAVASCRIPTHOST_NAME); - sb.append(".invokeNativeObject"); - } - - // Write the argument list for the invoke call. - sb.append("(\"@"); - String jsniSig = Jsni.getJsniSignature(method); - sb.append(jsniSig); - if (method.isStatic()) { - sb.append("\", null, "); - } else { - sb.append("\", this, "); - } - - // Build an array of classes that tells the invoker how to adapt the - // incoming arguments for calling into JavaScript. - sb.append(Jsni.buildTypeList(method)); - sb.append(','); - - // Build an array containing the arguments based on the names of the - // parameters. - sb.append(Jsni.buildArgList(method)); - sb.append(");" + nl); - - // Catch exceptions; rethrow if the exception is RTE or declared. - sb.append("} catch (java.lang.Throwable __gwt_exception) {" + nl); - sb.append("if (__gwt_exception instanceof java.lang.RuntimeException) throw (java.lang.RuntimeException) __gwt_exception;" - + nl); - sb.append("if (__gwt_exception instanceof java.lang.Error) throw (java.lang.Error) __gwt_exception;" - + nl); - JType[] throwTypes = method.getThrows(); - for (int i = 0; i < throwTypes.length; ++i) { - String typeName = throwTypes[i].getQualifiedSourceName(); - sb.append("if (__gwt_exception instanceof " + typeName + ") throw (" - + typeName + ") __gwt_exception;" + nl); - } - sb.append("throw new java.lang.RuntimeException(\"Undeclared checked exception thrown out of JavaScript; web mode behavior may differ.\", __gwt_exception);" - + nl); - sb.append("}" + nl); - - sb.append("}" + nl); - - // Add extra lines at the end to match JSNI body. - if (!pretty) { - for (int i = 0; i < expectedBodyLines; ++i) { - sb.append('\n'); - } - } - - return sb.toString(); - } - private void rewriteCompilationUnit(TreeLogger logger, char[] source, List<Replacement> changes, CompilationUnitProvider cup, boolean pretty) throws UnableToCompleteException { - // First create replacements for all native methods. + // Collect all JSNI methods in the compilation unit. JClassType[] types = oracle.getTypesInCompilationUnit(cup); for (JClassType type : types) { if (!type.getQualifiedSourceName().startsWith("java.")) { - rewriteType(logger, source, changes, type, pretty); + collectJsniMethods(logger, source, type); } } - // Then annotate the appropriate types with JsniMethod annotations. + // Annotate the appropriate types with JsniMethod annotations. for (JClassType type : types) { List<JsniMethod> jsniMethods = jsniMethodMap.get(type); - if (jsniMethods != null) { + if (jsniMethods != null && jsniMethods.size() > 0) { char[] annotation = genJsniMethodsAnnotation(jsniMethods, pretty); int declStart = type.getDeclStart(); changes.add(new Replacement(declStart, declStart, annotation)); } } } - - private void rewriteType(TreeLogger logger, char[] source, - List<Replacement> changes, JClassType type, boolean pretty) - throws UnableToCompleteException { - - String loc = type.getCompilationUnit().getLocation(); - - // Examine each method for JSNIness. - List<JMethod> patchedMethods = new ArrayList<JMethod>(); - JMethod[] methods = type.getMethods(); - for (int i = 0; i < methods.length; i++) { - JMethod method = methods[i]; - if (method.isNative()) { - Jsni.Interval interval = Jsni.findJsniSource(method); - if (interval != null) { - // The method itself needs to be replaced. - - // Parse it. - String js = String.valueOf(source, interval.start, interval.end - - interval.start); - int startLine = Jsni.countNewlines(source, 0, interval.start) + 1; - JsBlock body = Jsni.parseAsFunctionBody(logger, js, loc, startLine); - - // Remember this as being a valid JSNI method. - parsedJsByMethod.put(method, body); - - // Replace the method. - final int declStart = method.getDeclStart(); - final int declEnd = method.getDeclEnd(); - - int expectedHeaderLines = Jsni.countNewlines(source, declStart, - interval.start); - int expectedBodyLines = Jsni.countNewlines(source, interval.start, - interval.end); - String newDecl = genNonNativeVersionOfJsniMethod(method, - expectedHeaderLines, expectedBodyLines, pretty); - - final char[] newSource = newDecl.toCharArray(); - changes.add(new Replacement(declStart, declEnd, newSource)); - patchedMethods.add(method); - } else { - // report error - String msg = "No JavaScript body found for native method '" + method - + "' in type '" + type + "'"; - logger.log(TreeLogger.ERROR, msg, null); - throw new UnableToCompleteException(); - } - } - } - - if (!patchedMethods.isEmpty()) { - JMethod[] patched = new JMethod[patchedMethods.size()]; - patched = patchedMethods.toArray(patched); - - TreeLogger branch = logger.branch(TreeLogger.SPAM, "Patched methods in '" - + type.getQualifiedSourceName() + "'", null); - - for (int i = 0; i < patched.length; i++) { - branch.log(TreeLogger.SPAM, patched[i].getReadableDeclaration(), null); - } - - // Locate the nearest non-local type. - while (type.isLocalType()) { - type = type.getEnclosingType(); - } - - // Add JsniMethod infos to the nearest non-inner type for each method. - List<JsniMethod> jsniMethods = jsniMethodMap.get(type); - if (jsniMethods == null) { - jsniMethods = new ArrayList<JsniMethod>(); - jsniMethodMap.put(type, jsniMethods); - } - for (JMethod m : patched) { - jsniMethods.add(createJsniMethod(m, loc, source)); - } - } - } }
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java index 46cba56..0530026 100644 --- a/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java +++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
@@ -19,10 +19,8 @@ import com.google.gwt.dev.asm.ClassVisitor; import com.google.gwt.dev.asm.ClassWriter; import com.google.gwt.dev.asm.Opcodes; -import com.google.gwt.dev.asm.util.TraceClassVisitor; import com.google.gwt.dev.shell.JsValueGlue; -import java.io.PrintWriter; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -33,6 +31,8 @@ * This class performs any and all byte code rewriting needed to make hosted * mode work. Currently, it performs the following rewrites: * <ol> + * <li>Rewrites all native methods into non-native thunks to call JSNI via + * {@link com.google.gwt.dev.shell.JavaScriptHost}.</li> * <li>Rewrites all JSO types into an interface type (which retains the * original name) and an implementation type (which has a $ appended).</li> * <li>All JSO interface types are empty and mirror the original type @@ -46,6 +46,7 @@ * instantiable type.</li> * </ol> * + * @see RewriteJsniMethods * @see RewriteRefsToJsoClasses * @see WriteJsoInterface * @see WriteJsoImpl @@ -80,13 +81,13 @@ String findOriginalDeclaringClass(String declaredClass, String signature); } - static final String REFERENCE_FIELD = JsValueGlue.HOSTED_MODE_REFERENCE; - static final String JAVASCRIPTOBJECT_DESC = JsValueGlue.JSO_CLASS.replace( '.', '/'); static final String JAVASCRIPTOBJECT_IMPL_DESC = JsValueGlue.JSO_IMPL_CLASS.replace( - '.', '/');; + '.', '/'); + + static final String REFERENCE_FIELD = JsValueGlue.HOSTED_MODE_REFERENCE;; static String addSyntheticThisParam(String owner, String methodDescriptor) { return "(L" + owner + ";" + methodDescriptor.substring(1); @@ -109,16 +110,16 @@ private final Set<String> jsoIntfDescs; /** - * Maps methods to the class in which they are declared. - */ - private InstanceMethodOracle mapper; - - /** * Records the superclass of every JSO for generating empty JSO interfaces. */ private final Map<String, String> jsoSuperDescs; /** + * Maps methods to the class in which they are declared. + */ + private InstanceMethodOracle mapper; + + /** * Creates a new {@link HostedModeClassRewriter} for a specified set of * subclasses of JavaScriptObject. * @@ -184,6 +185,8 @@ v = new WriteJsoImpl(v, jsoIntfDescs, mapper); } + v = new RewriteJsniMethods(v); + new ClassReader(classBytes).accept(v, 0); return writer.toByteArray(); }
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java new file mode 100644 index 0000000..475a7d7 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java
@@ -0,0 +1,365 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed 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 com.google.gwt.dev.shell.rewrite; + +import com.google.gwt.dev.asm.ClassAdapter; +import com.google.gwt.dev.asm.ClassVisitor; +import com.google.gwt.dev.asm.MethodVisitor; +import com.google.gwt.dev.asm.Opcodes; +import com.google.gwt.dev.asm.Type; +import com.google.gwt.dev.asm.commons.GeneratorAdapter; +import com.google.gwt.dev.asm.commons.Method; +import com.google.gwt.dev.shell.JavaScriptHost; + +import java.lang.reflect.Modifier; + +/** + * Turns native method declarations into normal Java functions which perform the + * corresponding JSNI dispatch. + */ +public class RewriteJsniMethods extends ClassAdapter { + + /** + * Fast way to look up boxing methods. + */ + private static class Boxing { + + /** + * A matching list of boxed types for each primitive type. + */ + private static final Type[] BOXED_TYPES = new Type[] { + VOID_TYPE, BOOLEAN_TYPE, CHARACTER_TYPE, BYTE_TYPE, SHORT_TYPE, + INTEGER_TYPE, FLOAT_TYPE, LONG_TYPE, DOUBLE_TYPE,}; + + /** + * The list of primitive types. + */ + private static final Type[] PRIMITIVE_TYPES = new Type[] { + Type.VOID_TYPE, Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, + Type.SHORT_TYPE, Type.INT_TYPE, Type.FLOAT_TYPE, Type.LONG_TYPE, + Type.DOUBLE_TYPE,}; + + /** + * A map of Type.sort() to valueOf Method. There are 11 possible results + * from Type.sort(), 0 through 10. + */ + private static final Method[] SORT_MAP = new Method[11]; + + static { + assert PRIMITIVE_TYPES.length == BOXED_TYPES.length; + for (int i = 0; i < PRIMITIVE_TYPES.length; ++i) { + Type primitive = PRIMITIVE_TYPES[i]; + Type boxed = BOXED_TYPES[i]; + if (boxed != null) { + SORT_MAP[i] = new Method("valueOf", boxed, new Type[] {primitive}); + } + } + } + + public static Method getBoxMethod(Type type) { + int sortType = type.getSort(); + assert (sortType >= 0 && sortType < SORT_MAP.length) : "Unexpected JavaScriptHostInfo.get index - " + + sortType; + return SORT_MAP[sortType]; + } + } + + /** + * Oracle for info regarding {@link JavaScriptHost}. + */ + private static class JavaScriptHostInfo { + + /** + * The {@link JavaScriptHost} type. + */ + public static final Type TYPE = Type.getType(JavaScriptHost.class); + + /** + * The parameter signature of {@link JavaScriptHost}'s invoke* methods, in + * classes. + */ + private static final Class<?>[] INVOKE_NATIVE_PARAM_CLASSES = new Class[] { + String.class, Object.class, Class[].class, Object[].class,}; + + /** + * The parameter signature of {@link JavaScriptHost}'s invoke* methods, in + * types. + */ + private static final Type[] INVOKE_NATIVE_PARAM_TYPES; + + /** + * A map of Type.sort() to JavaScriptHostInfo. + */ + private static final JavaScriptHostInfo[] SORT_MAP = new JavaScriptHostInfo[11]; + + static { + INVOKE_NATIVE_PARAM_TYPES = new Type[INVOKE_NATIVE_PARAM_CLASSES.length]; + for (int i = 0; i < INVOKE_NATIVE_PARAM_TYPES.length; ++i) { + INVOKE_NATIVE_PARAM_TYPES[i] = Type.getType(INVOKE_NATIVE_PARAM_CLASSES[i]); + } + + Class<?>[] primitives = { + void.class, boolean.class, byte.class, char.class, short.class, + int.class, long.class, float.class, double.class}; + for (Class<?> c : primitives) { + Type type = Type.getType(c); + String typeName = type.getClassName(); + typeName = Character.toUpperCase(typeName.charAt(0)) + + typeName.substring(1); + SORT_MAP[type.getSort()] = new JavaScriptHostInfo(type, "invokeNative" + + typeName); + } + JavaScriptHostInfo objectType = new JavaScriptHostInfo( + Type.getType(Object.class), "invokeNativeObject", true); + SORT_MAP[Type.ARRAY] = objectType; + SORT_MAP[Type.OBJECT] = objectType; + assert noNulls(SORT_MAP) : "Did not fully fill in JavaScriptHostInfo.SORT_MAP"; + } + + public static JavaScriptHostInfo get(int sortType) { + assert (sortType >= 0 && sortType < SORT_MAP.length) : "Unexpected JavaScriptHostInfo.get index - " + + sortType; + return SORT_MAP[sortType]; + } + + /** + * Validate our model against the real JavaScriptHost class. + */ + private static boolean matchesRealMethod(String methodName, Type returnType) { + try { + java.lang.reflect.Method method = JavaScriptHost.class.getDeclaredMethod( + methodName, INVOKE_NATIVE_PARAM_CLASSES); + assert (method.getModifiers() & Modifier.STATIC) != 0 : "Was expecting method '" + + method + "' to be static"; + Type realReturnType = Type.getType(method.getReturnType()); + return realReturnType.getDescriptor().equals(returnType.getDescriptor()); + } catch (SecurityException e) { + } catch (NoSuchMethodException e) { + } + return false; + } + + private static boolean noNulls(Object[] array) { + for (Object element : array) { + if (element == null) { + return false; + } + } + return true; + } + + private final Method method; + + private final boolean requiresCast; + + private JavaScriptHostInfo(Type returnType, String methodName) { + this(returnType, methodName, false); + } + + private JavaScriptHostInfo(Type returnType, String methodName, + boolean requiresCast) { + this.requiresCast = requiresCast; + this.method = new Method(methodName, returnType, + INVOKE_NATIVE_PARAM_TYPES); + assert matchesRealMethod(methodName, returnType) : "JavaScriptHostInfo for '" + + this + "' does not match real method"; + } + + public Method getMethod() { + return method; + } + + public boolean requiresCast() { + return requiresCast; + } + + @Override + public String toString() { + return method.toString(); + } + } + + /** + * Rewrites native Java methods to dispatch as JSNI. + */ + private class MyMethodAdapter extends GeneratorAdapter { + + private String descriptor; + private boolean isStatic; + private String name; + + public MyMethodAdapter(MethodVisitor mv, int access, String name, + String desc) { + super(mv, access, name, desc); + this.descriptor = desc; + this.name = name; + isStatic = (access & Opcodes.ACC_STATIC) != 0; + } + + /** + * Replacement for {@link GeneratorAdapter#box(Type)}, which always calls, + * for example, {@code new Boolean} instead of using + * {@link Boolean#valueOf(boolean)}. + */ + @Override + public void box(Type type) { + Method method = Boxing.getBoxMethod(type); + if (method != null) { + invokeStatic(method.getReturnType(), method); + } + } + + /** + * Does all of the work necessary to do the dispatch to the appropriate + * variant of + * {@link JavaScriptHost#invokeNativeVoid JavaScriptHost.invokeNative*}. + * And example output: + * + * <pre> + * return JavaScriptHost.invokeNativeInt( + * "@com.google.gwt.sample.hello.client.Hello::echo(I)", null, + * new Class[] {int.class,}, new Object[] {x,}); + * </pre> + */ + public void visitCode() { + super.visitCode(); + + // First argument - JSNI signature + String jsniTarget = getJsniSignature(name, descriptor); + visitLdcInsn(jsniTarget); + // Stack is at 1 + + // Second argument - target; "null" if static, otherwise "this". + if (isStatic) { + visitInsn(Opcodes.ACONST_NULL); + } else { + loadThis(); + } + // Stack is at 2 + + // Third argument - a Class[] describing the types of this method + loadClassArray(); + // Stack is at 3; reaches 6 internally + + // Fourth argument - all the arguments boxed into an Object[] + loadArgArray(); + // Stack is at 4; reaches 7 or 8 internally (long/double takes 2) + + // Invoke the matching JavaScriptHost.invokeNative* method + Type returnType = Type.getReturnType(descriptor); + JavaScriptHostInfo info = JavaScriptHostInfo.get(returnType.getSort()); + invokeStatic(JavaScriptHostInfo.TYPE, info.getMethod()); + // Stack is at 1 + if (info.requiresCast()) { + checkCast(returnType); + } + returnValue(); + // Stack is at 0 + } + + @Override + public void visitEnd() { + // Force code to be visited; required since this was a native method. + visitCode(); + + /* + * For speed, we don't ask ASM to COMPUTE_MAXS. We manually calculated a + * max depth of 8. + */ + int maxStack = 8; + int maxLocals = 0; // Computed by GeneratorAdapter superclass. + super.visitMaxs(maxStack, maxLocals); + super.visitEnd(); + } + + private void loadClassArray() { + Type[] argTypes = Type.getArgumentTypes(descriptor); + push(argTypes.length); + newArray(CLASS_TYPE); + // Stack is at 3 + for (int i = 0; i < argTypes.length; ++i) { + dup(); + push(i); + push(argTypes[i]); + arrayStore(CLASS_TYPE); + } + } + } + + private static final Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean"); + private static final Type BYTE_TYPE = Type.getObjectType("java/lang/Byte"); + private static final Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character"); + private static final Type CLASS_TYPE = Type.getObjectType("java/lang/Class"); + private static final Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double"); + private static final Type FLOAT_TYPE = Type.getObjectType("java/lang/Float"); + private static final Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer"); + private static final Type LONG_TYPE = Type.getObjectType("java/lang/Long"); + private static final Type SHORT_TYPE = Type.getObjectType("java/lang/Short"); + private static final Type VOID_TYPE = Type.getObjectType("java/lang/Void"); + + /** + * The name of the class we're operating on. + */ + private String classDesc; + + public RewriteJsniMethods(ClassVisitor v) { + super(v); + } + + @Override + public void visit(final int version, final int access, final String name, + final String signature, final String superName, final String[] interfaces) { + this.classDesc = name; + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + + boolean isNative = (access & Opcodes.ACC_NATIVE) != 0; + access &= ~Opcodes.ACC_NATIVE; + + MethodVisitor mv = super.visitMethod(access, name, desc, signature, + exceptions); + + if (isNative) { + mv = new MyMethodAdapter(mv, access, name, desc); + } + + return mv; + } + + /** + * Returns the JSNI signature describing the method. + * + * @param name the name of the method; for example {@code "echo"} + * @param descriptor the descriptor for the method; for example {@code "(I)I"} + * @return the JSNI signature for the method; for example, + * {@code "@com.google.gwt.sample.hello.client.Hello::echo(I)"} + */ + private String getJsniSignature(String name, String descriptor) { + int argsIndexBegin = descriptor.indexOf('('); + int argsIndexEnd = descriptor.indexOf(')'); + assert argsIndexBegin != -1 && argsIndexEnd != -1 + && argsIndexBegin < argsIndexEnd : "Could not find the arguments in the descriptor, " + + descriptor; + String argsDescriptor = descriptor.substring(argsIndexBegin, + argsIndexEnd + 1); + String sourceName = classDesc.replace('/', '.').replace('$', '.'); + return "@" + sourceName + "::" + name + argsDescriptor; + } +}