From 96f1c444c84014a314609205f79a2b49452f9590 Mon Sep 17 00:00:00 2001 From: Bruce Bujon Date: Thu, 18 Sep 2025 12:31:56 +0200 Subject: [PATCH 01/12] chore: Clean up unused UnsafeUtils --- .../trace/agent/test/utils/UnsafeUtils.java | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 dd-java-agent/testing/src/main/java/datadog/trace/agent/test/utils/UnsafeUtils.java diff --git a/dd-java-agent/testing/src/main/java/datadog/trace/agent/test/utils/UnsafeUtils.java b/dd-java-agent/testing/src/main/java/datadog/trace/agent/test/utils/UnsafeUtils.java deleted file mode 100644 index e029a970533..00000000000 --- a/dd-java-agent/testing/src/main/java/datadog/trace/agent/test/utils/UnsafeUtils.java +++ /dev/null @@ -1,44 +0,0 @@ -package datadog.trace.agent.test.utils; - -import static datadog.environment.JavaVirtualMachine.isJavaVersionAtLeast; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; - -public class UnsafeUtils { - private UnsafeUtils() {} - - private static void setStaticBooleanFieldViaUnsafe(final Field field, final boolean newValue) - throws Exception { - final Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - final sun.misc.Unsafe unsafe = (sun.misc.Unsafe) unsafeField.get(null); - - unsafe.ensureClassInitialized(field.getDeclaringClass()); - - final Object staticFieldBase = unsafe.staticFieldBase(field); - final long staticFieldOffset = unsafe.staticFieldOffset(field); - unsafe.putBooleanVolatile(staticFieldBase, staticFieldOffset, newValue); - } - - private static void setStaticFieldViaReflection(final Field field, final Object newValue) - throws Exception { - field.setAccessible(true); - final Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - final int origModifiers = field.getModifiers(); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - field.set(null, newValue); - modifiersField.setInt(field, origModifiers); - } - - public static void setStaticBooleanField(final Field field, final boolean newValue) - throws Exception { - if (isJavaVersionAtLeast(12)) { - // since 12 we can't use reflection: http://hg.openjdk.java.net/jdk/jdk/rev/f55a4bc91ef4 - setStaticBooleanFieldViaUnsafe(field, newValue); - } else { - setStaticFieldViaReflection(field, newValue); - } - } -} From e73688ff5af3b1876ca58476b74b6817d008eae6 Mon Sep 17 00:00:00 2001 From: Bruce Bujon Date: Thu, 18 Sep 2025 12:37:55 +0200 Subject: [PATCH 02/12] feat: Add future prohibited methods --- gradle/forbiddenApiFilters/main.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/gradle/forbiddenApiFilters/main.txt b/gradle/forbiddenApiFilters/main.txt index 4993b965ed8..5db4fee5db5 100644 --- a/gradle/forbiddenApiFilters/main.txt +++ b/gradle/forbiddenApiFilters/main.txt @@ -28,3 +28,16 @@ net.bytebuddy.matcher.ElementMatchers#isAbstract() @defaultMessage Avoid using System.out/err to prevent excess logging. To override, add @SuppressForbidden. java.lang.System#out java.lang.System#err + +# prepare for JEP 500 (https://openjdk.org/jeps/500) +@defaultMessage Prepare to Make Final Mean Final - JEP 500 +java.lang.reflect.Field#set(java.lang.Object,java.lang.Object) +java.lang.reflect.Field#setBoolean(java.lang.Object,boolean) +java.lang.reflect.Field#setByte(java.lang.Object,byte) +java.lang.reflect.Field#setChar(java.lang.Object,char) +java.lang.reflect.Field#setShort(java.lang.Object,short) +java.lang.reflect.Field#setInt(java.lang.Object,int) +java.lang.reflect.Field#setLong(java.lang.Object,long) +java.lang.reflect.Field#setFloat(java.lang.Object,float) +java.lang.reflect.Field#setDouble(java.lang.Object,double) +java.lang.invoke.MethodHandles.Lookup#unreflectSetter(java.lang.reflect.Field) From 5ad4f5ee26f6d82cc8250a135d3354b4b1dc4674 Mon Sep 17 00:00:00 2001 From: Bruce Bujon Date: Thu, 18 Sep 2025 14:13:02 +0200 Subject: [PATCH 03/12] feat: Allow current usages --- .../akkahttp/appsec/Bug4304Instrumentation.java | 8 +++++++- .../instrumentation/weaver/WeaverInstrumentation.java | 3 +++ .../datadog/trace/api/iast/InstrumentationBridge.java | 3 +++ .../src/main/java/datadog/trace/util/UnsafeUtils.java | 3 +++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java index b185117f882..2dadc6a71ec 100644 --- a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java +++ b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java @@ -16,13 +16,17 @@ import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.gateway.RequestContextSlot; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import de.thetaphi.forbiddenapis.SuppressForbidden; import java.lang.reflect.Field; import java.util.regex.Pattern; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; -/** See https://github.com/akka/akka-http/issues/4304 */ +/** + * See Duplicated 100 responses if there is + * an exception thrown by the unmarshaller + */ @AutoService(InstrumenterModule.class) public class Bug4304Instrumentation extends InstrumenterModule.AppSec implements Instrumenter.ForTypeHierarchy, @@ -91,6 +95,8 @@ public void methodAdvice(MethodTransformer transformer) { } static class GraphStageLogicAdvice { + // Using Field.set() will be blocked in later Java versions + @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) static void after(@Advice.This GraphStageLogic thiz) throws NoSuchFieldException, IllegalAccessException { diff --git a/dd-java-agent/instrumentation/weaver/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java b/dd-java-agent/instrumentation/weaver/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java index f28a45f03c5..eca7c4b2dd4 100644 --- a/dd-java-agent/instrumentation/weaver/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java +++ b/dd-java-agent/instrumentation/weaver/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java @@ -5,6 +5,7 @@ import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; +import de.thetaphi.forbiddenapis.SuppressForbidden; import java.lang.reflect.Field; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -42,6 +43,8 @@ public void methodAdvice(MethodTransformer transformer) { } public static class SbtTaskCreationAdvice { + // Using Field.set() will be blocked in later Java versions + @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) public static void onTaskCreation( @Advice.This Object sbtTask, @Advice.FieldValue("taskDef") TaskDef taskDef) { diff --git a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java index 7c5c9d1ae3f..84cf554ff17 100644 --- a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java +++ b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java @@ -29,6 +29,7 @@ import datadog.trace.api.iast.sink.XContentTypeModule; import datadog.trace.api.iast.sink.XPathInjectionModule; import datadog.trace.api.iast.sink.XssModule; +import de.thetaphi.forbiddenapis.SuppressForbidden; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -124,6 +125,8 @@ private static M get(final Field field) { } } + // Using Field.set() will be blocked in later Java versions + @SuppressForbidden private static void set(final Field field, final IastModule module) { try { field.set(null, module); diff --git a/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java b/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java index cc0830b68c8..c062bb8fb7c 100644 --- a/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java +++ b/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java @@ -1,5 +1,6 @@ package datadog.trace.util; +import de.thetaphi.forbiddenapis.SuppressForbidden; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import org.slf4j.Logger; @@ -57,6 +58,8 @@ public static T tryShallowClone(T original) { } } + // Using Field.set() will be blocked in later Java versions + @SuppressForbidden private static void cloneFields(Class clazz, Object original, Object clone) throws Exception { for (Field field : clazz.getDeclaredFields()) { if ((field.getModifiers() & Modifier.STATIC) != 0) { From b698f4ffe95238ad1410d0398c07bd14522d497b Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Wed, 21 Jan 2026 13:21:49 -0500 Subject: [PATCH 04/12] Update forbiddenAPIs comments --- gradle/forbiddenApiFilters/main.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/forbiddenApiFilters/main.txt b/gradle/forbiddenApiFilters/main.txt index 85532e84583..334e0d79822 100644 --- a/gradle/forbiddenApiFilters/main.txt +++ b/gradle/forbiddenApiFilters/main.txt @@ -1,7 +1,7 @@ # loads and instantiates a class which may be inefficient depending on context java.lang.Class#forName(java.lang.String) -# String methods which uses regexes for matching +# String methods which use regexes for matching java.lang.String#split(java.lang.String) java.lang.String#split(java.lang.String,int) java.lang.String#replaceAll(java.lang.String,java.lang.String) @@ -34,12 +34,12 @@ java.lang.System#err java.lang.System#getenv() java.lang.System#getenv(java.lang.String) -#Use jdk LongAdder +# use jdk LongAdder @defaultMessage use LongAdder instead of the legacy jctools FixedSizeStripedLongCounter org.jctools.counters.FixedSizeStripedLongCounter -# prepare for JEP 500 (https://openjdk.org/jeps/500) -@defaultMessage Prepare to Make Final Mean Final - JEP 500 +# avoid methods that mutate final fields, as this will soon be disallowed. For more details: https://openjdk.org/jeps/500. +@defaultMessage Avoid mutating final fields (e.g. with methods such as Field::set and MethodHandles.Lookup::unreflectSetter). If the field is not final, override with @SuppressForbidden. java.lang.reflect.Field#set(java.lang.Object,java.lang.Object) java.lang.reflect.Field#setBoolean(java.lang.Object,boolean) java.lang.reflect.Field#setByte(java.lang.Object,byte) From 968946d2573c11fd729386d4d97cdca10d08c388 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Wed, 21 Jan 2026 16:03:04 -0500 Subject: [PATCH 05/12] Add comments where fields may be mutated --- .../java/com/datadog/debugger/agent/CapturedSnapshotTest.java | 1 + .../instrumentation/akkahttp/appsec/Bug4304Instrumentation.java | 2 +- .../src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy | 1 + .../src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy | 1 + .../java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy | 1 + .../trace/instrumentation/weaver/WeaverInstrumentation.java | 2 +- .../smoketest/appsec/springboot/SpringbootApplication.java | 1 + .../main/java/datadog/trace/api/iast/InstrumentationBridge.java | 2 +- .../src/main/java/datadog/trace/util/MethodHandles.java | 1 + internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java | 2 +- 10 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java index e291ba8d8aa..57741a9583a 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java @@ -2845,6 +2845,7 @@ private TestSnapshotListener setupInstrumentTheWorldTransformer( return listener; } + // TODO: determine whether or not the field is final private void setCorrelationSingleton(Object instance) { Class singletonClass = CorrelationAccess.class.getDeclaredClasses()[0]; try { diff --git a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java index 2dadc6a71ec..a6b3a893aec 100644 --- a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java +++ b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java @@ -95,7 +95,7 @@ public void methodAdvice(MethodTransformer transformer) { } static class GraphStageLogicAdvice { - // Using Field.set() will be blocked in later Java versions + // TODO: determine whether or not the field is final @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) static void after(@Advice.This GraphStageLogic thiz) diff --git a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy index c9abe1cd3fa..c7057ff6214 100644 --- a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy @@ -32,6 +32,7 @@ class GrizzlyByteBodyInstrumentationTest extends InstrumentationSpecification { def supplier boolean bodyDone + // TODO: determine whether or not the field is final def prepare(String encoding = null) { _ * mockHttpHeader.attributes >> attributeHolder if (encoding) { diff --git a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy index 8ab0d014c35..64f452381f4 100644 --- a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy @@ -29,6 +29,7 @@ class GrizzlyCharBodyInstrumentationTest extends InstrumentationSpecification { def supplier boolean bodyDone + // TODO: determine whether or not the field is final def setup() { _ * mockHttpHeader.attributes >> attributeHolder 1 * attributeHolder.setAttribute('datadog.intercepted_request_body', Boolean.TRUE) diff --git a/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy b/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy index 7a652ad5927..7c082a2b068 100644 --- a/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy +++ b/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy @@ -31,6 +31,7 @@ class SslSocketTest extends InstrumentationSpecification { injectSysConfig("dd.usm.enabled", "true") } + // TODO: determine whether or not the fields are final def "simple HTTPS request"() { setup: HttpsURLConnection.setDefaultSSLSocketFactory(server.sslContext.getSocketFactory()) diff --git a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java index eca7c4b2dd4..71a6d343eb2 100644 --- a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java +++ b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java @@ -43,7 +43,7 @@ public void methodAdvice(MethodTransformer transformer) { } public static class SbtTaskCreationAdvice { - // Using Field.set() will be blocked in later Java versions + // TODO: determine whether or not the field is final @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) public static void onTaskCreation( diff --git a/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java b/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java index 7c115bd857a..b2f8872189f 100644 --- a/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java +++ b/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java @@ -19,6 +19,7 @@ public static void main(final String[] args) { System.out.println("Started in " + ManagementFactory.getRuntimeMXBean().getUptime() + "ms"); } + // TODO: determine whether or not the field is final private static void activateAppSec() throws Exception { Class agentClass = ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.Agent"); diff --git a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java index 84cf554ff17..3837bd8e009 100644 --- a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java +++ b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java @@ -125,7 +125,7 @@ private static M get(final Field field) { } } - // Using Field.set() will be blocked in later Java versions + // TODO: determine whether or not the field is final @SuppressForbidden private static void set(final Field field, final IastModule module) { try { diff --git a/internal-api/src/main/java/datadog/trace/util/MethodHandles.java b/internal-api/src/main/java/datadog/trace/util/MethodHandles.java index bb0f756bf6f..4d2b82d3675 100644 --- a/internal-api/src/main/java/datadog/trace/util/MethodHandles.java +++ b/internal-api/src/main/java/datadog/trace/util/MethodHandles.java @@ -65,6 +65,7 @@ public MethodHandle privateFieldSetter(String className, String fieldName) { return clazz != null ? privateFieldSetter(clazz, fieldName) : null; } + // TODO: determine whether or not the field is final public MethodHandle privateFieldSetter(Class clazz, String fieldName) { return AccessController.doPrivileged( (PrivilegedAction) diff --git a/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java b/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java index c062bb8fb7c..c6ae4ae4ba8 100644 --- a/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java +++ b/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java @@ -58,7 +58,7 @@ public static T tryShallowClone(T original) { } } - // Using Field.set() will be blocked in later Java versions + // TODO: determine whether or not the fields are final @SuppressForbidden private static void cloneFields(Class clazz, Object original, Object clone) throws Exception { for (Field field : clazz.getDeclaredFields()) { From 1860f68fb727d141bd9154525b5b8a8f853361b9 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Thu, 22 Jan 2026 12:13:32 -0500 Subject: [PATCH 06/12] Mark which usages need changing --- .../java/com/datadog/debugger/agent/CapturedSnapshotTest.java | 2 +- .../akkahttp/appsec/Bug4304Instrumentation.java | 2 +- .../src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy | 1 - .../src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy | 1 - .../java-net-1.8/src/test/groovy/SslSocketTest.groovy | 1 - .../trace/instrumentation/weaver/WeaverInstrumentation.java | 2 +- .../smoketest/appsec/springboot/SpringbootApplication.java | 1 - .../java/datadog/trace/api/iast/InstrumentationBridge.java | 4 +++- .../src/main/java/datadog/trace/util/MethodHandles.java | 1 - .../src/main/java/datadog/trace/util/UnsafeUtils.java | 2 +- 10 files changed, 7 insertions(+), 10 deletions(-) diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java index 57741a9583a..62dff6442d9 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java @@ -2845,7 +2845,7 @@ private TestSnapshotListener setupInstrumentTheWorldTransformer( return listener; } - // TODO: determine whether or not the field is final + // TODO: JEP 500 - avoid mutating final fields private void setCorrelationSingleton(Object instance) { Class singletonClass = CorrelationAccess.class.getDeclaredClasses()[0]; try { diff --git a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java index a6b3a893aec..ab229353408 100644 --- a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java +++ b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java @@ -95,7 +95,7 @@ public void methodAdvice(MethodTransformer transformer) { } static class GraphStageLogicAdvice { - // TODO: determine whether or not the field is final + // TODO: JEP 500 - avoid mutating final fields @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) static void after(@Advice.This GraphStageLogic thiz) diff --git a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy index c7057ff6214..c9abe1cd3fa 100644 --- a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy @@ -32,7 +32,6 @@ class GrizzlyByteBodyInstrumentationTest extends InstrumentationSpecification { def supplier boolean bodyDone - // TODO: determine whether or not the field is final def prepare(String encoding = null) { _ * mockHttpHeader.attributes >> attributeHolder if (encoding) { diff --git a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy index 64f452381f4..8ab0d014c35 100644 --- a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy @@ -29,7 +29,6 @@ class GrizzlyCharBodyInstrumentationTest extends InstrumentationSpecification { def supplier boolean bodyDone - // TODO: determine whether or not the field is final def setup() { _ * mockHttpHeader.attributes >> attributeHolder 1 * attributeHolder.setAttribute('datadog.intercepted_request_body', Boolean.TRUE) diff --git a/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy b/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy index 7c082a2b068..7a652ad5927 100644 --- a/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy +++ b/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy @@ -31,7 +31,6 @@ class SslSocketTest extends InstrumentationSpecification { injectSysConfig("dd.usm.enabled", "true") } - // TODO: determine whether or not the fields are final def "simple HTTPS request"() { setup: HttpsURLConnection.setDefaultSSLSocketFactory(server.sslContext.getSocketFactory()) diff --git a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java index 71a6d343eb2..fe8e60e4fd2 100644 --- a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java +++ b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java @@ -43,7 +43,7 @@ public void methodAdvice(MethodTransformer transformer) { } public static class SbtTaskCreationAdvice { - // TODO: determine whether or not the field is final + // TODO: JEP 500 - avoid mutating final fields @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) public static void onTaskCreation( diff --git a/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java b/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java index b2f8872189f..7c115bd857a 100644 --- a/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java +++ b/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java @@ -19,7 +19,6 @@ public static void main(final String[] args) { System.out.println("Started in " + ManagementFactory.getRuntimeMXBean().getUptime() + "ms"); } - // TODO: determine whether or not the field is final private static void activateAppSec() throws Exception { Class agentClass = ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.Agent"); diff --git a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java index 3837bd8e009..835364fafcf 100644 --- a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java +++ b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java @@ -125,7 +125,9 @@ private static M get(final Field field) { } } - // TODO: determine whether or not the field is final + // Field::set() is forbidden because it may be used to mutate final fields, disallowed by + // https://openjdk.org/jeps/500. + // However, in this case the method is called on a non-final field, so it is safe. @SuppressForbidden private static void set(final Field field, final IastModule module) { try { diff --git a/internal-api/src/main/java/datadog/trace/util/MethodHandles.java b/internal-api/src/main/java/datadog/trace/util/MethodHandles.java index 4d2b82d3675..bb0f756bf6f 100644 --- a/internal-api/src/main/java/datadog/trace/util/MethodHandles.java +++ b/internal-api/src/main/java/datadog/trace/util/MethodHandles.java @@ -65,7 +65,6 @@ public MethodHandle privateFieldSetter(String className, String fieldName) { return clazz != null ? privateFieldSetter(clazz, fieldName) : null; } - // TODO: determine whether or not the field is final public MethodHandle privateFieldSetter(Class clazz, String fieldName) { return AccessController.doPrivileged( (PrivilegedAction) diff --git a/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java b/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java index c6ae4ae4ba8..06cd5f832a0 100644 --- a/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java +++ b/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java @@ -58,7 +58,7 @@ public static T tryShallowClone(T original) { } } - // TODO: determine whether or not the fields are final + // TODO: JEP 500 - avoid mutating final fields @SuppressForbidden private static void cloneFields(Class clazz, Object original, Object clone) throws Exception { for (Field field : clazz.getDeclaredFields()) { From 9aacc3cdc9081b521b6dd9e0c4fe63e38c189995 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Thu, 22 Jan 2026 14:46:08 -0500 Subject: [PATCH 07/12] Actually mark what usages need changing --- .../java/com/datadog/debugger/agent/CapturedSnapshotTest.java | 2 +- .../akkahttp/appsec/Bug4304Instrumentation.java | 2 +- .../src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy | 1 + .../src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy | 1 + .../java-net-1.8/src/test/groovy/SslSocketTest.groovy | 1 + .../trace/instrumentation/weaver/WeaverInstrumentation.java | 2 +- .../smoketest/appsec/springboot/SpringbootApplication.java | 1 + .../java/datadog/trace/api/iast/InstrumentationBridge.java | 4 +--- .../src/main/java/datadog/trace/util/MethodHandles.java | 3 +++ .../src/main/java/datadog/trace/util/UnsafeUtils.java | 2 +- 10 files changed, 12 insertions(+), 7 deletions(-) diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java index 62dff6442d9..57741a9583a 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java @@ -2845,7 +2845,7 @@ private TestSnapshotListener setupInstrumentTheWorldTransformer( return listener; } - // TODO: JEP 500 - avoid mutating final fields + // TODO: determine whether or not the field is final private void setCorrelationSingleton(Object instance) { Class singletonClass = CorrelationAccess.class.getDeclaredClasses()[0]; try { diff --git a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java index ab229353408..a6b3a893aec 100644 --- a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java +++ b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java @@ -95,7 +95,7 @@ public void methodAdvice(MethodTransformer transformer) { } static class GraphStageLogicAdvice { - // TODO: JEP 500 - avoid mutating final fields + // TODO: determine whether or not the field is final @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) static void after(@Advice.This GraphStageLogic thiz) diff --git a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy index c9abe1cd3fa..c7057ff6214 100644 --- a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy @@ -32,6 +32,7 @@ class GrizzlyByteBodyInstrumentationTest extends InstrumentationSpecification { def supplier boolean bodyDone + // TODO: determine whether or not the field is final def prepare(String encoding = null) { _ * mockHttpHeader.attributes >> attributeHolder if (encoding) { diff --git a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy index 8ab0d014c35..64f452381f4 100644 --- a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy @@ -29,6 +29,7 @@ class GrizzlyCharBodyInstrumentationTest extends InstrumentationSpecification { def supplier boolean bodyDone + // TODO: determine whether or not the field is final def setup() { _ * mockHttpHeader.attributes >> attributeHolder 1 * attributeHolder.setAttribute('datadog.intercepted_request_body', Boolean.TRUE) diff --git a/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy b/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy index 7a652ad5927..7c082a2b068 100644 --- a/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy +++ b/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy @@ -31,6 +31,7 @@ class SslSocketTest extends InstrumentationSpecification { injectSysConfig("dd.usm.enabled", "true") } + // TODO: determine whether or not the fields are final def "simple HTTPS request"() { setup: HttpsURLConnection.setDefaultSSLSocketFactory(server.sslContext.getSocketFactory()) diff --git a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java index fe8e60e4fd2..71a6d343eb2 100644 --- a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java +++ b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java @@ -43,7 +43,7 @@ public void methodAdvice(MethodTransformer transformer) { } public static class SbtTaskCreationAdvice { - // TODO: JEP 500 - avoid mutating final fields + // TODO: determine whether or not the field is final @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) public static void onTaskCreation( diff --git a/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java b/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java index 7c115bd857a..b2f8872189f 100644 --- a/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java +++ b/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java @@ -19,6 +19,7 @@ public static void main(final String[] args) { System.out.println("Started in " + ManagementFactory.getRuntimeMXBean().getUptime() + "ms"); } + // TODO: determine whether or not the field is final private static void activateAppSec() throws Exception { Class agentClass = ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.Agent"); diff --git a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java index 835364fafcf..3837bd8e009 100644 --- a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java +++ b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java @@ -125,9 +125,7 @@ private static M get(final Field field) { } } - // Field::set() is forbidden because it may be used to mutate final fields, disallowed by - // https://openjdk.org/jeps/500. - // However, in this case the method is called on a non-final field, so it is safe. + // TODO: determine whether or not the field is final @SuppressForbidden private static void set(final Field field, final IastModule module) { try { diff --git a/internal-api/src/main/java/datadog/trace/util/MethodHandles.java b/internal-api/src/main/java/datadog/trace/util/MethodHandles.java index bb0f756bf6f..70e6c61c4e2 100644 --- a/internal-api/src/main/java/datadog/trace/util/MethodHandles.java +++ b/internal-api/src/main/java/datadog/trace/util/MethodHandles.java @@ -1,6 +1,7 @@ package datadog.trace.util; import datadog.trace.api.telemetry.LogCollector; +// import de.thetaphi.forbiddenapis.SuppressForbidden; import java.lang.invoke.MethodHandle; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -65,6 +66,8 @@ public MethodHandle privateFieldSetter(String className, String fieldName) { return clazz != null ? privateFieldSetter(clazz, fieldName) : null; } + // TODO: determine whether or not the field is final + // @SuppressForbidden public MethodHandle privateFieldSetter(Class clazz, String fieldName) { return AccessController.doPrivileged( (PrivilegedAction) diff --git a/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java b/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java index 06cd5f832a0..c6ae4ae4ba8 100644 --- a/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java +++ b/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java @@ -58,7 +58,7 @@ public static T tryShallowClone(T original) { } } - // TODO: JEP 500 - avoid mutating final fields + // TODO: determine whether or not the fields are final @SuppressForbidden private static void cloneFields(Class clazz, Object original, Object clone) throws Exception { for (Field field : clazz.getDeclaredFields()) { From c997577e236fdbfa88876e9d1e1f2a36fa382ef5 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Thu, 22 Jan 2026 14:47:44 -0500 Subject: [PATCH 08/12] Revert "Actually mark what usages need changing" This reverts commit 9aacc3cdc9081b521b6dd9e0c4fe63e38c189995. --- .../java/com/datadog/debugger/agent/CapturedSnapshotTest.java | 2 +- .../akkahttp/appsec/Bug4304Instrumentation.java | 2 +- .../src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy | 1 - .../src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy | 1 - .../java-net-1.8/src/test/groovy/SslSocketTest.groovy | 1 - .../trace/instrumentation/weaver/WeaverInstrumentation.java | 2 +- .../smoketest/appsec/springboot/SpringbootApplication.java | 1 - .../java/datadog/trace/api/iast/InstrumentationBridge.java | 4 +++- .../src/main/java/datadog/trace/util/MethodHandles.java | 3 --- .../src/main/java/datadog/trace/util/UnsafeUtils.java | 2 +- 10 files changed, 7 insertions(+), 12 deletions(-) diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java index 57741a9583a..62dff6442d9 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java @@ -2845,7 +2845,7 @@ private TestSnapshotListener setupInstrumentTheWorldTransformer( return listener; } - // TODO: determine whether or not the field is final + // TODO: JEP 500 - avoid mutating final fields private void setCorrelationSingleton(Object instance) { Class singletonClass = CorrelationAccess.class.getDeclaredClasses()[0]; try { diff --git a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java index a6b3a893aec..ab229353408 100644 --- a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java +++ b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java @@ -95,7 +95,7 @@ public void methodAdvice(MethodTransformer transformer) { } static class GraphStageLogicAdvice { - // TODO: determine whether or not the field is final + // TODO: JEP 500 - avoid mutating final fields @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) static void after(@Advice.This GraphStageLogic thiz) diff --git a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy index c7057ff6214..c9abe1cd3fa 100644 --- a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyByteBodyInstrumentationTest.groovy @@ -32,7 +32,6 @@ class GrizzlyByteBodyInstrumentationTest extends InstrumentationSpecification { def supplier boolean bodyDone - // TODO: determine whether or not the field is final def prepare(String encoding = null) { _ * mockHttpHeader.attributes >> attributeHolder if (encoding) { diff --git a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy index 64f452381f4..8ab0d014c35 100644 --- a/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/grizzly/grizzly-http-2.3.20/src/test/groovy/GrizzlyCharBodyInstrumentationTest.groovy @@ -29,7 +29,6 @@ class GrizzlyCharBodyInstrumentationTest extends InstrumentationSpecification { def supplier boolean bodyDone - // TODO: determine whether or not the field is final def setup() { _ * mockHttpHeader.attributes >> attributeHolder 1 * attributeHolder.setAttribute('datadog.intercepted_request_body', Boolean.TRUE) diff --git a/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy b/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy index 7c082a2b068..7a652ad5927 100644 --- a/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy +++ b/dd-java-agent/instrumentation/java/java-net/java-net-1.8/src/test/groovy/SslSocketTest.groovy @@ -31,7 +31,6 @@ class SslSocketTest extends InstrumentationSpecification { injectSysConfig("dd.usm.enabled", "true") } - // TODO: determine whether or not the fields are final def "simple HTTPS request"() { setup: HttpsURLConnection.setDefaultSSLSocketFactory(server.sslContext.getSocketFactory()) diff --git a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java index 71a6d343eb2..fe8e60e4fd2 100644 --- a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java +++ b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java @@ -43,7 +43,7 @@ public void methodAdvice(MethodTransformer transformer) { } public static class SbtTaskCreationAdvice { - // TODO: determine whether or not the field is final + // TODO: JEP 500 - avoid mutating final fields @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) public static void onTaskCreation( diff --git a/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java b/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java index b2f8872189f..7c115bd857a 100644 --- a/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java +++ b/dd-smoke-tests/appsec/springboot/src/main/java/datadog/smoketest/appsec/springboot/SpringbootApplication.java @@ -19,7 +19,6 @@ public static void main(final String[] args) { System.out.println("Started in " + ManagementFactory.getRuntimeMXBean().getUptime() + "ms"); } - // TODO: determine whether or not the field is final private static void activateAppSec() throws Exception { Class agentClass = ClassLoader.getSystemClassLoader().loadClass("datadog.trace.bootstrap.Agent"); diff --git a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java index 3837bd8e009..835364fafcf 100644 --- a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java +++ b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java @@ -125,7 +125,9 @@ private static M get(final Field field) { } } - // TODO: determine whether or not the field is final + // Field::set() is forbidden because it may be used to mutate final fields, disallowed by + // https://openjdk.org/jeps/500. + // However, in this case the method is called on a non-final field, so it is safe. @SuppressForbidden private static void set(final Field field, final IastModule module) { try { diff --git a/internal-api/src/main/java/datadog/trace/util/MethodHandles.java b/internal-api/src/main/java/datadog/trace/util/MethodHandles.java index 70e6c61c4e2..bb0f756bf6f 100644 --- a/internal-api/src/main/java/datadog/trace/util/MethodHandles.java +++ b/internal-api/src/main/java/datadog/trace/util/MethodHandles.java @@ -1,7 +1,6 @@ package datadog.trace.util; import datadog.trace.api.telemetry.LogCollector; -// import de.thetaphi.forbiddenapis.SuppressForbidden; import java.lang.invoke.MethodHandle; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -66,8 +65,6 @@ public MethodHandle privateFieldSetter(String className, String fieldName) { return clazz != null ? privateFieldSetter(clazz, fieldName) : null; } - // TODO: determine whether or not the field is final - // @SuppressForbidden public MethodHandle privateFieldSetter(Class clazz, String fieldName) { return AccessController.doPrivileged( (PrivilegedAction) diff --git a/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java b/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java index c6ae4ae4ba8..06cd5f832a0 100644 --- a/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java +++ b/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java @@ -58,7 +58,7 @@ public static T tryShallowClone(T original) { } } - // TODO: determine whether or not the fields are final + // TODO: JEP 500 - avoid mutating final fields @SuppressForbidden private static void cloneFields(Class clazz, Object original, Object clone) throws Exception { for (Field field : clazz.getDeclaredFields()) { From de63e7031b758ace5d6c873e5d9498f75b49a1c9 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Fri, 23 Jan 2026 16:30:54 -0500 Subject: [PATCH 09/12] Add comment to Bug4304Instrumentation --- .../akkahttp/appsec/Bug4304Instrumentation.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java index ab229353408..dcfcfe207cb 100644 --- a/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java +++ b/dd-java-agent/instrumentation/akka/akka-http/akka-http-10.0/src/main/java/datadog/trace/instrumentation/akkahttp/appsec/Bug4304Instrumentation.java @@ -95,7 +95,10 @@ public void methodAdvice(MethodTransformer transformer) { } static class GraphStageLogicAdvice { - // TODO: JEP 500 - avoid mutating final fields + // Field::set() is forbidden because it may be used to mutate final fields, disallowed by + // https://openjdk.org/jeps/500. + // However, in this case the method is called on a non-final field, so it is safe. See + // https://github.com/akka/akka-http/blob/8fb19fce3548c3bfa1e8ebcb1115be29f342df69/akka-http-core/src/main/scala/akka/http/impl/engine/server/HttpServerBluePrint.scala#L588 @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) static void after(@Advice.This GraphStageLogic thiz) From 6e50e18f76fbe181e29a8912084f51fae456eb03 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Tue, 27 Jan 2026 12:03:18 -0500 Subject: [PATCH 10/12] Add log statements for final field mutation --- .../debugger/agent/CapturedSnapshotTest.java | 12 ++++++++++++ .../weaver/WeaverInstrumentation.java | 13 +++++++++++++ .../main/java/datadog/trace/util/UnsafeUtils.java | 9 +++++++++ 3 files changed, 34 insertions(+) diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java index 62dff6442d9..a17c2a5f134 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/CapturedSnapshotTest.java @@ -91,9 +91,13 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import utils.SourceCompiler; public class CapturedSnapshotTest extends CapturingTestBase { + private static final Logger log = LoggerFactory.getLogger(CapturedSnapshotTest.class); + private static final ProbeId PROBE_ID1 = new ProbeId("beae1807-f3b0-4ea8-a74f-826790c5e6f6", 0); private static final ProbeId PROBE_ID2 = new ProbeId("beae1807-f3b0-4ea8-a74f-826790c5e6f7", 0); private static final ProbeId PROBE_ID3 = new ProbeId("beae1807-f3b0-4ea8-a74f-826790c5e6f8", 0); @@ -2851,6 +2855,14 @@ private void setCorrelationSingleton(Object instance) { try { Field instanceField = singletonClass.getDeclaredField("INSTANCE"); instanceField.setAccessible(true); + boolean isFinal = Modifier.isFinal(instanceField.getModifiers()); + if (isFinal) { + log.warn( + "JEP 500: Final field 'INSTANCE' in class '{}' is being mutated. This will soon be disallowed. Field type: {}. Field declaring class: {}.", + singletonClass.getName(), + instanceField.getType().getName(), + instanceField.getDeclaringClass().getName()); + } Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(instanceField, instanceField.getModifiers() & ~Modifier.FINAL); diff --git a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java index fe8e60e4fd2..78c72a5f61d 100644 --- a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java +++ b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java @@ -7,9 +7,12 @@ import datadog.trace.agent.tooling.InstrumenterModule; import de.thetaphi.forbiddenapis.SuppressForbidden; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingQueue; import net.bytebuddy.asm.Advice; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sbt.testing.TaskDef; import weaver.framework.SuiteEvent; @@ -43,6 +46,8 @@ public void methodAdvice(MethodTransformer transformer) { } public static class SbtTaskCreationAdvice { + private static final Logger log = LoggerFactory.getLogger(SbtTaskCreationAdvice.class); + // TODO: JEP 500 - avoid mutating final fields @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) @@ -51,6 +56,14 @@ public static void onTaskCreation( try { Field queueField = sbtTask.getClass().getDeclaredField("queue"); queueField.setAccessible(true); + boolean isFinal = Modifier.isFinal(queueField.getModifiers()); + if (isFinal) { + log.warn( + "JEP 500: Final field 'queue' in class '{}' is being mutated. This will soon be disallowed. Field type: {}. Field declaring class: {}.", + sbtTask.getClass().getName(), + queueField.getType().getName(), + queueField.getDeclaringClass().getName()); + } Object queue = queueField.get(sbtTask); if (queue instanceof ConcurrentLinkedQueue) { // disney's implementation (0.8.4+) uses a ConcurrentLinkedQueue for the field diff --git a/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java b/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java index 06cd5f832a0..6c244fe394d 100644 --- a/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java +++ b/internal-api/src/main/java/datadog/trace/util/UnsafeUtils.java @@ -67,6 +67,15 @@ private static void cloneFields(Class clazz, Object original, Object clone) t } field.setAccessible(true); Object fieldValue = field.get(original); + boolean isFinal = Modifier.isFinal(field.getModifiers()); + if (isFinal) { + log.warn( + "JEP 500: Final field '{}' in cloned object class '{}' is being mutated. This will soon be disallowed. Field type: {}. Field declaring class: {}.", + field.getName(), + clone.getClass().getName(), + field.getType().getName(), + field.getDeclaringClass().getName()); + } field.set(clone, fieldValue); } } From 195cfb7a580f0b8bdf69734b31687216e805649a Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Tue, 27 Jan 2026 16:12:51 -0500 Subject: [PATCH 11/12] Fix weaver instrumentation --- .../weaver/WeaverInstrumentation.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java index 78c72a5f61d..f93008dff8f 100644 --- a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java +++ b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java @@ -46,8 +46,6 @@ public void methodAdvice(MethodTransformer transformer) { } public static class SbtTaskCreationAdvice { - private static final Logger log = LoggerFactory.getLogger(SbtTaskCreationAdvice.class); - // TODO: JEP 500 - avoid mutating final fields @SuppressForbidden @Advice.OnMethodExit(suppress = Throwable.class) @@ -58,11 +56,16 @@ public static void onTaskCreation( queueField.setAccessible(true); boolean isFinal = Modifier.isFinal(queueField.getModifiers()); if (isFinal) { - log.warn( - "JEP 500: Final field 'queue' in class '{}' is being mutated. This will soon be disallowed. Field type: {}. Field declaring class: {}.", - sbtTask.getClass().getName(), - queueField.getType().getName(), - queueField.getDeclaringClass().getName()); + try { + Logger log = LoggerFactory.getLogger(SbtTaskCreationAdvice.class); + log.warn( + "JEP 500: Final field 'queue' in class '{}' is being mutated. This will soon be disallowed. Field type: {}. Field declaring class: {}.", + sbtTask.getClass().getName(), + queueField.getType().getName(), + queueField.getDeclaringClass().getName()); + } catch (Throwable logError) { + // Ignore logging errors to avoid failing instrumentation + } } Object queue = queueField.get(sbtTask); if (queue instanceof ConcurrentLinkedQueue) { From 5e40aba0820d8e57e415a1f2a0e0993af922c8f6 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Tue, 27 Jan 2026 17:07:33 -0500 Subject: [PATCH 12/12] Fix weaver logging --- .../weaver/WeaverInstrumentation.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java index f93008dff8f..11fffe70997 100644 --- a/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java +++ b/dd-java-agent/instrumentation/weaver-0.9/src/main/java/datadog/trace/instrumentation/weaver/WeaverInstrumentation.java @@ -11,7 +11,6 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingQueue; import net.bytebuddy.asm.Advice; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sbt.testing.TaskDef; import weaver.framework.SuiteEvent; @@ -57,13 +56,13 @@ public static void onTaskCreation( boolean isFinal = Modifier.isFinal(queueField.getModifiers()); if (isFinal) { try { - Logger log = LoggerFactory.getLogger(SbtTaskCreationAdvice.class); - log.warn( - "JEP 500: Final field 'queue' in class '{}' is being mutated. This will soon be disallowed. Field type: {}. Field declaring class: {}.", - sbtTask.getClass().getName(), - queueField.getType().getName(), - queueField.getDeclaringClass().getName()); - } catch (Throwable logError) { + LoggerFactory.getLogger("datadog.trace.instrumentation.weaver.WeaverInstrumentation") + .warn( + "JEP 500: Final field 'queue' in class '{}' is being mutated. This will soon be disallowed. Field type: {}. Field declaring class: {}.", + sbtTask.getClass().getName(), + queueField.getType().getName(), + queueField.getDeclaringClass().getName()); + } catch (Throwable ignored) { // Ignore logging errors to avoid failing instrumentation } }