| /* |
| * 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.shell.rewrite.HostedModeClassRewriter.InstanceMethodOracle; |
| import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData; |
| |
| import org.objectweb.asm.ClassVisitor; |
| import org.objectweb.asm.FieldVisitor; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| import org.objectweb.asm.Type; |
| import org.objectweb.asm.commons.Method; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Writes the implementation classes for JSO and its subtypes. |
| * |
| * Changes made by the base class: |
| * <ol> |
| * <li>The new type has the same name as the old type with a '$' appended.</li> |
| * <li>All instance methods in the original type become static methods taking an |
| * explicit <code>this</code> parameter. Such methods have the same stack |
| * behavior as the original.</li> |
| * </ol> |
| */ |
| abstract class WriteJsoImpl extends ClassVisitor { |
| |
| /** |
| * This type implements JavaScriptObject. |
| * |
| * <ol> |
| * <li>JavaScriptObject itself gets a new synthetic field to store the |
| * underlying hosted mode reference.</li> |
| * <li>Instance methods are added so that JavaScriptObject implements all |
| * SingleJsoImpl interfaces.</li> |
| * </ol> |
| * |
| */ |
| private static class ForJsoDollar extends WriteJsoImpl { |
| /** |
| * An unmodifiable set of descriptors containing |
| * <code>JavaScriptObject</code> and all subclasses. |
| */ |
| private final Set<String> jsoDescriptors; |
| private final SingleJsoImplData jsoData; |
| |
| public ForJsoDollar(ClassVisitor cv, Set<String> jsoDescriptors, |
| InstanceMethodOracle mapper, SingleJsoImplData jsoData) { |
| super(cv, mapper); |
| this.jsoDescriptors = jsoDescriptors; |
| this.jsoData = jsoData; |
| } |
| |
| @Override |
| public void visit(int version, int access, String name, String signature, |
| String superName, String[] interfaces) { |
| |
| ArrayList<String> jsoDescList = new ArrayList<String>(); |
| jsoDescList.addAll(jsoDescriptors); |
| interfaces = jsoDescList.toArray(new String[jsoDescList.size()]); |
| |
| super.visit(version, access, name, signature, superName, interfaces); |
| |
| /* |
| * Generate the synthetic "hostedModeReferece" field to contain the |
| * underlying real reference to the JavaScript object. |
| */ |
| FieldVisitor fv = visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, |
| HostedModeClassRewriter.REFERENCE_FIELD, "Ljava/lang/Object;", null, |
| null); |
| if (fv != null) { |
| fv.visitEnd(); |
| } |
| |
| // Implement the trampoline methods |
| for (String mangledName : jsoData.getMangledNames()) { |
| List<Method> declarations = jsoData.getDeclarations(mangledName); |
| List<Method> implementations = jsoData.getImplementations(mangledName); |
| assert declarations.size() == implementations.size() : "Declaration / implementation size mismatch"; |
| |
| Iterator<Method> declIterator = declarations.iterator(); |
| Iterator<Method> implIterator = implementations.iterator(); |
| |
| while (declIterator.hasNext()) { |
| assert implIterator.hasNext(); |
| writeTrampoline(mangledName, declIterator.next(), implIterator.next()); |
| } |
| } |
| } |
| |
| @Override |
| public MethodVisitor visitMethod(int access, String name, String desc, |
| String signature, String[] exceptions) { |
| if (isCtor(name)) { |
| // make the JavaScriptObject$ constructor public |
| access &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED); |
| access |= Opcodes.ACC_PUBLIC; |
| } |
| return super.visitMethod(access, name, desc, signature, exceptions); |
| } |
| |
| /** |
| * JSO methods are implemented as flyweight style, with the instance being |
| * passed as the first parameter. This loop create instance methods on JSO$ |
| * for all of the mangled SingleJsoImpl interface method names. These |
| * instance methods simply turn around and call the static-dispatch methods. |
| * In Java, it might look like: |
| * |
| * <pre> |
| * interface Interface { |
| * String someMethod(int a, double b); |
| * } |
| * |
| * class J extends JSO implements I { |
| * public String com_google_Interface_someMethod(int a, double b) { |
| * return com.google.MyJso$.someMethod$(this, a, b); |
| * } |
| * } |
| * </pre> |
| * |
| * @param mangledName {@code com_google_gwt_sample_hello_client_Interface_a} |
| * @param interfaceMethod {@code java.lang.String a(int, double)} |
| * @param implementingMethod {@code static final java.lang.String |
| * a$(com.google.gwt.sample.hello.client.Jso, ...);} |
| */ |
| private void writeTrampoline(String mangledName, Method interfaceMethod, |
| Method implementingMethod) { |
| assert implementingMethod.getArgumentTypes().length > 0; |
| |
| /* |
| * The local descriptor is the same as the descriptor from the abstract |
| * method in the interface. |
| */ |
| String localDescriptor = interfaceMethod.getDescriptor(); |
| Method localMethod = new Method(mangledName, localDescriptor); |
| |
| /* |
| * We also use the first argument to know which type to statically |
| * dispatch to. |
| */ |
| Type implementingType = Type.getType("L" |
| + implementingMethod.getArgumentTypes()[0].getInternalName() + "$;"); |
| |
| // Maybe create the method. This is marked final as a sanity check |
| MethodVisitor mv = visitMethodNoRewrite(Opcodes.ACC_PUBLIC |
| | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC, localMethod.getName(), |
| localMethod.getDescriptor(), null, null); |
| |
| if (mv != null) { |
| mv.visitCode(); |
| |
| /* |
| * It just so happens that the stack and local variable sizes are the |
| * same, but they're kept distinct to aid in clarity should the dispatch |
| * logic change. |
| */ |
| int var = 0; |
| int size = 0; |
| |
| for (Type t : implementingMethod.getArgumentTypes()) { |
| size += t.getSize(); |
| mv.visitVarInsn(t.getOpcode(Opcodes.ILOAD), var); |
| var += t.getSize(); |
| } |
| |
| // Make sure there's enough room for the return value |
| size = Math.max(size, implementingMethod.getReturnType().getSize()); |
| |
| mv.visitMethodInsn(Opcodes.INVOKESTATIC, |
| implementingType.getInternalName(), implementingMethod.getName(), |
| implementingMethod.getDescriptor(), false); |
| mv.visitInsn(localMethod.getReturnType().getOpcode(Opcodes.IRETURN)); |
| mv.visitMaxs(size, var); |
| mv.visitEnd(); |
| } |
| } |
| } |
| |
| /** |
| * This type is used to implement subtypes of JSO. |
| * |
| * <ol> |
| * <li>The new type's superclass is mangled by adding $.</li> |
| * <li>Constructors are deleted.</li> |
| * </ol> |
| */ |
| private static class ForJsoInterface extends WriteJsoImpl { |
| public ForJsoInterface(ClassVisitor cv, InstanceMethodOracle mapper) { |
| super(cv, mapper); |
| } |
| |
| @Override |
| public void visit(int version, int access, String name, String signature, |
| String superName, String[] interfaces) { |
| // Reference the old superclass's implementation class. |
| superName += '$'; |
| interfaces = null; |
| |
| super.visit(version, access, name, signature, superName, interfaces); |
| } |
| |
| @Override |
| public MethodVisitor visitMethod(int access, String name, String desc, |
| String signature, String[] exceptions) { |
| boolean isCtor = isCtor(name); |
| if (isCtor) { |
| // Don't copy over constructors except for JavaScriptObject itself. |
| return null; |
| } |
| return super.visitMethod(access, name, desc, signature, exceptions); |
| } |
| } |
| |
| /** |
| * Creates a ClassVisitor to implement a JavaScriptObject subtype. This will |
| * select between a simple implementation for user-defined JSO subtypes and |
| * the complex implementation for implementing JavaScriptObject$. |
| */ |
| public static ClassVisitor create(ClassVisitor cv, String classDescriptor, |
| Set<String> jsoDescriptors, InstanceMethodOracle mapper, |
| SingleJsoImplData singleJsoImplData) { |
| |
| if (classDescriptor.equals(HostedModeClassRewriter.JAVASCRIPTOBJECT_IMPL_DESC)) { |
| return new ForJsoDollar(cv, jsoDescriptors, mapper, singleJsoImplData); |
| } else { |
| return new ForJsoInterface(cv, mapper); |
| } |
| } |
| |
| /** |
| * Maps methods to the class in which they are declared. |
| */ |
| private final InstanceMethodOracle mapper; |
| |
| /** |
| * The original name of the class being visited. |
| */ |
| private String originalName; |
| |
| /** |
| * Construct a new rewriter instance. |
| * |
| * @param cv the visitor to chain to |
| * @param jsoDescriptors an unmodifiable set of descriptors containing |
| * <code>JavaScriptObject</code> and all subclasses |
| * @param mapper maps methods to the class in which they are declared |
| */ |
| private WriteJsoImpl(ClassVisitor cv, InstanceMethodOracle mapper) { |
| super(Opcodes.ASM7, cv); |
| this.mapper = mapper; |
| } |
| |
| /** |
| * Records the original name and resets access opcodes. |
| */ |
| @Override |
| public void visit(int version, int access, String name, String signature, |
| String superName, String[] interfaces) { |
| originalName = name; |
| super.visit(version, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC |
| | Opcodes.ACC_SYNTHETIC, name + '$', signature, superName, interfaces); |
| } |
| |
| /** |
| * Mangle all instance methods declared in JavaScriptObject types. |
| */ |
| @Override |
| public MethodVisitor visitMethod(int access, String name, String desc, |
| String signature, String[] exceptions) { |
| boolean isCtor = isCtor(name); |
| if (!isCtor && !isStatic(access) && !isObjectMethod(name + desc)) { |
| access |= Opcodes.ACC_STATIC; |
| desc = HostedModeClassRewriter.addSyntheticThisParam(getOriginalName(), |
| desc); |
| name = name + "$"; |
| } |
| return super.visitMethod(access, name, desc, signature, exceptions); |
| } |
| |
| protected String getOriginalName() { |
| return originalName; |
| } |
| |
| protected boolean isCtor(String name) { |
| return "<init>".equals(name); |
| } |
| |
| protected boolean isObjectMethod(String signature) { |
| return "java/lang/Object".equals(mapper.findOriginalDeclaringClass( |
| originalName, signature)); |
| } |
| |
| protected boolean isStatic(int access) { |
| return (access & Opcodes.ACC_STATIC) != 0; |
| } |
| |
| /** |
| * Allows access to an unmodified visitMethod call. |
| */ |
| protected MethodVisitor visitMethodNoRewrite(int access, String name, |
| String desc, String signature, String[] exceptions) { |
| return super.visitMethod(access, name, desc, signature, exceptions); |
| } |
| } |