// Copyright © 2013-2017 Esko Luontola and other Retrolambda contributors
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0
package net.orfjackal.retrolambda;
import net.orfjackal.retrolambda.interfaces.*;
import net.orfjackal.retrolambda.lambdas.*;
import net.orfjackal.retrolambda.util.*;
import org.objectweb.asm.*;
import java.util.*;
import static java.util.stream.Collectors.toList;
import static net.orfjackal.retrolambda.util.Flags.*;
import static org.objectweb.asm.Opcodes.*;
public class ClassAnalyzer {
private final Map<Type, ClassInfo> classes = new HashMap<>();
private final Map<MethodRef, MethodRef> relocatedMethods = new HashMap<>();
private final Map<MethodRef, MethodRef> renamedLambdaMethods = new HashMap<>();
public void analyze(byte[] bytecode) {
analyze(new ClassReader(bytecode));
}
public void analyze(ClassReader cr) {
ClassInfo c = new ClassInfo(cr);
classes.put(c.type, c);
if (isInterface(cr.getAccess())) {
analyzeInterface(c, cr);
} else {
analyzeClass(c, cr);
}
analyzeClassOrInterface(c, cr);
}
private void analyzeClass(ClassInfo c, ClassReader cr) {
cr.accept(new ClassVisitor(ASM5) {
private String owner;
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.owner = name;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
int tag;
if (isConstructor(name)) {
tag = H_INVOKESPECIAL;
} else if (isStaticMethod(access)) {
tag = H_INVOKESTATIC;
} else {
tag = H_INVOKEVIRTUAL;
}
c.addMethod(access, new MethodRef(tag, owner, name, desc), new MethodKind.Implemented());
return null;
}
}, ClassReader.SKIP_CODE);
}
private void analyzeInterface(ClassInfo c, ClassReader cr) {
cr.accept(new ClassVisitor(ASM5) {
private String owner;
private String companion;
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.owner = name;
this.companion = name + "$";
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodRef method = new MethodRef(Handles.accessToTag(access, true), owner, name, desc);
if (isAbstractMethod(access)) {
c.addMethod(access, method, new MethodKind.Abstract());
} else if (isDefaultMethod(access)) {
MethodRef defaultImpl = new MethodRef(H_INVOKESTATIC, companion, name, Bytecode.prependArgumentType(desc, Type.getObjectType(owner)));
c.enableCompanionClass();
c.addMethod(access, method, new MethodKind.Default(defaultImpl));
} else if (isInstanceLambdaImplMethod(access)) {
relocatedMethods.put(method, new MethodRef(H_INVOKESTATIC, companion, name, Bytecode.prependArgumentType(desc, Type.getObjectType(owner))));
c.enableCompanionClass();
} else if (isStaticMethod(access) && !isStaticInitializer(name, desc, access)) {
relocatedMethods.put(method, new MethodRef(H_INVOKESTATIC, companion, name, desc));
c.enableCompanionClass();
}
return null;
}
}, ClassReader.SKIP_CODE);
}
private void analyzeClassOrInterface(ClassInfo c, ClassReader cr) {
cr.accept(new ClassVisitor(ASM5) {
private String owner;
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.owner = name;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodRef method = new MethodRef(Handles.accessToTag(access, true), owner, name, desc);
// XXX: duplicates code in net.orfjackal.retrolambda.lambdas.BackportLambdaInvocations.visitMethod()
if (LambdaNaming.isBodyMethod(access, name)
&& Flags.isPrivateMethod(access)
&& Flags.isInstanceMethod(access)) {
desc = Types.prependArgumentType(Type.getObjectType(owner), desc); // add 'this' as first parameter
renamedLambdaMethods.put(method, new MethodRef(H_INVOKESTATIC, owner, name, desc));
}
return null;
}
}, ClassReader.SKIP_CODE);
}
private static boolean isDefaultMethod(int access) {
return !isAbstractMethod(access)
&& !isStaticMethod(access)
&& isPublicMethod(access);
}
private static boolean isInstanceLambdaImplMethod(int access) {
return !isAbstractMethod(access)
&& !isStaticMethod(access)
&& isPrivateMethod(access);
}
public List<ClassInfo> getInterfaces() {
return classes.values()
.stream()
.filter(ClassInfo::isInterface)
.collect(toList());
}
public List<ClassInfo> getClasses() {
return classes.values()
.stream()
.filter(ClassInfo::isClass)
.collect(toList());
}
private ClassInfo getClass(Type type) {
return classes.getOrDefault(type, new ClassInfo());
}
public MethodRef getMethodCallTarget(MethodRef original) {
if (original.tag == H_INVOKESPECIAL) {
// change Interface.super.defaultMethod() calls to static calls on the companion class
MethodRef impl = getMethodDefaultImplementation(original);
if (impl != null) {
return impl;
}
}
return relocatedMethods.getOrDefault(original, original);
}
public MethodRef getRenamedLambdaMethod(MethodRef original) {
return renamedLambdaMethods.getOrDefault(original, original);
}
public MethodRef getMethodDefaultImplementation(MethodRef interfaceMethod) {
MethodSignature signature = interfaceMethod.getSignature();
for (MethodInfo method : getDefaultMethods(Type.getObjectType(interfaceMethod.owner))) {
if (method.signature.equals(signature)) {
return method.getDefaultMethodImpl();
}
}
return null;
}
public Optional<Type> getCompanionClass(Type type) {
return getClass(type).getCompanionClass();
}
public List<MethodInfo> getDefaultMethods(Type type) {
return getMethods(type).stream()
.filter(m -> m.kind instanceof MethodKind.Default)
.collect(toList());
}
public Collection<MethodInfo> getMethods(Type type) {
ClassInfo c = getClass(type);
Map<MethodSignature, MethodInfo> methods = new HashMap<>();
// in reverse priority order:
// - default methods
for (Type iface : c.getInterfaces()) {
for (MethodInfo m : getMethods(iface)) {
if (!isAlreadyInherited(m, methods)) {
methods.put(m.signature, m);
}
}
}
// - superclass methods
if (c.superclass != null) {
for (MethodInfo m : getMethods(c.superclass)) {
if (!isAlreadyInherited(m, methods)) {
methods.put(m.signature, m);
}
}
}
// - own methods
for (MethodInfo m : c.getMethods()) {
methods.put(m.signature, m);
}
return methods.values();
}
private boolean isAlreadyInherited(MethodInfo subject, Map<MethodSignature, MethodInfo> existingMethods) {
MethodInfo existing = existingMethods.get(subject.signature);
return existing != null && getAllInterfaces(existing.owner).contains(subject.owner);
}
private Set<Type> getAllInterfaces(Type interfaceType) {
assert getClass(interfaceType).isInterface() : "not interface: " + interfaceType;
HashSet<Type> results = new HashSet<>();
results.add(interfaceType);
for (Type parentInterface : getClass(interfaceType).getInterfaces()) {
results.addAll(getAllInterfaces(parentInterface));
}
return results;
}
}