#!/bin/bash -x
# Copyright 2018 Google Inc.
# Modifications copyright (C) 2023 ISP RAS
#
# 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.
#
################################################################################

if [[ $CONFIG = "libfuzzer" ]]
then
  export OUT="/fuzzer"
  export CC=clang
  export CXX=clang++
  export CFLAGS="-g -fsanitize=fuzzer-no-link,undefined,address,bounds,integer,null"
  export CXXFLAGS="-g -fsanitize=fuzzer-no-link,undefined,address,bounds,integer,null"
  export SANITIZERS="address undefined"
  export LINKOPTS="-fsanitize=fuzzer,undefined,address,bounds,integer,null"
  export FUZZING_ENGINE="$(find $(llvm-config --libdir) -name libclang_rt.fuzzer_no_main-x86_64.a | head -1)"
fi

if [[ $CONFIG = "afl" ]]
then
  export OUT="/afl"
  export CC=/custom_afl_cc/AFLplusplus/afl-clang-fast
  export CXX=/custom_afl_cc/AFLplusplus/afl-clang-fast++
  export CFLAGS="-g -fsanitize=undefined,address,bounds,integer,null -DPATCH_ARGS_FOR_AFL"
  export CXXFLAGS="-g -fsanitize=undefined,address,bounds,integer,null -DPATCH_ARGS_FOR_AFL"
  export SANITIZERS="address undefined"
  export FUZZING_ENGINE="$(find /usr/local/ -name 'libAFLDriver.a' | head -1)"
  export LINKOPTS="-fsanitize=undefined,address,bounds,integer,null $FUZZING_ENGINE"
fi

if [[ $CONFIG = "sydr" ]]
then
  export OUT="/sydr"
  export CC=clang
  export CXX=clang++
  export CFLAGS="-g"
  export CXXFLAGS="-g"
  export LINKOPTS="-g"
  export FUZZING_ENGINE=/sydr_driver.o
  $CC $CFLAGS -fPIC -o /sydr_driver.o -c /sydr_driver.cc
fi

if [[ $CONFIG = "coverage" ]]
then
  export OUT="/cov"
  export CC=clang
  export CXX=clang++
  export CFLAGS="-g -fprofile-instr-generate -fcoverage-mapping"
  export CXXFLAGS="-g -fprofile-instr-generate -fcoverage-mapping"
  export LINKOPTS="-g -fprofile-instr-generate -fcoverage-mapping"
  export FUZZING_ENGINE=/sydr_driver.o
  $CC $CFLAGS -fPIC -o /sydr_driver.o -c /sydr_driver.cc
fi

# Remove configure for runtime_client_fuzz target because it is spoilt
rm tensorflow/security/fuzzing/cc/core/function/BUILD

# Was in OSS-Fuzz build, don't touch
mkdir $OUT
git apply --ignore-space-change --ignore-whitespace /fuzz_patch.patch

# Patch fuzztest dependency to escape enabling fuzztest signal handlers and
# fix the command arguments for afl++ driver.
git apply --ignore-space-change --ignore-whitespace /fuzztest.patch

# Patch some targets because fuzztest doesn't check arguments constraints in fuzzer mode.
git apply --ignore-space-change --ignore-whitespace /targets.patch

find tensorflow/ -name "BUILD" -exec sed -i 's/tf_cc_fuzz_test/tf_oss_fuzz_fuzztest/g' {} \;

# Don't touch
# Overwrite compiler flags that break the oss-fuzz build
sed -i 's/build:linux --copt=\"-Wno-unknown-warning\"/# overwritten/g' ./.bazelrc
sed -i 's/build:linux --copt=\"-Wno-array-parameter\"/# overwritten/g' ./.bazelrc
sed -i 's/build:linux --copt=\"-Wno-stringop-overflow\"/# overwritten/g' ./.bazelrc

# Force Python3, run configure.py to pick the right build config
export TF_PYTHON_VERSION=3.9
PYTHON=python3
yes "" | python3 configure.py

# Don't touch
# Prepare flags for compiling fuzzers.
export FUZZTEST_EXTRA_ARGS="--jobs=$(nproc) --spawn_strategy=sandboxed --action_env=ASAN_OPTIONS=detect_leaks=0,detect_odr_violation=0 --define force_libcpp=enabled --verbose_failures --copt=-UNDEBUG --config=monolithic"

# Here are directories with fuzztest targets, and extra targets, which we added
# to OSS-Fuzz (core/kernels/fuzzing)
# Set fuzz targets
export FUZZTEST_TARGET_FOLDER="//tensorflow/security/fuzzing/...+//tensorflow/cc/framework/fuzzing/...+//tensorflow/core/common_runtime/...+//tensorflow/core/framework/..."
export FUZZTEST_EXTRA_TARGETS="//tensorflow/core/kernels/fuzzing:all"

# Don't touch
echo "  write_to_bazelrc('import %workspace%/tools/bazel.rc')" >> configure.py
yes "" | ./configure

# Get targets added by us to add build function for them and remove decode_base64
# (don't remember why, it didn't work)
declare FUZZERS=$(grep '^tf_ops_fuzz_target' tensorflow/core/kernels/fuzzing/BUILD | cut -d'"' -f2 | grep -v decode_base64)

# Add function for bazel config to build the targets from core/kernels/fuzzing
cat >> tensorflow/core/kernels/fuzzing/tf_ops_fuzz_target_lib.bzl << END
def cc_tf(name):
    native.cc_test(
        name = name + "_fuzz",
        deps = [
            "//tensorflow/core/kernels/fuzzing:fuzz_session",
            "//tensorflow/core/kernels/fuzzing:" + name + "_fuzz_lib",
            "//tensorflow/cc:cc_ops",
            "//tensorflow/cc:scope",
            "//tensorflow/core:core_cpu",
        ],
        linkopts = ["$LINKOPTS"]
    )
END

# Load added functions for bazel in bazel BUILD file
cat >> tensorflow/core/kernels/fuzzing/BUILD << END
load("//tensorflow/core/kernels/fuzzing:tf_ops_fuzz_target_lib.bzl", "cc_tf")
END

# Just printing
for fuzzer in ${FUZZERS}; do
    echo cc_tf\(\"${fuzzer}\"\) >> tensorflow/core/kernels/fuzzing/BUILD
done

# Get the names for our targets again, but with bazel query (this must work
# because we added build functions for them in previous steps)
declare FUZZERS=$(bazel query 'kind(cc_.*, tests(//tensorflow/core/kernels/fuzzing/...))' | grep -v decode_base64)

# Compile fuzztests
/compile_fuzztests.sh

# Copy out all non-fuzztest fuzzers.
# The fuzzers built above are in the `bazel-bin/` symlink. But they need to be
# in `$OUT`, so move them accordingly.
for bazel_target in ${FUZZERS}; do
  colon_index=$(expr index "${bazel_target}" ":")
  fuzz_name="${bazel_target:$colon_index}"
  bazel_location="bazel-bin/${bazel_target/:/\/}"
  cp ${bazel_location} $OUT/$fuzz_name
done

# Remove all the cache for all configs except of coverage
if [[ $CONFIG != "coverage" ]]
then
  rm -f bazel-*
  rm -rf /root/.cache/bazel/_bazel_root
fi
