fix(fuzzer): fix link failure, UBSan in fuzz targets, add ASan log capture#597
Conversation
… capture
- callTraceStorage.h: static const -> static constexpr for DROPPED_TRACE_ID
to satisfy ODR when passed by const-ref to unordered_set::find
- buffers.h: add no_sanitize("undefined") to put16, same as put32
- ConfigurationPresets.kt: add log_path=/tmp/asan.log to ASAN_OPTIONS
- test_workflow.yml: upload /tmp/asan.log.* on asan failure (all 4 jobs)
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
CI Test ResultsRun: #27546423637 | Commit:
Status Overview
Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled Failed TestsSummary: Total: 54 | Passed: 53 | Failed: 1 Updated: 2026-06-15 12:49:50 UTC |
|
JDK 25's G1GC reserves heap VA starting just below 2 GB (0x7fff7000), which is exactly where ASan maps its shadow bytes [0x7fff7000-0x10007fff7fff]. For JDK >= 25 ASan tests, force the heap to a low base address (-XX:HeapBaseMinAddress=0x4000000 -Xmx512m -XX:CompressedClassSpaceSize=256m) so the entire JVM footprint stays below the shadow range. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses sanitizer-related CI failures and improves diagnostics for ASan test crashes by fixing a C++ ODR/link issue, suppressing/handling sanitizer reports in fuzz targets, and capturing ASan output as CI artifacts. It also introduces per-configuration JVM args for tests (used to work around an ASan vs JDK 25 address-space conflict).
Changes:
- Fix C++ link failure by making
CallTraceStorage::DROPPED_TRACE_IDconstexpr(inline in headers). - Add per-build-configuration JVM args plumbing and apply JDK 25 ASan workaround JVM flags.
- Persist ASan output via
ASAN_OPTIONS=log_path=...and upload/tmp/asan.log.*artifacts ontestAsanfailures.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| ddprof-lib/src/main/cpp/callTraceStorage.h | Makes DROPPED_TRACE_ID constexpr to avoid ODR-use link failure in fuzz target. |
| ddprof-lib/src/main/cpp/buffers.h | Adds UBSan suppression for put16 misaligned store (review suggests switching to memcpy to avoid UB). |
| build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt | Plumbs BuildConfiguration.testJvmArgs into both Test and Exec-based test runners. |
| build-logic/conventions/src/main/kotlin/com/datadoghq/native/util/PlatformUtils.kt | Adds testJvmMajorVersion() helper for JDK-specific test configuration (bug found in timeout handling). |
| build-logic/conventions/src/main/kotlin/com/datadoghq/native/model/BuildConfiguration.kt | Adds testJvmArgs property with default empty list. |
| build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt | Adds ASan log_path and applies JDK 25 ASan heap-base workaround via testJvmArgs. |
| .github/workflows/test_workflow.yml | Uploads ASan logs as artifacts on ASan test failures across Linux jobs. |
Comments suppressed due to low confidence (1)
ddprof-lib/src/main/cpp/buffers.h:87
put16currently writes tochar[]via a potentially unalignedshort*store and then suppresses UBSan withno_sanitize("undefined"). This still relies on undefined behavior on some architectures and can mask real bugs. Prefer a byte-wise/copy write (e.g.,memcpy) so the code is well-defined and the extra UBSan suppression is unnecessary.
__attribute__((no_sanitize("undefined")))
__attribute__((no_sanitize("bounds")))
void put16(short v) {
assert(_offset + 2 < limit());
*(short *)(_data + _offset) = htons(v);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| val process = ProcessBuilder("$javaHome/bin/java", "-version") | ||
| .redirectErrorStream(true) | ||
| .start() | ||
| val output = process.inputStream.bufferedReader().readText() | ||
| process.waitFor(10, TimeUnit.SECONDS) | ||
| // version line: java version "1.8.0_xxx" or java version "21.0.x" etc. | ||
| val match = Regex("""version "(?:1\.)?(\d+)""").find(output) | ||
| match?.groupValues?.get(1)?.toIntOrNull() ?: 0 |
What does this PR do?:
Fixes fuzz-CI failures and the JDK 25 ASan shadow-memory conflict.
callTraceStorage.h—static const u64 DROPPED_TRACE_IDchanged tostatic constexpr. The fuzz target passes it byconst u64&tounordered_set::find, which ODR-uses it.static const(non-constexpr) requires an out-of-class definition in that case;static constexpris implicitly inline in C++17 and needs none. The linker error was:undefined reference to CallTraceStorage::DROPPED_TRACE_ID.buffers.h— Added__attribute__((no_sanitize("undefined")))toput16. It does an unalignedshort*store to a byte array (same pattern asput32, which already carries this annotation with a comment). UBSan reported:store to misaligned address for type 'short', which requires 2 byte alignment.ConfigurationPresets.kt— Addedlog_path=/tmp/asan.logtoASAN_OPTIONSfor theasanconfig. ASan writes one file per PID (/tmp/asan.log.<pid>), preserving output even when the JVM exits abruptly before Gradle captures stderr.test_workflow.yml— AddedUpload ASan logsartifact steps to all four test jobs (glibc/musl × amd64/aarch64), conditioned onfailure() && matrix.config == 'asan'withif-no-files-found: ignore. This makes the actual ASan report available as a downloadable artifact ontestAsanfailures.JDK 25 ASan shadow-memory conflict — The ASan log captured by change 4 revealed that JDK 25's G1GC reserves heap virtual address space starting just below 2 GB (
0x7fff7000), which is exactly where ASan needs to mmap its shadow bytes[0x7fff7000–0x10007fff7fff]. ASan aborts with "Shadow memory range interleaves with an existing memory mapping" before any test runs.Fix: for JDK ≥ 25 ASan tests, add JVM flags that force the heap to a low base address so the entire JVM footprint stays below the shadow range:
-XX:HeapBaseMinAddress=0x4000000(64 MB) — moves G1GC's heap reservation far below 2 GB-Xmx512m— bounds total heap size so it doesn't grow back into the shadow range-XX:CompressedClassSpaceSize=256m— keeps class space from pushing the total over 2 GBInfrastructure: added
testJvmArgs: ListProperty<String>toBuildConfiguration, atestJvmMajorVersion()helper toPlatformUtils, and wiring throughProfilerTestPluginso both theTesttask and the muslExectask paths pick up the args. Detection runs once at Gradle configuration time viajava -version.Motivation:
The
fuzzCI job was failing with a linker error. Thebuffersfuzzer was hitting a UBSan error. ThetestAsanjob on JDK 25 was aborting before any test ran because ASan could not initialise its shadow memory — a JDK 25-specific regression introduced by G1GC's new heap placement strategy.How to test the change?:
fuzzjob should link and run both fuzz targets without UBSan errors.test-linux-glibc-amd64 (25, asan)andtest-linux-glibc-aarch64 (25, asan)pass (previously failing with shadow-memory abort, now green).testAsanfailure, anasan-logs-*artifact will appear in the run.For Datadog employees: