Improvements in reflection:

1. During dependency analysis, propagate class literals from
   Class.forName return node
2. Use original class source to generate reflection metadata
3. Link classes when they appear in signature of reflectable methods
4. Turn Class.forName(string_literal) into class literal.
This commit is contained in:
Alexey Andreev 2018-01-27 00:21:50 +03:00
parent f49ec16021
commit ff7232ac3e
8 changed files with 90 additions and 9 deletions

View File

@ -26,15 +26,20 @@ import org.teavm.model.Instruction;
import org.teavm.model.MethodHolder; import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.Program; import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.Variable; import org.teavm.model.Variable;
import org.teavm.model.instructions.AssignInstruction; import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.StringConstantInstruction;
public class ClassForNameTransformer implements ClassHolderTransformer { public class ClassForNameTransformer implements ClassHolderTransformer {
private static final MethodReference getNameMethod = new MethodReference(Class.class, "getName", String.class); private static final MethodReference getNameMethod = new MethodReference(Class.class, "getName", String.class);
private static final MethodReference forNameMethod = new MethodReference(Class.class, "forName", String.class, private static final MethodReference forNameMethod = new MethodReference(Class.class, "forName", String.class,
boolean.class, ClassLoader.class, Class.class); boolean.class, ClassLoader.class, Class.class);
private static final MethodReference forNameShortMethod = new MethodReference(Class.class, "forName",
String.class, Class.class);
private static final MethodReference initMethod = new MethodReference(Class.class, "initialize", void.class); private static final MethodReference initMethod = new MethodReference(Class.class, "initialize", void.class);
@Override @Override
@ -42,17 +47,18 @@ public class ClassForNameTransformer implements ClassHolderTransformer {
for (MethodHolder method : cls.getMethods()) { for (MethodHolder method : cls.getMethods()) {
Program program = method.getProgram(); Program program = method.getProgram();
if (program != null) { if (program != null) {
transformProgram(program); transformProgram(program, innerSource);
} }
} }
} }
private void transformProgram(Program program) { private void transformProgram(Program program, ClassReaderSource classSource) {
DisjointSet varSet = new DisjointSet(); DisjointSet varSet = new DisjointSet();
for (int i = 0; i < program.variableCount(); i++) { for (int i = 0; i < program.variableCount(); i++) {
varSet.create(); varSet.create();
} }
int[] nameIndexes = new int[program.variableCount()]; int[] nameIndexes = new int[program.variableCount()];
String[] constants = new String[program.variableCount()];
Arrays.fill(nameIndexes, -1); Arrays.fill(nameIndexes, -1);
for (BasicBlock block : program.getBasicBlocks()) { for (BasicBlock block : program.getBasicBlocks()) {
@ -64,6 +70,9 @@ public class ClassForNameTransformer implements ClassHolderTransformer {
nameIndexes[invoke.getReceiver().getIndex()] = invoke.getInstance().getIndex(); nameIndexes[invoke.getReceiver().getIndex()] = invoke.getInstance().getIndex();
} }
} }
} else if (instruction instanceof StringConstantInstruction) {
StringConstantInstruction stringConstant = (StringConstantInstruction) instruction;
constants[stringConstant.getReceiver().getIndex()] = stringConstant.getConstant();
} else if (instruction instanceof AssignInstruction) { } else if (instruction instanceof AssignInstruction) {
AssignInstruction assign = (AssignInstruction) instruction; AssignInstruction assign = (AssignInstruction) instruction;
varSet.union(assign.getAssignee().getIndex(), assign.getReceiver().getIndex()); varSet.union(assign.getAssignee().getIndex(), assign.getReceiver().getIndex());
@ -74,6 +83,7 @@ public class ClassForNameTransformer implements ClassHolderTransformer {
nameIndexes = Arrays.copyOf(nameIndexes, varSet.size()); nameIndexes = Arrays.copyOf(nameIndexes, varSet.size());
int[] nameRepresentatives = new int[nameIndexes.length]; int[] nameRepresentatives = new int[nameIndexes.length];
Arrays.fill(nameRepresentatives, -1); Arrays.fill(nameRepresentatives, -1);
String[] constantsByClasses = new String[varSet.size()];
for (int i = 0; i < program.variableCount(); i++) { for (int i = 0; i < program.variableCount(); i++) {
int varClass = varSet.find(i); int varClass = varSet.find(i);
@ -83,6 +93,8 @@ public class ClassForNameTransformer implements ClassHolderTransformer {
if (nameIndexes[i] >= 0) { if (nameIndexes[i] >= 0) {
nameIndexes[varClass] = varSet.find(nameIndexes[i]); nameIndexes[varClass] = varSet.find(nameIndexes[i]);
} }
constantsByClasses[varClass] = constants[i];
} }
for (BasicBlock block : program.getBasicBlocks()) { for (BasicBlock block : program.getBasicBlocks()) {
@ -92,17 +104,30 @@ public class ClassForNameTransformer implements ClassHolderTransformer {
} }
InvokeInstruction invoke = (InvokeInstruction) instruction; InvokeInstruction invoke = (InvokeInstruction) instruction;
if (!invoke.getMethod().equals(forNameMethod)) { if (!invoke.getMethod().equals(forNameMethod) && !invoke.getMethod().equals(forNameShortMethod)) {
continue; continue;
} }
Variable representative;
int classNameIndex = invoke.getArguments().get(0).getIndex(); int classNameIndex = invoke.getArguments().get(0).getIndex();
int nameIndex = nameIndexes[classNameIndex]; int nameIndex = nameIndexes[classNameIndex];
if (nameIndex < 0) { String constant = constantsByClasses[invoke.getArguments().get(0).getIndex()];
if (nameIndex >= 0) {
representative = program.variableAt(nameRepresentatives[nameIndex]);
} else if (constant != null) {
if (classSource.get(constant) == null) {
continue;
}
ClassConstantInstruction classConstant = new ClassConstantInstruction();
classConstant.setConstant(ValueType.object(constant));
classConstant.setReceiver(program.createVariable());
classConstant.setLocation(invoke.getLocation());
invoke.insertPrevious(classConstant);
representative = classConstant.getReceiver();
} else {
continue; continue;
} }
Variable representative = program.variableAt(nameRepresentatives[nameIndex]);
InvokeInstruction initInvoke = new InvokeInstruction(); InvokeInstruction initInvoke = new InvokeInstruction();
initInvoke.setLocation(invoke.getLocation()); initInvoke.setLocation(invoke.getLocation());

View File

@ -54,6 +54,9 @@ public class ReflectionDependencyListener extends AbstractDependencyListener {
Constructor[].class); Constructor[].class);
private MethodReference getMethods = new MethodReference(Class.class, "getDeclaredMethods", private MethodReference getMethods = new MethodReference(Class.class, "getDeclaredMethods",
Method[].class); Method[].class);
private MethodReference forName = new MethodReference(Class.class, "forName", String.class, Boolean.class,
ClassLoader.class, Class.class);
private MethodReference forNameShort = new MethodReference(Class.class, "forName", String.class, Class.class);
private boolean fieldGetHandled; private boolean fieldGetHandled;
private boolean fieldSetHandled; private boolean fieldSetHandled;
private boolean newInstanceHandled; private boolean newInstanceHandled;
@ -62,11 +65,17 @@ public class ReflectionDependencyListener extends AbstractDependencyListener {
private Map<String, Set<MethodDescriptor>> accessibleMethodCache = new LinkedHashMap<>(); private Map<String, Set<MethodDescriptor>> accessibleMethodCache = new LinkedHashMap<>();
private Set<String> classesWithReflectableFields = new LinkedHashSet<>(); private Set<String> classesWithReflectableFields = new LinkedHashSet<>();
private Set<String> classesWithReflectableMethods = new LinkedHashSet<>(); private Set<String> classesWithReflectableMethods = new LinkedHashSet<>();
private DependencyNode allClasses;
public ReflectionDependencyListener(List<ReflectionSupplier> reflectionSuppliers) { public ReflectionDependencyListener(List<ReflectionSupplier> reflectionSuppliers) {
this.reflectionSuppliers = reflectionSuppliers; this.reflectionSuppliers = reflectionSuppliers;
} }
@Override
public void started(DependencyAgent agent) {
allClasses = agent.createNode();
}
public Set<String> getClassesWithReflectableFields() { public Set<String> getClassesWithReflectableFields() {
return classesWithReflectableFields; return classesWithReflectableFields;
} }
@ -83,6 +92,11 @@ public class ReflectionDependencyListener extends AbstractDependencyListener {
return accessibleMethodCache.get(className); return accessibleMethodCache.get(className);
} }
@Override
public void classReached(DependencyAgent agent, String className, CallLocation location) {
allClasses.propagate(agent.getType(className));
}
@Override @Override
public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) {
if (method.getReference().equals(fieldGet)) { if (method.getReference().equals(fieldGet)) {
@ -97,14 +111,29 @@ public class ReflectionDependencyListener extends AbstractDependencyListener {
method.getVariable(0).getClassValueNode().addConsumer(type -> { method.getVariable(0).getClassValueNode().addConsumer(type -> {
if (!type.getName().startsWith("[")) { if (!type.getName().startsWith("[")) {
classesWithReflectableFields.add(type.getName()); classesWithReflectableFields.add(type.getName());
ClassReader cls = agent.getClassSource().get(type.getName());
for (FieldReader field : cls.getFields()) {
linkType(agent, field.getType());
}
} }
}); });
} else if (method.getReference().equals(getConstructors) || method.getReference().equals(getMethods)) { } else if (method.getReference().equals(getConstructors) || method.getReference().equals(getMethods)) {
method.getVariable(0).getClassValueNode().addConsumer(type -> { method.getVariable(0).getClassValueNode().addConsumer(type -> {
if (!type.getName().startsWith("[")) { if (!type.getName().startsWith("[")) {
classesWithReflectableMethods.add(type.getName()); classesWithReflectableMethods.add(type.getName());
ClassReader cls = agent.getClassSource().get(type.getName());
for (MethodReader reflectableMethod : cls.getMethods()) {
linkType(agent, reflectableMethod.getResultType());
for (ValueType param : reflectableMethod.getParameterTypes()) {
linkType(agent, param);
}
}
} }
}); });
} else if (method.getReference().equals(forName) || method.getReference().equals(forNameShort)) {
allClasses.connect(method.getResult().getClassValueNode());
} }
} }
@ -213,6 +242,14 @@ public class ReflectionDependencyListener extends AbstractDependencyListener {
}); });
} }
private void linkType(DependencyAgent agent, ValueType type) {
while (type instanceof ValueType.Array) {
type = ((ValueType.Array) type).getItemType();
}
if (type instanceof ValueType.Object) {
agent.linkClass(((ValueType.Object) type).getClassName(), null);
}
}
private void linkClassIfNecessary(DependencyAgent agent, MemberReader member, CallLocation location) { private void linkClassIfNecessary(DependencyAgent agent, MemberReader member, CallLocation location) {
if (member.hasModifier(ElementModifier.STATIC)) { if (member.hasModifier(ElementModifier.STATIC)) {

View File

@ -159,7 +159,7 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin {
ReflectionDependencyListener reflection = context.getService(ReflectionDependencyListener.class); ReflectionDependencyListener reflection = context.getService(ReflectionDependencyListener.class);
Set<MethodDescriptor> accessibleMethods = reflection.getAccessibleMethods(className); Set<MethodDescriptor> accessibleMethods = reflection.getAccessibleMethods(className);
ClassReader cls = context.getClassSource().get(className); ClassReader cls = context.getInitialClassSource().get(className);
if (cls == null) { if (cls == null) {
return; return;
} }

View File

@ -250,7 +250,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
if (debugEmitterToUse == null) { if (debugEmitterToUse == null) {
debugEmitterToUse = new DummyDebugInformationEmitter(); debugEmitterToUse = new DummyDebugInformationEmitter();
} }
RenderingContext renderingContext = new RenderingContext(debugEmitterToUse, classes, RenderingContext renderingContext = new RenderingContext(debugEmitterToUse,
controller.getUnprocessedClassSource(), classes,
controller.getClassLoader(), controller.getServices(), controller.getProperties(), naming); controller.getClassLoader(), controller.getServices(), controller.getProperties(), naming);
renderingContext.setMinifying(minifying); renderingContext.setMinifying(minifying);
Renderer renderer = new Renderer(sourceWriter, asyncMethods, asyncFamilyMethods, Renderer renderer = new Renderer(sourceWriter, asyncMethods, asyncFamilyMethods,

View File

@ -41,6 +41,7 @@ import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.debugging.information.DummyDebugInformationEmitter;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;
import org.teavm.model.ListableClassReaderSource; import org.teavm.model.ListableClassReaderSource;
@ -859,6 +860,11 @@ public class Renderer implements RenderingManager {
return classSource; return classSource;
} }
@Override
public ClassReaderSource getInitialClassSource() {
return context.getInitialClassSource();
}
@Override @Override
public ClassLoader getClassLoader() { public ClassLoader getClassLoader() {
return classLoader; return classLoader;

View File

@ -33,6 +33,7 @@ import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.interop.PlatformMarker; import org.teavm.interop.PlatformMarker;
import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationReader;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ListableClassReaderSource; import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
@ -41,6 +42,7 @@ import org.teavm.model.ValueType;
public class RenderingContext { public class RenderingContext {
private final DebugInformationEmitter debugEmitter; private final DebugInformationEmitter debugEmitter;
private ClassReaderSource initialClassSource;
private ListableClassReaderSource classSource; private ListableClassReaderSource classSource;
private ClassLoader classLoader; private ClassLoader classLoader;
private ServiceRepository services; private ServiceRepository services;
@ -53,10 +55,12 @@ public class RenderingContext {
private final Map<MethodReference, InjectorHolder> injectorMap = new HashMap<>(); private final Map<MethodReference, InjectorHolder> injectorMap = new HashMap<>();
private boolean minifying; private boolean minifying;
public RenderingContext(DebugInformationEmitter debugEmitter, ListableClassReaderSource classSource, public RenderingContext(DebugInformationEmitter debugEmitter,
ClassReaderSource initialClassSource, ListableClassReaderSource classSource,
ClassLoader classLoader, ServiceRepository services, Properties properties, ClassLoader classLoader, ServiceRepository services, Properties properties,
NamingStrategy naming) { NamingStrategy naming) {
this.debugEmitter = debugEmitter; this.debugEmitter = debugEmitter;
this.initialClassSource = initialClassSource;
this.classSource = classSource; this.classSource = classSource;
this.classLoader = classLoader; this.classLoader = classLoader;
this.services = services; this.services = services;
@ -64,6 +68,10 @@ public class RenderingContext {
this.naming = naming; this.naming = naming;
} }
public ClassReaderSource getInitialClassSource() {
return initialClassSource;
}
public ListableClassReaderSource getClassSource() { public ListableClassReaderSource getClassSource() {
return classSource; return classSource;
} }

View File

@ -18,6 +18,7 @@ package org.teavm.backend.javascript.spi;
import java.util.Properties; import java.util.Properties;
import org.teavm.common.ServiceRepository; import org.teavm.common.ServiceRepository;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ListableClassReaderSource; import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
@ -25,6 +26,8 @@ import org.teavm.model.ValueType;
public interface GeneratorContext extends ServiceRepository { public interface GeneratorContext extends ServiceRepository {
String getParameterName(int index); String getParameterName(int index);
ClassReaderSource getInitialClassSource();
ListableClassReaderSource getClassSource(); ListableClassReaderSource getClassSource();
ClassLoader getClassLoader(); ClassLoader getClassLoader();

View File

@ -41,6 +41,7 @@ public class ClassLookupDependencySupport extends AbstractDependencyListener {
if (cls == null) { if (cls == null) {
return; return;
} }
MethodReader initMethod = cls.getMethod(new MethodDescriptor("<clinit>", void.class)); MethodReader initMethod = cls.getMethod(new MethodDescriptor("<clinit>", void.class));
if (initMethod != null) { if (initMethod != null) {
agent.linkMethod(initMethod.getReference(), location).use(); agent.linkMethod(initMethod.getReference(), location).use();