From e2173ebe7e97fff8a1167886ca38903ff1640baa Mon Sep 17 00:00:00 2001 From: Andre Maroneze <andre.oliveiramaroneze@cea.fr> Date: Wed, 3 Jun 2020 22:47:29 +0200 Subject: [PATCH] [analysis-scripts] improve and generalize creduce.sh --- Makefile | 4 + headers/header_spec.txt | 2 + share/analysis-scripts/creduce.sh | 215 +++++++++++------- .../script_for_creduce_fatal.sh | 17 ++ .../script_for_creduce_non_fatal.sh | 49 ++++ 5 files changed, 211 insertions(+), 76 deletions(-) create mode 100644 share/analysis-scripts/script_for_creduce_fatal.sh create mode 100644 share/analysis-scripts/script_for_creduce_non_fatal.sh diff --git a/Makefile b/Makefile index 9ac311c8cae..167e458ce8b 100644 --- a/Makefile +++ b/Makefile @@ -276,6 +276,8 @@ DISTRIB_FILES:=\ share/analysis-scripts/prologue.mk \ share/analysis-scripts/README.md \ share/analysis-scripts/results_display.py \ + share/analysis-scripts/script_for_creduce_fatal.sh \ + share/analysis-scripts/script_for_creduce_non_fatal.sh \ share/analysis-scripts/summary.py \ share/analysis-scripts/template.mk \ $(wildcard share/emacs/*.el) share/autocomplete_frama-c \ @@ -1943,6 +1945,8 @@ install:: install-lib-$(OCAMLBEST) share/analysis-scripts/prologue.mk \ share/analysis-scripts/README.md \ share/analysis-scripts/results_display.py \ + share/analysis-scripts/script_for_creduce_fatal.sh \ + share/analysis-scripts/script_for_creduce_non_fatal.sh \ share/analysis-scripts/summary.py \ share/analysis-scripts/template.mk \ $(FRAMAC_DATADIR)/analysis-scripts diff --git a/headers/header_spec.txt b/headers/header_spec.txt index fefab89f71c..edc62ce1b07 100644 --- a/headers/header_spec.txt +++ b/headers/header_spec.txt @@ -134,6 +134,8 @@ share/analysis-scripts/parse-coverage.sh: .ignore share/analysis-scripts/prologue.mk: CEA_LGPL share/analysis-scripts/README.md: .ignore share/analysis-scripts/results_display.py: CEA_LGPL +share/analysis-scripts/script_for_creduce_fatal.sh: .ignore +share/analysis-scripts/script_for_creduce_non_fatal.sh: .ignore share/analysis-scripts/summary.py: CEA_LGPL share/analysis-scripts/template.mk: .ignore share/compliance/c11_functions.json: .ignore diff --git a/share/analysis-scripts/creduce.sh b/share/analysis-scripts/creduce.sh index 94ddd8ef10f..ee96081100b 100755 --- a/share/analysis-scripts/creduce.sh +++ b/share/analysis-scripts/creduce.sh @@ -21,52 +21,71 @@ # # ########################################################################## -# Script to run C-Reduce (https://embed.cs.utah.edu/creduce) when debugging -# Frama-C crashes with a 'fatal' error, which produces a stack trace and -# a message "Please report". - -### Requirements -# -# - C-Reduce installed in the PATH, or its path given in the -# environment variable CREDUCE; -# -# - GCC, or the path to a compiler with compatible options given in the -# environment variable CC; -# -# - Frama-C installed in the PATH, or its path given in the environment -# variable FRAMAC; -# -# - a source file + a command line which causes Frama-C to crash with -# a 'fatal' error (producing a stack trace). - -### How it works -# -# creduce.sh <file.c> <command line options> -# -# This script creates a temporary directory named 'creducing'. -# It copies <file.c> inside it, then runs creduce. -# creduce runs "frama-c <command line options>" on the copied file, -# modifying it to make it smaller while still crashing. -# -# When done, copy the reduced file and erase directoy 'creducing'. +usage=" +Script to run C-Reduce (https://embed.cs.utah.edu/creduce) when debugging +Frama-C, or when minimizing test cases resulting in a Frama-C error. + +# Requirements + +- C-Reduce installed in the PATH, or its path in environment variable CREDUCE; +- Frama-C installed in the PATH, or the path to its 'bin' directory in + environment variable FRAMAC_BIN; +- a source file + a command line which causes Frama-C to crash with + a 'fatal' error (producing a stack trace); can also be used when + Frama-C reports other errors, but in this case a separate script must be + completed by the user. + +# Basic usage + + arguments: <file> <command line options> + +This script creates a temporary directory named 'creducing'. It copies <file> +inside it, then runs creduce with 'frama-c <command line options>' +on the copied file, modifying it to make it smaller while still crashing. + +When done, you need to copy the reduced file and erase directoy 'creducing'. + +# Advanced usage + +- if <file> is not a preprocessed (.i) source, this script + will retrieve the preprocessing arguments from Frama-C. It will try to + preprocess the file in GCC to make sure it is parseable during reduction. + This may cause some issues; use a preprocessed file if you can. + +- For "fatal" crashes (with stack trace and 'Please report...' message), + this script should work automatically. For other crashes (e.g. invalid + input), this script will copy a template to the current directory and you + need to fill it so that creduce will work. This usage mode requires knowing + how C-Reduce works. +" if [ $# -lt 1 ]; then - echo "usage: $0 file.c [Frama-C options that cause a crash]" + echo "$usage" exit 1 fi +case "$1" in + --help) echo "$usage"; exit;; + --h) echo "$usage"; exit;; + -help) echo "$usage"; exit;; + -h) echo "$usage"; exit;; +esac + file="$1" base=$(basename "$file") shift -options="$@" +fcflags="$@" dir_for_reduction="creducing" +script_for_creduce="./script_for_creduce.sh" + if [ -z "$CREDUCE" ]; then CREDUCE="creduce" fi if ! command -v "$CREDUCE" 2>&1 >/dev/null; then - echo "creduce not found, put it in PATH or in environment variable CREDUCE." + echo "creduce not found; install it in the PATH or" + echo "put it in environment variable CREDUCE." exit 1 fi @@ -75,12 +94,28 @@ if [[ ! -f "$file" ]]; then exit 1 fi -if [[ $(dirname "$file") -eq "$dir_for_reduction" ]]; then +if [[ $(dirname "$file") = "$dir_for_reduction" ]]; then echo "error: cannot reduce a file inside the directory used for reduction," echo " $dir_for_reduction" exit 1 fi +if [ -z "$FRAMAC_BIN" ]; then + if ! command -v "frama-c" >/dev/null; then + echo "error: frama-c must be in the PATH, or FRAMAC_BIN must point to" + echo " the directory containing the frama-c binary" + exit 1 + fi + FRAMAC="frama-c" +else + FRAMAC="$FRAMAC_BIN/frama-c" +fi +echo "[info] using FRAMAC: $FRAMAC" + +if [ -z "$FRAMAC_SHARE" ]; then + FRAMAC_SHARE="$("$FRAMAC" -print-share-path)" +fi + if [[ ! "$options" =~ no-autoload-plugins ]]; then echo "********************************************************************" echo "Hint: consider using -no-autoload-plugins -load-module [modules]" @@ -93,69 +128,97 @@ if [[ "$base" =~ \.c$ ]]; then echo "Hint: consider passing a preprocessed (.i) file if possible," echo " otherwise #includes may prevent further reduction" echo "********************************************************************" + # TODO: check for libc includes and suggest -print-libc fi -mkdir -p "$dir_for_reduction" - -if [ -z "$FRAMAC" ]; then - SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - FRAMAC=$(realpath "$SCRIPT_DIR/../../bin/frama-c") - echo "[info] FRAMAC not set, using binary at: $FRAMAC" +# Obtain cpp flags by running Frama-C with `-kernel-msg-key pp` +if [[ "$base" =~ \.i$ ]]; then + # Preprocessed file: no need to get preprocessing flags + # TODO: check for '-machdep' option and add/remove flags accordingly + CPP="gcc -fsyntax-only" else - echo "[info] using FRAMAC: $FRAMAC" + CPP=$("$FRAMAC" -kernel-msg-key pp $fcflags "$file" 2>/dev/null | \ + grep "preprocessing with " | \ + sed 's|.* preprocessing with "||' | sed 's|"$||') + if [[ "$CPP" = "" ]]; then + echo "[info] Could not get preprocessing flags when running Frama-C," + echo " using default flags" + # TODO: check for '-machdep' option and add/remove flags accordingly + CPP="gcc -fsyntax-only -m32" + else + # Replace -E with -fsyntax-only to force GCC to try parsing the file; + # also remove Frama-C's libc; use the standard system libraries. + CPP=$(echo $CPP | \ + sed 's| -E | -fsyntax-only |' | \ + sed 's| -nostdinc | |' | \ + sed 's| -I[^ ]\+/libc | |' ) + fi fi -if [ -z "$FRAMAC_SHARE" ]; then - FRAMAC_SHARE="$("$FRAMAC" -print-share-path)" +if [ ! -e "$script_for_creduce" ]; then + echo "[info] $script_for_creduce not found." + echo "Which kind of error are you reducing for?" + echo "1. Fatal crash (error message with stacktrace)" + echo "2. Non-fatal error" + read -p "Please enter 1 or 2: " errorkind + case $errorkind in + 1) + cp "$FRAMAC_SHARE/analysis-scripts/script_for_creduce_fatal.sh" "$script_for_creduce" + ;; + 2) + cp "$FRAMAC_SHARE/analysis-scripts/script_for_creduce_non_fatal.sh" "$script_for_creduce" + echo "Script copied. Please edit $script_for_creduce and re-run this script." + exit 0 + ;; + *) + echo "Invalid choice, aborting." + exit 0 + ;; + esac fi -if [ -z "$CC" ]; then - CC=gcc - CFLAGS+="-fsyntax-only -I\"$FRAMAC_SHARE\"/libc -D__FC_MACHDEP_X86_32 -m32" -fi - -echo "[info] checking syntactic validity with: $CC $CFLAGS" +echo "[info] the following command will be used to check for syntactic validity during reduction:" +echo " $CPP" +mkdir -p "$dir_for_reduction" cp "$file" "$dir_for_reduction" +cp "$script_for_creduce" "$dir_for_reduction" cd "$dir_for_reduction" -cat <<EOF > script_for_creduce.sh -#!/bin/bash -e - -# This script is given to creduce to reduce a Frama-C crashing test case -# (where "crashing" means a 'fatal' exception, with stack trace). - -set +e -$CC $CFLAGS "$base" -if [ \$? -ne 0 ]; then - exit 1 -fi -"$FRAMAC" "$base" $options -retcode=\$? -# see cmdline.ml for the different exit codes returned by Frama-C -if [ \$retcode -eq 125 -o \$retcode -eq 4 ]; then - exit 0 -else - exit 1 -fi -set -e - -EOF - -chmod u+x script_for_creduce.sh +# we avoid using '-i' for compatibility with macOS sed +sed -e "s|@CPP@|$CPP|g" "$script_for_creduce" > "${script_for_creduce}.tmp" +mv "${script_for_creduce}.tmp" "$script_for_creduce" +sed -e "s|@FRAMAC@|$FRAMAC|g" "$script_for_creduce" > "${script_for_creduce}.tmp" +mv "${script_for_creduce}.tmp" "$script_for_creduce" +sed -e "s|@BASE@|$base|g" "$script_for_creduce" > "${script_for_creduce}.tmp" +mv "${script_for_creduce}.tmp" "$script_for_creduce" +sed -e "s|@FCFLAGS@|$fcflags|g" "$script_for_creduce" > "${script_for_creduce}.tmp" +mv "${script_for_creduce}.tmp" "$script_for_creduce" +chmod u+x "$script_for_creduce" trap "{ echo \"Creduce interrupted!\"; echo \"\"; echo \"(partially) reduced file: $dir_for_reduction/$base\"; exit 0; }" SIGINT echo "File being reduced: $dir_for_reduction/$base (press Ctrl+C to stop at any time)" set +e -./script_for_creduce.sh 2>&1 >/tmp/script_for_creduce.out +rm -rf /tmp/script_for_creduce.out +$script_for_creduce >>/tmp/script_for_creduce.out 2>&1 if [ $? -ne 0 ]; then - echo "error: script did not produce a Frama-C 'fatal' crash." - echo " check the options given to Frama-C to ensure they make Frama-C crash." + # TODO: check if -cpp-extra-args exists and contains relative paths, + # and if so, warn about them + echo "#################################################" + echo "ERROR: script did not produce the expected error." + echo " check the options given to Frama-C." + echo " If you edited '$script_for_creduce', check it as well." echo "" - echo "Script output:" - cat /tmp/script_for_creduce.out + if [ $(wc -l /tmp/script_for_creduce.out | cut -d' ' -f1) -gt 20 ]; then + echo "# Script output (first 20 lines):" + head -n 20 /tmp/script_for_creduce.out + echo "(...) [truncated; full output in /tmp/script_for_creduce.out]" + else + echo "# Script output:" + cat /tmp/script_for_creduce.out + fi exit 1 fi set -e diff --git a/share/analysis-scripts/script_for_creduce_fatal.sh b/share/analysis-scripts/script_for_creduce_fatal.sh new file mode 100644 index 00000000000..a0fc2ba7b95 --- /dev/null +++ b/share/analysis-scripts/script_for_creduce_fatal.sh @@ -0,0 +1,17 @@ +#!/bin/bash -e + +# Script to reduce a Frama-C crashing test case. + +# This script requires no modification. +# Names between '@'s will be replaced by creduce.sh. + +set +e +"@FRAMAC@" "@BASE@" @FCFLAGS@ +retcode=$? +# see cmdline.ml for the different exit codes returned by Frama-C +if [ $retcode -eq 125 -o $retcode -eq 4 ]; then + exit 0 +else + exit 1 +fi +set -e diff --git a/share/analysis-scripts/script_for_creduce_non_fatal.sh b/share/analysis-scripts/script_for_creduce_non_fatal.sh new file mode 100644 index 00000000000..fab66d214b2 --- /dev/null +++ b/share/analysis-scripts/script_for_creduce_non_fatal.sh @@ -0,0 +1,49 @@ +#!/bin/bash -e + +# Script to reduce a Frama-C non-crashing test case. + +# This script must be completed by the user. +# Names between '@'s will be replaced by creduce.sh. +# See the Frama-C User Manual for more details. + +cc_out=$(mktemp creduce_cc_XXXXXX.log) +fc_out=$(mktemp creduce_fc_XXXXXX.log) + +# We always check that the reduced file remains valid C code. +set -o pipefail +@CPP@ "@BASE@" 2>&1 | tee $cc_out +set +o pipefail + +### Examples of conditions to be maintained by C-Reduce; copy and adapt +# +# Ensure that the C file contains <expr> +# grep -q expr "@BASE@" +# +# Ensure that the compiler output contains <expr> +# grep -q <expr> "$cc_out" +# +### + +########## INSERT CONDITION(S) RELATED TO THE SOURCE HERE ########## + +########## + +"@FRAMAC@" "@BASE@" @FCFLAGS@ 2>&1 | tee $fc_out +fc_retcode=$(echo ${PIPESTATUS[0]}) + +### Examples of conditions to be maintained by C-Reduce; copy and adapt +# +# Ensure that Frama-C emits <expr> +# grep -q <expr> "$fc_out" +# +# Ensure that the exit code of Frama_C is <rc> +# test $fc_retcode -eq <rc> +# +### + +########## INSERT CONDITION(S) RELATED TO FRAMA-C HERE ########## + +########## + +### Cleanup +rm -f $cc_out $fc_out -- GitLab