#!/bin/bash
# Check whether our preload library has any unexpected public symbol.
# Check that all our overridden methods actually exist in libc, libdl or libpthread.
# Check that for each overridden method we also override its fortified
# counterpart, if that exists at all.

# Copyright (c) 2022 Firebuild Inc.
# All rights reserved.
# Free for personal use and commercial trial.
# Non-trial commercial use requires licenses available from https://firebuild.com.
# Modification and redistribution are permitted, but commercial use of
# derivative works is subject to the same requirements of this license
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

# Figure out CMAKE_BINARY_DIR
if [ $# -lt 1 ]; then
  echo "Usage: test_symbols binary_dir" >&2
  exit 1
fi
binary_dir="$1"

status=0

platform=$(uname)
case "$platform" in
    "Linux")
        libfirebuild="$binary_dir/src/interceptor/libfirebuild.so"
        nm_params=""
        ;;
    "Darwin")
        libfirebuild="$binary_dir/src/interceptor/libfirebuild.dylib"
        nm_params="-arch all"
        ;;
esac

# Additional allowed public symbols that are not auto-generated into gen_list.txt.
additional_allowed_symbols=""

known_missing_Darwin_64bit="_fstat64
_fstatat64
_lstat64
_stat64"

known_missing_Darwin_underscore="___fork
___sigaction
___wait4"

function check_function_class () {
    local fix=$1
    local class=$2
    local kind=$3
    local base
    # Get the list of libc, libdl and libpthread symbols of this class
                        case $kind in
                            "prefix")
                                grep ^${fix} < libc-symbols.txt > libc-${class}-symbols.txt
                                ;;
                            "postfix")
                                grep ${fix}'$' < libc-symbols.txt > libc-${class}-symbols.txt
                        esac

    # Get the list of libc symbols that we don't override, but override the
    # counterpart with the prefix/postfix
    local missing=`for chk in $(< libc-${class}-symbols.txt); do
                        case $kind in
                            "prefix")
                                base="${chk#__}"
                                if grep -Fqx "$base" public-symbols.txt && ! grep -Fqx "$chk" public-symbols.txt; then
                                    echo "$chk"
                                fi
                                ;;
                            "postfix")
                                base="${chk%${fix}}"
                                base="${base#__}"
                                if grep -Fqx "$base" public-symbols.txt && ! grep -Fqx "$chk" public-symbols.txt; then
                                    echo "$chk"
                                fi
                                ;;
                        esac
                    done`

    # Report the non-overridden symbols
    known_missing_varname="known_missing_${platform}_${class}"
    if [ -z "$missing" -o "(" "${!known_missing_varname}" = "$missing" ")" ]; then
        echo "All expected ${class} methods overridden"
    else
        echo "${!known_missing_varname}"
        echo "${class} methods not overridden:"
        echo "$missing" | sed 's/^\(.*\)$/  \1/'
        status=1
    fi
}

# Extract and sort the public symbols of our library,
# filtering out the ones added by gcov (__gcov* and mangle_path)
# and ones appearing with old toolchains(__bss_start, _edata, etc.)
nm --extern-only $nm_params "$libfirebuild" | \
  grep -v 'architecture ...' | \
  sed 's/_interposing//' | \
  grep -v ' [Uvw] ' | \
  cut -d' ' -f3- | \
  grep -v '^__gcov_' | \
  grep -v '^mangle_path$' | \
  egrep -v '^_(_bss_start|edata|end|fini|init)$' | \
  LC_ALL=C sort | uniq > public-symbols.txt

# Gather and sort the allowed public symbols
{ cat "$binary_dir/src/interceptor/gen_list.txt"; echo "$additional_allowed_symbols"; } | \
  LC_ALL=C sort > public-symbols-allowed.txt

# Get the list of unexpected ones
unexpected=$(LC_ALL=C comm -23 public-symbols.txt public-symbols-allowed.txt)

# Report the list of unexpected ones
if [ -z "$unexpected" ]; then
  echo "No unexpected public symbols"
else
  echo "Unexpected public symbols:"
  echo "$unexpected" | sed 's/^\(.*\)$/  \1/'
  status=1
fi

# Get the list of libc, libdl and libpthread symbols.

# Some symbols have been removed from or introduced recently to glibc
known_extra="
__bss_start
_edata
_end
_fini
_init
_Fork
arc4random
arc4random_buf
arc4random_uniform
execveat
stime
ustat
stat
stat64
lstat
lstat64
fstat
fstat64
fstatat
fstatat64
mknod
mknodat
closefrom
close_range
pidfd_open
posix_spawn_file_actions_addclosefrom_np
shm_open
shm_unlink
"
# TODO(rbalint) provide properly versioned symbols in libfirebuild

case "$platform" in
    "Linux")
        libs=$(LD_PRELOAD=libpthread.so.0 ldd $libfirebuild | grep -E 'lib(c|dl|pthread).so' | cut -d' ' -f3)
        (nm -D $libs | \
             grep ' [TWi] ' | \
             cut -d' ' -f3- ; \
         echo "$known_extra") | \
            sed 's/\(.*\)@@\(.*\)/\1@@\2\n\1/' | \
            LC_ALL=C sort > libc-symbols.txt
        ;;
    "Darwin")
        lib=$(otool -L $libfirebuild | awk '/\/usr\/lib/ {print $1}')
        rm -f libc-symbols.txt
        (for arch in arm64 arm64e x86_64; do
             for exported_lib in "libdyld" "libsystem_c" "libsystem_info" "libsystem_kernel" "libsystem_pthread" "libsystem_blocks"; do
                 "$binary_dir/test/print_tbd_lib_arch_exports" "${CMAKE_OSX_SYSROOT}/${lib%.dylib}.tbd" "/usr/lib/system/${exported_lib}.dylib" ${arch}-macos
             done
        done) | LC_ALL=C sort | uniq > libc-symbols.txt
        ;;
esac

# Get the list of extra ones
extra=$(LC_ALL=C comm -23 public-symbols.txt libc-symbols.txt)

# Report the list of extra ones
if [ -z "$extra" ]; then
  echo "No unexpected overridden methods"
else
  echo "Overridden methods that do not exist in libc, libdl or libpthread:"
  echo "$extra" | sed 's/^\(.*\)$/  \1/'
  status=1
fi

# Check the list of libc, libdl and libpthread fortified symbols
check_function_class "_chk" "fortified" "postfix"

# Check the *64 variants
check_function_class "64" "64bit" "postfix"

# Check the *_time64 variants
check_function_class "_time64" "time64" "postfix"

# Check the __* variants
check_function_class "__" "underscore" "prefix"

exit $status
