CI testing with "sanitizers" applied to the C code

This CI script builds and tests the system using Clang and GCC
"sanitizers" to perform additional run-time checks on the C code,
e.g. out-of-bound memory accesses.

Currently, we use the Address, Undefined Behavior and Thread sanitizers.
The Memory sanitizer (reads from uninitialized memory) doesn't quite
work and is commented out in the script.
master
Xavier Leroy 2018-05-27 09:36:44 +02:00
parent fea588fb66
commit a9aa1d2983
3 changed files with 236 additions and 0 deletions

229
tools/ci/inria/extra-checks Normal file
View File

@ -0,0 +1,229 @@
#!/bin/sh
#**************************************************************************
#* *
#* OCaml *
#* *
#* Damien Doligez, Xavier Leroy, projet Gallium, INRIA Paris *
#* *
#* Copyright 2018 Institut National de Recherche en Informatique et *
#* en Automatique. *
#* *
#* All rights reserved. This file is distributed under the terms of *
#* the GNU Lesser General Public License version 2.1, with the *
#* special exception on linking described in the file LICENSE. *
#* *
#**************************************************************************
# This script is run on our continuous-integration servers to recompile
# from scratch, adding more run-time checks ("sanitizers") to the C code,
# and run the test suite.
# To know the slave's architecture, this script looks at the OCAML_ARCH
# environment variable. For a given node NODe, this variable can be defined
# in Jenkins at the following address:
# https://ci.inria.fr/ocaml/computer/NODE/configure
# Other environments variables that are honored:
# OCAML_JOBS number of jobs to run in parallel (make -j)
# Command-line arguments:
# -jNN pass "-jNN" option to make for parallel builds
error () {
echo "$1" >&2
exit 3
}
arch_error() {
configure_url="https://ci.inria.fr/ocaml/computer/${NODE_NAME}/configure"
msg="Unknown architecture. Make sure the OCAML_ARCH environemnt"
msg="$msg variable has been defined."
msg="$msg\nSee ${configure_url}"
error "$msg"
}
# Change a variable in config/Makefile
# Usage: set_config_var <variable name> <new value>
set_config_var() {
mv config/Makefile config/Makefile.bak
(grep -v "^$1=" config/Makefile.bak; echo "$1=$2") > config/Makefile
}
# Undefine a macro in a C header file
# Just adding #undef is not enough given the way tests in testsuite/
# grep for features in s.h
# Usage: undefine_macro <macro name> <.h file>
undefine_macro() {
mv $2 $2.bak
grep -v "#define $1" $2.bak > $2
}
#########################################################################
# stop on error
set -e
# be considerate towards other potential users of the test machine
case "${OCAML_ARCH}" in
bsd|macos|linux) renice 10 $$ ;;
esac
# set up variables
make=make
jobs=''
case "${OCAML_ARCH}" in
bsd) make=gmake ;;
macos) ;;
linux) ;;
cygwin|mingw|mingw64|msvc|msvc64) error "Don't run this test under Windows";;
*) arch_error;;
esac
case "${OCAML_JOBS}" in
[1-9]|[1-9][0-9]) jobs="-j${OCAML_JOBS}" ;;
esac
# parse optional command-line arguments
while [ $# -gt 0 ]; do
case $1 in
-j[1-9]|-j[1-9][0-9]) jobs="$1";;
*) error "unknown option $1";;
esac
shift
done
# Tell gcc to use only ASCII in its diagnostic outputs.
export LC_ALL=C
# How to run the test suite
if test -n "$jobs" && test -x /usr/bin/parallel; then
export PARALLEL="$jobs $PARALLEL"
run_testsuite="$make -C testsuite parallel"
else
run_testsuite="$make -C testsuite all"
fi
# A tool that make error backtrace nicer
# Need to pick the one that matches clang-6.0 and is named "llvm-symbolizer"
# (/usr/bin/llvm-symbolizer-6.0 doesn't work, that would be too easy)
export ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-6.0/bin/llvm-symbolizer
export TSAN_SYMBOLIZER_PATH="$ASAN_SYMBOLIZER_PATH"
#########################################################################
echo "======== clang 6.0, address sanitizer, UB sanitizer =========="
$make -s distclean || :
# Use clang 6.0
# We cannot give the sanitizer options as part of -cc because
# then various autoconfiguration tests fail.
# Instead, we'll fix CFLAGS a posteriori.
./configure -cc clang-6.0
# These are the undefined behaviors we want to check
# Others occur on purpose e.g. signed arithmetic overflow
ubsan="\
bool,\
builtin,\
bounds,\
enum,\
nonnull-attribute,\
nullability,\
object-size,\
pointer-overflow,\
returns-nonnull-attribute,\
shift-exponent,\
unreachable"
# Select address sanitizer and UB sanitizer, with trap-on-error behavior
# Don't optimize too much to get better backtraces of errors
set_config_var CFLAGS "-O1 \
-fno-strict-aliasing -fwrapv -fno-omit-frame-pointer \
-Wall -Werror \
-fsanitize=address \
-fsanitize-trap=$ubsan"
# AddressSanitizer reacts poorly to stack overflow, so don't test for it.
undefine_macro HAS_STACK_OVERFLOW_DETECTION byterun/caml/s.h
# Build the system. We want to check for memory leaks, hence
# 1- force ocamlrun to free memory before exiting
# 2- add an exception for ocamlyacc, which doesn't free memory
OCAMLRUNPARAM="c=1" \
LSAN_OPTIONS="suppressions=$(pwd)/tools/ci/inria/lsan-suppr.txt" \
make $jobs world.opt
# Run the testsuite.
# The suppressed leak detections related to ocamlyacc mess up the output
# of the tests and are reported as failures by ocamltest.
# Hence, deactivate leak detection entirely.
ASAN_OPTIONS="detect_leaks=0" $run_testsuite
#########################################################################
echo "======== clang 6.0, thread sanitizer =========="
$make -s distclean || :
./configure -cc clang-6.0
# Select thread sanitizer
# Don't optimize too much to get better backtraces of errors
set_config_var CFLAGS "-O1 \
-fno-strict-aliasing -fwrapv -fno-omit-frame-pointer \
-Wall -Werror \
-fsanitize=thread \
-fsanitize-blacklist=$(pwd)/tools/ci/inria/tsan-suppr.txt"
# ThreadSanitizer reacts poorly to stack overflow, so don't test for it.
undefine_macro HAS_STACK_OVERFLOW_DETECTION byterun/caml/s.h
# Build the system
make $jobs world.opt
# Run the testsuite.
# ThreadSanitizer complains about fork() in threaded programs,
# we ask it to just continue in this case.
TSAN_OPTIONS="die_after_fork=0" $run_testsuite
#########################################################################
# This is a failed attempt at using the memory sanitizer
# (to detect reads from uninitialized memory).
# Some alarms are reported that look like false positive
# and are impossible to debug.
# echo "======== clang 6.0, memory sanitizer =========="
# $make -s distclean || :
# # Use clang 6.0
# # We cannot give the sanitizer options as part of -cc because
# # then various autoconfiguration tests fail.
# # Instead, we'll fix CFLAGS a posteriori.
# # Memory sanitizer doesn't like the static data generated by ocamlopt,
# # hence build bytecode only
# ./configure -cc clang-6.0 -no-native-compiler
# # Select memory sanitizer
# # Don't optimize at all to get better backtraces of errors
# set_config_var CFLAGS "-O0 -g \
# -fno-strict-aliasing -fwrapv -fno-omit-frame-pointer \
# -Wall -Werror \
# -fsanitize=memory"
# # A tool that make error backtrace nicer
# # Need to pick the one that matches clang-6.0
# export MSAN_SYMBOLIZER_PATH=/usr/lib/llvm-6.0/bin/llvm-symbolizer
# # Build the system (bytecode only) and test
# make $jobs world
# $run_testsuite

View File

@ -0,0 +1,2 @@
# ocamlyacc doesn't clean memory on exit
leak:ocamlyacc

View File

@ -0,0 +1,5 @@
# The treatment of pending signals involves unsynchronized accesses
fun:caml_record_signal
fun:caml_process_pending_signals
# st_masterlock_waiters polls m->waiters without locking
fun:st_masterlock_waiters