com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter Maven / Gradle / Ivy
The newest version!
/*
* 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.javac.asmbridge.EmptyVisitor;
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 org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.Method;
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:
*
* - Rewrites all native methods into non-native thunks to call JSNI via
* {@link com.google.gwt.dev.shell.JavaScriptHost}.
* - Rewrites all JSO types into an interface type (which retains the original
* name) and an implementation type (which has a $ appended).
* - All JSO interface types are empty and mirror the original type hierarchy.
*
* - All JSO impl types contain the guts of the original type, except that all
* instance methods are reimplemented as statics.
* - 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 impl class.
* - JavaScriptObject$ implements all the interface types and is the only
* instantiable type.
*
*
* @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 declaredClass
.
*
* @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 declaredClass
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 getDeclarations(String mangledName);
/**
* Returns the implementations that the method declarations above should
* delegate to.{@link #getDeclarations} and {@link #getImplementations}
* maintain a pairwise mapping.
*/
List getImplementations(String mangledName);
/**
* Returns all of the mangled method names for SingleJsoImpl methods.
*/
SortedSet getMangledNames();
/**
* Returns the internal names of all interface types implemented by JSOs.
*/
Set 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
* JavaScriptObject
and all subclasses.
*/
private final Set jsoImplDescs;
/**
* An unmodifiable set of descriptors containing the interface form of
* JavaScriptObject
and all subclasses.
*/
private final Set jsoIntfDescs;
private final SingleJsoImplData jsoData;
/**
* Records the superclass of every JSO for generating empty JSO interfaces.
*/
private final Map> 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 jsoSubtypes,
Map> jsoSuperTypes, SingleJsoImplData jsoData,
InstanceMethodOracle mapper) {
Set buildJsoIntfDescs = new HashSet();
Set buildJsoImplDescs = new HashSet();
Map> buildJsoSuperDescs = new HashMap>();
for (String jsoSubtype : jsoSubtypes) {
String desc = toDescriptor(jsoSubtype);
buildJsoIntfDescs.add(desc);
buildJsoImplDescs.add(desc + "$");
List superTypes = jsoSuperTypes.get(jsoSubtype);
assert (superTypes != null);
assert (superTypes.size() > 0);
for (ListIterator 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 true
if the class is the implementation class for a
* JSO subtype.
*/
public boolean isJsoImpl(String className) {
return jsoImplDescs.contains(toDescriptor(className));
}
/**
* Returns true
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 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_8) {
// TODO(cromwellian) implement Retrolambda?
v = new ForceClassVersion15(v);
}
new ClassReader(classBytes).accept(v, 0);
classBytesRewriteEvent.end();
return writer.toByteArray();
}
public byte[] writeJsoIntf(final String className, byte classBytes[]) {
String desc = toDescriptor(className);
assert (jsoIntfDescs.contains(desc));
assert (jsoSuperDescs.containsKey(desc));
List 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);
final 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);
if (classBytes != null) {
// Java7 enforces innerclass/outerclass consistency. In order to fix this, copy from original
ClassVisitor cv = new EmptyVisitor() {
@Override
public void visitInnerClass(String name, String outerName, String innerName,
int access) {
// copy inner class table from original JSO to synthetic interface
v.visitInnerClass(name, outerName, innerName, access);
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
// copy outer class table from original JSO to synthetic interface
v.visitOuterClass(owner, name, desc);
}
};
new ClassReader(classBytes).accept(cv, 0);
}
v.visitEnd();
return writer.toByteArray();
}
}