Thanks to visit codestin.com
Credit goes to github.com

Skip to content

check: add sanitize option#12282

Open
danielrainer wants to merge 1 commit intofish-shell:masterfrom
danielrainer:sanitizer_script
Open

check: add sanitize option#12282
danielrainer wants to merge 1 commit intofish-shell:masterfrom
danielrainer:sanitizer_script

Conversation

@danielrainer
Copy link

@danielrainer danielrainer commented Jan 5, 2026

When invoked with the --sanitize option, the check.sh script will
run the checks with address and leak sanitation enabled. We already run
similar checks in CI, so it's desirable to have them available locally
as well. The check xtask also supports the --sanitize flag.

The code is assembled from the deleted jammy-asan.Dockerfile and the
ubuntu-asan job in .github/workflows/test.yml. The configuration in
these two sources is not entirely consistent, so we should take care to
find out which options we want to use. They should be the same locally
and in CI.

While we could resurrect a Dockerfile for running these checks, it adds
significant overhead, while not providing much value, so I think
integrating it into check.sh makes more sense.

TODO

  • Document the existence of the new script somewhere?

@danielrainer
Copy link
Author

The reported leak in the Fluent code can be suppressed by adding

leak:fish_fluent::AVAILABLE_LANGUAGES

to the lsan_suppressions.txt file. I'm fairly confident that this is a false positive, since the closure in LazyLock::new() should only run once and the leaked strings remain accessible, but ideally someone else should check if that's really the case.

@krobelus krobelus added this to the fish 4.4 milestone Jan 6, 2026
@krobelus
Copy link
Contributor

krobelus commented Jan 6, 2026 via email

export FISH_TEST_MAX_CONCURRENCY=4

export RUSTFLAGS=-Zsanitizer=address
export RUSTDOCFLAGS=-Zsanitizer=address
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I think this is okay to add, these flags don't change very often.

As written, it's not really reusable, since it only works with check.sh.
When we want that, we well separate the check.sh-specific ones in an extra file.
Then we can replace them witha "cargo build" command.
That means we should group the variables needed only at compile time like RUSTFLAGS and RUSTDOCFLAGS before the options needed only at runtime like FISH_CI_SAN

Copy link
Author

Choose a reason for hiding this comment

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

I pushed a version which puts the env vars needed at build time first.

I'm not sure if I entirely understand which kinds of reusability you have in mind. Are you suggesting to have a script which only sets options for enabling the sanitizer and then calls some configurable script/cargo command to run with these options set? And should changes beyond variable reordering be made in this PR?

From my side, these are the outstanding points to consider:

  • Right now, we set fast_unwind_on_malloc=0 in CI, but not in the script added here (because I took the options from the Dockerfile which did not include this). Do we want to set this option?
  • The CI version contains comments about the use_tls option, which might no longer be relevant.
  • I tried removing the leak:AsanThread suppression and did not get any errors. Should we remove it (assuming it also causes no issues in CI)?
  • ASAN_SYMBOLIZER_PATH is set in a somewhat hacky way in CI (541a069). Is this something we need to consider doing in the script as well? Or alternatively, is it still needed in CI?
  • Are there any other changes we want to make to the sanitizer options? It seems like they were last touched before the Rust rewrite.
  • Do we want documentation about the new sanitize script?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if I entirely understand which kinds of reusability you have in mind. Are you suggesting to have a script which only sets options for enabling the sanitizer and then calls some configurable script/cargo command to run with these
+options set? And should changes beyond variable reordering be made in this PR?

it would be nice to have a single source of truth for both CMake and Cargo-based builds
(source-able shell scripts for build-time and run-time variables?)
but asan is not really important for Rust and the config doesn't change a lot, so I'm fine with some duplication, and taking this as-is if it helps.
Maybe we should even remove asan, it should only help unsafe code.

From my side, these are the outstanding points to consider:

  • Right now, we set fast_unwind_on_malloc=0 in CI, but not in the script added here (because I took the options from the Dockerfile which did not include this). Do we want to set this option?

that sounds like it should be kept for CI (slowness doesn't matter as much).

  • The CI version contains comments about the use_tls option, which might no longer be relevant.
  • I tried removing the leak:AsanThread suppression and did not get any errors. Should we remove it (assuming it also causes no issues in CI)?

not sure but if doubt, we can remove both.

  • ASAN_SYMBOLIZER_PATH is set in a somewhat hacky way in CI (541a069). Is this something we need to consider doing in the script as well? Or alternatively, is it still needed in CI?

that's a workaround for Ubuntu not having llvm-symbolizer on PATH.
No strong opinion here.

  • Do we want documentation about the new sanitize script?

the best documentation would be if the github workflow used it.
We can wait until then.

Copy link
Author

Choose a reason for hiding this comment

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

source-able shell scripts for build-time and run-time variables?

That seems like a good idea if we keep using asan.

Maybe we should even remove asan, it should only help unsafe code.

We do have some unsafe blocks. Most of it seems to be wrappers around libc calls. Some of them could probably be replaced by nix crate wrappers. Not sure how useful asan actually is for fish. Has it found any issues in fish's Rust code before?

@krobelus
Copy link
Contributor

krobelus commented Jan 12, 2026 via email

@danielrainer danielrainer force-pushed the sanitizer_script branch 2 times, most recently from 310800f to ae669aa Compare January 21, 2026 19:03
@danielrainer
Copy link
Author

I extracted a script for setting the relevant env vars. For the CI job, it's not ideal, because RUSTFLAGS=-Zsanitizer=address needs to be set for the build step, which still happens separately. Ideally, we'd have the script set variables for the entire job, but I'm not sure if that's possible. Alternatively, we could combine steps, such that building and running tests happens in the same step. Another option is to stop supporting asan with CMake and use Cargo-only builds for the CI job.

Now that xtasks are on master, I also added one for sanitization. Not sure if we should keep the sanitize.sh script or put its functionality into the xtask.

@danielrainer danielrainer marked this pull request as ready for review January 30, 2026 18:34
@krobelus
Copy link
Contributor

Ideally, we'd have the script set variables for the entire job, but I'm not sure if that's possible.

I think we can add . "build_tools/set_asan_vars.sh" at the beginning of every job that needs it.
Ideally we'd turn RUSTFLAGS into a CMake variable so that a change in RUSTFLAGS invalidates
builds; then we'd only need to set RUSTFLAGS/RUSTDOCFLAGS at configure-time.

Otherwise we can set it at build-time.
And set the runtime options as usual.

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index afc9eab2cb..538f4a7457 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -71,14 +71,6 @@

   ubuntu-asan:
     runs-on: ubuntu-latest
-    env:
-        # Rust has two different memory sanitizers of interest; they can't be used at the same time:
-        # * AddressSanitizer detects out-of-bound access, use-after-free, use-after-return,
-        #   use-after-scope, double-free, invalid-free, and memory leaks.
-        # * MemorySanitizer detects uninitialized reads.
-        #
-        RUSTFLAGS: "-Zsanitizer=address"
-        # RUSTFLAGS: "-Zsanitizer=memory -Zsanitizer-memory-track-origins"
     steps:
     - uses: actions/checkout@v6
     # All -Z options require running nightly
@@ -104,11 +96,12 @@
         cmake .. -DASAN=1 -DRust_CARGO_TARGET=x86_64-unknown-linux-gnu -DCMAKE_BUILD_TYPE=Debug
     - name: make
       run: |
+        . "$PWD/build_tools/set_asan_vars.sh" build-time
         make -C build VERBOSE=1
     - name: make fish_run_tests
       run: |
         set -x
-        . "$PWD/build_tools/set_asan_vars.sh"
+        . "$PWD/build_tools/set_asan_vars.sh" run-time
         export ASAN_SYMBOLIZER_PATH=$(command -v /usr/bin/llvm-symbolizer* | sort -n | head -1)
         make -C build VERBOSE=1 fish_run_tests

Alternatively, we could combine steps, such that building and running tests happens in the same step.

that would be okay too

Another option is to stop supporting asan with CMake and use Cargo-only builds for the CI job.

yeah that's a good direction. But no rush; if we get rid of CMake completely, this will happen automatically.

Now that xtasks are on master, I also added one for sanitization.

wouldn't check.sh --sanitize and cargo xtask check --sanitize or cargo xtask check-sanitize be more obvious?
One issue with the script is that it assumes x86_64-unknown-linux-gnu.
Can probably use $(rustc --print host-tuple) instead.

Not sure if we should keep the sanitize.sh script or put its functionality into the xtask.

xtask would be good though maybe we should do that for check.sh first.

@danielrainer
Copy link
Author

I changed the approach to make --sanitize an option of the check.sh script, and by extension the xtask, so we no longer have a dedicated script/xtask for it.

I mostly adopted your diff for CI, except for passing arguments to the sourced script, because I don't think it's really necessary to complicate the script. The variables set for one step shouldn't interfere with the other step.

One thing I'm not entirely sure about is whether sanitize is the best name, since there are other types of sanitizers, cf. #12360.

@danielrainer danielrainer changed the title check: add sanitizer script check: add sanitize option Feb 4, 2026
@krobelus krobelus modified the milestones: fish 4.4, fish 4.5 Feb 6, 2026
export FISH_CHECK_RUST_TOOLCHAIN=nightly
export FISH_CHECK_CARGO_ARGS='-Zbuild-std'
# Build fails if this is not set.
FISH_CHECK_TARGET_TRIPLE="$(rustc --print host-tuple)"
Copy link
Contributor

Choose a reason for hiding this comment

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

we could maybe fail if any of these three variables is already set to non-empty.
We're unlikely to need to support that case but it can't hurt to prevent bad surprises.

Copy link
Author

Choose a reason for hiding this comment

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

How about only setting them when they are undefined, and for FISH_CHECK_CARGO_ARGS prepending -Zbuild-std? This would allow testing with non-default values, and if invalid values are used compilation will fail, so there is not much gained by moving the failure into our script.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok that sounds reasonable as well

export FISH_CHECK_CARGO_ARGS='-Zbuild-std'
# Build fails if this is not set.
FISH_CHECK_TARGET_TRIPLE="$(rustc --print host-tuple)"
export FISH_CHECK_TARGET_TRIPLE
Copy link
Contributor

Choose a reason for hiding this comment

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

any reason for not merging assignment + export?

Copy link
Author

Choose a reason for hiding this comment

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

# Variables used at runtime

export FISH_CHECK_LINT=false
export FISH_TEST_MAX_CONCURRENCY=4
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe these two should not override user preference,
so maybe only set them to the smart default if unset or empty

export FISH_CHECK_LINT=${FISH_CHECK_LINT:-false}
export FISH_TEST_MAX_CONCURRENCY=${FISH_TEST_MAX_CONCURRENCY:-4}

it's a tradeoff of course but this seems the least surprising behavior.

It should not be annoying because we shouldn't really be in a situation where multiple devs want to have these variables set globally.
We can avoid that by picking good defaults.

Copy link
Author

Choose a reason for hiding this comment

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

I agree that for FISH_CHECK_LINT, there is no good reason to override its value. One could even argue that there is no need to default to false, which might be less surprising and would mean that when the --sanitize flag is added, everything that is checked without it would still be checked.

For FISH_TEST_MAX_CONCURRENCY it was useful to have it set globally, but now that the test driver sets a reasonable default I think it makes sense to not override the var's value for asan checks.

Copy link
Author

Choose a reason for hiding this comment

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

Hm, I just tested my suggested changes and noticed that if FISH_CHECK_LINT is not set to false, cargo deny fails when -Zbuild-std is passed to it. So we can

  • always disable lints when --sanitize is passed
  • fail when FISH_CHECK_LINT is set to a value other than false when --sanitize is passed
  • build a more complex interface for specifying which cargo command gets which flags

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would make sense to add a separate variable to house -Zbuild-std, and pass that only to the cargo commands that can stomach it.

But the other options sound fine as well; if it occasionally causes temporary inconvenience to users of sanitize builds, that's fine since we very rarely use those.

Copy link
Author

Choose a reason for hiding this comment

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

The general problem with our approach of setting cargo arguments via a shared env var is that different subcommands take different options, but our cargo function blindly pastes options into the command line without taking the subcommand into account. If we want a non-hacky, general solution to this problem, I think we need to stop using the cargo function or make it subcommand-aware.

For the user interface, it depends whether we want to allow users to interactively specify arbitrary options, or whether we just want other scripts to be able to set specific, predetermined options. In the former case, it might make sense to have one env var per cargo command, which would allow precise control over which options are used where. In the latter case, it might be more sensible to have variables for specific options, and activate the options for the commands where they make sense if the corresponding variables are defined.

But I think we can address this separately, maybe when rewriting the check script in Rust. Since the sanitize feature is new, we won't break existing workflows, so I don't think it matters much that linting and sanitizing are not supported together for now. If we add support for it later, that wouldn't break things either, unless we change how env vars are used, but that would be a breaking change regardless, assuming anyone sets these variables.

When invoked with the `--sanitize` option, the `check.sh` script will
run the checks with address and leak sanitation enabled. We already run
similar checks in CI, so it's desirable to have them available locally
as well. The `check` xtask also supports the `--sanitize` flag.

The code is assembled from the deleted `jammy-asan.Dockerfile` and the
`ubuntu-asan` job in `.github/workflows/test.yml`. The configuration in
these two sources is not entirely consistent, so we should take care to
find out which options we want to use. They should be the same locally
and in CI.

While we could resurrect a Dockerfile for running these checks, it adds
significant overhead, while not providing much value, so I think
integrating it into `check.sh` makes more sense.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants