Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,57 @@ 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
Comment on lines +173 to +177

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we switch to macos-26 and ubuntu-26.04 after the merge of #365?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using -latest for both runner_os?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would move the new check to latest-dependencies.yaml. I don't think that's the right spot, though, because that workflow specifically checks whether the library still builds on the latest CI environments and dependencies (running cargo update first), thus mixing the two cases.

So, I'd continue to use fixed versions in test.yaml, to prevent sudden unexpected failures in the CI due to newly-released runner environments.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we should add a comment to keep the runners pinned at the latest available image, currently macos-26 and ubuntu-26.04.


runs-on: ${{ matrix.runner_os }}

steps:
- name: Install Rust toolchain
# Sanitizers are only available on nightly Rust.
uses: dtolnay/rust-toolchain@nightly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this is going to be a complete noop practically. If the target triple is set to something else other than the system's target triple, it's either going to run fine (e.g. x86_64-linux-unknown-musl vs -gnu) or it's going to have an incompatible ABI and running it is going to fail

@uklotzde uklotzde Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sgoll Thoughts?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the extra verification is necessary. All the other cargo commands inside our CI workflows don't run such a check either. matrix.target is only ever used to set up the toolchain itself. Therefore, the current change (without this check) is fine, and cargo takes the toolchain's target itself—just as it does in the other steps and jobs.


- name: Install cargo-binstall
uses: cargo-bins/cargo-binstall@main

- name: Install Cargo helpers
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
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
15 changes: 15 additions & 0 deletions .justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 --cfg asan"
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
Expand Down
8 changes: 5 additions & 3 deletions src/ua/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<<T as DataType>::Inner>();
let mut size = LEN;
let mut ptr =
unsafe { UA_Array_new(size, T::data_type()) }.cast::<<T as DataType>::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<T> = Array::from_raw_parts(size, ptr).unwrap();
// Take ownership; copying with `from_raw_parts()` would leak the original allocation.
let array: Array<T> = unsafe { Array::move_from_raw_parts(&mut size, &mut ptr) }.unwrap();
assert_eq!(array.len(), LEN);

drop(array);
Expand Down
Loading