Skip to content

Commit

Permalink
Add script to automatically run tests with the LLVM sanitizers or val…
Browse files Browse the repository at this point in the history
…grind

hash_strnstr uses intentional unaligned access, so add an __attribute__
to tell clang to ignore that.
  • Loading branch information
aswild authored and ggreer committed Jun 4, 2017
1 parent c8f8389 commit 581335d
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 1 deletion.
196 changes: 196 additions & 0 deletions sanitize.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#!/bin/bash
# Copyright 2016 Allen Wild
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

AVAILABLE_SANITIZERS=(
address
thread
undefined
valgrind
)

DEFAULT_SANITIZERS=(
address
thread
undefined
)

usage() {
cat <<EOF
Usage: $0 [-h] [valgrind | [SANITIZERS ...]]
This script recompiles ag using -fsanitize=<SANITIZER> and then runs the test suite.
Memory leaks or other errors will be printed in ag's output, thus failing the test.
Available LLVM sanitizers are: ${AVAILABLE_SANITIZERS[*]}
The compile-time sanitizers are supported in clang/llvm >= 3.1 and gcc >= 4.8
for x86_64 Linux only. clang is preferred and will be used, if available.
For function names and line numbers in error output traces, llvm-symbolizer needs
to be available in PATH or set through ASAN_SYMBOLIZER_PATH.
If 'valgrind' is passed as the sanitizer, then ag will be run through valgrind
without recompiling. If $(dirname $0)/ag doesn't exist, then it will be built.
WARNING: This script will run "make distclean" and "./configure" to recompile ag
once per sanitizer (except for valgrind). If you need to pass additional
options to ./configure, put them in the CONFIGOPTS environment variable.
EOF
}

vrun() {
echo "Running: $*"
"$@"
}

die() {
echo "Fatal: $*"
exit 1
}

valid_sanitizer() {
for san in "${AVAILABLE_SANITIZERS[@]}"; do
if [[ "$1" == "$san" ]]; then
return 0
fi
done
return 1
}

run_sanitizer() {
sanitizer=$1
if [[ "$sanitizer" == "valgrind" ]]; then
run_valgrind
return $?
fi

echo -e "\nCompiling for sanitizer '$sanitizer'"
[[ -f Makefile ]] && vrun make distclean
vrun ./configure $CONFIGOPTS CC=$SANITIZE_CC \
CFLAGS="-g -O0 -fsanitize=$sanitizer $EXTRA_CFLAGS"
if [[ $? != 0 ]]; then
echo "ERROR: Failed to configure. Try setting CONFIGOPTS?"
return 1
fi

vrun make
if [[ $? != 0 ]]; then
echo "ERROR: failed to build"
return 1
fi

echo "Testing with sanitizer '$sanitizer'"
vrun make test
if [[ $? != 0 ]]; then
echo "Tests for sanitizer '$sanitizer' FAIL!"
echo "Check the above output for failure information"
return 2
else
echo "Tests for sanitizer '$sanitizer' PASS!"
return 0
fi
}

run_valgrind() {
echo "Compiling ag normally for use with valgrind"
[[ -f Makefile ]] && vrun make distclean
vrun ./configure $CONFIGOPTS
if [[ $? != 0 ]]; then
echo "ERROR: Failed to configure. Try setting CONFIGOPTS?"
return 1
fi

vrun make
if [[ $? != 0 ]]; then
echo "ERROR: failed to build"
return 1
fi

echo "Running: AGPROG=\"valgrind -q $PWD/ag\" make test"
AGPROG="valgrind -q $PWD/ag" make test
if [[ $? != 0 ]]; then
echo "Valgrind tests FAIL!"
return 1
else
echo "Valgrind tests PASS!"
return 0
fi
}

#### MAIN ####
run_sanitizers=()
for opt in "$@"; do
if [[ "$opt" == -* ]]; then
case opt in
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: '$opt'"
usage
exit 1
;;
esac
else
if valid_sanitizer "$opt"; then
run_sanitizers+=("$opt")
else
echo "Invalid Sanitizer: '$opt'"
usage
exit 1
fi
fi
done

if [[ ${#run_sanitizers[@]} == 0 ]]; then
run_sanitizers=(${DEFAULT_SANITIZERS[@]})
fi

if [[ -n $CC ]]; then
echo "Using CC=$CC"
SANITIZE_CC="$CC"
elif which clang &>/dev/null; then
SANITIZE_CC="clang"
else
echo "Warning: CC unset and clang not found"
fi

if [[ -n $CFLAGS ]]; then
EXTRA_CFLAGS="$CFLAGS"
unset CFLAGS
fi

if [[ ! -e ./configure ]]; then
echo "Warning: ./configure not found. Running autogen"
vrun ./autogen.sh || die "autogen.sh failed"
fi

echo "Running sanitizers: ${run_sanitizers[*]}"
failedsan=()
for san in "${run_sanitizers[@]}"; do
run_sanitizer $san
if [[ $? != 0 ]]; then
failedsan+=($san)
fi
done

if [[ ${#failedsan[@]} == 0 ]]; then
echo "All sanitizers PASSED"
exit 0
else
echo "The following sanitizers FAILED: ${failedsan[*]}"
exit ${#failedsan[@]}
fi
4 changes: 3 additions & 1 deletion src/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ const char *boyer_moore_strnstr(const char *s, const char *find, const size_t s_
return NULL;
}

const char *hash_strnstr(const char *s, const char *find, const size_t s_len, const size_t f_len, uint8_t *h_table, const int case_sensitive) {
// Clang's -fsanitize=alignment (included in -fsanitize=undefined) will flag
// the intentional unaligned access here, so suppress it for this function
NO_SANITIZE_ALIGNMENT const char *hash_strnstr(const char *s, const char *find, const size_t s_len, const size_t f_len, uint8_t *h_table, const int case_sensitive) {
if (s_len < f_len)
return NULL;

Expand Down
6 changes: 6 additions & 0 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ FILE *out_fd;

#define H_SIZE (64 * 1024)

#ifdef __clang__
#define NO_SANITIZE_ALIGNMENT __attribute__((no_sanitize("alignment")))
#else
#define NO_SANITIZE_ALIGNMENT
#endif

void *ag_malloc(size_t size);
void *ag_realloc(void *ptr, size_t size);
void *ag_calloc(size_t nelem, size_t elsize);
Expand Down

0 comments on commit 581335d

Please sign in to comment.