| /* |
| * 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.core.ext.typeinfo.TypeOracle; |
| import com.google.gwt.dev.asm.ClassReader; |
| 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.commons.Method; |
| import com.google.gwt.dev.shell.JsValueGlue; |
| import com.google.gwt.dev.util.log.speedtracer.DevModeEventType; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.SortedSet; |
| |
| /** |
| * 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 hierarchy. |
| * </li> |
| * <li>All JSO impl types contain the guts of the original type, except that all |
| * instance methods are reimplemented as statics.</li> |
| * <li>Calls sites to JSO types rewritten to dispatch to impl types. Any virtual |
| * calls are also made static. Static field references to JSO types reference |
| * static fields in the the impl class.</li> |
| * <li>JavaScriptObject$ implements all the interface types and is the only |
| * instantiable type.</li> |
| * </ol> |
| * |
| * @see RewriteJsniMethods |
| * @see RewriteRefsToJsoClasses |
| * @see WriteJsoInterface |
| * @see WriteJsoImpl |
| */ |
| public class HostedModeClassRewriter { |
| /* |
| * Note: this rewriter operates on a class-by-class basis and has no global |
| * view on the entire system. However, its operation requires certain global |
| * state information. Therefore, all such global state must be passed into the |
| * constructor. |
| */ |
| |
| /** |
| * Maps instance methods to the class in which they are declared. This must be |
| * provided by the caller since it requires global program state. |
| */ |
| public interface InstanceMethodOracle { |
| |
| /** |
| * For a given instance method and declared enclosing class (which must be a |
| * JSO subtype), find the class in which that method was originally |
| * declared. Methods declared on Object will return "java/lang/Object". |
| * Static methods will always return <code>declaredClass</code>. |
| * |
| * @param declaredClass a descriptor of the static type of the qualifier |
| * @param signature the binary signature of the method |
| * @return the descriptor of the class in which that method was declared, |
| * which will either be <code>declaredClass</code> or some |
| * superclass |
| * @throws IllegalArgumentException if the method could not be found |
| */ |
| String findOriginalDeclaringClass(String declaredClass, String signature); |
| } |
| |
| /** |
| * Contains data about how SingleJsoImpl methods are to be dispatched. |
| */ |
| public interface SingleJsoImplData { |
| /** |
| * Returns the method declarations that should be generated for a given |
| * mangled method name. {@link #getDeclarations} and |
| * {@link #getImplementations} maintain a pairwise mapping. |
| */ |
| List<Method> getDeclarations(String mangledName); |
| |
| /** |
| * Returns the implementations that the method declarations above should |
| * delegate to.{@link #getDeclarations} and {@link #getImplementations} |
| * maintain a pairwise mapping. |
| */ |
| List<Method> getImplementations(String mangledName); |
| |
| /** |
| * Returns all of the mangled method names for SingleJsoImpl methods. |
| */ |
| SortedSet<String> getMangledNames(); |
| |
| /** |
| * Returns the internal names of all interface types implemented by JSOs. |
| */ |
| Set<String> getSingleJsoIntfTypes(); |
| } |
| |
| 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); |
| } |
| |
| private static String toDescriptor(String jsoSubtype) { |
| return jsoSubtype.replace('.', '/'); |
| } |
| |
| /** |
| * An unmodifiable set of descriptors containing the implementation form of |
| * <code>JavaScriptObject</code> and all subclasses. |
| */ |
| private final Set<String> jsoImplDescs; |
| |
| /** |
| * An unmodifiable set of descriptors containing the interface form of |
| * <code>JavaScriptObject</code> and all subclasses. |
| */ |
| private final Set<String> jsoIntfDescs; |
| |
| private final SingleJsoImplData jsoData; |
| |
| /** |
| * Records the superclass of every JSO for generating empty JSO interfaces. |
| */ |
| private final Map<String, List<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. |
| * |
| * @param jsoSubtypes a set of binary type names representing JavaScriptObject |
| * and all of its subtypes of |
| * @param mapper maps methods to the class in which they are declared |
| */ |
| public HostedModeClassRewriter(Set<String> jsoSubtypes, |
| Map<String, List<String>> jsoSuperTypes, SingleJsoImplData jsoData, |
| InstanceMethodOracle mapper) { |
| Set<String> buildJsoIntfDescs = new HashSet<String>(); |
| Set<String> buildJsoImplDescs = new HashSet<String>(); |
| Map<String, List<String>> buildJsoSuperDescs = new HashMap<String, List<String>>(); |
| for (String jsoSubtype : jsoSubtypes) { |
| String desc = toDescriptor(jsoSubtype); |
| buildJsoIntfDescs.add(desc); |
| buildJsoImplDescs.add(desc + "$"); |
| |
| List<String> superTypes = jsoSuperTypes.get(jsoSubtype); |
| assert (superTypes != null); |
| assert (superTypes.size() > 0); |
| for (ListIterator<String> i = superTypes.listIterator(); i.hasNext();) { |
| i.set(toDescriptor(i.next())); |
| } |
| buildJsoSuperDescs.put(desc, Collections.unmodifiableList(superTypes)); |
| } |
| |
| this.jsoIntfDescs = Collections.unmodifiableSet(buildJsoIntfDescs); |
| this.jsoImplDescs = Collections.unmodifiableSet(buildJsoImplDescs); |
| this.jsoSuperDescs = Collections.unmodifiableMap(buildJsoSuperDescs); |
| this.jsoData = jsoData; |
| this.mapper = mapper; |
| } |
| |
| /** |
| * Returns <code>true</code> if the class is the implementation class for a |
| * JSO subtype. |
| */ |
| public boolean isJsoImpl(String className) { |
| return jsoImplDescs.contains(toDescriptor(className)); |
| } |
| |
| /** |
| * Returns <code>true</code> if the class is the interface class for a JSO |
| * subtype. |
| */ |
| public boolean isJsoIntf(String className) { |
| return jsoIntfDescs.contains(toDescriptor(className)); |
| } |
| |
| /** |
| * Performs rewriting transformations on a class. |
| * |
| * @param typeOracle a typeOracle modeling the user classes |
| * @param className the name of the class |
| * @param classBytes the bytes of the class |
| * @param anonymousClassMap a map between the anonymous class names of java |
| * compiler used to compile code and jdt. Emma-specific. |
| */ |
| public byte[] rewrite(TypeOracle typeOracle, String className, |
| byte[] classBytes, Map<String, String> anonymousClassMap) { |
| Event classBytesRewriteEvent = |
| SpeedTracerLogger.start(DevModeEventType.CLASS_BYTES_REWRITE, "Class Name", className); |
| String desc = toDescriptor(className); |
| assert (!jsoIntfDescs.contains(desc)); |
| |
| // The ASM model is to chain a bunch of visitors together. |
| ClassWriter writer = new ClassWriter(0); |
| ClassVisitor v = writer; |
| |
| // v = new CheckClassAdapter(v); |
| // v = new TraceClassVisitor(v, new PrintWriter(System.out)); |
| |
| v = new UseMirroredClasses(v, className); |
| |
| v = new RewriteSingleJsoImplDispatches(v, typeOracle, jsoData); |
| |
| v = new RewriteRefsToJsoClasses(v, jsoIntfDescs, mapper); |
| |
| if (jsoImplDescs.contains(desc)) { |
| v = WriteJsoImpl.create(v, desc, jsoIntfDescs, mapper, jsoData); |
| } |
| |
| v = new RewriteJsniMethods(v, anonymousClassMap); |
| |
| if (Double.parseDouble(System.getProperty("java.class.version")) < Opcodes.V1_6) { |
| v = new ForceClassVersion15(v); |
| } |
| |
| new ClassReader(classBytes).accept(v, 0); |
| classBytesRewriteEvent.end(); |
| return writer.toByteArray(); |
| } |
| |
| public byte[] writeJsoIntf(String className) { |
| String desc = toDescriptor(className); |
| assert (jsoIntfDescs.contains(desc)); |
| assert (jsoSuperDescs.containsKey(desc)); |
| List<String> superDescs = jsoSuperDescs.get(desc); |
| assert (superDescs != null); |
| assert (superDescs.size() > 0); |
| |
| // The ASM model is to chain a bunch of visitors together. |
| ClassWriter writer = new ClassWriter(0); |
| ClassVisitor v = writer; |
| |
| // v = new CheckClassAdapter(v); |
| // v = new TraceClassVisitor(v, new PrintWriter(System.out)); |
| |
| String[] interfaces; |
| // TODO(bov): something better than linear? |
| if (superDescs.contains("java/lang/Object")) { |
| interfaces = null; |
| } else { |
| interfaces = superDescs.toArray(new String[superDescs.size()]); |
| } |
| v.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, desc, |
| null, "java/lang/Object", interfaces); |
| v.visitEnd(); |
| return writer.toByteArray(); |
| } |
| } |