From 3fc95cb199e7b89a721b342bd6698a77a38ecac7 Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Mon, 12 Oct 2026 12:25:17 -0400 Subject: [PATCH 1/4] add justfile recipe to run asan tests and fix existing leak --- .justfile | 15 +++++++++++++++ src/ua/array.rs | 8 +++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.justfile b/.justfile index 29b9af32..40a226d0 100644 --- a/.justfile +++ b/.justfile @@ -26,6 +26,21 @@ test: # would exclude the doctests. Specifying none of them gives the desired result. RUST_BACKTRACE=1 cargo test --locked --all-features -- --nocapture +# Run unit tests under AddressSanitizer with leak detection (requires nightly toolchain) +test-asan: + # Doctests are excluded, because they would also need `RUSTDOCFLAGS` to be instrumented. + just _asan test --locked --all-features --bins --lib --tests --examples + +# Run cargo command under AddressSanitizer with leak detection (requires nightly toolchain) +_asan +args: + #!/usr/bin/env bash + set -euo pipefail + # An explicit target keeps `RUSTFLAGS` from applying to build scripts and proc macros. + export CARGO_BUILD_TARGET="$(rustc +nightly -vV | sed -n 's/^host: //p')" + export RUSTFLAGS="-Zsanitizer=address" + export ASAN_OPTIONS="detect_leaks=1" + cargo +nightly {{ args }} + # Set up (and update) development tools setup: # Ignore rustup failures, because not everyone might use it diff --git a/src/ua/array.rs b/src/ua/array.rs index e0e3bc18..c6ee9ddd 100644 --- a/src/ua/array.rs +++ b/src/ua/array.rs @@ -601,15 +601,17 @@ mod tests { // Create array in `open62541`, delete locally. // - let size = LEN; - let ptr = unsafe { UA_Array_new(size, T::data_type()) }.cast::<::Inner>(); + let mut size = LEN; + let mut ptr = + unsafe { UA_Array_new(size, T::data_type()) }.cast::<::Inner>(); // Copy value with allocated data into array to catch double free. let string = CString::new(STRING).unwrap(); *unsafe { ptr.add(POS).as_mut() }.unwrap() = unsafe { UA_NODEID_STRING_ALLOC(0, string.as_ptr()) }; drop(string); - let array: Array = Array::from_raw_parts(size, ptr).unwrap(); + // Take ownership; copying with `from_raw_parts()` would leak the original allocation. + let array: Array = unsafe { Array::move_from_raw_parts(&mut size, &mut ptr) }.unwrap(); assert_eq!(array.len(), LEN); drop(array); From a4f33f7f450ba457c90e794c32b106a686d904bf Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Mon, 12 Oct 2026 12:25:44 -0400 Subject: [PATCH 2/4] add asan CI job --- .github/workflows/test.yaml | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 38861e6f..b8980718 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -162,3 +162,49 @@ jobs: run: >- cargo package --locked --all-features + + asan: + strategy: + # Don't give up on the whole matrix if one variant fails. + fail-fast: false + matrix: + # Only native targets: the sanitizer runtime must run on the build host. + include: + - target: aarch64-apple-darwin + runner_os: macos-15 + + - target: x86_64-unknown-linux-gnu + runner_os: ubuntu-24.04 + + runs-on: ${{ matrix.runner_os }} + + steps: + - name: Install Rust toolchain + # Sanitizers are only available on nightly Rust. + uses: dtolnay/rust-toolchain@nightly + + - name: Install cargo-binstall + uses: cargo-bins/cargo-binstall@main + + - name: Install Cargo helpers + run: >- + cargo binstall just + + # Check out the repository before the remaining steps that depend on it. + # All preceding steps are independent of the repository contents. + - name: Check out repository + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Cache Rust toolchain and build artifacts + uses: Swatinem/rust-cache@v2 + with: + # The cache should not be shared between different workflows, jobs, and targets. + shared-key: ${{ github.workflow }}-${{ github.job }}-${{ matrix.target }}-${{ matrix.runner_os }} + + # The recipe sets the target explicitly, which keeps the sanitizer flags + # away from build scripts and proc macros. + - name: Run tests (bins/lib/tests/examples) with AddressSanitizer + run: >- + just test-asan From 5cb7b848e42f60ab7df7e153fc41c5dc31b4d92d Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Mon, 12 Oct 2026 15:07:01 -0400 Subject: [PATCH 3/4] pin CC toolchain on macOS builds to mitigate ASAN symbol version mismatch at link time --- .github/workflows/test.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b8980718..56b2319c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -190,6 +190,14 @@ jobs: run: >- cargo binstall just + - name: Use LLVM Clang for native code on macOS + if: startsWith(matrix.runner_os, 'macos-') + # Code instrumented by Apple Clang does not link against the sanitizer + # runtime from the LLVM bundled with Rust (see README of `open62541-sys`). + run: | + brew install llvm + echo "CC=$(brew --prefix llvm)/bin/clang" >> "$GITHUB_ENV" + # Check out the repository before the remaining steps that depend on it. # All preceding steps are independent of the repository contents. - name: Check out repository From 843a54c2ababb678a4c1c60d4488f16c412e0a5d Mon Sep 17 00:00:00 2001 From: Carter Canedy Date: Thu, 15 Oct 2026 15:21:48 -0400 Subject: [PATCH 4/4] set `--cfg asan` in the test-asan recipe --- .justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.justfile b/.justfile index 40a226d0..90dd50e3 100644 --- a/.justfile +++ b/.justfile @@ -37,7 +37,7 @@ _asan +args: set -euo pipefail # An explicit target keeps `RUSTFLAGS` from applying to build scripts and proc macros. export CARGO_BUILD_TARGET="$(rustc +nightly -vV | sed -n 's/^host: //p')" - export RUSTFLAGS="-Zsanitizer=address" + export RUSTFLAGS="-Zsanitizer=address --cfg asan" export ASAN_OPTIONS="detect_leaks=1" cargo +nightly {{ args }}