Skip to content

Testing with sanitizers

Szabolcs Horvát edited this page May 30, 2023 · 5 revisions

Building R with sanitizers

The simplest way to test R packages with sanitizers is to build R itself with sanitizers as well.

Obtaining the sources

Basic instructions for building R are here: https://github.com/wch/r-source/wiki

Clone the repo:

git clone https://github.com/wch/r-source.git
cd r-source

Optionally check out the branch corresponding to a specific version.

Get recommended packages:

tools/rsync-recommended

Setting up sanitizers

If you have an /.R/Makevars file, temporarily disable it by renaming it.

Set environment variables for the C, C++ and Objective C compilers. Objective C is only necessary on macOS.

export CC="ccache clang-mp-16 -fsanitize=address" CXX="ccache clang++-mp-16 -fsanitize=address" OBJC="ccache clang-mp-16 -fsanitize=address"

In this example we use MacPorts's version of Clang, named clang-mp-16. Replace this with the appropriate compiler on your system.

  • Using Apple's Clang is not recommended because it does not support LeakSanitizer.
  • Instrumentation for LeakSanitizer does not need to be enabled separately, as it is part of AddressSanitizer.
  • UndefinedSanitizer is not recommended for general use because it produces many non-useful warnings, especially with older Clang versions or with Apple Clang. It also has a significant performance impact.
  • This is the appropriate place to include ccache, if desired (recommended).

Note: The sanitizer options must be included here, and not in CFLAGS, as they are also required in the linking step.

Set environment variables and linking options for the Fortran compiler:

export FC="ccache gfortran-mp-12"

Note: If using Clang as the C/C++ compiler, do not enable any sanitizers for gfortran. Clang's and GCC's sanitizer implementations are not compatible.

On some systems, additional linking options must be set for Fortran. The following example is for MacPorts. Adapt it to your system. The -Wl part will not typically be necessary.

export FLIBS="-L/opt/local/lib/libgcc -lgfortran -lquadmath -lm -Wl,-rpath /opt/local/lib/gcc12"

Set environment variables for the C preprocessor and linker. These may not be necessary on all systems.

export CPPFLAGS=-I/opt/local/include LDFLAGS="-L/opt/local/lib"

Set C/C++ compilation options. The following options are required for readable sanitizer output. -Og enables only debugger-friendly optimizations. Without any optimizations at all, R would be painfully slow.

export CFLAGS="-g -fno-omit-frame-pointer -Og"
export CXXFLAGS=$CFLAGS

Configure the build, and set your preferred installation location. Here we use ~/rsan.

./configure --prefix=$HOME/rsan

If this step errors with missing libraries (for example liblzma), install them, and double check that the C preprocessor and linker flags are set to the location where the libraries are installed.

Check the output and make sure that the compiler commands are as you expect, and they all include sanitizer flags (except Fortran). An example output:

R is now configured for x86_64-apple-darwin18.7.0

  Source directory:            .
  Installation directory:      /Users/szhorvat/rsan

  C compiler:                  ccache clang-mp-16 -fsanitize=address  -g -fno-omit-frame-pointer -Og
  Fortran fixed-form compiler: ccache gfortran-mp-12  -g -O2

  Default C++ compiler:        ccache clang++-mp-16 -fsanitize=address -std=gnu++17  -g -fno-omit-frame-pointer -Og
  C++11 compiler:              ccache clang++-mp-16 -fsanitize=address -std=gnu++11  -g -fno-omit-frame-pointer -Og
  C++14 compiler:              ccache clang++-mp-16 -fsanitize=address -std=gnu++14  -g -fno-omit-frame-pointer -Og
  C++17 compiler:              ccache clang++-mp-16 -fsanitize=address -std=gnu++17  -g -fno-omit-frame-pointer -Og
  C++20 compiler:              ccache clang++-mp-16 -fsanitize=address -std=gnu++20  -g -fno-omit-frame-pointer -Og
  C++23 compiler:              ccache clang++-mp-16 -fsanitize=address -std=gnu++2b  -g -fno-omit-frame-pointer -Og
  Fortran free-form compiler:  ccache gfortran-mp-12  -g -O2
  Obj-C compiler:	       ccache clang-mp-16 -fsanitize=address -g -O2 -fobjc-exceptions

  Interfaces supported:        X11, aqua, tcltk
  External libraries:          pcre2, readline, curl
  Additional capabilities:     PNG, JPEG, TIFF, NLS, cairo, ICU
  Options enabled:             shared BLAS, R profiling

  Capabilities skipped:        
  Options not enabled:         memory profiling

  Recommended packages:        yes

Before we can build R, the following must be run (see here):

cd doc/manual && make front-matter html-non-svn
cd ../..

echo -n 'Revision: ' > SVN-REVISION
git log --format=%B -n 1 \
  | grep "^git-svn-id"    \
  | sed -E 's/^git-svn-id: https:\/\/svn.r-project.org\/R\/[^@]*@([0-9]+).*$/\1/' \
  >> SVN-REVISION
echo -n 'Last Changed Date: ' >>  SVN-REVISION
git log -1 --pretty=format:"%ad" --date=iso | cut -d' ' -f1 >> SVN-REVISION

Now we are ready to run the build. For faster builds, set export MAKEFLAGS=-j8, then build R using make.

When the build is done, run make install to install R in the location specified by the --prefix option of ./configure.

Building igraph

Add the location of the newly installed R to your path, export PATH=$HOME/rsan/bin:$PATH. Start R, evaluate .libPaths(), and take note of the location of installed packages, in case you need to start with a clean slate later.

Before we build igraph, we must install devtools using install.packages('devtools'). This will take considerable time, but only needs to be done once. When the installation is done, quit R. At this point you may see memory leak warnings from LeakSanitizer.

Install the development version of igraph as usual. No special options need to be set to enable sanitizers, as compiler options may be inherited from the R build.

Testing igraph with sanitizers

Running the test suite

After having built igraph, change the igraph source directory, set the envvar TESTTHAT_PARALLEL=false to disable parallel testing and esure that all code is run in a single R process, and run the following in a fresh R session:

library(devtools)
test()
q()

Note that LeakSanitizer output will only be shown when R exits, hence the q(). It is important to run this in a newly started R session, otherwise LeakSanitizer may detect problems from previously run commands, possibly from other packages than igraph.

In order to run only a few test commands, start a new R session, then run:

library(devtools)
load_all()
# your commands
q()

load_all() will load igraph from the current directory instead of from the installed location. This is necessary in order to get line number references in sanitizer output.