Address sanitizer

> [!WARNING]
> This is not a good first issue to contribute. Great if you are interested to contribute to scikit-learn 🙏. Please have a look at our [contributing doc](https://scikit-learn.org/dev/developers/contributing.html) and in particular the section [Issues for new contributors](https://scikit-learn.org/dev/developers/contributing.html#new-contributors). 

### Describe the workflow you want to enable

This was discussed a little on issue https://github.com/scikit-learn/scikit-learn/issues/32491 and I wanted to give a bit of context about how to do it (unfortunately I don't think I know your CI workflow well enough to do it myself).

Essentially, address sanitizer is a compiler option which looks for out of bounds reads and writes so should catch errors like reading beyond the end of an array. It slows things down so you definitely don't want to distribute wheels with it enabled, but it may help with testing. It only works if you actually exercise the code so you'll want to build and run the tests. It's probably easiest to do on Linux and with just one Python version - there's likely little benefit to doing it comprehensively.

Undefined behaviour sanitizer is related and can be enabled at the same time. However it probably has less value, and there's some minor bits of undefined behaviour in Cython which you'll have to ignore.

### Describe your proposed solution

Rough outline:

* pick a compiler - `clang` is probably the best but `gcc` or `msvc` will work if you have strong preferences.
* Compile and link with `-fsanitize=address` as a command-line option.
* Run the tests with `ASAN_OPTIONS=detect_leaks=0` set as an environmental variable - Cython definitely doesn't clean everything up on exit so you'll get lots of fairly useless false positives.
* You may need `LD_PRELOAD=$(gcc --print-file-name=libasan.so)` as an environmental variable too.
* Dependency: I don't think it's strictly necessary to build all your dependencies with address sanitizer, however:
   * if you're linking to C++ libraries that use `std::string` and `std::vector` then it's a good idea to build them with it too (otherwise you have to manually disable some checks).
   * I imagine you're doing a lot of your memory allocation in Numpy? In which case it'd probably be a good idea to build Numpy with it too. I'm not the right person to advise on that.

Hopefully that makes some sense - feel ping me if it doesn't work, or close if it isn't something you want to do.


### Additional context

[Numpy doc](https://numpy.org/devdocs/dev/development_advanced_debugging.html#compiler-sanitizers) about compiler sanitizers

[Numpy CI workflow](https://github.com/numpy/numpy/blob/main/.github/workflows/compiler_sanitizers.yml)

COMMENT:
Super useful @da-woods, thanks! I edited your comment and added the numpy doc about compiler sanitizers and the link to numpy CI workflow.

I guess before thinking about having a CI Setup, a first step would be to try to compile things locally and get a feeling of how many false positives we get? Then we can think about whether this would be useful to catch subtle bugs in general and wether we think it's worth adding a CI for it?

Labels: Build / CI