#!/usr/bin/env bash

# A simple fuzzer for the LLVM parser. Generates random C programs
# with csmith, compiles them to bitcode with clang, and then runs
# llvm-disasm on the bitcode.

# Assumes that llvm-disasm, csmith and clang are in the PATH, and that
# the CSMITH_PATH environment variable is set to the include directory
# of the csmith installation.

# If a -k argument is given, the temporary files generated by csmith
# and clang are not deleted.

# If an -n argument is given, that sets the number of tests to run,
# for example -n 100. In this mode, stdout lists the seeds of csmith
# programs which cause llvm-disasm to exit with a non-0 exit code.

# If an -s argument is given, that sets the seed used by csmith to
# generate the program, and then tests only that program regardless of
# the -n argument, showing the full output of llvm-disasm.

# If an -w argument is given, all files will go in that directory.
# (Useful for building from read-only filesystems, e.g. in Docker.)

while getopts "kn:s:w:" opt; do
    case $opt in
        k)
            KEEP=yes
            ;;
        n)
            NUMTESTS=${OPTARG}
            ;;
        s)
            SEED=${OPTARG}
            ;;
        w)
            WORKDIR=${OPTARG}
            ;;
        \?)
            echo "Invalid option: -$OPTARG" >&2
            ;;
  esac
done

if [ -z WORKDIR ]; then
  WORKDIR=$PWD
fi

trap 'if [ -z ${KEEP} ]; then rm ${WORKDIR}/fuzz-temp-test.c ${WORKDIR}/fuzz-temp-test.bc; fi' EXIT

echo "Beginning fuzzing..."

if [ ${SEED+x} ]; then
    csmith -s ${SEED} > ${WORKDIR}/fuzz-temp-test.c;
    clang -I${CSMITH_PATH} -O -g -w \
          -c -emit-llvm ${WORKDIR}/fuzz-temp-test.c \
          -o ${WORKDIR}/fuzz-temp-test.bc;
    set -e
    llvm-disasm ${WORKDIR}/fuzz-temp-test.bc
    exit
fi

echo "..."

RESULT_DIR=${WORKDIR}/fuzz-results
if [ -n "${KEEP}" ]; then
    mkdir ${WORKDIR}/fuzz-results;
fi

echo "..."

RESULT=0
for i in `seq 1 ${NUMTESTS:-100}`;
do
    csmith > ${WORKDIR}/fuzz-temp-test.c;
    clang -I${CSMITH_PATH} -O -g -w \
          -c -emit-llvm ${WORKDIR}/fuzz-temp-test.c \
          -o ${WORKDIR}/fuzz-temp-test.bc;
    llvm-disasm ${WORKDIR}/fuzz-temp-test.bc &> /dev/null
    if [ $? -ne 0 ]; then
        RESULT=1
        SEED=$(grep '^ \* Seed:\s*\([0-9]*\)' ${WORKDIR}/fuzz-temp-test.c | grep -o '[0-9]*$')
        echo ${SEED}
        if [ -n "${KEEP}" ]; then
            cp ${WORKDIR}/fuzz-temp-test.c ${RESULT_DIR}/${SEED}.c;
        fi
    fi
done
exit ${RESULT}
