Skip to content

Commit 4827697

Browse files
feat(v26.1-Alpha.5): BytecodeTransformer API with ASM integration
Implements load-time bytecode transformation pipeline with: - BytecodeTransformer API for managing ClassFileTransformers - Support for registration, unregistration, and retransformation - Thread-safe implementation using CopyOnWriteArrayList - ASM 9.7.1 integration for bytecode manipulation - Comprehensive test suite (20+ scenarios) - Support for method injection, field addition, mixin-style weaving Tests: 29 new tests (BytecodeTransformer + ASM integration) All 213 tests passing
1 parent 8460602 commit 4827697

6 files changed

Lines changed: 773 additions & 1 deletion

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package aprism.agent.transform;
2+
3+
import jdk.aprismate.agent.BytecodeTransformer;
4+
5+
import java.lang.instrument.ClassFileTransformer;
6+
import java.lang.instrument.Instrumentation;
7+
import java.lang.instrument.UnmodifiableClassException;
8+
import java.util.ArrayList;
9+
import java.util.Collections;
10+
import java.util.List;
11+
import java.util.Objects;
12+
import java.util.concurrent.CopyOnWriteArrayList;
13+
import java.util.logging.Level;
14+
import java.util.logging.Logger;
15+
16+
/**
17+
* Default implementation of BytecodeTransformer.
18+
* Thread-safe implementation using CopyOnWriteArrayList for transformer storage.
19+
*/
20+
public class DefaultBytecodeTransformer implements BytecodeTransformer {
21+
22+
private static final Logger LOGGER = Logger.getLogger(DefaultBytecodeTransformer.class.getName());
23+
24+
private final Instrumentation instrumentation;
25+
private final CopyOnWriteArrayList<TransformerEntry> transformers;
26+
27+
public DefaultBytecodeTransformer(Instrumentation instrumentation) {
28+
this.instrumentation = Objects.requireNonNull(instrumentation, "instrumentation cannot be null");
29+
this.transformers = new CopyOnWriteArrayList<>();
30+
}
31+
32+
@Override
33+
public void registerTransformer(ClassFileTransformer transformer) {
34+
registerTransformer(transformer, false);
35+
}
36+
37+
@Override
38+
public void registerTransformer(ClassFileTransformer transformer, boolean canRetransform) {
39+
Objects.requireNonNull(transformer, "transformer cannot be null");
40+
41+
TransformerEntry entry = new TransformerEntry(transformer, canRetransform);
42+
transformers.add(entry);
43+
instrumentation.addTransformer(transformer, canRetransform);
44+
45+
LOGGER.log(Level.INFO, "Registered transformer: {0} (canRetransform={1})",
46+
new Object[]{transformer.getClass().getName(), canRetransform});
47+
}
48+
49+
@Override
50+
public boolean unregisterTransformer(ClassFileTransformer transformer) {
51+
Objects.requireNonNull(transformer, "transformer cannot be null");
52+
53+
boolean removed = transformers.removeIf(entry -> entry.transformer == transformer);
54+
if (removed) {
55+
boolean instrumentationRemoved = instrumentation.removeTransformer(transformer);
56+
LOGGER.log(Level.INFO, "Unregistered transformer: {0} (success={1})",
57+
new Object[]{transformer.getClass().getName(), instrumentationRemoved});
58+
return instrumentationRemoved;
59+
}
60+
return false;
61+
}
62+
63+
@Override
64+
public List<ClassFileTransformer> getTransformers() {
65+
List<ClassFileTransformer> result = new ArrayList<>(transformers.size());
66+
for (TransformerEntry entry : transformers) {
67+
result.add(entry.transformer);
68+
}
69+
return Collections.unmodifiableList(result);
70+
}
71+
72+
@Override
73+
public int getTransformerCount() {
74+
return transformers.size();
75+
}
76+
77+
@Override
78+
public void retransformClasses(Class<?>... classes) {
79+
if (classes == null || classes.length == 0) {
80+
throw new IllegalArgumentException("classes cannot be null or empty");
81+
}
82+
83+
if (!isRetransformSupported()) {
84+
throw new UnsupportedOperationException("Retransformation is not supported");
85+
}
86+
87+
try {
88+
instrumentation.retransformClasses(classes);
89+
LOGGER.log(Level.INFO, "Retransformed {0} classes", classes.length);
90+
} catch (UnmodifiableClassException e) {
91+
LOGGER.log(Level.SEVERE, "Failed to retransform classes", e);
92+
throw new RuntimeException("Retransformation failed", e);
93+
}
94+
}
95+
96+
@Override
97+
public boolean isRetransformSupported() {
98+
return instrumentation.isRetransformClassesSupported();
99+
}
100+
101+
@Override
102+
public void clearTransformers() {
103+
for (TransformerEntry entry : transformers) {
104+
instrumentation.removeTransformer(entry.transformer);
105+
}
106+
transformers.clear();
107+
LOGGER.log(Level.INFO, "Cleared all transformers");
108+
}
109+
110+
/**
111+
* Internal entry holding transformer and its retransform capability.
112+
*/
113+
private static class TransformerEntry {
114+
final ClassFileTransformer transformer;
115+
final boolean canRetransform;
116+
117+
TransformerEntry(ClassFileTransformer transformer, boolean canRetransform) {
118+
this.transformer = transformer;
119+
this.canRetransform = canRetransform;
120+
}
121+
}
122+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package jdk.aprismate.agent;
2+
3+
import java.lang.instrument.ClassFileTransformer;
4+
import java.util.List;
5+
6+
/**
7+
* Manages bytecode transformers for load-time class transformation.
8+
* Provides a simplified API for registering and managing ClassFileTransformers
9+
* with support for ordering and conditional transformation.
10+
*
11+
* @since AprismJDK 26.1-Alpha.5
12+
*/
13+
public interface BytecodeTransformer {
14+
15+
/**
16+
* Registers a class file transformer for load-time bytecode transformation.
17+
* Transformers are invoked in registration order during class loading.
18+
*
19+
* @param transformer the transformer to register
20+
* @throws IllegalArgumentException if transformer is null
21+
*/
22+
void registerTransformer(ClassFileTransformer transformer);
23+
24+
/**
25+
* Registers a class file transformer with retransformation capability.
26+
* When canRetransform is true, the transformer can be applied to already loaded classes.
27+
*
28+
* @param transformer the transformer to register
29+
* @param canRetransform whether this transformer can retransform classes
30+
* @throws IllegalArgumentException if transformer is null
31+
*/
32+
void registerTransformer(ClassFileTransformer transformer, boolean canRetransform);
33+
34+
/**
35+
* Unregisters a previously registered transformer.
36+
* After unregistration, the transformer will no longer be invoked for new class loads.
37+
*
38+
* @param transformer the transformer to unregister
39+
* @return true if the transformer was found and removed, false otherwise
40+
*/
41+
boolean unregisterTransformer(ClassFileTransformer transformer);
42+
43+
/**
44+
* Gets all currently registered transformers in registration order.
45+
*
46+
* @return immutable list of registered transformers
47+
*/
48+
List<ClassFileTransformer> getTransformers();
49+
50+
/**
51+
* Gets the number of currently registered transformers.
52+
*
53+
* @return transformer count
54+
*/
55+
int getTransformerCount();
56+
57+
/**
58+
* Retransforms the specified classes using all retransform-capable transformers.
59+
* This allows modification of already loaded classes.
60+
*
61+
* @param classes the classes to retransform
62+
* @throws IllegalArgumentException if classes array is null or empty
63+
* @throws UnsupportedOperationException if retransformation is not supported
64+
*/
65+
void retransformClasses(Class<?>... classes);
66+
67+
/**
68+
* Checks if retransformation is supported by the current JVM.
69+
*
70+
* @return true if retransformation is supported
71+
*/
72+
boolean isRetransformSupported();
73+
74+
/**
75+
* Clears all registered transformers.
76+
* This is primarily useful for testing or cleanup scenarios.
77+
*/
78+
void clearTransformers();
79+
}

aprismate-tests/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ dependencies {
77
testImplementation project(':aprismate-api')
88
testImplementation "org.junit.jupiter:junit-jupiter:${junitVersion}"
99
testImplementation "org.assertj:assertj-core:${assertjVersion}"
10+
testImplementation "org.mockito:mockito-core:5.11.0"
11+
testImplementation "org.mockito:mockito-junit-jupiter:5.11.0"
12+
testImplementation "org.ow2.asm:asm:${asmVersion}"
13+
testImplementation "org.ow2.asm:asm-commons:${asmVersion}"
14+
testImplementation "org.ow2.asm:asm-tree:${asmVersion}"
15+
testImplementation "org.ow2.asm:asm-util:${asmVersion}"
1016
}
1117

1218
test {

0 commit comments

Comments
 (0)