From 2dd177e97dca17df6c05e813b3fc4223fdf5e654 Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Fri, 25 Sep 2026 15:46:25 +0100 Subject: [PATCH 1/2] Confirm the DefineClassGlue constants survive a raw getBytes(UTF_16BE) call --- .../glue/DefineClassGlueGeneratorTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/class-inject/src/test/java/datadog/instrument/glue/DefineClassGlueGeneratorTest.java b/class-inject/src/test/java/datadog/instrument/glue/DefineClassGlueGeneratorTest.java index a68e0cc..aa20491 100644 --- a/class-inject/src/test/java/datadog/instrument/glue/DefineClassGlueGeneratorTest.java +++ b/class-inject/src/test/java/datadog/instrument/glue/DefineClassGlueGeneratorTest.java @@ -1,5 +1,6 @@ package datadog.instrument.glue; +import static java.nio.charset.StandardCharsets.UTF_16BE; import static org.assertj.core.api.Assertions.assertThat; import java.io.PrintWriter; @@ -22,4 +23,21 @@ void generatedBytecodePassesVerification(String unsafeNamespace) { assertThat(errors.toString()).isEmpty(); } + + /** + * ClassInjector decodes this glue via a raw {@code getBytes(UTF_16BE)} call; confirm the + * generated bytecode packs cleanly into surrogate pairs it can safely round-trip. + */ + @ParameterizedTest + @ValueSource(strings = {"sun/misc", "jdk/internal/misc"}) + void generatedBytecodeSurvivesRawUtf16beDecode(String unsafeNamespace) { + byte[] originalBytecode = DefineClassGlueGenerator.generateBytecode(unsafeNamespace); + String packedBytecode = + "sun/misc".equals(unsafeNamespace) ? DefineClassGlue.V8 : DefineClassGlue.V9; + + // mirrors exactly what ClassInjector's generated bytecode does at runtime + byte[] decodedBytecode = packedBytecode.getBytes(UTF_16BE); + + assertThat(decodedBytecode).isEqualTo(originalBytecode); + } } From 312e60dd1d6347d5a852f0360c6c9d2ce48180bd Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Fri, 25 Sep 2026 16:26:34 +0100 Subject: [PATCH 2/2] Fix potential round-trip issue in Glue.unpackBytecode for non-trivial bytecode --- .../datadog/instrument/classinject/ClassInjector.java | 3 ++- utils/src/main/java/datadog/instrument/glue/Glue.java | 10 ++++++++-- .../test/java/datadog/instrument/glue/GlueTest.java | 9 +++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/class-inject/src/main/java/datadog/instrument/classinject/ClassInjector.java b/class-inject/src/main/java/datadog/instrument/classinject/ClassInjector.java index c51390f..a04bbfe 100644 --- a/class-inject/src/main/java/datadog/instrument/classinject/ClassInjector.java +++ b/class-inject/src/main/java/datadog/instrument/classinject/ClassInjector.java @@ -290,7 +290,8 @@ public void visitCode() { mv.visitLdcInsn(DefineClassGlue.V8); } - // unpack the UTF-16BE encoded string back into bytecode + // unpack the UTF-16BE encoded glue string back into bytecode with a single JDK call + // note: this approach is safe because we know the string has no unpaired surrogates mv.visitFieldInsn( GETSTATIC, "java/nio/charset/StandardCharsets", "UTF_16BE", "Ljava/nio/charset/Charset;"); mv.visitMethodInsn( diff --git a/utils/src/main/java/datadog/instrument/glue/Glue.java b/utils/src/main/java/datadog/instrument/glue/Glue.java index 65c3824..2f34374 100644 --- a/utils/src/main/java/datadog/instrument/glue/Glue.java +++ b/utils/src/main/java/datadog/instrument/glue/Glue.java @@ -6,7 +6,6 @@ package datadog.instrument.glue; -import static java.nio.charset.StandardCharsets.UTF_16BE; import static java.util.Objects.requireNonNull; import datadog.instrument.utils.JVM; @@ -26,7 +25,14 @@ private Glue() {} * @return the unpacked bytecode */ public static byte[] unpackBytecode(String bytecode) { - return bytecode.getBytes(UTF_16BE); + int len = bytecode.length(); + byte[] unpacked = new byte[len << 1]; + for (int i = 0, j = 0; i < len; i++) { + char c = bytecode.charAt(i); + unpacked[j++] = (byte) (c >>> 8); + unpacked[j++] = (byte) c; + } + return unpacked; } /** diff --git a/utils/src/test/java/datadog/instrument/glue/GlueTest.java b/utils/src/test/java/datadog/instrument/glue/GlueTest.java index 7959d43..cf71e63 100644 --- a/utils/src/test/java/datadog/instrument/glue/GlueTest.java +++ b/utils/src/test/java/datadog/instrument/glue/GlueTest.java @@ -27,4 +27,13 @@ void missingGlueResource() { assertThrows( MissingResourceException.class, () -> Glue.loadBytecode(GlueTest.class, "missing.glue")); } + + @Test + void unpackBytecodePreservesUnpairedSurrogates() { + // getBytes(UTF_16BE) would replace this lone surrogate with U+FFFD instead of preserving it + String packed = new String(new char[] {0x0102, 0xD800, 0x0304}); + byte[] expected = {0x01, 0x02, (byte) 0xD8, 0x00, 0x03, 0x04}; + + assertArrayEquals(expected, Glue.unpackBytecode(packed)); + } }