| /* |
| * Copyright 2009 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.core.ext.typeinfo.JClassType; |
| import com.google.gwt.core.ext.typeinfo.TypeOracle; |
| import com.google.gwt.dev.asm.ClassAdapter; |
| import com.google.gwt.dev.asm.ClassVisitor; |
| import com.google.gwt.dev.asm.MethodAdapter; |
| 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.Method; |
| import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.SingleJsoImplData; |
| import com.google.gwt.dev.util.collect.Maps; |
| import com.google.gwt.dev.util.collect.Sets; |
| |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| /** |
| * Effects the renaming of {@code @SingleJsoImpl} methods from their original |
| * name to their mangled name. Let us call the original method an "unmangled |
| * method" and the new method a "mangled method". There are three steps in this |
| * process: |
| * <ol> |
| * <li>Within {@code @SingleJsoImpl} interfaces rename all unmangled methods to |
| * become mangled methods.</li> |
| * <li>Within non-JSO classes containing a concrete implementation of an |
| * unmangled method, add a mangled method which is implemented as a simple |
| * trampoline to the unmangled method. (We don't do this in JSO classes here |
| * because the one-and-only trampoline lives in JavaScriptObject$ and is emitted |
| * in {@link WriteJsoImpl}). |
| * <li>Update all call sites targeting unmangled methods to target mangled |
| * methods instead, provided the caller is binding to the interface rather than |
| * a concrete type.</li> |
| * </ol> |
| */ |
| public class RewriteSingleJsoImplDispatches extends ClassAdapter { |
| private class MyMethodVisitor extends MethodAdapter { |
| public MyMethodVisitor(MethodVisitor mv) { |
| super(mv); |
| } |
| |
| /* |
| * Implements objective #3: updates call sites to unmangled methods. |
| */ |
| @Override |
| public void visitMethodInsn(int opcode, String owner, String name, |
| String desc) { |
| if (opcode == Opcodes.INVOKEINTERFACE) { |
| if (jsoData.getSingleJsoIntfTypes().contains(owner)) { |
| // Simple case; referring directly to a SingleJso interface. |
| name = owner.replace('/', '_') + "_" + name; |
| assert jsoData.getMangledNames().contains(name) : "Missing " + name; |
| |
| } else { |
| /* |
| * Might be referring to a subtype of a SingleJso interface: |
| * |
| * interface IA { void foo() } |
| * |
| * interface JA extends JSO implements IA; |
| * |
| * interface IB extends IA {} |
| * |
| * void bar() { ((IB) object).foo(); } |
| */ |
| outer : for (String intf : computeAllInterfaces(owner)) { |
| if (jsoData.getSingleJsoIntfTypes().contains(intf)) { |
| /* |
| * Check that it really should be mangled and is not a reference |
| * to a method defined in a non-singleJso super-interface. If |
| * there are two super-interfaces that define methods with |
| * identical names and descriptors, the choice of implementation |
| * is undefined. |
| */ |
| String maybeMangled = intf.replace('/', '_') + "_" + name; |
| List<Method> methods = jsoData.getImplementations(maybeMangled); |
| if (methods != null) { |
| for (Method method : methods) { |
| /* |
| * Found a method with the right name, but we need to check |
| * the parameters and the return type. In order to do this, |
| * we'll look at the arguments and return type of the target |
| * method, removing the first argument, which is the instance. |
| */ |
| assert method.getArgumentTypes().length >= 1; |
| Type[] argumentTypes = new Type[method.getArgumentTypes().length - 1]; |
| System.arraycopy(method.getArgumentTypes(), 1, argumentTypes, |
| 0, argumentTypes.length); |
| String maybeDescriptor = Type.getMethodDescriptor( |
| method.getReturnType(), argumentTypes); |
| if (maybeDescriptor.equals(desc)) { |
| name = maybeMangled; |
| break outer; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| super.visitMethodInsn(opcode, owner, name, desc); |
| } |
| } |
| |
| private String currentTypeName; |
| private final Set<String> implementedMethods = new HashSet<String>(); |
| private boolean inSingleJsoImplInterfaceType; |
| private Map<String, Set<String>> intfNamesToAllInterfaces = Maps.create(); |
| private final SingleJsoImplData jsoData; |
| private final TypeOracle typeOracle; |
| |
| public RewriteSingleJsoImplDispatches(ClassVisitor v, TypeOracle typeOracle, |
| SingleJsoImplData jsoData) { |
| super(v); |
| this.typeOracle = typeOracle; |
| this.jsoData = jsoData; |
| } |
| |
| @Override |
| public void visit(int version, int access, String name, String signature, |
| String superName, String[] interfaces) { |
| assert currentTypeName == null; |
| super.visit(version, access, name, signature, superName, interfaces); |
| |
| /* |
| * This visitor would mangle JSO$ since it acts as a roll-up of all |
| * SingleJso types and the result would be repeated method definitions due |
| * to the trampoline methods this visitor would create. |
| */ |
| if (name.equals(HostedModeClassRewriter.JAVASCRIPTOBJECT_IMPL_DESC)) { |
| return; |
| } |
| |
| currentTypeName = name; |
| inSingleJsoImplInterfaceType = jsoData.getSingleJsoIntfTypes().contains( |
| name); |
| |
| /* |
| * Implements objective #2: non-JSO types that implement a SingleJsoImpl |
| * interface don't have their original instance methods altered. Instead, we |
| * add trampoline methods with mangled names that simply call over to the |
| * original methods. |
| */ |
| if (interfaces != null && (access & Opcodes.ACC_INTERFACE) == 0) { |
| Set<String> toStub = computeAllInterfaces(interfaces); |
| toStub.retainAll(jsoData.getSingleJsoIntfTypes()); |
| |
| for (String stubIntr : toStub) { |
| writeTrampoline(stubIntr); |
| } |
| } |
| } |
| |
| @Override |
| public void visitEnd() { |
| /* |
| * Add any missing methods that are defined by a super-interface, but that |
| * may be referenced via a more specific interface. |
| */ |
| if (inSingleJsoImplInterfaceType) { |
| for (String mangledName : toImplement(currentTypeName)) { |
| for (Method method : jsoData.getDeclarations(mangledName)) { |
| writeEmptyMethod(mangledName, method); |
| } |
| } |
| } |
| super.visitEnd(); |
| } |
| |
| @Override |
| public MethodVisitor visitMethod(int access, String name, String desc, |
| String signature, String[] exceptions) { |
| |
| /* |
| * Implements objective #2: Rename unmangled methods in a @SingleJsoImpl |
| * into mangled methods (except for clinit, LOL). |
| */ |
| if (inSingleJsoImplInterfaceType && !"<clinit>".equals(name)) { |
| name = currentTypeName.replace('/', '_') + "_" + name; |
| implementedMethods.add(name); |
| } |
| |
| MethodVisitor mv = super.visitMethod(access, name, desc, signature, |
| exceptions); |
| if (mv == null) { |
| return null; |
| } |
| |
| return new MyMethodVisitor(mv); |
| } |
| |
| private Set<String> computeAllInterfaces(String intfName) { |
| Set<String> toReturn = intfNamesToAllInterfaces.get(intfName); |
| if (toReturn != null) { |
| return toReturn; |
| } |
| |
| toReturn = Sets.create(); |
| List<JClassType> q = new LinkedList<JClassType>(); |
| JClassType intf = typeOracle.findType(intfName.replace('/', '.').replace( |
| '$', '.')); |
| |
| /* |
| * If the interface's compilation unit wasn't retained due to an error, then |
| * it won't be available in the typeOracle for us to rewrite |
| */ |
| if (intf != null) { |
| q.add(intf); |
| } |
| |
| while (!q.isEmpty()) { |
| intf = q.remove(0); |
| String resourceName = getResourceName(intf); |
| if (!toReturn.contains(resourceName)) { |
| toReturn = Sets.add(toReturn, resourceName); |
| Collections.addAll(q, intf.getImplementedInterfaces()); |
| } |
| } |
| |
| intfNamesToAllInterfaces = Maps.put(intfNamesToAllInterfaces, intfName, |
| toReturn); |
| return toReturn; |
| } |
| |
| private Set<String> computeAllInterfaces(String[] interfaces) { |
| Set<String> toReturn = new HashSet<String>(); |
| for (String intfName : interfaces) { |
| toReturn.addAll(computeAllInterfaces(intfName)); |
| } |
| return toReturn; |
| } |
| |
| private String getResourceName(JClassType type) { |
| if (type.getEnclosingType() != null) { |
| return getResourceName(type.getEnclosingType()) + "$" |
| + type.getSimpleSourceName(); |
| } |
| return type.getQualifiedSourceName().replace('.', '/'); |
| } |
| |
| /** |
| * Given a resource name of a class, find all mangled method names that must |
| * be implemented. |
| */ |
| private SortedSet<String> toImplement(String typeName) { |
| String name = typeName.replace('/', '_'); |
| String prefix = name + "_"; |
| String suffix = name + "`"; |
| SortedSet<String> toReturn = new TreeSet<String>(); |
| for (String mangledName : jsoData.getMangledNames().subSet(prefix, suffix)) { |
| if (!implementedMethods.contains(mangledName)) { |
| toReturn.add(mangledName); |
| } |
| } |
| return toReturn; |
| } |
| |
| private void writeEmptyMethod(String mangledMethodName, Method declMethod) { |
| MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC |
| | Opcodes.ACC_ABSTRACT, mangledMethodName, declMethod.getDescriptor(), |
| null, null); |
| mv.visitEnd(); |
| } |
| |
| /** |
| * For regular Java objects that implement a SingleJsoImpl interface, write |
| * instance trampoline dispatchers for mangled method names to the |
| * implementing method. |
| */ |
| private void writeTrampoline(String stubIntr) { |
| /* |
| * This is almost the same kind of trampoline as the ones generated in |
| * WriteJsoImpl, however there are enough small differences between the |
| * semantics of the dispatches that would make a common implementation far |
| * more awkward than the duplication of code. |
| */ |
| for (String mangledName : toImplement(stubIntr)) { |
| for (Method method : jsoData.getDeclarations(mangledName)) { |
| |
| Method toCall = new Method(method.getName(), method.getDescriptor()); |
| |
| // Must not be final |
| MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC |
| | Opcodes.ACC_SYNTHETIC, mangledName, method.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. |
| * |
| * These start at 1 because we need to load "this" onto the stack |
| */ |
| int var = 1; |
| int size = 1; |
| |
| // load this |
| mv.visitVarInsn(Opcodes.ALOAD, 0); |
| |
| // then the rest of the arguments |
| for (Type t : toCall.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, toCall.getReturnType().getSize()); |
| |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, currentTypeName, |
| toCall.getName(), toCall.getDescriptor()); |
| mv.visitInsn(toCall.getReturnType().getOpcode(Opcodes.IRETURN)); |
| mv.visitMaxs(size, var); |
| mv.visitEnd(); |
| } |
| } |
| } |
| } |
| } |