Skip to content

Commit 1a25615

Browse files
Emiel Vandeloorubenpieters
authored andcommitted
Add AccumulatedCodeInjector, allowing injection of multiple code injectors at once.
1 parent e0999ce commit 1a25615

File tree

13 files changed

+464
-106
lines changed

13 files changed

+464
-106
lines changed

base/src/main/java/proguard/classfile/util/OpcodeOffsetFinder.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
import proguard.classfile.instruction.visitor.InstructionVisitor;
1515

1616
/**
17-
* This utility class finds the offset of a provided list of opcode in the code attributes.
17+
* This utility class finds the offset of a provided list of opcodes in the code attributes.
1818
*
1919
* @author Kymeng Tang
2020
*/
2121
public class OpcodeOffsetFinder implements AttributeVisitor, InstructionVisitor, ConstantVisitor {
22-
private final List<Integer> foundOffsets = new ArrayList<>();
2322
private final int[] targetOpcodes;
23+
private final List<Integer> foundOffsets = new ArrayList<>();
2424

2525
public OpcodeOffsetFinder(int[] targetOpcodes) {
2626
this.targetOpcodes = targetOpcodes;
@@ -31,10 +31,11 @@ public List<Integer> getFoundOffsets() {
3131
}
3232

3333
public void reset() {
34-
this.foundOffsets.clear();
34+
foundOffsets.clear();
3535
}
3636

37-
// AttributeVisitor implementation
37+
// Implementations for AttributeVisitor.
38+
3839
@Override
3940
public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
4041

@@ -44,7 +45,8 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt
4445
clazz, method, new AllInstructionVisitor(new InstructionOpCodeFilter(targetOpcodes, this)));
4546
}
4647

47-
// InstructionVisitor implementation
48+
// Implementations for InstructionVisitor.
49+
4850
@Override
4951
public void visitAnyInstruction(
5052
Clazz clazz,
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package proguard.classfile.util.inject;
2+
3+
import java.util.*;
4+
import proguard.classfile.*;
5+
import proguard.classfile.attribute.Attribute;
6+
import proguard.classfile.attribute.CodeAttribute;
7+
import proguard.classfile.attribute.visitor.AllAttributeVisitor;
8+
import proguard.classfile.attribute.visitor.AttributeNameFilter;
9+
import proguard.classfile.attribute.visitor.AttributeVisitor;
10+
import proguard.classfile.editor.CodeAttributeEditor;
11+
import proguard.classfile.editor.InstructionSequenceBuilder;
12+
import proguard.classfile.instruction.Instruction;
13+
import proguard.classfile.util.inject.argument.InjectedArgument;
14+
import proguard.classfile.util.inject.location.InjectStrategy;
15+
16+
/**
17+
* This utility class allows for injecting multiple static method invocations in multiple target
18+
* methods. It allows the repeated use of the API methods defined in the {@link CodeInjector} class.
19+
*/
20+
public class AccumulatedCodeInjector extends CodeInjector {
21+
private final List<CodeInjector> injectors = new ArrayList<>();
22+
private CodeInjector currentInjector = new CodeInjector();
23+
24+
@Override
25+
public CodeInjector injectInvokeStatic(Clazz clazz, Method method) {
26+
return injectInvokeStatic(clazz, method, new InjectedArgument[0]);
27+
}
28+
29+
@Override
30+
public CodeInjector injectInvokeStatic(
31+
Clazz clazz, Method method, InjectedArgument... arguments) {
32+
currentInjector.injectInvokeStatic(clazz, method, arguments);
33+
commitCurrentInjector();
34+
return this;
35+
}
36+
37+
@Override
38+
public CodeInjector into(ProgramClass programClass, ProgramMethod programMethod) {
39+
currentInjector.into(programClass, programMethod);
40+
commitCurrentInjector();
41+
return this;
42+
}
43+
44+
@Override
45+
public CodeInjector at(InjectStrategy injectStrategy) {
46+
currentInjector.at(injectStrategy);
47+
commitCurrentInjector();
48+
return this;
49+
}
50+
51+
@Override
52+
public void commit() {
53+
// Construct a map of target methods to the code injectors that should be applied.
54+
Map<ClassMethodPair, List<CodeInjector>> targetToInjectorsMap = new HashMap<>();
55+
injectors.forEach(
56+
injector ->
57+
injector
58+
.getTargets()
59+
.forEach(
60+
target -> {
61+
List<CodeInjector> currentInjectors =
62+
targetToInjectorsMap.computeIfAbsent(target, k -> new ArrayList<>());
63+
currentInjectors.add(injector);
64+
}));
65+
66+
CodeAttributeEditor editor = new CodeAttributeEditor();
67+
68+
// Iterate over the target methods.
69+
targetToInjectorsMap.forEach(
70+
(target, injectors) -> {
71+
InstructionSequenceBuilder code =
72+
new InstructionSequenceBuilder((ProgramClass) target.clazz);
73+
74+
// Map offsets to the list of instructions that should be applied, either before or after
75+
// the offset.
76+
Map<Integer, LinkedList<Instruction[]>> beforeOffsetInsertions = new HashMap<>();
77+
Map<Integer, LinkedList<Instruction[]>> afterInstructionInsertions = new HashMap<>();
78+
injectors.forEach(
79+
injector -> {
80+
// Push arguments.
81+
injector
82+
.getArguments()
83+
.forEach(
84+
argument ->
85+
code.pushPrimitiveOrString(
86+
argument.getValue(), argument.getInternalType()));
87+
88+
// Call static method.
89+
code.invokestatic(injector.getContent().clazz, injector.getContent().method);
90+
91+
// Map instructions to offsets.
92+
InjectStrategy.InjectLocation[] injectLocations =
93+
injector
94+
.getInjectStrategy()
95+
.getAllSuitableInjectionLocation(
96+
(ProgramClass) target.clazz, (ProgramMethod) target.method);
97+
for (InjectStrategy.InjectLocation location : injectLocations) {
98+
if (location.shouldInjectBefore()) {
99+
beforeOffsetInsertions
100+
.computeIfAbsent(location.getOffset(), (i_) -> new LinkedList<>())
101+
.add(code.instructions());
102+
} else {
103+
afterInstructionInsertions
104+
.computeIfAbsent(location.getOffset(), (i_) -> new LinkedList<>())
105+
.add(code.instructions());
106+
}
107+
}
108+
});
109+
110+
// Reset code length.
111+
target.method.accept(
112+
target.clazz,
113+
new AllAttributeVisitor(
114+
new AttributeNameFilter(
115+
Attribute.CODE,
116+
new AttributeVisitor() {
117+
@Override
118+
public void visitCodeAttribute(
119+
Clazz clazz, Method method, CodeAttribute codeAttribute) {
120+
editor.reset(codeAttribute.u4codeLength);
121+
}
122+
})));
123+
124+
// Apply injections to the code attribute.
125+
for (Map.Entry<Integer, LinkedList<Instruction[]>> entry :
126+
beforeOffsetInsertions.entrySet()) {
127+
int offset = entry.getKey();
128+
Instruction[] instructions =
129+
entry.getValue().stream().flatMap(Arrays::stream).toArray(Instruction[]::new);
130+
editor.insertBeforeOffset(offset, instructions);
131+
}
132+
133+
for (Map.Entry<Integer, LinkedList<Instruction[]>> entry :
134+
afterInstructionInsertions.entrySet()) {
135+
int offset = entry.getKey();
136+
Instruction[] instructions =
137+
entry.getValue().stream().flatMap(Arrays::stream).toArray(Instruction[]::new);
138+
editor.insertAfterInstruction(offset, instructions);
139+
}
140+
141+
target.method.accept(
142+
target.clazz,
143+
new AllAttributeVisitor(new AttributeNameFilter(Attribute.CODE, editor)));
144+
});
145+
}
146+
147+
private void commitCurrentInjector() {
148+
if (currentInjector.readyToCommit()) {
149+
injectors.add(currentInjector);
150+
currentInjector = new CodeInjector();
151+
}
152+
}
153+
}

0 commit comments

Comments
 (0)