Fix IllegalFormatConversionException StringModuleImpl#onStringFormat by jandro996 · Pull Request #9907 · DataDog/dd-trace-java

What Does This Do

Fixes IllegalFormatConversionException in IAST when Scala's BigDecimal/BigInt are used with String.format().

Added unwrapScalaNumbers() in StringOpsCallSite that:

  • Detects scala.math.ScalaNumber instances via reflection
  • Calls underlying() method to extract Java numeric types (java.math.BigDecimal, java.math.BigInteger)
  • Applies unwrapping before passing arguments to onStringFormat

This ensures type compatibility while preserving IAST taint tracking.

Enhanced formatValue() error handling to catch IllegalFormatException and log telemetry with parameter type information. This provides context for detecting similar format conversion bugs in the future while maintaining existing exception behavior.

Motivation

Error tracking report

stack trace

java.util.IllegalFormatConversionException
  at java.base/java.util.Formatter$FormatSpecifier.failConversion(Unknown Source)
  at java.base/java.util.Formatter$FormatSpecifier.printFloat(Unknown Source)
  at java.base/java.util.Formatter$FormatSpecifier.print(Unknown Source)
  at java.base/java.util.Formatter.format(Unknown Source)
  at java.base/java.util.Formatter.format(Unknown Source)
  at java.base/java.lang.String.format(Unknown Source)
  at com.datadog.iast.propagation.StringModuleImpl.onStringFormat(StringModuleImpl.java:537)
  at com.datadog.iast.propagation.StringModuleImpl.onStringFormat(StringModuleImpl.java:487)
  at datadog.trace.instrumentation.scala.StringOpsCallSite.afterInterpolation(StringOpsCallSite.java:50)
  at (redacted: 22 frames)
  at java.base/java.util.concurrent.ForkJoinTask.doExec(Unknown Source)
  at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(Unknown Source)
  at java.base/java.util.concurrent.ForkJoinPool.scan(Unknown Source)
  at java.base/java.util.concurrent.ForkJoinPool.runWorker(Unknown Source)
  at java.base/java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source)

Additional Notes

Scala's String.format() internally calls unwrapArg() to convert scala.math.BigDecimal → java.math.BigDecimal before formatting. However, IAST's @CallSite.After interceptor captures arguments after Scala execution completes, receiving the original Scala types. This causes IllegalFormatConversionException when StringModuleImpl#formatValue attempts to format with incompatible types.

Contributor Checklist

Jira ticket: APPSEC-59883