From f19c1d3e736c69b774f10de9bbd6c0d9192c3a11 Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Fri, 14 Jul 2023 10:14:28 +0800 Subject: [PATCH] build/refactor: add `enable-only-hello-agent-mode` maven profile; refactor agent and its test code --- .github/workflows/ci.yaml | 40 ++++++++++- hello-agent/pom.xml | 26 ++++++- .../study/hello/agent/HelloAgent.java | 22 +++++- main-runner/pom.xml | 72 +++++++++++++++---- .../java/io/foldright/study/main/Main.java | 8 +++ .../foldright/study/main/StudyAgentMode.java | 23 ++++++ .../io/foldright/study/main/MainTest.java | 11 --- .../java/io/foldright/study/main/MainTest.kt | 36 ++++++++++ pom.xml | 1 + scripts/integration_test.sh | 36 +++++++--- utils/pom.xml | 19 +++++ .../io/foldright/study/agent/utils/Utils.java | 46 ++++++++++++ .../agent/utils/transform/ClassInfo.java | 70 ++++++++++++++++++ .../utils/transform/DispatchTransformer.java | 59 +++++++++++++++ .../ThreadPoolExecutorTransformlet.java | 41 +++++++++++ .../agent/utils/transform/Transformlet.java | 11 +++ world-agent/pom.xml | 26 ++++++- .../study/world/agent/WorldAgent.java | 22 +++++- 18 files changed, 526 insertions(+), 43 deletions(-) create mode 100644 main-runner/src/main/java/io/foldright/study/main/StudyAgentMode.java delete mode 100644 main-runner/src/test/java/io/foldright/study/main/MainTest.java create mode 100644 main-runner/src/test/java/io/foldright/study/main/MainTest.kt create mode 100644 utils/pom.xml create mode 100644 utils/src/main/java/io/foldright/study/agent/utils/Utils.java create mode 100644 utils/src/main/java/io/foldright/study/agent/utils/transform/ClassInfo.java create mode 100644 utils/src/main/java/io/foldright/study/agent/utils/transform/DispatchTransformer.java create mode 100644 utils/src/main/java/io/foldright/study/agent/utils/transform/ThreadPoolExecutorTransformlet.java create mode 100644 utils/src/main/java/io/foldright/study/agent/utils/transform/Transformlet.java diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 544ab55..ba8fecd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -25,16 +25,52 @@ jobs: distribution: temurin # only first java setup need enable cache cache: maven - - name: build and test with Java 17 + - name: build and test with Java 17 without agents run: ./mvnw -V --no-transfer-progress clean install + - name: test under hello and world agents + run: ./mvnw dependency:properties surefire:test + env: + STUDY_AGENT_RUN_MODE: hello-and-world-agents + - name: test under hello agent + run: ./mvnw dependency:properties surefire:test + env: + STUDY_AGENT_RUN_MODE: only-hello-agent + - name: run Main without agents + run: ./mvnw dependency:properties exec:exec -pl main-runner + - name: run Main under hello and world agents + run: ./mvnw dependency:properties exec:exec -pl main-runner + env: + STUDY_AGENT_RUN_MODE: hello-and-world-agents + - name: run Main under hello agent + run: ./mvnw dependency:properties exec:exec -pl main-runner + env: + STUDY_AGENT_RUN_MODE: only-hello-agent - name: setup Java 8 uses: actions/setup-java@v3 with: java-version: 8 distribution: zulu - - name: test with Java 8 + - name: test with Java 8 without agents run: ./mvnw -V --no-transfer-progress dependency:properties surefire:test + - name: test under hello and world agents + run: ./mvnw dependency:properties surefire:test + env: + STUDY_AGENT_RUN_MODE: hello-and-world-agents + - name: test under hello agent + run: ./mvnw dependency:properties surefire:test + env: + STUDY_AGENT_RUN_MODE: only-hello-agent + - name: run Main without agents + run: ./mvnw dependency:properties exec:exec -pl main-runner + - name: run Main under hello and world agents + run: ./mvnw dependency:properties exec:exec -pl main-runner + env: + STUDY_AGENT_RUN_MODE: hello-and-world-agents + - name: run Main under hello agent + run: ./mvnw dependency:properties exec:exec -pl main-runner + env: + STUDY_AGENT_RUN_MODE: only-hello-agent - name: 'remove self maven install files(OS: *nix)' run: rm -rf $HOME/.m2/repository/io/foldright/study* diff --git a/hello-agent/pom.xml b/hello-agent/pom.xml index 53fc339..94ef71f 100644 --- a/hello-agent/pom.xml +++ b/hello-agent/pom.xml @@ -10,7 +10,16 @@ hello-agent + + io.foldright.study.hello.agent + + + + io.foldright.study + utils + ${project.version} + org.javassist javassist @@ -25,10 +34,10 @@ - io.foldright.study.hello.agent.HelloAgent + ${project.root.package}.HelloAgent ${project.build.finalName}.jar - false true + true false @@ -46,17 +55,28 @@ + + io.foldright.study.agent.utils + ${project.root.package}.internal.utils + javassist - io.foldright.study.hello.agent.internal.javassist + ${project.root.package}.internal.javassist + io.foldright.study:utils org.javassist:javassist + + io.foldright.study:utils + + META-INF/MANIFEST.MF + + org.javassist:javassist diff --git a/hello-agent/src/main/java/io/foldright/study/hello/agent/HelloAgent.java b/hello-agent/src/main/java/io/foldright/study/hello/agent/HelloAgent.java index 573ea11..a8a9909 100644 --- a/hello-agent/src/main/java/io/foldright/study/hello/agent/HelloAgent.java +++ b/hello-agent/src/main/java/io/foldright/study/hello/agent/HelloAgent.java @@ -1,12 +1,32 @@ package io.foldright.study.hello.agent; import edu.umd.cs.findbugs.annotations.NonNull; +import io.foldright.study.agent.utils.transform.DispatchTransformer; +import io.foldright.study.agent.utils.transform.ThreadPoolExecutorTransformlet; import java.lang.instrument.Instrumentation; +import java.util.Collections; +import java.util.List; +import static io.foldright.study.agent.utils.transform.ThreadPoolExecutorTransformlet.THREAD_POOL_EXECUTOR_CLASS_NAME; +import static io.foldright.study.agent.utils.Utils.isClassLoaded; + +/** + * a demo agent. + */ public class HelloAgent { + private static final String NAME = "Hello"; + public static void premain(final String agentArgs, @NonNull final Instrumentation inst) { - System.out.println("Hello Agent!"); + System.out.println("[" + NAME + "Agent] Enter premain!"); + + if (isClassLoaded(inst, THREAD_POOL_EXECUTOR_CLASS_NAME)) + throw new IllegalStateException("class " + THREAD_POOL_EXECUTOR_CLASS_NAME + " already loaded"); + + List transformlets = Collections.singletonList( + new ThreadPoolExecutorTransformlet(NAME) + ); + inst.addTransformer(new DispatchTransformer(NAME, transformlets)); } } diff --git a/main-runner/pom.xml b/main-runner/pom.xml index a8b632b..ca88215 100644 --- a/main-runner/pom.xml +++ b/main-runner/pom.xml @@ -13,10 +13,8 @@ io.foldright.study.main.Main - -javaagent:${io.foldright.study:hello-agent:jar} - -Denable.hello.agent=true - -javaagent:${io.foldright.study:world-agent:jar} - -Denable.world.agent=true + -javaagent:${io.foldright.study:hello-agent:jar} + -javaagent:${io.foldright.study:world-agent:jar} @@ -60,17 +58,20 @@ - enable-hello-world-agents + enable-hello-and-world-agents-mode + + + env.STUDY_AGENT_RUN_MODE + hello-and-world-agents + + org.apache.maven.plugins maven-surefire-plugin - @{argLine} - ${agent.opt.hello} ${dopt.enable.agent.hello} - ${agent.opt.world} ${dopt.enable.agent.world} - + @{argLine} ${hello.agent.opt} ${world.agent.opt} @@ -83,10 +84,55 @@ -Duser.language=en -Duser.country=US - ${agent.opt.hello} - ${dopt.enable.agent.hello} - ${agent.opt.world} - ${dopt.enable.agent.world} + + + ${hello.agent.opt} + ${world.agent.opt} + + -classpath + + + ${exec.mainClass} + + + + + + + + enable-only-hello-agent-mode + + + env.STUDY_AGENT_RUN_MODE + only-hello-agent + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + @{argLine} ${hello.agent.opt} + + + + org.codehaus.mojo + exec-maven-plugin + + + -Xms256m + -ea + -Duser.language=en + -Duser.country=US + + + + ${hello.agent.opt} -classpath diff --git a/main-runner/src/main/java/io/foldright/study/main/Main.java b/main-runner/src/main/java/io/foldright/study/main/Main.java index 6ce5d73..e3cc3b4 100644 --- a/main-runner/src/main/java/io/foldright/study/main/Main.java +++ b/main-runner/src/main/java/io/foldright/study/main/Main.java @@ -1,7 +1,15 @@ package io.foldright.study.main; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + + public class Main { public static void main(String[] args) { + ExecutorService executor = Executors.newCachedThreadPool(); + System.out.println("========================================"); System.out.println("Hello World!"); + System.out.println(executor); + System.out.println("========================================"); } } diff --git a/main-runner/src/main/java/io/foldright/study/main/StudyAgentMode.java b/main-runner/src/main/java/io/foldright/study/main/StudyAgentMode.java new file mode 100644 index 0000000..89c8cae --- /dev/null +++ b/main-runner/src/main/java/io/foldright/study/main/StudyAgentMode.java @@ -0,0 +1,23 @@ +package io.foldright.study.main; + +public enum StudyAgentMode { + NO_AGENTS, + HELLO_AND_WORLD_AGENTS, + ONLY_HELLO_AGENT, + + ; + + public static StudyAgentMode getStudyAgentMode() { + final String envVarName = "STUDY_AGENT_RUN_MODE"; + final String value = System.getenv(envVarName); + if (value == null || "".equals(value)) { + return NO_AGENTS; + } else if ("hello-and-world-agents".equals(value)) { + return HELLO_AND_WORLD_AGENTS; + } else if ("only-hello-agent".equals(value)) { + return ONLY_HELLO_AGENT; + } else { + throw new IllegalStateException("Illegal value of env var(" + envVarName + "): " + value); + } + } +} diff --git a/main-runner/src/test/java/io/foldright/study/main/MainTest.java b/main-runner/src/test/java/io/foldright/study/main/MainTest.java deleted file mode 100644 index d348011..0000000 --- a/main-runner/src/test/java/io/foldright/study/main/MainTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.foldright.study.main; - -import org.junit.jupiter.api.Test; - - -class MainTest { - @Test - void hello() { - System.out.println("Hello world!"); - } -} diff --git a/main-runner/src/test/java/io/foldright/study/main/MainTest.kt b/main-runner/src/test/java/io/foldright/study/main/MainTest.kt new file mode 100644 index 0000000..b5bcf02 --- /dev/null +++ b/main-runner/src/test/java/io/foldright/study/main/MainTest.kt @@ -0,0 +1,36 @@ +package io.foldright.study.main + +import io.kotest.assertions.fail +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.string.shouldContainOnlyOnce +import io.kotest.matchers.string.shouldNotContain +import java.util.concurrent.Executors + + +class MainTest : FunSpec({ + test("agent hacked toString of ThreadPoolExecutor") { + val executor = Executors.newCachedThreadPool() as java.util.concurrent.ThreadPoolExecutor + val addedByHelloTransformlet = "[[[AddedByHelloTransformlet]]]" + val addedByWorldTransformlet = "[[[AddedByWorldTransformlet]]]" + + val executorString = executor.toString() + when (val studyAgentMode = StudyAgentMode.getStudyAgentMode()) { + StudyAgentMode.NO_AGENTS -> { + executorString.shouldNotContain(addedByHelloTransformlet) + executorString.shouldNotContain(addedByWorldTransformlet) + } + + StudyAgentMode.HELLO_AND_WORLD_AGENTS -> { + executorString.shouldContainOnlyOnce(addedByHelloTransformlet) + executorString.shouldContainOnlyOnce(addedByWorldTransformlet) + } + + StudyAgentMode.ONLY_HELLO_AGENT -> { + executorString.shouldContainOnlyOnce(addedByHelloTransformlet) + executorString.shouldNotContain(addedByWorldTransformlet) + } + + else -> fail("Unknown StudyAgentMode: $studyAgentMode") + } + } +}) diff --git a/pom.xml b/pom.xml index adc69b9..eb1b0b5 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,7 @@ + utils hello-agent world-agent main-runner diff --git a/scripts/integration_test.sh b/scripts/integration_test.sh index a7906c5..3990bfd 100755 --- a/scripts/integration_test.sh +++ b/scripts/integration_test.sh @@ -52,10 +52,14 @@ jvb::mvn_cmd clean install # test by multiply version jdks ######################################## -for jdk_version in "${JDK_VERSIONS[@]}"; do - # skip default jdk, already tested above - [ "$jdk_version" = "$default_build_jdk_version" ] && continue +# about CI env var +# https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables +if [ "${CI:-}" = true ]; then + CI_MORE_BEGIN_OPTS=jacoco:prepare-agent + CI_MORE_END_OPTS=jacoco:report +fi +for jdk_version in "${JDK_VERSIONS[@]}"; do jh_var_name="JAVA${jdk_version}_HOME" [ -d "${!jh_var_name:-}" ] || cu::die "\$${jh_var_name}(${!jh_var_name:-}) dir is not existed!" export JAVA_HOME="${!jh_var_name}" @@ -63,11 +67,25 @@ for jdk_version in "${JDK_VERSIONS[@]}"; do # just test without build cu::head_line_echo "test with Java $jdk_version: $JAVA_HOME" - # about CI env var - # https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables - if [ "${CI:-}" = true ]; then - jvb::mvn_cmd jacoco:prepare-agent dependency:properties surefire:test jacoco:report - else - jvb::mvn_cmd dependency:properties surefire:test + # skip default jdk, already tested above + if [ "$jdk_version" != "$default_build_jdk_version" ]; then + # just test without build + cu::head_line_echo "test without agents: $JAVA_HOME" + jvb::mvn_cmd ${CI_MORE_BEGIN_OPTS:-} dependency:properties surefire:test ${CI_MORE_END_OPTS:-} fi + + cu::head_line_echo test under hello and world agents + STUDY_AGENT_RUN_MODE=hello-and-world-agents jvb::mvn_cmd ${CI_MORE_BEGIN_OPTS:-} dependency:properties surefire:test ${CI_MORE_END_OPTS:-} + + cu::head_line_echo test under hello agent + STUDY_AGENT_RUN_MODE=only-hello-agent jvb::mvn_cmd ${CI_MORE_BEGIN_OPTS:-} dependency:properties surefire:test ${CI_MORE_END_OPTS:-} + + cu::head_line_echo run Main without agents + jvb::mvn_cmd dependency:properties exec:exec -pl main-runner + + cu::head_line_echo run Main under hello and world agents + STUDY_AGENT_RUN_MODE=hello-and-world-agents jvb::mvn_cmd dependency:properties exec:exec -pl main-runner + + cu::head_line_echo run Main under hello agent + STUDY_AGENT_RUN_MODE=only-hello-agent jvb::mvn_cmd dependency:properties exec:exec -pl main-runner done diff --git a/utils/pom.xml b/utils/pom.xml new file mode 100644 index 0000000..678c95b --- /dev/null +++ b/utils/pom.xml @@ -0,0 +1,19 @@ + + 4.0.0 + + io.foldright.study + java-agent-study + 0.1.0-SNAPSHOT + ../pom.xml + + + utils + + + + org.javassist + javassist + + + diff --git a/utils/src/main/java/io/foldright/study/agent/utils/Utils.java b/utils/src/main/java/io/foldright/study/agent/utils/Utils.java new file mode 100644 index 0000000..51d5652 --- /dev/null +++ b/utils/src/main/java/io/foldright/study/agent/utils/Utils.java @@ -0,0 +1,46 @@ +package io.foldright.study.agent.utils; + +import edu.umd.cs.findbugs.annotations.NonNull; +import org.jetbrains.annotations.NotNull; + +import java.lang.instrument.Instrumentation; +import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + +import static java.security.AccessController.doPrivileged; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toSet; + + +public class Utils { + public static boolean isClassLoaded(@NonNull final Instrumentation inst, @NonNull String className) { + return getLoadedClasses(inst).containsKey(className); + } + + public static boolean isClassLoadedByClassLoader(@NonNull final Instrumentation inst, @NonNull String className, ClassLoader classLoader) { + Map>>> loadedClasses = getLoadedClasses(inst); + if (!loadedClasses.containsKey(className)) return false; + + classLoader = classLoader == null ? NULL_CLASS_LOADER : classLoader; + return loadedClasses.get(className).containsKey(classLoader); + } + + @NotNull + private static Map>>> getLoadedClasses(@NotNull Instrumentation inst) { + return Arrays.stream((Class[]) inst.getAllLoadedClasses()) + .collect(groupingBy(Class::getName, groupingBy(clazz -> { + ClassLoader classLoader = clazz.getClassLoader(); + return classLoader == null ? NULL_CLASS_LOADER : classLoader; + }, toSet()))); + } + + @SuppressWarnings("removal") + private static final ClassLoader NULL_CLASS_LOADER = doPrivileged((PrivilegedAction) () -> new ClassLoader() { + }); + + public static String classFileToName(@NonNull final String classFile) { + return classFile.replace('/', '.'); + } +} diff --git a/utils/src/main/java/io/foldright/study/agent/utils/transform/ClassInfo.java b/utils/src/main/java/io/foldright/study/agent/utils/transform/ClassInfo.java new file mode 100644 index 0000000..77f9e79 --- /dev/null +++ b/utils/src/main/java/io/foldright/study/agent/utils/transform/ClassInfo.java @@ -0,0 +1,70 @@ +package io.foldright.study.agent.utils.transform; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.LoaderClassPath; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + + +class ClassInfo { + private final String className; + private final byte[] classFileBuffer; + private final ClassLoader loader; + + // SuppressFBWarnings for classFileBuffer/loader parameter: + // SuppressFBWarnings for classFileBuffer parameter: + // [ERROR] new com.alibaba.ttl.threadpool.agent.internal.transformlet.ClassInfo(String, byte[], ClassLoader) + // may expose internal representation by storing an externally mutable object + // into ClassInfo.classFileBuffer/loader + public ClassInfo(@NonNull String className, + @NonNull @SuppressFBWarnings({"EI_EXPOSE_REP2"}) byte[] classFileBuffer, + @Nullable @SuppressFBWarnings({"EI_EXPOSE_REP2"}) ClassLoader loader) { + this.className = className; + this.classFileBuffer = classFileBuffer; + this.loader = loader; + } + + @NonNull + public String getClassName() { + return className; + } + + private CtClass ctClass; + + @NonNull + @SuppressFBWarnings({"EI_EXPOSE_REP"}) + // [ERROR] Medium: com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo.getCtClass() + // may expose internal representation + // by returning ClassInfo.ctClass [com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo] + public CtClass getCtClass() throws IOException { + if (ctClass != null) return ctClass; + + final ClassPool classPool = new ClassPool(true); + if (loader == null) { + classPool.appendClassPath(new LoaderClassPath(ClassLoader.getSystemClassLoader())); + } else { + classPool.appendClassPath(new LoaderClassPath(loader)); + } + + final CtClass clazz = classPool.makeClass(new ByteArrayInputStream(classFileBuffer), false); + clazz.defrost(); + + this.ctClass = clazz; + return clazz; + } + + private boolean modified = false; + + public boolean isModified() { + return modified; + } + + public void setModified() { + this.modified = true; + } +} diff --git a/utils/src/main/java/io/foldright/study/agent/utils/transform/DispatchTransformer.java b/utils/src/main/java/io/foldright/study/agent/utils/transform/DispatchTransformer.java new file mode 100644 index 0000000..4cadc6a --- /dev/null +++ b/utils/src/main/java/io/foldright/study/agent/utils/transform/DispatchTransformer.java @@ -0,0 +1,59 @@ +package io.foldright.study.agent.utils.transform; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.lang.instrument.ClassFileTransformer; +import java.security.ProtectionDomain; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import static io.foldright.study.agent.utils.Utils.classFileToName; + + +public final class DispatchTransformer implements ClassFileTransformer { + /** + * "null if no transform is performed", + * see {@code @return} of {@link ClassFileTransformer#transform(ClassLoader, String, Class, ProtectionDomain, byte[])} + */ + @SuppressFBWarnings("MS_PKGPROTECT") + // [ERROR] com.alibaba.ttl.threadpool.agent.TtlTransformer.transform(ClassLoader, String, Class, ProtectionDomain, byte[]) + // may expose internal representation by returning TtlTransformer.NO_TRANSFORM + // the value is null, so there is NO "EI_EXPOSE_REP" problem actually. + public static final byte[] NO_TRANSFORM = null; + + private final String name; + private final Map transformlets; + + public DispatchTransformer(String name, Collection transformlets) { + Map transformletMap = new HashMap<>(); + for (Transformlet transformlet : transformlets) { + for (String className : transformlet.targetClassNames()) { + transformletMap.put(className, transformlet); + } + } + this.name = name; + this.transformlets = transformletMap; + } + + @Override + public byte[] transform(@Nullable final ClassLoader loader, @Nullable final String classFile, final Class classBeingRedefined, + final ProtectionDomain protectionDomain, @NonNull final byte[] classFileBuffer) { + try { + // Lambda has no class file, no need to transform, just return. + if (classFile == null) return NO_TRANSFORM; + System.out.println("[" + name + "Transformer] try transform " + classFile); + + final String className = classFileToName(classFile); + final Transformlet tr = transformlets.get(className); + if (tr == null) return NO_TRANSFORM; + return tr.transform(loader, classFile, classBeingRedefined, protectionDomain, classFileBuffer); + } catch (Exception e) { + System.err.println(e); + e.printStackTrace(); + throw new IllegalStateException(e); + } + } +} diff --git a/utils/src/main/java/io/foldright/study/agent/utils/transform/ThreadPoolExecutorTransformlet.java b/utils/src/main/java/io/foldright/study/agent/utils/transform/ThreadPoolExecutorTransformlet.java new file mode 100644 index 0000000..daf1c51 --- /dev/null +++ b/utils/src/main/java/io/foldright/study/agent/utils/transform/ThreadPoolExecutorTransformlet.java @@ -0,0 +1,41 @@ +package io.foldright.study.agent.utils.transform; + +import javassist.CtMethod; + +import java.security.ProtectionDomain; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + + +public class ThreadPoolExecutorTransformlet implements Transformlet { + public static final String THREAD_POOL_EXECUTOR_CLASS_NAME = "java.util.concurrent.ThreadPoolExecutor"; + + private final String name; + + public ThreadPoolExecutorTransformlet(String name) { + this.name = name; + } + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) throws Exception { + final ClassInfo classInfo = new ClassInfo(THREAD_POOL_EXECUTOR_CLASS_NAME, classFileBuffer, loader); + + // HACK toString method + final CtMethod toString = classInfo.getCtClass().getDeclaredMethod("toString"); + final String code = "$_ = $_ + \"[[[AddedBy" + name + "Transformlet]]]\";"; + toString.insertAfter(code); + log("insertAfter toString method of " + THREAD_POOL_EXECUTOR_CLASS_NAME + ": " + code); + + return classInfo.getCtClass().toBytecode(); + } + + @Override + public Set targetClassNames() { + return new HashSet<>(Collections.singletonList(THREAD_POOL_EXECUTOR_CLASS_NAME)); + } + + private void log(String msg) { + System.out.println("[" + name + "Transformlet] " + msg); + } +} diff --git a/utils/src/main/java/io/foldright/study/agent/utils/transform/Transformlet.java b/utils/src/main/java/io/foldright/study/agent/utils/transform/Transformlet.java new file mode 100644 index 0000000..bb887da --- /dev/null +++ b/utils/src/main/java/io/foldright/study/agent/utils/transform/Transformlet.java @@ -0,0 +1,11 @@ +package io.foldright.study.agent.utils.transform; + +import java.security.ProtectionDomain; +import java.util.Set; + + +public interface Transformlet { + byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) throws Exception; + + Set targetClassNames(); +} diff --git a/world-agent/pom.xml b/world-agent/pom.xml index 85996e1..fbe6106 100644 --- a/world-agent/pom.xml +++ b/world-agent/pom.xml @@ -10,7 +10,16 @@ world-agent + + io.foldright.study.world.agent + + + + io.foldright.study + utils + ${project.version} + org.javassist javassist @@ -25,10 +34,10 @@ - io.foldright.study.world.agent.WorldAgent + ${project.root.package}.WorldAgent ${project.build.finalName}.jar - false true + true false @@ -46,17 +55,28 @@ + + io.foldright.study.agent.utils + ${project.root.package}.internal.utils + javassist - io.foldright.study.world.agent.internal.javassist + ${project.root.package}.internal.javassist + io.foldright.study:utils org.javassist:javassist + + io.foldright.study:utils + + META-INF/MANIFEST.MF + + org.javassist:javassist diff --git a/world-agent/src/main/java/io/foldright/study/world/agent/WorldAgent.java b/world-agent/src/main/java/io/foldright/study/world/agent/WorldAgent.java index 383acd8..4548613 100644 --- a/world-agent/src/main/java/io/foldright/study/world/agent/WorldAgent.java +++ b/world-agent/src/main/java/io/foldright/study/world/agent/WorldAgent.java @@ -1,12 +1,32 @@ package io.foldright.study.world.agent; import edu.umd.cs.findbugs.annotations.NonNull; +import io.foldright.study.agent.utils.transform.DispatchTransformer; +import io.foldright.study.agent.utils.transform.ThreadPoolExecutorTransformlet; import java.lang.instrument.Instrumentation; +import java.util.Collections; +import java.util.List; +import static io.foldright.study.agent.utils.transform.ThreadPoolExecutorTransformlet.THREAD_POOL_EXECUTOR_CLASS_NAME; +import static io.foldright.study.agent.utils.Utils.isClassLoaded; + +/** + * a demo agent. + */ public class WorldAgent { + private static final String NAME = "World"; + public static void premain(final String agentArgs, @NonNull final Instrumentation inst) { - System.out.println("World Agent!"); + System.out.println("[" + NAME + "Agent] Enter premain!"); + + if (isClassLoaded(inst, THREAD_POOL_EXECUTOR_CLASS_NAME)) + throw new IllegalStateException("class " + THREAD_POOL_EXECUTOR_CLASS_NAME + " already loaded"); + + List transformlets = Collections.singletonList( + new ThreadPoolExecutorTransformlet(NAME) + ); + inst.addTransformer(new DispatchTransformer(NAME, transformlets)); } }