diff --git a/class-inject/build.gradle.kts b/class-inject/build.gradle.kts index ad6c3d3..6a41b36 100644 --- a/class-inject/build.gradle.kts +++ b/class-inject/build.gradle.kts @@ -6,6 +6,9 @@ plugins { dependencies { implementation(libs.asm) implementation(project(":utils")) + + testImplementation(sourceSets["glue"].output) + testImplementation(libs.asm.util) } // class-inject generates glue bytecode at build-time to provide access to Unsafe.defineClass diff --git a/class-inject/src/glue/java/datadog/instrument/glue/DefineClassGlueGenerator.java b/class-inject/src/glue/java/datadog/instrument/glue/DefineClassGlueGenerator.java index 7cca5d0..06c523b 100644 --- a/class-inject/src/glue/java/datadog/instrument/glue/DefineClassGlueGenerator.java +++ b/class-inject/src/glue/java/datadog/instrument/glue/DefineClassGlueGenerator.java @@ -116,7 +116,7 @@ public static void generateGlue(Path resourcePath, Path javaPath) throws IOExcep * @param unsafeNamespace which Unsafe to use for boot injection * @return glue to access {@code ClassLoader.defineClass} */ - private static byte[] generateBytecode(String unsafeNamespace) { + static byte[] generateBytecode(String unsafeNamespace) { // use Unsafe to define boot classes that have no class-loader final String unsafeClass = unsafeNamespace + "/Unsafe"; @@ -172,6 +172,7 @@ private static byte[] generateBytecode(String unsafeNamespace) { final Label defineBootClass = new Label(); final Label storeBootClass = new Label(); final Label loadBootClass = new Label(); + final Label lookupBootClass = new Label(); final Label rethrowOriginal = new Label(); final Label returnDefinedClasses = new Label(); @@ -199,7 +200,7 @@ private static byte[] generateBytecode(String unsafeNamespace) { // fall back and see if the boot class is already loaded if defining it fails mv.visitTryCatchBlock(defineBootClass, storeBootClass, loadBootClass, null); - mv.visitTryCatchBlock(loadBootClass, rethrowOriginal, rethrowOriginal, null); + mv.visitTryCatchBlock(lookupBootClass, rethrowOriginal, rethrowOriginal, null); // -------- SHARED SETUP CODE -------- @@ -389,6 +390,8 @@ private static byte[] generateBytecode(String unsafeNamespace) { // see if the boot class has already been defined mv.visitLabel(loadBootClass); mv.visitVarInsn(ASTORE, originalError); + // must be after originalError is stored, or the handler can't rely on it being set + mv.visitLabel(lookupBootClass); mv.visitVarInsn(ALOAD, className); mv.visitInsn(ICONST_0); // initialize=false mv.visitInsn(ACONST_NULL); diff --git a/class-inject/src/test/java/datadog/instrument/glue/DefineClassGlueGeneratorTest.java b/class-inject/src/test/java/datadog/instrument/glue/DefineClassGlueGeneratorTest.java new file mode 100644 index 0000000..a68e0cc --- /dev/null +++ b/class-inject/src/test/java/datadog/instrument/glue/DefineClassGlueGeneratorTest.java @@ -0,0 +1,25 @@ +package datadog.instrument.glue; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.PrintWriter; +import java.io.StringWriter; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.util.CheckClassAdapter; + +class DefineClassGlueGeneratorTest { + + @ParameterizedTest + @ValueSource(strings = {"sun/misc", "jdk/internal/misc"}) + void generatedBytecodePassesVerification(String unsafeNamespace) { + byte[] bytecode = DefineClassGlueGenerator.generateBytecode(unsafeNamespace); + StringWriter errors = new StringWriter(); + + // perform strict class verification, like -Xverify:all + CheckClassAdapter.verify(new ClassReader(bytecode), false, new PrintWriter(errors)); + + assertThat(errors.toString()).isEmpty(); + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ad54a82..83dab4b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,7 @@ jmh-plugin = "0.7.3" [libraries] asm = { module = "org.ow2.asm:asm", version.ref = "asm" } asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" } +asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } junit-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit-platform" } assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" }