From f1d2d0346a8190e2ea6ea74b48cf887fb0766584 Mon Sep 17 00:00:00 2001 From: JoelYYoung <56264140+JoelYYoung@users.noreply.github.com> Date: Wed, 2 Sep 2026 18:44:35 +1000 Subject: [PATCH] AE: replace legacy interval state with native Box Make BoxProgramState the authoritative abstract-execution state across dense, semi-sparse, and full-sparse modes. Remove the legacy IntervalState path and the unused Octagon, polyhedra, APRON, and alternate Box-storage implementations. Keep pointer, memory, shape, and lifetime facts as internal Box program-state facets. Add Box unit and end-to-end integration coverage. --- .github/workflows/github-action.yml | 8 +- .github/workflows/svf-lib_binaries.yml | 4 +- CMakeLists.txt | 45 +- Dockerfile | 2 +- build.sh | 5 +- cmake/Modules/FindGMP.cmake | 40 + cmake/Modules/FindMPFR.cmake | 33 + cmake/SVFConfig.cmake.in | 16 +- svf-llvm/CMakeLists.txt | 12 +- svf-llvm/tools/AE/ae.cpp | 605 ------- svf-llvm/tools/CMakeLists.txt | 16 + svf/CMakeLists.txt | 107 +- svf/include/AE/Core/AbstractState.h | 419 +---- svf/include/AE/Core/AbstractValue.h | 3 +- svf/include/AE/Core/AddressValue.h | 1 - svf/include/AE/Core/BoxDomain.h | 149 ++ svf/include/AE/Core/BoxProgramState.h | 614 +++++++ svf/include/AE/Core/ICFGWTO.h | 13 + svf/include/AE/Core/IntervalValue.h | 30 +- svf/include/AE/Core/LinearConstraint.h | 229 +++ svf/include/AE/Core/NumericPrimitives.h | 274 ++++ svf/include/AE/Core/NumericValue.h | 4 +- svf/include/AE/Core/NumericalDomain.h | 270 ++++ svf/include/AE/Core/RelationSolver.h | 95 -- svf/include/AE/Core/VariableEnvironment.h | 120 ++ svf/include/AE/Svfexe/AEDetector.h | 2 +- svf/include/AE/Svfexe/AbsExtAPI.h | 10 - .../AE/Svfexe/AbstractInterpretation.h | 255 +-- .../AE/Svfexe/DenseAbstractInterpretation.h | 133 ++ .../NativeSparseAbstractInterpretation.h | 182 +++ svf/include/AE/Svfexe/SVFIRAdapter.h | 74 + .../AE/Svfexe/SparseAbstractInterpretation.h | 178 --- svf/include/Util/Options.h | 2 + svf/lib/AE/Core/AbstractState.cpp | 355 +---- svf/lib/AE/Core/BoxDomain.cpp | 1041 ++++++++++++ svf/lib/AE/Core/BoxProgramState.cpp | 866 ++++++++++ svf/lib/AE/Core/LinearConstraint.cpp | 309 ++++ svf/lib/AE/Core/NumericPrimitives.cpp | 446 ++++++ svf/lib/AE/Core/NumericalDomain.cpp | 1411 +++++++++++++++++ svf/lib/AE/Core/RelationSolver.cpp | 437 ----- svf/lib/AE/Core/VariableEnvironment.cpp | 153 ++ svf/lib/AE/Svfexe/AEDetector.cpp | 23 +- svf/lib/AE/Svfexe/AELoopRecursion.cpp | 136 +- svf/lib/AE/Svfexe/AbsExtAPI.cpp | 86 +- svf/lib/AE/Svfexe/AbstractInterpretation.cpp | 581 +++---- svf/lib/AE/Svfexe/AbstractStateManager.cpp | 287 +--- .../AE/Svfexe/DenseAbstractInterpretation.cpp | 1025 ++++++++++++ .../NativeSparseAbstractInterpretation.cpp | 1039 ++++++++++++ svf/lib/AE/Svfexe/SVFIRAdapter.cpp | 213 +++ .../Svfexe/SparseAbstractInterpretation.cpp | 731 --------- svf/lib/AE/Test/BoxAEIntegrationTest.cpp | 158 ++ svf/lib/AE/Test/BoxDomainTest.cpp | 245 +++ svf/lib/AE/Test/BoxLoop.ll | 29 + svf/lib/AE/Test/BoxReducedProduct.ll | 32 + svf/lib/AE/Test/CMakeLists.txt | 91 ++ svf/lib/AE/Test/DenseEnvironmentAlignment.ll | 27 + svf/lib/AE/Test/SparseMemoryFlow.ll | 20 + svf/lib/AE/Test/WideIntegerTruncation.ll | 7 + svf/lib/Util/Options.cpp | 3 + 59 files changed, 10153 insertions(+), 3548 deletions(-) create mode 100644 cmake/Modules/FindGMP.cmake create mode 100644 cmake/Modules/FindMPFR.cmake create mode 100644 svf/include/AE/Core/BoxDomain.h create mode 100644 svf/include/AE/Core/BoxProgramState.h create mode 100644 svf/include/AE/Core/LinearConstraint.h create mode 100644 svf/include/AE/Core/NumericPrimitives.h create mode 100644 svf/include/AE/Core/NumericalDomain.h delete mode 100644 svf/include/AE/Core/RelationSolver.h create mode 100644 svf/include/AE/Core/VariableEnvironment.h create mode 100644 svf/include/AE/Svfexe/DenseAbstractInterpretation.h create mode 100644 svf/include/AE/Svfexe/NativeSparseAbstractInterpretation.h create mode 100644 svf/include/AE/Svfexe/SVFIRAdapter.h delete mode 100644 svf/include/AE/Svfexe/SparseAbstractInterpretation.h create mode 100644 svf/lib/AE/Core/BoxDomain.cpp create mode 100644 svf/lib/AE/Core/BoxProgramState.cpp create mode 100644 svf/lib/AE/Core/LinearConstraint.cpp create mode 100644 svf/lib/AE/Core/NumericPrimitives.cpp create mode 100644 svf/lib/AE/Core/NumericalDomain.cpp delete mode 100644 svf/lib/AE/Core/RelationSolver.cpp create mode 100644 svf/lib/AE/Core/VariableEnvironment.cpp create mode 100644 svf/lib/AE/Svfexe/DenseAbstractInterpretation.cpp create mode 100644 svf/lib/AE/Svfexe/NativeSparseAbstractInterpretation.cpp create mode 100644 svf/lib/AE/Svfexe/SVFIRAdapter.cpp delete mode 100644 svf/lib/AE/Svfexe/SparseAbstractInterpretation.cpp create mode 100644 svf/lib/AE/Test/BoxAEIntegrationTest.cpp create mode 100644 svf/lib/AE/Test/BoxDomainTest.cpp create mode 100644 svf/lib/AE/Test/BoxLoop.ll create mode 100644 svf/lib/AE/Test/BoxReducedProduct.ll create mode 100644 svf/lib/AE/Test/CMakeLists.txt create mode 100644 svf/lib/AE/Test/DenseEnvironmentAlignment.ll create mode 100644 svf/lib/AE/Test/SparseMemoryFlow.ll create mode 100644 svf/lib/AE/Test/WideIntegerTruncation.ll diff --git a/.github/workflows/github-action.yml b/.github/workflows/github-action.yml index 7b38bae5f6..f676529015 100644 --- a/.github/workflows/github-action.yml +++ b/.github/workflows/github-action.yml @@ -34,13 +34,13 @@ jobs: run: | XCODE_PATH=$(dirname $(dirname $(xcode-select -p))) sudo ln -sfn "$XCODE_PATH" /Applications/Xcode.app - brew install astyle + brew install astyle gmp mpfr - name: ubuntu-setup if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install cmake gcc g++ nodejs doxygen graphviz lcov libncurses5-dev libtinfo6 libzstd-dev astyle + sudo apt-get install cmake gcc g++ nodejs doxygen graphviz lcov libncurses5-dev libtinfo6 libzstd-dev libgmp-dev libmpfr-dev astyle # build-svf - name: build-svf @@ -97,10 +97,10 @@ jobs: run: ctest -R double_free -VV - - name: ctest ae_symabs + - name: ctest box domain and integration working-directory: ${{github.workspace}}/Release-build run: - ctest -R symabs -VV + ctest -R '^box-' -VV - name: ctest ae_assert working-directory: ${{github.workspace}}/Release-build diff --git a/.github/workflows/svf-lib_binaries.yml b/.github/workflows/svf-lib_binaries.yml index c41c8241ba..30b2dcc65b 100644 --- a/.github/workflows/svf-lib_binaries.yml +++ b/.github/workflows/svf-lib_binaries.yml @@ -40,14 +40,14 @@ jobs: run: | XCODE_PATH=$(dirname $(dirname $(xcode-select -p))) sudo ln -sfn "$XCODE_PATH" /Applications/Xcode.app - brew install astyle + brew install astyle gmp mpfr # Ubuntu settings - name: ubuntu-setup if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y cmake gcc g++ nodejs doxygen graphviz libncurses5-dev libtinfo6 libzstd-dev astyle + sudo apt-get install -y cmake gcc g++ nodejs doxygen graphviz libncurses5-dev libtinfo6 libzstd-dev libgmp-dev libmpfr-dev astyle - name: env-setup if: github.event_name == 'push' && github.repository == 'SVF-tools/SVF' diff --git a/CMakeLists.txt b/CMakeLists.txt index dc06781545..152d139d72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,10 @@ include(CMakePackageConfigHelpers) # Allow checking for IPO support by the used compiler include(CheckIPOSupported) +# Define BUILD_TESTING consistently even when the historical Test-Suite +# directory is absent. AE/Core tests are registered independently below. +include(CTest) + # Since SVF builds into non-standard directories; symlink/copy compile commands into top-level directory if(WIN32 OR MINGW @@ -93,6 +97,15 @@ option(SVF_EXPORT_DYNAMIC "Export all (not only the actually used) symbols to dy option(SVF_ENABLE_ASSERTIONS "Always enable debugging assertions, also if the build type is a release build") option(SVF_ENABLE_RTTI "Adds -fno-rtti to disable runtime type information (RTTI)" ON) option(SVF_ENABLE_EXCEPTIONS "Adds -fno-exceptions to disable exception handling" ON) +option(SVF_BUILD_ABSTRACT_DOMAIN_TESTS + "Build Box domain unit and abstract-execution integration tests" + ${BUILD_TESTING}) + +if(NOT SVF_ENABLE_EXCEPTIONS) + message(FATAL_ERROR + "AbstractDomainCore uses exception-based API diagnostics; " + "SVF_ENABLE_EXCEPTIONS must be ON") +endif() # If building dynamic libraries, always enable PIC if(SVF_SHARED_LIBS AND NOT SVF_USE_PIC) @@ -224,6 +237,18 @@ message( Z3 C++ include dirs: ${Z3_CXX_INCLUDE_DIRS}" ) +find_package(GMP REQUIRED) +find_package(MPFR REQUIRED) +message( + STATUS + "Using Box numeric libraries: + GMP include directory: ${GMP_INCLUDE_DIR} + GMP C library: ${GMP_LIBRARY} + GMP C++ library: ${GMPXX_LIBRARY} + MPFR include directory: ${MPFR_INCLUDE_DIR} + MPFR library: ${MPFR_LIBRARY}" +) + # ================================================================================= # SVF configuration interface library # ================================================================================= @@ -272,8 +297,7 @@ install(TARGETS SvfFlags EXPORT SVFTargets) # ================================================================================= # If ./Test-Suite exists, add & run the tests -if(EXISTS "${SVF_SOURCE_DIR}/Test-Suite") - include(CTest) +if(EXISTS "${SVF_SOURCE_DIR}/Test-Suite/CMakeLists.txt") enable_testing() add_subdirectory(Test-Suite) endif() @@ -285,6 +309,11 @@ endif() add_subdirectory(svf) add_subdirectory(svf-llvm) +if(SVF_BUILD_ABSTRACT_DOMAIN_TESTS) + enable_testing() + add_subdirectory(svf/lib/AE/Test) +endif() + # ================================================================================= # SVF build configuration handling (post linking LLVM) # ================================================================================= @@ -292,6 +321,18 @@ add_subdirectory(svf-llvm) # Expose the required ABI flags (e.g., whether RTTI was disabled) in the build & install trees target_compile_options(SvfFlags INTERFACE $<$>:-fno-rtti>) target_link_options(SvfFlags INTERFACE $<$>:-fno-rtti>) +if(TARGET AbstractStateCore) + target_compile_options( + AbstractStateCore PUBLIC + $<$>:-fno-rtti> + ) +endif() +if(TARGET AbstractDomainCore) + target_compile_options( + AbstractDomainCore PUBLIC + $<$>:-fno-rtti> + ) +endif() # Expose build/link flags not required for users of SVF only in the build tree target_compile_options( diff --git a/Dockerfile b/Dockerfile index b5ab108a59..a1a32a38c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ ENV HOME=/home/SVF-tools # Launchpad PPA infrastructure has been intermittently unreachable # (HTTP 504 from add-apt-repository), and SVF itself does not pin a Python # version, so the base-image python is sufficient. -ENV lib_deps="cmake g++ gcc git zlib1g-dev libncurses5-dev libtinfo6 build-essential libssl-dev libpcre2-dev zip libzstd-dev python3-dev" +ENV lib_deps="cmake g++ gcc git zlib1g-dev libncurses5-dev libtinfo6 build-essential libssl-dev libpcre2-dev zip libzstd-dev libgmp-dev libmpfr-dev python3-dev" ENV build_deps="wget xz-utils git tcl" # Fetch dependencies. diff --git a/build.sh b/build.sh index 6eb7e918e3..54aba67756 100755 --- a/build.sh +++ b/build.sh @@ -12,7 +12,8 @@ # If LLVM_DIR or Z3_DIR points to an existing directory, that installation is used. # Otherwise, this script installs/downloads the supported prebuilt dependencies. # -# Linux dependencies include: build-essential libncurses5 libncurses-dev cmake zlib1g-dev unzip xz-utils +# Linux dependencies include: build-essential libncurses5 libncurses-dev cmake +# zlib1g-dev libgmp-dev libmpfr-dev unzip xz-utils set -e set -o pipefail @@ -246,7 +247,7 @@ install_llvm_with_brew() { check_and_install_brew echo "Installing LLVM ${MajorLLVMVer} via Homebrew for ${PLATFORM}." - brew install "llvm@${MajorLLVMVer}" + brew install "llvm@${MajorLLVMVer}" gmp mpfr mkdir -p "$SVFHOME/$LLVMHome" ln -s "$(brew --prefix llvm@${MajorLLVMVer})"/* "$SVFHOME/$LLVMHome" diff --git a/cmake/Modules/FindGMP.cmake b/cmake/Modules/FindGMP.cmake new file mode 100644 index 0000000000..1d1dbdc5ef --- /dev/null +++ b/cmake/Modules/FindGMP.cmake @@ -0,0 +1,40 @@ +# Find the GMP C and C++ libraries. + +include(FindPackageHandleStandardArgs) + +set(_GMP_HINTS ${GMP_ROOT} $ENV{GMP_ROOT} ${GMP_DIR} $ENV{GMP_DIR}) + +find_path(GMP_INCLUDE_DIR NAMES gmp.h gmpxx.h + HINTS ${_GMP_HINTS} PATH_SUFFIXES include) +find_library(GMP_LIBRARY NAMES gmp libgmp + HINTS ${_GMP_HINTS} PATH_SUFFIXES lib lib64) +find_library(GMPXX_LIBRARY NAMES gmpxx libgmpxx + HINTS ${_GMP_HINTS} PATH_SUFFIXES lib lib64) + +find_package_handle_standard_args( + GMP REQUIRED_VARS GMP_INCLUDE_DIR GMP_LIBRARY GMPXX_LIBRARY +) + +if(GMP_FOUND) + if(NOT TARGET GMP::GMP) + add_library(GMP::GMP UNKNOWN IMPORTED) + set_target_properties( + GMP::GMP PROPERTIES + IMPORTED_LOCATION "${GMP_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${GMP_INCLUDE_DIR}" + ) + endif() + if(NOT TARGET GMP::GMPXX) + add_library(GMP::GMPXX UNKNOWN IMPORTED) + set_target_properties( + GMP::GMPXX PROPERTIES + IMPORTED_LOCATION "${GMPXX_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${GMP_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES GMP::GMP + ) + endif() + set(GMP_INCLUDE_DIRS "${GMP_INCLUDE_DIR}") + set(GMP_LIBRARIES GMP::GMPXX GMP::GMP) +endif() + +mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARY GMPXX_LIBRARY) diff --git a/cmake/Modules/FindMPFR.cmake b/cmake/Modules/FindMPFR.cmake new file mode 100644 index 0000000000..0a7b4457c9 --- /dev/null +++ b/cmake/Modules/FindMPFR.cmake @@ -0,0 +1,33 @@ +# Find MPFR and expose MPFR::MPFR. + +include(FindPackageHandleStandardArgs) + +set(_MPFR_HINTS ${MPFR_ROOT} $ENV{MPFR_ROOT} ${MPFR_DIR} $ENV{MPFR_DIR}) + +find_path(MPFR_INCLUDE_DIR NAMES mpfr.h + HINTS ${_MPFR_HINTS} PATH_SUFFIXES include) +find_library(MPFR_LIBRARY NAMES mpfr libmpfr + HINTS ${_MPFR_HINTS} PATH_SUFFIXES lib lib64) + +find_package_handle_standard_args( + MPFR REQUIRED_VARS MPFR_INCLUDE_DIR MPFR_LIBRARY +) + +if(MPFR_FOUND) + if(NOT TARGET GMP::GMP) + find_package(GMP REQUIRED) + endif() + if(NOT TARGET MPFR::MPFR) + add_library(MPFR::MPFR UNKNOWN IMPORTED) + set_target_properties( + MPFR::MPFR PROPERTIES + IMPORTED_LOCATION "${MPFR_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${MPFR_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES GMP::GMP + ) + endif() + set(MPFR_INCLUDE_DIRS "${MPFR_INCLUDE_DIR}") + set(MPFR_LIBRARIES MPFR::MPFR) +endif() + +mark_as_advanced(MPFR_INCLUDE_DIR MPFR_LIBRARY) diff --git a/cmake/SVFConfig.cmake.in b/cmake/SVFConfig.cmake.in index e41d5694a5..5339c9b335 100644 --- a/cmake/SVFConfig.cmake.in +++ b/cmake/SVFConfig.cmake.in @@ -4,9 +4,6 @@ # Ensure the CMake files in this package's directory can be found list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}" "${CMAKE_CURRENT_LIST_DIR}/Modules") -# Ensure SVF's targets & settings are available publicly -include("${CMAKE_CURRENT_LIST_DIR}/SVFTargets.cmake") - # Set basic variables from this SVF build set(SVF_VERSION @SVF_VERSION@) set(SVF_BUILD_TYPE @SVF_BUILD_TYPE@) @@ -69,18 +66,25 @@ set(SVF_BUILD_DIR "@SVF_BINARY_DIR@") set(SVF_BUILD_EXTAPI_BC "@SVF_BUILD_EXTAPI_BC@") set(SVF_EXTAPI_BC_NAME "@SVF_EXTAPI_BC_NAME@") -# Add the absolute paths of the extapi.bc bitcode file to the imported target's compiler definitions interface -target_compile_definitions(SVF::SvfFlags INTERFACE SVF_INSTALL_EXTAPI_BC="${SVF_INSTALL_EXTAPI_BC}") - # Make `find_dependency()` available include(CMakeFindDependencyMacro) # Find Z3 (required) find_dependency(Z3 REQUIRED) +# Box uses exact GMP rationals and explicit MPFR rounding. +find_dependency(GMP REQUIRED) +find_dependency(MPFR REQUIRED) + # Find upstream LLVM (required) find_dependency(LLVM CONFIG REQUIRED) +# Load exported targets only after all imported dependency targets exist. +include("${CMAKE_CURRENT_LIST_DIR}/SVFTargets.cmake") + +# Add the absolute path of extapi.bc after the imported target exists. +target_compile_definitions(SVF::SvfFlags INTERFACE SVF_INSTALL_EXTAPI_BC="${SVF_INSTALL_EXTAPI_BC}") + # Make the include/link directories & definitions available globally for users of SVF separate_arguments(_LLVM_DEFINITIONS NATIVE_COMMAND ${LLVM_DEFINITIONS}) include_directories(SYSTEM ${LLVM_INCLUDE_DIRS}) diff --git a/svf-llvm/CMakeLists.txt b/svf-llvm/CMakeLists.txt index 65ec966564..ee410bea08 100644 --- a/svf-llvm/CMakeLists.txt +++ b/svf-llvm/CMakeLists.txt @@ -168,8 +168,16 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") ) endif() -# Only expose the headers in the source tree to in-tree users of SVF -target_include_directories(SvfLLVM PUBLIC $) +# Only expose the headers in the source tree to in-tree users of SVF. SvfLLVM's +# public headers include LLVM headers, so their build-tree include directories +# are part of the public build interface as well. +target_include_directories( + SvfLLVM + PUBLIC $ +) +target_include_directories( + SvfLLVM SYSTEM PUBLIC "$" +) # Set the library & .so version of the LLVM library set_target_properties(SvfLLVM PROPERTIES VERSION ${SVF_VERSION} SOVERSION ${SVF_VERSION_MAJOR}) diff --git a/svf-llvm/tools/AE/ae.cpp b/svf-llvm/tools/AE/ae.cpp index 06220eafdd..1adc47c11e 100644 --- a/svf-llvm/tools/AE/ae.cpp +++ b/svf-llvm/tools/AE/ae.cpp @@ -26,602 +26,22 @@ // Author: Jiawei Wang, Xiao Cheng, Jiawei Yang, Jiawei Ren, Yulei Sui */ #include "SVF-LLVM/SVFIRBuilder.h" -#include "WPA/WPAPass.h" #include "Util/CommandLine.h" #include "Util/Options.h" #include "WPA/Andersen.h" -#include "AE/Core/RelExeState.h" -#include "AE/Core/RelationSolver.h" #include "AE/Svfexe/AbstractInterpretation.h" using namespace SVF; using namespace SVFUtil; -static Option SYMABS( - "symabs", - "symbolic abstraction test", - false -); - static Option AETEST( "aetest", "abstract execution basic function test", false ); -class SymblicAbstractionTest -{ -public: - SymblicAbstractionTest() = default; - - ~SymblicAbstractionTest() = default; - - static z3::context& getContext() - { - return Z3Expr::getContext(); - } - - void test_print() - { - outs() << "hello print\n"; - } - - AbstractState RSY_time(AbstractState& inv, const Z3Expr& phi, - RelationSolver& rs) - { - auto start_time = std::chrono::high_resolution_clock::now(); - AbstractState resRSY = rs.RSY(inv, phi); - auto end_time = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast( - end_time - start_time); - outs() << "running time of RSY : " << duration.count() - << " microseconds\n"; - return resRSY; - } - AbstractState Bilateral_time(AbstractState& inv, const Z3Expr& phi, - RelationSolver& rs) - { - auto start_time = std::chrono::high_resolution_clock::now(); - AbstractState resBilateral = rs.bilateral(inv, phi); - auto end_time = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast( - end_time - start_time); - outs() << "running time of Bilateral: " << duration.count() - << " microseconds\n"; - return resBilateral; - } - AbstractState BS_time(AbstractState& inv, const Z3Expr& phi, - RelationSolver& rs) - { - auto start_time = std::chrono::high_resolution_clock::now(); - AbstractState resBS = rs.BS(inv, phi); - auto end_time = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast( - end_time - start_time); - outs() << "running time of BS : " << duration.count() - << " microseconds\n"; - return resBS; - } - - void testRelExeState1_1() - { - outs() << sucMsg("\t SUCCESS :") << "test1_1 start\n"; - AbstractState itv; - RelExeState relation; - // var0 := [0, 1]; - itv[0] = IntervalValue(0, 1); - relation[0] = getContext().int_const("0"); - // var1 := var0 + 1; - relation[1] = - getContext().int_const("1") == getContext().int_const("0") + 1; - itv[1] = itv[0].getInterval() + IntervalValue(1); - // Test extract sub vars - Set res; - relation.extractSubVars(relation[1], res); - assert(res == Set({0, 1}) && "inconsistency occurs"); - AbstractState inv = itv.sliceState(res); - RelationSolver rs; - const Z3Expr& relExpr = relation[1]; - const Z3Expr& initExpr = rs.gamma_hat(inv); - const Z3Expr& phi = (relExpr && initExpr).simplify(); - AbstractState resRSY = rs.RSY(inv, phi); - AbstractState resBilateral = rs.bilateral(inv, phi); - AbstractState resBS = rs.BS(inv, phi); - // 0:[0,1] 1:[1,2] - assert(resRSY == resBS && resBS == resBilateral && "inconsistency occurs"); - for (auto r : resRSY.getVarToVal()) - { - outs() << r.first << " " << r.second.getInterval() << "\n"; - } - AbstractState::VarToAbsValMap intendedRes = {{0, IntervalValue(0, 1)}, {1, IntervalValue(1, 2)}}; - assert(resBS.eqVarToValMap(resBS.getVarToVal(), intendedRes) && "inconsistency occurs"); - } - - void testRelExeState1_2() - { - outs() << "test1_2 start\n"; - AbstractState itv; - RelExeState relation; - // var0 := [0, 1]; - relation[0] = getContext().int_const("0"); - itv[0] = IntervalValue(0, 1); - // var1 := var0 + 1; - relation[1] = - getContext().int_const("1") == getContext().int_const("0") * 2; - itv[1] = itv[0].getInterval() * IntervalValue(2); - - // Test extract sub vars - Set res; - relation.extractSubVars(relation[1], res); - assert(res == Set({0, 1}) && "inconsistency occurs"); - AbstractState inv = itv.sliceState(res); - RelationSolver rs; - const Z3Expr& relExpr = relation[1]; - const Z3Expr& initExpr = rs.gamma_hat(inv); - const Z3Expr& phi = (relExpr && initExpr).simplify(); - AbstractState resRSY = rs.RSY(inv, phi); - AbstractState resBilateral = rs.bilateral(inv, phi); - AbstractState resBS = rs.BS(inv, phi); - // 0:[0,1] 1:[0,2] - assert(resRSY == resBS && resBS == resBilateral && "inconsistency occurs"); - for (auto r : resRSY.getVarToVal()) - { - outs() << r.first << " " << r.second.getInterval() << "\n"; - } - AbstractState::VarToAbsValMap intendedRes = {{0, IntervalValue(0, 1)}, {1, IntervalValue(0, 2)}}; - assert(resBS.eqVarToValMap(resBS.getVarToVal(), intendedRes) && "inconsistency occurs"); - } - - void testRelExeState2_1() - { - outs() << "test2_1 start\n"; - AbstractState itv; - RelExeState relation; - // var0 := [0, 10]; - relation[0] = getContext().int_const("0"); - itv[0] = IntervalValue(0, 10); - // var1 := var0; - relation[1] = - getContext().int_const("1") == getContext().int_const("0"); - itv[1] = itv[0]; - // var2 := var1 - var0; - relation[2] = getContext().int_const("2") == - getContext().int_const("1") - getContext().int_const("0"); - itv[2] = itv[1].getInterval() - itv[0].getInterval(); - // Test extract sub vars - Set res; - relation.extractSubVars(relation[2], res); - assert(res == Set({0, 1, 2}) && "inconsistency occurs"); - AbstractState inv = itv.sliceState(res); - RelationSolver rs; - const Z3Expr& relExpr = relation[2] && relation[1]; - const Z3Expr& initExpr = rs.gamma_hat(inv); - const Z3Expr& phi = (relExpr && initExpr).simplify(); - AbstractState resRSY = rs.RSY(inv, phi); - AbstractState resBilateral = rs.bilateral(inv, phi); - AbstractState resBS = rs.BS(inv, phi); - // 0:[0,10] 1:[0,10] 2:[0,0] - assert(resRSY == resBS && resBS == resBilateral && "inconsistency occurs"); - for (auto r : resRSY.getVarToVal()) - { - outs() << r.first << " " << r.second.getInterval() << "\n"; - } - // ground truth - AbstractState::VarToAbsValMap intendedRes = {{0, IntervalValue(0, 10)}, - {1, IntervalValue(0, 10)}, - {2, IntervalValue(0, 0)} - }; - assert(resBS.eqVarToValMap(resBS.getVarToVal(), intendedRes) && "inconsistency occurs"); - } - - void testRelExeState2_2() - { - outs() << "test2_2 start\n"; - AbstractState itv; - RelExeState relation; - // var0 := [0, 100]; - relation[0] = getContext().int_const("0"); - itv[0] = IntervalValue(0, 100); - // var1 := var0; - relation[1] = - getContext().int_const("1") == getContext().int_const("0"); - itv[1] = itv[0]; - // var2 := var1 - var0; - relation[2] = getContext().int_const("2") == - getContext().int_const("1") - getContext().int_const("0"); - itv[2] = itv[1].getInterval() - itv[0].getInterval(); - - // Test extract sub vars - Set res; - relation.extractSubVars(relation[2], res); - assert(res == Set({0, 1, 2}) && "inconsistency occurs"); - AbstractState inv = itv.sliceState(res); - RelationSolver rs; - const Z3Expr& relExpr = relation[2] && relation[1]; - const Z3Expr& initExpr = rs.gamma_hat(inv); - const Z3Expr& phi = (relExpr && initExpr).simplify(); - AbstractState resRSY = rs.RSY(inv, phi); - AbstractState resBilateral = rs.bilateral(inv, phi); - AbstractState resBS = rs.BS(inv, phi); - // 0:[0,100] 1:[0,100] 2:[0,0] - assert(resRSY == resBS && resBS == resBilateral && "inconsistency occurs"); - for (auto r : resRSY.getVarToVal()) - { - outs() << r.first << " " << r.second.getInterval() << "\n"; - } - // ground truth - AbstractState::VarToAbsValMap intendedRes = {{0, IntervalValue(0, 100)}, - {1, IntervalValue(0, 100)}, - {2, IntervalValue(0, 0)} - }; - assert(resBS.eqVarToValMap(resBS.getVarToVal(), intendedRes) && "inconsistency occurs"); - } - - void testRelExeState2_3() - { - outs() << "test2_3 start\n"; - AbstractState itv; - RelExeState relation; - // var0 := [0, 1000]; - relation[0] = getContext().int_const("0"); - itv[0] = IntervalValue(0, 1000); - // var1 := var0; - relation[1] = - getContext().int_const("1") == getContext().int_const("0"); - itv[1] = itv[0]; - // var2 := var1 - var0; - relation[2] = getContext().int_const("2") == - getContext().int_const("1") - getContext().int_const("0"); - itv[2] = itv[1].getInterval() - itv[0].getInterval(); - - // Test extract sub vars - Set res; - relation.extractSubVars(relation[2], res); - assert(res == Set({0, 1, 2}) && "inconsistency occurs"); - AbstractState inv = itv.sliceState(res); - RelationSolver rs; - const Z3Expr& relExpr = relation[2] && relation[1]; - const Z3Expr& initExpr = rs.gamma_hat(inv); - const Z3Expr& phi = (relExpr && initExpr).simplify(); - AbstractState resRSY = rs.RSY(inv, phi); - AbstractState resBilateral = rs.bilateral(inv, phi); - AbstractState resBS = rs.BS(inv, phi); - // 0:[0,1000] 1:[0,1000] 2:[0,0] - assert(resRSY == resBS && resBS == resBilateral && "inconsistency occurs"); - for (auto r : resRSY.getVarToVal()) - { - outs() << r.first << " " << r.second.getInterval() << "\n"; - } - // ground truth - AbstractState::VarToAbsValMap intendedRes = {{0, IntervalValue(0, 1000)}, - {1, IntervalValue(0, 1000)}, - {2, IntervalValue(0, 0)} - }; - assert(resBS.eqVarToValMap(resBS.getVarToVal(), intendedRes) && "inconsistency occurs"); - } - - void testRelExeState2_4() - { - outs() << "test2_4 start\n"; - AbstractState itv; - RelExeState relation; - // var0 := [0, 10000]; - relation[0] = getContext().int_const("0"); - itv[0] = IntervalValue(0, 10000); - // var1 := var0; - relation[1] = - getContext().int_const("1") == getContext().int_const("0"); - itv[1] = itv[0]; - // var2 := var1 - var0; - relation[2] = getContext().int_const("2") == - getContext().int_const("1") - getContext().int_const("0"); - itv[2] = itv[1].getInterval() - itv[0].getInterval(); - - // Test extract sub vars - Set res; - relation.extractSubVars(relation[2], res); - assert(res == Set({0, 1, 2}) && "inconsistency occurs"); - AbstractState inv = itv.sliceState(res); - RelationSolver rs; - const Z3Expr& relExpr = relation[2] && relation[1]; - const Z3Expr& initExpr = rs.gamma_hat(inv); - const Z3Expr& phi = (relExpr && initExpr).simplify(); - AbstractState resRSY = RSY_time(inv, phi, rs); - AbstractState resBilateral = Bilateral_time(inv, phi, rs); - AbstractState resBS = BS_time(inv, phi, rs); - // 0:[0,10000] 1:[0,10000] 2:[0,0] - assert(resRSY == resBS && resBS == resBilateral && "inconsistency occurs"); - for (auto r : resRSY.getVarToVal()) - { - outs() << r.first << " " << r.second.getInterval() << "\n"; - } - // ground truth - AbstractState::VarToAbsValMap intendedRes = {{0, IntervalValue(0, 10000)}, - {1, IntervalValue(0, 10000)}, - {2, IntervalValue(0, 0)} - }; - assert(resBS.eqVarToValMap(resBS.getVarToVal(), intendedRes) && "inconsistency occurs"); - } - - void testRelExeState2_5() - { - outs() << "test2_5 start\n"; - AbstractState itv; - RelExeState relation; - // var0 := [0, 100000]; - relation[0] = getContext().int_const("0"); - itv[0] = IntervalValue(0, 100000); - // var1 := var0; - relation[1] = - getContext().int_const("1") == getContext().int_const("0"); - itv[1] = itv[0]; - // var2 := var1 - var0; - relation[2] = getContext().int_const("2") == - getContext().int_const("1") - getContext().int_const("0"); - itv[2] = itv[1].getInterval() - itv[0].getInterval(); - - // Test extract sub vars - Set res; - relation.extractSubVars(relation[2], res); - assert(res == Set({0, 1, 2}) && "inconsistency occurs"); - AbstractState inv = itv.sliceState(res); - RelationSolver rs; - const Z3Expr& relExpr = relation[2] && relation[1]; - const Z3Expr& initExpr = rs.gamma_hat(inv); - const Z3Expr& phi = (relExpr && initExpr).simplify(); - AbstractState resRSY = RSY_time(inv, phi, rs); - AbstractState resBilateral = Bilateral_time(inv, phi, rs); - AbstractState resBS = BS_time(inv, phi, rs); - // 0:[0,100000] 1:[0,100000] 2:[0,0] - assert(resRSY == resBS && resBS == resBilateral && "inconsistency occurs"); - for (auto r : resRSY.getVarToVal()) - { - outs() << r.first << " " << r.second.getInterval() << "\n"; - } - // ground truth - AbstractState::VarToAbsValMap intendedRes = {{0, IntervalValue(0, 100000)}, - {1, IntervalValue(0, 100000)}, - {2, IntervalValue(0, 0)} - }; - assert(resBS.eqVarToValMap(resBS.getVarToVal(), intendedRes) && "inconsistency occurs"); - } - - void testRelExeState3_1() - { - outs() << "test3_1 start\n"; - AbstractState itv; - RelExeState relation; - // var0 := [1, 10]; - relation[0] = getContext().int_const("0"); - itv[0] = IntervalValue(1, 10); - // var1 := var0; - relation[1] = - getContext().int_const("1") == getContext().int_const("0"); - itv[1] = itv[0]; - // var2 := var1 / var0; - relation[2] = getContext().int_const("2") == - getContext().int_const("1") / getContext().int_const("0"); - itv[2] = itv[1].getInterval() / itv[0].getInterval(); - // Test extract sub vars - Set res; - relation.extractSubVars(relation[2], res); - assert(res == Set({0, 1, 2}) && "inconsistency occurs"); - AbstractState inv = itv.sliceState(res); - RelationSolver rs; - const Z3Expr& relExpr = relation[2] && relation[1]; - const Z3Expr& initExpr = rs.gamma_hat(inv); - const Z3Expr& phi = (relExpr && initExpr).simplify(); - AbstractState resRSY = rs.RSY(inv, phi); - AbstractState resBilateral = rs.bilateral(inv, phi); - AbstractState resBS = rs.BS(inv, phi); - // 0:[1,10] 1:[1,10] 2:[1,1] - assert(resRSY == resBS && resBS == resBilateral && "inconsistency occurs"); - for (auto r : resRSY.getVarToVal()) - { - outs() << r.first << " " << r.second.getInterval() << "\n"; - } - // ground truth - AbstractState::VarToAbsValMap intendedRes = {{0, IntervalValue(1, 10)}, - {1, IntervalValue(1, 10)}, - {2, IntervalValue(1, 1)} - }; - assert(resBS.eqVarToValMap(resBS.getVarToVal(), intendedRes) && "inconsistency occurs"); - } - - void testRelExeState3_2() - { - outs() << "test3_2 start\n"; - AbstractState itv; - RelExeState relation; - // var0 := [1, 1000]; - relation[0] = getContext().int_const("0"); - itv[0] = IntervalValue(1, 1000); - // var1 := var0; - relation[1] = - getContext().int_const("1") == getContext().int_const("0"); - itv[1] = itv[0]; - // var2 := var1 / var0; - relation[2] = getContext().int_const("2") == - getContext().int_const("1") / getContext().int_const("0"); - itv[2] = itv[1].getInterval() / itv[0].getInterval(); - // Test extract sub vars - Set res; - relation.extractSubVars(relation[2], res); - assert(res == Set({0, 1, 2}) && "inconsistency occurs"); - AbstractState inv = itv.sliceState(res); - RelationSolver rs; - const Z3Expr& relExpr = relation[2] && relation[1]; - const Z3Expr& initExpr = rs.gamma_hat(inv); - const Z3Expr& phi = (relExpr && initExpr).simplify(); - AbstractState resRSY = rs.RSY(inv, phi); - AbstractState resBilateral = rs.bilateral(inv, phi); - AbstractState resBS = rs.BS(inv, phi); - // 0:[1,1000] 1:[1,1000] 2:[1,1] - assert(resRSY == resBS && resBS == resBilateral && "inconsistency occurs"); - for (auto r : resRSY.getVarToVal()) - { - outs() << r.first << " " << r.second.getInterval() << "\n"; - } - // ground truth - AbstractState::VarToAbsValMap intendedRes = {{0, IntervalValue(1, 1000)}, - {1, IntervalValue(1, 1000)}, - {2, IntervalValue(1, 1)} - }; - assert(resBS.eqVarToValMap(resBS.getVarToVal(), intendedRes) && "inconsistency occurs"); - } - - void testRelExeState3_3() - { - outs() << "test3_3 start\n"; - AbstractState itv; - RelExeState relation; - // var0 := [1, 10000]; - relation[0] = getContext().int_const("0"); - itv[0] = IntervalValue(1, 10000); - // var1 := var0; - relation[1] = - getContext().int_const("1") == getContext().int_const("0"); - itv[1] = itv[0]; - // var2 := var1 / var0; - relation[2] = getContext().int_const("2") == - getContext().int_const("1") / getContext().int_const("0"); - itv[2] = itv[1].getInterval() / itv[0].getInterval(); - // Test extract sub vars - Set res; - relation.extractSubVars(relation[2], res); - assert(res == Set({0, 1, 2}) && "inconsistency occurs"); - AbstractState inv = itv.sliceState(res); - RelationSolver rs; - const Z3Expr& relExpr = relation[2] && relation[1]; - const Z3Expr& initExpr = rs.gamma_hat(inv); - const Z3Expr& phi = (relExpr && initExpr).simplify(); - AbstractState resRSY = RSY_time(inv, phi, rs); - AbstractState resBilateral = Bilateral_time(inv, phi, rs); - AbstractState resBS = BS_time(inv, phi, rs); - // 0:[1,10000] 1:[1,10000] 2:[1,1] - assert(resRSY == resBS && resBS == resBilateral && "inconsistency occurs"); - for (auto r : resRSY.getVarToVal()) - { - outs() << r.first << " " << r.second.getInterval() << "\n"; - } - // ground truth - AbstractState::VarToAbsValMap intendedRes = {{0, IntervalValue(1, 10000)}, - {1, IntervalValue(1, 10000)}, - {2, IntervalValue(1, 1)} - }; - } - - void testRelExeState3_4() - { - outs() << "test3_4 start\n"; - AbstractState itv; - RelExeState relation; - // var0 := [1, 100000]; - relation[0] = getContext().int_const("0"); - itv[0] = IntervalValue(1, 100000); - // var1 := var0; - relation[1] = - getContext().int_const("1") == getContext().int_const("0"); - itv[1] = itv[0]; - // var2 := var1 / var0; - relation[2] = getContext().int_const("2") == - getContext().int_const("1") / getContext().int_const("0"); - itv[2] = itv[1].getInterval() / itv[0].getInterval(); - // Test extract sub vars - Set res; - relation.extractSubVars(relation[2], res); - assert(res == Set({0, 1, 2}) && "inconsistency occurs"); - AbstractState inv = itv.sliceState(res); - RelationSolver rs; - const Z3Expr& relExpr = relation[2] && relation[1]; - const Z3Expr& initExpr = rs.gamma_hat(inv); - const Z3Expr& phi = (relExpr && initExpr).simplify(); - AbstractState resRSY = RSY_time(inv, phi, rs); - AbstractState resBilateral = Bilateral_time(inv, phi, rs); - AbstractState resBS = BS_time(inv, phi, rs); - // 0:[1,100000] 1:[1,100000] 2:[1,1] - assert(resRSY == resBS && resBS == resBilateral && "inconsistency occurs"); - for (auto r : resRSY.getVarToVal()) - { - outs() << r.first << " " << r.second.getInterval() << "\n"; - } - // ground truth - AbstractState::VarToAbsValMap intendedRes = {{0, IntervalValue(1, 100000)}, - {1, IntervalValue(1, 100000)}, - {2, IntervalValue(1, 1)} - }; - assert(resBS.eqVarToValMap(resBS.getVarToVal(), intendedRes) && "inconsistency occurs"); - } - - void testRelExeState4_1() - { - outs() << "test4_1 start\n"; - AbstractState itv; - RelExeState relation; - // var0 := [0, 10]; - relation[0] = getContext().int_const("0"); - itv[0] = IntervalValue(0, 10); - // var1 := var0; - relation[1] = - getContext().int_const("1") == getContext().int_const("0"); - itv[1] = itv[0]; - // var2 := var1 / var0; - relation[2] = getContext().int_const("2") == - getContext().int_const("1") / getContext().int_const("0"); - itv[2] = itv[1].getInterval() / itv[0].getInterval(); - // Test extract sub vars - Set res; - relation.extractSubVars(relation[2], res); - assert(res == Set({0, 1, 2}) && "inconsistency occurs"); - AbstractState inv = itv.sliceState(res); - RelationSolver rs; - const Z3Expr& relExpr = relation[2] && relation[1]; - const Z3Expr& initExpr = rs.gamma_hat(inv); - const Z3Expr& phi = (relExpr && initExpr).simplify(); - // IntervalExeState resRSY = rs.RSY(inv, phi); - outs() << "rsy done\n"; - // IntervalExeState resBilateral = rs.bilateral(inv, phi); - outs() << "bilateral done\n"; - AbstractState resBS = rs.BS(inv, phi); - outs() << "bs done\n"; - // 0:[0,10] 1:[0,10] 2:[-00,+00] - // assert(resRSY == resBS && resBS == resBilateral); - for (auto r : resBS.getVarToVal()) - { - outs() << r.first << " " << r.second.getInterval() << "\n"; - } - // ground truth - AbstractState::VarToAbsValMap intendedRes = {{0, IntervalValue(0, 10)}, - {1, IntervalValue(0, 10)}, - {2, IntervalValue(0, 10)} - }; - assert(resBS.eqVarToValMap(resBS.getVarToVal(), intendedRes) && "inconsistency occurs"); - } - - void testsValidation() - { - SymblicAbstractionTest saTest; - saTest.testRelExeState1_1(); - saTest.testRelExeState1_2(); - - saTest.testRelExeState2_1(); - saTest.testRelExeState2_2(); - saTest.testRelExeState2_3(); - // saTest.testRelExeState2_4(); /// 10000 - // saTest.testRelExeState2_5(); /// 100000 - - saTest.testRelExeState3_1(); - saTest.testRelExeState3_2(); - // saTest.testRelExeState3_3(); /// 10000 - // saTest.testRelExeState3_4(); /// 100000 - - outs() << "start top\n"; - saTest.testRelExeState4_1(); /// top - } -}; - class AETest { public: @@ -825,23 +245,6 @@ class AETest assert((IntervalValue(-6, 6) ^ IntervalValue(3, 9)).equals(IntervalValue::top())); } - void testAbsState() - { - AbstractState as; - as[1] = IntervalValue(1, 3); - as[2] = IntervalValue(2, 7); - as[3] = AddressValue(0x7f000007); - as[4] = AddressValue(0x7f000008); - // store: *as[3] = as[1], *as[4] = as[2] - for (auto addr : as[3].getAddrs()) as.store(addr, as[1]); - for (auto addr : as[4].getAddrs()) as.store(addr, as[2]); - as.printAbstractState(); - // load: verify *as[3] == as[1] && *as[4] == as[2] - AbstractValue v3, v4; - for (auto addr : as[3].getAddrs()) v3.join_with(as.load(addr)); - for (auto addr : as[4].getAddrs()) v4.join_with(as.load(addr)); - assert(v3.equals(as[1]) && v4.equals(as[2])); - } }; @@ -865,18 +268,10 @@ int main(int argc, char** argv) arg_num, arg_value, "Static Symbolic Execution", "[options] " ); delete[] arg_value; - if (SYMABS()) - { - SymblicAbstractionTest saTest; - saTest.testsValidation(); - return 0; - } - if (AETEST()) { AETest aeTest; aeTest.testBinaryOpStmt(); - aeTest.testAbsState(); return 0; } diff --git a/svf-llvm/tools/CMakeLists.txt b/svf-llvm/tools/CMakeLists.txt index 8c3ba3530b..96d9851131 100644 --- a/svf-llvm/tools/CMakeLists.txt +++ b/svf-llvm/tools/CMakeLists.txt @@ -1,3 +1,19 @@ +if(APPLE) + # LLVM distributions may install a private libc++ beside libLLVM. Because + # that directory precedes the SDK on the link path, it can shadow the host + # runtime and leave exception-enabled SVF archives unresolved. Select the + # SDK runtime explicitly at the end of every tool link. + execute_process( + COMMAND xcrun --show-sdk-path + OUTPUT_VARIABLE SVF_APPLE_SDK + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set( + CMAKE_CXX_STANDARD_LIBRARIES + "${CMAKE_CXX_STANDARD_LIBRARIES} ${SVF_APPLE_SDK}/usr/lib/libc++.tbd" + ) +endif() + add_subdirectory(SABER) add_subdirectory(WPA) add_subdirectory(Example) diff --git a/svf/CMakeLists.txt b/svf/CMakeLists.txt index 04d9dbc057..a7f69239da 100644 --- a/svf/CMakeLists.txt +++ b/svf/CMakeLists.txt @@ -1,12 +1,117 @@ -# Define the core library +# Build the backend-neutral abstract-domain library as a separate target. None of +# these sources includes SVF IR or LLVM headers. +set(ABSTRACT_STATE_CORE_HEADERS + ${CMAKE_CURRENT_LIST_DIR}/include/AE/Core/AbstractState.h +) +set(ABSTRACT_STATE_CORE_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/lib/AE/Core/AbstractState.cpp +) +set(ABSTRACT_DOMAIN_CORE_HEADERS + ${CMAKE_CURRENT_LIST_DIR}/include/AE/Core/VariableEnvironment.h + ${CMAKE_CURRENT_LIST_DIR}/include/AE/Core/LinearConstraint.h + ${CMAKE_CURRENT_LIST_DIR}/include/AE/Core/NumericalDomain.h + ${CMAKE_CURRENT_LIST_DIR}/include/AE/Core/BoxDomain.h + ${CMAKE_CURRENT_LIST_DIR}/include/AE/Core/BoxProgramState.h + ${CMAKE_CURRENT_LIST_DIR}/include/AE/Core/NumericPrimitives.h +) +set(ABSTRACT_DOMAIN_CORE_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/lib/AE/Core/BoxDomain.cpp + ${CMAKE_CURRENT_LIST_DIR}/lib/AE/Core/BoxProgramState.cpp + ${CMAKE_CURRENT_LIST_DIR}/lib/AE/Core/VariableEnvironment.cpp + ${CMAKE_CURRENT_LIST_DIR}/lib/AE/Core/LinearConstraint.cpp + ${CMAKE_CURRENT_LIST_DIR}/lib/AE/Core/NumericalDomain.cpp + ${CMAKE_CURRENT_LIST_DIR}/lib/AE/Core/NumericPrimitives.cpp +) +set(ABSTRACT_DOMAIN_INTEGRATION_HEADERS + ${CMAKE_CURRENT_LIST_DIR}/include/AE/Svfexe/DenseAbstractInterpretation.h + ${CMAKE_CURRENT_LIST_DIR}/include/AE/Svfexe/NativeSparseAbstractInterpretation.h + ${CMAKE_CURRENT_LIST_DIR}/include/AE/Svfexe/SVFIRAdapter.h +) +set(ABSTRACT_DOMAIN_INTEGRATION_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/lib/AE/Svfexe/DenseAbstractInterpretation.cpp + ${CMAKE_CURRENT_LIST_DIR}/lib/AE/Svfexe/NativeSparseAbstractInterpretation.cpp + ${CMAKE_CURRENT_LIST_DIR}/lib/AE/Svfexe/SVFIRAdapter.cpp +) + +# The common state lattice is kept separate from the GMP/MPFR-backed Box core. +add_library(AbstractStateCore) +add_library(SVF::AbstractStateCore ALIAS AbstractStateCore) +target_compile_features(AbstractStateCore PUBLIC cxx_std_17) +target_sources( + AbstractStateCore + PUBLIC FILE_SET HEADERS + BASE_DIRS ${CMAKE_CURRENT_LIST_DIR}/include + FILES ${ABSTRACT_STATE_CORE_HEADERS} + PRIVATE ${ABSTRACT_STATE_CORE_SOURCES} +) +target_include_directories( + AbstractStateCore PUBLIC + $ +) +set_target_properties( + AbstractStateCore PROPERTIES + VERSION ${SVF_VERSION} + SOVERSION ${SVF_VERSION_MAJOR} +) +install( + TARGETS AbstractStateCore + EXPORT SVFTargets + RUNTIME DESTINATION ${SVF_INSTALL_BINDIR} + LIBRARY DESTINATION ${SVF_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${SVF_INSTALL_LIBDIR} + FILE_SET HEADERS + DESTINATION ${SVF_INSTALL_INCLUDEDIR} +) + +add_library(AbstractDomainCore) +add_library(SVF::AbstractDomainCore ALIAS AbstractDomainCore) +target_compile_features(AbstractDomainCore PUBLIC cxx_std_17) +target_sources( + AbstractDomainCore + PUBLIC FILE_SET HEADERS + BASE_DIRS ${CMAKE_CURRENT_LIST_DIR}/include + FILES ${ABSTRACT_DOMAIN_CORE_HEADERS} + PRIVATE ${ABSTRACT_DOMAIN_CORE_SOURCES} +) +target_include_directories( + AbstractDomainCore PUBLIC + $ +) +target_link_libraries( + AbstractDomainCore PUBLIC AbstractStateCore GMP::GMPXX MPFR::MPFR +) +set_target_properties( + AbstractDomainCore PROPERTIES + VERSION ${SVF_VERSION} + SOVERSION ${SVF_VERSION_MAJOR} +) + +install( + TARGETS AbstractDomainCore + EXPORT SVFTargets + RUNTIME DESTINATION ${SVF_INSTALL_BINDIR} + LIBRARY DESTINATION ${SVF_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${SVF_INSTALL_LIBDIR} + FILE_SET HEADERS + DESTINATION ${SVF_INSTALL_INCLUDEDIR} +) + +# Define the core SVF library. add_library(SvfCore) # Inherit compiler & linker options by publically linking against the interface library target_link_libraries(SvfCore PUBLIC SvfFlags) +target_link_libraries(SvfCore PUBLIC AbstractStateCore) # Gather & set all of the core library's source files by globbing all .h and .cpp files (recursively) file(GLOB_RECURSE SVF_CORE_HEADERS ${CMAKE_CURRENT_LIST_DIR}/include/*.h) file(GLOB_RECURSE SVF_CORE_SOURCES ${CMAKE_CURRENT_LIST_DIR}/lib/*.cpp) +list(FILTER SVF_CORE_SOURCES EXCLUDE REGEX "/lib/AE/Test/") +list(REMOVE_ITEM SVF_CORE_HEADERS + ${ABSTRACT_STATE_CORE_HEADERS} ${ABSTRACT_DOMAIN_CORE_HEADERS}) +list(REMOVE_ITEM SVF_CORE_SOURCES + ${ABSTRACT_STATE_CORE_SOURCES} ${ABSTRACT_DOMAIN_CORE_SOURCES}) +target_link_libraries(SvfCore PUBLIC AbstractDomainCore) target_sources( SvfCore PUBLIC FILE_SET HEADERS diff --git a/svf/include/AE/Core/AbstractState.h b/svf/include/AE/Core/AbstractState.h index 96c3e6edd0..f489be0baa 100644 --- a/svf/include/AE/Core/AbstractState.h +++ b/svf/include/AE/Core/AbstractState.h @@ -1,370 +1,83 @@ -//===- AbstractExeState.h ----Interval Domain-------------------------// -// -// SVF: Static Value-Flow Analysis -// -// Copyright (C) <2013-2022> -// +//===- AbstractState.h -- Common abstract-state lattice API -----*- C++ -*-===// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. +#ifndef SVF_AE_ABSTRACT_STATE_H +#define SVF_AE_ABSTRACT_STATE_H -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. +#include +#include -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// -//===----------------------------------------------------------------------===// -/* - * IntervalExeState.h - * - * Created on: Jul 9, 2022 - * Author: Xiao Cheng, Jiawei Wang - * - * [-oo,+oo] - * / / \ \ - * [-oo,1] ... [-oo,10] ... [-1,+oo] ... [0,+oo] - * \ \ / / - * \ [-1,10] / - * \ / \ / - * ... [-1,1] ... [0,10] ... - * \ | \ / \ / - * ... [-1,0] [0,1] ... [1,9] ... - * \ | \ | \ / - * ... [-1,-1] [0,0] [1,1] ... - * \ \ \ / / - * ⊥ - */ -// The implementation is based on -// Xiao Cheng, Jiawei Wang and Yulei Sui. Precise Sparse Abstract Execution via Cross-Domain Interaction. -// 46th International Conference on Software Engineering. (ICSE24) +namespace SVF::AbstractDomain +{ -#ifndef Z3_EXAMPLE_INTERVAL_DOMAIN_H -#define Z3_EXAMPLE_INTERVAL_DOMAIN_H +enum class CheckResult +{ + False, + True, + Unknown +}; -#include "AE/Core/AbstractValue.h" -#include "AE/Core/IntervalValue.h" -#include "Util/GeneralType.h" +const char* toString(CheckResult result); -namespace SVF -{ +/// Common value-level interface implemented by every complete abstract state. +/// +/// It deliberately contains only lattice operations. Transfer functions live +/// on more specific state interfaces such as NumericalState. class AbstractState { - friend class SVFIR2AbsState; - friend class RelationSolver; public: - typedef Map VarToAbsValMap; - typedef VarToAbsValMap AddrToAbsValMap; - /// default constructor - AbstractState() - { - } - - AbstractState(VarToAbsValMap&_varToValMap, AddrToAbsValMap&_locToValMap) : _varToAbsVal(_varToValMap), _addrToAbsVal(_locToValMap) {} - - /// copy constructor - AbstractState(const AbstractState&rhs) : _varToAbsVal(rhs.getVarToVal()), _addrToAbsVal(rhs.getLocToVal()), _freedAddrs(rhs._freedAddrs) - { - - } - - virtual ~AbstractState() = default; - - // initObjVar - void initObjVar(const ObjVar* objVar); - - - /// The physical address starts with 0x7f...... + idx - static inline u32_t getVirtualMemAddress(u32_t idx) - { - return AddressValue::getVirtualMemAddress(idx); - } - - /// Check bit value of val start with 0x7F000000, filter by 0xFF000000 - static inline bool isVirtualMemAddress(u32_t val) - { - return AddressValue::isVirtualMemAddress(val); - } - - /// Return the internal index if addr is an address otherwise return the value of idx - inline u32_t getIDFromAddr(u32_t addr) const - { - return _freedAddrs.count(addr) ? AddressValue::getInternalID(BlackHoleObjAddr) : AddressValue::getInternalID(addr); - } - - /// move constructor - AbstractState(AbstractState&&rhs) : _varToAbsVal(std::move(rhs._varToAbsVal)), - _addrToAbsVal(std::move(rhs._addrToAbsVal)), - _freedAddrs(std::move(rhs._freedAddrs)) - { - - } - - /// Set all value bottom - AbstractState bottom() const - { - AbstractState inv = *this; - for (auto &item: inv._varToAbsVal) - { - if (item.second.isInterval()) - item.second.getInterval().set_to_bottom(); - } - return inv; - } - - /// Set all value top - AbstractState top() const - { - AbstractState inv = *this; - for (auto &item: inv._varToAbsVal) - { - if (item.second.isInterval()) - item.second.getInterval().set_to_top(); - } - return inv; - } - - /// Copy some values and return a new IntervalExeState - AbstractState sliceState(Set &sl) - { - AbstractState inv; - for (u32_t id: sl) - inv._varToAbsVal[id] = _varToAbsVal[id]; - return inv; - } - - static inline bool isNullMem(u32_t addr) - { - return addr == NullMemAddr; - } - - static inline bool isBlackHoleObjAddr(u32_t addr) - { - return addr == BlackHoleObjAddr; - } - - -protected: - VarToAbsValMap _varToAbsVal; ///< Map a variable (symbol) to its abstract value - AddrToAbsValMap _addrToAbsVal; ///< Map a memory address to its stored abstract value - Set _freedAddrs; - -public: - - - /// get abstract value of variable - inline virtual AbstractValue &operator[](u32_t varId) - { - assert(!isVirtualMemAddress(varId) && "varId is a virtual memory address, use load() instead"); - return _varToAbsVal[varId]; - } - - /// get abstract value of variable - inline virtual const AbstractValue &operator[](u32_t varId) const - { - assert(!isVirtualMemAddress(varId) && "varId is a virtual memory address, use load() instead"); - return _varToAbsVal.at(varId); - } - - inline virtual AbstractValue &load(u32_t addr) - { - assert(isVirtualMemAddress(addr) && "not virtual address?"); - u32_t objId = getIDFromAddr(addr); - return _addrToAbsVal[objId]; - } - - inline virtual const AbstractValue &load(u32_t addr) const - { - assert(isVirtualMemAddress(addr) && "not virtual address?"); - u32_t objId = getIDFromAddr(addr); - return _addrToAbsVal.at(objId); - } - - inline void store(u32_t addr, const AbstractValue &val) - { - assert(isVirtualMemAddress(addr) && "not virtual address?"); - u32_t objId = getIDFromAddr(addr); - if (isNullMem(addr)) return; - _addrToAbsVal[objId] = val; - } - - /// whether the variable is in varToAddrs table - inline bool inVarToAddrsTable(u32_t id) const - { - if (_varToAbsVal.find(id)!= _varToAbsVal.end()) - { - if (_varToAbsVal.at(id).isAddr()) - return true; - } - return false; - } - - /// whether the variable is in varToVal table - inline virtual bool inVarToValTable(u32_t id) const - { - if (_varToAbsVal.find(id) != _varToAbsVal.end()) - { - if (_varToAbsVal.at(id).isInterval()) - return true; - } - return false; - } - - /// whether the memory address stores memory addresses - inline bool inAddrToAddrsTable(u32_t id) const - { - if (_addrToAbsVal.find(id)!= _addrToAbsVal.end()) - { - if (_addrToAbsVal.at(id).isAddr()) - { - return true; - } - } - return false; - } - - /// whether the memory address stores abstract value - inline virtual bool inAddrToValTable(u32_t id) const - { - if (_addrToAbsVal.find(id) != _addrToAbsVal.end()) - { - if (_addrToAbsVal.at(id).isInterval()) - { - return true; - } - } - return false; - } - - /// get var2val map - inline const VarToAbsValMap&getVarToVal() const - { - return _varToAbsVal; - } - - /// get loc2val map - inline const AddrToAbsValMap&getLocToVal() const - { - return _addrToAbsVal; - } - - /// domain widen with other, and return the widened domain - AbstractState widening(const AbstractState&other); - - /// domain narrow with other, and return the narrowed domain - AbstractState narrowing(const AbstractState&other); - - /// domain join with other, important! other widen this. - void joinWith(const AbstractState&other); - - /// Replace address-taken (ObjVar) state with other's, preserving ValVar state. - void updateAddrStateOnly(const AbstractState& other) - { - _addrToAbsVal = other._addrToAbsVal; - _freedAddrs = other._freedAddrs; - } - - /// domain meet with other, important! other widen this. - void meetWith(const AbstractState&other); - - void addToFreedAddrs(NodeID addr) - { - _freedAddrs.insert(addr); - } - - const Set& getFreedAddrs() const - { - return _freedAddrs; - } - - bool isFreedMem(u32_t addr) const - { - return _freedAddrs.find(addr) != _freedAddrs.end(); - } - - - void printAbstractState() const; - + virtual ~AbstractState(); + + virtual std::unique_ptr clone() const = 0; + virtual const char* name() const = 0; + + void joinWith(const AbstractState& other); + void meetWith(const AbstractState& other); + void widenWith(const AbstractState& next); + void narrowWith(const AbstractState& next); + + bool isBottom() const; + bool isTop() const; + /// Return whether every concrete state represented by this state is also + /// represented by `other`. + CheckResult isSubsetOf(const AbstractState& other) const; + CheckResult isEquivalentTo(const AbstractState& other) const; std::string toString() const; - u32_t hash() const; - - // lhs == rhs for varToValMap - bool eqVarToValMap(const VarToAbsValMap&lhs, const VarToAbsValMap&rhs) const; - // lhs >= rhs for varToValMap - bool geqVarToValMap(const VarToAbsValMap&lhs, const VarToAbsValMap&rhs) const; - // lhs == rhs for AbstractState - bool equals(const AbstractState&other) const; - - /// Assignment operator - AbstractState&operator=(const AbstractState&rhs) - { - if (&rhs != this) - { - _varToAbsVal = rhs._varToAbsVal; - _addrToAbsVal = rhs._addrToAbsVal; - _freedAddrs = rhs._freedAddrs; - } - return *this; - } - - /// operator= move constructor - AbstractState&operator=(AbstractState&&rhs) - { - if (&rhs != this) - { - _varToAbsVal = std::move(rhs._varToAbsVal); - _addrToAbsVal = std::move(rhs._addrToAbsVal); - _freedAddrs = std::move(rhs._freedAddrs); - } - return *this; - } - - bool operator==(const AbstractState&rhs) const - { - return eqVarToValMap(_varToAbsVal, rhs.getVarToVal()) && - eqVarToValMap(_addrToAbsVal, rhs.getLocToVal()); - } - - bool operator!=(const AbstractState&rhs) const + /// RTTI-free concrete-state query. SVF is commonly built with -fno-rtti, + /// so abstract domains use stable per-C++-type tokens for checked dispatch. + template bool isState() const noexcept { - return !(*this == rhs); + return dynamicTypeToken() == staticTypeToken(); } - bool operator<(const AbstractState&rhs) const - { - return !(*this >= rhs); - } - - bool operator>=(const AbstractState&rhs) const - { - return geqVarToValMap(_varToAbsVal, rhs.getVarToVal()) && geqVarToValMap(_addrToAbsVal, rhs.getLocToVal()); - } - - void clear() - { - _addrToAbsVal.clear(); - _varToAbsVal.clear(); - _freedAddrs.clear(); - } - - /// Drop all top-level variables (ValVars), keeping ObjVar storage and - /// freed addresses intact. Used when building a cycle snapshot so the - /// ValVar set is controlled by the caller rather than whatever was - /// cached at the seed node. - void clearValVars() - { - _varToAbsVal.clear(); - } - - +protected: + AbstractState() = default; + AbstractState(const AbstractState&) = default; + AbstractState(AbstractState&&) noexcept = default; + AbstractState& operator=(const AbstractState&) = default; + AbstractState& operator=(AbstractState&&) noexcept = default; + + void requireCompatible(const AbstractState& other) const; + + template static const void* staticTypeToken() noexcept + { + static const char token = 0; + return &token; + } + +private: + virtual const void* dynamicTypeToken() const noexcept = 0; + virtual bool hasCompatibleDomain(const AbstractState& other) const = 0; + virtual void joinState(const AbstractState& other) = 0; + virtual void meetState(const AbstractState& other) = 0; + virtual void widenState(const AbstractState& next) = 0; + virtual void narrowState(const AbstractState& next) = 0; + virtual bool isBottomState() const = 0; + virtual bool isTopState() const = 0; + virtual bool leqState(const AbstractState& other) const = 0; + virtual std::string stateToString() const = 0; }; -} - +} // namespace SVF::AbstractDomain -#endif //Z3_EXAMPLE_INTERVAL_DOMAIN_H +#endif // SVF_AE_ABSTRACT_STATE_H diff --git a/svf/include/AE/Core/AbstractValue.h b/svf/include/AE/Core/AbstractValue.h index 1430d37c12..0da3157bbb 100644 --- a/svf/include/AE/Core/AbstractValue.h +++ b/svf/include/AE/Core/AbstractValue.h @@ -1,4 +1,5 @@ //===- AbstractValue.h ----AbstractValue-------------------------// +#pragma once // // SVF: Static Value-Flow Analysis // @@ -154,4 +155,4 @@ class AbstractValue return "<" + interval.toString() + ", " + addrs.toString() + ">"; } }; -} \ No newline at end of file +} diff --git a/svf/include/AE/Core/AddressValue.h b/svf/include/AE/Core/AddressValue.h index 3cc8fd30ff..54721dd2e6 100644 --- a/svf/include/AE/Core/AddressValue.h +++ b/svf/include/AE/Core/AddressValue.h @@ -46,7 +46,6 @@ namespace SVF { class AddressValue { - friend class AbstractState; friend class RelExeState; public: typedef Set AddrSet; diff --git a/svf/include/AE/Core/BoxDomain.h b/svf/include/AE/Core/BoxDomain.h new file mode 100644 index 0000000000..d4c4367651 --- /dev/null +++ b/svf/include/AE/Core/BoxDomain.h @@ -0,0 +1,149 @@ +//===- BoxDomain.h -- Exact-rational interval box state --------*- C++ -*-===// + +#ifndef SVF_AE_BOX_DOMAIN_H +#define SVF_AE_BOX_DOMAIN_H + +#include "AE/Core/AbstractState.h" +#include "AE/Core/NumericalDomain.h" + +#include +#include +#include +#include + +namespace SVF::AbstractDomain +{ + +struct BoxConfig +{ + bool integerTightening = true; + std::shared_ptr diagnostics; + + bool operationCompatible(const BoxConfig& other) const + { + return integerTightening == other.integerTightening; + } +}; + +/// Non-relational numerical state with one exact-rational interval per +/// environment dimension. +class BoxState final : public NumericalState +{ +public: + using NumericalState::assignParallel; + using NumericalState::bound; + using NumericalState::substitute; + using NumericalState::substituteParallel; + + static BoxState top(const VariableEnvironment& environment, + const BoxConfig& config = {}); + static BoxState bottom(const VariableEnvironment& environment, + const BoxConfig& config = {}); + static BoxState fromBox(const VariableEnvironment& environment, + const IntervalBox& box, + const BoxConfig& config = {}); + static BoxState fromConstraints(const VariableEnvironment& environment, + const LinearConstraintSet& constraints, + const BoxConfig& config = {}); + + BoxState(const BoxState& other); + BoxState(BoxState&& other) noexcept = default; + BoxState& operator=(const BoxState& other) = default; + BoxState& operator=(BoxState&& other) noexcept = default; + + std::unique_ptr clone() const override; + const char* name() const override; + DomainCapabilities capabilities() const override; + + const VariableEnvironment& environment() const override + { + return environment_; + } + const BoxConfig& config() const + { + return config_; + } + + void assign(Variable target, const LinearExpression& expression) override; + void assign(Variable target, const TreeExpression& expression) override; + void assignParallel(const LinearAssignmentList& assignments) override; + void substitute(Variable target, + const LinearExpression& expression) override; + void substituteParallel(const LinearAssignmentList& assignments) override; + void assume(const LinearConstraint& constraint) override; + void assume(const TreeConstraint& constraint) override; + void forget(Variable variable) override; + void changeEnvironment(const VariableEnvironment& environment, + bool initializeNewVariablesToZero = false) override; + void expand(Variable source, + const std::vector& copies) override; + void fold(Variable target, const std::vector& folded) override; + + CheckResult entails(const LinearConstraint& constraint) const override; + Interval bound(Variable variable) const override; + Interval bound(const LinearExpression& expression) const override; + IntervalBox toBox() const override; + LinearConstraintSet toConstraints() const override; + void close() override; + void canonicalize() override; + + BoxState join(const BoxState& other) const; + BoxState meet(const BoxState& other) const; + BoxState widen(const BoxState& next, + const WideningPolicy& policy = {}) const; + BoxState narrow(const BoxState& next) const; + +private: + static constexpr std::size_t BoundsPerPage = 64; + + struct BoundPage + { + std::array, BoundsPerPage> bounds; + }; + + struct BoundPageEntry + { + std::size_t index; + std::shared_ptr page; + }; + + using BoundPageDirectory = std::vector; + BoxState(VariableEnvironment environment, BoxConfig config, bool bottom); + + const void* dynamicTypeToken() const noexcept override + { + return staticTypeToken(); + } + bool hasCompatibleDomain(const AbstractState& other) const override; + void joinState(const AbstractState& other) override; + void meetState(const AbstractState& other) override; + void widenState(const AbstractState& next) override; + void narrowState(const AbstractState& next) override; + bool isBottomState() const override; + bool isTopState() const override; + bool leqState(const AbstractState& other) const override; + std::string stateToString() const override; + + const BoxState& requireBox(const AbstractState& other) const; + const Interval& boundAt(Dimension dimension) const; + BoundPage& writablePage(std::size_t pageIndex); + void eraseBound(Dimension dimension); + static bool pageIsEmpty(const BoundPage& page); + std::vector boundedDimensions() const; + void makeBottom(); + void canonicalize(Dimension dimension); + void setBound(Dimension dimension, Interval interval); + void report(OperationKind operation, ApproximationKind approximation, + std::string reason, bool best = true) const; + VariableEnvironment environment_; + BoxConfig config_; + /// Missing pages and empty slots denote top. Active pages are kept sorted, + /// shared by state copies, and detached only when one of their bounds + /// changes. + BoundPageDirectory boundPages_; + bool bottom_ = false; +}; + +} // namespace SVF::AbstractDomain + +#endif // SVF_AE_BOX_DOMAIN_H diff --git a/svf/include/AE/Core/BoxProgramState.h b/svf/include/AE/Core/BoxProgramState.h new file mode 100644 index 0000000000..278ced2221 --- /dev/null +++ b/svf/include/AE/Core/BoxProgramState.h @@ -0,0 +1,614 @@ +//===- BoxProgramState.h -- Complete Box AE state --------*- C++ -*-===// + +#ifndef SVF_AE_BOX_PROGRAM_STATE_H +#define SVF_AE_BOX_PROGRAM_STATE_H + +#include "AE/Core/AbstractState.h" +#include "AE/Core/BoxDomain.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace SVF::AbstractDomain +{ + +class Location +{ +public: + explicit Location(std::uint32_t id = 0) : id_(id) {} + + std::uint32_t id() const + { + return id_; + } + + friend bool operator==(Location lhs, Location rhs) + { + return lhs.id_ == rhs.id_; + } + friend bool operator!=(Location lhs, Location rhs) + { + return !(lhs == rhs); + } + friend bool operator<(Location lhs, Location rhs) + { + return lhs.id_ < rhs.id_; + } + +private: + std::uint32_t id_; +}; + +class PointeeSet +{ +public: + PointeeSet() = default; + static PointeeSet bottom(); + static PointeeSet top(); + static PointeeSet singleton(Location location); + + bool isBottom() const; + bool isTop() const; + bool isSingleton() const; + bool contains(Location location) const; + const std::set& locations() const; + + void insert(Location location); + void joinWith(const PointeeSet& other); + void meetWith(const PointeeSet& other); + bool isSubsetOf(const PointeeSet& other) const; + std::string toString() const; + + friend bool operator==(const PointeeSet& lhs, const PointeeSet& rhs) + { + return lhs.top_ == rhs.top_ && lhs.locations_ == rhs.locations_; + } + friend bool operator!=(const PointeeSet& lhs, const PointeeSet& rhs) + { + return !(lhs == rhs); + } + +private: + explicit PointeeSet(bool top) : top_(top) {} + + bool top_ = false; + std::set locations_; +}; + +/// Internal Box-state pointer metadata. This is not an independently +/// selectable abstract domain; it exists only to preserve AE load/store and +/// object-lifetime semantics alongside the numerical Box carrier. +class PointerMap final +{ +public: + static PointerMap top(); + static PointerMap bottom(); + + PointeeSet pointeesOf(Variable variable) const; + void assign(Variable variable, PointeeSet addresses); + void forget(Variable variable); + void changeEnvironment(const VariableEnvironment& environment); + void joinWith(const PointerMap& other); + void meetWith(const PointerMap& other); + void widenWith(const PointerMap& next); + void narrowWith(const PointerMap& next); + bool isBottom() const; + bool isTop() const; + bool isSubsetOf(const PointerMap& other) const; + std::string toString() const; + +private: + using Values = std::map; + explicit PointerMap(bool defaultTop) + : defaultTop_(defaultTop), values_(std::make_shared()) + { + } + + void normalize(Variable variable); + Values& writableValues(); + PointeeSet defaultValue() const; + + bool defaultTop_ = false; + std::shared_ptr values_; +}; + +enum class Lifetime +{ + Bottom, + Alive, + Freed, + MaybeFreed +}; + +Lifetime join(Lifetime lhs, Lifetime rhs); +Lifetime meet(Lifetime lhs, Lifetime rhs); +bool isSubsetOf(Lifetime lhs, Lifetime rhs); +const char* toString(Lifetime lifetime); + +class LifetimeState final : public AbstractState +{ +public: + static LifetimeState top(); + static LifetimeState bottom(); + + std::unique_ptr clone() const override; + const char* name() const override; + + Lifetime statusOf(Location location) const; + void allocate(Location location); + void release(Location location); + bool mayBeFreed(Location location) const; + bool mustBeFreed(Location location) const; + +private: + using Values = std::map; + explicit LifetimeState(Lifetime defaultValue) + : defaultValue_(defaultValue), values_(std::make_shared()) + { + } + + const void* dynamicTypeToken() const noexcept override + { + return staticTypeToken(); + } + bool hasCompatibleDomain(const AbstractState& other) const override; + void joinState(const AbstractState& other) override; + void meetState(const AbstractState& other) override; + void widenState(const AbstractState& next) override; + void narrowState(const AbstractState& next) override; + bool isBottomState() const override; + bool isTopState() const override; + bool leqState(const AbstractState& other) const override; + std::string stateToString() const override; + + void set(Location location, Lifetime lifetime); + Values& writableValues(); + + Lifetime defaultValue_ = Lifetime::Bottom; + std::shared_ptr values_; +}; + +/// Tracks which facets of an AbstractValue are present for every domain +/// variable. The numerical and address domains deliberately use top/bottom +/// defaults, so they cannot by themselves distinguish an absent value from +/// an explicitly stored numeric top or address bottom. AE needs that +/// distinction for uninitialised-value checks and for sparse materialisation. +class ValueShapeState final : public AbstractState +{ +public: + struct Shape + { + bool defined = false; + bool numeric = false; + + friend bool operator==(Shape lhs, Shape rhs) + { + return lhs.defined == rhs.defined && lhs.numeric == rhs.numeric; + } + friend bool operator!=(Shape lhs, Shape rhs) + { + return !(lhs == rhs); + } + }; + + static ValueShapeState top(); + static ValueShapeState bottom(); + + std::unique_ptr clone() const override; + const char* name() const override; + + Shape shapeOf(Variable variable) const; + bool isDefined(Variable variable) const; + bool hasNumeric(Variable variable) const; + std::vector definedVariables( + const VariableEnvironment& environment) const; + void assign(Variable variable, bool numeric); + void forget(Variable variable); + void changeEnvironment(const VariableEnvironment& environment); + +private: + static constexpr std::size_t ShapesPerPage = 64; + + struct ShapePage + { + std::array shapes; + }; + + struct ShapePageEntry + { + std::size_t index; + std::shared_ptr page; + }; + + ValueShapeState(bool defaultDefined, bool defaultNumeric) + : default_(encode({defaultDefined, defaultNumeric})) + { + } + + const void* dynamicTypeToken() const noexcept override + { + return staticTypeToken(); + } + bool hasCompatibleDomain(const AbstractState& other) const override; + void joinState(const AbstractState& other) override; + void meetState(const AbstractState& other) override; + void widenState(const AbstractState& next) override; + void narrowState(const AbstractState& next) override; + bool isBottomState() const override; + bool isTopState() const override; + bool leqState(const AbstractState& other) const override; + std::string stateToString() const override; + + static std::uint8_t encode(Shape shape); + static Shape decode(std::uint8_t shape); + std::uint8_t encodedShapeOf(Variable variable) const; + void setEncodedShape(Variable variable, std::uint8_t shape); + static bool pageIsDefault(const ShapePage& page, std::uint8_t defaultShape); + + std::uint8_t default_ = 0; + /// As with BoxState, the page directory is sorted and cheap to copy while + /// the payload pages are detached only when a shape in that page changes. + std::vector pages_; +}; + +/// Immutable mapping from abstract locations to the scalar symbol denoting the +/// location's stored content. It is layout metadata, not mutable memory state. +class MemoryLayout +{ +public: + MemoryLayout() : cells_(std::make_shared()) {} + explicit MemoryLayout(std::map cells) + : cells_(std::make_shared(std::move(cells))) + { + } + + bool contains(Location location) const + { + return cells_->count(location) != 0; + } + Variable contentOf(Location location) const; + const std::map& cells() const + { + return *cells_; + } + + friend bool operator==(const MemoryLayout& lhs, const MemoryLayout& rhs) + { + return lhs.cells_ == rhs.cells_ || *lhs.cells_ == *rhs.cells_; + } + +private: + using Cells = std::map; + std::shared_ptr cells_; +}; + +/// Complete Box-backed program state. Memory contents are ordinary symbols in +/// the numerical state; pointer and lifetime facts are kept in small companion +/// facets because they are not numerical intervals. +class BoxProgramState final : public AbstractState +{ +public: + BoxProgramState(BoxState numerical, MemoryLayout memoryLayout, + PointerMap pointers = PointerMap::bottom(), + LifetimeState lifetimes = LifetimeState::bottom(), + ValueShapeState shapes = ValueShapeState::bottom()) + : numerical_(std::move(numerical)), + memoryLayout_(std::move(memoryLayout)), + pointers_(std::move(pointers)), lifetimes_(std::move(lifetimes)), + shapes_(std::move(shapes)) + { + } + + std::unique_ptr clone() const override + { + return std::make_unique(*this); + } + const char* name() const override + { + return "BoxProgramState"; + } + + BoxState& numerical() + { + return numerical_; + } + const BoxState& numerical() const + { + return numerical_; + } + PointerMap& pointers() + { + return pointers_; + } + const PointerMap& pointers() const + { + return pointers_; + } + LifetimeState& lifetimes() + { + return lifetimes_; + } + const LifetimeState& lifetimes() const + { + return lifetimes_; + } + ValueShapeState& shapes() + { + return shapes_; + } + const ValueShapeState& shapes() const + { + return shapes_; + } + const MemoryLayout& memoryLayout() const + { + return memoryLayout_; + } + + void assignPointer(Variable target, const PointeeSet& value) + { + pointers_.assign(target, value); + numerical_.forget(target); + shapes_.assign(target, false); + } + + void assignNumeric(Variable target, const LinearExpression& expression) + { + numerical_.assign(target, expression); + pointers_.assign(target, PointeeSet::bottom()); + shapes_.assign(target, true); + } + + void assignNumericParallel(const LinearAssignmentList& assignments) + { + numerical_.assignParallel(assignments); + for (const LinearAssignment& assignment : assignments) + { + pointers_.assign(assignment.target, PointeeSet::bottom()); + shapes_.assign(assignment.target, true); + } + } + + void assignNumericParallel(const TreeAssignmentList& assignments) + { + numerical_.assignParallel(assignments); + for (const TreeAssignment& assignment : assignments) + { + pointers_.assign(assignment.target, PointeeSet::bottom()); + shapes_.assign(assignment.target, true); + } + } + + void assume(const LinearConstraint& constraint) + { + numerical_.assume(constraint); + } + + void changeEnvironment(const VariableEnvironment& environment, + bool initializeNewVariablesToZero = false) + { + numerical_.changeEnvironment(environment, initializeNewVariablesToZero); + pointers_.changeEnvironment(environment); + shapes_.changeEnvironment(environment); + } + + void load(Variable target, Variable pointer) + { + const PointeeSet pointees = pointers_.pointeesOf(pointer); + if (pointees.isTop() || pointees.isBottom()) + { + numerical_.forget(target); + if (pointees.isTop()) + { + pointers_.forget(target); + shapes_.assign(target, true); + } + else + { + pointers_.assign(target, PointeeSet::bottom()); + shapes_.assign(target, false); + } + return; + } + + bool first = true; + BoxProgramState result(*this); + for (Location location : pointees.locations()) + { + if (!memoryLayout_.contains(location)) + continue; + BoxProgramState alternative(*this); + const Variable content = memoryLayout_.contentOf(location); + alternative.numerical_.assign(target, LinearExpression(content)); + alternative.pointers_.assign( + target, alternative.pointers_.pointeesOf(content)); + alternative.shapes_.assign(target, + alternative.shapes_.hasNumeric(content)); + if (first) + { + result = std::move(alternative); + first = false; + } + else + { + result.joinState(alternative); + } + } + if (first) + { + numerical_.forget(target); + pointers_.forget(target); + shapes_.assign(target, false); + } + else + { + *this = std::move(result); + } + } + + void store(Variable pointer, Variable source) + { + const PointeeSet pointees = pointers_.pointeesOf(pointer); + if (pointees.isTop()) + { + for (const auto& [location, content] : memoryLayout_.cells()) + { + (void)location; + weakStore(content, source); + } + return; + } + if (pointees.isBottom()) + return; + if (pointees.isSingleton()) + { + const Location location = *pointees.locations().begin(); + if (memoryLayout_.contains(location)) + strongStore(memoryLayout_.contentOf(location), source); + return; + } + for (Location location : pointees.locations()) + { + if (memoryLayout_.contains(location)) + weakStore(memoryLayout_.contentOf(location), source); + } + } + + void allocate(Location location) + { + lifetimes_.allocate(location); + } + + void release(Variable pointer) + { + const PointeeSet pointees = pointers_.pointeesOf(pointer); + if (pointees.isTop()) + { + for (const auto& [location, content] : memoryLayout_.cells()) + { + (void)content; + lifetimes_.release(location); + } + return; + } + for (Location location : pointees.locations()) + lifetimes_.release(location); + } + +private: + const void* dynamicTypeToken() const noexcept override + { + return staticTypeToken(); + } + bool hasCompatibleDomain(const AbstractState& other) const override + { + const auto* product = other.isState() + ? &static_cast(other) + : nullptr; + return product && memoryLayout_ == product->memoryLayout_ && + numerical_.environment() == product->numerical_.environment() && + numerical_.config().operationCompatible( + product->numerical_.config()); + } + + void joinState(const AbstractState& other) override + { + const BoxProgramState& product = requireProduct(other); + numerical_.joinWith(product.numerical_); + pointers_.joinWith(product.pointers_); + lifetimes_.joinWith(product.lifetimes_); + shapes_.joinWith(product.shapes_); + } + + void meetState(const AbstractState& other) override + { + const BoxProgramState& product = requireProduct(other); + numerical_.meetWith(product.numerical_); + pointers_.meetWith(product.pointers_); + lifetimes_.meetWith(product.lifetimes_); + shapes_.meetWith(product.shapes_); + } + + void widenState(const AbstractState& next) override + { + const BoxProgramState& product = requireProduct(next); + numerical_.widenWith(product.numerical_); + pointers_.widenWith(product.pointers_); + lifetimes_.widenWith(product.lifetimes_); + shapes_.widenWith(product.shapes_); + } + + void narrowState(const AbstractState& next) override + { + const BoxProgramState& product = requireProduct(next); + numerical_.narrowWith(product.numerical_); + pointers_.narrowWith(product.pointers_); + lifetimes_.narrowWith(product.lifetimes_); + shapes_.narrowWith(product.shapes_); + } + + bool isBottomState() const override + { + return numerical_.isBottom(); + } + + bool isTopState() const override + { + return numerical_.isTop() && pointers_.isTop() && lifetimes_.isTop() && + shapes_.isTop(); + } + + bool leqState(const AbstractState& other) const override + { + const BoxProgramState& product = requireProduct(other); + return numerical_.isSubsetOf(product.numerical_) == CheckResult::True && + pointers_.isSubsetOf(product.pointers_) && + lifetimes_.isSubsetOf(product.lifetimes_) == CheckResult::True && + shapes_.isSubsetOf(product.shapes_) == CheckResult::True; + } + + std::string stateToString() const override + { + return "numeric=" + numerical_.toString() + + ", pointers=" + pointers_.toString() + + ", lifetimes=" + lifetimes_.toString() + + ", shapes=" + shapes_.toString(); + } + + const BoxProgramState& requireProduct(const AbstractState& other) const + { + requireCompatible(other); + return static_cast(other); + } + + void strongStore(Variable content, Variable source) + { + numerical_.assign(content, LinearExpression(source)); + pointers_.assign(content, pointers_.pointeesOf(source)); + shapes_.assign(content, shapes_.hasNumeric(source)); + } + + void weakStore(Variable content, Variable source) + { + BoxProgramState alternative(*this); + alternative.strongStore(content, source); + joinState(alternative); + } + + BoxState numerical_; + MemoryLayout memoryLayout_; + PointerMap pointers_; + LifetimeState lifetimes_; + ValueShapeState shapes_; +}; + +} // namespace SVF::AbstractDomain + +#endif // SVF_AE_BOX_PROGRAM_STATE_H diff --git a/svf/include/AE/Core/ICFGWTO.h b/svf/include/AE/Core/ICFGWTO.h index 8e4c34d33f..84fcb2d064 100644 --- a/svf/include/AE/Core/ICFGWTO.h +++ b/svf/include/AE/Core/ICFGWTO.h @@ -34,6 +34,7 @@ #ifndef SVF_ICFGWTO_H #define SVF_ICFGWTO_H +#include #include #include "Graphs/ICFG.h" @@ -100,6 +101,18 @@ class ICFGWTO : public WTO } } + // ICFG edge containers may be ordered by allocation-dependent pointer + // values. WTO order determines widening points and therefore must not + // vary across otherwise identical analyzer processes. Canonicalize + // successors by the stable ICFG node ID; a call node can also produce + // the same return successor through multiple outgoing call edges. + std::sort(successors.begin(), successors.end(), + [](const ICFGNode* lhs, const ICFGNode* rhs) + { + return lhs->getId() < rhs->getId(); + }); + successors.erase(std::unique(successors.begin(), successors.end()), + successors.end()); return successors; } }; diff --git a/svf/include/AE/Core/IntervalValue.h b/svf/include/AE/Core/IntervalValue.h index 2eaabfa79d..7168623448 100644 --- a/svf/include/AE/Core/IntervalValue.h +++ b/svf/include/AE/Core/IntervalValue.h @@ -897,25 +897,17 @@ inline IntervalValue operator<<(const IntervalValue &lhs, const IntervalValue &r shift.meet_with(IntervalValue(0, IntervalValue::plus_infinity())); if (shift.isBottom()) return IntervalValue::bottom(); - BoundedInt lb = 0; - // If the shift is greater than 32, the result is always 0 - if ((s32_t) shift.lb().getNumeral() >= 32 || shift.lb().is_infinity()) - { - lb = IntervalValue::minus_infinity(); - } - else - { - lb = (1 << (s32_t) shift.lb().getNumeral()); - } - BoundedInt ub = 0; - if (shift.ub().is_infinity()) - { - ub = IntervalValue::plus_infinity(); - } - else - { - ub = (1 << (s32_t) shift.ub().getNumeral()); - } + if (shift.lb().is_infinity() || shift.ub().is_infinity()) + return IntervalValue::top(); + const s64_t lowerShift = shift.lb().getNumeral(); + const s64_t upperShift = shift.ub().getNumeral(); + // 2^63 is not representable by the signed numeral carrier. Keep the + // operation sound instead of overflowing a host integer while + // constructing the scaling interval. + if (lowerShift >= 63 || upperShift >= 63) + return IntervalValue::top(); + const BoundedInt lb = s64_t{1} << lowerShift; + const BoundedInt ub = s64_t{1} << upperShift; IntervalValue coeff(lb, ub); return lhs * coeff; } diff --git a/svf/include/AE/Core/LinearConstraint.h b/svf/include/AE/Core/LinearConstraint.h new file mode 100644 index 0000000000..0028d9cac6 --- /dev/null +++ b/svf/include/AE/Core/LinearConstraint.h @@ -0,0 +1,229 @@ +//===- LinearConstraint.h -- Domain-neutral linear syntax -------*- C++ -*-===// + +#ifndef SVF_AE_LINEAR_CONSTRAINT_H +#define SVF_AE_LINEAR_CONSTRAINT_H + +#include "AE/Core/VariableEnvironment.h" + +#include +#include +#include +#include +#include + +namespace SVF::AbstractDomain +{ + +class LinearExpression +{ +public: + using Terms = std::map; + + LinearExpression(); + explicit LinearExpression(Rational constant); + explicit LinearExpression(Variable variable); + + const Terms& terms() const + { + return terms_; + } + const Rational& constant() const + { + return constant_; + } + Rational coefficient(Variable variable) const; + + LinearExpression& setCoefficient(Variable variable, Rational coefficient); + LinearExpression& setConstant(Rational constant); + LinearExpression& operator+=(const LinearExpression& rhs); + LinearExpression& operator-=(const LinearExpression& rhs); + LinearExpression& operator*=(const Rational& scalar); + + /// Simultaneously replace variables in this expression. Replacement + /// expressions are inserted verbatim: variables occurring inside a + /// replacement are pre-state variables and are not recursively replaced. + LinearExpression substituted( + const std::map& replacements) const; + + std::string toString(const VariableEnvironment* environment = nullptr) const; + + friend LinearExpression operator+(LinearExpression lhs, + const LinearExpression& rhs) + { + return lhs += rhs; + } + friend LinearExpression operator-(LinearExpression lhs, + const LinearExpression& rhs) + { + return lhs -= rhs; + } + friend LinearExpression operator*(LinearExpression lhs, + const Rational& scalar) + { + return lhs *= scalar; + } + friend LinearExpression operator*(const Rational& scalar, + LinearExpression rhs) + { + return rhs *= scalar; + } + friend LinearExpression operator-(LinearExpression expression) + { + return expression *= Rational(-1); + } + +private: + void removeZeroTerms(); + + Terms terms_; + Rational constant_; +}; + +enum class UnaryOperator +{ + Negate, + Cast, + SquareRoot +}; + +enum class BinaryOperator +{ + Add, + Subtract, + Multiply, + Divide, + Remainder +}; + +class TreeExpression +{ +public: + enum class Kind + { + Constant, + Variable, + Unary, + Binary + }; + + static TreeExpression constant(Rational value, + NumericType type = NumericType::real()); + static TreeExpression variable(Variable value, NumericType type); + static TreeExpression unary( + UnaryOperator operation, TreeExpression operand, NumericType type, + RoundingMode rounding = RoundingMode::NearestTiesToEven); + static TreeExpression binary( + BinaryOperator operation, TreeExpression lhs, TreeExpression rhs, + NumericType type, + RoundingMode rounding = RoundingMode::NearestTiesToEven); + + Kind kind() const + { + return kind_; + } + const NumericType& type() const + { + return type_; + } + const Rational& constant() const + { + return constant_; + } + Variable variable() const + { + return variable_; + } + UnaryOperator unaryOperator() const + { + return unaryOperator_; + } + BinaryOperator binaryOperator() const + { + return binaryOperator_; + } + RoundingMode roundingMode() const + { + return roundingMode_; + } + const TreeExpression& lhs() const; + const TreeExpression& rhs() const; + + /// Return an exact affine expression when the tree is affine under + /// mathematical integer/real semantics. Floating and nonlinear trees + /// deliberately return nullopt and must use a sound backend fallback. + std::optional asLinear() const; + +private: + Kind kind_ = Kind::Constant; + NumericType type_ = NumericType::real(); + Rational constant_; + Variable variable_; + UnaryOperator unaryOperator_ = UnaryOperator::Negate; + BinaryOperator binaryOperator_ = BinaryOperator::Add; + RoundingMode roundingMode_ = RoundingMode::NearestTiesToEven; + std::shared_ptr lhs_; + std::shared_ptr rhs_; +}; + +enum class ConstraintKind +{ + Equal, + NotEqual, + LessThan, + LessEqual, + GreaterThan, + GreaterEqual +}; + +/// A normalized constraint of the form expression (relation) 0. +class LinearConstraint +{ +public: + LinearConstraint(LinearExpression expression, ConstraintKind kind); + + const LinearExpression& expression() const + { + return expression_; + } + ConstraintKind kind() const + { + return kind_; + } + std::string toString(const VariableEnvironment* environment = nullptr) const; + +private: + LinearExpression expression_; + ConstraintKind kind_; +}; + +class TreeConstraint +{ +public: + TreeConstraint(TreeExpression expression, ConstraintKind kind); + + const TreeExpression& expression() const + { + return expression_; + } + ConstraintKind kind() const + { + return kind_; + } + +private: + TreeExpression expression_; + ConstraintKind kind_; +}; + +using LinearConstraintSet = std::vector; + +LinearConstraint equal(LinearExpression lhs, LinearExpression rhs); +LinearConstraint notEqual(LinearExpression lhs, LinearExpression rhs); +LinearConstraint lessEqual(LinearExpression lhs, LinearExpression rhs); +LinearConstraint lessThan(LinearExpression lhs, LinearExpression rhs); +LinearConstraint greaterEqual(LinearExpression lhs, LinearExpression rhs); +LinearConstraint greaterThan(LinearExpression lhs, LinearExpression rhs); + +} // namespace SVF::AbstractDomain + +#endif // SVF_AE_LINEAR_CONSTRAINT_H diff --git a/svf/include/AE/Core/NumericPrimitives.h b/svf/include/AE/Core/NumericPrimitives.h new file mode 100644 index 0000000000..566dc11f6c --- /dev/null +++ b/svf/include/AE/Core/NumericPrimitives.h @@ -0,0 +1,274 @@ +//===- NumericPrimitives.h -- Exact abstract-domain numbers ---*- C++ -*-===// + +#ifndef SVF_AE_NUMERIC_PRIMITIVES_H +#define SVF_AE_NUMERIC_PRIMITIVES_H + +#include +#include + +#include +#include + +namespace SVF::AbstractDomain +{ + +class Integer +{ +public: + Integer(); + explicit Integer(std::int64_t value); + explicit Integer(const std::string& value); + + const mpz_class& value() const { return value_; } + std::string toString() const; + + friend bool operator==(const Integer& lhs, const Integer& rhs) + { + return lhs.value_ == rhs.value_; + } + +private: + mpz_class value_; +}; + +class Rational +{ +public: + Rational(); + explicit Rational(std::int64_t value); + explicit Rational(const Integer& value); + explicit Rational(const std::string& value); + Rational(const Integer& numerator, const Integer& denominator); + + static Rational fromRaw(const mpq_class& value); + + const mpq_class& value() const { return value_; } + bool isZero() const { return value_ == 0; } + int sign() const { return mpq_sgn(value_.get_mpq_t()); } + std::string toString() const; + + Rational floor() const; + Rational ceil() const; + Rational dividedByPowerOfTwo(unsigned exponent) const; + Rational& assignSum(const Rational& lhs, const Rational& rhs); + Rational& divideByPowerOfTwoInPlace(unsigned exponent); + + Rational& operator+=(const Rational& rhs); + Rational& operator-=(const Rational& rhs); + Rational& operator*=(const Rational& rhs); + Rational& operator/=(const Rational& rhs); + + friend Rational operator+(Rational lhs, const Rational& rhs) + { + return lhs += rhs; + } + friend Rational operator-(Rational lhs, const Rational& rhs) + { + return lhs -= rhs; + } + friend Rational operator*(Rational lhs, const Rational& rhs) + { + return lhs *= rhs; + } + friend Rational operator/(Rational lhs, const Rational& rhs) + { + return lhs /= rhs; + } + friend Rational operator-(const Rational& value) + { + return Rational::fromRaw(-value.value_); + } + + friend bool operator==(const Rational& lhs, const Rational& rhs) + { + return lhs.value_ == rhs.value_; + } + friend bool operator!=(const Rational& lhs, const Rational& rhs) + { + return !(lhs == rhs); + } + friend bool operator<(const Rational& lhs, const Rational& rhs) + { + return lhs.value_ < rhs.value_; + } + friend bool operator<=(const Rational& lhs, const Rational& rhs) + { + return lhs.value_ <= rhs.value_; + } + friend bool operator>(const Rational& lhs, const Rational& rhs) + { + return rhs < lhs; + } + friend bool operator>=(const Rational& lhs, const Rational& rhs) + { + return rhs <= lhs; + } + +private: + explicit Rational(mpq_class value, int); + mpq_class value_; +}; + +/// An ordered extended-rational endpoint. For a finite upper bound, strict +/// means "< value" and non-strict means "<= value". At an equal numeric +/// value a strict bound is tighter than a non-strict one. +class Bound +{ +public: + enum class Kind + { + MinusInfinity, + Finite, + PlusInfinity + }; + + Bound(); + static Bound minusInfinity(); + static Bound finite(Rational value, bool strict = false); + static Bound plusInfinity(); + + Kind kind() const { return kind_; } + bool isFinite() const { return kind_ == Kind::Finite; } + bool isMinusInfinity() const { return kind_ == Kind::MinusInfinity; } + bool isPlusInfinity() const { return kind_ == Kind::PlusInfinity; } + const Rational& value() const; + bool isStrict() const { return strict_; } + + /// Ordering used by upper bounds: tighter/smaller first. + static int compare(const Bound& lhs, const Bound& rhs); + static Bound min(const Bound& lhs, const Bound& rhs); + static Bound max(const Bound& lhs, const Bound& rhs); + static Bound add(const Bound& lhs, const Bound& rhs); + Bound& assignSum(const Bound& lhs, const Bound& rhs); + Bound& divideByTwoInPlace(); + static Bound divideByTwo(const Bound& bound); + static Bound divideByPositive(const Bound& bound, + const Rational& divisor); + + std::string toString() const; + + friend bool operator==(const Bound& lhs, const Bound& rhs) + { + return compare(lhs, rhs) == 0; + } + friend bool operator!=(const Bound& lhs, const Bound& rhs) + { + return !(lhs == rhs); + } + friend bool operator<(const Bound& lhs, const Bound& rhs) + { + return compare(lhs, rhs) < 0; + } + friend bool operator<=(const Bound& lhs, const Bound& rhs) + { + return compare(lhs, rhs) <= 0; + } + +private: + Bound(Kind kind, Rational value, bool strict); + + Kind kind_ = Kind::PlusInfinity; + Rational value_; + bool strict_ = false; +}; + +class Interval +{ +public: + Interval(); + Interval(Bound lower, Bound upper); + + static Interval top(); + static Interval singleton(const Rational& value); + + const Bound& lower() const { return lower_; } + const Bound& upper() const { return upper_; } + bool isTop() const; + bool isBottom() const; + std::string toString() const; + + friend bool operator==(const Interval& lhs, const Interval& rhs) + { + return lhs.lower_ == rhs.lower_ && lhs.upper_ == rhs.upper_; + } + friend bool operator!=(const Interval& lhs, const Interval& rhs) + { + return !(lhs == rhs); + } + +private: + Bound lower_; + Bound upper_; +}; + +enum class RoundingMode +{ + NearestTiesToEven, + TowardZero, + TowardPositive, + TowardNegative +}; + +struct FloatFormat +{ + unsigned exponentBits = 0; + unsigned significandBits = 0; + + static FloatFormat binary32() { return {8, 24}; } + static FloatFormat binary64() { return {11, 53}; } +}; + +class MpfrValue +{ +public: + explicit MpfrValue(mpfr_prec_t precision); + MpfrValue(const MpfrValue& rhs); + MpfrValue(MpfrValue&& rhs) noexcept; + MpfrValue& operator=(const MpfrValue& rhs); + MpfrValue& operator=(MpfrValue&& rhs) noexcept; + ~MpfrValue(); + + mpfr_ptr raw() { return value_; } + mpfr_srcptr raw() const { return value_; } + mpfr_prec_t precision() const { return mpfr_get_prec(value_); } + + void set(const Rational& value, mpfr_rnd_t rounding); + Rational toRational() const; + +private: + mpfr_t value_; +}; + +/// Ground MPFR operations used at the floating-semantics boundary. The +/// returned rational is the exact dyadic value of the rounded MPFR result. +class FloatSemantics +{ +public: + static Rational add(const Rational& lhs, const Rational& rhs, + unsigned significandBits, RoundingMode rounding); + static Rational subtract(const Rational& lhs, const Rational& rhs, + unsigned significandBits, + RoundingMode rounding); + static Rational multiply(const Rational& lhs, const Rational& rhs, + unsigned significandBits, + RoundingMode rounding); + static Rational divide(const Rational& lhs, const Rational& rhs, + unsigned significandBits, RoundingMode rounding); + +private: + enum class BinaryOperation + { + Add, + Subtract, + Multiply, + Divide + }; + + static Rational evaluate(BinaryOperation operation, const Rational& lhs, + const Rational& rhs, unsigned significandBits, + RoundingMode rounding); +}; + +} // namespace SVF::AbstractDomain + +#endif // SVF_AE_NUMERIC_PRIMITIVES_H diff --git a/svf/include/AE/Core/NumericValue.h b/svf/include/AE/Core/NumericValue.h index d52df3b161..7957bc23b5 100644 --- a/svf/include/AE/Core/NumericValue.h +++ b/svf/include/AE/Core/NumericValue.h @@ -39,7 +39,6 @@ #include "Util/GeneralType.h" -#define epsilon std::numeric_limits::epsilon(); namespace SVF { @@ -756,7 +755,8 @@ class BoundedDouble { if (std::isinf(a) && std::isinf(b)) return a == b; - return std::fabs(a - b) < epsilon; + return std::fabs(a - b) < + std::numeric_limits::epsilon(); } const double getFVal() const diff --git a/svf/include/AE/Core/NumericalDomain.h b/svf/include/AE/Core/NumericalDomain.h new file mode 100644 index 0000000000..0b25ce12e1 --- /dev/null +++ b/svf/include/AE/Core/NumericalDomain.h @@ -0,0 +1,270 @@ +//===- NumericalDomain.h -- Shared numerical-domain API --------*- C++ -*-===// + +#ifndef SVF_AE_NUMERICAL_DOMAIN_H +#define SVF_AE_NUMERICAL_DOMAIN_H + +#include "AE/Core/AbstractState.h" +#include "AE/Core/LinearConstraint.h" + +#include +#include +#include +#include +#include +#include + +namespace SVF::AbstractDomain +{ + +enum class ApproximationKind +{ + Exact, + SoundOverApproximation, + UnsupportedFallback +}; + +enum class OperationKind +{ + Assignment, + Assumption, + Substitution, + Forget, + EnvironmentChange, + Join, + Meet, + Widening, + Narrowing, + TopologicalClosure, + Canonicalization, + Expand, + Fold, + GeneratorImport, + GeneratorExport +}; + +/// APRON-style information about the most recently completed mutating +/// operation. `exact` means that no semantic approximation beyond the +/// selected abstract domain was introduced. `best` means that the operation +/// used the strongest implemented transformer for that domain and syntax. +struct OperationMetadata +{ + OperationKind operation = OperationKind::Assignment; + ApproximationKind approximation = ApproximationKind::Exact; + bool exact = true; + bool best = true; + std::string reason; +}; + +struct Diagnostic +{ + OperationKind operation; + ApproximationKind approximation; + std::string reason; +}; + +class DiagnosticSink +{ +public: + virtual ~DiagnosticSink() = default; + virtual void report(const Diagnostic& diagnostic) = 0; +}; + +struct DomainCapabilities +{ + bool strictInequalities = false; + bool integerTightening = false; + bool thresholdWidening = false; + bool narrowing = false; + bool parallelAssignments = false; + bool expressionBounds = false; + bool backwardAssignments = false; + bool topologicalClosure = false; + bool canonicalization = false; + bool expandFold = false; + bool operationMetadata = false; + bool generatorExchange = false; + bool ieeeTreeExpressions = false; + /// True only when nonlinear TreeExpression operations retain domain facts + /// instead of applying a sound forget/ignore fallback. + bool nonlinearTreeExpressions = false; +}; + +struct IntervalBox +{ + std::map bounds; +}; + +struct WideningPolicy +{ + WideningPolicy() = default; + + explicit WideningPolicy(std::vector thresholdValues) + : thresholds(std::move(thresholdValues)) + { + } + + WideningPolicy(std::vector thresholdValues, + LinearConstraintSet linearThresholdValues) + : thresholds(std::move(thresholdValues)), + linearThresholds(std::move(linearThresholdValues)) + { + } + + /// Constants used to delay a bound's jump to infinity. + std::vector thresholds; + /// General linear thresholds retained by a widening when both operands + /// entail them. Domains that cannot represent a threshold may ignore it + /// conservatively when the selected domain cannot represent one. + LinearConstraintSet linearThresholds; +}; + +struct LinearAssignment +{ + Variable target; + LinearExpression expression; +}; + +using LinearAssignmentList = std::vector; + +struct TreeAssignment +{ + Variable target; + TreeExpression expression; +}; + +using TreeAssignmentList = std::vector; + +/// Common interface for numerical abstract states. The representation and +/// lattice algorithms remain domain-specific; clients such as the SVF adapter +/// and test oracles only need this transfer/query surface. +class NumericalState : public AbstractState +{ +public: + using RawBuffer = std::vector; + + ~NumericalState() override = default; + + /// Return a deterministic semantic hash. Compatible states that are + /// equivalent according to isEquivalentTo() have the same hash. Hash + /// equality is not a substitute for an exact equivalence check. + std::uint64_t hash() const; + + /// Serialize the domain kind, operation-relevant configuration, + /// environment, and canonical mathematical state into a versioned binary + /// buffer. Diagnostic sinks are observational and are not serialized. + RawBuffer serializeRaw() const; + + /// Restore a Box state from serializeRaw(). + /// Malformed, truncated, corrupt, or unsupported data is rejected. + static std::unique_ptr deserializeRaw( + const RawBuffer& buffer); + + virtual DomainCapabilities capabilities() const = 0; + const OperationMetadata& lastOperation() const + { + return lastOperation_; + } + virtual const VariableEnvironment& environment() const = 0; + + virtual void assign(Variable target, + const LinearExpression& expression) = 0; + virtual void assign(Variable target, const TreeExpression& expression) = 0; + /// Assign every target simultaneously. Every right-hand side reads the + /// same incoming state, including old values of all assigned targets. + /// The default implementation uses temporary dimensions; domains may + /// override it with a representation-native implementation. + virtual void assignParallel(const LinearAssignmentList& assignments); + virtual void assignParallel(const TreeAssignmentList& assignments); + /// Compute the preimage of this post-state under target := expression. + /// This is APRON's substitute operation, not a forward strong update. + virtual void substitute(Variable target, + const LinearExpression& expression) = 0; + void substitute(Variable target, const TreeExpression& expression); + /// Simultaneous backward substitution. Every replacement is interpreted + /// over the same pre-state, including cyclic replacements. + virtual void substituteParallel( + const LinearAssignmentList& assignments) = 0; + void substituteParallel(const TreeAssignmentList& assignments); + virtual void assume(const LinearConstraint& constraint) = 0; + virtual void assume(const TreeConstraint& constraint) = 0; + virtual void forget(Variable variable) = 0; + virtual void changeEnvironment( + const VariableEnvironment& environment, + bool initializeNewVariablesToZero = false) = 0; + + /// Duplicate a summary dimension into new dimensions. Every copy has the + /// source dimension's relations with all other dimensions, while the + /// expanded dimensions remain mutually unrelated except where those + /// duplicated relations logically imply otherwise. This is APRON's + /// expand operation. + virtual void expand(Variable source, + const std::vector& copies) = 0; + /// Merge several materialized dimensions into `target` by taking the + /// abstract hull of every possible representative, then remove the other + /// dimensions. This is APRON's fold operation. + virtual void fold(Variable target, const std::vector& folded) = 0; + + /// Assume every constraint, letting them propagate into each other until + /// the state stops moving. + /// + /// Assuming them one at a time is weaker than a client of a guard such as + /// `a && b && c` expects: a bound learned from the last constraint cannot + /// flow back into the first. A domain that is exact on linear constraints + /// settles in one pass and pays only the comparison; a non-relational or + /// octagonal domain is the reason this exists. + virtual void assumeAll(const LinearConstraintSet& constraints); + + virtual CheckResult entails(const LinearConstraint& constraint) const = 0; + virtual Interval bound(Variable variable) const = 0; + /// Bound a complete affine expression using the relational backend, not + /// merely interval arithmetic over its individual variables. + virtual Interval bound(const LinearExpression& expression) const = 0; + /// Affine integer/real trees use bound(LinearExpression). Nonlinear and + /// finite IEEE trees use sound interval evaluation with outward rounding; + /// exceptional IEEE outcomes that cannot be represented numerically lose + /// the affected bound to top. + Interval bound(const TreeExpression& expression) const; + virtual IntervalBox toBox() const = 0; + virtual LinearConstraintSet toConstraints() const = 0; + + /// Replace strict boundaries by non-strict boundaries. This is the + /// topological closure operation, not DBM/polyhedral normalization. + virtual void close() = 0; + /// Materialize the backend's canonical representation and remove semantic + /// redundancy where the representation supports it. + virtual void canonicalize() = 0; + /// Dense native representations use canonicalization as their minimize + /// operation. + void minimize() + { + canonicalize(); + } + + /// Align both states to the union variable schema in one API-level + /// operation. Lattice compatibility is still checked later. + VariableEnvironment unifyEnvironmentWith( + NumericalState& other, bool initializeNewVariablesToZero = false); + +protected: + /// Evaluate nonlinear and finite IEEE trees by sound interval semantics, + /// applying each IEEE node's requested rounding mode at its endpoints. + /// Exceptional IEEE outcomes conservatively produce top. + Interval evaluateTreeExpression(const TreeExpression& expression) const; + /// Necessary affine consequences of a nonlinear tree guard. The result + /// may be empty when the guard cannot safely refine the selected domain. + LinearConstraintSet treeConstraintConsequences( + const TreeConstraint& constraint) const; + /// Strongly update a target from an already-computed interval. This is + /// used to preserve simultaneous semantics for nonlinear tree batches. + virtual void assignInterval(Variable target, const Interval& value); + void recordOperation(OperationKind operation, + ApproximationKind approximation, bool best, + std::string reason = {}) const; + +private: + mutable OperationMetadata lastOperation_; +}; + +} // namespace SVF::AbstractDomain + +#endif // SVF_AE_NUMERICAL_DOMAIN_H diff --git a/svf/include/AE/Core/RelationSolver.h b/svf/include/AE/Core/RelationSolver.h deleted file mode 100644 index df08465c34..0000000000 --- a/svf/include/AE/Core/RelationSolver.h +++ /dev/null @@ -1,95 +0,0 @@ -//===- RelationSolver.h ----Relation Solver for Interval Domains-----------// -// -// SVF: Static Value-Flow Analysis -// -// Copyright (C) <2013-2022> -// - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// -//===----------------------------------------------------------------------===// -/* - * RelationSolver.h - * - * Created on: Aug 4, 2022 - * Author: Jiawei Ren - * - */ - -#ifndef Z3_EXAMPLE_RELATIONSOLVER_H -#define Z3_EXAMPLE_RELATIONSOLVER_H - -#include "AE/Core/NumericValue.h" -#include "Util/Z3Expr.h" -#include "Util/GeneralType.h" - -namespace SVF -{ - -class AbstractState; - -class RelationSolver -{ -public: - RelationSolver() = default; - - /* gamma_hat, beta and abstract_consequence works on - IntervalESBase (the last element of inputs) for RSY or bilateral solver */ - - /// Return Z3Expr according to valToValMap - Z3Expr gamma_hat(const AbstractState&exeState) const; - - /// Return Z3Expr according to another valToValMap - Z3Expr gamma_hat(const AbstractState&alpha, const AbstractState&exeState) const; - - /// Return Z3Expr from a NodeID - Z3Expr gamma_hat(u32_t id, const AbstractState&exeState) const; - - AbstractState abstract_consequence(const AbstractState&lower, const AbstractState&upper, const AbstractState&domain) const; - - AbstractState beta(const Map &sigma, const AbstractState&exeState) const; - - - /// Return Z3 expression lazily based on SVFVar ID - virtual inline Z3Expr toIntZ3Expr(u32_t varId) const - { - return Z3Expr::getContext().int_const(std::to_string(varId).c_str()); - } - - inline Z3Expr toIntVal(s32_t f) const - { - return Z3Expr::getContext().int_val(f); - } - inline Z3Expr toRealVal(BoundedDouble f) const - { - return Z3Expr::getContext().real_val(std::to_string(f.getFVal()).c_str()); - } - - /* two optional solvers: RSY and bilateral */ - - AbstractState bilateral(const AbstractState& domain, const Z3Expr &phi, u32_t descend_check = 0); - - AbstractState RSY(const AbstractState& domain, const Z3Expr &phi); - - Map BoxedOptSolver(const Z3Expr& phi, Map& ret, Map& low_values, Map& high_values); - - AbstractState BS(const AbstractState& domain, const Z3Expr &phi); - - void updateMap(Map& map, u32_t key, const s32_t& value); - - void decide_cpa_ext(const Z3Expr &phi, Map&, Map&, Map&, Map&, Map&); -}; -} - -#endif //Z3_EXAMPLE_RELATIONSOLVER_H diff --git a/svf/include/AE/Core/VariableEnvironment.h b/svf/include/AE/Core/VariableEnvironment.h new file mode 100644 index 0000000000..93044e55e8 --- /dev/null +++ b/svf/include/AE/Core/VariableEnvironment.h @@ -0,0 +1,120 @@ +//===- VariableEnvironment.h -- Variables and dimensions ------*- C++ -*-===// + +#ifndef SVF_AE_VARIABLE_ENVIRONMENT_H +#define SVF_AE_VARIABLE_ENVIRONMENT_H + +#include "AE/Core/NumericPrimitives.h" + +#include +#include +#include +#include +#include + +namespace SVF::AbstractDomain +{ + +using Dimension = std::size_t; + +class Variable +{ +public: + explicit Variable(std::uint32_t id = 0) : id_(id) {} + std::uint32_t id() const { return id_; } + + friend bool operator==(Variable lhs, Variable rhs) + { + return lhs.id_ == rhs.id_; + } + friend bool operator!=(Variable lhs, Variable rhs) + { + return !(lhs == rhs); + } + friend bool operator<(Variable lhs, Variable rhs) + { + return lhs.id_ < rhs.id_; + } + +private: + std::uint32_t id_; +}; + +enum class NumericKind +{ + Integer, + Real, + IEEEFloat +}; + +struct NumericType +{ + NumericKind kind = NumericKind::Integer; + FloatFormat floatFormat{}; + + static NumericType integer() { return {NumericKind::Integer, {}}; } + static NumericType real() { return {NumericKind::Real, {}}; } + static NumericType ieee(FloatFormat format) + { + return {NumericKind::IEEEFloat, format}; + } + + friend bool operator==(const NumericType& lhs, const NumericType& rhs) + { + return lhs.kind == rhs.kind && + lhs.floatFormat.exponentBits == rhs.floatFormat.exponentBits && + lhs.floatFormat.significandBits == + rhs.floatFormat.significandBits; + } + friend bool operator!=(const NumericType& lhs, const NumericType& rhs) + { + return !(lhs == rhs); + } +}; + +struct VariableDeclaration +{ + Variable variable; + NumericType type; + std::string name; +}; + +/// Immutable, reference-counted mapping from public variables to dense domain +/// dimensions. It deliberately has no dependency on SVF NodeID or LLVM. +class VariableEnvironment +{ +public: + VariableEnvironment(); + explicit VariableEnvironment(std::vector variables); + + std::size_t size() const; + bool empty() const { return size() == 0; } + bool contains(Variable variable) const; + Dimension dimensionOf(Variable variable) const; + Variable variableOf(Dimension dimension) const; + const NumericType& typeOf(Variable variable) const; + const std::string& nameOf(Variable variable) const; + const std::vector& variables() const; + + VariableEnvironment add(std::vector variables) const; + VariableEnvironment remove(const std::vector& variables) const; + VariableEnvironment merge(const VariableEnvironment& other) const; + + friend bool operator==(const VariableEnvironment& lhs, const VariableEnvironment& rhs); + friend bool operator!=(const VariableEnvironment& lhs, const VariableEnvironment& rhs) + { + return !(lhs == rhs); + } + +private: + struct Data; + std::shared_ptr data_; +}; + +// Keep a namespace-scope declaration in addition to the friend declaration. +// This makes qualified out-of-line definitions well-formed without relying on +// friend-only argument-dependent lookup behavior. +bool operator==(const VariableEnvironment& lhs, const VariableEnvironment& rhs); + +} // namespace SVF::AbstractDomain + +#endif // SVF_AE_VARIABLE_ENVIRONMENT_H diff --git a/svf/include/AE/Svfexe/AEDetector.h b/svf/include/AE/Svfexe/AEDetector.h index c2ebe4c0d4..ee16fe4785 100644 --- a/svf/include/AE/Svfexe/AEDetector.h +++ b/svf/include/AE/Svfexe/AEDetector.h @@ -27,7 +27,7 @@ // #pragma once #include -#include +#include #include "Util/SVFBugReport.h" namespace SVF diff --git a/svf/include/AE/Svfexe/AbsExtAPI.h b/svf/include/AE/Svfexe/AbsExtAPI.h index 9805b03431..c3180e0f25 100644 --- a/svf/include/AE/Svfexe/AbsExtAPI.h +++ b/svf/include/AE/Svfexe/AbsExtAPI.h @@ -28,7 +28,6 @@ #include -#include "AE/Core/AbstractState.h" #include "AE/Core/IntervalValue.h" #include "SVFIR/SVFIR.h" #include "Util/GeneralType.h" @@ -37,7 +36,6 @@ namespace SVF { class AbstractInterpretation; -class AbstractState; class CallICFGNode; class ICFGNode; @@ -112,14 +110,6 @@ class AbsExtAPI */ IntervalValue getRangeLimitFromType(const SVFType* type); - /** - * @brief Retrieves the abstract state from the trace for a given ICFG node. - * @param node Pointer to the ICFG node. - * @return Reference to the abstract state. - * @throws Assertion if no trace exists for the node. - */ - AbstractState& getAbsState(const ICFGNode* node); - void collectCheckPoint(); void checkPointAllSet(); diff --git a/svf/include/AE/Svfexe/AbstractInterpretation.h b/svf/include/AE/Svfexe/AbstractInterpretation.h index d86ad7769b..c2dff42f51 100644 --- a/svf/include/AE/Svfexe/AbstractInterpretation.h +++ b/svf/include/AE/Svfexe/AbstractInterpretation.h @@ -20,26 +20,27 @@ // //===----------------------------------------------------------------------===// - // // Created on: Jan 10, 2024 // Author: Xiao Cheng, Jiawei Wang // The implementation is based on -// Xiao Cheng, Jiawei Wang and Yulei Sui. Precise Sparse Abstract Execution via Cross-Domain Interaction. -// 46th International Conference on Software Engineering. (ICSE24) +// Xiao Cheng, Jiawei Wang and Yulei Sui. Precise Sparse Abstract Execution via +// Cross-Domain Interaction. 46th International Conference on Software +// Engineering. (ICSE24) // #pragma once #include "AE/Core/AbstractState.h" +#include "AE/Core/AbstractValue.h" #include "AE/Core/ICFGWTO.h" #include "AE/Svfexe/AEDetector.h" +#include "AE/Svfexe/AEStat.h" #include "AE/Svfexe/AEWTO.h" #include "AE/Svfexe/AbsExtAPI.h" -#include "AE/Svfexe/AEStat.h" +#include "Graphs/CallGraph.h" +#include "Graphs/SCC.h" #include "SVFIR/SVFIR.h" #include "Util/SVFBugReport.h" #include "Util/WorkList.h" -#include "Graphs/SCC.h" -#include "Graphs/CallGraph.h" namespace SVF { @@ -49,7 +50,7 @@ class AEStat; class AEAPI; class AndersenWaveDiff; -template class FILOWorkList; +template class FILOWorkList; /// AbstractInterpretation is same as Abstract Execution. /// @@ -76,9 +77,13 @@ class AbstractInterpretation int main() { int result = demo(0); } - * if set TOP, result = [-oo, +oo] since the return value, and any stored object pointed by q at *q = p in recursive functions will be set to the top value. - * if set WIDEN_ONLY, result = [10000, +oo] since only widening is applied at the cycle head of recursive functions without narrowing. - * if set WIDEN_NARROW, result = [10000, 10000] since both widening and narrowing are applied at the cycle head of recursive functions. + * if set TOP, result = [-oo, +oo] since the return value, and any stored + object pointed by q at *q = p in recursive functions will be set to the top + value. + * if set WIDEN_ONLY, result = [10000, +oo] since only widening is applied + at the cycle head of recursive functions without narrowing. + * if set WIDEN_NARROW, result = [10000, 10000] since both widening and + narrowing are applied at the cycle head of recursive functions. * */ enum AESparsity { @@ -116,9 +121,8 @@ class AbstractInterpretation /// Factory: returns the singleton instance. The concrete class is /// chosen once, on first call, from `Options::AESparsity()`: - /// `SemiSparseAbstractInterpretation` for SemiSparse, - /// `FullSparseAbstractInterpretation` for Sparse, otherwise the - /// dense base. Must be called only after the option parser has run. + /// the native Box semi-sparse/full-sparse implementation, or the dense + /// Box implementation. Must be called only after option parsing. static AbstractInterpretation& getAEInstance(); void addDetector(std::unique_ptr detector) @@ -139,38 +143,63 @@ class AbstractInterpretation /// resolution chain (def-site walk, call-result fallback, etc.). /// All three overloads are virtual so full-sparse can route ObjVar /// reads through the SVFG. - virtual const AbstractValue& getAbsValue(const ValVar* var, const ICFGNode* node); - virtual const AbstractValue& getAbsValue(const ObjVar* var, const ICFGNode* node); - virtual const AbstractValue& getAbsValue(const SVFVar* var, const ICFGNode* node); + virtual AbstractValue getAbsValue(const ValVar* var, + const ICFGNode* node) = 0; + virtual AbstractValue getAbsValue(const ObjVar* var, + const ICFGNode* node) = 0; + virtual AbstractValue getAbsValue(const SVFVar* var, + const ICFGNode* node) = 0; /// Side-effect-free existence check. - virtual bool hasAbsValue(const ValVar* var, const ICFGNode* node) const; - virtual bool hasAbsValue(const ObjVar* var, const ICFGNode* node) const; - virtual bool hasAbsValue(const SVFVar* var, const ICFGNode* node) const; + virtual bool hasAbsValue(const ValVar* var, const ICFGNode* node) const = 0; + virtual bool hasAbsValue(const ObjVar* var, const ICFGNode* node) const = 0; + virtual bool hasAbsValue(const SVFVar* var, const ICFGNode* node) const = 0; /// Write a variable's abstract value. Sparse subclasses re-route /// ValVar writes to the def-site. - virtual void updateAbsValue(const ValVar* var, const AbstractValue& val, const ICFGNode* node); - virtual void updateAbsValue(const ObjVar* var, const AbstractValue& val, const ICFGNode* node); - virtual void updateAbsValue(const SVFVar* var, const AbstractValue& val, const ICFGNode* node); + virtual void updateAbsValue(const ValVar* var, const AbstractValue& value, + const ICFGNode* node) = 0; + virtual void updateAbsValue(const ObjVar* var, const AbstractValue& value, + const ICFGNode* node) = 0; + virtual void updateAbsValue(const SVFVar* var, const AbstractValue& value, + const ICFGNode* node) = 0; + + /// Representation-independent memory and lifetime access. Addresses use + /// the existing AE virtual-address encoding at this API boundary; native + /// domains translate them to Location internally. + virtual AbstractValue getMemoryValue(u32_t address, + const ICFGNode* node) = 0; + virtual bool hasMemoryValue(u32_t address, const ICFGNode* node) const = 0; + virtual void updateMemoryValue(u32_t address, const AbstractValue& value, + const ICFGNode* node) = 0; + virtual void markFreedMemory(u32_t address, const ICFGNode* node) = 0; + virtual bool isFreedMemory(u32_t address, const ICFGNode* node) const = 0; + + static NodeID objectIdFromAddress(u32_t address) + { + return address & FlippedAddressMask; + } // ---- State Access ------------------------------------------------- - AbstractState& getAbsState(const ICFGNode* node); - - /// Replace the state at `node`. Sparse subclasses replace only the - /// ObjVar map (ValVars live at def-sites). - virtual void updateAbsState(const ICFGNode* node, const AbstractState& state); + /// Return the authoritative complete state used for control-flow joins + /// and fixpoint computation. + virtual const AbstractDomain::AbstractState& getAbstractState( + const ICFGNode* node) const = 0; - /// Join `src` into `dst` with sparsity-aware semantics. Dense merges - /// everything; semi-sparse skips ValVars. - virtual void joinStates(AbstractState& dst, const AbstractState& src); + /// Return the analysis-wide SSA-value carrier for `function` when the + /// selected sparse implementation separates ValVars from ICFG memory + /// states. Other implementations return nullptr. + virtual const AbstractDomain::AbstractState* getScalarAbstractState( + const FunObjVar* function) const; - bool hasAbsState(const ICFGNode* node); + /// Return the sparse relational checkpoint associated with a particular + /// SSA definition, or nullptr when the implementation has no separated + /// definition checkpoint. + virtual const AbstractDomain::AbstractState* getScalarAbstractState( + const ValVar* value) const; - void getAbsState(const Set& vars, AbstractState& result, const ICFGNode* node); - void getAbsState(const Set& vars, AbstractState& result, const ICFGNode* node); - void getAbsState(const Set& vars, AbstractState& result, const ICFGNode* node); + virtual bool hasAbsState(const ICFGNode* node) const = 0; // ---- GEP / Load-Store / Type Helpers ------------------------------ @@ -180,125 +209,142 @@ class AbstractInterpretation /// Virtual so full-sparse can layer the GepObj overlay on top. virtual AbstractValue loadValue(const ValVar* pointer, - const ICFGNode* node); + const ICFGNode* node) = 0; virtual void storeValue(const ValVar* pointer, const AbstractValue& val, - const ICFGNode* node); + const ICFGNode* node) = 0; const SVFType* getPointeeElement(const ObjVar* var, const ICFGNode* node); u32_t getAllocaInstByteSize(const AddrStmt* addr); - // ---- Direct Trace Access ------------------------------------------ - - Map& getTrace() - { - return abstractTrace; - } - AbstractState& operator[](const ICFGNode* node) + const Set& getAnalyzedNodes() const { - return abstractTrace[node]; + return allAnalyzedNodes; } protected: /// Factory-only construction. External callers must use getAEInstance(); - /// `SparseAbstractInterpretation` reaches this via its own ctor. + /// Concrete Box implementations reach this through their constructors. AbstractInterpretation(); - // ---- Cycle helpers overridden by SparseAbstractInterpretation ---- + // ---- Cycle helpers implemented by Box-backed execution modes ---- // The dense versions write only to trace[cycle_head]. The semi-sparse // subclass adds def-site scatter on top for body ValVars. - /// Build a full cycle-head AbstractState. Dense default: trace[cycle_head] - /// as-is. Semi-sparse subclass: also pull cycle ValVars from def-sites. - virtual AbstractState getFullCycleHeadState(const ICFGCycleWTO* cycle); + /// Clone the complete cycle-head state. Sparse subclasses may first gather + /// values held at def-sites; dense implementations clone their domain + /// state directly. + virtual std::unique_ptr cloneCycleHeadState( + const ICFGCycleWTO* cycle) = 0; /// Widen prev with cur; write the widened state to trace[cycle_head]. /// Returns true when next == prev (fixpoint). Semi-sparse subclass /// additionally scatters ValVars to their def-sites. - virtual bool widenCycleState(const AbstractState& prev, const AbstractState& cur, - const ICFGCycleWTO* cycle); + virtual bool widenCycleState(const AbstractDomain::AbstractState& prev, + const AbstractDomain::AbstractState& cur, + const ICFGCycleWTO* cycle) = 0; /// Narrow prev with cur; write the narrowed state back. Returns true /// when narrowing is disabled or the narrowed state equals prev. /// Semi-sparse subclass scatters the narrowed ValVars on non-fixpoint. - virtual bool narrowCycleState(const AbstractState& prev, const AbstractState& cur, - const ICFGCycleWTO* cycle); + virtual bool narrowCycleState(const AbstractDomain::AbstractState& prev, + const AbstractDomain::AbstractState& cur, + const ICFGCycleWTO* cycle) = 0; protected: + /// Representation-independent state lifecycle used by the shared WTO and + /// call/return drivers. Dense and sparse analyses provide different + /// storage implementations behind this small surface. + virtual void resetAbstractState(const ICFGNode* node) = 0; + virtual void copyAbstractState(const ICFGNode* source, + const ICFGNode* destination) = 0; + virtual std::unique_ptr cloneAbstractState( + const ICFGNode* node) const = 0; + virtual bool isAbstractStateEquivalent( + const ICFGNode* node, + const AbstractDomain::AbstractState& snapshot) const = 0; + + /// Normalize a node after its transfers and detectors have consumed any + /// temporary operands. Native sparse implementations use this boundary to + /// keep ValVar values out of persistent ICFG states. + virtual void finalizeAbstractState(const ICFGNode* node); + /// Pull-based state merge: read abstractTrace[pred] for each predecessor, /// apply branch refinement for conditional IntraCFGEdges, and join into /// abstractTrace[node]. Returns true if at least one predecessor had state. /// Virtual so full-sparse can layer per-MRSVFGNode obj pulls on top of the /// base ICFG-edge merge. - virtual bool mergeStatesFromPredecessors(const ICFGNode* node); + virtual bool mergeStatesFromPredecessors(const ICFGNode* node) = 0; - /// Returns true if the branch edge is reachable under the current state. - /// Pure query: does not update `as` or branch refinement traces. - bool isBranchEdgeFeasible(const IntraCFGEdge* edge, AbstractState& as); + /// Representation-independent feasibility query used by shared transfer + /// code. Native dense domains apply the constraint directly. + virtual bool isBranchEdgeFeasibleAt(const IntraCFGEdge* edge, + const ICFGNode* predecessor) = 0; /// Collect branch-induced interval refinement after a feasible edge has /// been selected for normal CFG-state merging. - void collectBranchRefinement(const IntraCFGEdge* edge, AbstractState& as); - - /// Hook called by collectBranchRefinement for each obj that the - /// branch narrows. Default (dense/semi): MEET `narrowed` onto - /// obj's value (read at `loadIcfg` where sparse keeps it) and - /// write the result into the local `as` (per-edge predState copy) - /// so joinStates carries it to `succ`. FullSparse overrides to - /// capture into refinementTrace[succ] instead. + void collectBranchRefinement(const IntraCFGEdge* edge, + AbstractDomain::AbstractState& state); + + /// Hook called by collectBranchRefinement for each object narrowed by the + /// branch. Dense and semi-sparse implementations meet the constraint into + /// the transient edge state; full-sparse records it for MemorySSA flow. virtual void recordBranchRefinement(NodeID objId, const IntervalValue& narrowed, - AbstractState& as, + AbstractDomain::AbstractState& state, const ICFGNode* loadIcfg, const ICFGNode* succ); -private: +protected: /// Initialize abstract state for the global ICFG node and process global /// statements - virtual void handleGlobalNode(); + virtual void handleGlobalNode() = 0; + + /// Materialise the value produced by an AddrStmt without prescribing a + /// concrete state representation. + virtual AbstractValue initializeObjectAddress(const ObjVar* object, + const ICFGNode* node) = 0; - /// Handle a call site node: dispatch to ext-call, direct-call, or indirect-call handling + /// Handle a call site node: dispatch to ext-call, direct-call, or + /// indirect-call handling virtual void handleCallSite(const ICFGNode* node); - /// Handle a WTO cycle (loop or recursive function) using widening/narrowing iteration - virtual void handleLoopOrRecursion(const ICFGCycleWTO* cycle, const CallICFGNode* caller); + /// Handle a WTO cycle (loop or recursive function) using widening/narrowing + /// iteration + virtual void handleLoopOrRecursion(const ICFGCycleWTO* cycle, + const CallICFGNode* caller); - /// Handle a function body via worklist-driven WTO traversal starting from funEntry + /// Handle a function body via worklist-driven WTO traversal starting from + /// funEntry void handleFunction(const ICFGNode* funEntry, const CallICFGNode* caller); /// Handle an ICFG node: execute statements; return true if state changed bool handleICFGNode(const ICFGNode* node); - /// Dispatch an SVF statement (Addr/Binary/Cmp/Load/Store/Copy/Gep/Select/Phi/Call/Ret) to its handler + /// Dispatch an SVF statement + /// (Addr/Binary/Cmp/Load/Store/Copy/Gep/Select/Phi/Call/Ret) to its handler virtual void handleSVFStatement(const SVFStmt* stmt); - /// Returns true if the cmp-conditional branch is feasible. - bool isCmpBranchEdgeFeasible(const IntraCFGEdge* edge, AbstractState& as); - - /// Returns true if the switch branch is feasible. - bool isSwitchBranchEdgeFeasible(const IntraCFGEdge* edge, - AbstractState& as); + void updateStateOnAddr(const AddrStmt* addr); - void updateStateOnAddr(const AddrStmt *addr); + void updateStateOnBinary(const BinaryOPStmt* binary); - void updateStateOnBinary(const BinaryOPStmt *binary); + void updateStateOnCmp(const CmpStmt* cmp); - void updateStateOnCmp(const CmpStmt *cmp); + void updateStateOnLoad(const LoadStmt* load); - void updateStateOnLoad(const LoadStmt *load); + void updateStateOnStore(const StoreStmt* store); - void updateStateOnStore(const StoreStmt *store); + void updateStateOnCopy(const CopyStmt* copy); - void updateStateOnCopy(const CopyStmt *copy); + void updateStateOnCall(const CallPE* callPE); - void updateStateOnCall(const CallPE *callPE); + void updateStateOnRet(const RetPE* retPE); - void updateStateOnRet(const RetPE *retPE); + void updateStateOnGep(const GepStmt* gep); - void updateStateOnGep(const GepStmt *gep); + void updateStateOnSelect(const SelectStmt* select); - void updateStateOnSelect(const SelectStmt *select); - - void updateStateOnPhi(const PhiStmt *phi); + void updateStateOnPhi(const PhiStmt* phi); /// Execution State, used to store the Interval Value of every SVF variable AEAPI* api{nullptr}; @@ -316,8 +362,9 @@ class AbstractInterpretation virtual bool isExtCall(const CallICFGNode* callNode); virtual void handleExtCall(const CallICFGNode* callNode); virtual bool isRecursiveFun(const FunObjVar* fun); - virtual void skipRecursionWithTop(const CallICFGNode *callNode); - virtual bool isRecursiveCallSite(const CallICFGNode* callNode, const FunObjVar *); + virtual void skipRecursionWithTop(const CallICFGNode* callNode); + virtual bool isRecursiveCallSite(const CallICFGNode* callNode, + const FunObjVar*); virtual void handleFunCall(const CallICFGNode* callNode); bool skipRecursiveCall(const CallICFGNode* callNode); @@ -326,18 +373,34 @@ class AbstractInterpretation // there data should be shared with subclasses Map> func_map; - Set allAnalyzedNodes; // All nodes ever analyzed (across all entry points) + Set + allAnalyzedNodes; // All nodes ever analyzed (across all entry points) std::string moduleName; std::vector> detectors; AbsExtAPI* utils; protected: - /// Data and helpers reachable from SparseAbstractInterpretation. + /// Data and helpers reachable from native sparse implementations. SVFIR* svfir{nullptr}; AEWTO* preAnalysis{nullptr}; - Map abstractTrace; ///< per-node trace; owned here bool shouldApplyNarrowing(const FunObjVar* fun); + + // ---- Domain-specific precision hooks ----------------------------- + // Sparse modes use the no-op base implementations. Dense mode updates + // its selected numerical domain through these hooks after legacy + // transfer code computes the compatibility AbstractValue. + virtual void initializeDomainState(const ICFGNode* node); + virtual void assignDomainInterval(const ICFGNode* node, + const SVFVar* target, + const IntervalValue& interval); + virtual void updateDomainOnBinary(const BinaryOPStmt* binary, + const IntervalValue& result); + virtual void updateDomainOnCopy(const CopyStmt* copy); + virtual void updateDomainCopyValue(const ICFGNode* node, + const SVFVar* target, + const SVFVar* source, + bool exactMathematicalCopy); }; } // namespace SVF diff --git a/svf/include/AE/Svfexe/DenseAbstractInterpretation.h b/svf/include/AE/Svfexe/DenseAbstractInterpretation.h new file mode 100644 index 0000000000..b043bd3f3a --- /dev/null +++ b/svf/include/AE/Svfexe/DenseAbstractInterpretation.h @@ -0,0 +1,133 @@ +//===- DenseAbstractInterpretation.h -- Domain-backed dense AE -*- C++ -*-===// + +#ifndef SVF_AE_DENSE_ABSTRACT_INTERPRETATION_H +#define SVF_AE_DENSE_ABSTRACT_INTERPRETATION_H + +#include "AE/Core/BoxDomain.h" +#include "AE/Core/BoxProgramState.h" +#include "AE/Svfexe/AbstractInterpretation.h" +#include "AE/Svfexe/SVFIRAdapter.h" + +namespace SVF +{ + +/// Native dense AE storage backed by one complete AbstractDomain state per +/// ICFG node. Values, memory, lifetimes, definedness, joins, widening, and +/// fixpoint checks all operate on BoxProgramState; no compatibility trace +/// is maintained by this implementation. +template +class DenseAbstractInterpretation : public AbstractInterpretation +{ +public: + using DenseState = AbstractDomain::BoxProgramState; + + DenseAbstractInterpretation(); + ~DenseAbstractInterpretation() override = default; + void runOnModule() override; + + const AbstractDomain::AbstractState& getAbstractState( + const ICFGNode* node) const override; + bool hasAbsState(const ICFGNode* node) const override; + + AbstractValue getAbsValue(const ValVar* var, const ICFGNode* node) override; + AbstractValue getAbsValue(const ObjVar* var, const ICFGNode* node) override; + AbstractValue getAbsValue(const SVFVar* var, const ICFGNode* node) override; + + bool hasAbsValue(const ValVar* var, const ICFGNode* node) const override; + bool hasAbsValue(const ObjVar* var, const ICFGNode* node) const override; + bool hasAbsValue(const SVFVar* var, const ICFGNode* node) const override; + + void updateAbsValue(const ValVar* var, const AbstractValue& value, + const ICFGNode* node) override; + void updateAbsValue(const ObjVar* var, const AbstractValue& value, + const ICFGNode* node) override; + void updateAbsValue(const SVFVar* var, const AbstractValue& value, + const ICFGNode* node) override; + + AbstractValue getMemoryValue(u32_t address, const ICFGNode* node) override; + bool hasMemoryValue(u32_t address, const ICFGNode* node) const override; + void updateMemoryValue(u32_t address, const AbstractValue& value, + const ICFGNode* node) override; + void markFreedMemory(u32_t address, const ICFGNode* node) override; + bool isFreedMemory(u32_t address, const ICFGNode* node) const override; + + AbstractValue loadValue(const ValVar* pointer, + const ICFGNode* node) override; + void storeValue(const ValVar* pointer, const AbstractValue& value, + const ICFGNode* node) override; + +protected: + void handleGlobalNode() override; + AbstractValue initializeObjectAddress(const ObjVar* object, + const ICFGNode* node) override; + void resetAbstractState(const ICFGNode* node) override; + void copyAbstractState(const ICFGNode* source, + const ICFGNode* destination) override; + std::unique_ptr cloneAbstractState( + const ICFGNode* node) const override; + bool isAbstractStateEquivalent( + const ICFGNode* node, + const AbstractDomain::AbstractState& snapshot) const override; + + std::unique_ptr cloneCycleHeadState( + const ICFGCycleWTO* cycle) override; + bool widenCycleState(const AbstractDomain::AbstractState& previous, + const AbstractDomain::AbstractState& current, + const ICFGCycleWTO* cycle) override; + bool narrowCycleState(const AbstractDomain::AbstractState& previous, + const AbstractDomain::AbstractState& current, + const ICFGCycleWTO* cycle) override; + bool mergeStatesFromPredecessors(const ICFGNode* node) override; + bool isBranchEdgeFeasibleAt(const IntraCFGEdge* edge, + const ICFGNode* predecessor) override; + void recordBranchRefinement(NodeID objectId, + const IntervalValue& narrowed, + AbstractDomain::AbstractState& state, + const ICFGNode* loadNode, + const ICFGNode* successor) override; + void initializeDomainState(const ICFGNode* node) override; + void assignDomainInterval(const ICFGNode* node, const SVFVar* target, + const IntervalValue& interval) override; + void updateDomainOnBinary(const BinaryOPStmt* binary, + const IntervalValue& result) override; + void updateDomainOnCopy(const CopyStmt* copy) override; + void updateDomainCopyValue(const ICFGNode* node, const SVFVar* target, + const SVFVar* source, + bool exactMathematicalCopy) override; + +protected: + DenseState& ensureState(const ICFGNode* node); + const DenseState& state(const ICFGNode* node) const; + DenseState topState(const ICFGNode* node) const; + DenseState bottomState(const ICFGNode* node) const; + NumericalStateT makeNumericalTop( + const AbstractDomain::VariableEnvironment& environment) const; + NumericalStateT makeNumericalBottom( + const AbstractDomain::VariableEnvironment& environment) const; + + AbstractValue projectValue(const DenseState& state, + AbstractDomain::Variable variable) const; + void assignValue(DenseState& state, AbstractDomain::Variable variable, + const AbstractValue& value); + void ensureVariable(DenseState& state, + AbstractDomain::Variable variable) const; + void assignInterval(DenseState& state, AbstractDomain::Variable variable, + const IntervalValue& interval); + void constrainInterval(DenseState& state, AbstractDomain::Variable variable, + const IntervalValue& interval); + virtual void materializeValue(DenseState& state, const ValVar* value, + const ICFGNode* node); + void forgetValue(DenseState& state, + AbstractDomain::Variable variable) const; + void forgetScalarValues(DenseState& state) const; + void assumeBranch(const IntraCFGEdge* edge, DenseState& state); + + SVFIRAdapter adapter_; + Map denseTrace_; +}; + +extern template class DenseAbstractInterpretation; + +} // namespace SVF + +#endif // SVF_AE_DENSE_ABSTRACT_INTERPRETATION_H diff --git a/svf/include/AE/Svfexe/NativeSparseAbstractInterpretation.h b/svf/include/AE/Svfexe/NativeSparseAbstractInterpretation.h new file mode 100644 index 0000000000..69b84d8abf --- /dev/null +++ b/svf/include/AE/Svfexe/NativeSparseAbstractInterpretation.h @@ -0,0 +1,182 @@ +//===- NativeSparseAbstractInterpretation.h -- Domain sparse AE -*- C++ -*-===// + +#ifndef SVF_AE_NATIVE_SPARSE_ABSTRACT_INTERPRETATION_H +#define SVF_AE_NATIVE_SPARSE_ABSTRACT_INTERPRETATION_H + +#include +#include + +#include "AE/Svfexe/DenseAbstractInterpretation.h" + +namespace SVF +{ + +class IndirectSVFGEdge; +class SVFGBuilder; +class VFGNode; + +/// Semi-sparse AE backed by BoxProgramState. Box values use one module-wide +/// scalar carrier plus sparse definition checkpoints. Persistent ICFG states +/// carry memory and lifetime values, while transfers materialize scalar +/// operands only temporarily. +template +class NativeSemiSparseAbstractInterpretation + : public DenseAbstractInterpretation +{ +public: + using Base = DenseAbstractInterpretation; + using DenseState = typename Base::DenseState; + + NativeSemiSparseAbstractInterpretation(); + ~NativeSemiSparseAbstractInterpretation() override = default; + void runOnModule() override; + const AbstractDomain::AbstractState* getScalarAbstractState( + const FunObjVar* function) const override; + const AbstractDomain::AbstractState* getScalarAbstractState( + const ValVar* value) const override; + +protected: + void handleGlobalNode() override; + struct PhaseMetric + { + std::uint64_t calls = 0; + std::uint64_t nanoseconds = 0; + }; + + struct SparsePhaseProfile + { + PhaseMetric total; + PhaseMetric stateCopy; + PhaseMetric stateMerge; + PhaseMetric environmentAlignment; + PhaseMetric stateJoin; + PhaseMetric stateEquivalence; + PhaseMetric scalarMaterialization; + PhaseMetric scalarCheckpoint; + PhaseMetric stateFiltering; + PhaseMetric cycle; + PhaseMetric svfgBuild; + PhaseMetric objectPull; + PhaseMetric pathFeasibility; + PhaseMetric memoryRefinement; + }; + + AbstractValue getAbsValue(const ValVar* var, const ICFGNode* node) override; + using Base::getAbsValue; + bool hasAbsValue(const ValVar* var, const ICFGNode* node) const override; + using Base::hasAbsValue; + void updateAbsValue(const ValVar* var, const AbstractValue& value, + const ICFGNode* node) override; + using Base::updateAbsValue; + + void copyAbstractState(const ICFGNode* source, + const ICFGNode* destination) override; + void resetAbstractState(const ICFGNode* node) override; + void finalizeAbstractState(const ICFGNode* node) override; + bool mergeStatesFromPredecessors(const ICFGNode* node) override; + bool isAbstractStateEquivalent( + const ICFGNode* node, + const AbstractDomain::AbstractState& snapshot) const override; + + std::unique_ptr cloneCycleHeadState( + const ICFGCycleWTO* cycle) override; + bool widenCycleState(const AbstractDomain::AbstractState& previous, + const AbstractDomain::AbstractState& current, + const ICFGCycleWTO* cycle) override; + bool narrowCycleState(const AbstractDomain::AbstractState& previous, + const AbstractDomain::AbstractState& current, + const ICFGCycleWTO* cycle) override; + + void assignDomainInterval(const ICFGNode* node, const SVFVar* target, + const IntervalValue& interval) override; + void updateDomainOnBinary(const BinaryOPStmt* binary, + const IntervalValue& result) override; + void updateDomainCopyValue(const ICFGNode* node, const SVFVar* target, + const SVFVar* source, + bool exactMathematicalCopy) override; + void materializeValue(DenseState& state, const ValVar* value, + const ICFGNode* node) override; + AbstractValue loadValue(const ValVar* pointer, + const ICFGNode* node) override; + void storeValue(const ValVar* pointer, const AbstractValue& value, + const ICFGNode* node) override; + + /// Keep only the state facets that should flow along ordinary ICFG + /// edges. Full-sparse overrides this to remove MemorySSA-managed objects. + virtual void filterPropagatedState(DenseState& state) const; + + /// Optional memory refinement hook after a conditional edge has been + /// proven feasible by the native numerical state. + virtual void collectMemoryBranchRefinement(const IntraCFGEdge* edge, + DenseState& state); + + DenseState& scalarState(const FunObjVar* function); + const DenseState* findScalarState(const FunObjVar* function) const; + DenseState flowState(const FunObjVar* function, bool bottom = false) const; + void commitBinaryResult(const BinaryOPStmt* binary, + const DenseState& transferState, + const IntervalValue& fallback); + void commitCopyResult(const SVFVar* target, bool exactMathematicalCopy, + const DenseState& transferState); + void forgetActiveScalarValues(DenseState& state) const; + void forgetMemoryValues(DenseState& state) const; + void applyScalarCheckpoint(DenseState& state, const DenseState& checkpoint); + void scatterCycleValues(const ICFGCycleWTO* cycle, const DenseState& state); + virtual const char* sparseProfileMode() const; + void reportSparseProfile() const; + + Map refinementTrace_; + Map scalarStates_; + Map scalarCheckpoints_; + mutable SparsePhaseProfile sparseProfile_; +}; + +/// Full-sparse AE backed by BoxProgramState. Scalar SSA values remain at +/// definition sites as in semi-sparse mode. Base/Dummy ObjVar contents move +/// along MemorySSA/SVFG def-use edges; GepObjVar snapshots and lifetime facts +/// continue to flow along the ICFG because they are not fully represented by +/// those edges. +template +class NativeFullSparseAbstractInterpretation + : public NativeSemiSparseAbstractInterpretation +{ +public: + using Base = NativeSemiSparseAbstractInterpretation; + using DenseState = typename Base::DenseState; + + NativeFullSparseAbstractInterpretation(); + ~NativeFullSparseAbstractInterpretation() override; + +protected: + bool mergeStatesFromPredecessors(const ICFGNode* node) override; + void storeValue(const ValVar* pointer, const AbstractValue& value, + const ICFGNode* node) override; + void filterPropagatedState(DenseState& state) const override; + void collectMemoryBranchRefinement(const IntraCFGEdge* edge, + DenseState& state) override; + void recordBranchRefinement(NodeID objectId, const IntervalValue& narrowed, + AbstractDomain::AbstractState& state, + const ICFGNode* loadNode, + const ICFGNode* successor) override; + +private: + const char* sparseProfileMode() const override; + void pullObjectValueFlows(const ICFGNode* node); + bool isIndirectSVFGEdgeFeasible(const IndirectSVFGEdge* edge, + const VFGNode* destination); + bool isIntraEdgeBranchFeasible(const IntraCFGEdge* edge, + const ICFGNode* source); + void propagateAndApplyMemoryRefinement(const ICFGNode* node); + + Map> memoryRefinementTrace_; + std::unique_ptr svfgBuilder_; +}; + +extern template class NativeSemiSparseAbstractInterpretation< + AbstractDomain::BoxState>; +extern template class NativeFullSparseAbstractInterpretation< + AbstractDomain::BoxState>; + +} // namespace SVF + +#endif // SVF_AE_NATIVE_SPARSE_ABSTRACT_INTERPRETATION_H diff --git a/svf/include/AE/Svfexe/SVFIRAdapter.h b/svf/include/AE/Svfexe/SVFIRAdapter.h new file mode 100644 index 0000000000..3010d05d15 --- /dev/null +++ b/svf/include/AE/Svfexe/SVFIRAdapter.h @@ -0,0 +1,74 @@ +//===- SVFIRAdapter.h -- SVFIR to abstract-domain symbols ----*- C++ -*-===// + +#ifndef SVF_AE_SVFIR_ADAPTER_H +#define SVF_AE_SVFIR_ADAPTER_H + +#include "AE/Core/BoxProgramState.h" + +#include +#include + +namespace SVF +{ + +class FunObjVar; +class ObjVar; +class SVFIR; +class ValVar; + +/// Owns the IR-specific identity mapping. Abstract-domain states only see +/// Variable and Location; they never depend on SVF NodeID or SVFIR classes. +class SVFIRAdapter +{ +public: + explicit SVFIRAdapter(const SVFIR& svfir); + + bool contains(const ValVar& value) const; + bool contains(const ObjVar& object) const; + + AbstractDomain::Variable variable(const ValVar& value) const; + const ValVar* value(AbstractDomain::Variable variable) const; + const AbstractDomain::VariableDeclaration& declaration( + AbstractDomain::Variable variable) const; + AbstractDomain::Location location(const ObjVar& object) const; + AbstractDomain::Variable contentVariable(const ObjVar& object) const; + const ObjVar* contentObject(AbstractDomain::Variable variable) const; + const ObjVar& object(AbstractDomain::Location location) const; + + const AbstractDomain::VariableEnvironment& environment( + const FunObjVar* function = nullptr) const; + const AbstractDomain::VariableEnvironment& scalarEnvironment( + const FunObjVar* function = nullptr) const; + const AbstractDomain::VariableEnvironment& allScalarEnvironment() const + { + return allScalarEnvironment_; + } + + const AbstractDomain::MemoryLayout& memoryLayout() const + { + return memoryLayout_; + } + + AbstractDomain::LinearExpression linearExpression( + const std::vector>& + terms, + AbstractDomain::Rational constant = {}) const; + AbstractDomain::TreeExpression treeExpression(const ValVar& value) const; + +private: + std::map variables_; + std::vector valuesByVariableId_; + std::map + declarations_; + std::map locations_; + std::map objects_; + std::map contentVariables_; + std::vector contentObjectsByVariableId_; + AbstractDomain::VariableEnvironment globalEnvironment_; + AbstractDomain::VariableEnvironment allScalarEnvironment_; + AbstractDomain::MemoryLayout memoryLayout_; +}; + +} // namespace SVF + +#endif // SVF_AE_SVFIR_ADAPTER_H diff --git a/svf/include/AE/Svfexe/SparseAbstractInterpretation.h b/svf/include/AE/Svfexe/SparseAbstractInterpretation.h deleted file mode 100644 index 03dfca4cca..0000000000 --- a/svf/include/AE/Svfexe/SparseAbstractInterpretation.h +++ /dev/null @@ -1,178 +0,0 @@ -//===- SparseAbstractInterpretation.h -- Sparse Abstract Execution------// -// -// SVF: Static Value-Flow Analysis -// -// Copyright (C) <2013-> -// - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// -//===---------------------------------------------------------------------===// - -#ifndef INCLUDE_AE_SVFEXE_SPARSEABSTRACTINTERPRETATION_H_ -#define INCLUDE_AE_SVFEXE_SPARSEABSTRACTINTERPRETATION_H_ - -#include - -#include "AE/Svfexe/AbstractInterpretation.h" -#include "MSSA/SVFGBuilder.h" - -namespace SVF -{ - -class SVFG; -class SVFGBuilder; -class IndirectSVFGEdge; -class VFGNode; - -/// Abstract Interpretation for `Options::AESparsity::SemiSparse`. -/// -/// ValVars live at their SVFG-style def-sites: reads pull from there, -/// writes go there, state merges replace only the ObjVar map and skip -/// the ValVar map, and the cycle helpers gather/scatter cycle ValVars -/// around each widening iteration. -class SemiSparseAbstractInterpretation : public AbstractInterpretation -{ -public: - SemiSparseAbstractInterpretation() - { - preAnalysis->initCycleValVars(); - } - ~SemiSparseAbstractInterpretation() override = default; - -protected: - AbstractState getFullCycleHeadState(const ICFGCycleWTO* cycle) override; - - bool widenCycleState(const AbstractState& prev, - const AbstractState& cur, - const ICFGCycleWTO* cycle) override; - - bool narrowCycleState(const AbstractState& prev, - const AbstractState& cur, - const ICFGCycleWTO* cycle) override; - - const AbstractValue& getAbsValue(const ValVar* var, const ICFGNode* node) override; - using AbstractInterpretation::getAbsValue; - - bool hasAbsValue(const ValVar* var, const ICFGNode* node) const override; - using AbstractInterpretation::hasAbsValue; - - void updateAbsValue(const ValVar* var, const AbstractValue& val, const ICFGNode* node) override; - using AbstractInterpretation::updateAbsValue; - - void updateAbsState(const ICFGNode* node, const AbstractState& state) override; - - void joinStates(AbstractState& dst, const AbstractState& src) override; - - const ICFGNode* getICFGNode(const ValVar* var) const; -}; - -/// Abstract Interpretation for `Options::AESparsity::Sparse` (full-sparse). -/// -/// In full-sparse mode both ValVars and ObjVars live at their SVFG -/// def-sites; reads query the SVFG for the reaching-def site, writes -/// happen at def-sites. See `doc/plan-full-sparse.md` for the -/// phase plan; Phase 1 routes ValVar and ObjVar reads through the SVFG. -class FullSparseAbstractInterpretation : public SemiSparseAbstractInterpretation -{ -public: - FullSparseAbstractInterpretation() - { - buildSVFG(); - } - ~FullSparseAbstractInterpretation() override; - -protected: - /// Full-sparse does not merge normal value-flow state along ICFG - /// edges. The ICFG join carries only side-channel state that is not - /// represented as MemorySSA def-use flow: GepObjVar field snapshots - /// and `_freedAddrs`. Base/Dummy ObjVars are populated later by - /// pullObjValueFlows from SVFG indirect in-edges; ValVars stay at their - /// def-sites. - void joinStates(AbstractState& dst, const AbstractState& src) override; - - /// After a store overwrites an ObjVar, clear any branch refinement - /// for that ObjVar at the store's node so stale branch constraints - /// don't propagate past the redefinition. - void storeValue(const ValVar* pointer, const AbstractValue& val, - const ICFGNode* node) override; - - /// Thin wrapper: defer to base for ICFG-edge bookkeeping - /// (predecessor iteration, branch feasibility, joinStates, - /// updateAbsState, reachability return). For reachable nodes, - /// additionally run pullObjValueFlows to populate trace[node] with obj - /// values from SVFG def-sites. - bool mergeStatesFromPredecessors(const ICFGNode* node) override; - - /// Capture branch narrowings into refinementTrace[succ] instead of - /// writing them into the local `as`: in FullSparse `as` would be - /// discarded by joinStates (no-op for ObjVar), so we route the - /// narrowing to refinementTrace and let propagateAndApplyRefinement - /// bake it into trace at the end of mergeStatesFromPredecessors. - void recordBranchRefinement(NodeID objId, const IntervalValue& narrowed, - AbstractState& as, const ICFGNode* loadIcfg, - const ICFGNode* succ) override; - -private: - /// SVFG-pull helper: walk each VFG node's indirect SVFG in-edges - /// and pull obj values from upstream def-site traces into - /// trace[node]. Multiple sources (e.g. mphi operands) JOIN. - void pullObjValueFlows(const ICFGNode* node); - - /// Return whether an indirect SVFG edge should be pulled into dst. - /// Besides branch-feasible ICFG reachability, this rejects paths where - /// another store to the same points-to object kills the edge's value. - bool isIndirectSVFGEdgeFeasible(const IndirectSVFGEdge* edge, - const VFGNode* dst); - - /// Return whether a branch-feasible ICFG path exists from src to dst. - /// Conditional edges are checked with a pure branch-feasibility query, - /// so path probing does not create branch-refinement side effects. - bool isICFGPathFeasible(const ICFGNode* src, const ICFGNode* dst); - - /// Return whether this intra edge is allowed by the current branch state. - bool isIntraEdgeBranchFeasible(const IntraCFGEdge* edge, - const ICFGNode* src); - - /// Compose pred-inherited refinement into refinementTrace[node] - /// (single-pred linear copy / multi-pred intersect-JOIN; any pred - /// without refinement drops the inheritance), then MEET the final - /// refinementTrace[node] into trace[node]._addrToAbsVal so the - /// inherited base getAbsValue(ObjVar*, node) returns the narrowed - /// value directly — no read-time override or cache. Called once - /// per merge as the last step. - void propagateAndApplyRefinement(const ICFGNode* node); - - /// Path-refined obj values produced by branch narrowing. Each - /// entry is the *interval constraint* (not effective value) so - /// base trace can widen/narrow independently. Cached at branch - /// successors by recordBranchRefinement; propagated and applied by - /// propagateAndApplyRefinement at the end of - /// mergeStatesFromPredecessors. - Map> refinementTrace; - - /// Build the SVFG on top of the semi-sparse precompute. - void buildSVFG(); - - /// Owns the SVFG (via SVFGBuilder's internal unique_ptr). Without - /// this, SVFGBuilder would be a local in buildSVFG() and free the - /// graph at scope exit, leaving `svfg` dangling. - std::unique_ptr svfgBuilder; - /// View pointer into svfgBuilder's graph; non-null after buildSVFG(). - SVFG* svfg{nullptr}; -}; - -} // namespace SVF - -#endif /* INCLUDE_AE_SVFEXE_SPARSEABSTRACTINTERPRETATION_H_ */ diff --git a/svf/include/Util/Options.h b/svf/include/Util/Options.h index 978efa488e..8a0f39cab9 100644 --- a/svf/include/Util/Options.h +++ b/svf/include/Util/Options.h @@ -236,6 +236,8 @@ class Options static const OptionMap AESparsity; static const OptionMap AEFunEntry; static const Option WidenDelay; + /// Print inclusive phase timings for native semi/full-sparse AE. + static const Option AESparseProfile; /// recursion handling mode, Default: TOP static const OptionMap HandleRecur; /// the max time consumptions (seconds). Default: 4 hours 14400s diff --git a/svf/lib/AE/Core/AbstractState.cpp b/svf/lib/AE/Core/AbstractState.cpp index c8e86c7da6..5ff2738c41 100644 --- a/svf/lib/AE/Core/AbstractState.cpp +++ b/svf/lib/AE/Core/AbstractState.cpp @@ -1,345 +1,88 @@ -//===- IntervalExeState.cpp----Interval Domain-------------------------// -// -// SVF: Static Value-Flow Analysis -// -// Copyright (C) <2013-2022> -// +//===- AbstractState.cpp -- Common abstract-state lattice API -----------===// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// -//===----------------------------------------------------------------------===// -/* - * AbstractExeState.cpp - * - * Created on: Jul 9, 2022 - * Author: Xiao Cheng, Jiawei Wang - * - */ - -#include #include "AE/Core/AbstractState.h" -#include "SVFIR/SVFIR.h" -#include "Util/SVFUtil.h" -#include "Util/Options.h" -using namespace SVF; -using namespace SVFUtil; +#include -bool AbstractState::equals(const AbstractState&other) const +namespace SVF::AbstractDomain { - return *this == other; -} -u32_t AbstractState::hash() const +const char* toString(CheckResult result) { - size_t h = getVarToVal().size() * 2; - Hash hf; - for (const auto &t: getVarToVal()) + switch (result) { - h ^= hf(t.first) + 0x9e3779b9 + (h << 6) + (h >> 2); + case CheckResult::False: + return "false"; + case CheckResult::True: + return "true"; + case CheckResult::Unknown: + return "unknown"; } - size_t h2 = getLocToVal().size() * 2; - for (const auto &t: getLocToVal()) - { - h2 ^= hf(t.first) + 0x9e3779b9 + (h2 << 6) + (h2 >> 2); - } - Hash> pairH; - return pairH({h, h2}); + return "unknown"; } -AbstractState AbstractState::widening(const AbstractState& other) -{ - // widen interval - AbstractState es = *this; - for (auto it = es._varToAbsVal.begin(); it != es._varToAbsVal.end(); ++it) - { - auto key = it->first; - if (other._varToAbsVal.find(key) != other._varToAbsVal.end()) - if (it->second.isInterval() && other._varToAbsVal.at(key).isInterval()) - it->second.getInterval().widen_with(other._varToAbsVal.at(key).getInterval()); - } - for (auto it = es._addrToAbsVal.begin(); it != es._addrToAbsVal.end(); ++it) - { - auto key = it->first; - if (other._addrToAbsVal.find(key) != other._addrToAbsVal.end()) - if (it->second.isInterval() && other._addrToAbsVal.at(key).isInterval()) - it->second.getInterval().widen_with(other._addrToAbsVal.at(key).getInterval()); - } - return es; -} +AbstractState::~AbstractState() = default; -AbstractState AbstractState::narrowing(const AbstractState& other) +void AbstractState::requireCompatible(const AbstractState& other) const { - AbstractState es = *this; - for (auto it = es._varToAbsVal.begin(); it != es._varToAbsVal.end(); ++it) - { - auto key = it->first; - if (other._varToAbsVal.find(key) != other._varToAbsVal.end()) - if (it->second.isInterval() && other._varToAbsVal.at(key).isInterval()) - it->second.getInterval().narrow_with(other._varToAbsVal.at(key).getInterval()); - } - for (auto it = es._addrToAbsVal.begin(); it != es._addrToAbsVal.end(); ++it) - { - auto key = it->first; - if (other._addrToAbsVal.find(key) != other._addrToAbsVal.end()) - if (it->second.isInterval() && other._addrToAbsVal.at(key).isInterval()) - it->second.getInterval().narrow_with(other._addrToAbsVal.at(key).getInterval()); - } - return es; - + if (!hasCompatibleDomain(other)) + throw std::invalid_argument( + "abstract states use different domains or configurations"); } -/// domain join with other, important! other widen this. void AbstractState::joinWith(const AbstractState& other) { - for (auto it = other._varToAbsVal.begin(); it != other._varToAbsVal.end(); ++it) - { - auto key = it->first; - auto oit = _varToAbsVal.find(key); - if (oit != _varToAbsVal.end()) - { - oit->second.join_with(it->second); - } - else - { - _varToAbsVal.emplace(key, it->second); - } - } - for (auto it = other._addrToAbsVal.begin(); it != other._addrToAbsVal.end(); ++it) - { - auto key = it->first; - auto oit = _addrToAbsVal.find(key); - if (oit != _addrToAbsVal.end()) - { - oit->second.join_with(it->second); - } - else - { - _addrToAbsVal.emplace(key, it->second); - } - } - _freedAddrs.insert(other._freedAddrs.begin(), other._freedAddrs.end()); + requireCompatible(other); + joinState(other); } -/// domain meet with other, important! other widen this. void AbstractState::meetWith(const AbstractState& other) { - for (auto it = other._varToAbsVal.begin(); it != other._varToAbsVal.end(); ++it) - { - auto key = it->first; - auto oit = _varToAbsVal.find(key); - if (oit != _varToAbsVal.end()) - { - oit->second.meet_with(it->second); - } - } - for (auto it = other._addrToAbsVal.begin(); it != other._addrToAbsVal.end(); ++it) - { - auto key = it->first; - auto oit = _addrToAbsVal.find(key); - if (oit != _addrToAbsVal.end()) - { - oit->second.meet_with(it->second); - } - } - Set intersection; - std::set_intersection(_freedAddrs.begin(), _freedAddrs.end(), - other._freedAddrs.begin(), other._freedAddrs.end(), - std::inserter(intersection, intersection.begin())); - _freedAddrs = std::move(intersection); + requireCompatible(other); + meetState(other); } -// initObjVar -void AbstractState::initObjVar(const ObjVar* objVar) +void AbstractState::widenWith(const AbstractState& next) { - NodeID varId = objVar->getId(); - - // Check if the object variable has an associated value - - const BaseObjVar* obj = PAG::getPAG()->getBaseObject(objVar->getId()); - - // Handle constant data, arrays, and structures - if (obj->isConstDataOrConstGlobal() || obj->isConstantArray() || obj->isConstantStruct()) - { - if (const ConstIntObjVar* consInt = SVFUtil::dyn_cast(objVar)) - { - s64_t numeral = consInt->getSExtValue(); - (*this)[varId] = IntervalValue(numeral, numeral); - } - else if (const ConstFPObjVar* consFP = SVFUtil::dyn_cast(objVar)) - { - (*this)[varId] = IntervalValue(consFP->getFPValue(), consFP->getFPValue()); - } - else if (SVFUtil::isa(objVar)) - { - (*this)[varId] = IntervalValue(0, 0); - } - else if (SVFUtil::isa(objVar)) - { - (*this)[varId] = AddressValue(AbstractState::getVirtualMemAddress(varId)); - } - else if (obj->isConstantArray() || obj->isConstantStruct()) - { - (*this)[varId] = IntervalValue::top(); - } - else - { - (*this)[varId] = IntervalValue::top(); - } - } - // Handle non-constant memory objects - else - { - (*this)[varId] = AddressValue(AbstractState::getVirtualMemAddress(varId)); - } - return; + requireCompatible(next); + widenState(next); } -void AbstractState::printAbstractState() const +void AbstractState::narrowWith(const AbstractState& next) { - SVFUtil::outs() << "-----------Var and Value-----------\n"; - u32_t fieldWidth = 20; - SVFUtil::outs().flags(std::ios::left); - std::vector> varToAbsValVec(_varToAbsVal.begin(), _varToAbsVal.end()); - std::sort(varToAbsValVec.begin(), varToAbsValVec.end(), [](const auto &a, const auto &b) - { - return a.first < b.first; - }); - for (const auto &item: varToAbsValVec) - { - SVFUtil::outs() << std::left << std::setw(fieldWidth) << ("Var" + std::to_string(item.first)); - if (item.second.isInterval()) - { - SVFUtil::outs() << " Value: " << item.second.getInterval().toString() << "\n"; - } - else if (item.second.isAddr()) - { - SVFUtil::outs() << " Value: {"; - u32_t i = 0; - for (const auto& addr: item.second.getAddrs()) - { - ++i; - if (i < item.second.getAddrs().size()) - { - SVFUtil::outs() << "0x" << std::hex << addr << ", "; - } - else - { - SVFUtil::outs() << "0x" << std::hex << addr; - } - } - SVFUtil::outs() << "}\n"; - } - else - { - SVFUtil::outs() << " Value: ⊥\n"; - } - } - - std::vector> addrToAbsValVec(_addrToAbsVal.begin(), _addrToAbsVal.end()); - std::sort(addrToAbsValVec.begin(), addrToAbsValVec.end(), [](const auto &a, const auto &b) - { - return a.first < b.first; - }); + requireCompatible(next); + if (!next.leqState(*this)) + throw std::invalid_argument( + "narrowing requires next to be included in current"); + narrowState(next); +} - for (const auto& item: addrToAbsValVec) - { - std::ostringstream oss; - oss << "0x" << std::hex << AbstractState::getVirtualMemAddress(item.first); - SVFUtil::outs() << std::left << std::setw(fieldWidth) << oss.str(); - if (item.second.isInterval()) - { - SVFUtil::outs() << " Value: " << item.second.getInterval().toString() << "\n"; - } - else if (item.second.isAddr()) - { - SVFUtil::outs() << " Value: {"; - u32_t i = 0; - for (const auto& addr: item.second.getAddrs()) - { - ++i; - if (i < item.second.getAddrs().size()) - { - SVFUtil::outs() << "0x" << std::hex << addr << ", "; - } - else - { - SVFUtil::outs() << "0x" << std::hex << addr; - } - } - SVFUtil::outs() << "}\n"; - } - else - { - SVFUtil::outs() << " Value: ⊥\n"; - } - } - SVFUtil::outs() << "-----------------------------------------\n"; +bool AbstractState::isBottom() const +{ + return isBottomState(); } -std::string AbstractState::toString() const +bool AbstractState::isTop() const { - u32_t varIntervals = 0, varAddrs = 0, varBottom = 0; - for (const auto& item : _varToAbsVal) - { - if (item.second.isInterval()) ++varIntervals; - else if (item.second.isAddr()) ++varAddrs; - else ++varBottom; - } - u32_t addrIntervals = 0, addrAddrs = 0, addrBottom = 0; - for (const auto& item : _addrToAbsVal) - { - if (item.second.isInterval()) ++addrIntervals; - else if (item.second.isAddr()) ++addrAddrs; - else ++addrBottom; - } - std::ostringstream oss; - oss << "AbstractState {\n" - << " VarToAbsVal: " << _varToAbsVal.size() << " entries (" - << varIntervals << " intervals, " << varAddrs << " addresses, " << varBottom << " bottom)\n" - << " AddrToAbsVal: " << _addrToAbsVal.size() << " entries (" - << addrIntervals << " intervals, " << addrAddrs << " addresses, " << addrBottom << " bottom)\n" - << " FreedAddrs: " << _freedAddrs.size() << "\n" - << "}"; - return oss.str(); + return isTopState(); } +CheckResult AbstractState::isSubsetOf(const AbstractState& other) const +{ + requireCompatible(other); + return leqState(other) ? CheckResult::True : CheckResult::False; +} -bool AbstractState::eqVarToValMap(const VarToAbsValMap&lhs, const VarToAbsValMap&rhs) const +CheckResult AbstractState::isEquivalentTo(const AbstractState& other) const { - if (lhs.size() != rhs.size()) return false; - for (const auto &item: lhs) - { - auto it = rhs.find(item.first); - if (it == rhs.end()) - return false; - if (!item.second.equals(it->second)) - return false; - } - return true; + requireCompatible(other); + return leqState(other) && other.leqState(*this) ? CheckResult::True + : CheckResult::False; } -bool AbstractState::geqVarToValMap(const VarToAbsValMap&lhs, const VarToAbsValMap&rhs) const +std::string AbstractState::toString() const { - if (rhs.empty()) return true; - for (const auto &item: rhs) - { - auto it = lhs.find(item.first); - if (it == lhs.end()) return false; - if (!it->second.getInterval().contain( - item.second.getInterval())) - return false; - } - return true; + return stateToString(); } + +} // namespace SVF::AbstractDomain diff --git a/svf/lib/AE/Core/BoxDomain.cpp b/svf/lib/AE/Core/BoxDomain.cpp new file mode 100644 index 0000000000..0fdbad01be --- /dev/null +++ b/svf/lib/AE/Core/BoxDomain.cpp @@ -0,0 +1,1041 @@ +//===- BoxDomain.cpp -- Exact-rational interval box state ----------------===// + +#include "AE/Core/BoxDomain.h" + +#include +#include +#include +#include +#include +#include + +namespace SVF::AbstractDomain +{ + +namespace +{ + +int compareLower(const Bound& lhs, const Bound& rhs) +{ + if (lhs.kind() != rhs.kind()) + return static_cast(lhs.kind()) < static_cast(rhs.kind()) ? -1 + : 1; + if (!lhs.isFinite()) + return 0; + if (lhs.value() < rhs.value()) + return -1; + if (rhs.value() < lhs.value()) + return 1; + if (lhs.isStrict() == rhs.isStrict()) + return 0; + return lhs.isStrict() ? 1 : -1; +} + +Bound minLower(const Bound& lhs, const Bound& rhs) +{ + return compareLower(lhs, rhs) <= 0 ? lhs : rhs; +} + +Bound maxLower(const Bound& lhs, const Bound& rhs) +{ + return compareLower(lhs, rhs) >= 0 ? lhs : rhs; +} + +Bound scaleBound(const Bound& bound, const Rational& coefficient) +{ + if (coefficient.isZero()) + return Bound::finite(Rational()); + if (bound.isMinusInfinity()) + return coefficient.sign() > 0 ? Bound::minusInfinity() + : Bound::plusInfinity(); + if (bound.isPlusInfinity()) + return coefficient.sign() > 0 ? Bound::plusInfinity() + : Bound::minusInfinity(); + return Bound::finite(bound.value() * coefficient, bound.isStrict()); +} + +Interval scaleInterval(const Interval& interval, const Rational& coefficient) +{ + if (coefficient.isZero()) + return Interval::singleton(Rational()); + if (coefficient.sign() > 0) + return Interval(scaleBound(interval.lower(), coefficient), + scaleBound(interval.upper(), coefficient)); + return Interval(scaleBound(interval.upper(), coefficient), + scaleBound(interval.lower(), coefficient)); +} + +Interval addIntervals(const Interval& lhs, const Interval& rhs) +{ + return Interval(Bound::add(lhs.lower(), rhs.lower()), + Bound::add(lhs.upper(), rhs.upper())); +} + +Interval joinIntervals(const Interval& lhs, const Interval& rhs) +{ + return Interval(minLower(lhs.lower(), rhs.lower()), + Bound::max(lhs.upper(), rhs.upper())); +} + +Interval meetIntervals(const Interval& lhs, const Interval& rhs) +{ + return Interval(maxLower(lhs.lower(), rhs.lower()), + Bound::min(lhs.upper(), rhs.upper())); +} + +bool intervalIncluded(const Interval& lhs, const Interval& rhs) +{ + if (lhs.isBottom()) + return true; + if (rhs.isBottom()) + return false; + return compareLower(lhs.lower(), rhs.lower()) >= 0 && + Bound::compare(lhs.upper(), rhs.upper()) <= 0; +} + +Interval evaluate(const BoxState& state, const LinearExpression& expression, + std::optional excluded = std::nullopt) +{ + Interval result = Interval::singleton(expression.constant()); + for (const auto& [variable, coefficient] : expression.terms()) + { + if (excluded && variable == *excluded) + continue; + result = addIntervals( + result, scaleInterval(state.bound(variable), coefficient)); + } + return result; +} + +Bound integerLower(Bound bound) +{ + if (!bound.isFinite()) + return bound; + const Rational value = bound.isStrict() + ? bound.value().floor() + Rational(1) + : bound.value().ceil(); + return Bound::finite(value); +} + +Bound integerUpper(Bound bound) +{ + if (!bound.isFinite()) + return bound; + const Rational value = bound.isStrict() ? bound.value().ceil() - Rational(1) + : bound.value().floor(); + return Bound::finite(value); +} + +LinearConstraint normalizedLessEqual(const LinearConstraint& constraint, + bool& strict) +{ + strict = constraint.kind() == ConstraintKind::LessThan || + constraint.kind() == ConstraintKind::GreaterThan; + if (constraint.kind() == ConstraintKind::GreaterEqual || + constraint.kind() == ConstraintKind::GreaterThan) + return LinearConstraint(-constraint.expression(), + strict ? ConstraintKind::LessThan + : ConstraintKind::LessEqual); + return LinearConstraint(constraint.expression(), + strict ? ConstraintKind::LessThan + : ConstraintKind::LessEqual); +} + +} // namespace + +BoxState::BoxState(VariableEnvironment environment, BoxConfig config, + bool bottom) + : environment_(std::move(environment)), config_(std::move(config)), + bottom_(bottom) +{ +} + +BoxState::BoxState(const BoxState& other) + : NumericalState(other), environment_(other.environment_), + config_(other.config_), boundPages_(other.boundPages_), + bottom_(other.bottom_) +{ +} + +BoxState BoxState::top(const VariableEnvironment& environment, + const BoxConfig& config) +{ + BoxState result(environment, config, false); + return result; +} + +BoxState BoxState::bottom(const VariableEnvironment& environment, + const BoxConfig& config) +{ + BoxState result(environment, config, true); + return result; +} + +BoxState BoxState::fromBox(const VariableEnvironment& environment, + const IntervalBox& box, const BoxConfig& config) +{ + BoxState result = top(environment, config); + for (const auto& [variable, interval] : box.bounds) + { + if (!environment.contains(variable)) + throw std::invalid_argument("box contains an unknown variable"); + result.setBound(environment.dimensionOf(variable), interval); + } + return result; +} + +BoxState BoxState::fromConstraints(const VariableEnvironment& environment, + const LinearConstraintSet& constraints, + const BoxConfig& config) +{ + BoxState result = top(environment, config); + result.assumeAll(constraints); + return result; +} + +std::unique_ptr BoxState::clone() const +{ + return std::make_unique(*this); +} + +const char* BoxState::name() const +{ + return "BoxState"; +} + +DomainCapabilities BoxState::capabilities() const +{ + DomainCapabilities result; + result.strictInequalities = true; + result.integerTightening = config_.integerTightening; + result.thresholdWidening = true; + result.narrowing = true; + result.parallelAssignments = true; + result.expressionBounds = true; + result.backwardAssignments = true; + result.topologicalClosure = true; + result.canonicalization = true; + result.expandFold = true; + result.operationMetadata = true; + result.ieeeTreeExpressions = true; + result.nonlinearTreeExpressions = true; + return result; +} + +void BoxState::assign(Variable target, const LinearExpression& expression) +{ + if (!environment_.contains(target)) + throw std::invalid_argument("assignment target is not in environment"); + for (const auto& [variable, coefficient] : expression.terms()) + { + (void)coefficient; + if (!environment_.contains(variable)) + throw std::invalid_argument( + "assignment expression uses an unknown variable"); + } + recordOperation(OperationKind::Assignment, ApproximationKind::Exact, true); + if (bottom_) + return; + setBound(environment_.dimensionOf(target), evaluate(*this, expression)); +} + +void BoxState::assign(Variable target, const TreeExpression& expression) +{ + const std::optional linear = expression.asLinear(); + if (linear) + { + assign(target, *linear); + return; + } + const Interval value = evaluateTreeExpression(expression); + if (!bottom_) + setBound(environment_.dimensionOf(target), value); + report(OperationKind::Assignment, ApproximationKind::SoundOverApproximation, + "nonlinear or finite IEEE assignment was interval-linearized", + false); +} + +void BoxState::assignParallel(const LinearAssignmentList& assignments) +{ + std::set targets; + for (const LinearAssignment& assignment : assignments) + { + if (!environment_.contains(assignment.target)) + throw std::invalid_argument( + "parallel assignment target is not in environment"); + if (!targets.insert(assignment.target).second) + throw std::invalid_argument( + "parallel assignment contains a duplicate target"); + for (const auto& [variable, coefficient] : + assignment.expression.terms()) + { + (void)coefficient; + if (!environment_.contains(variable)) + throw std::invalid_argument( + "parallel assignment expression uses an unknown variable"); + } + } + recordOperation(OperationKind::Assignment, ApproximationKind::Exact, true); + if (bottom_) + return; + + std::vector> updates; + updates.reserve(assignments.size()); + for (const LinearAssignment& assignment : assignments) + updates.emplace_back(environment_.dimensionOf(assignment.target), + evaluate(*this, assignment.expression)); + for (auto& [dimension, value] : updates) + setBound(dimension, std::move(value)); +} + +void BoxState::substitute(Variable target, const LinearExpression& expression) +{ + substituteParallel({{target, expression}}); +} + +void BoxState::substituteParallel(const LinearAssignmentList& assignments) +{ + std::map replacements; + for (const LinearAssignment& assignment : assignments) + { + if (!environment_.contains(assignment.target)) + throw std::invalid_argument( + "substitution target is not in environment"); + if (!replacements.emplace(assignment.target, assignment.expression) + .second) + throw std::invalid_argument( + "parallel substitution contains a duplicate target"); + for (const auto& [variable, coefficient] : + assignment.expression.terms()) + { + (void)coefficient; + if (!environment_.contains(variable)) + throw std::invalid_argument( + "substitution expression uses an unknown variable"); + } + } + recordOperation(OperationKind::Substitution, ApproximationKind::Exact, + true); + if (assignments.empty() || bottom_) + return; + + LinearConstraintSet preimage; + for (const LinearConstraint& constraint : toConstraints()) + preimage.emplace_back(constraint.expression().substituted(replacements), + constraint.kind()); + *this = fromConstraints(environment_, preimage, config_); +} + +void BoxState::assume(const LinearConstraint& constraint) +{ + recordOperation(OperationKind::Assumption, ApproximationKind::Exact, true); + if (bottom_) + return; + for (const auto& [variable, coefficient] : constraint.expression().terms()) + { + (void)coefficient; + if (!environment_.contains(variable)) + throw std::invalid_argument("constraint uses an unknown variable"); + } + + if (constraint.kind() == ConstraintKind::NotEqual) + { + const Interval value = evaluate(*this, constraint.expression()); + if (!value.lower().isFinite() || !value.upper().isFinite() || + value.lower().value() != Rational() || + value.upper().value() != Rational() || value.lower().isStrict() || + value.upper().isStrict()) + return; + makeBottom(); + return; + } + + if (constraint.kind() == ConstraintKind::Equal) + { + assume(LinearConstraint(constraint.expression(), + ConstraintKind::LessEqual)); + assume(LinearConstraint(-constraint.expression(), + ConstraintKind::LessEqual)); + return; + } + + bool strict = false; + const LinearConstraint normalized = normalizedLessEqual(constraint, strict); + const LinearExpression& expression = normalized.expression(); + + // Repeating interval propagation lets bounds inferred for one dimension + // tighten another without introducing an unbounded worklist. + for (std::size_t pass = 0; pass <= environment_.size(); ++pass) + { + bool changed = false; + for (const auto& [variable, coefficient] : expression.terms()) + { + if (coefficient.isZero()) + continue; + const Interval rest = evaluate(*this, expression, variable); + if (!rest.lower().isFinite()) + continue; + + const Rational rhs = -rest.lower().value() / coefficient; + const bool resultStrict = strict || rest.lower().isStrict(); + const Dimension dimension = environment_.dimensionOf(variable); + Interval next = boundAt(dimension); + if (coefficient.sign() > 0) + { + next = meetIntervals( + next, Interval(Bound::minusInfinity(), + Bound::finite(rhs, resultStrict))); + } + else + { + next = meetIntervals(next, + Interval(Bound::finite(rhs, resultStrict), + Bound::plusInfinity())); + } + const Interval previous = boundAt(dimension); + setBound(dimension, next); + if (bottom_) + return; + changed = changed || + !intervalIncluded(previous, boundAt(dimension)) || + !intervalIncluded(boundAt(dimension), previous); + } + if (!changed) + break; + } + + const Interval value = evaluate(*this, expression); + if (value.lower().isFinite()) + { + const int sign = value.lower().value().sign(); + if (sign > 0 || (sign == 0 && (strict || value.lower().isStrict()))) + makeBottom(); + } +} + +void BoxState::assume(const TreeConstraint& constraint) +{ + const std::optional linear = + constraint.expression().asLinear(); + if (linear) + { + assume(LinearConstraint(*linear, constraint.kind())); + return; + } + const LinearConstraintSet consequences = + treeConstraintConsequences(constraint); + assumeAll(consequences); + report(OperationKind::Assumption, ApproximationKind::SoundOverApproximation, + consequences.empty() + ? "nonlinear or finite IEEE guard had no affine consequence" + : "nonlinear guard was reduced to sound affine consequences", + false); +} + +void BoxState::forget(Variable variable) +{ + if (!environment_.contains(variable)) + throw std::invalid_argument("forgotten variable is not in environment"); + if (!bottom_) + eraseBound(environment_.dimensionOf(variable)); + recordOperation(OperationKind::Forget, ApproximationKind::Exact, true); +} + +void BoxState::changeEnvironment(const VariableEnvironment& environment, + bool initializeNewVariablesToZero) +{ + if (environment_ == environment) + { + recordOperation(OperationKind::EnvironmentChange, + ApproximationKind::Exact, true); + return; + } + for (const VariableDeclaration& declaration : environment.variables()) + { + if (environment_.contains(declaration.variable) && + environment_.typeOf(declaration.variable) != declaration.type) + throw std::invalid_argument( + "environment change modifies a variable's numeric type"); + } + BoxState next = BoxState::top(environment, config_); + if (bottom_) + next.makeBottom(); + else + { + for (Dimension oldDimension : boundedDimensions()) + { + const Variable variable = environment_.variableOf(oldDimension); + if (environment.contains(variable)) + next.setBound(environment.dimensionOf(variable), + boundAt(oldDimension)); + } + if (initializeNewVariablesToZero) + { + for (const VariableDeclaration& declaration : + environment.variables()) + { + if (!environment_.contains(declaration.variable)) + next.setBound(environment.dimensionOf(declaration.variable), + Interval::singleton(Rational())); + } + } + } + environment_ = std::move(next.environment_); + boundPages_ = std::move(next.boundPages_); + bottom_ = next.bottom_; + recordOperation(OperationKind::EnvironmentChange, ApproximationKind::Exact, + true); +} + +void BoxState::expand(Variable source, + const std::vector& copies) +{ + if (!environment_.contains(source)) + throw std::invalid_argument("expanded variable is not in environment"); + std::set seen; + for (const VariableDeclaration& copy : copies) + { + if (environment_.contains(copy.variable) || + !seen.insert(copy.variable).second) + throw std::invalid_argument( + "expanded variables must be new and unique"); + if (copy.type != environment_.typeOf(source)) + throw std::invalid_argument( + "expanded variables must have the source numeric type"); + } + if (copies.empty()) + { + recordOperation(OperationKind::Expand, ApproximationKind::Exact, true); + return; + } + const Interval sourceValue = bound(source); + changeEnvironment(environment_.add(copies)); + for (const VariableDeclaration& copy : copies) + if (!bottom_) + setBound(environment_.dimensionOf(copy.variable), sourceValue); + recordOperation(OperationKind::Expand, ApproximationKind::Exact, true); +} + +void BoxState::fold(Variable target, const std::vector& folded) +{ + if (!environment_.contains(target)) + throw std::invalid_argument("fold target is not in environment"); + std::set seen; + std::vector sources{target}; + for (Variable variable : folded) + { + if (variable == target || !environment_.contains(variable) || + !seen.insert(variable).second) + throw std::invalid_argument( + "folded variables must be distinct non-target dimensions"); + if (environment_.typeOf(variable) != environment_.typeOf(target)) + throw std::invalid_argument( + "folded variables must have the target numeric type"); + sources.push_back(variable); + } + if (folded.empty()) + { + recordOperation(OperationKind::Fold, ApproximationKind::Exact, true); + return; + } + + BoxState result = bottom(environment_, config_); + for (Variable source : sources) + { + BoxState branch = *this; + if (source != target) + branch.setBound(environment_.dimensionOf(target), bound(source)); + result = result.join(branch); + } + result.changeEnvironment(environment_.remove(folded)); + *this = std::move(result); + recordOperation(OperationKind::Fold, ApproximationKind::Exact, true); +} + +CheckResult BoxState::entails(const LinearConstraint& constraint) const +{ + if (bottom_) + return CheckResult::True; + const Interval value = evaluate(*this, constraint.expression()); + const auto upperAtMostZero = [&]() { + if (!value.upper().isFinite()) + return false; + return value.upper().value().sign() <= 0; + }; + const auto upperBelowZero = [&]() { + return value.upper().isFinite() && + (value.upper().value().sign() < 0 || + (value.upper().value().isZero() && value.upper().isStrict())); + }; + const auto lowerAtLeastZero = [&]() { + return value.lower().isFinite() && value.lower().value().sign() >= 0; + }; + const auto lowerAboveZero = [&]() { + return value.lower().isFinite() && + (value.lower().value().sign() > 0 || + (value.lower().value().isZero() && value.lower().isStrict())); + }; + + switch (constraint.kind()) + { + case ConstraintKind::LessEqual: + return upperAtMostZero() ? CheckResult::True : CheckResult::Unknown; + case ConstraintKind::LessThan: + return upperBelowZero() ? CheckResult::True : CheckResult::Unknown; + case ConstraintKind::GreaterEqual: + return lowerAtLeastZero() ? CheckResult::True : CheckResult::Unknown; + case ConstraintKind::GreaterThan: + return lowerAboveZero() ? CheckResult::True : CheckResult::Unknown; + case ConstraintKind::Equal: + return upperAtMostZero() && lowerAtLeastZero() ? CheckResult::True + : CheckResult::Unknown; + case ConstraintKind::NotEqual: + return upperBelowZero() || lowerAboveZero() ? CheckResult::True + : CheckResult::Unknown; + } + return CheckResult::Unknown; +} + +Interval BoxState::bound(Variable variable) const +{ + if (!environment_.contains(variable)) + throw std::invalid_argument("bounded variable is not in environment"); + if (bottom_) + return Interval(Bound::plusInfinity(), Bound::minusInfinity()); + return boundAt(environment_.dimensionOf(variable)); +} + +Interval BoxState::bound(const LinearExpression& expression) const +{ + for (const auto& [variable, coefficient] : expression.terms()) + { + (void)coefficient; + if (!environment_.contains(variable)) + throw std::invalid_argument( + "bounded expression uses an unknown variable"); + } + if (bottom_) + return Interval(Bound::plusInfinity(), Bound::minusInfinity()); + return evaluate(*this, expression); +} + +IntervalBox BoxState::toBox() const +{ + IntervalBox result; + for (Dimension dimension = 0; dimension < environment_.size(); ++dimension) + result.bounds.emplace(environment_.variableOf(dimension), + bottom_ + ? bound(environment_.variableOf(dimension)) + : boundAt(dimension)); + return result; +} + +LinearConstraintSet BoxState::toConstraints() const +{ + LinearConstraintSet result; + if (bottom_) + { + result.emplace_back(LinearExpression(Rational(1)), + ConstraintKind::LessEqual); + return result; + } + for (Dimension dimension : boundedDimensions()) + { + const Variable variable = environment_.variableOf(dimension); + const Interval& interval = boundAt(dimension); + if (interval.lower().isFinite()) + { + result.emplace_back(LinearExpression(variable) - + LinearExpression(interval.lower().value()), + interval.lower().isStrict() + ? ConstraintKind::GreaterThan + : ConstraintKind::GreaterEqual); + } + if (interval.upper().isFinite()) + { + result.emplace_back(LinearExpression(variable) - + LinearExpression(interval.upper().value()), + interval.upper().isStrict() + ? ConstraintKind::LessThan + : ConstraintKind::LessEqual); + } + } + return result; +} + +void BoxState::close() +{ + recordOperation(OperationKind::TopologicalClosure, ApproximationKind::Exact, + true, "topological closure"); + if (bottom_) + return; + for (Dimension dimension : boundedDimensions()) + { + const Interval& interval = boundAt(dimension); + const Bound lower = interval.lower().isFinite() + ? Bound::finite(interval.lower().value()) + : interval.lower(); + const Bound upper = interval.upper().isFinite() + ? Bound::finite(interval.upper().value()) + : interval.upper(); + setBound(dimension, Interval(lower, upper)); + } +} + +void BoxState::canonicalize() +{ + for (Dimension dimension : boundedDimensions()) + canonicalize(dimension); + recordOperation(OperationKind::Canonicalization, ApproximationKind::Exact, + true, "canonicalization"); +} + +BoxState BoxState::join(const BoxState& other) const +{ + BoxState result(*this); + result.joinState(other); + result.recordOperation(OperationKind::Join, ApproximationKind::Exact, true); + return result; +} + +BoxState BoxState::meet(const BoxState& other) const +{ + BoxState result(*this); + result.meetState(other); + result.recordOperation(OperationKind::Meet, ApproximationKind::Exact, true); + return result; +} + +BoxState BoxState::widen(const BoxState& next, + const WideningPolicy& policy) const +{ + requireBox(next); + if (bottom_) + { + BoxState result(next); + result.recordOperation(OperationKind::Widening, + ApproximationKind::SoundOverApproximation, true); + return result; + } + if (next.bottom_) + { + BoxState result(*this); + result.recordOperation(OperationKind::Widening, + ApproximationKind::SoundOverApproximation, true); + return result; + } + BoxState result(*this); + for (Dimension dimension : boundedDimensions()) + { + Bound lower = boundAt(dimension).lower(); + Bound upper = boundAt(dimension).upper(); + const Interval& following = next.boundAt(dimension); + if (compareLower(following.lower(), lower) < 0) + { + lower = Bound::minusInfinity(); + if (following.lower().isFinite()) + { + for (const Rational& threshold : policy.thresholds) + { + if (threshold <= following.lower().value() && + (lower.isMinusInfinity() || lower.value() < threshold)) + lower = Bound::finite(threshold); + } + } + } + if (Bound::compare(following.upper(), upper) > 0) + { + upper = Bound::plusInfinity(); + if (following.upper().isFinite()) + { + for (const Rational& threshold : policy.thresholds) + { + if (following.upper().value() <= threshold && + (upper.isPlusInfinity() || threshold < upper.value())) + upper = Bound::finite(threshold); + } + } + } + result.setBound(dimension, Interval(lower, upper)); + } + for (const LinearConstraint& threshold : policy.linearThresholds) + { + if (entails(threshold) == CheckResult::True && + next.entails(threshold) == CheckResult::True) + result.assume(threshold); + } + result.recordOperation(OperationKind::Widening, + ApproximationKind::SoundOverApproximation, true); + return result; +} + +BoxState BoxState::narrow(const BoxState& next) const +{ + requireBox(next); + if (bottom_ || next.bottom_) + { + BoxState result = bottom(environment_, config_); + result.recordOperation(OperationKind::Narrowing, + ApproximationKind::Exact, true); + return result; + } + BoxState result(*this); + for (Dimension dimension : next.boundedDimensions()) + { + Bound lower = boundAt(dimension).lower(); + Bound upper = boundAt(dimension).upper(); + if (lower.isMinusInfinity()) + lower = next.boundAt(dimension).lower(); + if (upper.isPlusInfinity()) + upper = next.boundAt(dimension).upper(); + result.setBound(dimension, Interval(lower, upper)); + } + result.recordOperation(OperationKind::Narrowing, ApproximationKind::Exact, + true); + return result; +} + +bool BoxState::hasCompatibleDomain(const AbstractState& other) const +{ + const auto* box = other.isState() + ? &static_cast(other) + : nullptr; + return box && environment_ == box->environment_ && + config_.operationCompatible(box->config_); +} + +void BoxState::joinState(const AbstractState& other) +{ + const BoxState& box = requireBox(other); + if (box.bottom_) + return; + if (bottom_) + { + *this = box; + return; + } + for (Dimension dimension : boundedDimensions()) + setBound(dimension, + joinIntervals(boundAt(dimension), box.boundAt(dimension))); +} + +void BoxState::meetState(const AbstractState& other) +{ + const BoxState& box = requireBox(other); + if (bottom_ || box.bottom_) + { + makeBottom(); + return; + } + for (Dimension dimension : box.boundedDimensions()) + { + setBound(dimension, + meetIntervals(boundAt(dimension), box.boundAt(dimension))); + if (bottom_) + return; + } +} + +void BoxState::widenState(const AbstractState& next) +{ + *this = widen(requireBox(next)); +} + +void BoxState::narrowState(const AbstractState& next) +{ + *this = narrow(requireBox(next)); +} + +bool BoxState::isBottomState() const +{ + return bottom_; +} + +bool BoxState::isTopState() const +{ + return !bottom_ && boundPages_.empty(); +} + +bool BoxState::leqState(const AbstractState& other) const +{ + const BoxState& box = requireBox(other); + if (bottom_ == box.bottom_ && boundPages_.size() == box.boundPages_.size()) + { + bool equal = true; + for (std::size_t index = 0; index < boundPages_.size(); ++index) + { + if (boundPages_[index].index != box.boundPages_[index].index || + (boundPages_[index].page != box.boundPages_[index].page && + boundPages_[index].page->bounds != + box.boundPages_[index].page->bounds)) + { + equal = false; + break; + } + } + if (equal) + return true; + } + if (bottom_) + return true; + if (box.bottom_) + return false; + for (Dimension dimension : box.boundedDimensions()) + { + if (!intervalIncluded(boundAt(dimension), box.boundAt(dimension))) + return false; + } + return true; +} + +std::string BoxState::stateToString() const +{ + if (bottom_) + return "bottom"; + std::ostringstream output; + output << "{"; + for (Dimension dimension = 0; dimension < environment_.size(); ++dimension) + { + if (dimension != 0) + output << ", "; + output << environment_.nameOf(environment_.variableOf(dimension)) << "=" + << boundAt(dimension).toString(); + } + output << "}"; + return output.str(); +} + +const BoxState& BoxState::requireBox(const AbstractState& other) const +{ + requireCompatible(other); + return static_cast(other); +} + +void BoxState::canonicalize(Dimension dimension) +{ + if (bottom_) + return; + Interval interval = boundAt(dimension); + const Variable variable = environment_.variableOf(dimension); + if (config_.integerTightening && + environment_.typeOf(variable).kind == NumericKind::Integer) + { + interval = Interval(integerLower(interval.lower()), + integerUpper(interval.upper())); + } + if (interval.isBottom()) + { + makeBottom(); + return; + } + if (interval.isTop()) + eraseBound(dimension); + else + writablePage(dimension / BoundsPerPage) + .bounds[dimension % BoundsPerPage] = std::move(interval); +} + +void BoxState::setBound(Dimension dimension, Interval interval) +{ + if (interval.isTop()) + eraseBound(dimension); + else + writablePage(dimension / BoundsPerPage) + .bounds[dimension % BoundsPerPage] = std::move(interval); + canonicalize(dimension); +} + +const Interval& BoxState::boundAt(Dimension dimension) const +{ + static const Interval top = Interval::top(); + const std::size_t pageIndex = dimension / BoundsPerPage; + const auto iterator = + std::lower_bound(boundPages_.begin(), boundPages_.end(), pageIndex, + [](const BoundPageEntry& entry, std::size_t index) { + return entry.index < index; + }); + if (iterator == boundPages_.end() || iterator->index != pageIndex) + return top; + const auto& slot = iterator->page->bounds[dimension % BoundsPerPage]; + return slot ? *slot : top; +} + +BoxState::BoundPage& BoxState::writablePage(std::size_t pageIndex) +{ + auto iterator = + std::lower_bound(boundPages_.begin(), boundPages_.end(), pageIndex, + [](const BoundPageEntry& entry, std::size_t index) { + return entry.index < index; + }); + if (iterator == boundPages_.end() || iterator->index != pageIndex) + iterator = boundPages_.insert( + iterator, {pageIndex, std::make_shared()}); + else if (iterator->page.use_count() != 1) + iterator->page = std::make_shared(*iterator->page); + return *iterator->page; +} + +void BoxState::eraseBound(Dimension dimension) +{ + const std::size_t pageIndex = dimension / BoundsPerPage; + auto existing = + std::lower_bound(boundPages_.begin(), boundPages_.end(), pageIndex, + [](const BoundPageEntry& entry, std::size_t index) { + return entry.index < index; + }); + if (existing == boundPages_.end() || existing->index != pageIndex) + return; + const std::size_t offset = dimension % BoundsPerPage; + if (!existing->page->bounds[offset]) + return; + auto iterator = + std::lower_bound(boundPages_.begin(), boundPages_.end(), pageIndex, + [](const BoundPageEntry& entry, std::size_t index) { + return entry.index < index; + }); + if (iterator->page.use_count() != 1) + iterator->page = std::make_shared(*iterator->page); + iterator->page->bounds[offset].reset(); + if (pageIsEmpty(*iterator->page)) + boundPages_.erase(iterator); +} + +bool BoxState::pageIsEmpty(const BoundPage& page) +{ + return std::none_of(page.bounds.begin(), page.bounds.end(), + [](const auto& bound) { return bound.has_value(); }); +} + +std::vector BoxState::boundedDimensions() const +{ + std::vector dimensions; + for (const BoundPageEntry& entry : boundPages_) + { + for (std::size_t offset = 0; offset < BoundsPerPage; ++offset) + { + const Dimension dimension = entry.index * BoundsPerPage + offset; + if (dimension >= environment_.size()) + break; + if (entry.page->bounds[offset]) + dimensions.push_back(dimension); + } + } + return dimensions; +} + +void BoxState::makeBottom() +{ + bottom_ = true; + boundPages_.clear(); +} + +void BoxState::report(OperationKind operation, ApproximationKind approximation, + std::string reason, bool best) const +{ + recordOperation(operation, approximation, best, reason); + if (config_.diagnostics) + config_.diagnostics->report( + {operation, approximation, std::move(reason)}); +} + +} // namespace SVF::AbstractDomain diff --git a/svf/lib/AE/Core/BoxProgramState.cpp b/svf/lib/AE/Core/BoxProgramState.cpp new file mode 100644 index 0000000000..931f63c3ed --- /dev/null +++ b/svf/lib/AE/Core/BoxProgramState.cpp @@ -0,0 +1,866 @@ +//===- BoxProgramState.cpp -- Complete Box AE state -----------------===// + +#include "AE/Core/BoxProgramState.h" + +#include +#include + +namespace SVF::AbstractDomain +{ + +namespace +{ + +template +std::set combinedKeys(const std::map& lhs, + const std::map& rhs) +{ + std::set keys; + for (const auto& entry : lhs) + keys.insert(entry.first); + for (const auto& entry : rhs) + keys.insert(entry.first); + return keys; +} + +} // namespace + +PointeeSet PointeeSet::bottom() +{ + return PointeeSet(false); +} + +PointeeSet PointeeSet::top() +{ + return PointeeSet(true); +} + +PointeeSet PointeeSet::singleton(Location location) +{ + PointeeSet result = bottom(); + result.insert(location); + return result; +} + +bool PointeeSet::isBottom() const +{ + return !top_ && locations_.empty(); +} + +bool PointeeSet::isTop() const +{ + return top_; +} + +bool PointeeSet::contains(Location location) const +{ + return top_ || locations_.count(location) != 0; +} + +bool PointeeSet::isSingleton() const +{ + return !top_ && locations_.size() == 1; +} + +const std::set& PointeeSet::locations() const +{ + if (top_) + throw std::logic_error("top address set has no finite enumeration"); + return locations_; +} + +void PointeeSet::insert(Location location) +{ + if (!top_) + locations_.insert(location); +} + +void PointeeSet::joinWith(const PointeeSet& other) +{ + if (top_ || other.isBottom()) + return; + if (other.top_) + { + *this = top(); + return; + } + locations_.insert(other.locations_.begin(), other.locations_.end()); +} + +void PointeeSet::meetWith(const PointeeSet& other) +{ + if (other.top_ || isBottom()) + return; + if (top_) + { + *this = other; + return; + } + std::set intersection; + std::set_intersection(locations_.begin(), locations_.end(), + other.locations_.begin(), other.locations_.end(), + std::inserter(intersection, intersection.begin())); + locations_ = std::move(intersection); +} + +bool PointeeSet::isSubsetOf(const PointeeSet& other) const +{ + if (other.top_ || isBottom()) + return true; + if (top_) + return false; + return std::includes(other.locations_.begin(), other.locations_.end(), + locations_.begin(), locations_.end()); +} + +std::string PointeeSet::toString() const +{ + if (top_) + return "top"; + if (locations_.empty()) + return "bottom"; + std::ostringstream output; + output << "{"; + bool first = true; + for (Location location : locations_) + { + if (!first) + output << ","; + first = false; + output << location.id(); + } + output << "}"; + return output.str(); +} + +PointerMap PointerMap::top() +{ + return PointerMap(true); +} + +PointerMap PointerMap::bottom() +{ + return PointerMap(false); +} + +PointeeSet PointerMap::pointeesOf(Variable variable) const +{ + const auto it = values_->find(variable); + return it == values_->end() ? defaultValue() : it->second; +} + +void PointerMap::assign(Variable variable, PointeeSet addresses) +{ + writableValues()[variable] = std::move(addresses); + normalize(variable); +} + +void PointerMap::forget(Variable variable) +{ + assign(variable, PointeeSet::top()); +} + +void PointerMap::changeEnvironment(const VariableEnvironment& environment) +{ + const bool hasOutOfScope = + std::any_of(values_->begin(), values_->end(), [&](const auto& entry) { + return !environment.contains(entry.first); + }); + if (!hasOutOfScope) + return; + Values& values = writableValues(); + for (auto iterator = values.begin(); iterator != values.end();) + { + if (!environment.contains(iterator->first)) + iterator = values.erase(iterator); + else + ++iterator; + } +} + +void PointerMap::joinWith(const PointerMap& other) +{ + if (other.isBottom()) + return; + if (isBottom()) + { + *this = other; + return; + } + const std::set variables = combinedKeys(*values_, *other.values_); + const bool nextDefaultTop = defaultTop_ || other.defaultTop_; + std::map next; + for (Variable variable : variables) + { + PointeeSet value = pointeesOf(variable); + value.joinWith(other.pointeesOf(variable)); + if (value != + (nextDefaultTop ? PointeeSet::top() : PointeeSet::bottom())) + next.emplace(variable, std::move(value)); + } + defaultTop_ = nextDefaultTop; + values_ = std::make_shared(std::move(next)); +} + +void PointerMap::meetWith(const PointerMap& other) +{ + if (other.isTop()) + return; + if (isTop()) + { + *this = other; + return; + } + const std::set variables = combinedKeys(*values_, *other.values_); + const bool nextDefaultTop = defaultTop_ && other.defaultTop_; + std::map next; + for (Variable variable : variables) + { + PointeeSet value = pointeesOf(variable); + value.meetWith(other.pointeesOf(variable)); + if (value != + (nextDefaultTop ? PointeeSet::top() : PointeeSet::bottom())) + next.emplace(variable, std::move(value)); + } + defaultTop_ = nextDefaultTop; + values_ = std::make_shared(std::move(next)); +} + +void PointerMap::widenWith(const PointerMap& next) +{ + joinWith(next); +} + +void PointerMap::narrowWith(const PointerMap& next) +{ + meetWith(next); +} + +bool PointerMap::isBottom() const +{ + return !defaultTop_ && values_->empty(); +} + +bool PointerMap::isTop() const +{ + return defaultTop_ && values_->empty(); +} + +bool PointerMap::isSubsetOf(const PointerMap& other) const +{ + if (defaultTop_ == other.defaultTop_ && + (values_ == other.values_ || *values_ == *other.values_)) + return true; + if (defaultTop_ && !other.defaultTop_) + return false; + const std::set variables = combinedKeys(*values_, *other.values_); + return std::all_of( + variables.begin(), variables.end(), [&](Variable variable) { + return pointeesOf(variable).isSubsetOf(other.pointeesOf(variable)); + }); +} + +std::string PointerMap::toString() const +{ + std::ostringstream output; + output << "default=" << defaultValue().toString() << " {"; + bool first = true; + for (const auto& [variable, value] : *values_) + { + if (!first) + output << ", "; + first = false; + output << variable.id() << "=" << value.toString(); + } + output << "}"; + return output.str(); +} + +void PointerMap::normalize(Variable variable) +{ + const auto it = values_->find(variable); + if (it != values_->end() && it->second == defaultValue()) + writableValues().erase(variable); +} + +PointerMap::Values& PointerMap::writableValues() +{ + if (values_.use_count() != 1) + values_ = std::make_shared(*values_); + return *values_; +} + +PointeeSet PointerMap::defaultValue() const +{ + return defaultTop_ ? PointeeSet::top() : PointeeSet::bottom(); +} + +Lifetime join(Lifetime lhs, Lifetime rhs) +{ + if (lhs == Lifetime::Bottom) + return rhs; + if (rhs == Lifetime::Bottom || lhs == rhs) + return lhs; + return Lifetime::MaybeFreed; +} + +Lifetime meet(Lifetime lhs, Lifetime rhs) +{ + if (lhs == Lifetime::MaybeFreed) + return rhs; + if (rhs == Lifetime::MaybeFreed || lhs == rhs) + return lhs; + return Lifetime::Bottom; +} + +bool isSubsetOf(Lifetime lhs, Lifetime rhs) +{ + return lhs == Lifetime::Bottom || rhs == Lifetime::MaybeFreed || lhs == rhs; +} + +const char* toString(Lifetime lifetime) +{ + switch (lifetime) + { + case Lifetime::Bottom: + return "bottom"; + case Lifetime::Alive: + return "alive"; + case Lifetime::Freed: + return "freed"; + case Lifetime::MaybeFreed: + return "maybe-freed"; + } + return "invalid"; +} + +LifetimeState LifetimeState::top() +{ + return LifetimeState(Lifetime::MaybeFreed); +} + +LifetimeState LifetimeState::bottom() +{ + return LifetimeState(Lifetime::Bottom); +} + +ValueShapeState ValueShapeState::top() +{ + return ValueShapeState(true, true); +} + +ValueShapeState ValueShapeState::bottom() +{ + return ValueShapeState(false, false); +} + +std::unique_ptr ValueShapeState::clone() const +{ + return std::make_unique(*this); +} + +const char* ValueShapeState::name() const +{ + return "ValueShapeState"; +} + +ValueShapeState::Shape ValueShapeState::shapeOf(Variable variable) const +{ + return decode(encodedShapeOf(variable)); +} + +bool ValueShapeState::isDefined(Variable variable) const +{ + return shapeOf(variable).defined; +} + +bool ValueShapeState::hasNumeric(Variable variable) const +{ + return shapeOf(variable).numeric; +} + +std::vector ValueShapeState::definedVariables( + const VariableEnvironment& environment) const +{ + std::vector result; + if (decode(default_).defined) + { + result.reserve(environment.size()); + for (const VariableDeclaration& declaration : environment.variables()) + { + if (isDefined(declaration.variable)) + result.push_back(declaration.variable); + } + return result; + } + + for (const ShapePageEntry& entry : pages_) + { + for (std::size_t offset = 0; offset < ShapesPerPage; ++offset) + { + if (!decode(entry.page->shapes[offset]).defined) + continue; + const Variable variable(static_cast( + entry.index * ShapesPerPage + offset)); + if (environment.contains(variable)) + result.push_back(variable); + } + } + return result; +} + +void ValueShapeState::assign(Variable variable, bool numeric) +{ + setEncodedShape(variable, encode({true, numeric})); +} + +void ValueShapeState::forget(Variable variable) +{ + setEncodedShape(variable, encode({false, false})); +} + +void ValueShapeState::changeEnvironment(const VariableEnvironment& environment) +{ + std::vector next; + next.reserve(pages_.size()); + for (const ShapePageEntry& entry : pages_) + { + std::shared_ptr page = entry.page; + bool changed = false; + for (std::size_t offset = 0; offset < ShapesPerPage; ++offset) + { + if (page->shapes[offset] == default_) + continue; + const Variable variable(static_cast( + entry.index * ShapesPerPage + offset)); + if (!environment.contains(variable)) + { + if (!changed) + page = std::make_shared(*page); + page->shapes[offset] = default_; + changed = true; + } + } + if (!pageIsDefault(*page, default_)) + next.push_back({entry.index, std::move(page)}); + } + pages_ = std::move(next); +} + +bool ValueShapeState::hasCompatibleDomain(const AbstractState& other) const +{ + return other.isState(); +} + +void ValueShapeState::joinState(const AbstractState& other) +{ + const auto& state = static_cast(other); + if (state.isBottomState()) + return; + if (isBottomState()) + { + *this = state; + return; + } + const std::uint8_t nextDefault = default_ | state.default_; + std::vector next; + next.reserve(pages_.size() + state.pages_.size()); + std::size_t lhs = 0; + std::size_t rhs = 0; + while (lhs < pages_.size() || rhs < state.pages_.size()) + { + const std::size_t pageIndex = + rhs == state.pages_.size() || + (lhs < pages_.size() && + pages_[lhs].index < state.pages_[rhs].index) + ? pages_[lhs].index + : state.pages_[rhs].index; + const ShapePage* lhsPage = + lhs < pages_.size() && pages_[lhs].index == pageIndex + ? pages_[lhs++].page.get() + : nullptr; + const ShapePage* rhsPage = + rhs < state.pages_.size() && state.pages_[rhs].index == pageIndex + ? state.pages_[rhs++].page.get() + : nullptr; + auto page = std::make_shared(); + for (std::size_t offset = 0; offset < ShapesPerPage; ++offset) + page->shapes[offset] = + (lhsPage ? lhsPage->shapes[offset] : default_) | + (rhsPage ? rhsPage->shapes[offset] : state.default_); + if (!pageIsDefault(*page, nextDefault)) + next.push_back({pageIndex, std::move(page)}); + } + default_ = nextDefault; + pages_ = std::move(next); +} + +void ValueShapeState::meetState(const AbstractState& other) +{ + const auto& state = static_cast(other); + if (state.isTopState()) + return; + if (isTopState()) + { + *this = state; + return; + } + const std::uint8_t nextDefault = default_ & state.default_; + std::vector next; + next.reserve(pages_.size() + state.pages_.size()); + std::size_t lhs = 0; + std::size_t rhs = 0; + while (lhs < pages_.size() || rhs < state.pages_.size()) + { + const std::size_t pageIndex = + rhs == state.pages_.size() || + (lhs < pages_.size() && + pages_[lhs].index < state.pages_[rhs].index) + ? pages_[lhs].index + : state.pages_[rhs].index; + const ShapePage* lhsPage = + lhs < pages_.size() && pages_[lhs].index == pageIndex + ? pages_[lhs++].page.get() + : nullptr; + const ShapePage* rhsPage = + rhs < state.pages_.size() && state.pages_[rhs].index == pageIndex + ? state.pages_[rhs++].page.get() + : nullptr; + auto page = std::make_shared(); + for (std::size_t offset = 0; offset < ShapesPerPage; ++offset) + page->shapes[offset] = + (lhsPage ? lhsPage->shapes[offset] : default_) & + (rhsPage ? rhsPage->shapes[offset] : state.default_); + if (!pageIsDefault(*page, nextDefault)) + next.push_back({pageIndex, std::move(page)}); + } + default_ = nextDefault; + pages_ = std::move(next); +} + +void ValueShapeState::widenState(const AbstractState& next) +{ + joinState(next); +} + +void ValueShapeState::narrowState(const AbstractState& next) +{ + meetState(next); +} + +bool ValueShapeState::isBottomState() const +{ + return default_ == encode({false, false}) && pages_.empty(); +} + +bool ValueShapeState::isTopState() const +{ + return default_ == encode({true, true}) && pages_.empty(); +} + +bool ValueShapeState::leqState(const AbstractState& other) const +{ + const auto& state = static_cast(other); + if (default_ == state.default_ && pages_.size() == state.pages_.size()) + { + bool equal = true; + for (std::size_t index = 0; index < pages_.size(); ++index) + { + if (pages_[index].index != state.pages_[index].index || + (pages_[index].page != state.pages_[index].page && + pages_[index].page->shapes != + state.pages_[index].page->shapes)) + { + equal = false; + break; + } + } + if (equal) + return true; + } + if ((default_ & ~state.default_) != 0) + return false; + std::size_t lhs = 0; + std::size_t rhs = 0; + while (lhs < pages_.size() || rhs < state.pages_.size()) + { + const std::size_t pageIndex = + rhs == state.pages_.size() || + (lhs < pages_.size() && + pages_[lhs].index < state.pages_[rhs].index) + ? pages_[lhs].index + : state.pages_[rhs].index; + const ShapePage* lhsPage = + lhs < pages_.size() && pages_[lhs].index == pageIndex + ? pages_[lhs++].page.get() + : nullptr; + const ShapePage* rhsPage = + rhs < state.pages_.size() && state.pages_[rhs].index == pageIndex + ? state.pages_[rhs++].page.get() + : nullptr; + for (std::size_t offset = 0; offset < ShapesPerPage; ++offset) + { + const std::uint8_t lhsShape = + lhsPage ? lhsPage->shapes[offset] : default_; + const std::uint8_t rhsShape = + rhsPage ? rhsPage->shapes[offset] : state.default_; + if ((lhsShape & ~rhsShape) != 0) + return false; + } + } + return true; +} + +std::string ValueShapeState::stateToString() const +{ + std::ostringstream output; + const Shape defaultShape = decode(default_); + output << "default=(defined=" << defaultShape.defined + << ",numeric=" << defaultShape.numeric << ") {"; + bool first = true; + for (const ShapePageEntry& entry : pages_) + { + for (std::size_t offset = 0; offset < ShapesPerPage; ++offset) + { + if (entry.page->shapes[offset] == default_) + continue; + if (!first) + output << ", "; + first = false; + const Shape shape = decode(entry.page->shapes[offset]); + output << entry.index * ShapesPerPage + offset + << "=(defined=" << shape.defined + << ",numeric=" << shape.numeric << ")"; + } + } + output << "}"; + return output.str(); +} + +std::uint8_t ValueShapeState::encode(Shape shape) +{ + return static_cast((shape.defined ? 1U : 0U) | + (shape.numeric ? 2U : 0U)); +} + +ValueShapeState::Shape ValueShapeState::decode(std::uint8_t shape) +{ + return {(shape & 1U) != 0, (shape & 2U) != 0}; +} + +std::uint8_t ValueShapeState::encodedShapeOf(Variable variable) const +{ + const std::size_t pageIndex = variable.id() / ShapesPerPage; + const auto iterator = + std::lower_bound(pages_.begin(), pages_.end(), pageIndex, + [](const ShapePageEntry& entry, std::size_t index) { + return entry.index < index; + }); + if (iterator == pages_.end() || iterator->index != pageIndex) + return default_; + return iterator->page->shapes[variable.id() % ShapesPerPage]; +} + +void ValueShapeState::setEncodedShape(Variable variable, std::uint8_t shape) +{ + const std::size_t pageIndex = variable.id() / ShapesPerPage; + auto iterator = + std::lower_bound(pages_.begin(), pages_.end(), pageIndex, + [](const ShapePageEntry& entry, std::size_t index) { + return entry.index < index; + }); + if (iterator == pages_.end() || iterator->index != pageIndex) + { + if (shape == default_) + return; + auto page = std::make_shared(); + page->shapes.fill(default_); + iterator = pages_.insert(iterator, {pageIndex, std::move(page)}); + } + else if (iterator->page.use_count() != 1) + { + iterator->page = std::make_shared(*iterator->page); + } + iterator->page->shapes[variable.id() % ShapesPerPage] = shape; + if (pageIsDefault(*iterator->page, default_)) + pages_.erase(iterator); +} + +bool ValueShapeState::pageIsDefault(const ShapePage& page, + std::uint8_t defaultShape) +{ + return std::all_of( + page.shapes.begin(), page.shapes.end(), + [defaultShape](std::uint8_t shape) { return shape == defaultShape; }); +} + +std::unique_ptr LifetimeState::clone() const +{ + return std::make_unique(*this); +} + +const char* LifetimeState::name() const +{ + return "LifetimeState"; +} + +Lifetime LifetimeState::statusOf(Location location) const +{ + const auto it = values_->find(location); + return it == values_->end() ? defaultValue_ : it->second; +} + +void LifetimeState::allocate(Location location) +{ + set(location, Lifetime::Alive); +} + +void LifetimeState::release(Location location) +{ + const Lifetime current = statusOf(location); + set(location, current == Lifetime::Alive || current == Lifetime::Freed + ? Lifetime::Freed + : Lifetime::MaybeFreed); +} + +bool LifetimeState::mayBeFreed(Location location) const +{ + const Lifetime lifetime = statusOf(location); + return lifetime == Lifetime::Freed || lifetime == Lifetime::MaybeFreed; +} + +bool LifetimeState::mustBeFreed(Location location) const +{ + return statusOf(location) == Lifetime::Freed; +} + +bool LifetimeState::hasCompatibleDomain(const AbstractState& other) const +{ + return other.isState(); +} + +void LifetimeState::joinState(const AbstractState& other) +{ + const auto& state = static_cast(other); + if (state.isBottomState()) + return; + if (isBottomState()) + { + *this = state; + return; + } + const std::set locations = combinedKeys(*values_, *state.values_); + const Lifetime nextDefault = join(defaultValue_, state.defaultValue_); + std::map next; + for (Location location : locations) + { + const Lifetime value = + join(statusOf(location), state.statusOf(location)); + if (value != nextDefault) + next.emplace(location, value); + } + defaultValue_ = nextDefault; + values_ = std::make_shared(std::move(next)); +} + +void LifetimeState::meetState(const AbstractState& other) +{ + const auto& state = static_cast(other); + if (state.isTopState()) + return; + if (isTopState()) + { + *this = state; + return; + } + const std::set locations = combinedKeys(*values_, *state.values_); + const Lifetime nextDefault = meet(defaultValue_, state.defaultValue_); + std::map next; + for (Location location : locations) + { + const Lifetime value = + meet(statusOf(location), state.statusOf(location)); + if (value != nextDefault) + next.emplace(location, value); + } + defaultValue_ = nextDefault; + values_ = std::make_shared(std::move(next)); +} + +void LifetimeState::widenState(const AbstractState& next) +{ + joinState(next); +} + +void LifetimeState::narrowState(const AbstractState& next) +{ + meetState(next); +} + +bool LifetimeState::isBottomState() const +{ + return defaultValue_ == Lifetime::Bottom && values_->empty(); +} + +bool LifetimeState::isTopState() const +{ + return defaultValue_ == Lifetime::MaybeFreed && values_->empty(); +} + +bool LifetimeState::leqState(const AbstractState& other) const +{ + const auto& state = static_cast(other); + if (defaultValue_ == state.defaultValue_ && + (values_ == state.values_ || *values_ == *state.values_)) + return true; + if (!SVF::AbstractDomain::isSubsetOf(defaultValue_, state.defaultValue_)) + return false; + const std::set locations = combinedKeys(*values_, *state.values_); + return std::all_of(locations.begin(), locations.end(), + [&](Location location) { + return SVF::AbstractDomain::isSubsetOf( + statusOf(location), state.statusOf(location)); + }); +} + +std::string LifetimeState::stateToString() const +{ + std::ostringstream output; + output << "default=" << SVF::AbstractDomain::toString(defaultValue_) + << " {"; + bool first = true; + for (const auto& [location, value] : *values_) + { + if (!first) + output << ", "; + first = false; + output << location.id() << "=" << SVF::AbstractDomain::toString(value); + } + output << "}"; + return output.str(); +} + +void LifetimeState::set(Location location, Lifetime lifetime) +{ + if (lifetime == defaultValue_) + writableValues().erase(location); + else + writableValues()[location] = lifetime; +} + +LifetimeState::Values& LifetimeState::writableValues() +{ + if (values_.use_count() != 1) + values_ = std::make_shared(*values_); + return *values_; +} + +Variable MemoryLayout::contentOf(Location location) const +{ + const auto it = cells_->find(location); + if (it == cells_->end()) + throw std::out_of_range("location has no content symbol"); + return it->second; +} + +} // namespace SVF::AbstractDomain diff --git a/svf/lib/AE/Core/LinearConstraint.cpp b/svf/lib/AE/Core/LinearConstraint.cpp new file mode 100644 index 0000000000..fbdfd1ce21 --- /dev/null +++ b/svf/lib/AE/Core/LinearConstraint.cpp @@ -0,0 +1,309 @@ +//===- LinearConstraint.cpp -- Domain-neutral linear syntax -------------===// + +#include "AE/Core/LinearConstraint.h" + +#include +#include +#include + +namespace SVF::AbstractDomain +{ + +LinearExpression::LinearExpression() = default; + +LinearExpression::LinearExpression(Rational constant) + : constant_(std::move(constant)) +{ +} +LinearExpression::LinearExpression(Variable variable) +{ + terms_.emplace(variable, Rational(1)); +} + +Rational LinearExpression::coefficient(Variable variable) const +{ + const auto it = terms_.find(variable); + return it == terms_.end() ? Rational() : it->second; +} + +LinearExpression& LinearExpression::setCoefficient(Variable variable, + Rational coefficient) +{ + if (coefficient.isZero()) + terms_.erase(variable); + else + terms_[variable] = std::move(coefficient); + return *this; +} + +LinearExpression& LinearExpression::setConstant(Rational constant) +{ + constant_ = std::move(constant); + return *this; +} + +LinearExpression& LinearExpression::operator+=(const LinearExpression& rhs) +{ + constant_ += rhs.constant_; + for (const auto& [variable, coefficient] : rhs.terms_) + terms_[variable] += coefficient; + removeZeroTerms(); + return *this; +} + +LinearExpression& LinearExpression::operator-=(const LinearExpression& rhs) +{ + constant_ -= rhs.constant_; + for (const auto& [variable, coefficient] : rhs.terms_) + terms_[variable] -= coefficient; + removeZeroTerms(); + return *this; +} + +LinearExpression& LinearExpression::operator*=(const Rational& scalar) +{ + constant_ *= scalar; + for (auto& [variable, coefficient] : terms_) + { + (void)variable; + coefficient *= scalar; + } + removeZeroTerms(); + return *this; +} + +LinearExpression LinearExpression::substituted( + const std::map& replacements) const +{ + LinearExpression result(constant_); + for (const auto& [variable, coefficient] : terms_) + { + const auto replacement = replacements.find(variable); + if (replacement == replacements.end()) + result.setCoefficient( + variable, result.coefficient(variable) + coefficient); + else + result += replacement->second * coefficient; + } + return result; +} + +void LinearExpression::removeZeroTerms() +{ + for (auto it = terms_.begin(); it != terms_.end();) + { + if (it->second.isZero()) + it = terms_.erase(it); + else + ++it; + } +} + +std::string LinearExpression::toString(const VariableEnvironment* environment) const +{ + std::ostringstream output; + bool first = true; + for (const auto& [variable, coefficient] : terms_) + { + if (!first) + output << " + "; + first = false; + output << coefficient.toString() << '*'; + if (environment && environment->contains(variable) && + !environment->nameOf(variable).empty()) + output << environment->nameOf(variable); + else + output << 'v' << variable.id(); + } + if (!constant_.isZero() || first) + { + if (!first) + output << " + "; + output << constant_.toString(); + } + return output.str(); +} + +TreeExpression TreeExpression::constant(Rational value, NumericType type) +{ + TreeExpression expression; + expression.kind_ = Kind::Constant; + expression.type_ = type; + expression.constant_ = std::move(value); + return expression; +} + +TreeExpression TreeExpression::variable(Variable value, NumericType type) +{ + TreeExpression expression; + expression.kind_ = Kind::Variable; + expression.type_ = type; + expression.variable_ = value; + return expression; +} + +TreeExpression TreeExpression::unary(UnaryOperator operation, + TreeExpression operand, NumericType type, + RoundingMode rounding) +{ + TreeExpression expression; + expression.kind_ = Kind::Unary; + expression.type_ = type; + expression.unaryOperator_ = operation; + expression.roundingMode_ = rounding; + expression.lhs_ = std::make_shared(std::move(operand)); + return expression; +} + +TreeExpression TreeExpression::binary(BinaryOperator operation, + TreeExpression lhs, TreeExpression rhs, + NumericType type, RoundingMode rounding) +{ + TreeExpression expression; + expression.kind_ = Kind::Binary; + expression.type_ = type; + expression.binaryOperator_ = operation; + expression.roundingMode_ = rounding; + expression.lhs_ = std::make_shared(std::move(lhs)); + expression.rhs_ = std::make_shared(std::move(rhs)); + return expression; +} + +const TreeExpression& TreeExpression::lhs() const +{ + if (!lhs_) + throw std::logic_error("tree expression has no left operand"); + return *lhs_; +} + +const TreeExpression& TreeExpression::rhs() const +{ + if (!rhs_) + throw std::logic_error("tree expression has no right operand"); + return *rhs_; +} + +std::optional TreeExpression::asLinear() const +{ + if (type_.kind == NumericKind::IEEEFloat) + return std::nullopt; + + switch (kind_) + { + case Kind::Constant: + return LinearExpression(constant_); + case Kind::Variable: + return LinearExpression(variable_); + case Kind::Unary: { + if (unaryOperator_ != UnaryOperator::Negate) + return std::nullopt; + std::optional operand = lhs().asLinear(); + return operand ? std::optional(-*operand) + : std::nullopt; + } + case Kind::Binary: { + std::optional left = lhs().asLinear(); + std::optional right = rhs().asLinear(); + if (!left || !right) + return std::nullopt; + switch (binaryOperator_) + { + case BinaryOperator::Add: + return *left + *right; + case BinaryOperator::Subtract: + return *left - *right; + case BinaryOperator::Multiply: + if (left->terms().empty()) + return *right * left->constant(); + if (right->terms().empty()) + return *left * right->constant(); + return std::nullopt; + case BinaryOperator::Divide: + if (right->terms().empty() && !right->constant().isZero()) + return *left * (Rational(1) / right->constant()); + return std::nullopt; + case BinaryOperator::Remainder: + return std::nullopt; + } + } + } + return std::nullopt; +} + +LinearConstraint::LinearConstraint(LinearExpression expression, + ConstraintKind kind) + : expression_(std::move(expression)), kind_(kind) +{ +} + +std::string LinearConstraint::toString(const VariableEnvironment* environment) const +{ + const char* relation; + switch (kind_) + { + case ConstraintKind::Equal: + relation = "=="; + break; + case ConstraintKind::NotEqual: + relation = "!="; + break; + case ConstraintKind::LessThan: + relation = "<"; + break; + case ConstraintKind::LessEqual: + relation = "<="; + break; + case ConstraintKind::GreaterThan: + relation = ">"; + break; + case ConstraintKind::GreaterEqual: + relation = ">="; + break; + default: + throw std::logic_error("unknown linear constraint kind"); + } + return expression_.toString(environment) + ' ' + relation + " 0"; +} + +TreeConstraint::TreeConstraint(TreeExpression expression, ConstraintKind kind) + : expression_(std::move(expression)), kind_(kind) +{ +} + +LinearConstraint equal(LinearExpression lhs, + LinearExpression rhs) +{ + return LinearConstraint(std::move(lhs) - rhs, ConstraintKind::Equal); +} + +LinearConstraint notEqual(LinearExpression lhs, + LinearExpression rhs) +{ + return LinearConstraint(std::move(lhs) - rhs, ConstraintKind::NotEqual); +} + +LinearConstraint lessEqual(LinearExpression lhs, + LinearExpression rhs) +{ + return LinearConstraint(std::move(lhs) - rhs, ConstraintKind::LessEqual); +} + +LinearConstraint lessThan(LinearExpression lhs, + LinearExpression rhs) +{ + return LinearConstraint(std::move(lhs) - rhs, ConstraintKind::LessThan); +} + +LinearConstraint greaterEqual(LinearExpression lhs, + LinearExpression rhs) +{ + return LinearConstraint(std::move(lhs) - rhs, ConstraintKind::GreaterEqual); +} + +LinearConstraint greaterThan(LinearExpression lhs, + LinearExpression rhs) +{ + return LinearConstraint(std::move(lhs) - rhs, ConstraintKind::GreaterThan); +} + +} // namespace SVF::AbstractDomain diff --git a/svf/lib/AE/Core/NumericPrimitives.cpp b/svf/lib/AE/Core/NumericPrimitives.cpp new file mode 100644 index 0000000000..a69dd29c3d --- /dev/null +++ b/svf/lib/AE/Core/NumericPrimitives.cpp @@ -0,0 +1,446 @@ +//===- NumericPrimitives.cpp -- Exact abstract-domain numbers -----------===// + +#include "AE/Core/NumericPrimitives.h" + +#include +#include + +namespace SVF::AbstractDomain +{ + +Integer::Integer() : value_(0) {} + +Integer::Integer(std::int64_t value) +{ + if (mpz_set_str(value_.get_mpz_t(), std::to_string(value).c_str(), 10) != 0) + throw std::invalid_argument("invalid 64-bit integer"); +} + +Integer::Integer(const std::string& value) : value_(value) {} + +std::string Integer::toString() const +{ + return value_.get_str(); +} + +Rational::Rational() : value_(0) {} + +Rational::Rational(std::int64_t value) +{ + mpz_class integer; + if (mpz_set_str(integer.get_mpz_t(), std::to_string(value).c_str(), 10) != + 0) + throw std::invalid_argument("invalid 64-bit rational integer"); + mpq_set_z(value_.get_mpq_t(), integer.get_mpz_t()); +} + +Rational::Rational(const Integer& value) : value_(value.value()) {} + +Rational::Rational(const std::string& value) : value_(value) +{ + value_.canonicalize(); +} + +Rational::Rational(const Integer& numerator, const Integer& denominator) +{ + if (denominator.value() == 0) + throw std::invalid_argument("a rational denominator cannot be zero"); + mpq_set_num(value_.get_mpq_t(), numerator.value().get_mpz_t()); + mpq_set_den(value_.get_mpq_t(), denominator.value().get_mpz_t()); + value_.canonicalize(); +} + +Rational::Rational(mpq_class value, int) : value_(std::move(value)) +{ + value_.canonicalize(); +} + +Rational Rational::fromRaw(const mpq_class& value) +{ + return Rational(value, 0); +} + +std::string Rational::toString() const +{ + return value_.get_str(); +} + +Rational Rational::floor() const +{ + mpz_class result; + mpz_fdiv_q(result.get_mpz_t(), mpq_numref(value_.get_mpq_t()), + mpq_denref(value_.get_mpq_t())); + return Rational::fromRaw(mpq_class(result)); +} + +Rational Rational::ceil() const +{ + mpz_class result; + mpz_cdiv_q(result.get_mpz_t(), mpq_numref(value_.get_mpq_t()), + mpq_denref(value_.get_mpq_t())); + return Rational::fromRaw(mpq_class(result)); +} + +Rational Rational::dividedByPowerOfTwo(unsigned exponent) const +{ + Rational result; + mpq_div_2exp(result.value_.get_mpq_t(), value_.get_mpq_t(), exponent); + return result; +} + +Rational& Rational::assignSum(const Rational& lhs, const Rational& rhs) +{ + mpq_add(value_.get_mpq_t(), lhs.value_.get_mpq_t(), + rhs.value_.get_mpq_t()); + return *this; +} + +Rational& Rational::divideByPowerOfTwoInPlace(unsigned exponent) +{ + mpq_div_2exp(value_.get_mpq_t(), value_.get_mpq_t(), exponent); + return *this; +} + +Rational& Rational::operator+=(const Rational& rhs) +{ + value_ += rhs.value_; + return *this; +} + +Rational& Rational::operator-=(const Rational& rhs) +{ + value_ -= rhs.value_; + return *this; +} + +Rational& Rational::operator*=(const Rational& rhs) +{ + value_ *= rhs.value_; + return *this; +} + +Rational& Rational::operator/=(const Rational& rhs) +{ + if (rhs.isZero()) + throw std::domain_error("division by zero rational"); + value_ /= rhs.value_; + return *this; +} + +Bound::Bound() = default; + +Bound::Bound(Kind kind, Rational value, bool strict) + : kind_(kind), value_(std::move(value)), + strict_(kind == Kind::Finite && strict) +{ +} + +Bound Bound::minusInfinity() +{ + return Bound(Kind::MinusInfinity, Rational(), false); +} + +Bound Bound::finite(Rational value, bool strict) +{ + return Bound(Kind::Finite, std::move(value), strict); +} + +Bound Bound::plusInfinity() +{ + return Bound(Kind::PlusInfinity, Rational(), false); +} + +const Rational& Bound::value() const +{ + if (!isFinite()) + throw std::logic_error("an infinite bound has no finite value"); + return value_; +} + +int Bound::compare(const Bound& lhs, const Bound& rhs) +{ + if (lhs.kind_ != rhs.kind_) + return static_cast(lhs.kind_) < static_cast(rhs.kind_) ? -1 + : 1; + if (!lhs.isFinite()) + return 0; + if (lhs.value_ < rhs.value_) + return -1; + if (rhs.value_ < lhs.value_) + return 1; + if (lhs.strict_ == rhs.strict_) + return 0; + return lhs.strict_ ? -1 : 1; +} + +Bound Bound::min(const Bound& lhs, const Bound& rhs) +{ + return lhs <= rhs ? lhs : rhs; +} + +Bound Bound::max(const Bound& lhs, const Bound& rhs) +{ + return lhs <= rhs ? rhs : lhs; +} + +Bound Bound::add(const Bound& lhs, const Bound& rhs) +{ + if ((lhs.isMinusInfinity() && rhs.isPlusInfinity()) || + (lhs.isPlusInfinity() && rhs.isMinusInfinity())) + throw std::domain_error("indeterminate sum of opposite infinities"); + if (lhs.isMinusInfinity() || rhs.isMinusInfinity()) + return minusInfinity(); + if (lhs.isPlusInfinity() || rhs.isPlusInfinity()) + return plusInfinity(); + return finite(lhs.value_ + rhs.value_, lhs.strict_ || rhs.strict_); +} + +Bound& Bound::assignSum(const Bound& lhs, const Bound& rhs) +{ + if ((lhs.isMinusInfinity() && rhs.isPlusInfinity()) || + (lhs.isPlusInfinity() && rhs.isMinusInfinity())) + throw std::domain_error("indeterminate sum of opposite infinities"); + if (lhs.isMinusInfinity() || rhs.isMinusInfinity()) + { + kind_ = Kind::MinusInfinity; + strict_ = false; + return *this; + } + if (lhs.isPlusInfinity() || rhs.isPlusInfinity()) + { + kind_ = Kind::PlusInfinity; + strict_ = false; + return *this; + } + kind_ = Kind::Finite; + value_.assignSum(lhs.value_, rhs.value_); + strict_ = lhs.strict_ || rhs.strict_; + return *this; +} + +Bound& Bound::divideByTwoInPlace() +{ + if (isFinite()) + value_.divideByPowerOfTwoInPlace(1); + return *this; +} + +Bound Bound::divideByTwo(const Bound& bound) +{ + if (!bound.isFinite()) + return bound; + return finite(bound.value_.dividedByPowerOfTwo(1), bound.strict_); +} + +Bound Bound::divideByPositive(const Bound& bound, const Rational& divisor) +{ + if (divisor.sign() <= 0) + throw std::invalid_argument("bound divisor must be positive"); + if (!bound.isFinite()) + return bound; + return finite(bound.value_ / divisor, bound.strict_); +} + +std::string Bound::toString() const +{ + if (isMinusInfinity()) + return "-inf"; + if (isPlusInfinity()) + return "+inf"; + return std::string(strict_ ? "<" : "<=") + value_.toString(); +} + +Interval::Interval() : lower_(Bound::minusInfinity()), + upper_(Bound::plusInfinity()) +{ +} + +Interval::Interval(Bound lower, Bound upper) + : lower_(std::move(lower)), upper_(std::move(upper)) +{ +} + +Interval Interval::top() +{ + return Interval(); +} + +Interval Interval::singleton(const Rational& value) +{ + return Interval(Bound::finite(value), Bound::finite(value)); +} + +bool Interval::isTop() const +{ + return lower_.isMinusInfinity() && upper_.isPlusInfinity(); +} + +bool Interval::isBottom() const +{ + if (lower_.isPlusInfinity() || upper_.isMinusInfinity()) + return true; + if (!lower_.isFinite() || !upper_.isFinite()) + return false; + if (upper_.value() < lower_.value()) + return true; + return upper_.value() == lower_.value() && + (lower_.isStrict() || upper_.isStrict()); +} + +std::string Interval::toString() const +{ + const char left = lower_.isStrict() ? '(' : '['; + const char right = upper_.isStrict() ? ')' : ']'; + const std::string lower = lower_.isMinusInfinity() + ? "-inf" + : lower_.isPlusInfinity() + ? "+inf" + : lower_.value().toString(); + const std::string upper = upper_.isPlusInfinity() + ? "+inf" + : upper_.isMinusInfinity() + ? "-inf" + : upper_.value().toString(); + return std::string(1, left) + lower + ", " + upper + + std::string(1, right); +} + +MpfrValue::MpfrValue(mpfr_prec_t precision) +{ + if (precision < MPFR_PREC_MIN || precision > MPFR_PREC_MAX) + throw std::invalid_argument("invalid MPFR precision"); + mpfr_init2(value_, precision); + mpfr_set_zero(value_, 1); +} + +MpfrValue::MpfrValue(const MpfrValue& rhs) +{ + mpfr_init2(value_, rhs.precision()); + mpfr_set(value_, rhs.value_, MPFR_RNDN); +} + +MpfrValue::MpfrValue(MpfrValue&& rhs) noexcept +{ + mpfr_init2(value_, rhs.precision()); + mpfr_swap(value_, rhs.value_); +} + +MpfrValue& MpfrValue::operator=(const MpfrValue& rhs) +{ + if (this == &rhs) + return *this; + mpfr_set_prec(value_, rhs.precision()); + mpfr_set(value_, rhs.value_, MPFR_RNDN); + return *this; +} + +MpfrValue& MpfrValue::operator=(MpfrValue&& rhs) noexcept +{ + if (this != &rhs) + mpfr_swap(value_, rhs.value_); + return *this; +} + +MpfrValue::~MpfrValue() +{ + mpfr_clear(value_); +} + +void MpfrValue::set(const Rational& value, mpfr_rnd_t rounding) +{ + mpfr_set_q(value_, value.value().get_mpq_t(), rounding); +} + +Rational MpfrValue::toRational() const +{ + if (!mpfr_number_p(value_)) + throw std::domain_error("a non-finite MPFR value is not rational"); + mpq_class result; + mpfr_get_q(result.get_mpq_t(), value_); + result.canonicalize(); + return Rational::fromRaw(result); +} + +namespace +{ +mpfr_rnd_t toMpfrRounding(RoundingMode mode) +{ + switch (mode) + { + case RoundingMode::NearestTiesToEven: + return MPFR_RNDN; + case RoundingMode::TowardZero: + return MPFR_RNDZ; + case RoundingMode::TowardPositive: + return MPFR_RNDU; + case RoundingMode::TowardNegative: + return MPFR_RNDD; + } + return MPFR_RNDN; +} +} // namespace + +Rational FloatSemantics::evaluate(BinaryOperation operation, + const Rational& lhs, const Rational& rhs, + unsigned significandBits, + RoundingMode rounding) +{ + if (significandBits < static_cast(MPFR_PREC_MIN)) + throw std::invalid_argument("invalid floating significand precision"); + + const mpfr_rnd_t mode = toMpfrRounding(rounding); + MpfrValue left(significandBits); + MpfrValue right(significandBits); + MpfrValue result(significandBits); + left.set(lhs, mode); + right.set(rhs, mode); + + switch (operation) + { + case BinaryOperation::Add: + mpfr_add(result.raw(), left.raw(), right.raw(), mode); + break; + case BinaryOperation::Subtract: + mpfr_sub(result.raw(), left.raw(), right.raw(), mode); + break; + case BinaryOperation::Multiply: + mpfr_mul(result.raw(), left.raw(), right.raw(), mode); + break; + case BinaryOperation::Divide: + mpfr_div(result.raw(), left.raw(), right.raw(), mode); + break; + } + return result.toRational(); +} + +Rational FloatSemantics::add(const Rational& lhs, const Rational& rhs, + unsigned significandBits, RoundingMode rounding) +{ + return evaluate(BinaryOperation::Add, lhs, rhs, significandBits, rounding); +} + +Rational FloatSemantics::subtract(const Rational& lhs, const Rational& rhs, + unsigned significandBits, + RoundingMode rounding) +{ + return evaluate(BinaryOperation::Subtract, lhs, rhs, significandBits, + rounding); +} + +Rational FloatSemantics::multiply(const Rational& lhs, const Rational& rhs, + unsigned significandBits, + RoundingMode rounding) +{ + return evaluate(BinaryOperation::Multiply, lhs, rhs, significandBits, + rounding); +} + +Rational FloatSemantics::divide(const Rational& lhs, const Rational& rhs, + unsigned significandBits, + RoundingMode rounding) +{ + return evaluate(BinaryOperation::Divide, lhs, rhs, significandBits, + rounding); +} + +} // namespace SVF::AbstractDomain diff --git a/svf/lib/AE/Core/NumericalDomain.cpp b/svf/lib/AE/Core/NumericalDomain.cpp new file mode 100644 index 0000000000..28e6a4949b --- /dev/null +++ b/svf/lib/AE/Core/NumericalDomain.cpp @@ -0,0 +1,1411 @@ +//===- NumericalDomain.cpp -- Shared numerical-state implementation ----===// + +#include "AE/Core/NumericalDomain.h" + +#include "AE/Core/BoxDomain.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace SVF::AbstractDomain +{ + +namespace +{ + +constexpr std::array RawMagic{'S', 'V', 'F', 'A', + 'D', 'R', 'A', 'W'}; +constexpr std::uint16_t RawVersion = 1; +constexpr std::uint32_t MaxCollectionEntries = 1U << 20; +constexpr std::uint64_t FnvOffset = 14695981039346656037ULL; +constexpr std::uint64_t FnvPrime = 1099511628211ULL; + +enum class DomainTag : std::uint8_t +{ + Box = 1 +}; + +std::uint64_t fnv1a(const std::uint8_t* data, std::size_t size) +{ + std::uint64_t result = FnvOffset; + for (std::size_t index = 0; index < size; ++index) + { + result ^= data[index]; + result *= FnvPrime; + } + return result; +} + +class Writer +{ +public: + void writeByte(std::uint8_t value) + { + bytes_.push_back(value); + } + + void writeU16(std::uint16_t value) + { + for (unsigned shift = 0; shift < 16; shift += 8) + writeByte(static_cast(value >> shift)); + } + + void writeU32(std::uint32_t value) + { + for (unsigned shift = 0; shift < 32; shift += 8) + writeByte(static_cast(value >> shift)); + } + + void writeU64(std::uint64_t value) + { + for (unsigned shift = 0; shift < 64; shift += 8) + writeByte(static_cast(value >> shift)); + } + + void writeString(const std::string& value) + { + if (value.size() > std::numeric_limits::max()) + throw std::length_error("raw state string is too large"); + writeU32(static_cast(value.size())); + bytes_.insert(bytes_.end(), value.begin(), value.end()); + } + + void writeMagic() + { + bytes_.insert(bytes_.end(), RawMagic.begin(), RawMagic.end()); + } + + NumericalState::RawBuffer finish() + { + const std::uint64_t checksum = fnv1a(bytes_.data(), bytes_.size()); + writeU64(checksum); + return std::move(bytes_); + } + +private: + NumericalState::RawBuffer bytes_; +}; + +std::uint64_t readTrailingU64(const NumericalState::RawBuffer& buffer) +{ + if (buffer.size() < sizeof(std::uint64_t)) + throw std::invalid_argument("raw state buffer is truncated"); + std::uint64_t value = 0; + const std::size_t offset = buffer.size() - sizeof(std::uint64_t); + for (unsigned index = 0; index < sizeof(std::uint64_t); ++index) + value |= static_cast(buffer[offset + index]) + << (8 * index); + return value; +} + +class Reader +{ +public: + explicit Reader(const NumericalState::RawBuffer& bytes) + : bytes_(bytes), limit_(checkedLimit(bytes)) + { + const std::uint64_t expected = readTrailingU64(bytes_); + const std::uint64_t actual = fnv1a(bytes_.data(), limit_); + if (actual != expected) + throw std::invalid_argument("raw state checksum mismatch"); + } + + void readMagic() + { + for (std::uint8_t expected : RawMagic) + { + if (readByte() != expected) + throw std::invalid_argument("raw state has invalid magic"); + } + } + + std::uint8_t readByte() + { + require(1); + return bytes_[position_++]; + } + + std::uint16_t readU16() + { + std::uint16_t value = 0; + for (unsigned index = 0; index < sizeof(value); ++index) + value |= static_cast(readByte()) << (8 * index); + return value; + } + + std::uint32_t readU32() + { + std::uint32_t value = 0; + for (unsigned index = 0; index < sizeof(value); ++index) + value |= static_cast(readByte()) << (8 * index); + return value; + } + + std::string readString() + { + const std::uint32_t size = readU32(); + require(size); + const auto begin = + bytes_.begin() + static_cast(position_); + position_ += size; + return std::string(begin, begin + size); + } + + bool empty() const + { + return position_ == limit_; + } + +private: + static std::size_t checkedLimit(const NumericalState::RawBuffer& bytes) + { + if (bytes.size() < + RawMagic.size() + sizeof(std::uint16_t) + 2 + sizeof(std::uint64_t)) + throw std::invalid_argument("raw state buffer is truncated"); + return bytes.size() - sizeof(std::uint64_t); + } + + void require(std::size_t size) const + { + if (size > limit_ - position_) + throw std::invalid_argument("raw state buffer is truncated"); + } + + const NumericalState::RawBuffer& bytes_; + std::size_t limit_; + std::size_t position_ = 0; +}; + +std::uint8_t encodeKind(NumericKind kind) +{ + switch (kind) + { + case NumericKind::Integer: + return 0; + case NumericKind::Real: + return 1; + case NumericKind::IEEEFloat: + return 2; + } + throw std::logic_error("unknown numerical kind"); +} + +NumericKind decodeKind(std::uint8_t value) +{ + switch (value) + { + case 0: + return NumericKind::Integer; + case 1: + return NumericKind::Real; + case 2: + return NumericKind::IEEEFloat; + default: + throw std::invalid_argument("raw state has invalid numerical kind"); + } +} + +std::uint8_t encodeConstraintKind(ConstraintKind kind) +{ + switch (kind) + { + case ConstraintKind::Equal: + return 0; + case ConstraintKind::NotEqual: + return 1; + case ConstraintKind::LessThan: + return 2; + case ConstraintKind::LessEqual: + return 3; + case ConstraintKind::GreaterThan: + return 4; + case ConstraintKind::GreaterEqual: + return 5; + } + throw std::logic_error("unknown linear constraint kind"); +} + +ConstraintKind decodeConstraintKind(std::uint8_t value) +{ + switch (value) + { + case 0: + return ConstraintKind::Equal; + case 1: + return ConstraintKind::NotEqual; + case 2: + return ConstraintKind::LessThan; + case 3: + return ConstraintKind::LessEqual; + case 4: + return ConstraintKind::GreaterThan; + case 5: + return ConstraintKind::GreaterEqual; + default: + throw std::invalid_argument("raw state has invalid constraint kind"); + } +} + +DomainTag domainTag(const NumericalState& state) +{ + if (state.isState()) + return DomainTag::Box; + throw std::invalid_argument( + "raw serialization does not support this domain"); +} + +std::uint8_t configurationFlags(const NumericalState& state, DomainTag tag) +{ + switch (tag) + { + case DomainTag::Box: { + const auto& box = static_cast(state); + return box.config().integerTightening ? 1U : 0U; + } + } + throw std::logic_error("unknown raw state domain tag"); +} + +void writeEnvironment(Writer& writer, const VariableEnvironment& environment) +{ + if (environment.size() > std::numeric_limits::max()) + throw std::length_error("raw state environment is too large"); + writer.writeU32(static_cast(environment.size())); + for (const VariableDeclaration& declaration : environment.variables()) + { + writer.writeU32(declaration.variable.id()); + writer.writeByte(encodeKind(declaration.type.kind)); + writer.writeU32(declaration.type.floatFormat.exponentBits); + writer.writeU32(declaration.type.floatFormat.significandBits); + writer.writeString(declaration.name); + } +} + +VariableEnvironment readEnvironment(Reader& reader) +{ + const std::uint32_t count = reader.readU32(); + if (count > MaxCollectionEntries) + throw std::invalid_argument("raw state environment is too large"); + std::vector declarations; + declarations.reserve(count); + for (std::uint32_t index = 0; index < count; ++index) + { + const Variable variable(reader.readU32()); + NumericType type; + type.kind = decodeKind(reader.readByte()); + type.floatFormat.exponentBits = reader.readU32(); + type.floatFormat.significandBits = reader.readU32(); + declarations.push_back({variable, type, reader.readString()}); + } + return VariableEnvironment(std::move(declarations)); +} + +void writeConstraints(Writer& writer, const LinearConstraintSet& constraints) +{ + if (constraints.size() > std::numeric_limits::max()) + throw std::length_error("raw state has too many constraints"); + writer.writeU32(static_cast(constraints.size())); + for (const LinearConstraint& constraint : constraints) + { + writer.writeByte(encodeConstraintKind(constraint.kind())); + writer.writeString(constraint.expression().constant().toString()); + const auto& terms = constraint.expression().terms(); + if (terms.size() > std::numeric_limits::max()) + throw std::length_error("raw state constraint has too many terms"); + writer.writeU32(static_cast(terms.size())); + for (const auto& [variable, coefficient] : terms) + { + writer.writeU32(variable.id()); + writer.writeString(coefficient.toString()); + } + } +} + +LinearConstraintSet canonicalConstraints(const NumericalState& state, DomainTag) +{ + return state.isBottom() ? LinearConstraintSet{} : state.toConstraints(); +} + +Rational readRational(Reader& reader) +{ + const std::string encoded = reader.readString(); + if (encoded.empty()) + throw std::invalid_argument("raw state contains an empty rational"); + try + { + return Rational(encoded); + } + catch (const std::exception&) + { + throw std::invalid_argument("raw state contains an invalid rational"); + } +} + +LinearConstraintSet readConstraints(Reader& reader, + const VariableEnvironment& environment) +{ + const std::uint32_t count = reader.readU32(); + if (count > MaxCollectionEntries) + throw std::invalid_argument("raw state has too many constraints"); + LinearConstraintSet constraints; + constraints.reserve(count); + for (std::uint32_t index = 0; index < count; ++index) + { + const ConstraintKind kind = decodeConstraintKind(reader.readByte()); + LinearExpression expression(readRational(reader)); + const std::uint32_t termCount = reader.readU32(); + if (termCount > environment.size()) + throw std::invalid_argument( + "raw state constraint has too many terms"); + std::set seen; + for (std::uint32_t term = 0; term < termCount; ++term) + { + const Variable variable(reader.readU32()); + if (!environment.contains(variable)) + throw std::invalid_argument( + "raw state constraint uses an unknown variable"); + if (!seen.insert(variable).second) + throw std::invalid_argument( + "raw state constraint repeats a variable"); + expression.setCoefficient(variable, readRational(reader)); + } + constraints.emplace_back(std::move(expression), kind); + } + return constraints; +} + +DomainTag decodeDomainTag(std::uint8_t value) +{ + switch (value) + { + case static_cast(DomainTag::Box): + return DomainTag::Box; + default: + throw std::invalid_argument("raw state has an unknown domain tag"); + } +} + +std::unique_ptr restore(DomainTag tag, std::uint8_t flags, + const VariableEnvironment& environment, + bool bottom, + const LinearConstraintSet& constraints) +{ + switch (tag) + { + case DomainTag::Box: { + if ((flags & ~1U) != 0) + throw std::invalid_argument("raw Box state has invalid flags"); + BoxConfig config; + config.integerTightening = (flags & 1U) != 0; + BoxState state = + bottom + ? BoxState::bottom(environment, config) + : BoxState::fromConstraints(environment, constraints, config); + return std::make_unique(std::move(state)); + } + } + throw std::logic_error("unknown raw state domain tag"); +} + +Interval bottomInterval() +{ + return Interval(Bound::plusInfinity(), Bound::minusInfinity()); +} + +bool singletonZero(const Interval& value) +{ + return value.lower().isFinite() && value.upper().isFinite() && + value.lower().value().isZero() && value.upper().value().isZero() && + !value.lower().isStrict() && !value.upper().isStrict(); +} + +std::optional singletonValue(const Interval& value) +{ + if (!value.lower().isFinite() || !value.upper().isFinite() || + value.lower().isStrict() || value.upper().isStrict() || + value.lower().value() != value.upper().value()) + return std::nullopt; + return value.lower().value(); +} + +Interval negateInterval(const Interval& value) +{ + if (value.isBottom()) + return bottomInterval(); + const Bound lower = + value.upper().isFinite() + ? Bound::finite(-value.upper().value(), value.upper().isStrict()) + : value.upper().isPlusInfinity() ? Bound::minusInfinity() + : Bound::plusInfinity(); + const Bound upper = + value.lower().isFinite() + ? Bound::finite(-value.lower().value(), value.lower().isStrict()) + : value.lower().isMinusInfinity() ? Bound::plusInfinity() + : Bound::minusInfinity(); + return Interval(lower, upper); +} + +Interval addIntervals(const Interval& lhs, const Interval& rhs) +{ + if (lhs.isBottom() || rhs.isBottom()) + return bottomInterval(); + Bound lower = Bound::minusInfinity(); + Bound upper = Bound::plusInfinity(); + if (lhs.lower().isFinite() && rhs.lower().isFinite()) + lower = Bound::finite(lhs.lower().value() + rhs.lower().value(), + lhs.lower().isStrict() || rhs.lower().isStrict()); + if (lhs.upper().isFinite() && rhs.upper().isFinite()) + upper = Bound::finite(lhs.upper().value() + rhs.upper().value(), + lhs.upper().isStrict() || rhs.upper().isStrict()); + return Interval(lower, upper); +} + +struct ExtendedRational +{ + /// -1 is minus infinity, 0 is finite, and 1 is plus infinity. + int infinity = 0; + Rational value; +}; + +ExtendedRational extended(const Bound& bound) +{ + if (bound.isMinusInfinity()) + return {-1, Rational()}; + if (bound.isPlusInfinity()) + return {1, Rational()}; + return {0, bound.value()}; +} + +int compareExtended(const ExtendedRational& lhs, const ExtendedRational& rhs) +{ + if (lhs.infinity != rhs.infinity) + return lhs.infinity < rhs.infinity ? -1 : 1; + if (lhs.infinity != 0) + return 0; + if (lhs.value < rhs.value) + return -1; + if (rhs.value < lhs.value) + return 1; + return 0; +} + +ExtendedRational multiplyExtended(const ExtendedRational& lhs, + const ExtendedRational& rhs) +{ + if (lhs.infinity == 0 && rhs.infinity == 0) + return {0, lhs.value * rhs.value}; + if ((lhs.infinity == 0 && lhs.value.isZero()) || + (rhs.infinity == 0 && rhs.value.isZero())) + return {0, Rational()}; + const int lhsSign = lhs.infinity != 0 ? lhs.infinity : lhs.value.sign(); + const int rhsSign = rhs.infinity != 0 ? rhs.infinity : rhs.value.sign(); + return {lhsSign * rhsSign, Rational()}; +} + +Bound extendedBound(const ExtendedRational& value) +{ + if (value.infinity < 0) + return Bound::minusInfinity(); + if (value.infinity > 0) + return Bound::plusInfinity(); + return Bound::finite(value.value); +} + +Interval multiplyIntervals(const Interval& lhs, const Interval& rhs) +{ + if (lhs.isBottom() || rhs.isBottom()) + return bottomInterval(); + if (singletonZero(lhs) || singletonZero(rhs)) + return Interval::singleton(Rational()); + const ExtendedRational lhsLower = extended(lhs.lower()); + const ExtendedRational lhsUpper = extended(lhs.upper()); + const ExtendedRational rhsLower = extended(rhs.lower()); + const ExtendedRational rhsUpper = extended(rhs.upper()); + const std::array products{ + multiplyExtended(lhsLower, rhsLower), + multiplyExtended(lhsLower, rhsUpper), + multiplyExtended(lhsUpper, rhsLower), + multiplyExtended(lhsUpper, rhsUpper)}; + ExtendedRational lower = products.front(); + ExtendedRational upper = products.front(); + for (const ExtendedRational& product : products) + { + if (compareExtended(product, lower) < 0) + lower = product; + if (compareExtended(upper, product) < 0) + upper = product; + } + return Interval(extendedBound(lower), extendedBound(upper)); +} + +bool containsZero(const Interval& value) +{ + if (value.isBottom()) + return false; + const bool aboveLower = + value.lower().isMinusInfinity() || + (value.lower().isFinite() && + (value.lower().value() < Rational() || + (value.lower().value().isZero() && !value.lower().isStrict()))); + const bool belowUpper = + value.upper().isPlusInfinity() || + (value.upper().isFinite() && + (Rational() < value.upper().value() || + (value.upper().value().isZero() && !value.upper().isStrict()))); + return aboveLower && belowUpper; +} + +Rational truncateTowardZero(const Rational& value) +{ + return value.sign() < 0 ? value.ceil() : value.floor(); +} + +Interval divideIntervals(const Interval& lhs, const Interval& rhs, + bool integerDivision) +{ + if (lhs.isBottom() || rhs.isBottom()) + return bottomInterval(); + if (containsZero(rhs)) + return Interval::top(); + + Bound reciprocalLower; + Bound reciprocalUpper; + const bool positive = + rhs.lower().isFinite() && rhs.lower().value().sign() >= 0; + if (positive) + { + reciprocalLower = + rhs.upper().isPlusInfinity() + ? Bound::finite(Rational()) + : Bound::finite(Rational(1) / rhs.upper().value()); + reciprocalUpper = + rhs.lower().value().isZero() + ? Bound::plusInfinity() + : Bound::finite(Rational(1) / rhs.lower().value()); + } + else + { + reciprocalLower = + rhs.upper().isFinite() && rhs.upper().value().isZero() + ? Bound::minusInfinity() + : Bound::finite(Rational(1) / rhs.upper().value()); + reciprocalUpper = + rhs.lower().isMinusInfinity() + ? Bound::finite(Rational()) + : Bound::finite(Rational(1) / rhs.lower().value()); + } + Interval result = + multiplyIntervals(lhs, Interval(reciprocalLower, reciprocalUpper)); + if (!integerDivision || result.isBottom()) + return result; + const Bound lower = + result.lower().isFinite() + ? Bound::finite(truncateTowardZero(result.lower().value())) + : result.lower(); + const Bound upper = + result.upper().isFinite() + ? Bound::finite(truncateTowardZero(result.upper().value())) + : result.upper(); + return Interval(lower, upper); +} + +Rational powerOfTwo(long exponent) +{ + mpq_class value(1); + if (exponent >= 0) + mpz_mul_2exp(value.get_num_mpz_t(), value.get_num_mpz_t(), exponent); + else + mpz_mul_2exp(value.get_den_mpz_t(), value.get_den_mpz_t(), -exponent); + value.canonicalize(); + return Rational::fromRaw(value); +} + +struct IEEEFormatBounds +{ + Rational maximum; + Rational minimumNormal; + Rational minimumSubnormal; +}; + +IEEEFormatBounds ieeeBounds(const FloatFormat& format) +{ + if (format.exponentBits < 2 || format.exponentBits >= 63 || + format.significandBits < 2) + throw std::invalid_argument("invalid IEEE floating format"); + const std::uint64_t bias = + (std::uint64_t(1) << (format.exponentBits - 1)) - 1; + const Rational maximum = + (Rational(2) - + powerOfTwo(1 - static_cast(format.significandBits))) * + powerOfTwo(static_cast(bias)); + const Rational minimumNormal = powerOfTwo(1 - static_cast(bias)); + const long minimumExponent = 1 - static_cast(bias) - + static_cast(format.significandBits - 1); + return {maximum, minimumNormal, powerOfTwo(minimumExponent)}; +} + +Rational roundIntegral(const Rational& value, RoundingMode rounding) +{ + const Rational lower = value.floor(); + const Rational upper = value.ceil(); + switch (rounding) + { + case RoundingMode::TowardZero: + return value.sign() < 0 ? upper : lower; + case RoundingMode::TowardPositive: + return upper; + case RoundingMode::TowardNegative: + return lower; + case RoundingMode::NearestTiesToEven: { + const Rational lowerDistance = value - lower; + const Rational upperDistance = upper - value; + if (lowerDistance < upperDistance) + return lower; + if (upperDistance < lowerDistance) + return upper; + return mpz_even_p(lower.value().get_num_mpz_t()) != 0 ? lower : upper; + } + } + return value; +} + +std::optional roundedIEEE(const Rational& value, + const FloatFormat& format, + RoundingMode rounding) +{ + const IEEEFormatBounds bounds = ieeeBounds(format); + if (bounds.maximum < value || value < -bounds.maximum) + return std::nullopt; + if (-bounds.minimumNormal < value && value < bounds.minimumNormal) + return roundIntegral(value / bounds.minimumSubnormal, rounding) * + bounds.minimumSubnormal; + return FloatSemantics::add(value, Rational(), format.significandBits, + rounding); +} + +Interval roundIEEEInterval(const Interval& value, const FloatFormat& format, + RoundingMode rounding) +{ + if (value.isBottom()) + return bottomInterval(); + if (!value.lower().isFinite() || !value.upper().isFinite()) + return Interval::top(); + const std::optional lower = + roundedIEEE(value.lower().value(), format, rounding); + const std::optional upper = + roundedIEEE(value.upper().value(), format, rounding); + if (!lower || !upper) + return Interval::top(); + return Interval(Bound::finite(*lower), Bound::finite(*upper)); +} + +Interval remainderIntervals(const Interval& lhs, const Interval& rhs) +{ + if (lhs.isBottom() || rhs.isBottom()) + return bottomInterval(); + if (containsZero(rhs)) + return Interval::top(); + if (singletonZero(lhs)) + return Interval::singleton(Rational()); + const std::optional lhsValue = singletonValue(lhs); + const std::optional rhsValue = singletonValue(rhs); + if (lhsValue && rhsValue) + { + const Rational quotient = truncateTowardZero(*lhsValue / *rhsValue); + return Interval::singleton(*lhsValue - quotient * *rhsValue); + } + std::optional magnitude; + if (rhs.lower().isFinite() && rhs.upper().isFinite()) + { + magnitude = rhs.lower().value().sign() < 0 ? -rhs.lower().value() + : rhs.lower().value(); + const Rational upperMagnitude = rhs.upper().value().sign() < 0 + ? -rhs.upper().value() + : rhs.upper().value(); + if (*magnitude < upperMagnitude) + magnitude = upperMagnitude; + } + if (lhs.lower().isFinite() && lhs.upper().isFinite()) + { + Rational lhsMagnitude = lhs.lower().value().sign() < 0 + ? -lhs.lower().value() + : lhs.lower().value(); + const Rational upperMagnitude = lhs.upper().value().sign() < 0 + ? -lhs.upper().value() + : lhs.upper().value(); + if (lhsMagnitude < upperMagnitude) + lhsMagnitude = upperMagnitude; + if (!magnitude || lhsMagnitude < *magnitude) + magnitude = lhsMagnitude; + } + if (!magnitude) + return Interval::top(); + if (magnitude->isZero()) + return Interval::top(); + Rational lower = -*magnitude; + Rational upper = *magnitude; + if (lhs.lower().isFinite() && lhs.lower().value().sign() >= 0) + lower = Rational(); + if (lhs.upper().isFinite() && lhs.upper().value().sign() <= 0) + upper = Rational(); + return Interval(Bound::finite(lower, true), Bound::finite(upper, true)); +} + +Interval squareRootInterval(const Interval& operand, const NumericType& type, + RoundingMode rounding) +{ + if (operand.isBottom()) + return bottomInterval(); + if (!operand.lower().isFinite() || operand.lower().value().sign() < 0) + return Interval::top(); + const unsigned precision = type.kind == NumericKind::IEEEFloat + ? type.floatFormat.significandBits + : 256U; + MpfrValue input(precision); + MpfrValue output(precision); + input.set(operand.lower().value(), MPFR_RNDD); + mpfr_sqrt(output.raw(), input.raw(), MPFR_RNDD); + const Rational lower = output.toRational(); + if (!operand.upper().isFinite()) + return Interval(Bound::finite(lower), Bound::plusInfinity()); + input.set(operand.upper().value(), MPFR_RNDU); + mpfr_sqrt(output.raw(), input.raw(), MPFR_RNDU); + Interval result(Bound::finite(lower), Bound::finite(output.toRational())); + return type.kind == NumericKind::IEEEFloat + ? roundIEEEInterval(result, type.floatFormat, rounding) + : result; +} + +Interval castInterval(const Interval& operand, const NumericType& type, + RoundingMode rounding) +{ + if (operand.isBottom()) + return bottomInterval(); + if (type.kind == NumericKind::Real) + return operand; + if (type.kind == NumericKind::IEEEFloat) + return roundIEEEInterval(operand, type.floatFormat, rounding); + if (!operand.lower().isFinite() || !operand.upper().isFinite()) + return Interval::top(); + Rational lower = truncateTowardZero(operand.lower().value()); + Rational upper = truncateTowardZero(operand.upper().value()); + if (upper < lower) + std::swap(lower, upper); + return Interval(Bound::finite(lower), Bound::finite(upper)); +} + +Interval evaluateTree(const NumericalState& state, + const TreeExpression& expression) +{ + switch (expression.kind()) + { + case TreeExpression::Kind::Constant: + return castInterval(Interval::singleton(expression.constant()), + expression.type(), expression.roundingMode()); + case TreeExpression::Kind::Variable: + if (!state.environment().contains(expression.variable())) + throw std::invalid_argument( + "tree expression uses an unknown variable"); + if (state.environment().typeOf(expression.variable()) != + expression.type()) + throw std::invalid_argument( + "tree variable type does not match environment"); + return state.bound(expression.variable()); + case TreeExpression::Kind::Unary: { + const Interval operand = evaluateTree(state, expression.lhs()); + switch (expression.unaryOperator()) + { + case UnaryOperator::Negate: + return expression.type().kind == NumericKind::IEEEFloat + ? roundIEEEInterval(negateInterval(operand), + expression.type().floatFormat, + expression.roundingMode()) + : negateInterval(operand); + case UnaryOperator::Cast: + return castInterval(operand, expression.type(), + expression.roundingMode()); + case UnaryOperator::SquareRoot: + return squareRootInterval(operand, expression.type(), + expression.roundingMode()); + } + } + case TreeExpression::Kind::Binary: { + const Interval lhs = evaluateTree(state, expression.lhs()); + const Interval rhs = evaluateTree(state, expression.rhs()); + Interval result; + switch (expression.binaryOperator()) + { + case BinaryOperator::Add: + result = addIntervals(lhs, rhs); + break; + case BinaryOperator::Subtract: + result = addIntervals(lhs, negateInterval(rhs)); + break; + case BinaryOperator::Multiply: + result = multiplyIntervals(lhs, rhs); + break; + case BinaryOperator::Divide: + result = divideIntervals( + lhs, rhs, expression.type().kind == NumericKind::Integer); + break; + case BinaryOperator::Remainder: + result = remainderIntervals(lhs, rhs); + break; + } + return expression.type().kind == NumericKind::IEEEFloat + ? roundIEEEInterval(result, expression.type().floatFormat, + expression.roundingMode()) + : result; + } + } + return Interval::top(); +} + +bool definitelyTrue(const Interval& value, ConstraintKind kind) +{ + if (value.isBottom()) + return true; + switch (kind) + { + case ConstraintKind::Equal: + return singletonZero(value); + case ConstraintKind::NotEqual: + return !containsZero(value); + case ConstraintKind::LessEqual: + return value.upper().isFinite() && value.upper().value() <= Rational(); + case ConstraintKind::LessThan: + return value.upper().isFinite() && + (value.upper().value() < Rational() || + (value.upper().value().isZero() && value.upper().isStrict())); + case ConstraintKind::GreaterEqual: + return value.lower().isFinite() && Rational() <= value.lower().value(); + case ConstraintKind::GreaterThan: + return value.lower().isFinite() && + (Rational() < value.lower().value() || + (value.lower().value().isZero() && value.lower().isStrict())); + } + return false; +} + +bool definitelyFalse(const Interval& value, ConstraintKind kind) +{ + if (value.isBottom()) + return false; + switch (kind) + { + case ConstraintKind::Equal: + return !containsZero(value); + case ConstraintKind::NotEqual: + return singletonZero(value); + case ConstraintKind::LessEqual: + return value.lower().isFinite() && + (Rational() < value.lower().value() || + (value.lower().value().isZero() && value.lower().isStrict())); + case ConstraintKind::LessThan: + return value.lower().isFinite() && Rational() <= value.lower().value(); + case ConstraintKind::GreaterEqual: + return value.upper().isFinite() && + (value.upper().value() < Rational() || + (value.upper().value().isZero() && value.upper().isStrict())); + case ConstraintKind::GreaterThan: + return value.upper().isFinite() && value.upper().value() <= Rational(); + } + return false; +} + +struct BilinearDecomposition +{ + LinearExpression affine; + LinearExpression lhs; + LinearExpression rhs; + Rational factor; + bool hasProduct = false; +}; + +bool affineConstant(const std::optional& expression, + Rational& value) +{ + if (!expression || !expression->terms().empty()) + return false; + value = expression->constant(); + return true; +} + +bool decomposeSingleProduct(const TreeExpression& expression, + const Rational& scale, + BilinearDecomposition& result) +{ + if (scale.isZero()) + return true; + if (const std::optional linear = expression.asLinear()) + { + result.affine += *linear * scale; + return true; + } + if (expression.kind() == TreeExpression::Kind::Unary && + expression.unaryOperator() == UnaryOperator::Negate) + return decomposeSingleProduct(expression.lhs(), -scale, result); + if (expression.kind() != TreeExpression::Kind::Binary) + return false; + + if (expression.binaryOperator() == BinaryOperator::Add || + expression.binaryOperator() == BinaryOperator::Subtract) + { + if (!decomposeSingleProduct(expression.lhs(), scale, result)) + return false; + const Rational rhsScale = + expression.binaryOperator() == BinaryOperator::Add ? scale : -scale; + return decomposeSingleProduct(expression.rhs(), rhsScale, result); + } + + const std::optional lhs = expression.lhs().asLinear(); + const std::optional rhs = expression.rhs().asLinear(); + if (expression.binaryOperator() == BinaryOperator::Multiply) + { + if (lhs && rhs) + { + if (result.hasProduct) + return false; + result.lhs = *lhs; + result.rhs = *rhs; + result.factor = scale; + result.hasProduct = true; + return true; + } + Rational constant; + if (affineConstant(lhs, constant)) + return decomposeSingleProduct(expression.rhs(), scale * constant, + result); + if (affineConstant(rhs, constant)) + return decomposeSingleProduct(expression.lhs(), scale * constant, + result); + return false; + } + if (expression.binaryOperator() == BinaryOperator::Divide) + { + Rational divisor; + return affineConstant(rhs, divisor) && !divisor.isZero() && + decomposeSingleProduct(expression.lhs(), scale / divisor, + result); + } + return false; +} + +} // namespace + +void NumericalState::assignParallel(const LinearAssignmentList& assignments) +{ + if (assignments.empty()) + { + recordOperation(OperationKind::Assignment, ApproximationKind::Exact, + true); + return; + } + + const VariableEnvironment originalEnvironment = environment(); + std::set targets; + for (const LinearAssignment& assignment : assignments) + { + if (!originalEnvironment.contains(assignment.target)) + throw std::invalid_argument( + "parallel assignment target is not in environment"); + if (!targets.insert(assignment.target).second) + throw std::invalid_argument( + "parallel assignment contains a duplicate target"); + for (const auto& [variable, coefficient] : + assignment.expression.terms()) + { + (void)coefficient; + if (!originalEnvironment.contains(variable)) + throw std::invalid_argument( + "parallel assignment expression uses an unknown variable"); + } + } + if (isBottom()) + { + recordOperation(OperationKind::Assignment, ApproximationKind::Exact, + true); + return; + } + + ApproximationKind approximation = ApproximationKind::Exact; + bool best = true; + std::string reason; + const auto includeLastOperation = [&]() { + const OperationMetadata& metadata = lastOperation(); + if (metadata.approximation == ApproximationKind::UnsupportedFallback || + (metadata.approximation == + ApproximationKind::SoundOverApproximation && + approximation == ApproximationKind::Exact)) + approximation = metadata.approximation; + best = best && metadata.best; + if (metadata.approximation != ApproximationKind::Exact && + !metadata.reason.empty()) + reason = metadata.reason; + }; + + std::uint64_t nextId = 0; + for (const VariableDeclaration& declaration : + originalEnvironment.variables()) + nextId = std::max( + nextId, static_cast(declaration.variable.id()) + 1); + if (nextId + assignments.size() > + static_cast(std::numeric_limits::max()) + + 1) + throw std::overflow_error( + "not enough temporary variable IDs for parallel assignment"); + + std::map oldValues; + std::vector temporaries; + temporaries.reserve(assignments.size()); + for (const LinearAssignment& assignment : assignments) + { + const Variable temporary(static_cast(nextId++)); + oldValues.emplace(assignment.target, temporary); + temporaries.push_back( + {temporary, originalEnvironment.typeOf(assignment.target), + "$parallel_old_" + originalEnvironment.nameOf(assignment.target)}); + } + + changeEnvironment(originalEnvironment.add(std::move(temporaries))); + for (const auto& [target, temporary] : oldValues) + { + assign(temporary, LinearExpression(target)); + includeLastOperation(); + } + + for (const LinearAssignment& assignment : assignments) + { + LinearExpression rewritten(assignment.expression.constant()); + for (const auto& [variable, coefficient] : + assignment.expression.terms()) + { + const auto old = oldValues.find(variable); + const Variable source = + old == oldValues.end() ? variable : old->second; + rewritten.setCoefficient(source, rewritten.coefficient(source) + + coefficient); + } + assign(assignment.target, rewritten); + includeLastOperation(); + } + changeEnvironment(originalEnvironment); + recordOperation(OperationKind::Assignment, approximation, best, + std::move(reason)); +} + +void NumericalState::assignParallel(const TreeAssignmentList& assignments) +{ + std::set targets; + LinearAssignmentList affine; + std::vector> intervalized; + affine.reserve(assignments.size()); + intervalized.reserve(assignments.size()); + for (const TreeAssignment& assignment : assignments) + { + if (!environment().contains(assignment.target)) + throw std::invalid_argument( + "parallel tree assignment target is not in environment"); + if (!targets.insert(assignment.target).second) + throw std::invalid_argument( + "parallel tree assignment contains a duplicate target"); + if (const std::optional linear = + assignment.expression.asLinear()) + affine.push_back({assignment.target, *linear}); + else + intervalized.emplace_back( + assignment.target, + evaluateTreeExpression(assignment.expression)); + } + + // Every nonlinear RHS interval was evaluated above from the common + // incoming state. Affine right-hand sides now run simultaneously, then the + // precomputed nonlinear intervals are committed without rereading targets. + assignParallel(affine); + for (const auto& [target, value] : intervalized) + assignInterval(target, value); + if (!intervalized.empty()) + recordOperation(OperationKind::Assignment, + ApproximationKind::SoundOverApproximation, false, + "parallel nonlinear or finite IEEE assignments were " + "interval-linearized"); +} + +void NumericalState::assumeAll(const LinearConstraintSet& constraints) +{ + if (constraints.empty()) + { + recordOperation(OperationKind::Assumption, ApproximationKind::Exact, + true); + return; + } + if (constraints.size() < 2) + { + for (const LinearConstraint& constraint : constraints) + assume(constraint); + return; + } + + // One pass per constraint and dimension bounds any propagation chain that + // terminates at all; the equivalence test stops earlier in practice, and + // immediately for a domain that is exact on linear constraints. + const std::size_t limit = + constraints.size() * (environment().size() + 1) + 1; + for (std::size_t pass = 0; pass < limit; ++pass) + { + const std::unique_ptr before = clone(); + for (const LinearConstraint& constraint : constraints) + assume(constraint); + if (isBottom() || isEquivalentTo(*before) == CheckResult::True) + return; + } +} + +NumericalState::RawBuffer NumericalState::serializeRaw() const +{ + Writer writer; + writer.writeMagic(); + writer.writeU16(RawVersion); + const DomainTag tag = domainTag(*this); + writer.writeByte(static_cast(tag)); + writer.writeByte(configurationFlags(*this, tag)); + writeEnvironment(writer, environment()); + writer.writeByte(isBottom() ? 1U : 0U); + writeConstraints(writer, canonicalConstraints(*this, tag)); + return writer.finish(); +} + +void NumericalState::substitute(Variable target, + const TreeExpression& expression) +{ + if (const std::optional linear = expression.asLinear()) + { + substitute(target, *linear); + return; + } + // Existentially eliminate the unknown post-value. No fact involving that + // value can soundly constrain the pre-state without nonlinear/machine + // semantics for the right-hand side. + forget(target); + recordOperation(OperationKind::Substitution, + ApproximationKind::UnsupportedFallback, false, + "nonlinear backward substitution projected the output"); +} + +void NumericalState::substituteParallel(const TreeAssignmentList& assignments) +{ + std::set targets; + LinearAssignmentList affine; + std::vector unsupported; + affine.reserve(assignments.size()); + unsupported.reserve(assignments.size()); + for (const TreeAssignment& assignment : assignments) + { + if (!environment().contains(assignment.target)) + throw std::invalid_argument( + "parallel substitution target is not in environment"); + if (!targets.insert(assignment.target).second) + throw std::invalid_argument( + "parallel substitution contains a duplicate target"); + if (const std::optional linear = + assignment.expression.asLinear()) + affine.push_back({assignment.target, *linear}); + else + unsupported.push_back(assignment.target); + } + + // Unknown output dimensions are existentially projected before the + // remaining simultaneous affine preimage is formed. + for (Variable target : unsupported) + forget(target); + substituteParallel(affine); + if (!unsupported.empty()) + recordOperation(OperationKind::Substitution, + ApproximationKind::UnsupportedFallback, false, + "parallel nonlinear backward substitution projected " + "unsupported outputs"); +} + +Interval NumericalState::bound(const TreeExpression& expression) const +{ + if (const std::optional linear = expression.asLinear()) + return bound(*linear); + if (isBottom()) + return bottomInterval(); + return evaluateTreeExpression(expression); +} + +Interval NumericalState::evaluateTreeExpression( + const TreeExpression& expression) const +{ + if (isBottom()) + return bottomInterval(); + return evaluateTree(*this, expression); +} + +LinearConstraintSet NumericalState::treeConstraintConsequences( + const TreeConstraint& constraint) const +{ + const Interval value = evaluateTreeExpression(constraint.expression()); + if (definitelyFalse(value, constraint.kind())) + return {LinearConstraint(LinearExpression(Rational(1)), + ConstraintKind::LessEqual)}; + if (definitelyTrue(value, constraint.kind())) + return {}; + + const TreeExpression& expression = constraint.expression(); + if (expression.type().kind == NumericKind::IEEEFloat) + return {}; + BilinearDecomposition decomposition; + if (!decomposeSingleProduct(expression, Rational(1), decomposition) || + !decomposition.hasProduct) + return {}; + const Interval lhsBounds = bound(decomposition.lhs); + const Interval rhsBounds = bound(decomposition.rhs); + if (!lhsBounds.lower().isFinite() || !lhsBounds.upper().isFinite() || + !rhsBounds.lower().isFinite() || !rhsBounds.upper().isFinite()) + return {}; + + const Rational& lx = lhsBounds.lower().value(); + const Rational& ux = lhsBounds.upper().value(); + const Rational& ly = rhsBounds.lower().value(); + const Rational& uy = rhsBounds.upper().value(); + std::vector productLowerForms{ + decomposition.rhs * lx + decomposition.lhs * ly - + LinearExpression(lx * ly), + decomposition.rhs * ux + decomposition.lhs * uy - + LinearExpression(ux * uy)}; + std::vector productUpperForms{ + decomposition.rhs * ux + decomposition.lhs * ly - + LinearExpression(ux * ly), + decomposition.rhs * lx + decomposition.lhs * uy - + LinearExpression(lx * uy)}; + if (decomposition.factor.sign() < 0) + std::swap(productLowerForms, productUpperForms); + std::vector lowerForms; + std::vector upperForms; + lowerForms.reserve(productLowerForms.size()); + upperForms.reserve(productUpperForms.size()); + for (const LinearExpression& form : productLowerForms) + lowerForms.push_back(decomposition.affine + + form * decomposition.factor); + for (const LinearExpression& form : productUpperForms) + upperForms.push_back(decomposition.affine + + form * decomposition.factor); + + LinearConstraintSet result; + const auto appendLower = [&](ConstraintKind kind) { + for (const LinearExpression& form : lowerForms) + result.emplace_back(form, kind); + }; + const auto appendUpper = [&](ConstraintKind kind) { + for (const LinearExpression& form : upperForms) + result.emplace_back(form, kind); + }; + switch (constraint.kind()) + { + case ConstraintKind::LessEqual: + appendLower(ConstraintKind::LessEqual); + break; + case ConstraintKind::LessThan: + appendLower(ConstraintKind::LessThan); + break; + case ConstraintKind::GreaterEqual: + appendUpper(ConstraintKind::GreaterEqual); + break; + case ConstraintKind::GreaterThan: + appendUpper(ConstraintKind::GreaterThan); + break; + case ConstraintKind::Equal: + appendLower(ConstraintKind::LessEqual); + appendUpper(ConstraintKind::GreaterEqual); + break; + case ConstraintKind::NotEqual: + break; + } + return result; +} + +void NumericalState::assignInterval(Variable target, const Interval& value) +{ + if (!environment().contains(target)) + throw std::invalid_argument("assignment target is not in environment"); + if (isBottom()) + return; + forget(target); + if (value.isBottom()) + { + assume(LinearConstraint(LinearExpression(Rational(1)), + ConstraintKind::LessEqual)); + return; + } + if (value.lower().isFinite()) + assume(LinearConstraint( + LinearExpression(target) - LinearExpression(value.lower().value()), + value.lower().isStrict() ? ConstraintKind::GreaterThan + : ConstraintKind::GreaterEqual)); + if (value.upper().isFinite()) + assume(LinearConstraint( + LinearExpression(target) - LinearExpression(value.upper().value()), + value.upper().isStrict() ? ConstraintKind::LessThan + : ConstraintKind::LessEqual)); +} + +void NumericalState::recordOperation(OperationKind operation, + ApproximationKind approximation, bool best, + std::string reason) const +{ + lastOperation_ = {operation, approximation, + approximation == ApproximationKind::Exact, best, + std::move(reason)}; +} + +VariableEnvironment NumericalState::unifyEnvironmentWith( + NumericalState& other, bool initializeNewVariablesToZero) +{ + const VariableEnvironment merged = environment().merge(other.environment()); + changeEnvironment(merged, initializeNewVariablesToZero); + other.changeEnvironment(merged, initializeNewVariablesToZero); + return merged; +} + +std::uint64_t NumericalState::hash() const +{ + const RawBuffer raw = serializeRaw(); + return readTrailingU64(raw); +} + +std::unique_ptr NumericalState::deserializeRaw( + const RawBuffer& buffer) +{ + Reader reader(buffer); + reader.readMagic(); + if (reader.readU16() != RawVersion) + throw std::invalid_argument("raw state has an unsupported version"); + const DomainTag tag = decodeDomainTag(reader.readByte()); + const std::uint8_t flags = reader.readByte(); + const VariableEnvironment environment = readEnvironment(reader); + const std::uint8_t bottomByte = reader.readByte(); + if (bottomByte > 1) + throw std::invalid_argument("raw state has an invalid bottom flag"); + const LinearConstraintSet constraints = + readConstraints(reader, environment); + if (!reader.empty()) + throw std::invalid_argument("raw state has trailing data"); + if (bottomByte != 0 && !constraints.empty()) + throw std::invalid_argument( + "raw bottom state unexpectedly contains constraints"); + return restore(tag, flags, environment, bottomByte != 0, constraints); +} + +} // namespace SVF::AbstractDomain diff --git a/svf/lib/AE/Core/RelationSolver.cpp b/svf/lib/AE/Core/RelationSolver.cpp deleted file mode 100644 index ab037cb5cf..0000000000 --- a/svf/lib/AE/Core/RelationSolver.cpp +++ /dev/null @@ -1,437 +0,0 @@ -//===- RelationSolver.cpp ----Relation Solver for Interval Domains-----------// -// -// SVF: Static Value-Flow Analysis -// -// Copyright (C) <2013-2022> -// - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// -//===----------------------------------------------------------------------===// -/* - * RelationSolver.cpp - * - * Created on: Aug 4, 2022 - * Author: Jiawei Ren - * - */ - -#include - -#include "AE/Core/AbstractState.h" -#include "AE/Core/RelationSolver.h" -#include "AE/Core/IntervalValue.h" -#include "Util/GeneralType.h" -#include "Util/Options.h" -#include "Util/SVFUtil.h" - -using namespace SVF; -using namespace SVFUtil; - -AbstractState RelationSolver::bilateral(const AbstractState&domain, const Z3Expr& phi, - u32_t descend_check) -{ - /// init variables - AbstractState upper = domain.top(); - AbstractState lower = domain.bottom(); - u32_t meets_in_a_row = 0; - z3::solver solver = Z3Expr::getSolver(); - z3::params p(Z3Expr::getContext()); - /// TODO: add option for timeout - p.set(":timeout", static_cast(600)); // in milliseconds - solver.set(p); - AbstractState consequence; - - /// start processing - while (lower != upper) - { - if (meets_in_a_row == descend_check) - { - consequence = lower; - } - else - { - consequence = abstract_consequence(lower, upper, domain); - } - /// compute domain.model_and(phi, domain.logic_not(domain.gamma_hat(consequence))) - Z3Expr rhs = !(gamma_hat(consequence, domain)); - solver.push(); - solver.add(phi.getExpr() && rhs.getExpr()); - Map solution; - z3::check_result checkRes = solver.check(); - /// find any solution, which is sat - if (checkRes == z3::sat) - { - z3::model m = solver.get_model(); - for (u32_t i = 0; i < m.size(); i++) - { - z3::func_decl v = m[i]; - // assert(v.arity() == 0); - if (v.arity() != 0) - continue; - solution.emplace(std::stoi(v.name().str()), - m.get_const_interp(v).get_numeral_int()); - } - for (const auto& item : domain.getVarToVal()) - { - if (solution.find(item.first) == solution.end()) - { - solution.emplace(item.first, 0); - } - } - solver.pop(); - AbstractState newLower = domain.bottom(); - newLower.joinWith(lower); - AbstractState rhs = beta(solution, domain); - newLower.joinWith(rhs); - lower = newLower; - meets_in_a_row = 0; - } - else /// unknown or unsat - { - solver.pop(); - if (checkRes == z3::unknown) - { - /// for timeout reason return upper - if (solver.reason_unknown() == "timeout") - return upper; - } - AbstractState newUpper = domain.top(); - newUpper.meetWith(upper); - newUpper.meetWith(consequence); - upper = newUpper; - meets_in_a_row += 1; - } - } - return upper; -} - -AbstractState RelationSolver::RSY(const AbstractState& domain, const Z3Expr& phi) -{ - AbstractState lower = domain.bottom(); - z3::solver& solver = Z3Expr::getSolver(); - z3::params p(Z3Expr::getContext()); - /// TODO: add option for timeout - p.set(":timeout", static_cast(600)); // in milliseconds - solver.set(p); - while (1) - { - Z3Expr rhs = !(gamma_hat(lower, domain)); - solver.push(); - solver.add(phi.getExpr() && rhs.getExpr()); - Map solution; - z3::check_result checkRes = solver.check(); - /// find any solution, which is sat - if (checkRes == z3::sat) - { - z3::model m = solver.get_model(); - for (u32_t i = 0; i < m.size(); i++) - { - z3::func_decl v = m[i]; - if (v.arity() != 0) - continue; - - solution.emplace(std::stoi(v.name().str()), - m.get_const_interp(v).get_numeral_int()); - } - for (const auto& item : domain.getVarToVal()) - { - if (solution.find(item.first) == solution.end()) - { - solution.emplace(item.first, 0); - } - } - solver.pop(); - AbstractState newLower = domain.bottom(); - newLower.joinWith(lower); - newLower.joinWith(beta(solution, domain)); - lower = newLower; - } - else /// unknown or unsat - { - solver.pop(); - if (checkRes == z3::unknown) - { - /// for timeout reason return upper - if (solver.reason_unknown() == "timeout") - return domain.top(); - } - break; - } - } - return lower; -} - -AbstractState RelationSolver::abstract_consequence( - const AbstractState& lower, const AbstractState& upper, const AbstractState& domain) const -{ - /*Returns the "abstract consequence" of lower and upper. - - The abstract consequence must be a superset of lower and *NOT* a - superset of upper. - - Note that this is a fairly "simple" abstract consequence, in that it - sets only one variable to a non-top interval. This improves performance - of the SMT solver in many cases. In certain cases, other choices for - the abstract consequence will lead to better algorithm performance.*/ - - for (auto it = domain.getVarToVal().begin(); - it != domain.getVarToVal().end(); ++it) - /// for variable in self.variables: - { - AbstractState proposed = domain.top(); /// proposed = self.top.copy() - proposed[it->first] = lower[it->first].getInterval(); - /// proposed.set_interval(variable, lower.interval_of(variable)) - /// proposed._locToItvVal - if (!(proposed >= upper)) /// if not proposed >= upper: - { - return proposed; /// return proposed - } - } - return lower; /// return lower.copy() -} - -Z3Expr RelationSolver::gamma_hat(const AbstractState& exeState) const -{ - Z3Expr res(Z3Expr::getContext().bool_val(true)); - for (auto& item : exeState.getVarToVal()) - { - IntervalValue interval = item.second.getInterval(); - if (interval.isBottom()) - return Z3Expr::getContext().bool_val(false); - if (interval.isTop()) - continue; - Z3Expr v = toIntZ3Expr(item.first); - res = (res && v >= (int)interval.lb().getNumeral() && - v <= (int)interval.ub().getNumeral()).simplify(); - } - return res; -} - -Z3Expr RelationSolver::gamma_hat(const AbstractState& alpha, - const AbstractState& exeState) const -{ - Z3Expr res(Z3Expr::getContext().bool_val(true)); - for (auto& item : exeState.getVarToVal()) - { - IntervalValue interval = alpha[item.first].getInterval(); - if (interval.isBottom()) - return Z3Expr::getContext().bool_val(false); - if (interval.isTop()) - continue; - Z3Expr v = toIntZ3Expr(item.first); - res = (res && v >= (int)interval.lb().getNumeral() && - v <= (int)interval.ub().getNumeral()).simplify(); - } - return res; -} - -Z3Expr RelationSolver::gamma_hat(u32_t id, const AbstractState& exeState) const -{ - auto it = exeState.getVarToVal().find(id); - assert(it != exeState.getVarToVal().end() && "id not in varToVal?"); - Z3Expr v = toIntZ3Expr(id); - // Z3Expr v = Z3Expr::getContext().int_const(std::to_string(id).c_str()); - Z3Expr res = (v >= (int)it->second.getInterval().lb().getNumeral() && - v <= (int)it->second.getInterval().ub().getNumeral()); - return res; -} - -AbstractState RelationSolver::beta(const Map& sigma, - const AbstractState& exeState) const -{ - AbstractState res; - for (const auto& item : exeState.getVarToVal()) - { - res[item.first] = IntervalValue( - sigma.at(item.first), sigma.at(item.first)); - } - return res; -} - -void RelationSolver::updateMap(Map& map, u32_t key, const s32_t& value) -{ - auto it = map.find(key); - if (it == map.end()) - { - map.emplace(key, value); - } - else - { - it->second = value; - } -} - -AbstractState RelationSolver::BS(const AbstractState& domain, const Z3Expr &phi) -{ - /// because key of _varToItvVal is u32_t, -key may out of range for int - /// so we do key + bias for -key - u32_t bias = 0; - s32_t infinity = INT32_MAX/2 - 1; - - // int infinity = (INT32_MAX) - 1; - // int infinity = 20; - Map ret; - Map low_values, high_values; - Z3Expr new_phi = phi; - /// init low, ret, high - for (const auto& item: domain.getVarToVal()) - { - IntervalValue interval = item.second.getInterval(); - updateMap(ret, item.first, interval.ub().getIntNumeral()); - if (interval.lb().is_minus_infinity()) - updateMap(low_values, item.first, -infinity); - else - updateMap(low_values, item.first, interval.lb().getIntNumeral()); - if (interval.ub().is_plus_infinity()) - updateMap(high_values, item.first, infinity); - else - updateMap(high_values, item.first, interval.ub().getIntNumeral()); - if (item.first > bias) - bias = item.first + 1; - } - for (const auto& item: domain.getVarToVal()) - { - /// init objects -x - IntervalValue interval = item.second.getInterval(); - u32_t reverse_key = item.first + bias; - updateMap(ret, reverse_key, -interval.lb().getIntNumeral()); - if (interval.ub().is_plus_infinity()) - updateMap(low_values, reverse_key, -infinity); - else - updateMap(low_values, reverse_key, -interval.ub().getIntNumeral()); - if (interval.lb().is_minus_infinity()) - updateMap(high_values, reverse_key, infinity); - else - updateMap(high_values, reverse_key, -interval.lb().getIntNumeral()); - /// add a relation that x == -(x+bias) - new_phi = (new_phi && (toIntZ3Expr(reverse_key) == -1 * toIntZ3Expr(item.first))); - } - /// optimize each object - BoxedOptSolver(new_phi.simplify(), ret, low_values, high_values); - /// fill in the return values - AbstractState retInv; - for (const auto& item: ret) - { - if (item.first >= bias) - { - if (!retInv.inVarToValTable(item.first-bias)) - retInv[item.first-bias] = IntervalValue::top(); - - if (item.second == (infinity)) - retInv[item.first - bias] = IntervalValue(BoundedInt::minus_infinity(), - retInv[item.first - bias].getInterval().ub()); - else - retInv[item.first - bias] = IntervalValue(float(-item.second), retInv[item.first - bias].getInterval().ub()); - - } - else - { - if (item.second == (infinity)) - retInv[item.first] = IntervalValue(retInv[item.first].getInterval().lb(), - BoundedInt::plus_infinity()); - else - retInv[item.first] = IntervalValue(retInv[item.first].getInterval().lb(), float(item.second)); - } - } - return retInv; -} - -Map RelationSolver::BoxedOptSolver(const Z3Expr& phi, Map& ret, Map& low_values, Map& high_values) -{ - /// this is the S in the original paper - Map L_phi; - Map mid_values; - while (1) - { - L_phi.clear(); - for (const auto& item : ret) - { - Z3Expr v = toIntZ3Expr(item.first); - if (low_values.at(item.first) <= (high_values.at(item.first))) - { - s32_t mid = (low_values.at(item.first) + (high_values.at(item.first) - low_values.at(item.first)) / 2); - updateMap(mid_values, item.first, mid); - Z3Expr expr = (toIntVal(mid) <= v && v <= toIntVal(high_values.at(item.first))); - L_phi[item.first] = expr; - } - } - if (L_phi.empty()) - break; - else - decide_cpa_ext(phi, L_phi, mid_values, ret, low_values, high_values); - } - return ret; -} - - -void RelationSolver::decide_cpa_ext(const Z3Expr& phi, - Map& L_phi, - Map& mid_values, - Map& ret, - Map& low_values, - Map& high_values) -{ - while (1) - { - Z3Expr join_expr(Z3Expr::getContext().bool_val(false)); - for (const auto& item : L_phi) - join_expr = (join_expr || item.second); - join_expr = (join_expr && phi).simplify(); - z3::solver& solver = Z3Expr::getSolver(); - solver.push(); - solver.add(join_expr.getExpr()); - Map solution; - z3::check_result checkRes = solver.check(); - /// find any solution, which is sat - if (checkRes == z3::sat) - { - z3::model m = solver.get_model(); - solver.pop(); - for(const auto & item : L_phi) - { - u32_t id = item.first; - int value = m.eval(toIntZ3Expr(id).getExpr()).get_numeral_int(); - // int value = m.eval(Z3Expr::getContext().int_const(std::to_string(id).c_str())).get_numeral_int(); - /// id is the var id, value is the solution found for var_id - /// add a relation to check if the solution meets phi_id - Z3Expr expr = (item.second && toIntZ3Expr(id) == value); - solver.push(); - solver.add(expr.getExpr()); - // solution meets phi_id - if (solver.check() == z3::sat) - { - updateMap(ret, id, (value)); - updateMap(low_values, id, ret.at(id) + 1); - - s32_t mid = (low_values.at(id) + high_values.at(id) + 1) / 2; - updateMap(mid_values, id, mid); - Z3Expr v = toIntZ3Expr(id); - // Z3Expr v = Z3Expr::getContext().int_const(std::to_string(id).c_str()); - Z3Expr expr = (toIntVal(mid_values.at(id)) <= v && v <= toIntVal(high_values.at(id))); - L_phi[id] = expr; - } - solver.pop(); - } - } - else /// unknown or unsat, we consider unknown as unsat - { - solver.pop(); - for (const auto& item : L_phi) - high_values.at(item.first) = mid_values.at(item.first) - 1; - return; - } - } - -} diff --git a/svf/lib/AE/Core/VariableEnvironment.cpp b/svf/lib/AE/Core/VariableEnvironment.cpp new file mode 100644 index 0000000000..689da155dc --- /dev/null +++ b/svf/lib/AE/Core/VariableEnvironment.cpp @@ -0,0 +1,153 @@ +//===- VariableEnvironment.cpp -- Variables and dimensions --------------===// + +#include "AE/Core/VariableEnvironment.h" + +#include +#include +#include +#include + +namespace SVF::AbstractDomain +{ + +struct VariableEnvironment::Data +{ + explicit Data(std::vector declarations) + : variables(std::move(declarations)) + { + std::sort(variables.begin(), variables.end(), + [](const VariableDeclaration& lhs, + const VariableDeclaration& rhs) + { return lhs.variable < rhs.variable; }); + for (Dimension dimension = 1; dimension < variables.size(); ++dimension) + { + if (variables[dimension - 1].variable == + variables[dimension].variable) + throw std::invalid_argument( + "duplicate variable in relational environment"); + } + } + + std::vector variables; +}; + +VariableEnvironment::VariableEnvironment() : data_(std::make_shared( + std::vector{})) +{ +} +VariableEnvironment::VariableEnvironment(std::vector variables) + : data_(std::make_shared(std::move(variables))) +{ +} + +std::size_t VariableEnvironment::size() const +{ + return data_->variables.size(); +} + +bool VariableEnvironment::contains(Variable variable) const +{ + const auto iterator = std::lower_bound( + data_->variables.begin(), data_->variables.end(), variable, + [](const VariableDeclaration& declaration, Variable candidate) + { return declaration.variable < candidate; }); + return iterator != data_->variables.end() && + iterator->variable == variable; +} + +Dimension VariableEnvironment::dimensionOf(Variable variable) const +{ + const auto iterator = std::lower_bound( + data_->variables.begin(), data_->variables.end(), variable, + [](const VariableDeclaration& declaration, Variable candidate) + { return declaration.variable < candidate; }); + if (iterator == data_->variables.end() || iterator->variable != variable) + throw std::out_of_range("variable is not in relational environment"); + return static_cast(iterator - data_->variables.begin()); +} + +Variable VariableEnvironment::variableOf(Dimension dimension) const +{ + if (dimension >= size()) + throw std::out_of_range("invalid relational dimension"); + return data_->variables[dimension].variable; +} + +const NumericType& VariableEnvironment::typeOf(Variable variable) const +{ + return data_->variables[dimensionOf(variable)].type; +} + +const std::string& VariableEnvironment::nameOf(Variable variable) const +{ + return data_->variables[dimensionOf(variable)].name; +} + +const std::vector& VariableEnvironment::variables() const +{ + return data_->variables; +} + +VariableEnvironment VariableEnvironment::add( + std::vector declarations) const +{ + std::vector combined = data_->variables; + combined.insert(combined.end(), + std::make_move_iterator(declarations.begin()), + std::make_move_iterator(declarations.end())); + return VariableEnvironment(std::move(combined)); +} + +VariableEnvironment VariableEnvironment::remove(const std::vector& removed) const +{ + std::vector remaining; + remaining.reserve(size()); + for (const VariableDeclaration& declaration : data_->variables) + { + if (std::find(removed.begin(), removed.end(), declaration.variable) == + removed.end()) + remaining.push_back(declaration); + } + return VariableEnvironment(std::move(remaining)); +} + +VariableEnvironment VariableEnvironment::merge(const VariableEnvironment& other) const +{ + std::map combined; + for (const VariableDeclaration& declaration : data_->variables) + combined.emplace(declaration.variable, declaration); + for (const VariableDeclaration& declaration : other.data_->variables) + { + const auto [it, inserted] = + combined.emplace(declaration.variable, declaration); + if (!inserted && it->second.type != declaration.type) + throw std::invalid_argument( + "cannot merge relational environments with conflicting types"); + } + + std::vector declarations; + declarations.reserve(combined.size()); + for (auto& entry : combined) + declarations.push_back(std::move(entry.second)); + return VariableEnvironment(std::move(declarations)); +} + +bool operator==(const VariableEnvironment& lhs, + const VariableEnvironment& rhs) +{ + if (lhs.data_ == rhs.data_) + return true; + if (lhs.data_->variables.size() != rhs.data_->variables.size()) + return false; + for (std::size_t index = 0; index < lhs.data_->variables.size(); ++index) + { + const VariableDeclaration& left = lhs.data_->variables[index]; + const VariableDeclaration& right = rhs.data_->variables[index]; + if (left.variable != right.variable || left.type != right.type || + left.name != right.name) + return false; + } + return true; +} + +} // namespace SVF::AbstractDomain diff --git a/svf/lib/AE/Svfexe/AEDetector.cpp b/svf/lib/AE/Svfexe/AEDetector.cpp index 7b611085c8..24ee569ecb 100644 --- a/svf/lib/AE/Svfexe/AEDetector.cpp +++ b/svf/lib/AE/Svfexe/AEDetector.cpp @@ -63,7 +63,7 @@ void BufOverflowDetector::detect(const ICFGNode* node) const AddressValue& objAddrs = rhsVal.getAddrs(); for (const auto& addr : objAddrs) { - NodeID objId = ae.getAbsState(node).getIDFromAddr(addr); + NodeID objId = AbstractInterpretation::objectIdFromAddress(addr); u32_t size = 0; // like `int arr[10]` which has constant size before runtime if (svfir->getBaseObject(objId)->isConstantByteSize()) @@ -336,12 +336,11 @@ IntervalValue BufOverflowDetector::getAccessOffset(SVF::NodeID objId, const SVF: void BufOverflowDetector::updateGepObjOffsetFromBase(const SVF::ICFGNode* node, SVF::AddressValue gepAddrs, SVF::AddressValue objAddrs, SVF::IntervalValue offset) { SVFIR* svfir = PAG::getPAG(); - auto& ae = AbstractInterpretation::getAEInstance(); - const AbstractState& as = ae.getAbsState(node); + (void)node; for (const auto& objAddr : objAddrs) { - NodeID objId = as.getIDFromAddr(objAddr); + NodeID objId = AbstractInterpretation::objectIdFromAddress(objAddr); auto obj = svfir->getSVFVar(objId); if (SVFUtil::isa(obj)) @@ -351,14 +350,14 @@ void BufOverflowDetector::updateGepObjOffsetFromBase(const SVF::ICFGNode* node, // we write key value pair {gep, 4} for (const auto& gepAddr : gepAddrs) { - NodeID gepObj = as.getIDFromAddr(gepAddr); + NodeID gepObj = AbstractInterpretation::objectIdFromAddress(gepAddr); if (const GepObjVar* gepObjVar = SVFUtil::dyn_cast(svfir->getSVFVar(gepObj))) { addToGepObjOffsetFromBase(gepObjVar, offset); } else { - assert(AbstractState::isBlackHoleObjAddr(gepAddr) && "GEP object is neither a GepObjVar nor an invalid memory address"); + assert(gepAddr == BlackHoleObjAddr && "GEP object is neither a GepObjVar nor an invalid memory address"); } } } @@ -370,7 +369,7 @@ void BufOverflowDetector::updateGepObjOffsetFromBase(const SVF::ICFGNode* node, const GepObjVar* objVar = SVFUtil::cast(obj); for (const auto& gepAddr : gepAddrs) { - NodeID gepObj = as.getIDFromAddr(gepAddr); + NodeID gepObj = AbstractInterpretation::objectIdFromAddress(gepAddr); if (const GepObjVar* gepObjVar = SVFUtil::dyn_cast(svfir->getSVFVar(gepObj))) { if (hasGepObjOffsetFromBase(objVar)) @@ -389,7 +388,7 @@ void BufOverflowDetector::updateGepObjOffsetFromBase(const SVF::ICFGNode* node, } else { - assert(AbstractState::isBlackHoleObjAddr(gepAddr) && "GEP object is neither a GepObjVar nor an invalid memory address"); + assert(gepAddr == BlackHoleObjAddr && "GEP object is neither a GepObjVar nor an invalid memory address"); } } } @@ -470,7 +469,7 @@ bool BufOverflowDetector::canSafelyAccessMemory(const SVF::ValVar* value, const } for (const auto& addr : ptrVal.getAddrs()) { - NodeID objId = ae.getAbsState(node).getIDFromAddr(addr); + NodeID objId = AbstractInterpretation::objectIdFromAddress(addr); u32_t size = 0; // if the object is a constant size object, get the size directly if (svfir->getBaseObject(objId)->isConstantByteSize()) @@ -675,13 +674,13 @@ bool NullptrDerefDetector::canSafelyDerefPtr(const ValVar* value, const ICFGNode for (const auto &addr: AbsVal.getAddrs()) { // if the addr itself is invalid mem, report unsafe - if (AbstractState::isBlackHoleObjAddr(addr)) + if (addr == BlackHoleObjAddr) return false; // if nullptr is detected, return unsafe - else if (AbstractState::isNullMem(addr)) + else if (addr == NullMemAddr) return false; // if addr is labeled freed mem, report unsafe - else if (ae.getAbsState(node).isFreedMem(addr)) + else if (ae.isFreedMemory(addr, node)) return false; } return true; diff --git a/svf/lib/AE/Svfexe/AELoopRecursion.cpp b/svf/lib/AE/Svfexe/AELoopRecursion.cpp index 075a9d65fe..4c9ba341a7 100644 --- a/svf/lib/AE/Svfexe/AELoopRecursion.cpp +++ b/svf/lib/AE/Svfexe/AELoopRecursion.cpp @@ -23,18 +23,15 @@ // Loop and recursion handling factored out of AbstractInterpretation.cpp. // Contains: // * The widen/narrow fixpoint driver (handleLoopOrRecursion) -// * The dense base cycle helpers (getFullCycleHeadState / -// widenCycleState / narrowCycleState — semi-sparse overrides live in -// SparseAbstractInterpretation.cpp) // * Recursion-specific helpers (isRecursiveFun, isRecursiveCallSite, // skipRecursiveCall, skipRecursionWithTop, shouldApplyNarrowing) // -#include "AE/Svfexe/AbstractInterpretation.h" #include "AE/Svfexe/AEWTO.h" +#include "AE/Svfexe/AbstractInterpretation.h" #include "SVFIR/SVFIR.h" -#include "WPA/Andersen.h" #include "Util/Options.h" +#include "WPA/Andersen.h" using namespace SVF; using namespace SVFUtil; @@ -51,28 +48,31 @@ bool AbstractInterpretation::isRecursiveFun(const FunObjVar* fun) /// TOP mode for recursive calls: skip the function body entirely and /// conservatively set all reachable stores and the return value to TOP. -void AbstractInterpretation::skipRecursionWithTop(const CallICFGNode *callNode) +void AbstractInterpretation::skipRecursionWithTop(const CallICFGNode* callNode) { - const RetICFGNode *retNode = callNode->getRetICFGNode(); + const RetICFGNode* retNode = callNode->getRetICFGNode(); // 1. Set return value to TOP if (retNode->getSVFStmts().size() > 0) { - if (const RetPE *retPE = SVFUtil::dyn_cast(*retNode->getSVFStmts().begin())) + if (const RetPE* retPE = + SVFUtil::dyn_cast(*retNode->getSVFStmts().begin())) { if (!retPE->getLHSVar()->isPointer() && - !retPE->getLHSVar()->isConstDataOrAggDataButNotNullPtr()) - updateAbsValue(retPE->getLHSVar(), IntervalValue::top(), callNode); + !retPE->getLHSVar()->isConstDataOrAggDataButNotNullPtr()) + updateAbsValue(retPE->getLHSVar(), IntervalValue::top(), + callNode); } } // 2. Set all stores in callee's reachable BBs to TOP if (retNode->getOutEdges().size() > 1) { - updateAbsState(retNode, getAbsState(callNode)); + copyAbstractState(callNode, retNode); return; } - for (const SVFBasicBlock* bb : callNode->getCalledFunction()->getReachableBBs()) + for (const SVFBasicBlock* bb : + callNode->getCalledFunction()->getReachableBBs()) { for (const ICFGNode* node : bb->getICFGNodeList()) { @@ -81,14 +81,18 @@ void AbstractInterpretation::skipRecursionWithTop(const CallICFGNode *callNode) if (const StoreStmt* store = SVFUtil::dyn_cast(stmt)) { const SVFVar* rhsVar = store->getRHSVar(); - if (!rhsVar->isPointer() && !rhsVar->isConstDataOrAggDataButNotNullPtr()) + if (!rhsVar->isPointer() && + !rhsVar->isConstDataOrAggDataButNotNullPtr()) { - const AbstractValue& addrs = getAbsValue(store->getLHSVar(), callNode); + const AbstractValue& addrs = + getAbsValue(store->getLHSVar(), callNode); if (addrs.isAddr()) { - AbstractState& as = getAbsState(callNode); for (const auto& addr : addrs.getAddrs()) - as.store(addr, IntervalValue::top()); + { + updateMemoryValue(addr, IntervalValue::top(), + callNode); + } } } } @@ -97,18 +101,21 @@ void AbstractInterpretation::skipRecursionWithTop(const CallICFGNode *callNode) } // 3. Copy callNode's state to retNode - updateAbsState(retNode, getAbsState(callNode)); + copyAbstractState(callNode, retNode); } -/// Check if caller and callee are in the same CallGraph SCC (i.e. a recursive callsite) +/// Check if caller and callee are in the same CallGraph SCC (i.e. a recursive +/// callsite) bool AbstractInterpretation::isRecursiveCallSite(const CallICFGNode* callNode, - const FunObjVar* callee) + const FunObjVar* callee) { const FunObjVar* caller = callNode->getCaller(); - return preAnalysis->getPointerAnalysis()->inSameCallGraphSCC(caller, callee); + return preAnalysis->getPointerAnalysis()->inSameCallGraphSCC(caller, + callee); } -/// Skip recursive callsites (within SCC); entry calls from outside SCC are not skipped +/// Skip recursive callsites (within SCC); entry calls from outside SCC are not +/// skipped bool AbstractInterpretation::skipRecursiveCall(const CallICFGNode* callNode) { const FunObjVar* callee = getCallee(callNode); @@ -126,7 +133,8 @@ bool AbstractInterpretation::skipRecursiveCall(const CallICFGNode* callNode) return isRecursiveCallSite(callNode, callee); } -/// Check if narrowing should be applied: always for regular loops, mode-dependent for recursion +/// Check if narrowing should be applied: always for regular loops, +/// mode-dependent for recursion bool AbstractInterpretation::shouldApplyNarrowing(const FunObjVar* fun) { // Non-recursive functions (regular loops): always apply narrowing @@ -138,62 +146,19 @@ bool AbstractInterpretation::shouldApplyNarrowing(const FunObjVar* fun) switch (Options::HandleRecur()) { case TOP: - assert(false && "TOP mode should not reach narrowing phase for recursive functions"); + assert(false && "TOP mode should not reach narrowing phase for " + "recursive functions"); return false; case WIDEN_ONLY: - return false; // Skip narrowing for recursive functions + return false; // Skip narrowing for recursive functions case WIDEN_NARROW: - return true; // Apply narrowing for recursive functions + return true; // Apply narrowing for recursive functions default: assert(false && "Unknown recursion handling mode"); return false; } } -// ===================================================================== -// Cycle state helpers (dense base) -// -// Dense default: trace[cycle_head] is the authoritative primary -// storage, so the snapshot / write-back are trivial. -// SemiSparseAbstractInterpretation overrides these to additionally -// pull/scatter cycle ValVars from/to their def-sites. -// ===================================================================== - -AbstractState AbstractInterpretation::getFullCycleHeadState(const ICFGCycleWTO* cycle) -{ - const ICFGNode* cycle_head = cycle->head()->getICFGNode(); - AbstractState snap; - if (hasAbsState(cycle_head)) - snap = getAbsState(cycle_head); - return snap; -} - -bool AbstractInterpretation::widenCycleState( - const AbstractState& prev, const AbstractState& cur, const ICFGCycleWTO* cycle) -{ - AbstractState prev_copy = prev; - AbstractState next = prev_copy.widening(cur); - // Always write back (even at fixpoint) so cycle_head's trace holds the - // widened state for the upcoming narrowing phase. - const ICFGNode* cycle_head = cycle->head()->getICFGNode(); - abstractTrace[cycle_head] = next; - return next == prev; -} - -bool AbstractInterpretation::narrowCycleState( - const AbstractState& prev, const AbstractState& cur, const ICFGCycleWTO* cycle) -{ - const ICFGNode* cycle_head = cycle->head()->getICFGNode(); - if (!shouldApplyNarrowing(cycle_head->getFun())) - return true; - AbstractState prev_copy = prev; - AbstractState next = prev_copy.narrowing(cur); - if (next == prev) - return true; // fixpoint - abstractTrace[cycle_head] = next; - return false; -} - // ===================================================================== // Cycle / recursion driver // @@ -227,16 +192,18 @@ bool AbstractInterpretation::narrowCycleState( // == Semi-sparse note == // In semi-sparse mode ValVars live at their def-sites and do not flow // through cycle_head's merge. The cycle helpers in -// SparseAbstractInterpretation.cpp gather them into the cycle_head +// Native sparse implementations gather them into the cycle head // snapshot and scatter them back after each widen/narrow step so the // fixpoint can observe ValVar growth across iterations. // ===================================================================== -void AbstractInterpretation::handleLoopOrRecursion(const ICFGCycleWTO* cycle, const CallICFGNode* caller) +void AbstractInterpretation::handleLoopOrRecursion(const ICFGCycleWTO* cycle, + const CallICFGNode* caller) { const ICFGNode* cycle_head = cycle->head()->getICFGNode(); - // TOP mode for recursive function cycles: set all stores and return value to TOP + // TOP mode for recursive function cycles: set all stores and return value + // to TOP if (Options::HandleRecur() == TOP && isRecursiveFun(cycle_head->getFun())) { if (caller) @@ -251,17 +218,21 @@ void AbstractInterpretation::handleLoopOrRecursion(const ICFGCycleWTO* cycle, co { if (cur_iter >= widen_delay) { - // getFullCycleHeadState handles dense (returns trace[cycle_head]) + // cloneCycleHeadState handles dense (returns trace[cycle_head]) // and semi-sparse (collects ValVars from def-sites) uniformly. - AbstractState prev = getFullCycleHeadState(cycle); + std::unique_ptr previous = + cloneCycleHeadState(cycle); if (mergeStatesFromPredecessors(cycle_head)) handleICFGNode(cycle_head); - AbstractState cur = getFullCycleHeadState(cycle); + std::unique_ptr current = + cloneCycleHeadState(cycle); if (increasing) { - if (widenCycleState(prev, cur, cycle)) + const bool stateFixpoint = + widenCycleState(*previous, *current, cycle); + if (stateFixpoint) { increasing = false; continue; @@ -269,7 +240,9 @@ void AbstractInterpretation::handleLoopOrRecursion(const ICFGCycleWTO* cycle, co } else { - if (narrowCycleState(prev, cur, cycle)) + const bool stateFixpoint = + narrowCycleState(*previous, *current, cycle); + if (stateFixpoint) break; } } @@ -283,15 +256,18 @@ void AbstractInterpretation::handleLoopOrRecursion(const ICFGCycleWTO* cycle, co // Process cycle body components (each with gated merge+handle) for (const ICFGWTOComp* comp : cycle->getWTOComponents()) { - if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) + if (const ICFGSingletonWTO* singleton = + SVFUtil::dyn_cast(comp)) { const ICFGNode* node = singleton->getICFGNode(); if (mergeStatesFromPredecessors(node)) handleICFGNode(node); } - else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) + else if (const ICFGCycleWTO* subCycle = + SVFUtil::dyn_cast(comp)) { - if (mergeStatesFromPredecessors(subCycle->head()->getICFGNode())) + if (mergeStatesFromPredecessors( + subCycle->head()->getICFGNode())) handleLoopOrRecursion(subCycle, caller); } } diff --git a/svf/lib/AE/Svfexe/AbsExtAPI.cpp b/svf/lib/AE/Svfexe/AbsExtAPI.cpp index a595dc4dfc..9250c3143a 100644 --- a/svf/lib/AE/Svfexe/AbsExtAPI.cpp +++ b/svf/lib/AE/Svfexe/AbsExtAPI.cpp @@ -31,6 +31,8 @@ #include "SVFIR/SVFIR.h" #include "Util/Options.h" +#include + using namespace SVF; AbsExtAPI::AbsExtAPI(AbstractInterpretation* ae): ae(ae) { @@ -122,7 +124,6 @@ void AbsExtAPI::initExtFunMap() auto svf_set_value = [&](const CallICFGNode* callNode) { if (callNode->arg_size() < 2) return; - AbstractState&as = getAbsState(callNode); const AbstractValue& lbVal = ae->getAbsValue(callNode->getArgument(1), callNode); const AbstractValue& ubVal = ae->getAbsValue(callNode->getArgument(2), callNode); assert(lbVal.getInterval().is_numeral() && ubVal.getInterval().is_numeral()); @@ -138,7 +139,7 @@ void AbsExtAPI::initExtFunMap() const LoadStmt* load = SVFUtil::cast(stmt); const AbstractValue& ptrVal = ae->getAbsValue(load->getRHSVar(), callNode); for (auto addr : ptrVal.getAddrs()) - as.store(addr, num); + ae->updateMemoryValue(addr, num, callNode); } } return; @@ -231,16 +232,15 @@ void AbsExtAPI::initExtFunMap() auto sse_free = [&](const CallICFGNode *callNode) { if (callNode->arg_size() < 1) return; - AbstractState& as = getAbsState(callNode); const AbstractValue& ptrVal = ae->getAbsValue(callNode->getArgument(0), callNode); for (auto addr: ptrVal.getAddrs()) { - if (AbstractState::isBlackHoleObjAddr(addr)) + if (addr == BlackHoleObjAddr) { } else { - as.addToFreedAddrs(addr); + ae->markFreedMemory(addr, callNode); } } }; @@ -259,10 +259,6 @@ void AbsExtAPI::initExtFunMap() } }; -AbstractState& AbsExtAPI::getAbsState(const SVF::ICFGNode* node) -{ - return ae->getAbsState(node); -} void AbsExtAPI::collectCheckPoint() { @@ -321,7 +317,6 @@ void AbsExtAPI::checkPointAllSet() std::string AbsExtAPI::strRead(const ValVar* rhs, const ICFGNode* node) { - AbstractState& as = getAbsState(node); std::string str0; for (u32_t index = 0; index < Options::MaxFieldLimit(); index++) @@ -333,7 +328,7 @@ std::string AbsExtAPI::strRead(const ValVar* rhs, const ICFGNode* node) AbstractValue val; for (const auto &addr: expr0.getAddrs()) { - val.join_with(as.load(addr)); + val.join_with(ae->getMemoryValue(addr, node)); } if (!val.getInterval().is_numeral()) { @@ -449,33 +444,44 @@ bool AbsExtAPI::isValidLength(const IntervalValue& len) /// Returns an IntervalValue: exact length if '\0' found, otherwise [0, MaxFieldLimit]. IntervalValue AbsExtAPI::getStrlen(const ValVar *strValue, const ICFGNode* node) { - AbstractState& as = getAbsState(node); // Step 1: determine the buffer size (in bytes) backing this pointer u32_t dst_size = 0; const AbstractValue& ptrVal = ae->getAbsValue(strValue, node); for (const auto& addr : ptrVal.getAddrs()) { - NodeID objId = as.getIDFromAddr(addr); - if (svfir->getBaseObject(objId)->isConstantByteSize()) + NodeID objId = AbstractInterpretation::objectIdFromAddress(addr); + const BaseObjVar* baseObject = svfir->getBaseObject(objId); + // Abstract addresses may denote black-hole, integer-derived, or other + // non-object nodes. In that case the backing size is unknown; keep + // the conservative unknown-length result instead of dereferencing a + // missing BaseObjVar. + if (baseObject == nullptr) + continue; + if (baseObject->isConstantByteSize()) { - dst_size = svfir->getBaseObject(objId)->getByteSizeOfObj(); + dst_size = std::max(dst_size, baseObject->getByteSizeOfObj()); } else { - const ICFGNode* icfgNode = svfir->getBaseObject(objId)->getICFGNode(); + const ICFGNode* icfgNode = baseObject->getICFGNode(); + if (icfgNode == nullptr) + continue; for (const SVFStmt* stmt2: icfgNode->getSVFStmts()) { if (const AddrStmt* addrStmt = SVFUtil::dyn_cast(stmt2)) { - dst_size = ae->getAllocaInstByteSize(addrStmt); + dst_size = std::max( + dst_size, ae->getAllocaInstByteSize(addrStmt)); } } } } - // Step 2: scan for '\0' terminator - u32_t len = 0; - if (ae->getAbsValue(strValue, node).isAddr()) + // Step 2: scan for a definitely positioned '\0' terminator. A pointer + // may denote several backing objects, so every byte before the terminator + // must be definitely non-zero across all pointees. An unknown byte or a + // missing terminator cannot soundly be treated as an exact string length. + if (ae->getAbsValue(strValue, node).isAddr() && dst_size != 0) { for (u32_t index = 0; index < dst_size; index++) { @@ -484,22 +490,23 @@ IntervalValue AbsExtAPI::getStrlen(const ValVar *strValue, const ICFGNode* node) AbstractValue val; for (const auto &addr: expr0.getAddrs()) { - val.join_with(as.load(addr)); + val.join_with(ae->getMemoryValue(addr, node)); } - if (val.getInterval().is_numeral() && - (char) val.getInterval().getIntNumeral() == '\0') + if (!val.getInterval().is_numeral()) + return IntervalValue((s64_t)0, + (s64_t)Options::MaxFieldLimit()); + if (val.getInterval().getIntNumeral() == 0) { - break; + const u32_t elemSize = getElementSize(strValue); + return IntervalValue(index * elemSize); } - ++len; } } - // Step 3: scale by element size and return - u32_t elemSize = getElementSize(strValue); - if (len == 0) - return IntervalValue((s64_t)0, (s64_t)Options::MaxFieldLimit()); - return IntervalValue(len * elemSize); + // No definite terminator was established. This includes unknown backing + // size, an empty points-to set, and a fully scanned but unterminated + // buffer. Preserve the documented conservative fallback. + return IntervalValue((s64_t)0, (s64_t)Options::MaxFieldLimit()); } // ===----------------------------------------------------------------------===// @@ -547,7 +554,6 @@ void AbsExtAPI::handleMemcpy(const ValVar *dst, u32_t start_idx, const ICFGNode* node) { if (!isValidLength(len)) return; - AbstractState& as = getAbsState(node); u32_t elemSize = getElementSize(dst); u32_t size = std::min((u32_t)Options::MaxFieldLimit(), @@ -567,11 +573,9 @@ void AbsExtAPI::handleMemcpy(const ValVar *dst, { for (const auto &srcAddr: expr_src.getAddrs()) { - u32_t objId = as.getIDFromAddr(srcAddr); - if (as.inAddrToValTable(objId) || as.inAddrToAddrsTable(objId)) - { - as.store(dstAddr, as.load(srcAddr)); - } + if (ae->hasMemoryValue(srcAddr, node)) + ae->updateMemoryValue( + dstAddr, ae->getMemoryValue(srcAddr, node), node); } } } @@ -586,7 +590,6 @@ void AbsExtAPI::handleMemset(const ValVar *dst, const IntervalValue& elem, const IntervalValue& len, const ICFGNode* node) { if (!isValidLength(len)) return; - AbstractState& as = getAbsState(node); u32_t elemSize = 1; if (dst->getType()->isArrayTy()) @@ -613,16 +616,15 @@ void AbsExtAPI::handleMemset(const ValVar *dst, AbstractValue lhs_gep = ae->getGepObjAddrs(dst, IntervalValue(index)); for (const auto &addr: lhs_gep.getAddrs()) { - u32_t objId = as.getIDFromAddr(addr); - if (as.inAddrToValTable(objId)) + if (ae->hasMemoryValue(addr, node)) { - AbstractValue tmp = as.load(addr); + AbstractValue tmp = ae->getMemoryValue(addr, node); tmp.join_with(elem); - as.store(addr, tmp); + ae->updateMemoryValue(addr, tmp, node); } else { - as.store(addr, elem); + ae->updateMemoryValue(addr, elem, node); } } } diff --git a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp index 3faea759f2..dbb389c536 100644 --- a/svf/lib/AE/Svfexe/AbstractInterpretation.cpp +++ b/svf/lib/AE/Svfexe/AbstractInterpretation.cpp @@ -1,4 +1,5 @@ -//===- AbstractExecution.cpp -- Abstract Execution---------------------------------// +//===- AbstractExecution.cpp -- Abstract +//Execution---------------------------------// // // SVF: Static Value-Flow Analysis // @@ -20,27 +21,28 @@ // //===----------------------------------------------------------------------===// - // // Created on: Jan 10, 2024 // Author: Xiao Cheng, Jiawei Wang // #include "AE/Svfexe/AbstractInterpretation.h" -#include "AE/Svfexe/SparseAbstractInterpretation.h" #include "AE/Svfexe/AbsExtAPI.h" +#include "AE/Svfexe/DenseAbstractInterpretation.h" +#include "AE/Svfexe/NativeSparseAbstractInterpretation.h" +#include "Graphs/CallGraph.h" #include "SVFIR/SVFIR.h" #include "Util/Options.h" #include "Util/WorkList.h" -#include "Graphs/CallGraph.h" #include "WPA/Andersen.h" #include +#include #include +#include using namespace SVF; using namespace SVFUtil; - void AbstractInterpretation::runOnModule() { stat->startClk(); @@ -54,7 +56,7 @@ void AbstractInterpretation::runOnModule() stat->finializeStat(); if (Options::PStat()) stat->performStat(); - for (auto& detector: detectors) + for (auto& detector : detectors) detector->reportBug(); } @@ -91,23 +93,24 @@ AbstractInterpretation& AbstractInterpretation::getAEInstance() // // A process-lifetime singleton has no observable lifecycle past // program exit, so leaking is benign and avoids the use-after-destroy. - static AbstractInterpretation* instance = []() -> AbstractInterpretation* - { + static AbstractInterpretation* instance = []() -> AbstractInterpretation* { switch (Options::AESparsity()) { case AESparsity::SemiSparse: - return new SemiSparseAbstractInterpretation(); + return new NativeSemiSparseAbstractInterpretation< + SVF::AbstractDomain::BoxState>(); case AESparsity::Sparse: - return new FullSparseAbstractInterpretation(); + return new NativeFullSparseAbstractInterpretation< + SVF::AbstractDomain::BoxState>(); case AESparsity::Dense: default: - return new AbstractInterpretation(); + return new DenseAbstractInterpretation< + SVF::AbstractDomain::BoxState>(); } }(); return *instance; } - /// Destructor AbstractInterpretation::~AbstractInterpretation() { @@ -116,6 +119,27 @@ AbstractInterpretation::~AbstractInterpretation() delete preAnalysis; } +void AbstractInterpretation::initializeDomainState(const ICFGNode*) {} + +void AbstractInterpretation::assignDomainInterval(const ICFGNode*, + const SVFVar*, + const IntervalValue&) +{ +} + +void AbstractInterpretation::updateDomainOnBinary(const BinaryOPStmt*, + const IntervalValue&) +{ +} + +void AbstractInterpretation::updateDomainOnCopy(const CopyStmt*) {} + +void AbstractInterpretation::updateDomainCopyValue(const ICFGNode*, + const SVFVar*, const SVFVar*, + bool) +{ +} + /// Collect entry point functions for analysis. /// In main mode, entry is main/svf.main. In no-main mode, /// entries are SCCs with no external caller in the Andersen-resolved CallGraph. @@ -173,7 +197,8 @@ FIFOWorkList AbstractInterpretation::collectProgEntryFuns() const FunObjVar* entryFun = fun; for (NodeID nodeId : cgSCCNodes) { - const FunObjVar* sccFun = callGraph->getGNode(nodeId)->getFunction(); + const FunObjVar* sccFun = + callGraph->getGNode(nodeId)->getFunction(); if (SVFUtil::isProgEntryFunction(sccFun)) { entryFun = sccFun; @@ -187,15 +212,16 @@ FIFOWorkList AbstractInterpretation::collectProgEntryFuns() if (mainEntry && entryFunctions.empty()) { SVFUtil::errs() << SVFUtil::errMsg( - "AE -ae-fun-entry=main requires a program entry function, but main/svf.main was not found.\n"); - assert(false && "No program entry function found for -ae-fun-entry=main"); + "AE -ae-fun-entry=main requires a program entry function, but " + "main/svf.main was not found.\n"); + assert(false && + "No program entry function found for -ae-fun-entry=main"); abort(); } return entryFunctions; } - /// Program entry - entry policy is selected by -ae-fun-entry. void AbstractInterpretation::analyse() { @@ -222,116 +248,11 @@ void AbstractInterpretation::analyzeFromAllProgEntries() { const FunObjVar* entryFun = entryFunctions.pop(); const ICFGNode* funEntry = icfg->getFunEntryICFGNode(entryFun); - updateAbsState(funEntry, getAbsState(globalNode)); + copyAbstractState(globalNode, funEntry); handleFunction(funEntry, nullptr); } } -/// handle global node -/// Initializes the abstract state for the global ICFG node and processes all global statements. -/// This includes setting up the null pointer and black hole pointer (blkPtr). -/// BlkPtr is initialized to point to the BlackHole object, representing -/// an unknown memory location that cannot be statically resolved. -void AbstractInterpretation::handleGlobalNode() -{ - const ICFGNode* node = icfg->getGlobalICFGNode(); - // Global init is one of the few legitimate direct-mutation sites: - // updateAbsState filters out ValVars in semi-sparse mode, but NullPtr/ - // BlkPtr have no SVFVar so we cannot route them through updateAbsValue. - // Use the manager's operator[] (auto-creates the entry if absent). - AbstractState& init = abstractTrace[node]; - init = AbstractState(); - // TODO: we cannot find right SVFVar for NullPtr, so we use init[NullPtr] - // directly. Same for BlkPtr below. - init[IRGraph::NullPtr] = AddressValue(); - - // Global Node, we just need to handle addr, load, store, copy and gep - for (const SVFStmt *stmt: node->getSVFStmts()) - { - handleSVFStatement(stmt); - } - - // BlkPtr is the canonical unknown value. Keep its address-domain meaning - // for pointer uses, and also give it numeric top so external-input stores - // can flow through ordinary store/load state as [-inf, +inf]. - AbstractValue blkPtrValue(IntervalValue::top()); - blkPtrValue.getAddrs().insert(BlackHoleObjAddr); - abstractTrace[node][PAG::getPAG()->getBlkPtr()] = blkPtrValue; -} - -/// Pull-based state merge: for each predecessor that has an abstract state, -/// copy its state, apply branch refinement for conditional IntraCFGEdges, -/// and join all feasible states into getAbsState(node). -/// The join is dispatched through the manager so semi-sparse can skip -/// ValVar merging. -/// Returns true if at least one predecessor contributed state. -bool AbstractInterpretation::mergeStatesFromPredecessors(const ICFGNode* node) -{ - // Collect all feasible predecessor states, then merge at the end. - AbstractState merged; - bool hasFeasiblePred = false; - - for (auto& edge : node->getInEdges()) - { - const ICFGNode* pred = edge->getSrcNode(); - if (!hasAbsState(pred)) - continue; - - if (const IntraCFGEdge* intraCfgEdge = SVFUtil::dyn_cast(edge)) - { - if (intraCfgEdge->getCondition()) - { - AbstractState predState = getAbsState(pred); - if (isBranchEdgeFeasible(intraCfgEdge, predState)) - { - collectBranchRefinement(intraCfgEdge, predState); - joinStates(merged, predState); - hasFeasiblePred = true; - } - } - else - { - joinStates(merged, getAbsState(pred)); - hasFeasiblePred = true; - } - } - else if (SVFUtil::isa(edge)) - { - joinStates(merged, getAbsState(pred)); - hasFeasiblePred = true; - } - else if (SVFUtil::isa(edge)) - { - switch (Options::HandleRecur()) - { - case TOP: - joinStates(merged, getAbsState(pred)); - hasFeasiblePred = true; - break; - case WIDEN_ONLY: - case WIDEN_NARROW: - { - const RetICFGNode* returnSite = SVFUtil::dyn_cast(node); - const CallICFGNode* callSite = returnSite->getCallICFGNode(); - if (hasAbsState(callSite)) - { - joinStates(merged, getAbsState(pred)); - hasFeasiblePred = true; - } - break; - } - } - } - } - - if (!hasFeasiblePred) - return false; - - updateAbsState(node, merged); - - return true; -} - /// Given a cmp operand, walk its SSA def edge to find the LoadStmt that /// produced it. This lets us trace back to the ObjVar in memory so that /// branch narrowing can refine the stored value. @@ -372,18 +293,17 @@ static const LoadStmt* findBackingLoad(const SVFVar* var) /// [6, +inf). On the false branch (succ=0), %a is constrained to (-inf, 5]. /// The result is used to narrow the ObjVar behind %a's load. static IntervalValue computeCmpConstraint(s32_t predicate, s64_t succ, - bool isLHS, const IntervalValue& self, - const IntervalValue& other) + bool isLHS, const IntervalValue& self, + const IntervalValue& other) { // Normalize: always reason from the LHS perspective. // If we are the RHS operand, swap the predicate direction. if (!isLHS) { // a > b from b's perspective: b < a - static const Map swapPred = - { - {CmpStmt::ICMP_EQ, CmpStmt::ICMP_EQ}, - {CmpStmt::ICMP_NE, CmpStmt::ICMP_NE}, + static const Map swapPred = { + {CmpStmt::ICMP_EQ, CmpStmt::ICMP_EQ}, + {CmpStmt::ICMP_NE, CmpStmt::ICMP_NE}, {CmpStmt::ICMP_SGT, CmpStmt::ICMP_SLT}, {CmpStmt::ICMP_SGE, CmpStmt::ICMP_SLE}, {CmpStmt::ICMP_SLT, CmpStmt::ICMP_SGT}, @@ -406,17 +326,17 @@ static IntervalValue computeCmpConstraint(s32_t predicate, s64_t succ, {CmpStmt::FCMP_UNE, CmpStmt::FCMP_UNE}, }; auto it = swapPred.find(predicate); - if (it == swapPred.end()) return IntervalValue::top(); + if (it == swapPred.end()) + return IntervalValue::top(); predicate = it->second; } // If false branch, negate the predicate. if (succ == 0) { - static const Map negPred = - { - {CmpStmt::ICMP_EQ, CmpStmt::ICMP_NE}, - {CmpStmt::ICMP_NE, CmpStmt::ICMP_EQ}, + static const Map negPred = { + {CmpStmt::ICMP_EQ, CmpStmt::ICMP_NE}, + {CmpStmt::ICMP_NE, CmpStmt::ICMP_EQ}, {CmpStmt::ICMP_SGT, CmpStmt::ICMP_SLE}, {CmpStmt::ICMP_SGE, CmpStmt::ICMP_SLT}, {CmpStmt::ICMP_SLT, CmpStmt::ICMP_SGE}, @@ -439,7 +359,8 @@ static IntervalValue computeCmpConstraint(s32_t predicate, s64_t succ, {CmpStmt::FCMP_UNE, CmpStmt::FCMP_UEQ}, }; auto it = negPred.find(predicate); - if (it == negPred.end()) return IntervalValue::top(); + if (it == negPred.end()) + return IntervalValue::top(); predicate = it->second; } @@ -462,25 +383,29 @@ static IntervalValue computeCmpConstraint(s32_t predicate, s64_t succ, case CmpStmt::ICMP_SGT: case CmpStmt::FCMP_OGT: case CmpStmt::FCMP_UGT: - result.meet_with(IntervalValue(other.lb() + 1, IntervalValue::plus_infinity())); + result.meet_with( + IntervalValue(other.lb() + 1, IntervalValue::plus_infinity())); break; case CmpStmt::ICMP_UGE: case CmpStmt::ICMP_SGE: case CmpStmt::FCMP_OGE: case CmpStmt::FCMP_UGE: - result.meet_with(IntervalValue(other.lb(), IntervalValue::plus_infinity())); + result.meet_with( + IntervalValue(other.lb(), IntervalValue::plus_infinity())); break; case CmpStmt::ICMP_ULT: case CmpStmt::ICMP_SLT: case CmpStmt::FCMP_OLT: case CmpStmt::FCMP_ULT: - result.meet_with(IntervalValue(IntervalValue::minus_infinity(), other.ub() - 1)); + result.meet_with( + IntervalValue(IntervalValue::minus_infinity(), other.ub() - 1)); break; case CmpStmt::ICMP_ULE: case CmpStmt::ICMP_SLE: case CmpStmt::FCMP_OLE: case CmpStmt::FCMP_ULE: - result.meet_with(IntervalValue(IntervalValue::minus_infinity(), other.ub())); + result.meet_with( + IntervalValue(IntervalValue::minus_infinity(), other.ub())); break; default: return IntervalValue::top(); @@ -488,54 +413,8 @@ static IntervalValue computeCmpConstraint(s32_t predicate, s64_t succ, return result; } -bool AbstractInterpretation::isCmpBranchEdgeFeasible(const IntraCFGEdge* edge, - AbstractState& as) -{ - const ICFGNode* pred = edge->getSrcNode(); - s64_t succ = edge->getSuccessorCondValue(); - const CmpStmt* cmpStmt = SVFUtil::cast( - *edge->getCondition()->getInEdges().begin()); - - if (cmpStmt->getOpVarID(0) == IRGraph::NullPtr || - cmpStmt->getOpVarID(1) == IRGraph::NullPtr) - return true; - - AbstractValue opVal[2] = - { - getAbsValue(cmpStmt->getOpVar(0), pred), - getAbsValue(cmpStmt->getOpVar(1), pred) - }; - - const bool hasIntervalCmp = opVal[0].isInterval() && opVal[1].isInterval(); - if (!hasIntervalCmp && (opVal[0].isAddr() || opVal[1].isAddr())) - return true; - - // Feasibility check: cmp result must be compatible with branch successor - IntervalValue resVal = getAbsValue(cmpStmt->getRes(), pred).getInterval(); - resVal.meet_with(IntervalValue((s64_t)succ, succ)); - if (resVal.isBottom()) - return false; - - return true; -} - -bool AbstractInterpretation::isSwitchBranchEdgeFeasible( - const IntraCFGEdge* edge, AbstractState& as) -{ - const ICFGNode* pred = edge->getSrcNode(); - s64_t succ = edge->getSuccessorCondValue(); - const SVFVar* var = edge->getCondition(); - - AbstractValue condVal = getAbsValue(var, pred); - IntervalValue switch_cond = condVal.getInterval(); - switch_cond.meet_with(IntervalValue(succ, succ)); - if (switch_cond.isBottom()) - return false; - return true; -} - -void AbstractInterpretation::collectBranchRefinement(const IntraCFGEdge* edge, - AbstractState& as) +void AbstractInterpretation::collectBranchRefinement( + const IntraCFGEdge* edge, AbstractDomain::AbstractState& state) { const SVFVar* cond = edge->getCondition(); const ICFGNode* pred = edge->getSrcNode(); @@ -551,15 +430,14 @@ void AbstractInterpretation::collectBranchRefinement(const IntraCFGEdge* edge, s32_t predicate = cmpStmt->getPredicate(); if (cmpStmt->getOpVarID(0) == IRGraph::NullPtr || - cmpStmt->getOpVarID(1) == IRGraph::NullPtr) + cmpStmt->getOpVarID(1) == IRGraph::NullPtr) { // p == NULL / p != NULL: no interval obj to refine. } else { AbstractValue opVal[2] = {getAbsValue(cmpStmt->getOpVar(0), pred), - getAbsValue(cmpStmt->getOpVar(1), pred) - }; + getAbsValue(cmpStmt->getOpVar(1), pred)}; const bool hasIntervalCmp = opVal[0].isInterval() && opVal[1].isInterval(); @@ -590,8 +468,8 @@ void AbstractInterpretation::collectBranchRefinement(const IntraCFGEdge* edge, else { IntervalValue narrowed = computeCmpConstraint( - predicate, succ, i == 0, opVal[i].getInterval(), - opVal[other].getInterval()); + predicate, succ, i == 0, opVal[i].getInterval(), + opVal[other].getInterval()); if (narrowed.isTop()) { @@ -610,9 +488,11 @@ void AbstractInterpretation::collectBranchRefinement(const IntraCFGEdge* edge, { for (const auto& addr : ptrVal.getAddrs()) { - NodeID objId = as.getIDFromAddr(addr); - recordBranchRefinement(objId, narrowed, as, - loadIcfg, succNode); + const NodeID objId = + objectIdFromAddress(addr); + recordBranchRefinement( + objId, narrowed, state, loadIcfg, + succNode); } } } @@ -634,8 +514,6 @@ void AbstractInterpretation::collectBranchRefinement(const IntraCFGEdge* edge, } else { - as[var->getId()] = AbstractValue(switch_cond); - FIFOWorkList stmtList; for (SVFStmt* stmt : var->getInEdges()) stmtList.push(stmt); @@ -660,8 +538,8 @@ void AbstractInterpretation::collectBranchRefinement(const IntraCFGEdge* edge, { for (const auto& addr : ptrVal.getAddrs()) { - NodeID objId = as.getIDFromAddr(addr); - recordBranchRefinement(objId, switch_cond, as, + const NodeID objId = objectIdFromAddress(addr); + recordBranchRefinement(objId, switch_cond, state, loadIcfg, succNode); } } @@ -671,54 +549,20 @@ void AbstractInterpretation::collectBranchRefinement(const IntraCFGEdge* edge, } } -void AbstractInterpretation::recordBranchRefinement( - NodeID objId, const IntervalValue& narrowed, AbstractState& as, - const ICFGNode* loadIcfg, const ICFGNode* /*succ*/) -{ - // Default (dense / semi-sparse): MEET narrowed onto obj's current - // value, store back into the local `as`. Caller's joinStates - // propagates `as` into `merged`, then `updateAbsState(succ, merged)` - // commits it to trace[succ]. - // - // We can't go through the polymorphic updateAbsValue here: `as` is - // a transient per-edge predState copy that lives outside - // abstractTrace, so it has no node id. Writing via `updateAbsValue` - // with `succ` as the node would land in trace[succ] but get - // clobbered by the subsequent `updateAbsState(succ, merged)`; with - // `loadIcfg` it would corrupt the obj's authoritative value at its - // load site. AbstractState::store on the transient `as` is the - // only sound primitive — and recordBranchRefinement itself is the - // virtual customisation point (FullSparse routes to - // refinementTrace instead of touching `as`). - const ObjVar* objVar = SVFUtil::dyn_cast(svfir->getGNode(objId)); - if (objVar && hasAbsValue(objVar, loadIcfg)) - { - AbstractValue cur = getAbsValue(objVar, loadIcfg); - if (cur.isInterval()) - { - IntervalValue itv = cur.getInterval(); - itv.meet_with(narrowed); - u32_t addr = AbstractState::getVirtualMemAddress(objId); - as.store(addr, AbstractValue(itv)); - } - } -} - -bool AbstractInterpretation::isBranchEdgeFeasible(const IntraCFGEdge* edge, - AbstractState& as) +void AbstractInterpretation::recordBranchRefinement(NodeID, + const IntervalValue&, + AbstractDomain::AbstractState&, + const ICFGNode*, + const ICFGNode*) { - const SVFVar* cmpVar = edge->getCondition(); - assert(!cmpVar->getInEdges().empty() && "branch condition has no defining edge?"); - if (SVFUtil::isa(*cmpVar->getInEdges().begin())) - return isCmpBranchEdgeFeasible(edge, as); - return isSwitchBranchEdgeFeasible(edge, as); } /** * Handle an ICFG node: execute statements on the current abstract state. - * The node's pre-state must already be in getAbsState(node) (set by - * mergeStatesFromPredecessors, or by handleGlobalNode for the global node). - * Returns true if the abstract state has changed, false if fixpoint reached or unreachable. + * The node's pre-state must already be installed by + * mergeStatesFromPredecessors, or by handleGlobalNode for the global node. + * Returns true if the abstract state has changed, false if fixpoint reached or + * unreachable. */ bool AbstractInterpretation::handleICFGNode(const ICFGNode* node) { @@ -731,24 +575,31 @@ bool AbstractInterpretation::handleICFGNode(const ICFGNode* node) // Entry point with no callers: inherit from global node const ICFGNode* globalNode = icfg->getGlobalICFGNode(); if (hasAbsState(globalNode)) - updateAbsState(node, getAbsState(globalNode)); + { + copyAbstractState(globalNode, node); + } else - updateAbsState(node, AbstractState()); + { + resetAbstractState(node); + } } else { - return false; // unreachable node + return false; // unreachable node } } + initializeDomainState(node); + // Store the previous state for fixpoint detection - AbstractState prevState = getAbsState(node); + std::unique_ptr previousState = + cloneAbstractState(node); stat->getBlockTrace()++; stat->getICFGNodeTrace()++; // Handle SVF statements - for (const SVFStmt *stmt: node->getSVFStmts()) + for (const SVFStmt* stmt : node->getSVFStmts()) { handleSVFStatement(stmt); } @@ -760,14 +611,17 @@ bool AbstractInterpretation::handleICFGNode(const ICFGNode* node) } // Run detectors - for (auto& detector: detectors) + for (auto& detector : detectors) detector->detect(node); + + finalizeAbstractState(node); stat->countStateSize(); - // Track this node as analyzed (for coverage statistics across all entry points) + // Track this node as analyzed (for coverage statistics across all entry + // points) allAnalyzedNodes.insert(node); - if (getAbsState(node) == prevState) + if (isAbstractStateEquivalent(node, *previousState)) return false; return true; @@ -779,10 +633,12 @@ bool AbstractInterpretation::handleICFGNode(const ICFGNode* node) * so the traversal order is exactly the WTO order — each node is * visited once, and cycles are handled as whole components. */ -void AbstractInterpretation::handleFunction(const ICFGNode* funEntry, const CallICFGNode* caller) +void AbstractInterpretation::handleFunction(const ICFGNode* funEntry, + const CallICFGNode* caller) { auto it = preAnalysis->getFuncToWTO().find(funEntry->getFun()); - assert(it != preAnalysis->getFuncToWTO().end() && "Missing WTO for function"); + assert(it != preAnalysis->getFuncToWTO().end() && + "Missing WTO for function"); // Push all top-level WTO components into the worklist in WTO order FIFOWorkList worklist(it->second->getWTOComponents()); @@ -791,13 +647,15 @@ void AbstractInterpretation::handleFunction(const ICFGNode* funEntry, const Call { const ICFGWTOComp* comp = worklist.pop(); - if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) + if (const ICFGSingletonWTO* singleton = + SVFUtil::dyn_cast(comp)) { const ICFGNode* node = singleton->getICFGNode(); if (mergeStatesFromPredecessors(node)) handleICFGNode(node); } - else if (const ICFGCycleWTO* cycle = SVFUtil::dyn_cast(comp)) + else if (const ICFGCycleWTO* cycle = + SVFUtil::dyn_cast(comp)) { if (mergeStatesFromPredecessors(cycle->head()->getICFGNode())) handleLoopOrRecursion(cycle, caller); @@ -805,7 +663,6 @@ void AbstractInterpretation::handleFunction(const ICFGNode* funEntry, const Call } } - void AbstractInterpretation::handleCallSite(const ICFGNode* node) { if (const CallICFGNode* callNode = SVFUtil::dyn_cast(node)) @@ -821,15 +678,15 @@ void AbstractInterpretation::handleCallSite(const ICFGNode* node) } } else - assert (false && "it is not call node"); + assert(false && "it is not call node"); } -bool AbstractInterpretation::isExtCall(const CallICFGNode *callNode) +bool AbstractInterpretation::isExtCall(const CallICFGNode* callNode) { return SVFUtil::isExtCall(callNode->getCalledFunction()); } -void AbstractInterpretation::handleExtCall(const CallICFGNode *callNode) +void AbstractInterpretation::handleExtCall(const CallICFGNode* callNode) { utils->handleExtAPI(callNode); for (auto& detector : detectors) @@ -838,7 +695,8 @@ void AbstractInterpretation::handleExtCall(const CallICFGNode *callNode) } } -/// Get callee function: directly for direct calls, via pointer analysis for indirect calls +/// Get callee function: directly for direct calls, via pointer analysis for +/// indirect calls const FunObjVar* AbstractInterpretation::getCallee(const CallICFGNode* callNode) { // Direct call: get callee directly from call node @@ -855,25 +713,28 @@ const FunObjVar* AbstractInterpretation::getCallee(const CallICFGNode* callNode) if (!hasAbsState(callNode)) return nullptr; - const AbstractValue& Addrs = getAbsValue(svfir->getSVFVar(call_id), callNode); + const AbstractValue& Addrs = + getAbsValue(svfir->getSVFVar(call_id), callNode); if (!Addrs.isAddr() || Addrs.getAddrs().empty()) return nullptr; NodeID addr = *Addrs.getAddrs().begin(); - const SVFVar* func_var = getSVFVar(getAbsState(callNode).getIDFromAddr(addr)); + const SVFVar* func_var = getSVFVar(objectIdFromAddress(addr)); return SVFUtil::dyn_cast(func_var); } -/// Handle direct or indirect call: get callee(s), process function body, set return state. +/// Handle direct or indirect call: get callee(s), process function body, set +/// return state. /// /// For direct calls, the callee is known statically. -/// For indirect calls, the previous implementation resolved callees from the abstract -/// state's address domain, which only picked the first address and missed other targets. -/// Since the abstract state's address domain is not an over-approximation for function -/// pointers (it may be uninitialized or incomplete), we now use Andersen's pointer -/// analysis results from the pre-computed call graph, which soundly resolves all -/// possible indirect call targets. -void AbstractInterpretation::handleFunCall(const CallICFGNode *callNode) +/// For indirect calls, the previous implementation resolved callees from the +/// abstract state's address domain, which only picked the first address and +/// missed other targets. Since the abstract state's address domain is not an +/// over-approximation for function pointers (it may be uninitialized or +/// incomplete), we now use Andersen's pointer analysis results from the +/// pre-computed call graph, which soundly resolves all possible indirect call +/// targets. +void AbstractInterpretation::handleFunCall(const CallICFGNode* callNode) { if (skipRecursiveCall(callNode)) return; @@ -884,7 +745,7 @@ void AbstractInterpretation::handleFunCall(const CallICFGNode *callNode) const ICFGNode* calleeEntry = icfg->getFunEntryICFGNode(callee); handleFunction(calleeEntry, callNode); const RetICFGNode* retNode = callNode->getRetICFGNode(); - updateAbsState(retNode, getAbsState(callNode)); + copyAbstractState(callNode, retNode); return; } @@ -893,7 +754,13 @@ void AbstractInterpretation::handleFunCall(const CallICFGNode *callNode) if (callGraph->hasIndCSCallees(callNode)) { const auto& callees = callGraph->getIndCSCallees(callNode); - for (const FunObjVar* callee : callees) + std::vector orderedCallees(callees.begin(), + callees.end()); + std::sort(orderedCallees.begin(), orderedCallees.end(), + [](const FunObjVar* lhs, const FunObjVar* rhs) { + return lhs->getId() < rhs->getId(); + }); + for (const FunObjVar* callee : orderedCallees) { if (callee->isDeclaration()) continue; @@ -902,23 +769,23 @@ void AbstractInterpretation::handleFunCall(const CallICFGNode *callNode) } } // Resume return node from caller's state (context-insensitive) - updateAbsState(retNode, getAbsState(callNode)); + copyAbstractState(callNode, retNode); } // Loop / recursion handling (handleLoopOrRecursion + cycle helpers + // recursion utilities) lives in AELoopRecursion.cpp. -void AbstractInterpretation::handleSVFStatement(const SVFStmt *stmt) +void AbstractInterpretation::handleSVFStatement(const SVFStmt* stmt) { - if (const AddrStmt *addr = SVFUtil::dyn_cast(stmt)) + if (const AddrStmt* addr = SVFUtil::dyn_cast(stmt)) { updateStateOnAddr(addr); } - else if (const BinaryOPStmt *binary = SVFUtil::dyn_cast(stmt)) + else if (const BinaryOPStmt* binary = SVFUtil::dyn_cast(stmt)) { updateStateOnBinary(binary); } - else if (const CmpStmt *cmp = SVFUtil::dyn_cast(stmt)) + else if (const CmpStmt* cmp = SVFUtil::dyn_cast(stmt)) { updateStateOnCmp(cmp); } @@ -929,36 +796,36 @@ void AbstractInterpretation::handleSVFStatement(const SVFStmt *stmt) { // branch stmt is handled in hasBranchES } - else if (const LoadStmt *load = SVFUtil::dyn_cast(stmt)) + else if (const LoadStmt* load = SVFUtil::dyn_cast(stmt)) { updateStateOnLoad(load); } - else if (const StoreStmt *store = SVFUtil::dyn_cast(stmt)) + else if (const StoreStmt* store = SVFUtil::dyn_cast(stmt)) { updateStateOnStore(store); } - else if (const CopyStmt *copy = SVFUtil::dyn_cast(stmt)) + else if (const CopyStmt* copy = SVFUtil::dyn_cast(stmt)) { updateStateOnCopy(copy); } - else if (const GepStmt *gep = SVFUtil::dyn_cast(stmt)) + else if (const GepStmt* gep = SVFUtil::dyn_cast(stmt)) { updateStateOnGep(gep); } - else if (const SelectStmt *select = SVFUtil::dyn_cast(stmt)) + else if (const SelectStmt* select = SVFUtil::dyn_cast(stmt)) { updateStateOnSelect(select); } - else if (const PhiStmt *phi = SVFUtil::dyn_cast(stmt)) + else if (const PhiStmt* phi = SVFUtil::dyn_cast(stmt)) { updateStateOnPhi(phi); } - else if (const CallPE *callPE = SVFUtil::dyn_cast(stmt)) + else if (const CallPE* callPE = SVFUtil::dyn_cast(stmt)) { // To handle Call Edge updateStateOnCall(callPE); } - else if (const RetPE *retPE = SVFUtil::dyn_cast(stmt)) + else if (const RetPE* retPE = SVFUtil::dyn_cast(stmt)) { updateStateOnRet(retPE); } @@ -967,24 +834,18 @@ void AbstractInterpretation::handleSVFStatement(const SVFStmt *stmt) // NullPtr should not be changed by any statement. If the entry is missing // (not yet auto-inserted) we treat that as "unchanged" — only check the // entry if it actually exists. - { - const auto& vmap = getAbsState(stmt->getICFGNode()).getVarToVal(); - auto it = vmap.find(IRGraph::NullPtr); - (void)it; // Suppress warning of unused variable under release build - assert(it == vmap.end() || - (!it->second.isInterval() && !it->second.isAddr())); - } } -void AbstractInterpretation::updateStateOnGep(const GepStmt *gep) +void AbstractInterpretation::updateStateOnGep(const GepStmt* gep) { const ICFGNode* node = gep->getICFGNode(); IntervalValue offsetPair = getGepElementIndex(gep); - AddressValue gepAddrs = getGepObjAddrs(SVFUtil::cast(gep->getRHSVar()), offsetPair); + AddressValue gepAddrs = + getGepObjAddrs(SVFUtil::cast(gep->getRHSVar()), offsetPair); updateAbsValue(gep->getLHSVar(), gepAddrs, node); } -void AbstractInterpretation::updateStateOnSelect(const SelectStmt *select) +void AbstractInterpretation::updateStateOnSelect(const SelectStmt* select) { const ICFGNode* node = select->getICFGNode(); const AbstractValue& condVal = getAbsValue(select->getCondition(), node); @@ -1001,9 +862,10 @@ void AbstractInterpretation::updateStateOnSelect(const SelectStmt *select) resVal.join_with(fVal); } updateAbsValue(select->getRes(), resVal, node); + assignDomainInterval(node, select->getRes(), resVal.getInterval()); } -void AbstractInterpretation::updateStateOnPhi(const PhiStmt *phi) +void AbstractInterpretation::updateStateOnPhi(const PhiStmt* phi) { const ICFGNode* icfgNode = phi->getICFGNode(); AbstractValue rhs; @@ -1012,15 +874,17 @@ void AbstractInterpretation::updateStateOnPhi(const PhiStmt *phi) const ICFGNode* opICFGNode = phi->getOpICFGNode(i); if (hasAbsState(opICFGNode)) { - AbstractState tmpState = getAbsState(opICFGNode); - const AbstractValue& opVal = getAbsValue(phi->getOpVar(i), opICFGNode); - const ICFGEdge* edge = icfg->getICFGEdge(opICFGNode, icfgNode, ICFGEdge::IntraCF); + const AbstractValue& opVal = + getAbsValue(phi->getOpVar(i), opICFGNode); + const ICFGEdge* edge = + icfg->getICFGEdge(opICFGNode, icfgNode, ICFGEdge::IntraCF); if (edge) { - const IntraCFGEdge* intraEdge = SVFUtil::cast(edge); + const IntraCFGEdge* intraEdge = + SVFUtil::cast(edge); if (intraEdge->getCondition()) { - if (isBranchEdgeFeasible(intraEdge, tmpState)) + if (isBranchEdgeFeasibleAt(intraEdge, opICFGNode)) rhs.join_with(opVal); } else @@ -1033,12 +897,13 @@ void AbstractInterpretation::updateStateOnPhi(const PhiStmt *phi) } } updateAbsValue(phi->getRes(), rhs, icfgNode); + assignDomainInterval(icfgNode, phi->getRes(), rhs.getInterval()); } - /// Handle CallPE: phi-like merging of actual parameters from all call sites -/// into the formal parameter at FunEntryICFGNode (e.g., formal = join(actual1@cs1, actual2@cs2, ...)) -void AbstractInterpretation::updateStateOnCall(const CallPE *callPE) +/// into the formal parameter at FunEntryICFGNode (e.g., formal = +/// join(actual1@cs1, actual2@cs2, ...)) +void AbstractInterpretation::updateStateOnCall(const CallPE* callPE) { const ICFGNode* node = callPE->getICFGNode(); const SVFVar* res = callPE->getRes(); @@ -1048,47 +913,44 @@ void AbstractInterpretation::updateStateOnCall(const CallPE *callPE) const ICFGNode* opICFGNode = callPE->getOpCallICFGNode(i); if (hasAbsState(opICFGNode)) { - const AbstractValue& opVal = getAbsValue(callPE->getOpVar(i), opICFGNode); + const AbstractValue& opVal = + getAbsValue(callPE->getOpVar(i), opICFGNode); rhs.join_with(opVal); } } updateAbsValue(res, rhs, node); + assignDomainInterval(node, res, rhs.getInterval()); } -void AbstractInterpretation::updateStateOnRet(const RetPE *retPE) +void AbstractInterpretation::updateStateOnRet(const RetPE* retPE) { const ICFGNode* node = retPE->getICFGNode(); const AbstractValue& rhsVal = getAbsValue(retPE->getRHSVar(), node); updateAbsValue(retPE->getLHSVar(), rhsVal, node); + updateDomainCopyValue(node, retPE->getLHSVar(), retPE->getRHSVar(), true); } - -void AbstractInterpretation::updateStateOnAddr(const AddrStmt *addr) +void AbstractInterpretation::updateStateOnAddr(const AddrStmt* addr) { const ICFGNode* node = addr->getICFGNode(); - // initObjVar mutates _varToAbsVal/_addrToAbsVal directly, so we need - // mutable access; route via the manager. - AbstractState& as = getAbsState(node); - as.initObjVar(SVFUtil::cast(addr->getRHSVar())); - // AddrStmt: lhs(ValVar) = &rhs(ObjVar). - // as[rhsId] stores the ObjVar's virtual address in _varToVal, - // NOT the object contents. So we must use as[] directly for ObjVar. - u32_t rhsId = addr->getRHSVarID(); + AbstractValue value = + initializeObjectAddress(SVFUtil::cast(addr->getRHSVar()), node); if (addr->getRHSVar()->getType()->getKind() == SVFType::SVFIntegerTy) - as[rhsId].getInterval().meet_with(utils->getRangeLimitFromType(addr->getRHSVar()->getType())); - // LHS is a ValVar (pointer), write through the API - updateAbsValue(addr->getLHSVar(), as[rhsId], node); + value.getInterval().meet_with( + utils->getRangeLimitFromType(addr->getRHSVar()->getType())); + updateAbsValue(addr->getLHSVar(), value, node); } - -void AbstractInterpretation::updateStateOnBinary(const BinaryOPStmt *binary) +void AbstractInterpretation::updateStateOnBinary(const BinaryOPStmt* binary) { const ICFGNode* node = binary->getICFGNode(); // Treat bottom (uninitialized) operands as top for soundness const AbstractValue& op0Val = getAbsValue(binary->getOpVar(0), node); const AbstractValue& op1Val = getAbsValue(binary->getOpVar(1), node); - IntervalValue lhs = op0Val.getInterval().isBottom() ? IntervalValue::top() : op0Val.getInterval(); - IntervalValue rhs = op1Val.getInterval().isBottom() ? IntervalValue::top() : op1Val.getInterval(); + IntervalValue lhs = op0Val.getInterval().isBottom() ? IntervalValue::top() + : op0Val.getInterval(); + IntervalValue rhs = op1Val.getInterval().isBottom() ? IntervalValue::top() + : op1Val.getInterval(); IntervalValue resVal; switch (binary->getOpcode()) { @@ -1136,9 +998,10 @@ void AbstractInterpretation::updateStateOnBinary(const BinaryOPStmt *binary) assert(false && "undefined binary: "); } updateAbsValue(binary->getRes(), resVal, node); + updateDomainOnBinary(binary, resVal); } -void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp) +void AbstractInterpretation::updateStateOnCmp(const CmpStmt* cmp) { const ICFGNode* node = cmp->getICFGNode(); u32_t op0 = cmp->getOpVarID(0); @@ -1166,10 +1029,12 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp) } updateAbsValue(cmp->getRes(), resVal, node); } - // if op0 or op1 is nullptr, compare abstractValue instead of touching addr or interval + // if op0 or op1 is nullptr, compare abstractValue instead of touching addr + // or interval else if (op0 == IRGraph::NullPtr || op1 == IRGraph::NullPtr) { - IntervalValue resVal = (op0Val.equals(op1Val)) ? IntervalValue(1, 1) : IntervalValue(0, 0); + IntervalValue resVal = + (op0Val.equals(op1Val)) ? IntervalValue(1, 1) : IntervalValue(0, 0); updateAbsValue(cmp->getRes(), resVal, node); } else @@ -1179,8 +1044,12 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp) if (op0Val.isInterval() && op1Val.isInterval()) { // Treat bottom (uninitialized) operands as top for soundness - IntervalValue lhs = op0Val.getInterval().isBottom() ? IntervalValue::top() : op0Val.getInterval(), - rhs = op1Val.getInterval().isBottom() ? IntervalValue::top() : op1Val.getInterval(); + IntervalValue lhs = op0Val.getInterval().isBottom() + ? IntervalValue::top() + : op0Val.getInterval(), + rhs = op1Val.getInterval().isBottom() + ? IntervalValue::top() + : op1Val.getInterval(); // AbstractValue auto predicate = cmp->getPredicate(); switch (predicate) @@ -1247,8 +1116,7 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp) { case CmpStmt::ICMP_EQ: case CmpStmt::FCMP_OEQ: - case CmpStmt::FCMP_UEQ: - { + case CmpStmt::FCMP_UEQ: { if (lhs.hasIntersect(rhs)) { resVal = IntervalValue(0, 1); @@ -1265,8 +1133,7 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp) } case CmpStmt::ICMP_NE: case CmpStmt::FCMP_ONE: - case CmpStmt::FCMP_UNE: - { + case CmpStmt::FCMP_UNE: { if (lhs.hasIntersect(rhs)) { resVal = IntervalValue(0, 1); @@ -1284,8 +1151,7 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp) case CmpStmt::ICMP_UGT: case CmpStmt::ICMP_SGT: case CmpStmt::FCMP_OGT: - case CmpStmt::FCMP_UGT: - { + case CmpStmt::FCMP_UGT: { if (lhs.size() == 1 && rhs.size() == 1) { resVal = IntervalValue(*lhs.begin() > *rhs.begin()); @@ -1299,8 +1165,7 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp) case CmpStmt::ICMP_UGE: case CmpStmt::ICMP_SGE: case CmpStmt::FCMP_OGE: - case CmpStmt::FCMP_UGE: - { + case CmpStmt::FCMP_UGE: { if (lhs.size() == 1 && rhs.size() == 1) { resVal = IntervalValue(*lhs.begin() >= *rhs.begin()); @@ -1314,8 +1179,7 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp) case CmpStmt::ICMP_ULT: case CmpStmt::ICMP_SLT: case CmpStmt::FCMP_OLT: - case CmpStmt::FCMP_ULT: - { + case CmpStmt::FCMP_ULT: { if (lhs.size() == 1 && rhs.size() == 1) { resVal = IntervalValue(*lhs.begin() < *rhs.begin()); @@ -1329,8 +1193,7 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp) case CmpStmt::ICMP_ULE: case CmpStmt::ICMP_SLE: case CmpStmt::FCMP_OLE: - case CmpStmt::FCMP_ULE: - { + case CmpStmt::FCMP_ULE: { if (lhs.size() == 1 && rhs.size() == 1) { resVal = IntervalValue(*lhs.begin() <= *rhs.begin()); @@ -1361,31 +1224,36 @@ void AbstractInterpretation::updateStateOnCmp(const CmpStmt *cmp) } } } + if (hasAbsValue(cmp->getRes(), node)) + { + const AbstractValue& result = getAbsValue(cmp->getRes(), node); + assignDomainInterval(node, cmp->getRes(), result.getInterval()); + } } -void AbstractInterpretation::updateStateOnLoad(const LoadStmt *load) +void AbstractInterpretation::updateStateOnLoad(const LoadStmt* load) { const ICFGNode* node = load->getICFGNode(); AbstractValue loaded = loadValue(SVFUtil::cast(load->getRHSVar()), node); updateAbsValue(load->getLHSVar(), loaded, node); + assignDomainInterval(node, load->getLHSVar(), loaded.getInterval()); } -void AbstractInterpretation::updateStateOnStore(const StoreStmt *store) +void AbstractInterpretation::updateStateOnStore(const StoreStmt* store) { const ICFGNode* node = store->getICFGNode(); AbstractValue val = getAbsValue(store->getRHSVar(), node); storeValue(SVFUtil::cast(store->getLHSVar()), val, node); } -void AbstractInterpretation::updateStateOnCopy(const CopyStmt *copy) +void AbstractInterpretation::updateStateOnCopy(const CopyStmt* copy) { const ICFGNode* node = copy->getICFGNode(); const SVFVar* lhsVar = copy->getLHSVar(); const SVFVar* rhsVar = copy->getRHSVar(); - auto getZExtValue = [&](const SVFVar* var) - { + auto getZExtValue = [&](const SVFVar* var) { const SVFType* type = var->getType(); if (SVFUtil::isa(type)) { @@ -1396,7 +1264,8 @@ void AbstractInterpretation::updateStateOnCopy(const CopyStmt *copy) if (bits == 8) { int8_t signed_i8_value = val.getInterval().getIntNumeral(); - u32_t unsigned_value = static_cast(signed_i8_value); + u32_t unsigned_value = + static_cast(signed_i8_value); return IntervalValue(unsigned_value, unsigned_value); } else if (bits == 16) @@ -1414,10 +1283,12 @@ void AbstractInterpretation::updateStateOnCopy(const CopyStmt *copy) else if (bits == 64) { s64_t signed_i64_value = val.getInterval().getIntNumeral(); - return IntervalValue((s64_t)signed_i64_value, (s64_t)signed_i64_value); + return IntervalValue((s64_t)signed_i64_value, + (s64_t)signed_i64_value); } else - assert(false && "cannot support int type other than u8/16/32/64"); + assert(false && + "cannot support int type other than u8/16/32/64"); } else { @@ -1427,10 +1298,10 @@ void AbstractInterpretation::updateStateOnCopy(const CopyStmt *copy) return IntervalValue::top(); }; - auto getTruncValue = [&](const SVFVar* var, const SVFType* dstType) - { - const IntervalValue& itv = getAbsValue(var, node).getInterval(); - if(itv.isBottom()) return itv; + auto getTruncValue = [&](const SVFVar* var, const SVFType* dstType) { + const IntervalValue itv = getAbsValue(var, node).getInterval(); + if (itv.isBottom()) + return itv; s64_t int_lb = itv.lb().getIntNumeral(); s64_t int_ub = itv.ub().getIntNumeral(); u32_t dst_bits = dstType->getByteSize() * 8; @@ -1460,8 +1331,11 @@ void AbstractInterpretation::updateStateOnCopy(const CopyStmt *copy) } else { - assert(false && "cannot support dst int type other than u8/16/32"); - abort(); + // The interval carrier stores machine numerals in s64_t, so + // uncommon truncation targets (for example i64 from i128) cannot + // always be converted exactly here. Falling back to the full + // destination-type range is sound and lets analysis continue. + return utils->getRangeLimitFromType(dstType); } }; @@ -1505,7 +1379,7 @@ void AbstractInterpretation::updateStateOnCopy(const CopyStmt *copy) } else if (copy->getCopyKind() == CopyStmt::INTTOPTR) { - //insert nullptr + // insert nullptr } else if (copy->getCopyKind() == CopyStmt::PTRTOINT) { @@ -1518,4 +1392,5 @@ void AbstractInterpretation::updateStateOnCopy(const CopyStmt *copy) } else assert(false && "undefined copy kind"); + updateDomainOnCopy(copy); } diff --git a/svf/lib/AE/Svfexe/AbstractStateManager.cpp b/svf/lib/AE/Svfexe/AbstractStateManager.cpp index 6b0324581c..01ff0616af 100644 --- a/svf/lib/AE/Svfexe/AbstractStateManager.cpp +++ b/svf/lib/AE/Svfexe/AbstractStateManager.cpp @@ -20,178 +20,28 @@ // //===----------------------------------------------------------------------===// // -// State-access bodies factored out of AbstractInterpretation.cpp / -// SparseAbstractInterpretation.cpp. Class declarations stay in their -// respective headers; this file just hosts the implementations of the -// methods that used to live on the (now-folded) AbstractStateManager — -// trace lookup, value get/has/update, GEP / load / store helpers, and -// def/use queries — for dense and semi-sparse. Full-sparse stubs and -// SVFG-backed overrides live in SparseAbstractInterpretation.cpp. +// Representation-independent GEP and type helpers shared by Box-backed +// dense, semi-sparse, and full-sparse abstract execution. // #include "AE/Svfexe/AbstractInterpretation.h" -#include "AE/Svfexe/SparseAbstractInterpretation.h" #include "SVFIR/SVFIR.h" -#include "Util/Options.h" using namespace SVF; -// ===================================================================== -// Dense (AbstractInterpretation) — direct trace lookup; sparse -// subclasses override the virtuals below. -// ===================================================================== - -AbstractState& AbstractInterpretation::getAbsState(const ICFGNode* node) -{ - if (abstractTrace.count(node) == 0) - { - assert(false && "No preAbsTrace for this node"); - abort(); - } - return abstractTrace[node]; -} - -void AbstractInterpretation::updateAbsState(const ICFGNode* node, const AbstractState& state) -{ - abstractTrace[node] = state; -} - -void AbstractInterpretation::joinStates(AbstractState& dst, const AbstractState& src) -{ - dst.joinWith(src); -} - -bool AbstractInterpretation::hasAbsState(const ICFGNode* node) -{ - return abstractTrace.count(node) != 0; -} - -/// Dense base: direct trace lookup, with a top sentinel for genuinely -/// missing entries (e.g. function parameters like argc, never written -/// before first read). Sparse subclasses override with a def-site -/// resolution chain. -/// -/// The "in map" check is a raw map.count — NOT inVarToValTable / -/// inVarToAddrsTable, which gate on isInterval / isAddr. SVF -/// canonically represents uninit and null-pointer shapes as -/// (interval=bottom ∧ addrs=∅); those predicates would falsely report -/// such an entry as "not present", and the top fallback below would -/// then clobber the very signal NullptrDerefDetector::isUninit keys off. -const AbstractValue& AbstractInterpretation::getAbsValue(const ValVar* var, const ICFGNode* node) -{ - u32_t id = var->getId(); - AbstractState& as = abstractTrace[node]; - if (as.getVarToVal().count(id)) - return as[id]; - as[id] = IntervalValue::top(); - return as[id]; -} - -const AbstractValue& AbstractInterpretation::getAbsValue(const ObjVar* var, const ICFGNode* node) -{ - AbstractState& as = getAbsState(node); - u32_t addr = AbstractState::getVirtualMemAddress(var->getId()); - return as.load(addr); -} - -const AbstractValue& AbstractInterpretation::getAbsValue(const SVFVar* var, const ICFGNode* node) -{ - if (const ObjVar* objVar = SVFUtil::dyn_cast(var)) - return getAbsValue(objVar, node); - if (const ValVar* valVar = SVFUtil::dyn_cast(var)) - return getAbsValue(valVar, node); - assert(false && "Unknown SVFVar kind"); - abort(); -} - -/// Dense base: direct existence check at `node`. Mirrors the simplified -/// getAbsValue lookup — uses raw map.contains rather than -/// inVar*Table predicates, which would falsely report neutral -/// (interval=bottom ∧ addrs=∅) entries as "not present". -bool AbstractInterpretation::hasAbsValue(const ValVar* var, const ICFGNode* node) const -{ - auto it = abstractTrace.find(node); - if (it == abstractTrace.end()) - return false; - return it->second.getVarToVal().count(var->getId()) != 0; -} - -bool AbstractInterpretation::hasAbsValue(const ObjVar* var, const ICFGNode* node) const -{ - auto it = abstractTrace.find(node); - if (it == abstractTrace.end()) - return false; - return it->second.getLocToVal().count(var->getId()) != 0; -} - -bool AbstractInterpretation::hasAbsValue(const SVFVar* var, const ICFGNode* node) const +const AbstractDomain::AbstractState* AbstractInterpretation:: + getScalarAbstractState(const FunObjVar*) const { - if (const ObjVar* objVar = SVFUtil::dyn_cast(var)) - return hasAbsValue(objVar, node); - if (const ValVar* valVar = SVFUtil::dyn_cast(var)) - return hasAbsValue(valVar, node); - return false; -} - -void AbstractInterpretation::updateAbsValue(const ValVar* var, const AbstractValue& val, const ICFGNode* node) -{ - abstractTrace[node][var->getId()] = val; -} - -void AbstractInterpretation::updateAbsValue(const ObjVar* var, const AbstractValue& val, const ICFGNode* node) -{ - AbstractState& as = getAbsState(node); - u32_t addr = AbstractState::getVirtualMemAddress(var->getId()); - as.store(addr, val); -} - -void AbstractInterpretation::updateAbsValue(const SVFVar* var, const AbstractValue& val, const ICFGNode* node) -{ - if (const ObjVar* objVar = SVFUtil::dyn_cast(var)) - updateAbsValue(objVar, val, node); - else if (const ValVar* valVar = SVFUtil::dyn_cast(var)) - updateAbsValue(valVar, val, node); - else - assert(false && "Unknown SVFVar kind"); -} - -void AbstractInterpretation::getAbsState(const Set& vars, AbstractState& result, const ICFGNode* node) -{ - AbstractState& as = getAbsState(node); - for (const ValVar* var : vars) - { - u32_t id = var->getId(); - result[id] = as[id]; - } + return nullptr; } -void AbstractInterpretation::getAbsState(const Set& vars, AbstractState& result, const ICFGNode* node) +const AbstractDomain::AbstractState* AbstractInterpretation:: + getScalarAbstractState(const ValVar*) const { - AbstractState& as = getAbsState(node); - for (const ObjVar* var : vars) - { - u32_t addr = AbstractState::getVirtualMemAddress(var->getId()); - result.store(addr, as.load(addr)); - } + return nullptr; } -void AbstractInterpretation::getAbsState(const Set& vars, AbstractState& result, const ICFGNode* node) -{ - AbstractState& as = getAbsState(node); - for (const SVFVar* var : vars) - { - if (const ValVar* valVar = SVFUtil::dyn_cast(var)) - { - u32_t id = valVar->getId(); - result[id] = as[id]; - } - else if (const ObjVar* objVar = SVFUtil::dyn_cast(var)) - { - u32_t addr = AbstractState::getVirtualMemAddress(objVar->getId()); - result.store(addr, as.load(addr)); - } - } -} +void AbstractInterpretation::finalizeAbstractState(const ICFGNode*) {} IntervalValue AbstractInterpretation::getGepElementIndex(const GepStmt* gep) { @@ -206,7 +56,8 @@ IntervalValue AbstractInterpretation::getGepElementIndex(const GepStmt* gep) const SVFType* type = gep->getOffsetVarAndGepTypePairVec()[i].second; s64_t idxLb, idxUb; - if (const ConstIntValVar* constInt = SVFUtil::dyn_cast(var)) + if (const ConstIntValVar* constInt = + SVFUtil::dyn_cast(var)) idxLb = idxUb = constInt->getSExtValue(); else { @@ -222,15 +73,21 @@ IntervalValue AbstractInterpretation::getGepElementIndex(const GepStmt* gep) if (SVFUtil::isa(type)) { - u32_t elemNum = gep->getAccessPath().getElementNum(gep->getAccessPath().gepSrcPointeeType()); - idxLb = (double)Options::MaxFieldLimit() / elemNum < idxLb ? Options::MaxFieldLimit() : idxLb * elemNum; - idxUb = (double)Options::MaxFieldLimit() / elemNum < idxUb ? Options::MaxFieldLimit() : idxUb * elemNum; + u32_t elemNum = gep->getAccessPath().getElementNum( + gep->getAccessPath().gepSrcPointeeType()); + idxLb = (double)Options::MaxFieldLimit() / elemNum < idxLb + ? Options::MaxFieldLimit() + : idxLb * elemNum; + idxUb = (double)Options::MaxFieldLimit() / elemNum < idxUb + ? Options::MaxFieldLimit() + : idxUb * elemNum; } else { if (Options::ModelArrays()) { - const std::vector& so = PAG::getPAG()->getTypeInfo(type)->getFlattenedElemIdxVec(); + const std::vector& so = + PAG::getPAG()->getTypeInfo(type)->getFlattenedElemIdxVec(); if (so.empty() || idxUb >= (APOffset)so.size() || idxLb < 0) idxLb = idxUb = 0; else @@ -259,48 +116,63 @@ IntervalValue AbstractInterpretation::getGepByteOffset(const GepStmt* gep) IntervalValue res(0); for (int i = gep->getOffsetVarAndGepTypePairVec().size() - 1; i >= 0; i--) { - const ValVar* idxOperandVar = gep->getOffsetVarAndGepTypePairVec()[i].first; - const SVFType* idxOperandType = gep->getOffsetVarAndGepTypePairVec()[i].second; + const ValVar* idxOperandVar = + gep->getOffsetVarAndGepTypePairVec()[i].first; + const SVFType* idxOperandType = + gep->getOffsetVarAndGepTypePairVec()[i].second; - if (SVFUtil::isa(idxOperandType) || SVFUtil::isa(idxOperandType)) + if (SVFUtil::isa(idxOperandType) || + SVFUtil::isa(idxOperandType)) { u32_t elemByteSize = 1; - if (const SVFArrayType* arrOperandType = SVFUtil::dyn_cast(idxOperandType)) - elemByteSize = arrOperandType->getTypeOfElement()->getByteSize(); + if (const SVFArrayType* arrOperandType = + SVFUtil::dyn_cast(idxOperandType)) + elemByteSize = + arrOperandType->getTypeOfElement()->getByteSize(); else if (SVFUtil::isa(idxOperandType)) - elemByteSize = gep->getAccessPath().gepSrcPointeeType()->getByteSize(); + elemByteSize = + gep->getAccessPath().gepSrcPointeeType()->getByteSize(); else assert(false && "idxOperandType must be ArrType or PtrType"); - if (const ConstIntValVar* op = SVFUtil::dyn_cast(idxOperandVar)) + if (const ConstIntValVar* op = + SVFUtil::dyn_cast(idxOperandVar)) { - s64_t lb = (double)Options::MaxFieldLimit() / elemByteSize >= op->getSExtValue() - ? op->getSExtValue() * elemByteSize - : Options::MaxFieldLimit(); + s64_t lb = (double)Options::MaxFieldLimit() / elemByteSize >= + op->getSExtValue() + ? op->getSExtValue() * elemByteSize + : Options::MaxFieldLimit(); res = res + IntervalValue(lb, lb); } else { - IntervalValue idxVal = getAbsValue(idxOperandVar, node).getInterval(); + IntervalValue idxVal = + getAbsValue(idxOperandVar, node).getInterval(); if (idxVal.isBottom()) res = res + IntervalValue(0, 0); else { - s64_t ub = (idxVal.ub().getIntNumeral() < 0) ? 0 - : (double)Options::MaxFieldLimit() / elemByteSize >= idxVal.ub().getIntNumeral() - ? elemByteSize * idxVal.ub().getIntNumeral() - : Options::MaxFieldLimit(); - s64_t lb = (idxVal.lb().getIntNumeral() < 0) ? 0 - : (double)Options::MaxFieldLimit() / elemByteSize >= idxVal.lb().getIntNumeral() - ? elemByteSize * idxVal.lb().getIntNumeral() - : Options::MaxFieldLimit(); + s64_t ub = + (idxVal.ub().getIntNumeral() < 0) ? 0 + : (double)Options::MaxFieldLimit() / elemByteSize >= + idxVal.ub().getIntNumeral() + ? elemByteSize * idxVal.ub().getIntNumeral() + : Options::MaxFieldLimit(); + s64_t lb = + (idxVal.lb().getIntNumeral() < 0) ? 0 + : (double)Options::MaxFieldLimit() / elemByteSize >= + idxVal.lb().getIntNumeral() + ? elemByteSize * idxVal.lb().getIntNumeral() + : Options::MaxFieldLimit(); res = res + IntervalValue(lb, ub); } } } - else if (const SVFStructType* structOperandType = SVFUtil::dyn_cast(idxOperandType)) + else if (const SVFStructType* structOperandType = + SVFUtil::dyn_cast(idxOperandType)) { - res = res + IntervalValue(gep->getAccessPath().getStructFieldOffset(idxOperandVar, structOperandType)); + res = res + IntervalValue(gep->getAccessPath().getStructFieldOffset( + idxOperandVar, structOperandType)); } else { @@ -310,59 +182,62 @@ IntervalValue AbstractInterpretation::getGepByteOffset(const GepStmt* gep) return res; } -AddressValue AbstractInterpretation::getGepObjAddrs(const ValVar* pointer, IntervalValue offset) +AddressValue AbstractInterpretation::getGepObjAddrs(const ValVar* pointer, + IntervalValue offset) { const ICFGNode* node = pointer->getICFGNode(); AddressValue gepAddrs; - AbstractState& as = getAbsState(node); - APOffset lb = offset.lb().getIntNumeral() < Options::MaxFieldLimit() ? offset.lb().getIntNumeral() - : Options::MaxFieldLimit(); - APOffset ub = offset.ub().getIntNumeral() < Options::MaxFieldLimit() ? offset.ub().getIntNumeral() - : Options::MaxFieldLimit(); + APOffset lb = offset.lb().getIntNumeral() < Options::MaxFieldLimit() + ? offset.lb().getIntNumeral() + : Options::MaxFieldLimit(); + APOffset ub = offset.ub().getIntNumeral() < Options::MaxFieldLimit() + ? offset.ub().getIntNumeral() + : Options::MaxFieldLimit(); for (APOffset i = lb; i <= ub; i++) { const AbstractValue& addrs = getAbsValue(pointer, node); for (const auto& addr : addrs.getAddrs()) { - s64_t baseObj = as.getIDFromAddr(addr); - assert(SVFUtil::isa(svfir->getSVFVar(baseObj)) && "Fail to get the base object address!"); + s64_t baseObj = objectIdFromAddress(addr); + assert(SVFUtil::isa(svfir->getSVFVar(baseObj)) && + "Fail to get the base object address!"); NodeID gepObj = svfir->getGepObjVar(baseObj, i); - as[gepObj] = AddressValue(AbstractState::getVirtualMemAddress(gepObj)); - gepAddrs.insert(AbstractState::getVirtualMemAddress(gepObj)); + gepAddrs.insert(AddressValue::getVirtualMemAddress(gepObj)); } } return gepAddrs; } -AbstractValue AbstractInterpretation::loadValue(const ValVar* pointer, const ICFGNode* node) +AbstractValue AbstractInterpretation::loadValue(const ValVar* pointer, + const ICFGNode* node) { const AbstractValue& ptrVal = getAbsValue(pointer, node); - AbstractState& as = getAbsState(node); AbstractValue res; for (auto addr : ptrVal.getAddrs()) { - res.join_with( - getAbsValue(svfir->getSVFVar(as.getIDFromAddr(addr)), node)); + res.join_with(getMemoryValue(addr, node)); } return res; } -void AbstractInterpretation::storeValue(const ValVar* pointer, const AbstractValue& val, const ICFGNode* node) +void AbstractInterpretation::storeValue(const ValVar* pointer, + const AbstractValue& val, + const ICFGNode* node) { const AbstractValue& ptrVal = getAbsValue(pointer, node); - AbstractState& as = getAbsState(node); for (auto addr : ptrVal.getAddrs()) - updateAbsValue(svfir->getSVFVar(as.getIDFromAddr(addr)), val, node); + updateMemoryValue(addr, val, node); } -const SVFType* AbstractInterpretation::getPointeeElement(const ObjVar* var, const ICFGNode* node) +const SVFType* AbstractInterpretation::getPointeeElement(const ObjVar* var, + const ICFGNode* node) { const AbstractValue& ptrVal = getAbsValue(var, node); if (!ptrVal.isAddr()) return nullptr; for (auto addr : ptrVal.getAddrs()) { - NodeID objId = getAbsState(node).getIDFromAddr(addr); + NodeID objId = objectIdFromAddress(addr); if (objId == 0) continue; return svfir->getBaseObject(objId)->getType(); @@ -391,7 +266,8 @@ u32_t AbstractInterpretation::getAllocaInstByteSize(const AddrStmt* addr) if (itv.isBottom()) itv = IntervalValue(Options::MaxFieldLimit()); res = res * itv.ub().getIntNumeral() > Options::MaxFieldLimit() - ? Options::MaxFieldLimit() : res * itv.ub().getIntNumeral(); + ? Options::MaxFieldLimit() + : res * itv.ub().getIntNumeral(); } return (u32_t)res; } @@ -399,4 +275,3 @@ u32_t AbstractInterpretation::getAllocaInstByteSize(const AddrStmt* addr) assert(false && "Addr rhs value is not ObjVar"); abort(); } - diff --git a/svf/lib/AE/Svfexe/DenseAbstractInterpretation.cpp b/svf/lib/AE/Svfexe/DenseAbstractInterpretation.cpp new file mode 100644 index 0000000000..3fc1ad9b09 --- /dev/null +++ b/svf/lib/AE/Svfexe/DenseAbstractInterpretation.cpp @@ -0,0 +1,1025 @@ +//===- DenseAbstractInterpretation.cpp -- Domain-backed dense AE --------===// + +#include "AE/Svfexe/DenseAbstractInterpretation.h" + +#include "SVFIR/SVFIR.h" +#include "Util/Options.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace SVF +{ + +namespace AD = AbstractDomain; + +namespace +{ + +std::vector orderedIncomingEdges(const ICFGNode* node) +{ + std::vector edges(node->getInEdges().begin(), + node->getInEdges().end()); + std::sort(edges.begin(), edges.end(), + [](const ICFGEdge* lhs, const ICFGEdge* rhs) { + return std::make_tuple(lhs->getSrcID(), + lhs->getEdgeKindWithoutMask()) < + std::make_tuple(rhs->getSrcID(), + rhs->getEdgeKindWithoutMask()); + }); + return edges; +} + +s64_t toSigned64(const mpz_class& value, bool upper) +{ + if (!mpz_fits_slong_p(value.get_mpz_t())) + return upper ? std::numeric_limits::max() + : std::numeric_limits::min(); + return static_cast(value.get_si()); +} + +BoundedInt lowerBound(const AD::Bound& bound) +{ + if (bound.isMinusInfinity()) + return IntervalValue::minus_infinity(); + if (bound.isPlusInfinity()) + return IntervalValue::plus_infinity(); + AD::Rational integer = bound.isStrict() + ? bound.value().floor() + AD::Rational(1) + : bound.value().ceil(); + return BoundedInt(toSigned64(integer.value().get_num(), false)); +} + +BoundedInt upperBound(const AD::Bound& bound) +{ + if (bound.isPlusInfinity()) + return IntervalValue::plus_infinity(); + if (bound.isMinusInfinity()) + return IntervalValue::minus_infinity(); + AD::Rational integer = bound.isStrict() + ? bound.value().ceil() - AD::Rational(1) + : bound.value().floor(); + return BoundedInt(toSigned64(integer.value().get_num(), true)); +} + +IntervalValue projectInterval(const AD::Interval& interval) +{ + if (interval.isBottom()) + return IntervalValue::bottom(); + return IntervalValue(lowerBound(interval.lower()), + upperBound(interval.upper())); +} + +AD::ConstraintKind negatePredicate(AD::ConstraintKind kind) +{ + switch (kind) + { + case AD::ConstraintKind::Equal: + return AD::ConstraintKind::NotEqual; + case AD::ConstraintKind::NotEqual: + return AD::ConstraintKind::Equal; + case AD::ConstraintKind::LessThan: + return AD::ConstraintKind::GreaterEqual; + case AD::ConstraintKind::LessEqual: + return AD::ConstraintKind::GreaterThan; + case AD::ConstraintKind::GreaterThan: + return AD::ConstraintKind::LessEqual; + case AD::ConstraintKind::GreaterEqual: + return AD::ConstraintKind::LessThan; + } + return kind; +} + +bool constraintKind(u32_t predicate, AD::ConstraintKind& kind) +{ + switch (predicate) + { + case CmpStmt::ICMP_EQ: + kind = AD::ConstraintKind::Equal; + return true; + case CmpStmt::ICMP_NE: + kind = AD::ConstraintKind::NotEqual; + return true; + case CmpStmt::ICMP_SLT: + kind = AD::ConstraintKind::LessThan; + return true; + case CmpStmt::ICMP_SLE: + kind = AD::ConstraintKind::LessEqual; + return true; + case CmpStmt::ICMP_SGT: + kind = AD::ConstraintKind::GreaterThan; + return true; + case CmpStmt::ICMP_SGE: + kind = AD::ConstraintKind::GreaterEqual; + return true; + default: + return false; + } +} + +template +void alignEnvironments(DenseStateT& lhs, DenseStateT& rhs) +{ + if (lhs.numerical().environment() == rhs.numerical().environment()) + return; + const AD::VariableEnvironment environment = + lhs.numerical().environment().merge(rhs.numerical().environment()); + if (lhs.numerical().environment() != environment) + lhs.changeEnvironment(environment); + if (rhs.numerical().environment() != environment) + rhs.changeEnvironment(environment); +} + +} // namespace + +template +DenseAbstractInterpretation::DenseAbstractInterpretation() + : adapter_(*svfir) +{ +} + +template +void DenseAbstractInterpretation::runOnModule() +{ + AbstractInterpretation::runOnModule(); +} + +template +void DenseAbstractInterpretation::handleGlobalNode() +{ + const ICFGNode* node = icfg->getGlobalICFGNode(); + // The global ICFG node contains address initializers for local pointer + // values from every function. Growing a relational environment once per + // initializer repeatedly rebuilds and normalizes the same state. Batch + // those result dimensions into the initial top state instead. + std::vector initialDeclarations; + std::set initialVariables; + auto addInitialValue = [&](const SVFVar* variable) { + const auto* value = SVFUtil::dyn_cast(variable); + if (!value || !adapter_.contains(*value)) + return; + const AD::Variable symbol = adapter_.variable(*value); + if (!adapter_.environment().contains(symbol) && + initialVariables.insert(symbol).second) + initialDeclarations.push_back(adapter_.declaration(symbol)); + }; + for (const SVFStmt* statement : node->getSVFStmts()) + { + if (const auto* assignment = SVFUtil::dyn_cast(statement)) + addInitialValue(assignment->getLHSVar()); + else if (const auto* multi = + SVFUtil::dyn_cast(statement)) + addInitialValue(multi->getRes()); + } + if (const auto* blackHole = SVFUtil::dyn_cast( + svfir->getGNode(PAG::getPAG()->getBlkPtr()))) + addInitialValue(blackHole); + const AD::VariableEnvironment initialEnvironment = + adapter_.environment().add(std::move(initialDeclarations)); + denseTrace_.insert_or_assign( + node, DenseState(makeNumericalTop(initialEnvironment), + adapter_.memoryLayout())); + for (const SVFStmt* statement : node->getSVFStmts()) + handleSVFStatement(statement); + + AbstractValue blackHole(IntervalValue::top()); + blackHole.getAddrs().insert(BlackHoleObjAddr); + if (const auto* variable = SVFUtil::dyn_cast( + svfir->getGNode(PAG::getPAG()->getBlkPtr()))) + updateAbsValue(variable, blackHole, node); +} + +template +AbstractValue DenseAbstractInterpretation< + NumericalStateT>::initializeObjectAddress(const ObjVar* object, + const ICFGNode* node) +{ + DenseState& denseState = ensureState(node); + if (adapter_.contains(*object)) + denseState.allocate(adapter_.location(*object)); + + const BaseObjVar* base = PAG::getPAG()->getBaseObject(object->getId()); + if (base->isConstDataOrConstGlobal() || base->isConstantArray() || + base->isConstantStruct()) + { + if (const auto* integer = SVFUtil::dyn_cast(object)) + return IntervalValue(integer->getSExtValue()); + if (const auto* floating = SVFUtil::dyn_cast(object)) + return IntervalValue(floating->getFPValue(), + floating->getFPValue()); + if (SVFUtil::isa(object)) + return IntervalValue(0, 0); + if (!SVFUtil::isa(object)) + return IntervalValue::top(); + } + return AddressValue(AddressValue::getVirtualMemAddress(object->getId())); +} + +template +const AbstractDomain::AbstractState& DenseAbstractInterpretation< + NumericalStateT>::getAbstractState(const ICFGNode* node) const +{ + return state(node); +} + +template +bool DenseAbstractInterpretation::hasAbsState( + const ICFGNode* node) const +{ + return denseTrace_.count(node) != 0; +} + +template +typename DenseAbstractInterpretation::DenseState +DenseAbstractInterpretation::topState( + const ICFGNode* node) const +{ + return DenseState( + makeNumericalTop(adapter_.environment(node ? node->getFun() : nullptr)), + adapter_.memoryLayout()); +} + +template +typename DenseAbstractInterpretation::DenseState +DenseAbstractInterpretation::bottomState( + const ICFGNode* node) const +{ + return DenseState(makeNumericalBottom(adapter_.environment( + node ? node->getFun() : nullptr)), + adapter_.memoryLayout()); +} + +template +NumericalStateT DenseAbstractInterpretation::makeNumericalTop( + const AD::VariableEnvironment& environment) const +{ + if constexpr (std::is_same_v) + return AD::BoxState::top(environment); + else + return NumericalStateT::top(environment); +} + +template +NumericalStateT DenseAbstractInterpretation:: + makeNumericalBottom(const AD::VariableEnvironment& environment) const +{ + if constexpr (std::is_same_v) + return AD::BoxState::bottom(environment); + else + return NumericalStateT::bottom(environment); +} + +template +typename DenseAbstractInterpretation::DenseState& +DenseAbstractInterpretation::ensureState(const ICFGNode* node) +{ + auto iterator = denseTrace_.find(node); + if (iterator == denseTrace_.end()) + iterator = denseTrace_.emplace(node, topState(node)).first; + return iterator->second; +} + +template +const typename DenseAbstractInterpretation::DenseState& +DenseAbstractInterpretation::state(const ICFGNode* node) const +{ + const auto iterator = denseTrace_.find(node); + if (iterator == denseTrace_.end()) + throw std::out_of_range("no dense abstract state for ICFG node"); + return iterator->second; +} + +template +void DenseAbstractInterpretation::resetAbstractState( + const ICFGNode* node) +{ + denseTrace_.insert_or_assign(node, topState(node)); +} + +template +void DenseAbstractInterpretation::copyAbstractState( + const ICFGNode* source, const ICFGNode* destination) +{ + DenseState copy = state(source); + const AD::VariableEnvironment& destinationEnvironment = + adapter_.environment(destination->getFun()); + if (copy.numerical().environment() == destinationEnvironment) + { + denseTrace_.insert_or_assign(destination, std::move(copy)); + return; + } + + copy.changeEnvironment(destinationEnvironment); + denseTrace_.insert_or_assign(destination, std::move(copy)); +} + +template +std::unique_ptr DenseAbstractInterpretation< + NumericalStateT>::cloneAbstractState(const ICFGNode* node) const +{ + return state(node).clone(); +} + +template +bool DenseAbstractInterpretation::isAbstractStateEquivalent( + const ICFGNode* node, const AbstractDomain::AbstractState& snapshot) const +{ + DenseState current = state(node); + DenseState previous = static_cast(snapshot); + alignEnvironments(current, previous); + return current.isEquivalentTo(previous) == + AbstractDomain::CheckResult::True; +} + +template +std::unique_ptr DenseAbstractInterpretation< + NumericalStateT>::cloneCycleHeadState(const ICFGCycleWTO* cycle) +{ + return cloneAbstractState(cycle->head()->getICFGNode()); +} + +template +bool DenseAbstractInterpretation::widenCycleState( + const AbstractDomain::AbstractState& previous, + const AbstractDomain::AbstractState& current, const ICFGCycleWTO* cycle) +{ + DenseState previousDense = static_cast(previous); + DenseState currentDense = static_cast(current); + alignEnvironments(previousDense, currentDense); + DenseState next = previousDense; + next.widenWith(currentDense); + const bool fixpoint = + next.isEquivalentTo(previousDense) == AbstractDomain::CheckResult::True; + const ICFGNode* head = cycle->head()->getICFGNode(); + denseTrace_.insert_or_assign(head, std::move(next)); + return fixpoint; +} + +template +bool DenseAbstractInterpretation::narrowCycleState( + const AbstractDomain::AbstractState& previous, + const AbstractDomain::AbstractState& current, const ICFGCycleWTO* cycle) +{ + const ICFGNode* head = cycle->head()->getICFGNode(); + if (!shouldApplyNarrowing(head->getFun())) + return true; + DenseState previousDense = static_cast(previous); + DenseState currentDense = static_cast(current); + alignEnvironments(previousDense, currentDense); + // Sparse transfers may materialize a new MemorySSA/cycle facet during the + // descending phase. Enforce narrowing's generic next <= current contract. + // The normal descending path already satisfies that contract. Avoid + // rebuilding and closing a relational meet when the lattice check proves + // that the meet would be exactly currentDense. False and Unknown retain + // the original conservative meet. + if (currentDense.isSubsetOf(previousDense) != AD::CheckResult::True) + currentDense.meetWith(previousDense); + DenseState next = previousDense; + next.narrowWith(currentDense); + const bool fixpoint = + next.isEquivalentTo(previousDense) == AbstractDomain::CheckResult::True; + if (!fixpoint) + denseTrace_.insert_or_assign(head, std::move(next)); + return fixpoint; +} + +template +void DenseAbstractInterpretation::ensureVariable( + DenseState& denseState, AD::Variable variable) const +{ + if (denseState.numerical().environment().contains(variable)) + return; + denseState.changeEnvironment(denseState.numerical().environment().add( + {adapter_.declaration(variable)})); +} + +template +void DenseAbstractInterpretation::assignInterval( + DenseState& denseState, AD::Variable variable, + const IntervalValue& interval) +{ + ensureVariable(denseState, variable); + denseState.numerical().forget(variable); + constrainInterval(denseState, variable, interval); +} + +template +void DenseAbstractInterpretation::constrainInterval( + DenseState& denseState, AD::Variable variable, + const IntervalValue& interval) +{ + if (interval.isBottom()) + return; + + AD::LinearConstraintSet constraints; + AD::LinearExpression expression(variable); + if (!interval.lb().is_minus_infinity()) + { + constraints.push_back(AD::greaterEqual( + expression, + AD::LinearExpression(AD::Rational(interval.lb().getIntNumeral())))); + } + if (!interval.ub().is_plus_infinity()) + { + constraints.push_back(AD::lessEqual( + expression, + AD::LinearExpression(AD::Rational(interval.ub().getIntNumeral())))); + } + denseState.numerical().assumeAll(constraints); +} + +template +void DenseAbstractInterpretation::initializeDomainState( + const ICFGNode* node) +{ + (void)ensureState(node); +} + +template +void DenseAbstractInterpretation::assignDomainInterval( + const ICFGNode* node, const SVFVar* target, const IntervalValue& interval) +{ + DenseState& denseState = ensureState(node); + if (const auto* value = SVFUtil::dyn_cast(target)) + { + if (adapter_.contains(*value)) + { + const AD::Variable variable = adapter_.variable(*value); + if (interval.isBottom() || + projectInterval(denseState.numerical().bound(variable)) + .equals(interval)) + return; + assignInterval(denseState, variable, interval); + } + } + else if (const auto* object = SVFUtil::dyn_cast(target)) + { + if (adapter_.contains(*object)) + { + const AD::Variable variable = adapter_.contentVariable(*object); + if (interval.isBottom() || + projectInterval(denseState.numerical().bound(variable)) + .equals(interval)) + return; + assignInterval(denseState, variable, interval); + } + } +} + +template +void DenseAbstractInterpretation::updateDomainOnBinary( + const BinaryOPStmt* binary, const IntervalValue& result) +{ + DenseState& denseState = ensureState(binary->getICFGNode()); + const auto* target = SVFUtil::dyn_cast(binary->getRes()); + if (!target || !adapter_.contains(*target)) + return; + const AD::Variable targetVariable = adapter_.variable(*target); + + auto operand = [&](const SVFVar* value, + AD::LinearExpression& expression) -> bool { + if (const auto* integer = SVFUtil::dyn_cast(value)) + { + expression = + AD::LinearExpression(AD::Rational(integer->getSExtValue())); + return true; + } + const auto* scalar = SVFUtil::dyn_cast(value); + if (!scalar || !adapter_.contains(*scalar)) + return false; + materializeValue(denseState, scalar, binary->getICFGNode()); + ensureVariable(denseState, adapter_.variable(*scalar)); + expression = AD::LinearExpression(adapter_.variable(*scalar)); + return true; + }; + + AD::LinearExpression lhs; + AD::LinearExpression rhs; + const bool affine = (binary->getOpcode() == BinaryOPStmt::Add || + binary->getOpcode() == BinaryOPStmt::Sub) && + operand(binary->getOpVar(0), lhs) && + operand(binary->getOpVar(1), rhs); + if (!affine) + { + assignInterval(denseState, targetVariable, result); + return; + } + + ensureVariable(denseState, targetVariable); + denseState.numerical().assign( + targetVariable, + binary->getOpcode() == BinaryOPStmt::Add ? lhs + rhs : lhs - rhs); + constrainInterval(denseState, targetVariable, result); +} + +template +void DenseAbstractInterpretation::updateDomainCopyValue( + const ICFGNode* node, const SVFVar* target, const SVFVar* source, + bool exactMathematicalCopy) +{ + const auto* targetValue = SVFUtil::dyn_cast(target); + if (!targetValue || !adapter_.contains(*targetValue)) + return; + DenseState& denseState = ensureState(node); + const AD::Variable targetVariable = adapter_.variable(*targetValue); + const AbstractValue result = getAbsValue(targetValue, node); + if (!result.isInterval()) + { + if (denseState.numerical().environment().contains(targetVariable)) + denseState.numerical().forget(targetVariable); + return; + } + + const auto* sourceValue = SVFUtil::dyn_cast(source); + if (exactMathematicalCopy && sourceValue && adapter_.contains(*sourceValue)) + { + materializeValue(denseState, sourceValue, node); + const AD::Variable sourceVariable = adapter_.variable(*sourceValue); + ensureVariable(denseState, targetVariable); + ensureVariable(denseState, sourceVariable); + denseState.numerical().assign(targetVariable, + AD::LinearExpression(sourceVariable)); + constrainInterval(denseState, targetVariable, result.getInterval()); + } + else + { + assignInterval(denseState, targetVariable, result.getInterval()); + } +} + +template +void DenseAbstractInterpretation::updateDomainOnCopy( + const CopyStmt* copy) +{ + const bool exact = copy->getCopyKind() == CopyStmt::COPYVAL || + copy->getCopyKind() == CopyStmt::SEXT; + updateDomainCopyValue(copy->getICFGNode(), copy->getLHSVar(), + copy->getRHSVar(), exact); +} + +template +void DenseAbstractInterpretation::assignValue( + DenseState& denseState, AD::Variable variable, const AbstractValue& value) +{ + ensureVariable(denseState, variable); + if (value.isInterval()) + assignInterval(denseState, variable, value.getInterval()); + else + denseState.numerical().forget(variable); + + AD::PointeeSet addresses = AD::PointeeSet::bottom(); + for (u32_t address : value.getAddrs()) + { + const NodeID objectId = address & FlippedAddressMask; + const auto* object = + SVFUtil::dyn_cast(svfir->getGNode(objectId)); + if (object && adapter_.contains(*object)) + addresses.insert(adapter_.location(*object)); + } + denseState.pointers().assign(variable, std::move(addresses)); + denseState.shapes().assign(variable, value.isInterval()); +} + +template +void DenseAbstractInterpretation::materializeValue( + DenseState&, const ValVar*, const ICFGNode*) +{ +} + +template +void DenseAbstractInterpretation::forgetValue( + DenseState& denseState, AD::Variable variable) const +{ + if (!denseState.numerical().environment().contains(variable)) + return; + denseState.numerical().forget(variable); + // This helper removes an AE value; it does not model an unknown pointer. + // PointerMap::forget means address-top and would retain an explicit map + // entry for every purged sparse scalar. + denseState.pointers().assign(variable, AD::PointeeSet::bottom()); + denseState.shapes().forget(variable); +} + +template +void DenseAbstractInterpretation::forgetScalarValues( + DenseState& denseState) const +{ + for (const AD::VariableDeclaration& declaration : + denseState.numerical().environment().variables()) + { + if (adapter_.value(declaration.variable)) + forgetValue(denseState, declaration.variable); + } +} + +template +AbstractValue DenseAbstractInterpretation::projectValue( + const DenseState& denseState, AD::Variable variable) const +{ + if (!denseState.numerical().environment().contains(variable)) + return AbstractValue(IntervalValue::top()); + if (!denseState.shapes().isDefined(variable)) + return AbstractValue(); + AbstractValue result; + if (denseState.shapes().hasNumeric(variable)) + result.interval = + projectInterval(denseState.numerical().bound(variable)); + const AD::PointeeSet addresses = denseState.pointers().pointeesOf(variable); + if (addresses.isTop()) + { + result.getAddrs().insert(BlackHoleObjAddr); + return result; + } + for (AD::Location location : addresses.locations()) + { + const ObjVar& object = adapter_.object(location); + result.getAddrs().insert( + AddressValue::getVirtualMemAddress(object.getId())); + } + return result; +} + +template +AbstractValue DenseAbstractInterpretation::getAbsValue( + const ValVar* var, const ICFGNode* node) +{ + if (const auto* integer = SVFUtil::dyn_cast(var)) + return IntervalValue(integer->getSExtValue()); + if (!adapter_.contains(*var)) + return IntervalValue::top(); + + DenseState& denseState = ensureState(node); + const AD::Variable variable = adapter_.variable(*var); + if (!denseState.shapes().isDefined(variable)) + assignValue(denseState, variable, IntervalValue::top()); + AbstractValue value = projectValue(denseState, variable); + if (var->isPointer()) + value.interval = IntervalValue::bottom(); + return value; +} + +template +AbstractValue DenseAbstractInterpretation::getAbsValue( + const ObjVar* var, const ICFGNode* node) +{ + if (!adapter_.contains(*var)) + return AbstractValue(); + DenseState& denseState = ensureState(node); + const AD::Variable content = adapter_.contentVariable(*var); + if (!denseState.shapes().isDefined(content)) + assignValue(denseState, content, AbstractValue()); + return projectValue(denseState, content); +} + +template +AbstractValue DenseAbstractInterpretation::getAbsValue( + const SVFVar* var, const ICFGNode* node) +{ + if (const auto* object = SVFUtil::dyn_cast(var)) + return getAbsValue(object, node); + if (const auto* value = SVFUtil::dyn_cast(var)) + return getAbsValue(value, node); + throw std::invalid_argument("unsupported SVF variable kind"); +} + +template +bool DenseAbstractInterpretation::hasAbsValue( + const ValVar* var, const ICFGNode* node) const +{ + if (SVFUtil::isa(var)) + return true; + if (denseTrace_.count(node) == 0 || !adapter_.contains(*var)) + return false; + return state(node).shapes().isDefined(adapter_.variable(*var)); +} + +template +bool DenseAbstractInterpretation::hasAbsValue( + const ObjVar* var, const ICFGNode* node) const +{ + if (denseTrace_.count(node) == 0 || !adapter_.contains(*var)) + return false; + return state(node).shapes().isDefined(adapter_.contentVariable(*var)); +} + +template +bool DenseAbstractInterpretation::hasAbsValue( + const SVFVar* var, const ICFGNode* node) const +{ + if (const auto* object = SVFUtil::dyn_cast(var)) + return hasAbsValue(object, node); + if (const auto* value = SVFUtil::dyn_cast(var)) + return hasAbsValue(value, node); + return false; +} + +template +void DenseAbstractInterpretation::updateAbsValue( + const ValVar* var, const AbstractValue& value, const ICFGNode* node) +{ + if (adapter_.contains(*var)) + assignValue(ensureState(node), adapter_.variable(*var), value); +} + +template +void DenseAbstractInterpretation::updateAbsValue( + const ObjVar* var, const AbstractValue& value, const ICFGNode* node) +{ + if (adapter_.contains(*var)) + assignValue(ensureState(node), adapter_.contentVariable(*var), value); +} + +template +AbstractValue DenseAbstractInterpretation::getMemoryValue( + u32_t address, const ICFGNode* node) +{ + const auto* object = SVFUtil::dyn_cast( + svfir->getGNode(objectIdFromAddress(address))); + return object ? getAbsValue(object, node) : AbstractValue(); +} + +template +bool DenseAbstractInterpretation::hasMemoryValue( + u32_t address, const ICFGNode* node) const +{ + const auto* object = SVFUtil::dyn_cast( + svfir->getGNode(objectIdFromAddress(address))); + return object && hasAbsValue(object, node); +} + +template +void DenseAbstractInterpretation::updateMemoryValue( + u32_t address, const AbstractValue& value, const ICFGNode* node) +{ + const auto* object = SVFUtil::dyn_cast( + svfir->getGNode(objectIdFromAddress(address))); + if (object) + updateAbsValue(object, value, node); +} + +template +void DenseAbstractInterpretation::markFreedMemory( + u32_t address, const ICFGNode* node) +{ + const auto* object = SVFUtil::dyn_cast( + svfir->getGNode(objectIdFromAddress(address))); + if (object && adapter_.contains(*object)) + ensureState(node).lifetimes().release(adapter_.location(*object)); +} + +template +bool DenseAbstractInterpretation::isFreedMemory( + u32_t address, const ICFGNode* node) const +{ + if (denseTrace_.count(node) == 0) + return false; + const auto* object = SVFUtil::dyn_cast( + svfir->getGNode(objectIdFromAddress(address))); + return object && adapter_.contains(*object) && + state(node).lifetimes().mayBeFreed(adapter_.location(*object)); +} + +template +void DenseAbstractInterpretation::updateAbsValue( + const SVFVar* var, const AbstractValue& value, const ICFGNode* node) +{ + if (const auto* object = SVFUtil::dyn_cast(var)) + updateAbsValue(object, value, node); + else if (const auto* scalar = SVFUtil::dyn_cast(var)) + updateAbsValue(scalar, value, node); + else + throw std::invalid_argument("unsupported SVF variable kind"); +} + +template +AbstractValue DenseAbstractInterpretation::loadValue( + const ValVar* pointer, const ICFGNode* node) +{ + if (!adapter_.contains(*pointer)) + return AbstractInterpretation::loadValue(pointer, node); + DenseState& denseState = ensureState(node); + materializeValue(denseState, pointer, node); + const AD::PointeeSet pointees = + denseState.pointers().pointeesOf(adapter_.variable(*pointer)); + if (pointees.isTop()) + return AbstractValue(IntervalValue::top()); + + AbstractValue result; + for (AD::Location location : pointees.locations()) + { + if (denseState.lifetimes().mayBeFreed(location)) + { + result.join_with(AbstractValue(IntervalValue::top())); + continue; + } + if (denseState.memoryLayout().contains(location)) + { + result.join_with(projectValue( + denseState, denseState.memoryLayout().contentOf(location))); + } + } + return result; +} + +template +void DenseAbstractInterpretation::storeValue( + const ValVar* pointer, const AbstractValue& value, const ICFGNode* node) +{ + if (!adapter_.contains(*pointer)) + { + AbstractInterpretation::storeValue(pointer, value, node); + return; + } + DenseState& denseState = ensureState(node); + materializeValue(denseState, pointer, node); + const AD::PointeeSet pointees = + denseState.pointers().pointeesOf(adapter_.variable(*pointer)); + const bool strong = pointees.isSingleton(); + auto write = [&](AD::Location location) { + if (!denseState.memoryLayout().contains(location)) + return; + const AD::Variable content = + denseState.memoryLayout().contentOf(location); + if (strong) + { + assignValue(denseState, content, value); + return; + } + AbstractValue joined = projectValue(denseState, content); + joined.join_with(value); + assignValue(denseState, content, joined); + }; + + if (pointees.isTop()) + { + for (const auto& [location, content] : + denseState.memoryLayout().cells()) + { + (void)content; + write(location); + } + } + else + { + for (AD::Location location : pointees.locations()) + write(location); + } +} + +template +void DenseAbstractInterpretation::assumeBranch( + const IntraCFGEdge* edge, DenseState& denseState) +{ + const SVFVar* condition = edge->getCondition(); + if (!condition || condition->getInEdges().empty()) + return; + const auto* comparison = + SVFUtil::dyn_cast(*condition->getInEdges().begin()); + if (!comparison) + { + const auto* value = SVFUtil::dyn_cast(condition); + if (!value || !adapter_.contains(*value)) + return; + materializeValue(denseState, value, edge->getSrcNode()); + ensureVariable(denseState, adapter_.variable(*value)); + denseState.assume(AD::equal( + AD::LinearExpression(adapter_.variable(*value)), + AD::LinearExpression(AD::Rational(edge->getSuccessorCondValue())))); + return; + } + + AD::ConstraintKind kind; + if (!constraintKind(comparison->getPredicate(), kind)) + return; + if (edge->getSuccessorCondValue() == 0) + kind = negatePredicate(kind); + + auto operand = [&](const SVFVar* variable, + AD::LinearExpression& expression) -> bool { + if (const auto* integer = SVFUtil::dyn_cast(variable)) + { + expression = + AD::LinearExpression(AD::Rational(integer->getSExtValue())); + return true; + } + const auto* value = SVFUtil::dyn_cast(variable); + if (!value || !adapter_.contains(*value)) + return false; + materializeValue(denseState, value, edge->getSrcNode()); + ensureVariable(denseState, adapter_.variable(*value)); + expression = AD::LinearExpression(adapter_.variable(*value)); + return true; + }; + + AD::LinearExpression lhs; + AD::LinearExpression rhs; + if (!operand(comparison->getOpVar(0), lhs) || + !operand(comparison->getOpVar(1), rhs)) + return; + denseState.assume(AD::LinearConstraint(lhs - rhs, kind)); +} + +template +bool DenseAbstractInterpretation::mergeStatesFromPredecessors( + const ICFGNode* node) +{ + DenseState merged = bottomState(node); + bool hasFeasiblePredecessor = false; + + for (const ICFGEdge* edge : orderedIncomingEdges(node)) + { + const ICFGNode* predecessor = edge->getSrcNode(); + if (denseTrace_.count(predecessor) == 0) + continue; + + bool shouldMerge = false; + const IntraCFGEdge* conditional = SVFUtil::dyn_cast(edge); + if (conditional) + shouldMerge = true; + else if (SVFUtil::isa(edge)) + { + shouldMerge = true; + } + else if (SVFUtil::isa(edge)) + { + shouldMerge = Options::HandleRecur() == TOP; + if (!shouldMerge) + { + const auto* returnSite = SVFUtil::dyn_cast(node); + shouldMerge = + returnSite && + denseTrace_.count(returnSite->getCallICFGNode()) != 0; + } + } + if (!shouldMerge) + continue; + + DenseState source = state(predecessor); + const AD::VariableEnvironment& destinationEnvironment = + adapter_.environment(node->getFun()); + if (source.numerical().environment() != destinationEnvironment) + source.changeEnvironment(destinationEnvironment); + if (conditional && conditional->getCondition()) + { + assumeBranch(conditional, source); + collectBranchRefinement(conditional, source); + } + if (source.isBottom()) + continue; + + // Branch refinement can materialize a condition variable that is not + // present in the destination function's precomputed environment (for + // example, a value returned across a call edge). Join over the union + // environment just as the other fixpoint comparison paths do. + alignEnvironments(merged, source); + merged.joinWith(source); + hasFeasiblePredecessor = true; + } + + if (!hasFeasiblePredecessor) + return false; + denseTrace_.insert_or_assign(node, std::move(merged)); + return true; +} + +template +void DenseAbstractInterpretation::recordBranchRefinement( + NodeID objectId, const IntervalValue& narrowed, + AD::AbstractState& abstractState, const ICFGNode*, const ICFGNode*) +{ + const auto* object = + SVFUtil::dyn_cast(svfir->getGNode(objectId)); + if (!object || !adapter_.contains(*object)) + return; + + DenseState& denseState = static_cast(abstractState); + const AD::Variable content = adapter_.contentVariable(*object); + AbstractValue current = projectValue(denseState, content); + if (!current.isInterval()) + return; + IntervalValue refined = current.getInterval(); + refined.meet_with(narrowed); + assignValue(denseState, content, AbstractValue(refined)); +} + +template +bool DenseAbstractInterpretation::isBranchEdgeFeasibleAt( + const IntraCFGEdge* edge, const ICFGNode* predecessor) +{ + DenseState candidate = state(predecessor); + assumeBranch(edge, candidate); + return !candidate.isBottom(); +} + +#ifndef SVF_DENSE_AE_SUPPRESS_EXPLICIT_INSTANTIATIONS +template class DenseAbstractInterpretation; +#endif + +} // namespace SVF diff --git a/svf/lib/AE/Svfexe/NativeSparseAbstractInterpretation.cpp b/svf/lib/AE/Svfexe/NativeSparseAbstractInterpretation.cpp new file mode 100644 index 0000000000..da3d3630f0 --- /dev/null +++ b/svf/lib/AE/Svfexe/NativeSparseAbstractInterpretation.cpp @@ -0,0 +1,1039 @@ +//===- NativeSparseAbstractInterpretation.cpp -- Domain sparse AE -------===// + +#include "AE/Svfexe/NativeSparseAbstractInterpretation.h" + +#include "Graphs/SVFG.h" +#include "MSSA/SVFGBuilder.h" +#include "SVFIR/SVFIR.h" +#include "Util/Options.h" + +#include +#include +#include +#include +#include + +namespace SVF +{ + +namespace AD = AbstractDomain; + +namespace +{ + +template class PhaseTimer +{ +public: + PhaseTimer(MetricT& metric, bool enabled) + : metric_(metric), enabled_(enabled) + { + if (enabled_) + start_ = Clock::now(); + } + + ~PhaseTimer() + { + if (!enabled_) + return; + ++metric_.calls; + metric_.nanoseconds += static_cast( + std::chrono::duration_cast(Clock::now() - + start_) + .count()); + } + +private: + using Clock = std::chrono::steady_clock; + MetricT& metric_; + bool enabled_; + Clock::time_point start_{}; +}; + +} // namespace + +template +NativeSemiSparseAbstractInterpretation< + NumericalStateT>::NativeSemiSparseAbstractInterpretation() +{ + this->preAnalysis->initCycleValVars(); +} + +template +typename NativeSemiSparseAbstractInterpretation::DenseState +NativeSemiSparseAbstractInterpretation::flowState( + const FunObjVar* function, bool bottom) const +{ + const AD::VariableEnvironment& environment = + this->adapter_.environment(function); + return DenseState(bottom ? this->makeNumericalBottom(environment) + : this->makeNumericalTop(environment), + this->adapter_.memoryLayout()); +} + +template +typename NativeSemiSparseAbstractInterpretation::DenseState& +NativeSemiSparseAbstractInterpretation::scalarState( + const FunObjVar* function) +{ + if constexpr (std::is_same_v) + function = nullptr; + auto iterator = scalarStates_.find(function); + if (iterator == scalarStates_.end()) + { + iterator = + scalarStates_ + .emplace( + function, + DenseState( + this->makeNumericalTop( + std::is_same_v + ? this->adapter_.allScalarEnvironment() + : this->adapter_.scalarEnvironment(function)), + this->adapter_.memoryLayout())) + .first; + } + return iterator->second; +} + +template +const typename NativeSemiSparseAbstractInterpretation< + NumericalStateT>::DenseState* +NativeSemiSparseAbstractInterpretation::findScalarState( + const FunObjVar* function) const +{ + if constexpr (std::is_same_v) + function = nullptr; + const auto iterator = scalarStates_.find(function); + return iterator == scalarStates_.end() ? nullptr : &iterator->second; +} + +template +const AD::AbstractState* NativeSemiSparseAbstractInterpretation< + NumericalStateT>::getScalarAbstractState(const FunObjVar* function) const +{ + return findScalarState(function); +} + +template +const AD::AbstractState* NativeSemiSparseAbstractInterpretation< + NumericalStateT>::getScalarAbstractState(const ValVar* value) const +{ + if (!value) + return nullptr; + const auto iterator = scalarCheckpoints_.find(value); + return iterator == scalarCheckpoints_.end() + ? getScalarAbstractState(value->getFunction()) + : &iterator->second; +} + +template +void NativeSemiSparseAbstractInterpretation::handleGlobalNode() +{ + Base::handleGlobalNode(); + finalizeAbstractState(this->icfg->getGlobalICFGNode()); +} + +template +void NativeSemiSparseAbstractInterpretation::runOnModule() +{ + { + PhaseTimer timer(sparseProfile_.total, Options::AESparseProfile()); + Base::runOnModule(); + } + if (Options::AESparseProfile()) + reportSparseProfile(); +} + +template +const char* NativeSemiSparseAbstractInterpretation< + NumericalStateT>::sparseProfileMode() const +{ + return "semi"; +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::reportSparseProfile() const +{ + const std::ios::fmtflags previousFlags = std::cout.flags(); + const std::streamsize previousPrecision = std::cout.precision(); + auto report = [&](const char* phase, const PhaseMetric& metric) { + const double seconds = + static_cast(metric.nanoseconds) / 1'000'000'000.0; + const double nanosecondsPerCall = + metric.calls == 0 ? 0.0 + : static_cast(metric.nanoseconds) / + static_cast(metric.calls); + std::cout << "AE_SPARSE_PHASE mode=" << sparseProfileMode() + << " phase=" << phase << " calls=" << metric.calls + << " seconds=" << std::fixed << std::setprecision(6) + << seconds << " ns_per_call=" << std::setprecision(1) + << nanosecondsPerCall << '\n'; + }; + report("total", sparseProfile_.total); + report("state-copy", sparseProfile_.stateCopy); + report("state-merge", sparseProfile_.stateMerge); + report("environment-alignment", sparseProfile_.environmentAlignment); + report("state-join", sparseProfile_.stateJoin); + report("state-equivalence", sparseProfile_.stateEquivalence); + report("scalar-materialization", sparseProfile_.scalarMaterialization); + report("scalar-checkpoint", sparseProfile_.scalarCheckpoint); + report("state-filtering", sparseProfile_.stateFiltering); + report("cycle", sparseProfile_.cycle); + report("svfg-build", sparseProfile_.svfgBuild); + report("object-pull", sparseProfile_.objectPull); + report("path-feasibility", sparseProfile_.pathFeasibility); + report("memory-refinement", sparseProfile_.memoryRefinement); + std::cout.flags(previousFlags); + std::cout.precision(previousPrecision); +} + +template +AbstractValue NativeSemiSparseAbstractInterpretation< + NumericalStateT>::getAbsValue(const ValVar* value, const ICFGNode* node) +{ + (void)node; + if (const auto* integer = SVFUtil::dyn_cast(value)) + return IntervalValue(integer->getSExtValue()); + if (!value || !this->adapter_.contains(*value)) + return IntervalValue::top(); + + DenseState& scalars = scalarState(value->getFunction()); + const AD::Variable variable = this->adapter_.variable(*value); + if (!scalars.shapes().isDefined(variable)) + this->assignValue(scalars, variable, IntervalValue::top()); + AbstractValue result = this->projectValue(scalars, variable); + if (value->isPointer()) + result.interval = IntervalValue::bottom(); + return result; +} + +template +bool NativeSemiSparseAbstractInterpretation::hasAbsValue( + const ValVar* value, const ICFGNode* node) const +{ + (void)node; + if (SVFUtil::isa(value)) + return true; + if (!value || !this->adapter_.contains(*value)) + return false; + const DenseState* scalars = findScalarState(value->getFunction()); + return scalars && + scalars->shapes().isDefined(this->adapter_.variable(*value)); +} + +template +void NativeSemiSparseAbstractInterpretation::updateAbsValue( + const ValVar* value, const AbstractValue& abstractValue, + const ICFGNode* node) +{ + (void)node; + if (value && this->adapter_.contains(*value)) + this->assignValue(scalarState(value->getFunction()), + this->adapter_.variable(*value), abstractValue); +} + +template +void NativeSemiSparseAbstractInterpretation::copyAbstractState( + const ICFGNode* source, const ICFGNode* destination) +{ + PhaseTimer timer(sparseProfile_.stateCopy, Options::AESparseProfile()); + DenseState copy = this->state(source); + const AD::VariableEnvironment& destinationEnvironment = + this->adapter_.environment(destination->getFun()); + if (copy.numerical().environment() != destinationEnvironment) + copy.changeEnvironment(destinationEnvironment); + this->denseTrace_.insert_or_assign(destination, std::move(copy)); +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::resetAbstractState(const ICFGNode* node) +{ + this->denseTrace_.insert_or_assign(node, flowState(node->getFun())); +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::finalizeAbstractState(const ICFGNode* node) +{ + PhaseTimer timer(sparseProfile_.stateFiltering, Options::AESparseProfile()); + DenseState& denseState = this->ensureState(node); + forgetActiveScalarValues(denseState); +} + +template +bool NativeSemiSparseAbstractInterpretation:: + isAbstractStateEquivalent(const ICFGNode* node, + const AD::AbstractState& snapshot) const +{ + PhaseTimer timer(sparseProfile_.stateEquivalence, + Options::AESparseProfile()); + return Base::isAbstractStateEquivalent(node, snapshot); +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::forgetActiveScalarValues(DenseState& denseState) const +{ + if constexpr (std::is_same_v) + { + const std::vector defined = + denseState.shapes().definedVariables( + denseState.numerical().environment()); + for (AD::Variable variable : defined) + { + if (this->adapter_.value(variable)) + this->forgetValue(denseState, variable); + } + } + else + { + // Box checkpoints may constrain a variable without exposing + // it through the definedness facet, so relational domains retain the + // conservative full-environment purge. + this->forgetScalarValues(denseState); + } +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::forgetMemoryValues(DenseState& denseState) const +{ + const std::vector defined = + denseState.shapes().definedVariables( + denseState.numerical().environment()); + for (AD::Variable variable : defined) + { + if (this->adapter_.contentObject(variable)) + this->forgetValue(denseState, variable); + } +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::applyScalarCheckpoint(DenseState& denseState, + const DenseState& checkpoint) +{ + PhaseTimer timer(sparseProfile_.scalarCheckpoint, + Options::AESparseProfile()); + if constexpr (std::is_same_v) + { + const std::vector defined = + checkpoint.shapes().definedVariables( + checkpoint.numerical().environment()); + for (AD::Variable variable : defined) + { + if (!this->adapter_.value(variable) || + !checkpoint.shapes().hasNumeric(variable)) + continue; + if (!denseState.numerical().environment().contains(variable)) + this->ensureVariable(denseState, variable); + const AbstractValue value = + this->projectValue(checkpoint, variable); + if (!value.isInterval()) + continue; + this->constrainInterval(denseState, variable, value.getInterval()); + denseState.pointers().assign(variable, AD::PointeeSet::bottom()); + denseState.shapes().assign(variable, true); + } + return; + } + + if (checkpoint.isTop()) + return; + DenseState scalar = checkpoint; + forgetMemoryValues(scalar); + if (denseState.numerical().environment() != + scalar.numerical().environment()) + { + const AD::VariableEnvironment environment = + denseState.numerical().environment().merge( + scalar.numerical().environment()); + denseState.changeEnvironment(environment); + scalar.changeEnvironment(environment); + } + denseState.numerical().meetWith(scalar.numerical()); +} + +template +void NativeSemiSparseAbstractInterpretation::materializeValue( + DenseState& denseState, const ValVar* value, const ICFGNode* node) +{ + PhaseTimer timer(sparseProfile_.scalarMaterialization, + Options::AESparseProfile()); + if (!value || !this->adapter_.contains(*value)) + return; + const AD::Variable variable = this->adapter_.variable(*value); + if (denseState.shapes().isDefined(variable)) + return; + auto materializeFacets = [&]() { + const AbstractValue projected = getAbsValue(value, node); + AD::PointeeSet addresses = AD::PointeeSet::bottom(); + for (u32_t address : projected.getAddrs()) + { + const auto* object = SVFUtil::dyn_cast( + this->svfir->getGNode(Base::objectIdFromAddress(address))); + if (object && this->adapter_.contains(*object)) + addresses.insert(this->adapter_.location(*object)); + } + denseState.pointers().assign(variable, std::move(addresses)); + denseState.shapes().assign(variable, projected.isInterval()); + }; + if constexpr (!std::is_same_v) + { + const auto checkpoint = scalarCheckpoints_.find(value); + if (checkpoint != scalarCheckpoints_.end()) + { + applyScalarCheckpoint(denseState, checkpoint->second); + materializeFacets(); + return; + } + } + // Branch-refinement states carry numerical constraints without marking + // the corresponding scalar as a persistent product value. Preserve that + // latent constraint and materialize only its address/shape facets. + if (denseState.numerical().environment().contains(variable)) + { + materializeFacets(); + return; + } + this->assignValue(denseState, variable, getAbsValue(value, node)); +} + +template +AbstractValue NativeSemiSparseAbstractInterpretation< + NumericalStateT>::loadValue(const ValVar* pointer, const ICFGNode* node) +{ + AbstractValue result = Base::loadValue(pointer, node); + if (pointer && this->adapter_.contains(*pointer)) + this->forgetValue(this->ensureState(node), + this->adapter_.variable(*pointer)); + return result; +} + +template +void NativeSemiSparseAbstractInterpretation::storeValue( + const ValVar* pointer, const AbstractValue& value, const ICFGNode* node) +{ + Base::storeValue(pointer, value, node); + if (pointer && this->adapter_.contains(*pointer)) + this->forgetValue(this->ensureState(node), + this->adapter_.variable(*pointer)); +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::filterPropagatedState(DenseState& denseState) const +{ + (void)denseState; +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::collectMemoryBranchRefinement(const IntraCFGEdge* edge, + DenseState& state) +{ + this->collectBranchRefinement(edge, state); +} + +template +bool NativeSemiSparseAbstractInterpretation< + NumericalStateT>::mergeStatesFromPredecessors(const ICFGNode* node) +{ + PhaseTimer timer(sparseProfile_.stateMerge, Options::AESparseProfile()); + DenseState merged = flowState(node->getFun(), true); + std::optional mergedRefinement; + bool refinementIsTop = false; + bool hasFeasiblePredecessor = false; + + for (const ICFGEdge* edge : node->getInEdges()) + { + const ICFGNode* predecessor = edge->getSrcNode(); + if (!this->hasAbsState(predecessor)) + continue; + + bool shouldMerge = false; + const auto* conditional = SVFUtil::dyn_cast(edge); + if (conditional || SVFUtil::isa(edge)) + { + shouldMerge = true; + } + else if (SVFUtil::isa(edge)) + { + shouldMerge = Options::HandleRecur() == Base::TOP; + if (!shouldMerge) + { + const auto* returnSite = SVFUtil::dyn_cast(node); + shouldMerge = returnSite && + this->hasAbsState(returnSite->getCallICFGNode()); + } + } + if (!shouldMerge) + continue; + + const auto refinementIterator = refinementTrace_.find(predecessor); + const bool hasConditional = conditional && conditional->getCondition(); + const bool needsRefinement = + hasConditional || refinementIterator != refinementTrace_.end(); + std::optional refinement; + if (needsRefinement) + { + refinement = refinementIterator != refinementTrace_.end() + ? refinementIterator->second + : this->topState(node); + const AD::VariableEnvironment& destinationEnvironment = + this->adapter_.environment(node->getFun()); + if (refinement->numerical().environment() != destinationEnvironment) + { + PhaseTimer environmentTimer(sparseProfile_.environmentAlignment, + Options::AESparseProfile()); + refinement->changeEnvironment(destinationEnvironment); + } + if (hasConditional) + this->assumeBranch(conditional, *refinement); + if (refinement->isBottom()) + continue; + } + + DenseState source = this->state(predecessor); + filterPropagatedState(source); + const AD::VariableEnvironment& destinationEnvironment = + this->adapter_.environment(node->getFun()); + if (source.numerical().environment() != destinationEnvironment) + { + PhaseTimer environmentTimer(sparseProfile_.environmentAlignment, + Options::AESparseProfile()); + source.changeEnvironment(destinationEnvironment); + } + if (hasConditional) + collectMemoryBranchRefinement(conditional, source); + + { + PhaseTimer joinTimer(sparseProfile_.stateJoin, + Options::AESparseProfile()); + merged.joinWith(source); + } + if (!refinement || refinement->isTop()) + { + refinementIsTop = true; + mergedRefinement.reset(); + } + else if (!refinementIsTop) + { + forgetMemoryValues(*refinement); + if (!mergedRefinement) + mergedRefinement = std::move(*refinement); + else + mergedRefinement->joinWith(*refinement); + } + hasFeasiblePredecessor = true; + } + + if (!hasFeasiblePredecessor) + return false; + if (mergedRefinement && !refinementIsTop && !mergedRefinement->isTop()) + { + refinementTrace_.insert_or_assign(node, *mergedRefinement); + applyScalarCheckpoint(merged, *mergedRefinement); + } + else + { + refinementTrace_.erase(node); + } + this->denseTrace_.insert_or_assign(node, std::move(merged)); + return true; +} + +template +std::unique_ptr NativeSemiSparseAbstractInterpretation< + NumericalStateT>::cloneCycleHeadState(const ICFGCycleWTO* cycle) +{ + PhaseTimer timer(sparseProfile_.cycle, Options::AESparseProfile()); + const ICFGNode* head = cycle->head()->getICFGNode(); + DenseState snapshot = this->state(head); + for (const ValVar* value : this->preAnalysis->getCycleValVars(cycle)) + { + if (!value || !this->adapter_.contains(*value) || + !hasAbsValue(value, head)) + continue; + this->assignValue(snapshot, this->adapter_.variable(*value), + getAbsValue(value, head)); + } + return std::make_unique(std::move(snapshot)); +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::scatterCycleValues(const ICFGCycleWTO* cycle, + const DenseState& cycleState) +{ + for (const ValVar* value : this->preAnalysis->getCycleValVars(cycle)) + { + if (!value || !this->adapter_.contains(*value)) + continue; + const AD::Variable variable = this->adapter_.variable(*value); + if (!cycleState.shapes().isDefined(variable)) + continue; + updateAbsValue(value, this->projectValue(cycleState, variable), + cycle->head()->getICFGNode()); + } +} + +template +bool NativeSemiSparseAbstractInterpretation::widenCycleState( + const AD::AbstractState& previous, const AD::AbstractState& current, + const ICFGCycleWTO* cycle) +{ + PhaseTimer timer(sparseProfile_.cycle, Options::AESparseProfile()); + const bool fixpoint = Base::widenCycleState(previous, current, cycle); + scatterCycleValues(cycle, this->state(cycle->head()->getICFGNode())); + finalizeAbstractState(cycle->head()->getICFGNode()); + return fixpoint; +} + +template +bool NativeSemiSparseAbstractInterpretation::narrowCycleState( + const AD::AbstractState& previous, const AD::AbstractState& current, + const ICFGCycleWTO* cycle) +{ + PhaseTimer timer(sparseProfile_.cycle, Options::AESparseProfile()); + const bool fixpoint = Base::narrowCycleState(previous, current, cycle); + if (!fixpoint) + { + scatterCycleValues(cycle, this->state(cycle->head()->getICFGNode())); + } + finalizeAbstractState(cycle->head()->getICFGNode()); + return fixpoint; +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::assignDomainInterval(const ICFGNode* node, + const SVFVar* target, + const IntervalValue& interval) +{ + if (const auto* value = SVFUtil::dyn_cast(target)) + { + if (!this->adapter_.contains(*value) || interval.isBottom()) + return; + this->assignInterval(scalarState(value->getFunction()), + this->adapter_.variable(*value), interval); + return; + } + Base::assignDomainInterval(node, target, interval); +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::commitBinaryResult(const BinaryOPStmt* binary, + const DenseState& transferState, + const IntervalValue& fallback) +{ + const auto* target = SVFUtil::dyn_cast(binary->getRes()); + if (!target || !this->adapter_.contains(*target)) + return; + + const AD::Variable targetVariable = this->adapter_.variable(*target); + AbstractValue projected = this->projectValue(transferState, targetVariable); + const IntervalValue interval = + projected.isInterval() ? projected.getInterval() : fallback; + DenseState& scalars = scalarState(target->getFunction()); + this->assignInterval(scalars, targetVariable, interval); + if constexpr (!std::is_same_v) + { + DenseState checkpoint = transferState; + checkpoint.changeEnvironment( + this->adapter_.scalarEnvironment(target->getFunction())); + scalarCheckpoints_.insert_or_assign(target, std::move(checkpoint)); + } +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::updateDomainOnBinary(const BinaryOPStmt* binary, + const IntervalValue& result) +{ + Base::updateDomainOnBinary(binary, result); + DenseState& transferState = this->ensureState(binary->getICFGNode()); + if (const auto* target = SVFUtil::dyn_cast(binary->getRes()); + target && this->adapter_.contains(*target)) + { + const AD::Variable variable = this->adapter_.variable(*target); + transferState.pointers().assign(variable, AD::PointeeSet::bottom()); + transferState.shapes().assign(variable, true); + } + commitBinaryResult(binary, transferState, result); +} + +template +void NativeSemiSparseAbstractInterpretation::commitCopyResult( + const SVFVar* target, bool exactMathematicalCopy, + const DenseState& transferState) +{ + const auto* targetValue = SVFUtil::dyn_cast(target); + if (!targetValue || !this->adapter_.contains(*targetValue)) + return; + const AD::Variable targetVariable = this->adapter_.variable(*targetValue); + const AbstractValue projected = + this->projectValue(transferState, targetVariable); + if (!projected.isInterval()) + return; + + DenseState& scalars = scalarState(targetValue->getFunction()); + this->assignInterval(scalars, targetVariable, projected.getInterval()); + if constexpr (!std::is_same_v) + { + if (exactMathematicalCopy) + { + DenseState checkpoint = transferState; + checkpoint.changeEnvironment( + this->adapter_.scalarEnvironment(targetValue->getFunction())); + scalarCheckpoints_.insert_or_assign(targetValue, + std::move(checkpoint)); + } + } +} + +template +void NativeSemiSparseAbstractInterpretation< + NumericalStateT>::updateDomainCopyValue(const ICFGNode* node, + const SVFVar* target, + const SVFVar* source, + bool exactMathematicalCopy) +{ + Base::updateDomainCopyValue(node, target, source, exactMathematicalCopy); + DenseState& transferState = this->ensureState(node); + const auto* targetValue = SVFUtil::dyn_cast(target); + if (targetValue && this->adapter_.contains(*targetValue) && + getAbsValue(targetValue, node).isInterval()) + { + const AD::Variable variable = this->adapter_.variable(*targetValue); + transferState.pointers().assign(variable, AD::PointeeSet::bottom()); + transferState.shapes().assign(variable, true); + } + commitCopyResult(target, exactMathematicalCopy, transferState); +} + +namespace +{ + +bool hasRedefinitionOf(const ICFGNode* node, const IndirectSVFGEdge* edge) +{ + for (const VFGNode* valueFlowNode : node->getVFGNodes()) + { + if (SVFUtil::isa(valueFlowNode) && + valueFlowNode->getDefSVFVars().intersects(edge->getPointsTo())) + return true; + } + return false; +} + +} // namespace + +template +NativeFullSparseAbstractInterpretation< + NumericalStateT>::NativeFullSparseAbstractInterpretation() +{ + PhaseTimer timer(this->sparseProfile_.svfgBuild, + Options::AESparseProfile()); + svfgBuilder_ = std::make_unique(true); + svfgBuilder_->buildFullSVFG(this->preAnalysis->getPointerAnalysis()); +} + +template +NativeFullSparseAbstractInterpretation< + NumericalStateT>::~NativeFullSparseAbstractInterpretation() = default; + +template +const char* NativeFullSparseAbstractInterpretation< + NumericalStateT>::sparseProfileMode() const +{ + return "full"; +} + +template +void NativeFullSparseAbstractInterpretation< + NumericalStateT>::filterPropagatedState(DenseState& denseState) const +{ + PhaseTimer timer(this->sparseProfile_.stateFiltering, + Options::AESparseProfile()); + this->forgetActiveScalarValues(denseState); + const std::vector defined = + denseState.shapes().definedVariables( + denseState.numerical().environment()); + for (AD::Variable variable : defined) + { + const ObjVar* object = this->adapter_.contentObject(variable); + if (object && !SVFUtil::isa(object)) + this->forgetValue(denseState, variable); + } +} + +template +void NativeFullSparseAbstractInterpretation< + NumericalStateT>::collectMemoryBranchRefinement(const IntraCFGEdge* edge, + DenseState& state) +{ + this->collectBranchRefinement(edge, state); +} + +template +void NativeFullSparseAbstractInterpretation< + NumericalStateT>::recordBranchRefinement(NodeID objectId, + const IntervalValue& narrowed, + AD::AbstractState&, + const ICFGNode*, + const ICFGNode* successor) +{ + if (narrowed.isBottom()) + return; + auto& refinements = memoryRefinementTrace_[successor]; + const auto iterator = refinements.find(objectId); + if (iterator == refinements.end()) + refinements.emplace(objectId, narrowed); + else + iterator->second.join_with(narrowed); +} + +template +void NativeFullSparseAbstractInterpretation::storeValue( + const ValVar* pointer, const AbstractValue& value, const ICFGNode* node) +{ + const AbstractValue addresses = Base::getAbsValue(pointer, node); + auto refinement = memoryRefinementTrace_.find(node); + if (refinement != memoryRefinementTrace_.end()) + { + for (u32_t address : addresses.getAddrs()) + refinement->second.erase(this->objectIdFromAddress(address)); + } + Base::storeValue(pointer, value, node); +} + +template +bool NativeFullSparseAbstractInterpretation< + NumericalStateT>::mergeStatesFromPredecessors(const ICFGNode* node) +{ + memoryRefinementTrace_.erase(node); + if (!Base::mergeStatesFromPredecessors(node)) + return false; + // A direct object constraint collected from one incoming branch cannot be + // applied after another incoming path has joined without that constraint. + // Inherited constraints below already implement the precise all-preds + // intersection rule; discard edge-local constraints at explicit merges. + if (node->getInEdges().size() > 1) + memoryRefinementTrace_.erase(node); + pullObjectValueFlows(node); + propagateAndApplyMemoryRefinement(node); + return true; +} + +template +void NativeFullSparseAbstractInterpretation< + NumericalStateT>::pullObjectValueFlows(const ICFGNode* node) +{ + PhaseTimer timer(this->sparseProfile_.objectPull, + Options::AESparseProfile()); + NodeBS denseLocalObjects; + const DenseState& destination = this->state(node); + const std::vector defined = + destination.shapes().definedVariables( + destination.numerical().environment()); + for (AD::Variable variable : defined) + { + const ObjVar* object = this->adapter_.contentObject(variable); + if (object && SVFUtil::isa(object) && + destination.shapes().isDefined(variable)) + denseLocalObjects.set(object->getId()); + } + + for (const VFGNode* valueFlowNode : node->getVFGNodes()) + { + for (auto edgeIterator = valueFlowNode->InEdgeBegin(); + edgeIterator != valueFlowNode->InEdgeEnd(); ++edgeIterator) + { + const auto* indirect = + SVFUtil::dyn_cast(*edgeIterator); + if (!indirect || + !isIndirectSVFGEdgeFeasible(indirect, valueFlowNode)) + continue; + + const auto* sourceNode = + SVFUtil::dyn_cast(indirect->getSrcNode()); + assert(sourceNode && sourceNode->getICFGNode() && + "SVFG source must have an ICFG node"); + const ICFGNode* source = sourceNode->getICFGNode(); + if (!this->hasAbsState(source)) + continue; + + for (NodeID objectId : indirect->getPointsTo()) + { + SVFVar* graphNode = this->svfir->getGNode(objectId); + NodeBS objectsToPull; + if (SVFUtil::isa(graphNode)) + objectsToPull.set(objectId); + else if (auto* base = SVFUtil::dyn_cast(graphNode)) + objectsToPull = this->svfir->getAllFieldsObjVars(base); + else + objectsToPull.set(objectId); + + for (NodeID fieldId : objectsToPull) + { + if (denseLocalObjects.test(fieldId)) + continue; + const auto* object = SVFUtil::dyn_cast( + this->svfir->getGNode(fieldId)); + if (!object || !Base::hasAbsValue(object, source)) + continue; + + AbstractValue joined; + if (Base::hasAbsValue(object, node)) + joined = Base::getAbsValue(object, node); + joined.join_with(Base::getAbsValue(object, source)); + Base::updateAbsValue(object, joined, node); + } + } + } + } +} + +template +bool NativeFullSparseAbstractInterpretation< + NumericalStateT>::isIntraEdgeBranchFeasible(const IntraCFGEdge* edge, + const ICFGNode* source) +{ + return !edge->getCondition() || !this->hasAbsState(source) || + this->isBranchEdgeFeasibleAt(edge, source); +} + +template +bool NativeFullSparseAbstractInterpretation< + NumericalStateT>::isIndirectSVFGEdgeFeasible(const IndirectSVFGEdge* edge, + const VFGNode* destination) +{ + PhaseTimer timer(this->sparseProfile_.pathFeasibility, + Options::AESparseProfile()); + assert(edge && destination && "SVFG edge and destination must exist"); + const auto* sourceNode = SVFUtil::dyn_cast(edge->getSrcNode()); + assert(sourceNode && "indirect SVFG edge must have an SVFG source"); + const ICFGNode* source = sourceNode->getICFGNode(); + const ICFGNode* target = destination->getICFGNode(); + assert(source && target && "SVFG endpoints must have ICFG nodes"); + + const FunObjVar* function = source->getFun(); + if (source == target || !function || function != target->getFun()) + return true; + + std::deque worklist; + Set visited; + worklist.push_back(source); + visited.insert(source); + while (!worklist.empty()) + { + const ICFGNode* current = worklist.front(); + worklist.pop_front(); + if (current != source && hasRedefinitionOf(current, edge)) + continue; + + if (const auto* call = SVFUtil::dyn_cast(current)) + { + const ICFGNode* successor = call->getRetICFGNode(); + if (successor && successor->getFun() == function) + { + if (successor == target) + return true; + if (visited.insert(successor).second) + worklist.push_back(successor); + } + } + + for (const ICFGEdge* cfgEdge : current->getOutEdges()) + { + const auto* intra = SVFUtil::dyn_cast(cfgEdge); + const ICFGNode* successor = intra ? intra->getDstNode() : nullptr; + if (!successor || successor->getFun() != function || + !isIntraEdgeBranchFeasible(intra, current)) + continue; + if (successor == target) + return true; + if (visited.insert(successor).second) + worklist.push_back(successor); + } + } + return false; +} + +template +void NativeFullSparseAbstractInterpretation< + NumericalStateT>::propagateAndApplyMemoryRefinement(const ICFGNode* node) +{ + PhaseTimer timer(this->sparseProfile_.memoryRefinement, + Options::AESparseProfile()); + Map inherited; + bool canInherit = true; + bool first = true; + for (const ICFGEdge* edge : node->getInEdges()) + { + const ICFGNode* predecessor = edge->getSrcNode(); + if (!this->hasAbsState(predecessor)) + continue; + const auto predecessorRefinement = + memoryRefinementTrace_.find(predecessor); + if (predecessorRefinement == memoryRefinementTrace_.end()) + { + canInherit = false; + break; + } + if (first) + { + inherited = predecessorRefinement->second; + first = false; + continue; + } + for (auto iterator = inherited.begin(); iterator != inherited.end();) + { + const auto incoming = + predecessorRefinement->second.find(iterator->first); + if (incoming == predecessorRefinement->second.end()) + iterator = inherited.erase(iterator); + else + { + iterator->second.join_with(incoming->second); + ++iterator; + } + } + } + + if (canInherit && !first) + { + auto& refinements = memoryRefinementTrace_[node]; + for (const auto& [objectId, constraint] : inherited) + { + const auto current = refinements.find(objectId); + if (current == refinements.end()) + refinements.emplace(objectId, constraint); + else + current->second.meet_with(constraint); + } + } + + const auto refinements = memoryRefinementTrace_.find(node); + if (refinements == memoryRefinementTrace_.end()) + return; + DenseState& denseState = this->ensureState(node); + for (const auto& [objectId, constraint] : refinements->second) + { + const auto* object = + SVFUtil::dyn_cast(this->svfir->getGNode(objectId)); + if (!object || !this->adapter_.contains(*object)) + continue; + const AD::Variable content = this->adapter_.contentVariable(*object); + if (denseState.shapes().isDefined(content)) + this->constrainInterval(denseState, content, constraint); + } +} + +template class NativeSemiSparseAbstractInterpretation; +template class NativeFullSparseAbstractInterpretation; + +} // namespace SVF diff --git a/svf/lib/AE/Svfexe/SVFIRAdapter.cpp b/svf/lib/AE/Svfexe/SVFIRAdapter.cpp new file mode 100644 index 0000000000..b48716f29a --- /dev/null +++ b/svf/lib/AE/Svfexe/SVFIRAdapter.cpp @@ -0,0 +1,213 @@ +//===- SVFIRAdapter.cpp -- SVFIR to abstract-domain symbols ------------===// + +#include "AE/Svfexe/SVFIRAdapter.h" + +#include "SVFIR/SVFIR.h" +#include "SVFIR/SVFType.h" +#include "SVFIR/SVFVariables.h" +#include "Util/SVFUtil.h" + +#include +#include +#include +#include + +namespace SVF +{ + +using AbstractDomain::LinearExpression; +using AbstractDomain::Location; +using AbstractDomain::MemoryLayout; +using AbstractDomain::NumericType; +using AbstractDomain::Rational; +using AbstractDomain::TreeExpression; +using AbstractDomain::Variable; +using AbstractDomain::VariableDeclaration; +using AbstractDomain::VariableEnvironment; + +namespace +{ + +Variable nextVariable(std::uint64_t& next) +{ + if (next > std::numeric_limits::max()) + throw std::overflow_error("too many abstract-domain variables"); + return Variable(static_cast(next++)); +} + +Location nextLocation(std::uint64_t& next) +{ + if (next > std::numeric_limits::max()) + throw std::overflow_error("too many abstract-domain locations"); + return Location(static_cast(next++)); +} + +} // namespace + +SVFIRAdapter::SVFIRAdapter(const SVFIR& svfir) +{ + std::uint64_t nextVariableId = 1; + std::uint64_t nextLocationId = 1; + std::vector commonDeclarations; + std::vector allScalarDeclarations; + std::map cells; + + for (auto iterator = svfir.begin(); iterator != svfir.end(); ++iterator) + { + const SVFVar* svfVariable = iterator->second; + if (const auto* value = SVFUtil::dyn_cast(svfVariable)) + { + if (value->isConstDataOrAggDataButNotNullPtr()) + continue; + if (!value->isPointer() && + !SVFUtil::isa(value->getType())) + continue; + + const Variable variable = nextVariable(nextVariableId); + variables_.emplace(value, variable); + valuesByVariableId_.resize(variable.id() + 1); + valuesByVariableId_[variable.id()] = value; + VariableDeclaration declaration{variable, NumericType::integer(), + "svf_value_" + + std::to_string(value->getId())}; + declarations_.emplace(variable, declaration); + allScalarDeclarations.push_back(declaration); + commonDeclarations.push_back(std::move(declaration)); + continue; + } + + const auto* object = SVFUtil::dyn_cast(svfVariable); + if (!object) + continue; + const Location location = nextLocation(nextLocationId); + const Variable content = nextVariable(nextVariableId); + locations_.emplace(object, location); + objects_.emplace(location, object); + contentVariables_.emplace(object, content); + contentObjectsByVariableId_.resize(content.id() + 1); + contentObjectsByVariableId_[content.id()] = object; + VariableDeclaration declaration{ + content, NumericType::integer(), + "svf_object_" + std::to_string(object->getId()) + "_content"}; + declarations_.emplace(content, declaration); + commonDeclarations.push_back(std::move(declaration)); + cells.emplace(location, content); + } + + globalEnvironment_ = VariableEnvironment(commonDeclarations); + allScalarEnvironment_ = VariableEnvironment(allScalarDeclarations); + memoryLayout_ = MemoryLayout(std::move(cells)); +} + +const VariableEnvironment& SVFIRAdapter::scalarEnvironment( + const FunObjVar* function) const +{ + (void)function; + return allScalarEnvironment_; +} + +bool SVFIRAdapter::contains(const ValVar& value) const +{ + return variables_.count(&value) != 0; +} + +bool SVFIRAdapter::contains(const ObjVar& object) const +{ + return locations_.count(&object) != 0; +} + +Variable SVFIRAdapter::variable(const ValVar& value) const +{ + const auto iterator = variables_.find(&value); + if (iterator == variables_.end()) + throw std::invalid_argument("ValVar is not tracked by this adapter"); + return iterator->second; +} + +const ValVar* SVFIRAdapter::value(Variable variable) const +{ + return variable.id() < valuesByVariableId_.size() + ? valuesByVariableId_[variable.id()] + : nullptr; +} + +const VariableDeclaration& SVFIRAdapter::declaration(Variable variable) const +{ + const auto iterator = declarations_.find(variable); + if (iterator == declarations_.end()) + throw std::invalid_argument("variable is not tracked by this adapter"); + return iterator->second; +} + +Location SVFIRAdapter::location(const ObjVar& object) const +{ + const auto iterator = locations_.find(&object); + if (iterator == locations_.end()) + throw std::invalid_argument("ObjVar is not tracked by this adapter"); + return iterator->second; +} + +Variable SVFIRAdapter::contentVariable(const ObjVar& object) const +{ + const auto iterator = contentVariables_.find(&object); + if (iterator == contentVariables_.end()) + throw std::invalid_argument("ObjVar is not tracked by this adapter"); + return iterator->second; +} + +const ObjVar* SVFIRAdapter::contentObject(Variable variable) const +{ + return variable.id() < contentObjectsByVariableId_.size() + ? contentObjectsByVariableId_[variable.id()] + : nullptr; +} + +const ObjVar& SVFIRAdapter::object(Location location) const +{ + const auto iterator = objects_.find(location); + if (iterator == objects_.end()) + throw std::invalid_argument("location is not tracked by this adapter"); + return *iterator->second; +} + +const VariableEnvironment& SVFIRAdapter::environment( + const FunObjVar* function) const +{ + (void)function; + return globalEnvironment_; +} + +LinearExpression SVFIRAdapter::linearExpression( + const std::vector>& terms, + Rational constant) const +{ + LinearExpression expression(std::move(constant)); + for (const auto& [value, coefficient] : terms) + { + if (!value) + throw std::invalid_argument("affine term has a null ValVar"); + if (const auto* integer = SVFUtil::dyn_cast(value)) + { + expression.setConstant(expression.constant() + + coefficient * + Rational(integer->getSExtValue())); + continue; + } + const Variable symbol = variable(*value); + expression.setCoefficient(symbol, + expression.coefficient(symbol) + coefficient); + } + return expression; +} + +TreeExpression SVFIRAdapter::treeExpression(const ValVar& value) const +{ + if (const auto* integer = SVFUtil::dyn_cast(&value)) + return TreeExpression::constant(Rational(integer->getSExtValue()), + NumericType::integer()); + const Variable symbol = variable(value); + return TreeExpression::variable( + symbol, environment(value.getFunction()).typeOf(symbol)); +} + +} // namespace SVF diff --git a/svf/lib/AE/Svfexe/SparseAbstractInterpretation.cpp b/svf/lib/AE/Svfexe/SparseAbstractInterpretation.cpp deleted file mode 100644 index 911871e917..0000000000 --- a/svf/lib/AE/Svfexe/SparseAbstractInterpretation.cpp +++ /dev/null @@ -1,731 +0,0 @@ -//===- SparseAbstractInterpretation.cpp -- Sparse Abstract Execution----// -// -// SVF: Static Value-Flow Analysis -// -// Copyright (C) <2013-> -// - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. - -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . -// -//===---------------------------------------------------------------------===// - -#include "AE/Svfexe/SparseAbstractInterpretation.h" -#include "AE/Svfexe/AEWTO.h" -#include "SVFIR/SVFIR.h" -#include "Graphs/SVFG.h" -#include "MSSA/SVFGBuilder.h" -#include "WPA/Andersen.h" - -using namespace SVF; - -// SemiSparse state-access overrides (get/has/updateAbsValue, -// updateAbsState, joinStates) live in AbstractStateManager.cpp; the -// FullSparse-specific overrides — including the SVFG-backed def/use -// queries and the ValVar stubs — live below alongside the rest of -// FullSparse so the whole subclass stays in one file. - -// ===================================================================== -// Full-sparse — class lifecycle + SVFG construction. -// ===================================================================== - -FullSparseAbstractInterpretation::~FullSparseAbstractInterpretation() = default; - -void FullSparseAbstractInterpretation::buildSVFG() -{ - svfgBuilder = std::make_unique(true); - svfg = svfgBuilder->buildFullSVFG(preAnalysis->getPointerAnalysis()); -} - -// ===================================================================== -// Full-sparse — merge. -// -// mergeStatesFromPredecessors is a thin wrapper: defer to base for -// ICFG-edge bookkeeping (predecessor iteration, branch feasibility, -// joinStates, updateAbsState, reachability return). If the node is -// reachable, run pullObjValueFlows to populate trace[node] with obj values -// pulled along SVFG indirect in-edges. -// -// joinStates carries only state that is not represented as MemorySSA -// def-use flow: GepObjVar field snapshots and _freedAddrs. Base/Dummy -// ObjVars are handled by pullObjValueFlows, not by ICFG-edge joins. -// ===================================================================== - -void FullSparseAbstractInterpretation::joinStates(AbstractState& dst, - const AbstractState& src) -{ - // Propagate GepObjVar entries along ICFG edges. Kill semantics - // come from handleNode's as.store(addr, val) overwriting trace at - // store sites (not from a JOIN here), so joinStates only forwards - // the post-write snapshot. This lets Gep fields scattered across - // many store ICFG nodes converge at downstream use sites, and lets - // extapi handlers (memcpy/memset/strlen) read upstream-written - // values via plain as.load(srcAddr). Base/Dummy are NOT propagated - // here — they ride pullObjValueFlows Step 1 (SVFG indirect edges, with - // MSSA chi/mu kill semantics). - for (const auto& [id, val] : src.getLocToVal()) - { - if (!SVFUtil::isa(svfir->getGNode(id))) - continue; - u32_t addr = AbstractState::getVirtualMemAddress(id); - if (dst.getLocToVal().count(id)) - dst.load(addr).join_with(val); - else - dst.store(addr, val); - } - for (NodeID a : src.getFreedAddrs()) - dst.addToFreedAddrs(a); -} - -void FullSparseAbstractInterpretation::storeValue(const ValVar* pointer, - const AbstractValue& val, - const ICFGNode* node) -{ - // Clear branch refinement for every ObjVar this store overwrites. - // A store redefines the ObjVar; the pre-store branch constraint - // (inherited into refinementTrace[node]) is immediately stale. - // Without this, successors inherit the stale constraint and MEET - // it onto the pulled post-store value, erasing the store's effect. - const AbstractValue& ptrVal = getAbsValue(pointer, node); - AbstractState& as = getAbsState(node); - for (auto addr : ptrVal.getAddrs()) - { - NodeID objId = as.getIDFromAddr(addr); - auto rit = refinementTrace.find(node); - if (rit != refinementTrace.end()) - rit->second.erase(objId); - } - // Delegate to base for the actual ObjVar update. - SemiSparseAbstractInterpretation::storeValue(pointer, val, node); -} - -bool FullSparseAbstractInterpretation::mergeStatesFromPredecessors( - const ICFGNode* node) -{ - refinementTrace.erase(node); - - if (!AbstractInterpretation::mergeStatesFromPredecessors(node)) - return false; - - pullObjValueFlows(node); - - // Compose pred-inherited refinement on top of branch narrowings - // just captured, then MEET into trace[node] so reads see narrowed. - propagateAndApplyRefinement(node); - return true; -} - -void FullSparseAbstractInterpretation::pullObjValueFlows(const ICFGNode* node) -{ - NodeBS denseLocalObjs; - for (const auto& item : abstractTrace[node].getLocToVal()) - { - NodeID id = item.first; - if (SVFUtil::isa(svfir->getGNode(id))) - denseLocalObjs.set(id); - } - // e.g. - // store i32 7, i32* %p ; def-site D for obj_p - // ... - // %v = load i32, i32* %p ; use-site U - // Step 1: intra-node SVFG-pull. For each VFG node hosted at U, walk - // the indirect SVFG in-edges back to D; for every obj id labelling - // the edge, JOIN the obj's value at D into U's trace. GepObjVar - // labels are pulled exactly. BaseObjVar labels are expanded to every - // sibling field via getAllFieldsObjVars because Andersen may label a - // field-sensitive consumer with the field-insensitive base. - // - // Gep fields already present at this node came through the dense - // ICFG propagation in joinStates. Treat those as authoritative and - // do not re-join older SVFG defs over them; otherwise a killed init - // field can be reintroduced at the load site (e.g. a[9] initialized - // to 9, overwritten to 10, then pulled back to [9,10]). - // Reads/writes go through SemiSparse to bypass FullSparse's refinement - // layer (these are def-site pulls, not real stores; refinement is - // applied later in propagateAndApplyRefinement). - for (const VFGNode* v : node->getVFGNodes()) - { - for (auto eit = v->InEdgeBegin(); eit != v->InEdgeEnd(); ++eit) - { - const IndirectSVFGEdge* indEdge = - SVFUtil::dyn_cast(*eit); - if (indEdge) - { - const SVFGNode* src = - SVFUtil::dyn_cast(indEdge->getSrcNode()); - assert(src && "SVFG incoming edge must have a source node"); - assert(v && "SVFG incoming edge must have a destination node"); - - const ICFGNode* srcICFG = src->getICFGNode(); - const ICFGNode* dstICFG = v->getICFGNode(); - (void)dstICFG; // Suppress warning of unused variable under release build - assert(srcICFG && "SVFG source node must have an ICFG node"); - assert(dstICFG && - "SVFG destination node must have an ICFG node"); - - if (!isIndirectSVFGEdgeFeasible(indEdge, v)) - continue; - - if (srcICFG && hasAbsState(srcICFG)) - { - for (NodeID id : indEdge->getPointsTo()) - { - SVFVar* gn = svfir->getGNode(id); - NodeBS idsToPull; - - if (SVFUtil::isa(gn)) - { - idsToPull.set(id); - } - else if (auto* base = SVFUtil::dyn_cast(gn)) - { - idsToPull = svfir->getAllFieldsObjVars(base); - } - else - { - idsToPull.set(id); - } - - for (NodeID fid : idsToPull) - { - const ObjVar* obj = - SVFUtil::dyn_cast(svfir->getGNode(fid)); - // Dense Gep propagation has already carried the - // current value to this node. - if (denseLocalObjs.test(fid)) - { - continue; - } - if (obj && - SemiSparseAbstractInterpretation::hasAbsValue( - obj, srcICFG)) - { - AbstractValue cur; - if (SemiSparseAbstractInterpretation:: - hasAbsValue(obj, node)) - { - cur = SemiSparseAbstractInterpretation:: - getAbsValue(obj, node); - } - cur.join_with(SemiSparseAbstractInterpretation:: - getAbsValue(obj, srcICFG)); - SemiSparseAbstractInterpretation:: - updateAbsValue(obj, cur, node); - } - } - } - } - } - } - } - - // Step 2 (boundary pull) intentionally removed: with GepObjVar dense - // propagation in joinStates above, Gep field values arrive at use - // sites along ICFG edges without needing the boundary pull. -} - -// ===================================================================== -// Full-sparse — refinement trace machinery. -// ===================================================================== - -static bool hasRedefineToSameObj(const ICFGNode* node, - const IndirectSVFGEdge* edge) -{ - for (const VFGNode* vfgNode : node->getVFGNodes()) - { - if (SVFUtil::isa(vfgNode) && - vfgNode->getDefSVFVars().intersects(edge->getPointsTo())) - return true; - } - - return false; -} - -bool FullSparseAbstractInterpretation::isIndirectSVFGEdgeFeasible( - const IndirectSVFGEdge* edge, const VFGNode* dst) -{ - assert(edge && "Indirect SVFG edge must exist"); - assert(dst && "Indirect SVFG edge must have a destination node"); - - const SVFGNode* src = SVFUtil::dyn_cast(edge->getSrcNode()); - assert(src && "Indirect SVFG edge must have an SVFG source node"); - - const ICFGNode* srcICFG = src->getICFGNode(); - const ICFGNode* dstICFG = dst->getICFGNode(); - assert(srcICFG && "SVFG source node must have an ICFG node"); - assert(dstICFG && "SVFG destination node must have an ICFG node"); - - const FunObjVar* fun = srcICFG->getFun(); - bool feasible = true; - if (srcICFG == dstICFG) - { - feasible = true; - } - else if (!fun || fun != dstICFG->getFun()) - { - feasible = true; - } - else - { - feasible = false; - std::deque worklist; - Set visited; - worklist.push_back(srcICFG); - visited.insert(srcICFG); - - while (!worklist.empty() && !feasible) - { - const ICFGNode* cur = worklist.front(); - worklist.pop_front(); - - if (cur != srcICFG && hasRedefineToSameObj(cur, edge)) - { - // This ICFG path redefines the same object before dst. - } - else - { - // Treat a call as an intra-procedural summary edge for path - // queries. Feasibility of the callee body is handled by the - // normal analysis; here we only need caller-side reachability, - // e.g. entry -> ret-site. - if (const CallICFGNode* call = - SVFUtil::dyn_cast(cur)) - { - const ICFGNode* succ = call->getRetICFGNode(); - if (!succ || succ->getFun() != fun) - { - // Ignore missing or cross-function return summaries. - } - else if (succ == dstICFG) - { - feasible = true; - } - else if (!visited.count(succ)) - { - visited.insert(succ); - worklist.push_back(succ); - } - else - { - // Already visited. - } - } - - for (const ICFGEdge* icfgEdge : cur->getOutEdges()) - { - const IntraCFGEdge* intraEdge = - SVFUtil::dyn_cast(icfgEdge); - const ICFGNode* succ = - intraEdge ? intraEdge->getDstNode() : nullptr; - - if (!intraEdge) - { - // Non-intra ICFG edges are not part of this path query. - } - else if (!succ || succ->getFun() != fun) - { - // Keep the query inside src's function. - } - else if (!isIntraEdgeBranchFeasible(intraEdge, cur)) - { - // The conditional edge is unreachable in cur's state. - } - else if (succ == dstICFG) - { - feasible = true; - } - else if (!visited.count(succ)) - { - visited.insert(succ); - worklist.push_back(succ); - } - else - { - // Already visited. - } - } - } - } - } - - return feasible; -} - -bool FullSparseAbstractInterpretation::isICFGPathFeasible(const ICFGNode* src, - const ICFGNode* dst) -{ - bool feasible = true; - if (!src || !dst) - { - feasible = true; - } - else if (src == dst) - { - feasible = true; - } - else - { - const FunObjVar* fun = src->getFun(); - if (!fun || fun != dst->getFun()) - { - feasible = true; - } - else - { - feasible = false; - std::deque worklist; - Set visited; - worklist.push_back(src); - visited.insert(src); - - while (!worklist.empty() && !feasible) - { - const ICFGNode* cur = worklist.front(); - worklist.pop_front(); - - // Treat a call as an intra-procedural summary edge for path - // queries. Feasibility of the callee body is handled by the - // normal analysis; here we only need caller-side reachability, - // e.g. entry -> ret-site. - if (const CallICFGNode* call = - SVFUtil::dyn_cast(cur)) - { - const ICFGNode* succ = call->getRetICFGNode(); - if (!succ || succ->getFun() != fun) - { - // Ignore missing or cross-function return summaries. - } - else if (succ == dst) - { - feasible = true; - } - else if (!visited.count(succ)) - { - visited.insert(succ); - worklist.push_back(succ); - } - else - { - // Already visited. - } - } - - for (const ICFGEdge* edge : cur->getOutEdges()) - { - const IntraCFGEdge* intraEdge = - SVFUtil::dyn_cast(edge); - const ICFGNode* succ = - intraEdge ? intraEdge->getDstNode() : nullptr; - - if (!intraEdge) - { - // Non-intra ICFG edges are not part of this path query. - } - else if (!succ || succ->getFun() != fun) - { - // Keep the query inside src's function. - } - else if (!isIntraEdgeBranchFeasible(intraEdge, cur)) - { - // The conditional edge is unreachable in cur's state. - } - else if (succ == dst) - { - feasible = true; - } - else if (!visited.count(succ)) - { - visited.insert(succ); - worklist.push_back(succ); - } - else - { - // Already visited. - } - } - } - } - } - - return feasible; -} - -bool FullSparseAbstractInterpretation::isIntraEdgeBranchFeasible( - const IntraCFGEdge* edge, const ICFGNode* src) -{ - bool feasible = true; - if (!edge->getCondition()) - { - feasible = true; - } - else if (!hasAbsState(src)) - { - feasible = true; - } - else - { - AbstractState edgeState = getAbsState(src); - feasible = isBranchEdgeFeasible(edge, edgeState); - } - - return feasible; -} - -void FullSparseAbstractInterpretation::recordBranchRefinement( - NodeID objId, const IntervalValue& narrowed, AbstractState&, - const ICFGNode*, const ICFGNode* succ) -{ - if (narrowed.isBottom()) - return; - - auto& succRef = refinementTrace[succ]; - auto rit = succRef.find(objId); - if (rit == succRef.end()) - { - succRef[objId] = narrowed; - } - else - { - rit->second.join_with(narrowed); - } -} - -void FullSparseAbstractInterpretation::propagateAndApplyRefinement( - const ICFGNode* node) -{ - // e.g. - // if (x > 0) { - // use(x); // use 1 - // use(x); // use 2 - // } - // Step 1: compose pred-inherited refinement into refinementTrace[node]. - // At use2, we don't have conditional intra-edge, but we can inherit the - // refinement from use1's conditional edge. When multiple preds, JOIN the - // inherited constraints. - Map inherited; - bool inheritOk = true; - bool first = true; - for (auto& e : node->getInEdges()) - { - const ICFGNode* pred = e->getSrcNode(); - if (hasAbsState(pred)) - { - auto pit = refinementTrace.find(pred); - if (pit == refinementTrace.end()) - { - inheritOk = false; - break; - } - else if (first) - { - inherited = pit->second; - first = false; - } - else - { - for (auto it = inherited.begin(); it != inherited.end();) - { - auto eit = pit->second.find(it->first); - if (eit == pit->second.end()) - { - it = inherited.erase(it); - } - else - { - it->second.join_with(eit->second); - ++it; - } - } - } - } - } - if (inheritOk && !first && !inherited.empty()) - { - auto& nodeRef = refinementTrace[node]; - for (const auto& [id, val] : inherited) - { - auto rit = nodeRef.find(id); - if (rit == nodeRef.end()) - { - nodeRef[id] = val; - } - else - { - rit->second.meet_with(val); - } - } - } - // e.g. - // if (x > 0) { - // use(x); // use 1 - // use(x); // use 2 - // } - // Step 2: at use1, recordBranchRefinement captures the predState's narrowed - // constraint into refinementTrace[use1]. At use2, we find the inherited - // refinement from use1 and MEET it into the base value so the use observes - // the narrowed constraint. - auto nit = refinementTrace.find(node); - if (nit != refinementTrace.end()) - { - AbstractState& trace = abstractTrace[node]; - for (const auto& [id, constraint] : nit->second) - { - if (trace.inAddrToValTable(id)) - { - u32_t addr = AbstractState::getVirtualMemAddress(id); - trace.load(addr).getInterval().meet_with(constraint); - } - } - } -} - -AbstractState SemiSparseAbstractInterpretation::getFullCycleHeadState( - const ICFGCycleWTO* cycle) -{ - // Start from the dense snapshot (ObjVars + any ValVars that happen to - // be cached at cycle_head's trace entry). - AbstractState snap = AbstractInterpretation::getFullCycleHeadState(cycle); - - const Set& valVars = preAnalysis->getCycleValVars(cycle); - if (valVars.empty()) - return snap; // no cycle ValVars known: nothing to pull - - // Drop stale ValVar entries and pull each cycle ValVar from its - // def-site. ValVars without a genuine stored value are skipped to - // avoid getAbsValue's top-fallback contaminating body def-sites on - // the subsequent widen/narrow scatter. - snap.clearValVars(); - for (const ValVar* v : valVars) - { - const ICFGNode* defSite = v->getICFGNode(); - if (!defSite || !hasAbsValue(v, defSite)) - continue; - snap[v->getId()] = getAbsValue(v, defSite); - } - return snap; -} - -bool SemiSparseAbstractInterpretation::widenCycleState( - const AbstractState& prev, const AbstractState& cur, const ICFGCycleWTO* cycle) -{ - // Base widens, writes trace[cycle_head], and returns fixpoint bool. - bool fixpoint = AbstractInterpretation::widenCycleState(prev, cur, cycle); - - // Scatter the widened ValVars back to their def-sites so body nodes - // observe the widened values on the next iteration. Matches the - // pre-refactor semantics: scatter unconditionally, including at - // widening fixpoint (see the narrowing-starts-with-stale-body issue - // fixed by always writing widened state back). - const ICFGNode* cycle_head = cycle->head()->getICFGNode(); - const AbstractState& next = abstractTrace[cycle_head]; - for (const auto& [id, val] : next.getVarToVal()) - updateAbsValue(svfir->getSVFVar(id), val, cycle_head); - return fixpoint; -} - -bool SemiSparseAbstractInterpretation::narrowCycleState( - const AbstractState& prev, const AbstractState& cur, const ICFGCycleWTO* cycle) -{ - // Delegate to base. It returns true on the two non-scatter cases - // (narrowing disabled, or narrow fixpoint); we preserve the original - // "skip scatter at fixpoint" semantics by bailing early here. - bool fixpoint = AbstractInterpretation::narrowCycleState(prev, cur, cycle); - if (fixpoint) - return true; - - // Non-fixpoint: base wrote the narrowed state to trace. Scatter the - // narrowed ValVars back to def-sites. - const ICFGNode* cycle_head = cycle->head()->getICFGNode(); - const AbstractState& next = abstractTrace[cycle_head]; - for (const auto& [id, val] : next.getVarToVal()) - updateAbsValue(svfir->getSVFVar(id), val, cycle_head); - return false; -} - -// ===================================================================== -// Semi-sparse state-access overrides (used by both SemiSparse and -// FullSparse subclasses; the latter further restricts ValVar reads). -// ===================================================================== - -void SemiSparseAbstractInterpretation::updateAbsState( - const ICFGNode* node, const AbstractState& state) -{ - // Only replace ObjVar state. ValVars live at their def-sites and - // must not be overwritten when the predecessor's state is merged in. - abstractTrace[node].updateAddrStateOnly(state); -} - -void SemiSparseAbstractInterpretation::joinStates(AbstractState& dst, - const AbstractState& src) -{ - // ValVars live at def-sites in semi-sparse mode; they don't flow - // through state merges. Iterate src's ObjVar (_addrToAbsVal) entries - // directly and join into dst, leaving dst's ValVar map untouched. - // _freedAddrs (used by the null-deref detector) also rides along - // ICFG edges — there is no SVFG-level encoding of free events. - for (const auto& [id, val] : src.getLocToVal()) - { - u32_t addr = AbstractState::getVirtualMemAddress(id); - if (dst.getLocToVal().count(id)) - dst.load(addr).join_with(val); - else - dst.store(addr, val); - } - for (NodeID a : src.getFreedAddrs()) - dst.addToFreedAddrs(a); -} - -const ICFGNode* SemiSparseAbstractInterpretation::getICFGNode( - const ValVar* var) const -{ - // const ValVars are all defined in global node - if (!var->getICFGNode()) - { - return svfir->getICFG()->getGlobalICFGNode(); - } - // for return value of callsite, use the ret-site as def-site - else if (SVFUtil::isa(var->getICFGNode()) && - SVFUtil::isa(var)) - { - return SVFUtil::dyn_cast(var->getICFGNode()) - ->getRetICFGNode(); - } - // for other ValVars, use their def-site as the node to query abstract - // value. - else - { - return var->getICFGNode(); - } -} - -void SemiSparseAbstractInterpretation::updateAbsValue(const ValVar* var, - const AbstractValue& val, - const ICFGNode* node) -{ - // Write to the var's def-site so getAbsValue stays consistent. - const ICFGNode* defNode = var->getICFGNode(); - abstractTrace[defNode ? defNode : node][var->getId()] = val; -} - -const AbstractValue& SemiSparseAbstractInterpretation::getAbsValue( - const ValVar* var, const ICFGNode* node) -{ - // Read from the var's def-site (where updateAbsValue wrote it). - return AbstractInterpretation::getAbsValue(var, getICFGNode(var)); -} - -bool SemiSparseAbstractInterpretation::hasAbsValue(const ValVar* var, - const ICFGNode* node) const -{ - return AbstractInterpretation::hasAbsValue(var, getICFGNode(var)); -} diff --git a/svf/lib/AE/Test/BoxAEIntegrationTest.cpp b/svf/lib/AE/Test/BoxAEIntegrationTest.cpp new file mode 100644 index 0000000000..21fd81557b --- /dev/null +++ b/svf/lib/AE/Test/BoxAEIntegrationTest.cpp @@ -0,0 +1,158 @@ +//===- BoxAEIntegrationTest.cpp -- Box-backed AE integration test -------===// + +#include "AE/Core/BoxDomain.h" +#include "AE/Core/BoxProgramState.h" +#include "AE/Svfexe/AbstractInterpretation.h" +#include "AE/Svfexe/SVFIRAdapter.h" +#include "SVF-LLVM/SVFIRBuilder.h" +#include "Util/CommandLine.h" +#include "Util/Options.h" +#include "WPA/Andersen.h" + +#include +#include +#include +#include +#include + +using namespace SVF; + +namespace +{ +namespace AD = SVF::AbstractDomain; +using BoxProgramState = AD::BoxProgramState; + +const SVFVar* findValue(const SVFIR& graph, const std::string& name) +{ + for (auto iterator = graph.begin(); iterator != graph.end(); ++iterator) + { + const SVFVar* value = iterator->second; + const std::string& candidate = value->getValueName(); + if (candidate == name || candidate.rfind(name + " ", 0) == 0) + return value; + } + return nullptr; +} + +const BoxProgramState& requireBoxState(const AD::AbstractState& state) +{ + if (!state.isState()) + throw std::runtime_error(std::string("AE state is not Box-backed: ") + + state.name()); + return static_cast(state); +} + +const BoxProgramState& stateForValue(AbstractInterpretation& analysis, + const ValVar* value, const ICFGNode* node) +{ + if (const AD::AbstractState* checkpoint = + analysis.getScalarAbstractState(value)) + return requireBoxState(*checkpoint); + if (const AD::AbstractState* scalar = + analysis.getScalarAbstractState(value->getFunction())) + return requireBoxState(*scalar); + return requireBoxState(analysis.getAbstractState(node)); +} + +bool hasFiniteBounds(const AD::Interval& interval, s64_t lower, s64_t upper) +{ + return interval.lower().isFinite() && interval.upper().isFinite() && + interval.lower().value() == AD::Rational(lower) && + interval.upper().value() == AD::Rational(upper); +} + +void validateAuthoritativeStorage(AbstractInterpretation& analysis) +{ + if (analysis.getAnalyzedNodes().empty()) + throw std::runtime_error("Box AE analyzed no ICFG nodes"); + for (const ICFGNode* node : analysis.getAnalyzedNodes()) + requireBoxState(analysis.getAbstractState(node)); +} + +void validateProjection(const SVFIR& graph, AbstractInterpretation& analysis) +{ + const bool loopFixture = findValue(graph, "loop_result") != nullptr; + const SVFVar* result = findValue(graph, loopFixture ? "loop_result" : "z"); + if (!result) + return; + const auto* scalar = SVFUtil::dyn_cast(result); + if (!scalar) + throw std::runtime_error("Box fixture result is not an SSA value"); + + const s64_t expectedLower = loopFixture ? 4 : 1; + const s64_t expectedUpper = loopFixture ? 4 : 11; + SVFIRAdapter adapter(graph); + const AD::Variable variable = adapter.variable(*scalar); + bool observed = false; + for (const ICFGNode* node : analysis.getAnalyzedNodes()) + { + if (!analysis.hasAbsValue(scalar, node)) + continue; + const AbstractValue projected = analysis.getAbsValue(scalar, node); + const BoxProgramState& state = stateForValue(analysis, scalar, node); + if (projected.isInterval() && + projected.getInterval().equals( + IntervalValue(expectedLower, expectedUpper)) && + state.numerical().environment().contains(variable) && + hasFiniteBounds(state.numerical().bound(variable), expectedLower, + expectedUpper)) + observed = true; + } + if (!observed) + throw std::runtime_error( + "Box numerical state and AE value projection diverged"); +} + +void validateSparseMemoryRefinement(const SVFIR& graph, + AbstractInterpretation& analysis) +{ + const SVFVar* result = findValue(graph, "memory_result"); + if (!result) + return; + bool observedPositive = false; + for (const ICFGNode* node : analysis.getAnalyzedNodes()) + { + if (!analysis.hasAbsValue(result, node)) + continue; + const AbstractValue value = analysis.getAbsValue(result, node); + observedPositive |= value.isInterval() && + !value.getInterval().lb().is_infinity() && + value.getInterval().lb().getNumeral() == 1; + } + if (!observedPositive) + throw std::runtime_error( + "Box sparse memory refinement did not reach the second load"); +} +} // namespace + +int main(int argc, char** argv) +{ + try + { + const std::vector modules = OptionBase::parseOptions( + argc, argv, "Box AE integration test", "[options] "); + LLVMModuleSet::getLLVMModuleSet()->buildSVFModule(modules); + SVFIRBuilder builder; + SVFIR* graph = builder.build(); + AndersenWaveDiff* ander = + AndersenWaveDiff::createAndersenWaveDiff(graph); + builder.updateCallGraph(ander->getCallGraph()); + + AbstractInterpretation& analysis = + AbstractInterpretation::getAEInstance(); + analysis.runOnModule(); + validateAuthoritativeStorage(analysis); + validateProjection(*graph, analysis); + validateSparseMemoryRefinement(*graph, analysis); + + std::cout << "Box AE integration test: PASS\n"; + AndersenWaveDiff::releaseAndersenWaveDiff(); + LLVMModuleSet::releaseLLVMModuleSet(); + return EXIT_SUCCESS; + } + catch (const std::exception& error) + { + std::cerr << "Box AE integration test: FAIL: " << error.what() << '\n'; + return EXIT_FAILURE; + } +} diff --git a/svf/lib/AE/Test/BoxDomainTest.cpp b/svf/lib/AE/Test/BoxDomainTest.cpp new file mode 100644 index 0000000000..c7e5a3d567 --- /dev/null +++ b/svf/lib/AE/Test/BoxDomainTest.cpp @@ -0,0 +1,245 @@ +//===- BoxDomainTest.cpp -- Native Box domain regression tests ----------===// + +#include "AE/Core/BoxDomain.h" +#include "AE/Core/BoxProgramState.h" + +#include +#include +#include +#include +#include +#include + +using namespace SVF::AbstractDomain; + +namespace +{ +void require(bool condition, const std::string& message) +{ + if (!condition) + throw std::runtime_error(message); +} + +template +void requireThrows(Action&& action, const std::string& message) +{ + try + { + action(); + } + catch (const std::exception&) + { + return; + } + throw std::runtime_error(message); +} + +LinearConstraint atLeast(Variable variable, const Rational& value) +{ + return greaterEqual(LinearExpression(variable), LinearExpression(value)); +} + +LinearConstraint atMost(Variable variable, const Rational& value) +{ + return lessEqual(LinearExpression(variable), LinearExpression(value)); +} + +bool hasBounds(const Interval& interval, const Rational& lower, + const Rational& upper) +{ + return interval.lower().isFinite() && interval.upper().isFinite() && + interval.lower().value() == lower && + interval.upper().value() == upper; +} + +void testLatticeAndTransferSurface() +{ + const Variable x(1); + const Variable y(2); + const Variable z(3); + const VariableEnvironment environment({{x, NumericType::integer(), "x"}, + {y, NumericType::integer(), "y"}, + {z, NumericType::real(), "z"}}); + + BoxState state = BoxState::top(environment); + state.assume(atLeast(x, Rational(0))); + state.assume(atMost(x, Rational(10))); + state.assign(y, LinearExpression(x) + LinearExpression(Rational(2))); + require(hasBounds(state.bound(x), Rational(0), Rational(10)) && + hasBounds(state.bound(y), Rational(2), Rational(12)), + "Box assumptions and affine assignment lost interval bounds"); + require(hasBounds(state.bound(LinearExpression(x) + LinearExpression(y)), + Rational(2), Rational(22)), + "Box expression bounds did not use all terms"); + + BoxState simultaneous = state; + simultaneous.assignParallel( + {{x, LinearExpression(y)}, {y, LinearExpression(x)}}); + require(hasBounds(simultaneous.bound(x), Rational(2), Rational(12)) && + hasBounds(simultaneous.bound(y), Rational(0), Rational(10)), + "Box parallel assignment was not simultaneous"); + + BoxState post = BoxState::top(environment); + post.assume(atLeast(y, Rational(5))); + post.assume(atMost(y, Rational(7))); + post.substitute(y, LinearExpression(x) + LinearExpression(Rational(1))); + require(hasBounds(post.bound(x), Rational(4), Rational(6)), + "Box backward substitution computed the wrong preimage"); + + BoxState alternative = BoxState::top(environment); + alternative.assume(atLeast(x, Rational(5))); + alternative.assume(atMost(x, Rational(20))); + const BoxState joined = state.join(alternative); + const BoxState met = state.meet(alternative); + require(hasBounds(joined.bound(x), Rational(0), Rational(20)) && + hasBounds(met.bound(x), Rational(5), Rational(10)), + "Box join/meet did not compute interval hull/intersection"); + require(state.isSubsetOf(joined) == CheckResult::True && + met.isSubsetOf(state) == CheckResult::True, + "Box lattice ordering disagrees with join/meet"); + + const BoxState widened = state.widen(alternative); + require(widened.bound(x).upper().isPlusInfinity(), + "Box widening did not extrapolate an unstable upper bound"); + require(widened.narrow(alternative).bound(x).upper().value() == + Rational(20), + "Box narrowing did not recover the finite successor bound"); + + BoxState contradiction = BoxState::top(environment); + contradiction.assume(atLeast(x, Rational(2))); + contradiction.assume(atMost(x, Rational(1))); + require(contradiction.isBottom(), + "Box failed to detect contradictory bounds"); +} + +void testEnvironmentExpandFoldAndTrees() +{ + const Variable x(1); + const Variable y(2); + const Variable copy(4097); + const VariableEnvironment base( + {{x, NumericType::integer(), "x"}, {y, NumericType::integer(), "y"}}); + BoxState state = BoxState::top(base); + state.assume(atLeast(x, Rational(1))); + state.assume(atMost(x, Rational(3))); + state.expand(x, {{copy, NumericType::integer(), "copy"}}); + require(hasBounds(state.bound(copy), Rational(1), Rational(3)), + "Box expand did not duplicate the source interval"); + state.assume(atLeast(copy, Rational(2))); + state.fold(x, {copy}); + require(!state.environment().contains(copy) && + hasBounds(state.bound(x), Rational(1), Rational(3)), + "Box fold did not merge and remove the expanded dimension"); + + const VariableEnvironment extended = + state.environment().add({{copy, NumericType::integer(), "copy"}}); + state.changeEnvironment(extended, true); + require(hasBounds(state.bound(copy), Rational(0), Rational(0)), + "Box environment extension did not initialize a new variable"); + state.changeEnvironment(base); + require(!state.environment().contains(copy), + "Box environment projection retained a removed variable"); + requireThrows( + [&] { + state.changeEnvironment( + VariableEnvironment({{x, NumericType::real(), "x"}, + {y, NumericType::integer(), "y"}})); + }, + "Box accepted an environment type change"); + + TreeExpression xTree = TreeExpression::variable(x, NumericType::integer()); + TreeExpression two = + TreeExpression::constant(Rational(2), NumericType::integer()); + state.assign(y, TreeExpression::binary(BinaryOperator::Multiply, xTree, two, + NumericType::integer())); + require(hasBounds(state.bound(y), Rational(2), Rational(6)), + "Box nonlinear tree interval evaluation lost finite bounds"); +} + +void testPagedCopyOnWriteAndSerialization() +{ + std::vector declarations; + for (std::uint32_t id = 0; id < 256; ++id) + declarations.push_back({Variable(id * 17 + 1), NumericType::integer(), + "v" + std::to_string(id)}); + const VariableEnvironment environment(std::move(declarations)); + const Variable first = environment.variableOf(0); + const Variable distant = environment.variableOf(200); + + BoxState original = BoxState::top(environment); + original.assume(atLeast(first, Rational(1))); + original.assume(atMost(first, Rational(3))); + original.assume(atLeast(distant, Rational(9))); + original.assume(atMost(distant, Rational(11))); + BoxState copy = original; + copy.assign(first, LinearExpression(Rational(7))); + require(hasBounds(original.bound(first), Rational(1), Rational(3)) && + hasBounds(copy.bound(first), Rational(7), Rational(7)) && + hasBounds(copy.bound(distant), Rational(9), Rational(11)), + "paged Box COW mutated a source or detached unrelated data"); + + const NumericalState::RawBuffer raw = original.serializeRaw(); + std::unique_ptr restored = + NumericalState::deserializeRaw(raw); + require(restored->isState() && + restored->isEquivalentTo(original) == CheckResult::True && + restored->hash() == original.hash(), + "Box raw round-trip changed semantic state or hash"); + NumericalState::RawBuffer corrupt = raw; + corrupt[corrupt.size() / 2] ^= 1U; + requireThrows([&] { (void)NumericalState::deserializeRaw(corrupt); }, + "Box raw deserialization accepted corrupt data"); +} + +void testProgramStateMemoryFacet() +{ + const Variable pointer(1); + const Variable source(2); + const Variable target(3); + const Variable cell(4); + const VariableEnvironment environment( + {{pointer, NumericType::integer(), "pointer"}, + {source, NumericType::integer(), "source"}, + {target, NumericType::integer(), "target"}, + {cell, NumericType::integer(), "cell"}}); + const Location object(10); + BoxProgramState state(BoxState::top(environment), + MemoryLayout({{object, cell}})); + state.allocate(object); + state.assignPointer(pointer, PointeeSet::singleton(object)); + state.assignNumeric(source, LinearExpression(Rational(7))); + state.store(pointer, source); + state.load(target, pointer); + require( + hasBounds(state.numerical().bound(target), Rational(7), Rational(7)), + "Box program state did not preserve a strong store/load"); + state.release(pointer); + require(state.lifetimes().mustBeFreed(object), + "Box program state did not preserve released-memory status"); + + BoxProgramState other = state; + other.assignNumeric(source, LinearExpression(Rational(9))); + BoxProgramState joined = state; + joined.joinWith(other); + require(other.isSubsetOf(joined) == CheckResult::True, + "Box program-state join omitted a component"); +} +} // namespace + +int main() +{ + try + { + testLatticeAndTransferSurface(); + testEnvironmentExpandFoldAndTrees(); + testPagedCopyOnWriteAndSerialization(); + testProgramStateMemoryFacet(); + std::cout << "SVF Box domain test: PASS\n"; + return EXIT_SUCCESS; + } + catch (const std::exception& error) + { + std::cerr << "SVF Box domain test: FAIL: " << error.what() << '\n'; + return EXIT_FAILURE; + } +} diff --git a/svf/lib/AE/Test/BoxLoop.ll b/svf/lib/AE/Test/BoxLoop.ll new file mode 100644 index 0000000000..7149cc0c18 --- /dev/null +++ b/svf/lib/AE/Test/BoxLoop.ll @@ -0,0 +1,29 @@ +; Bounded-loop fixture for the unified program-state lifecycle. The cycle +; forces AE through widening and narrowing; the exit branch checks that the +; resulting interval and Octagon components agree that %i is exactly four. + +define i32 @main() { +entry: + br label %loop + +loop: + %i = phi i32 [ 0, %entry ], [ %next, %body ] + %continue = icmp slt i32 %i, 4 + br i1 %continue, label %body, label %exit + +body: + %next = add nsw i32 %i, 1 + br label %loop + +exit: + %too_small = icmp slt i32 %i, 4 + br i1 %too_small, label %unreachable, label %done + +unreachable: + %loop_bad = add nsw i32 %i, 100 + ret i32 %loop_bad + +done: + %loop_result = add nsw i32 %i, 0 + ret i32 %loop_result +} diff --git a/svf/lib/AE/Test/BoxReducedProduct.ll b/svf/lib/AE/Test/BoxReducedProduct.ll new file mode 100644 index 0000000000..7c4c973979 --- /dev/null +++ b/svf/lib/AE/Test/BoxReducedProduct.ll @@ -0,0 +1,32 @@ +; End-to-end reduced-product fixture. Interval AE alone does not propagate +; branch bounds between %x and its affine copy %y. The Octagon component does. + +define i32 @main(i32 %x, ptr %argv) { +entry: + %nonnegative = icmp sge i32 %x, 0 + br i1 %nonnegative, label %upper_check, label %exit + +upper_check: + %at_most_ten = icmp sle i32 %x, 10 + br i1 %at_most_ten, label %bounded, label %exit + +bounded: + %y = add nsw i32 %x, 0 + %impossible = icmp slt i32 %x, %y + br i1 %impossible, label %unreachable, label %small_check + +unreachable: + %bad = add nsw i32 %x, 100 + ret i32 %bad + +small_check: + %small = icmp sle i32 %x, 5 + br i1 %small, label %reduced, label %exit + +reduced: + %z = add nsw i32 %y, 1 + ret i32 %z + +exit: + ret i32 0 +} diff --git a/svf/lib/AE/Test/CMakeLists.txt b/svf/lib/AE/Test/CMakeLists.txt new file mode 100644 index 0000000000..1c7b03e113 --- /dev/null +++ b/svf/lib/AE/Test/CMakeLists.txt @@ -0,0 +1,91 @@ +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + cmake_minimum_required(VERSION 3.23) + project(SVFBoxDomainTests LANGUAGES CXX) + include(CTest) + + get_filename_component( + SVF_REPOSITORY_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../../.." ABSOLUTE + ) + list(APPEND CMAKE_MODULE_PATH "${SVF_REPOSITORY_ROOT}/cmake/Modules") + find_package(GMP REQUIRED) + find_package(MPFR REQUIRED) + + set(ABSTRACT_DOMAIN_SOURCE_ROOT "${SVF_REPOSITORY_ROOT}/svf") + add_library( + AbstractDomainCore + ${ABSTRACT_DOMAIN_SOURCE_ROOT}/lib/AE/Core/AbstractState.cpp + ${ABSTRACT_DOMAIN_SOURCE_ROOT}/lib/AE/Core/BoxDomain.cpp + ${ABSTRACT_DOMAIN_SOURCE_ROOT}/lib/AE/Core/LinearConstraint.cpp + ${ABSTRACT_DOMAIN_SOURCE_ROOT}/lib/AE/Core/BoxProgramState.cpp + ${ABSTRACT_DOMAIN_SOURCE_ROOT}/lib/AE/Core/NumericPrimitives.cpp + ${ABSTRACT_DOMAIN_SOURCE_ROOT}/lib/AE/Core/NumericalDomain.cpp + ${ABSTRACT_DOMAIN_SOURCE_ROOT}/lib/AE/Core/VariableEnvironment.cpp + ) + target_compile_features(AbstractDomainCore PUBLIC cxx_std_17) + target_include_directories( + AbstractDomainCore PUBLIC ${ABSTRACT_DOMAIN_SOURCE_ROOT}/include + ) + target_link_libraries(AbstractDomainCore PUBLIC GMP::GMPXX MPFR::MPFR) +endif() + +add_executable(svf-box-domain-test BoxDomainTest.cpp) +target_link_libraries(svf-box-domain-test PRIVATE AbstractDomainCore) +add_test(NAME box-domain COMMAND svf-box-domain-test) + +if(TARGET SvfLLVM) + add_executable(svf-box-ae-integration-test BoxAEIntegrationTest.cpp) + target_link_libraries(svf-box-ae-integration-test PRIVATE SvfLLVM) + + function(add_box_ae_test test_name fixture sparsity) + add_test( + NAME ${test_name} + COMMAND svf-box-ae-integration-test + -ae-sparsity=${sparsity} + -widen-delay=1 + -stat=false + -extapi=${CMAKE_BINARY_DIR}/lib/extapi.bc + ${CMAKE_CURRENT_LIST_DIR}/${fixture} + ) + endfunction() + + foreach(sparsity IN ITEMS dense semi-sparse sparse) + add_box_ae_test( + box-ae-projection-${sparsity} BoxReducedProduct.ll ${sparsity} + ) + add_box_ae_test( + box-ae-loop-${sparsity} BoxLoop.ll ${sparsity} + ) + add_box_ae_test( + box-ae-memory-refinement-${sparsity} SparseMemoryFlow.ll ${sparsity} + ) + endforeach() + add_box_ae_test( + box-ae-environment-alignment DenseEnvironmentAlignment.ll dense + ) + add_box_ae_test( + box-ae-wide-integer WideIntegerTruncation.ll dense + ) +endif() + +if(APPLE AND TARGET z3::libz3) + get_target_property(Z3_TEST_LIBRARY z3::libz3 IMPORTED_LOCATION) + get_filename_component( + Z3_TEST_LIBRARY_DIRECTORY "${Z3_TEST_LIBRARY}" DIRECTORY + ) + set(box_test_targets svf-box-domain-test) + if(TARGET svf-box-ae-integration-test) + list(APPEND box_test_targets svf-box-ae-integration-test) + endif() + foreach(box_test_target IN LISTS box_test_targets) + set_property( + TARGET ${box_test_target} APPEND + PROPERTY BUILD_RPATH "${Z3_TEST_LIBRARY_DIRECTORY}" + ) + add_custom_command( + TARGET ${box_test_target} POST_BUILD + COMMAND install_name_tool -change libz3.dylib @rpath/libz3.dylib + "$" + VERBATIM + ) + endforeach() +endif() diff --git a/svf/lib/AE/Test/DenseEnvironmentAlignment.ll b/svf/lib/AE/Test/DenseEnvironmentAlignment.ll new file mode 100644 index 0000000000..5cd12a8052 --- /dev/null +++ b/svf/lib/AE/Test/DenseEnvironmentAlignment.ll @@ -0,0 +1,27 @@ +; Cross-function fixture for state-local environment growth. Processing the +; call introduces callee variables into a caller state after its fixpoint +; snapshot was taken, so native Dense AE must align environments before state +; equivalence checks. + +define i32 @callee(i32 %callee_x) { +entry: + %callee_y = add nsw i32 %callee_x, 1 + ret i32 %callee_y +} + +define i32 @main(i32 %caller_x, ptr %argv) { +entry: + %env_result = call i32 @callee(i32 %caller_x) + %env_condition = icmp sgt i32 %env_result, 0 + br i1 %env_condition, label %positive, label %non_positive + +positive: + br label %merge + +non_positive: + br label %merge + +merge: + %env_phi = phi i32 [ %env_result, %positive ], [ 0, %non_positive ] + ret i32 %env_phi +} diff --git a/svf/lib/AE/Test/SparseMemoryFlow.ll b/svf/lib/AE/Test/SparseMemoryFlow.ll new file mode 100644 index 0000000000..3681ea4560 --- /dev/null +++ b/svf/lib/AE/Test/SparseMemoryFlow.ll @@ -0,0 +1,20 @@ +; Memory refinement fixture. The first load drives a branch refinement of the +; backing ObjVar. The second load must observe both the reaching store and the +; branch constraint, so %memory_result has lower bound one in every mode. + +define i32 @main(i32 %input) { +entry: + %cell = alloca i32, align 4 + store i32 %input, ptr %cell, align 4 + %first = load i32, ptr %cell, align 4 + %positive = icmp sgt i32 %first, 0 + br i1 %positive, label %positive_path, label %exit + +positive_path: + %second = load i32, ptr %cell, align 4 + %memory_result = add nsw i32 %second, 0 + ret i32 %memory_result + +exit: + ret i32 0 +} diff --git a/svf/lib/AE/Test/WideIntegerTruncation.ll b/svf/lib/AE/Test/WideIntegerTruncation.ll new file mode 100644 index 0000000000..3d4b1c014c --- /dev/null +++ b/svf/lib/AE/Test/WideIntegerTruncation.ll @@ -0,0 +1,7 @@ +; Regression for conservative handling of truncation targets wider than i32. + +define i64 @main(i32 %argc, ptr %argv) { +entry: + %wide_result = trunc i128 42 to i64 + ret i64 %wide_result +} diff --git a/svf/lib/Util/Options.cpp b/svf/lib/Util/Options.cpp index 4e15f9236f..4473acfce6 100644 --- a/svf/lib/Util/Options.cpp +++ b/svf/lib/Util/Options.cpp @@ -800,6 +800,9 @@ const OptionMap Options::AEFunEntry( }); const Option Options::WidenDelay( "widen-delay", "Loop Widen Delay", 3); +const Option Options::AESparseProfile( + "ae-sparse-profile", + "Print inclusive phase timings for native semi/full-sparse AE", false); const OptionMap Options::HandleRecur( "handle-recur", "Recursion handling mode in abstract execution (Default -widen-narrow)",