#!/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 Inria's continuous-integration servers to recompile
# from scratch, adding more run-time checks ("sanitizers") to the C code,
# and run the test suite.

# In this context, it is necessary to skip a few tests whose behaviour
# is modified by the instrumentation:

export OCAMLTEST_SKIP_TESTS="tests/afl-instrumentation/afltest.ml \
tests/runtime-errors/stackoverflow.ml"

# 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 environment 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 environment"
  msg="$msg variable has been defined."
  msg="$msg\nSee ${configure_url}"
  error "$msg"
}

# Change a variable in Makefile.config
# Usage: set_config_var <variable name> <new value>


set_config_var() {
  conffile=Makefile.config
  mv ${conffile} ${conffile}.bak
  (grep -v "^$1=" ${conffile}.bak; echo "$1=$2") > ${conffile}
}

#########################################################################
# 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|cygwin64|mingw|mingw64|msvc|msvc64)
    error "Don't run this test under Windows";;
  solaris)
    echo OCaml 4.11 does not support Solaris. Exiting.
    exit
  ;;
  *) 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-9 and is named "llvm-symbolizer"
# (/usr/bin/llvm-symbolizer-9 doesn't work, that would be too easy)
export ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-9/bin/llvm-symbolizer
export TSAN_SYMBOLIZER_PATH="$ASAN_SYMBOLIZER_PATH"

#########################################################################

# Cleanup repository
git clean -q -f -d -x

# Ensure that the repo still passes the check-typo script
if [ ! -x tools/check-typo ] ; then
  error "tools/check-typo does not appear to be executable?"
fi
tools/check-typo

#########################################################################

echo "======== old school build =========="

instdir="$HOME/ocaml-tmp-install-$$"
./configure --prefix "$instdir"

# Build the system without using world.opt
make $jobs world
make $jobs opt
make $jobs opt.opt
make install

rm -rf "$instdir"

# It's a build system test only, so we don't bother testing the compiler

#########################################################################

echo "======== clang 9, address sanitizer, UB sanitizer =========="

git clean -q -f -d -x

# Use clang 9
# We cannot give the sanitizer options as part of -cc because
# then various autoconfiguration tests fail.
# Instead, we'll fix OC_CFLAGS a posteriori.
./configure CC=clang-9 --disable-stdlib-manpages

# 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 OC_CFLAGS "-O1 \
-fno-strict-aliasing -fwrapv -fno-omit-frame-pointer \
-Wall -Werror \
-fsanitize=address \
-fsanitize-trap=$ubsan"

# 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.
# We deactivate leak detection for two reasons:
# - The suppressed leak detections related to ocamlyacc mess up the
# output of the tests and are reported as failures by ocamltest.
# - The Ocaml runtime does not free the memory when a fatal error
# occurs.

# We already use sigaltstack for stack overflow detection. Our use
# interacts with ASAN's. Hence, we tell ASAN not to use it.

ASAN_OPTIONS="detect_leaks=0,use_sigaltstack=0" $run_testsuite

#########################################################################

echo "======== clang 9, thread sanitizer =========="

git clean -q -f -d -x

./configure CC=clang-9 --disable-stdlib-manpages

# Select thread sanitizer
# Don't optimize too much to get better backtraces of errors
set_config_var OC_CFLAGS "-O1 \
-fno-strict-aliasing -fwrapv -fno-omit-frame-pointer \
-Wall -Werror \
-fsanitize=thread"

# 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 =========="

# git clean -q -f -d -x

# # Use clang 6.0
# # We cannot give the sanitizer options as part of -cc because
# # then various autoconfiguration tests fail.
# # Instead, we'll fix OC_CFLAGS a posteriori.
# # Memory sanitizer doesn't like the static data generated by ocamlopt,
# # hence build bytecode only
# ./configure CC=clang-9 --disable-native-compiler

# # Select memory sanitizer
# # Don't optimize at all to get better backtraces of errors
# set_config_var OC_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
