/
Transformers.java
136 lines (117 loc) · 5.68 KB
/
Transformers.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// 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.requirenonnull.RequireNonNull;
import net.orfjackal.retrolambda.trywithresources.SwallowSuppressedExceptions;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import java.util.*;
import java.util.function.Consumer;
public class Transformers {
private final int targetVersion;
private final boolean defaultMethodsEnabled;
private final ClassAnalyzer analyzer;
public Transformers(int targetVersion, boolean defaultMethodsEnabled, ClassAnalyzer analyzer) {
this.targetVersion = targetVersion;
this.defaultMethodsEnabled = defaultMethodsEnabled;
this.analyzer = analyzer;
}
public byte[] backportLambdaClass(ClassReader reader) {
return transform(reader, (next) -> {
if (defaultMethodsEnabled) {
// Lambda classes are generated dynamically, so they were not
// part of the original analytics and must be analyzed now,
// in case they implement interfaces with default methods.
analyzer.analyze(reader);
next = new UpdateRelocatedMethodInvocations(next, analyzer);
next = new AddMethodDefaultImplementations(next, analyzer);
} else {
next = new UpdateRelocatedMethodInvocations(next, analyzer); // needed for lambdas in an interface's constant initializer
}
next = new BackportLambdaClass(next);
return next;
});
}
public byte[] backportClass(ClassReader reader) {
return transform(reader, (next) -> {
if (defaultMethodsEnabled) {
next = new UpdateRelocatedMethodInvocations(next, analyzer);
next = new AddMethodDefaultImplementations(next, analyzer);
}
next = new BackportLambdaInvocations(next, analyzer);
return next;
});
}
public List<byte[]> backportInterface(ClassReader reader) {
// The lambdas must be backported only once, because bad things will happen if a lambda
// is called by different class name in the interface and its companion class, and then
// the wrong one of them is written to disk last.
ClassNode lambdasBackported = new ClassNode();
ClassVisitor next = lambdasBackported;
next = new BackportLambdaInvocations(next, analyzer);
reader.accept(next, 0);
List<byte[]> results = new ArrayList<>();
results.add(backportInterface2(lambdasBackported));
results.addAll(extractInterfaceCompanion(lambdasBackported));
return results;
}
private byte[] backportInterface2(ClassNode clazz) {
return transform(clazz, (next) -> {
if (defaultMethodsEnabled) {
next = new RemoveStaticMethods(next);
next = new RemoveDefaultMethodBodies(next);
next = new UpdateRelocatedMethodInvocations(next, analyzer);
} else {
// XXX: It would be better to remove only those static methods which are lambda implementation methods,
// but that would either require the use of naming patterns (not guaranteed to work with every Java compiler)
// or passing around information that which relocated static methods are because of lambdas.
next = new RemoveStaticMethods(next); // needed for lambdas in an interface's constant initializer
next = new WarnAboutDefaultAndStaticMethods(next);
}
next = new RemoveBridgeMethods(next);
return next;
});
}
private List<byte[]> extractInterfaceCompanion(ClassNode clazz) {
Optional<Type> companion = analyzer.getCompanionClass(Type.getObjectType(clazz.name));
if (!companion.isPresent()) {
return Collections.emptyList();
}
return Arrays.asList(transform(clazz, (next) -> {
next = new UpdateRelocatedMethodInvocations(next, analyzer);
next = new ExtractInterfaceCompanionClass(next, companion.get());
return next;
}));
}
private byte[] transform(ClassNode node, ClassVisitorChain chain) {
return transform(node.name, node::accept, chain);
}
private byte[] transform(ClassReader reader, ClassVisitorChain chain) {
return transform(reader.getClassName(), cv -> reader.accept(cv, 0), chain);
}
private byte[] transform(String className, Consumer<ClassVisitor> reader, ClassVisitorChain chain) {
try {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor next = writer;
next = new LowerBytecodeVersion(next, targetVersion);
if (targetVersion < Opcodes.V1_7) {
next = new SwallowSuppressedExceptions(next);
next = new RemoveMethodHandlesLookupReferences(next);
next = new RequireNonNull(next);
}
next = new FixInvokeStaticOnInterfaceMethod(next);
next = new UpdateRenamedEnclosingMethods(next, analyzer);
next = chain.wrap(next);
reader.accept(next);
return writer.toByteArray();
} catch (Throwable t) {
throw new RuntimeException("Failed to backport class: " + className, t);
}
}
private interface ClassVisitorChain {
ClassVisitor wrap(ClassVisitor next);
}
}