diff --git a/.clang-tidy b/.clang-tidy index 3308a915db..d7b7d2d702 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -19,6 +19,9 @@ Checks: '-*, -readability-identifier-length, -readability-redundant-member-init, -readability-use-anyofallof, + -readability-avoid-return-with-void-value, + -readability-use-std-min-max, + -readability-math-missing-parentheses, cppcoreguidelines-*, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, @@ -36,6 +39,7 @@ Checks: '-*, -modernize-use-trailing-return-type, -modernize-avoid-c-arrays, performance-*, + -performance-enum-size, clang-analyzer-* ' diff --git a/.dockerignore b/.dockerignore index 2b31c69a2b..22da52561e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,7 @@ * -!.git !cmake/ !config/ +!external/ !include/ !lib/ !test/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7cf419c0e3..ea0a25eda0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,9 +15,9 @@ jobs: fail-fast: true matrix: os: [ubuntu-24.04, ubuntu-24.04-arm] - compiler: [ [clang++-19, clang-19, "clang-19 libclang-rt-19-dev clang-tools-19"] ] + compiler: [ [clang++-21, clang-21, "clang-21 libclang-rt-21-dev clang-tools-21"] ] build: [ Debug, Release, DebugLibdeps, DebugCov ] - llvm-version: [ 16, 17 ] + llvm-version: [ 16, "22.1" ] include: - build: Debug cmake_build_type: Debug @@ -31,7 +31,11 @@ jobs: - build: DebugCov cmake_build_type: Debug flags: -DCODE_COVERAGE=ON - extra_dependencies: llvm-19 # For coverage + extra_dependencies: llvm-21 # For coverage + - llvm-version: 16 + llvm-major-version: 16 + - llvm-version: "22.1" + llvm-major-version: 22 exclude: - os: ubuntu-24.04-arm build: Debug @@ -43,7 +47,7 @@ jobs: continue-on-error: false steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: recursive @@ -56,7 +60,11 @@ jobs: - name: Install Phasar Dependencies shell: bash run: | - ./utils/InstallAptDependencies.sh --llvm-version ${{ matrix.llvm-version }} --noninteractive tzdata ccache ${{ matrix.compiler[2] }} ${{ matrix.extra_dependencies }} + ./utils/InstallAptDependencies.sh \ + --llvm-version ${{ matrix.llvm-major-version }} \ + --noninteractive \ + tzdata ccache ${{ matrix.compiler[2] }} ${{ matrix.extra_dependencies }} \ + llvm-${{ matrix.llvm-major-version }}-tools # For lit, FileCheck - name: Building Phasar in ${{ matrix.build }} with ${{ matrix.compiler[0] }} env: @@ -71,6 +79,7 @@ jobs: -DBUILD_PHASAR_CLANG=OFF \ -DPHASAR_USE_Z3=ON \ -DPHASAR_BUILD_MODULES=ON \ + -DPHASAR_TARGET_ARCH="" \ -DPHASAR_LLVM_VERSION=${{ matrix.llvm-version }} \ ${{ matrix.flags }} \ -G Ninja @@ -79,7 +88,7 @@ jobs: - name: Run Unittests shell: bash run: | - cmake --build ./build --target check-phasar-unittests + cmake --build ./build --target check-phasar-unittests check-phasar-cli - name: Install PhASAR and Build Examples if: matrix.build == 'DebugLibdeps' # Circumvent conflicting ASAn flags @@ -91,7 +100,7 @@ jobs: cmake -DCMAKE_INSTALL_PREFIX=./INSTALL -P ./build/cmake_install.cmake PHASAR_ROOT_DIR=$(pwd) cd ./examples/how-to - cmake -S . -B build -Dphasar_ROOT="$PHASAR_ROOT_DIR/INSTALL" + cmake -S . -B build -G Ninja -Dphasar_ROOT="$PHASAR_ROOT_DIR/INSTALL" cmake --build ./build --target run_sample_programs - name: Check test coverage and generate HTML report @@ -102,7 +111,7 @@ jobs: - name: Upload coverage HTML report artifact if: matrix.build == 'DebugCov' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: phasar-coverage-report-llvm-${{ matrix.llvm-version }} path: ./build/ccov/all-merged/ diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 702184a4f4..495ec24474 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -18,7 +18,10 @@ jobs: contents: read steps: - name: Check out the repo - uses: actions/checkout@v4 + uses: actions/checkout@v7 + with: + fetch-depth: 0 + submodules: recursive - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to the Container registry diff --git a/.gitignore b/.gitignore index 2695fef470..7af933a2c1 100644 --- a/.gitignore +++ b/.gitignore @@ -142,3 +142,6 @@ unittests/PhasarLLVM/ifdside/Problems/IFDSTaintAnalysisTest unittests/PhasarLLVM/Pointer/LLVMTypeHierarchyTest unittests/Utils/LLVMShorthandsTest unittests/Utils/PAMMTest + +# LLVM lit test times files +*.lit_test_times.txt diff --git a/.gitmodules b/.gitmodules index 350885ac54..4afb3bad23 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,6 @@ [submodule "external/json-schema-validator"] path = external/json-schema-validator url = https://github.com/pboettch/json-schema-validator.git +[submodule "external/CRoaring"] + path = external/CRoaring + url = https://github.com/fabianbs96/CRoaring.git diff --git a/BUILD.md b/BUILD.md index e8c026e721..4439bc0956 100644 --- a/BUILD.md +++ b/BUILD.md @@ -4,7 +4,9 @@ It is recommended to compile PhASAR yourself in order to get the full C++ experience and to have full control over the build mode. However, you may also want to try out one of the pre-built versions of PhASAR or the Docker container. -As a shortcut for the very first PhASAR build on your system, you can use our [bootstrap](./bootstrap.sh) script. +As a shortcut for the very first PhASAR build on your system, you can use our [bootstrap](./bootstrap.sh) script. It installs required dependencies and builds the libraries.
+**For subsequent builds**, see [Compiling PhASAR](#compiling-phasar-if-not-already-done-using-the-bootstrap-script). + Please note that you must have python installed for the script to work properly. ```bash @@ -19,11 +21,22 @@ Note: If you want to do changes within PhASAR, it is recommended to build it in The bootstrap script may ask for superuser permissions (to install the dependencies); however it is not recommended to start the whole script with `sudo`. -For subsequent builds, see [Compiling PhASAR](#compiling-phasar-if-not-already-done-using-the-installation-scripts). +### Requirements + +PhASAR requires the following system dependencies: +- LLVM >=16 + - We have tested PhASAR only with LLVM versions built with RTTI (runtime-type-info) enabled. Usually, this is no problem as the `llvm-dev` apt package ships with RTTI by default, but may be relevant if you wish to build LLVM from source. +- C++20 compatible compiler, *we recommend clang >=20* +- cmake >=3.23 +- some build system, *we recommend ninja* +- depending on your LLVM configuration, you may need `zlib1g-dev`, `libzstd-dev` +- *optional*: SVF >= 3.1, if you want to use SVF's points-to analyses from within PhASAR (`-DPHASAR_USE_SVF=ON` in cmake) + +If you are on a Debian-based system, you may use `./utils/InstallAptDependencies.sh` to install all required dependencies. If using the `bootstrap.sh` script, it does that for you. ### Compiling PhASAR (if not already done using the bootstrap script) -Set the system's variables for the C and C++ compiler to clang: +Set the system's variables for the C and C++ compiler to clang (gcc should also work, but we strongly recommend clang): ```bash export CC=/usr/local/bin/clang @@ -45,21 +58,31 @@ utils/init-submodules-release.sh Navigate into the PhASAR directory. The following commands will do the job and compile the PhASAR framework: ```bash -mkdir build +mkdir -p build cd build/ -cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .. +cmake -G Ninja -DCMAKE_BUILD_TYPE=Release .. # ninja is not required, but recommended for + # build-performance ninja -j $(nproc) # or use a different number of cores to compile it sudo ninja install # only if you wish to install PhASAR system wide ``` +To uninstall PhASAR from the same install location, invoke the `uninstall` target +from the build directory: + +```bash +sudo ninja uninstall +``` + When you have used the `bootstrap.sh` script to install PhASAR, the above steps are already done. Use them as a reference if you wish to modify PhASAR and recompile it. -After compilation using cmake the following two binaries can be found in the build/tools directory: +After compilation using cmake the following two binaries can be found in the `build/tools` directory: + `phasar-cli` - the PhASAR command-line tool (previously called `phasar-llvm`) that provides access to analyses that are already implemented within PhASAR. Use this if you don't want to build an own tool on top of PhASAR. + `myphasartool` - an example tool that shows how tools can be build on top of PhASAR +Also, find the `libphasar.a` or `libphasar.so` library in `build/lib/`. + Please be careful and check if errors occur during the compilation. When using CMake to compile PhASAR the following optional parameters can be used: @@ -69,18 +92,20 @@ When using CMake to compile PhASAR the following optional parameters can be used | **BUILD_SHARED_LIBS** : BOOL | Build shared libraries -- Not recommended anymore. You may want to use PHASAR_BUILD_DYNLIB instead (default is OFF) | | **PHASAR_BUILD_DYNLIB** : BOOL | Build one fat shared library (default is OFF) | | **CMAKE_BUILD_TYPE** : STRING | Build PhASAR in 'Debug', 'RelWithDebInfo' or 'Release' mode (default is 'Debug') | -| **CMAKE_INSTALL_PREFIX** : PATH | Path where PhASAR will be installed if "ninja install” is invoked or the “install” target is built (default is /usr/local/phasar) | +| **CMAKE_INSTALL_PREFIX** : PATH | Path where PhASAR will be installed if "ninja install” is invoked or the “install” target is built (default is /usr/local/phasar or whatever is your system's default install location) | | **PHASAR_CUSTOM_CONFIG_INSTALL_DIR** : PATH | If set, customizes the directory, where configuration files for PhASAR are installed (default is /usr/local/.phasar-config)| -| **PHASAR_ENABLE_DYNAMIC_LOG** : BOOL|Makes it possible to switch the logger on and off at runtime (default is ON)| -| **PHASAR_BUILD_DOC** : BOOL | Build PhASAR documentation (default is OFF) | -| **PHASAR_BUILD_UNITTESTS** : BOOL | Build PhASAR unit tests (default is ON) | +| **PHASAR_ENABLE_DYNAMIC_LOG** : BOOL|Makes it possible to switch the logger on and off at runtime, otherwise all log-statements are compiled-out (default is ON)| +| **PHASAR_BUILD_DOC** : BOOL | Build PhASAR's Doxygen documentation (default is OFF) | | **PHASAR_BUILD_IR** : BOOL | Build PhASAR IR (required for running the unit tests) (default is ON) | +| **PHASAR_BUILD_UNITTESTS** : BOOL | Build PhASAR unit tests. Requires `PHASAR_BUILD_IR` (default is ON) | +| **PHASAR_ENABLE_INTEGRATIONTESTS** : BOOL | Build PhASAR LIT integration tests. Requires `PHASAR_BUILD_IR` (default is ON) | | **PHASAR_BUILD_OPENSSL_TS_UNITTESTS** : BOOL | Build PhASAR unit tests that require OpenSSL (default is OFF) | | **PHASAR_ENABLE_PAMM** : STRING | Enable the performance measurement mechanism ('Off', 'Core' or 'Full', default is Off) | | **PHASAR_ENABLE_PIC** : BOOL | Build Position-Independed Code (default is ON) | | **PHASAR_ENABLE_WARNINGS** : BOOL | Enable compiler warnings (default is ON) | | **CMAKE_CXX_STANDARD** : INT|Adapt the used C++ standard (minimum required is 20)| -| **PHASAR_LLVM_VERSION** : INT|The LLVM major-version to use. Can be 16 or 17 (default is 16)| +| **PHASAR_LLVM_VERSION** : VERSION|The LLVM major-version to use. Can be between 16 and 22.1 (default is 16)| +| **PHASAR_USE_SVF** : BOOL| Enable using SVF points-to info from PhASAR. See [Use PhASAR with SVF](https://github.com/secure-software-engineering/phasar/wiki/FAQ#how-to-use-phasar-with-svf). Requires SVF installed (default OFF) | You can use these parameters either directly or modify the installer-script `bootstrap.sh` @@ -88,15 +113,30 @@ You can use these parameters either directly or modify the installer-script `boo C++'s long compile times are always a pain. As shown in the above, when using cmake the compilation can easily be run in parallel, resulting in shorter compilation times. Make use of it! -### Running a Test Solver +### Using PhASAR with Conan v2 -To test if everything works as expected please run the following command: +To export the recipe and dependencies, execute from the repo root: -`$ phasar-cli -m test/llvm_test_code/basic/module_cpp.ll -D ifds-solvertest` +- `conan export utils/conan/llvm-core/ --version 15.0.7 --user secure-software-engineering` +- `conan export utils/conan/clang/ --version 15.0.7 --user secure-software-engineering` +- `conan export .` +- View exported: `conan list "phasar/*"` +- [Consume the package](https://docs.conan.io/2/tutorial/consuming_packages.html) -You can find the `phasar-cli` tool in the build-tree under `tools/phasar-cli`. +If you just want to use phasar-cli: -If you obtain output other than a segmentation fault or an exception terminating the program abnormally everything works as expected. +- `conan install --tool-requires phasar/... --build=missing -of .` +- `source conanbuild.sh` +- `phasar-cli --help` + +### Running a Test Solver + +To test if everything works as expected please run the following command (from the `build/` folder): + +`$ ./tools/phasar-cli/phasar-cli -m test/llvm_test_code/basic/module_cpp.ll -D ifds-solvertest --emit-raw-results` + +The output should show that the `@zero_value` data-flow fact holds at every LLVM instruction in the module. +Especially, there should be no segmentation fault, exception, or other non-zero exit condition. ### Building PhASAR on a MacOS System @@ -107,7 +147,7 @@ Now you can just attach your docker container to VS Code or any other IDE, which ## Installation PhASAR can be installed using the installer scripts as explained in the following. -However, you do not need to install PhASAR in order to use it. +However, **you do not need to install PhASAR in order to use it**. ### Installing PhASAR on an Ubuntu System @@ -129,3 +169,9 @@ If You have already built phasar, you can just invoke ```bash sudo ninja install ``` + +To uninstall PhASAR again, invoke the uninstall target from the same build directory: + +```bash +sudo ninja uninstall +``` diff --git a/BreakingChanges.md b/BreakingChanges.md index f113d64b3c..c8805cfbb8 100644 --- a/BreakingChanges.md +++ b/BreakingChanges.md @@ -2,7 +2,18 @@ ## development HEAD -*None* +- *None* + +## v2610 + +- Existing IFDS/IDE problems provided by PhASAR do no longer inherit from `IFDSTabulationProblem`/`IDETabulationProblem`. The solvers can now deal with them without polymorphic inheritance hierarchy. However, **you cannot cast an analysis problem to one of these base classes anymore**. If that's required (unlikely), wrap your problem in a `LegacyIDEProblemWrapper`. + - Although the IFDS/IDETabulationProblem classes still work, new IFDS/IDE analysis problems should conform to the `IFDSProblem`/`IDEProblem` concepts instead. Use `IfdsIdeProblemMixin` as helper for that. +- `IDESolver` and `IFDSSolver` now take analysis problems by pointer instead of by reference to better document that they capture the address (without taking ownership). +- `FlowFunctionPtrType` is now a move-only type. +- Removed the CRTP-template `ProjectIRDBBase`. Use the concept `ProjectIRDB` instead. +- Removed `EdgeFunction::equals`. Use `operator==` instead. +- Removed ctor-overloads of `LLVMProjectIRDB` that use the `EnableOpaquePointers` parameter. LLVM is removing support for non-opaque pointers. Use the other ctors instead. +- Removed type-trait `has_getAsJson`, as it is not used in our codebase anymore. We switched from `getAsJson` to `printAsJson` a while ago. ## v2604 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bb9701c17..2171b80bdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.14...3.28) +cmake_minimum_required (VERSION 3.23...4.2) # 3.23+ needed by FILE_SET (https://cmake.org/cmake/help/latest/command/target_sources.html#file-sets) # Avoid IPO/LTO Warnings: cmake_policy(SET CMP0069 NEW) @@ -8,11 +8,9 @@ set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) cmake_policy(SET CMP0077 NEW) set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) -if ("${CMAKE_VERSION}" GREATER_EQUAL "3.21") - # Allow overwriting cache variables of external projects from this CMakeLists file - cmake_policy(SET CMP0126 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) -endif() +# Allow overwriting cache variables of external projects from this CMakeLists file +cmake_policy(SET CMP0126 NEW) +set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) # Allow portable use of CMAKE_VISIBILITY_INLINES_HIDDEN not only for shared libraries cmake_policy(SET CMP0063 NEW) @@ -23,7 +21,7 @@ if (DEFINED LLVM_MAIN_SRC_DIR) set(PHASAR_IN_TREE 1) endif() -set(PHASAR_VERSION 2604) +set(PHASAR_VERSION 2610) if (NOT PHASAR_IN_TREE) project (phasar LANGUAGES C CXX @@ -74,7 +72,9 @@ set(RELEASE_CONFIGURATIONS RELWITHDEBINFO RELEASE CACHE INTERNAL "" FORCE) string(APPEND CMAKE_CXX_FLAGS " -MP -fstack-protector-strong -ffunction-sections -fdata-sections -pipe") string(APPEND CMAKE_CXX_FLAGS_DEBUG " -fno-omit-frame-pointer") +string(APPEND CMAKE_C_FLAGS_DEBUG " -fno-omit-frame-pointer") string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -fno-omit-frame-pointer") +string(APPEND CMAKE_C_FLAGS_RELWITHDEBINFO " -fno-omit-frame-pointer") string(APPEND CMAKE_CXX_FLAGS_RELEASE "") option(CMAKE_VISIBILITY_INLINES_HIDDEN "Hide inlined functions from the DSO table (default ON)" ON) @@ -123,6 +123,7 @@ if (NOT "${PHASAR_TARGET_ARCH_INTERNAL}" STREQUAL "") if (MARCH_SUPPORTED) message(STATUS "Target architecture '${PHASAR_TARGET_ARCH_INTERNAL}' enabled") string(APPEND CMAKE_CXX_FLAGS_RELEASE " -march=${PHASAR_TARGET_ARCH_INTERNAL}") + string(APPEND CMAKE_C_FLAGS_RELEASE " -march=${PHASAR_TARGET_ARCH_INTERNAL}") else() message(WARNING "Target architecture '${PHASAR_TARGET_ARCH_INTERNAL}' not supported. Fallback to generic build") endif() @@ -158,6 +159,7 @@ if(PHASAR_ALLOW_LTO_IN_RELEASE_BUILD) endif() option(PHASAR_BUILD_UNITTESTS "Build all tests (default is ON)" ${PHASAR_BUILD_OPTIONAL_TARGETS_DEFAULT}) +option(PHASAR_ENABLE_INTEGRATIONTESTS "Enable LIT integration tests (default is ON)" ${PHASAR_BUILD_OPTIONAL_TARGETS_DEFAULT}) option(PHASAR_BUILD_OPENSSL_TS_UNITTESTS "Build OPENSSL typestate tests (require OpenSSL, default is OFF)" OFF) @@ -197,18 +199,16 @@ if (PHASAR_ENABLE_PIC) endif (PHASAR_ENABLE_PIC) # PAMM -if (NOT PHASAR_ENABLE_PAMM) - set(PHASAR_ENABLE_PAMM "Off" CACHE STRING "Enable the performance measurement mechanism ('Off', 'Core' or 'Full', default is 'Off')" FORCE) - set_property(CACHE PHASAR_ENABLE_PAMM PROPERTY STRINGS "Off" "Core" "Full") -endif() -if(PHASAR_ENABLE_PAMM STREQUAL "Core" AND NOT PHASAR_BUILD_UNITTESTS) +set(PHASAR_ENABLE_PAMM "Off" CACHE STRING "Enable the performance measurement mechanism ('Off', 'Core' or 'Full', default is 'Off')") +set_property(CACHE PHASAR_ENABLE_PAMM PROPERTY STRINGS "Off" "Core" "Full") +option(PHASAR_THREAD_SAFE_PAMM "Use a thread-safe version of PAMM, if PAMM is enabled" ON) + +if(PHASAR_ENABLE_PAMM STREQUAL "Core") set(PAMM_CORE ON) message(STATUS "PAMM metric severity level: Core") -elseif(PHASAR_ENABLE_PAMM STREQUAL "Full" AND NOT PHASAR_BUILD_UNITTESTS) +elseif(PHASAR_ENABLE_PAMM STREQUAL "Full") set(PAMM_FULL ON) message(STATUS "PAMM metric severity level: Full") -elseif(PHASAR_BUILD_UNITTESTS AND (PHASAR_ENABLE_PAMM STREQUAL "Core" OR PHASAR_ENABLE_PAMM STREQUAL "Full")) - message(WARNING "PAMM metric severity level: Off (due to unittests)") else() message(STATUS "PAMM metric severity level: Off") endif() @@ -225,16 +225,6 @@ endif() if (PHASAR_USE_CONAN) elseif (NOT PHASAR_IN_TREE) - # RPATH - set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) - - if (NOT "${CMAKE_INSTALL_LIBDIR}" STREQUAL "lib") - message(STATUS "Detected CMAKE_INSTALL_LIBDIR that deviates from 'lib': ${CMAKE_INSTALL_LIBDIR}. Add ${CMAKE_INSTALL_PREFIX}/lib to the RPATH as json-schema-validator needs it") - list(APPEND CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib) - endif() - - set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) - # Export set set(PHASAR_DEPS_EXPORT_SET PhasarDepsExports) else() @@ -280,11 +270,15 @@ include(add_nlohmann_json) add_nlohmann_json() add_json_schema_validator() +# TBB +find_package(TBB QUIET) # needed by GCC +set(PHASAR_USE_TBB ${TBB_FOUND}) + # Coverage if (CODE_COVERAGE) set(CODE_COVERAGE_SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/code-coverage.cmake") - file(DOWNLOAD "https://raw.githubusercontent.com/StableCoder/cmake-scripts/refs/tags/25.08/code-coverage.cmake" "${CODE_COVERAGE_SCRIPT}" - EXPECTED_HASH SHA256=cad4dd2bdf944f77627687e0298debc8341bfdeffaf949937f319c7c4ff3fdaf + file(DOWNLOAD "https://raw.githubusercontent.com/StableCoder/cmake-scripts/c012a75c255e57523c9e2faacd86356439318542/code-coverage.cmake" "${CODE_COVERAGE_SCRIPT}" + EXPECTED_HASH SHA256=21caeb58c8270b00a15c58b06cab72255467f9917d07df5a5042259bbcdfd1ab TLS_VERIFY ON STATUS CODE_COVERAGE_SCRIPT_DOWNLOAD_STATUS ) @@ -339,6 +333,15 @@ set(PHASAR_LLVM_VERSION 16 CACHE STRING "The LLVM major-version that PhASAR shou include(add_llvm) add_llvm() +# Roaring + +find_package(roaring QUIET CONFIG) +if(NOT TARGET roaring::roaring) + set(ENABLE_ROARING_TESTS OFF) + add_subdirectory(external/CRoaring) + set(PHASAR_PROVIDE_CROARING ON) +endif() + # SVF option(PHASAR_USE_SVF "Use SVF for more options in alias analysis (default is OFF)" OFF) if(PHASAR_USE_SVF) @@ -425,6 +428,9 @@ target_sources(phasar_interface INTERFACE BASE_DIRS "${PHASAR_SRC_DIR}/include" "${PHASAR_BINARY_DIR}/include" FILES ${PHASAR_PUBLIC_HEADERS} "${PHASAR_BINARY_DIR}/include/phasar/Config/phasar-config.h" ) +if (TARGET TBB::tbb) + target_link_libraries(phasar_interface INTERFACE TBB::tbb) # needed by GCC +endif() # Some preprocessor symbols that need to be available in phasar sources, but should not be installed add_cxx_compile_definitions(PHASAR_SRC_DIR="${CMAKE_SOURCE_DIR}") @@ -538,6 +544,20 @@ install( DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/phasar" ) +if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND NOT TARGET uninstall) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + @ONLY + ) + + add_custom_target(uninstall + COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + COMMENT "Uninstalling PhASAR" + VERBATIM + ) +endif() + # If the Phasar shared object libraries are not installed into a system folder # the so libs must be added manually to the linker search path and the linker # config must be updated as follows: diff --git a/Config.cmake.in b/Config.cmake.in index 085a277031..da9ffd1ffa 100644 --- a/Config.cmake.in +++ b/Config.cmake.in @@ -15,6 +15,18 @@ set(PHASAR_USE_LLVM_FAT_LIB @USE_LLVM_FAT_LIB@) set(PHASAR_BUILD_DYNLIB @PHASAR_BUILD_DYNLIB@) set(PHASAR_USE_Z3 @PHASAR_USE_Z3@) set(PHASAR_BUILD_MODULES @PHASAR_BUILD_MODULES@) +set(PHASAR_PROVIDE_CROARING @PHASAR_PROVIDE_CROARING@) + +if (PHASAR_PROVIDE_CROARING) + # TODO: Is that path portable? + include("${CMAKE_CURRENT_LIST_DIR}/../roaring/roaring-targets.cmake") +else() + find_dependency(roaring REQUIRED CONFIG) +endif() + +if (@PHASAR_USE_TBB@) + find_dependency(TBB) +endif() if (PHASAR_USE_Z3) find_dependency(Z3 REQUIRED) diff --git a/Dockerfile b/Dockerfile index ae8b16d24f..dada676b79 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,10 +3,10 @@ FROM "$baseimage" AS build RUN --mount=type=bind,source=./utils/InstallAptDependencies.sh,target=/InstallAptDependencies.sh \ set -eux; \ - ./InstallAptDependencies.sh --noninteractive tzdata clang-19 libclang-rt-19-dev clang-tools-19 + ./InstallAptDependencies.sh --noninteractive tzdata clang-20 libclang-rt-20-dev clang-tools-20 llvm-16-tools llvm-20-tools -ENV CC=/usr/bin/clang-19 \ - CXX=/usr/bin/clang++-19 +ENV CC=/usr/bin/clang-20 \ + CXX=/usr/bin/clang++-20 FROM build @@ -14,7 +14,10 @@ ARG RUN_TESTS=OFF RUN --mount=type=bind,source=.,target=/usr/src/phasar,rw \ set -eux; \ cd /usr/src/phasar; \ - git submodule update --init; \ + if find external -mindepth 1 -maxdepth 1 -type d -empty | grep -q .; then \ + echo "external/* submodules are not checked out; run 'git submodule update --init --recursive' on the host before building"; \ + exit 1; \ + fi; \ cmake -S . -B cmake-build/Release \ -DCMAKE_BUILD_TYPE=Release \ -DPHASAR_TARGET_ARCH="" \ @@ -23,10 +26,11 @@ RUN --mount=type=bind,source=.,target=/usr/src/phasar,rw \ -DPHASAR_USE_Z3=ON \ -DPHASAR_BUILD_UNITTESTS=$RUN_TESTS \ -DPHASAR_BUILD_IR=$RUN_TESTS \ + -DPHASAR_ENABLE_INTEGRATIONTESTS=$RUN_TESTS \ -DPHASAR_BUILD_OPENSSL_TS_UNITTESTS=OFF \ -G Ninja; \ ninja -C cmake-build/Release install; \ - [ "${RUN_TESTS}" = "ON" ] && ctest --test-dir cmake-build/Release --output-on-failure || true; \ + [ "${RUN_TESTS}" = "OFF" ] || cmake --build ./cmake-build/Release --target check-phasar-unittests check-phasar-cli ; \ phasar-cli --version ENTRYPOINT [ "phasar-cli" ] diff --git a/LICENSE.txt b/LICENSE.txt index 6b44aff4be..ed8ad367db 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -208,3 +208,38 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +CRoaring +------------------------------------------------------------------------------- +The CRoaring project is under a dual license (Apache/MIT). +Users of the library may choose one or the other license. +--- + +MIT License + +Copyright 2016-2022 The CRoaring authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index db217e8d39..ea2b1673f0 100644 --- a/README.md +++ b/README.md @@ -8,53 +8,59 @@ ## What is PhASAR? -PhASAR is a LLVM-based static analysis framework written in C++. It allows users -to specify arbitrary data-flow problems which are then solved in a -fully-automated manner on the specified LLVM IR target code. Computing points-to -information, call-graph(s), etc. is done by the framework, thus you can focus on -what matters. +PhASAR is a LLVM-based static analysis framework written in C++. +It allows users to specify arbitrary data-flow problems which are then solved in a fully-automated manner on the specified LLVM IR target code. +Computing points-to information, call-graph(s), etc. is done by the framework, thus you can focus on what matters. You can find available literature on PhASAR [here](https://github.com/secure-software-engineering/phasar/wiki/Useful-Literature#papers-on-phasar). -### How do I get started with PhASAR? +### Key Features -We have some documentation on PhASAR in our [***Wiki***](https://github.com/secure-software-engineering/phasar/wiki). You probably would like to read -this README first. - -Please also have a look on PhASAR's project directory and notice the project directory [examples](./examples/) as well as the custom tool `tools/example-tool/myphasartool.cpp`. +- **IFDS/IDE solver**: Interprocedural data-flow solvers based on the IFDS/IDE algorithm +- **MonoIFDS solver**: High-performance data-flow solver for *bottom-up* IFDS analyses +- **Sparse analysis**: SparseIFDS/SparseIDE for improved performance +- **Call-graph construction**: Several algorithms (CHA, RTA, VTA, alias-based) +- **Type-hierarchy construction**: Extract high-level C++ type information from LLVM IR +- **Points-to/alias infrastructure**: High-performance alias analyses for LLVM IR. Integration with state-of-the-art alias/points-to information from SVF supported +- **Interprocedural CFG (ICFG)**: Connecting control-flow with call-graph information +- **Path-tracking**: Improve results-reporting by reconstruct concrete data-flow paths from IFDS/IDE results +- **Monotone solver**: Simple intra-procedural analysis engine, based on Monotone Frameworks +- **Taint analysis**: Infrastructure for taint-configuration & IFDS/IDE-based taint analysis +- **Modern C++20 API**: Modular, easy-to use interfaces, also for non C++ experts -You can find PhASAR's API reference [here](https://secure-software-engineering.github.io/phasar/). +#### Experimental Features +- **WPDS solver**: Data-flow solver based on weighted pushdown systems. Can solve any IFDS/IDE problem; supports sparsification +- **DFI solver**: Data-flow solver based on DFS-interval reachability. Can solve any IFDS problem; supports sparsification -## Secure Software Engineering Group +### How do I get started with PhASAR? -PhASAR is primarily developed and maintained by the Secure Software Engineering Group at Heinz Nixdorf Institute (University of Paderborn) and Fraunhofer IEM. +We have some documentation on PhASAR in our [***Wiki***](https://github.com/secure-software-engineering/phasar/wiki). You probably would like to read this README first. -PhASAR was initially developed by Philipp Dominik Schubert (@pdschubert)(). +Please also have a look at PhASAR's project directory, in particular the [examples](./examples/) directory and the custom tool `tools/example-tool/myphasartool.cpp`. -Currently, PhASAR is maintained by -- Fabian Schiebel (@fabianbs96)() -- Sriteja Kummita (@sritejakv) -- Lucas Briese (@jusito) -- Martin Mory (@MMory)() -- *others* +You can find PhASAR's API reference [here](https://secure-software-engineering.github.io/phasar/). + +## Requirements -## Required Version of the C++ Standard +### C++ Standard -**NEW**: PhASAR requires at least C++-20. +PhASAR requires at least C++20. PhASAR supports C++20 modules as an experimental feature. -## Currently Supported Version of LLVM +### LLVM Version -**NEW**: PhASAR is currently set up to support **LLVM-16 and 17**, using LLVM 16 by default.
-Specify the `PHASAR_LLVM_VERSION` cmake-variable to change the LLVM version to use. +PhASAR supports LLVM versions **between LLVM-16 and LLVM-22.1**, using LLVM-16 by default. +We actively test PhASAR with LLVM-16 and LLVM-22.1, so if something does not work, try these versions instead. +Specify the `PHASAR_LLVM_VERSION` cmake variable to change the LLVM version to use. +> [!NOTE] +> Since v18, LLVM uses minor versions, so use `-DPHASAR_LLVM_VERSION=17` for LLVM-17 but `-DPHASAR_LLVM_VERSION=18.1` for LLVM-18 ## Breaking Changes -To keep PhASAR in a state that it is well suited for state-of-the-art research in static analysis, as well as for productive use, we have to make breaking changes. Please refer to [Breaking Changes](./BreakingChanges.md) for detailed information on what was broken recently and how to migrate. +To keep PhASAR in a state that is well suited for state-of-the-art research in static analysis, as well as for productive use, we have to make breaking changes. Please refer to [Breaking Changes](./BreakingChanges.md) for detailed information on what was broken recently and how to migrate. ## Building PhASAR @@ -62,12 +68,39 @@ Please refer to [BUILD.md](./BUILD.md) for instructions on how to build PhASAR. ## How to use PhASAR? -We recomment using phasar as a library with `cmake` or `conan`. +The following example shows how to use PhASAR's core concepts of IFDS/IDE analysis, alias analysis, type-hierarchy, call-graph, and taint analysis: -If you already have installed phasar, [Use-PhASAR-as-a-library](https://github.com/secure-software-engineering/phasar/wiki/Using-Phasar-as-a-Library) may be a good start. +```cpp +#include "phasar.h" -Otherwise, we recommend adding PhASAR as a git submodule to your repository. -In this case, just `add_subdirectory` the phasar submodule directory within your `CMakeLists.txt`. +// Load the target LLVM IR +auto IRDB = psr::LLVMProjectIRDB::loadOrExit("target.ll"); + +// Build alias information, a type-hierarchy, and a taint configuration +// (sources/sinks can come from IR annotations, a JSON file, or callbacks) +psr::LLVMAliasSet AS(&IRDB); +psr::DIBasedTypeHierarchy TH(IRDB); +psr::LLVMTaintConfig TC(IRDB); + +// Build the interprocedural CFG using VTA call-graph construction +psr::LLVMBasedICFG ICFG(&IRDB, psr::CallGraphAnalysisType::VTA, + {"main"}, &TH, &AS); + +// Instantiate and solve the taint analysis +psr::IFDSTaintAnalysis Problem(&IRDB, &AS, &TC, {"main"}); +psr::solveIFDSProblem(Problem, ICFG); + +// Inspect detected leaks +for (const auto &[Inst, Facts] : Problem.Leaks) { + llvm::outs() << "Leak at: " << psr::llvmIRToString(Inst) << '\n'; +} +``` + +For more examples, including how to write a custom analysis, see [examples/how-to/](./examples/how-to/). + +### Integrating PhASAR into your build + +We recommend using PhASAR as a library with `cmake`, using `FetchContent` or as git submodule. Assuming you have checked out phasar in `external/phasar`, the phasar-related cmake commands may look like this: @@ -84,41 +117,28 @@ target_link_libraries(yourphasartool Depending on your use of PhASAR you also may need to add LLVM to your build. -For more information please consult our [PhASAR wiki pages](https://github.com/secure-software-engineering/phasar/wiki). -## How to use with Conan v2 ? - -To export the recipe and dependencies execute from repo root: -- `conan export utils/conan/llvm-core/ --version 15.0.7 --user secure-software-engineering` -- `conan export utils/conan/clang/ --version 15.0.7 --user secure-software-engineering` -- `conan export .` -- View exported `conan list "phasar/*"` -- [Consume the package](https://docs.conan.io/2/tutorial/consuming_packages.html) +For more information please consult our [PhASAR wiki pages](https://github.com/secure-software-engineering/phasar/wiki). -If you just want to use phasar-cli: -- `conan install --tool-requires phasar/... --build=missing -of .` -- `source conanbuild.sh` -- `phasar-cli --help` +If you have PhASAR *installed*, [Use-PhASAR-as-a-library](https://github.com/secure-software-engineering/phasar/wiki/Using-Phasar-as-a-Library) may be a good start. -## Please help us to improve PhASAR +## Contributing -You are using PhASAR and would like to help us in the future? Then please -support us by filling out this [web form](https://goo.gl/forms/YG6m3M7sxeUJmKyi1). +You are very welcome to contribute to the PhASAR project. +Just raise an issue or a pull request on GitHub. -By giving us feedback you help to decide in what direction PhASAR should stride in -the future and give us clues about our user base. Thank you very much! +For details see [Contributing to PhASAR](https://github.com/secure-software-engineering/phasar/wiki/Contributing-to-PhASAR) and [Coding Conventions](https://github.com/secure-software-engineering/phasar/wiki/Coding-Conventions). -### Installing PhASAR's Git pre-commit hook +## Secure Software Engineering Group -You are very much welcome to contribute to the PhASAR project. -Please make sure that you install our pre-commit hook that ensures your commit adheres to the most important coding rules of the PhASAR project. -For more details please consult [Coding Conventions](https://github.com/secure-software-engineering/phasar/wiki/Coding-Conventions) and [Contributing to PhASAR](https://github.com/secure-software-engineering/phasar/wiki/Contributing-to-PhASAR). +PhASAR is primarily developed and maintained by the Secure Software Engineering Group at Heinz Nixdorf Institute (University of Paderborn) and Fraunhofer IEM. -To install the pre-commit hook, please run the following commands in PhASAR's root directory: +PhASAR was initially developed by Philipp Dominik Schubert (@pdschubert)(). -```bash -pip install pre-commit -pre-commit install -``` +Currently, PhASAR is maintained by -Thanks. And have fun with the project. +- Fabian Schiebel (@fabianbs96)() +- Sriteja Kummita (@sritejakv) +- Lucas Briese (@jusito) +- Martin Mory (@MMory)() +- *others* diff --git a/cmake/add_llvm.cmake b/cmake/add_llvm.cmake index 1cbc9cd637..55fc6e1edb 100644 --- a/cmake/add_llvm.cmake +++ b/cmake/add_llvm.cmake @@ -48,7 +48,7 @@ macro(add_llvm) endif(NOT PHASAR_IN_TREE) if(NOT LLVM_ENABLE_RTTI AND NOT PHASAR_IN_TREE) - message(FATAL_ERROR "PhASAR requires a LLVM version that is built with RTTI") + message(WARNING "PhASAR has been tested only with LLVM versions built with RTTI. You are trying to link an LLVM version *without* RTTI. Do at your own risk.") endif() endmacro() diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in new file mode 100644 index 0000000000..941910d13b --- /dev/null +++ b/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,81 @@ +set(PHASAR_INSTALL_MANIFEST "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +if (NOT EXISTS "${PHASAR_INSTALL_MANIFEST}") + message(FATAL_ERROR + "Cannot find install manifest: ${PHASAR_INSTALL_MANIFEST}. " + "Run the install target before uninstalling." + ) +endif() + +file(READ "${PHASAR_INSTALL_MANIFEST}" PHASAR_INSTALLED_FILES) +string(REGEX REPLACE "\n" ";" PHASAR_INSTALLED_FILES "${PHASAR_INSTALLED_FILES}") + +foreach (PHASAR_INSTALLED_FILE IN LISTS PHASAR_INSTALLED_FILES) + if ("${PHASAR_INSTALLED_FILE}" STREQUAL "") + continue() + endif() + + set(PHASAR_UNINSTALL_FILE "$ENV{DESTDIR}${PHASAR_INSTALLED_FILE}") + + if (EXISTS "${PHASAR_UNINSTALL_FILE}" OR IS_SYMLINK "${PHASAR_UNINSTALL_FILE}") + message(STATUS "Removing ${PHASAR_UNINSTALL_FILE}") + execute_process( + COMMAND "@CMAKE_COMMAND@" -E remove -f "${PHASAR_UNINSTALL_FILE}" + RESULT_VARIABLE PHASAR_REMOVE_RESULT + ) + + if (NOT "${PHASAR_REMOVE_RESULT}" STREQUAL "0") + message(FATAL_ERROR "Failed to remove ${PHASAR_UNINSTALL_FILE}") + endif() + else() + message(STATUS "File does not exist: ${PHASAR_UNINSTALL_FILE}") + endif() +endforeach() + +function(phasar_remove_empty_directories PHASAR_DIRECTORY) + if (NOT IS_DIRECTORY "${PHASAR_DIRECTORY}" OR IS_SYMLINK "${PHASAR_DIRECTORY}") + return() + endif() + + file(GLOB PHASAR_DIRECTORY_CHILDREN + LIST_DIRECTORIES true + "${PHASAR_DIRECTORY}/*" + "${PHASAR_DIRECTORY}/.[!.]*" + "${PHASAR_DIRECTORY}/..?*" + ) + + foreach (PHASAR_CHILD IN LISTS PHASAR_DIRECTORY_CHILDREN) + phasar_remove_empty_directories("${PHASAR_CHILD}") + endforeach() + + file(GLOB PHASAR_REMAINING_CHILDREN + LIST_DIRECTORIES true + "${PHASAR_DIRECTORY}/*" + "${PHASAR_DIRECTORY}/.[!.]*" + "${PHASAR_DIRECTORY}/..?*" + ) + + if (NOT PHASAR_REMAINING_CHILDREN) + message(STATUS "Removing empty directory ${PHASAR_DIRECTORY}") + file(REMOVE_RECURSE "${PHASAR_DIRECTORY}") + else() + message(STATUS "Not removing non-empty directory ${PHASAR_DIRECTORY}") + endif() +endfunction() + +set(PHASAR_OWNED_INSTALL_DIRECTORIES + "@CMAKE_INSTALL_FULL_INCLUDEDIR@/phasar" + "@CMAKE_INSTALL_FULL_LIBDIR@/cmake/phasar" + "@CMAKE_INSTALL_FULL_LIBDIR@/phasar" +) + +set(PHASAR_CONFIG_INSTALL_DIR "@PHASAR_CONFIG_INSTALL_DIR@") +if (IS_ABSOLUTE "${PHASAR_CONFIG_INSTALL_DIR}") + list(APPEND PHASAR_OWNED_INSTALL_DIRECTORIES "${PHASAR_CONFIG_INSTALL_DIR}") +else() + list(APPEND PHASAR_OWNED_INSTALL_DIRECTORIES "@CMAKE_INSTALL_PREFIX@/${PHASAR_CONFIG_INSTALL_DIR}") +endif() + +foreach (PHASAR_OWNED_INSTALL_DIRECTORY IN LISTS PHASAR_OWNED_INSTALL_DIRECTORIES) + phasar_remove_empty_directories("$ENV{DESTDIR}${PHASAR_OWNED_INSTALL_DIRECTORY}") +endforeach() diff --git a/cmake/phasar_macros.cmake b/cmake/phasar_macros.cmake index 961d57697d..cae0ed9691 100644 --- a/cmake/phasar_macros.cmake +++ b/cmake/phasar_macros.cmake @@ -70,9 +70,7 @@ function(generate_ll_file) set(user_compiled_llvm "/usr/local/llvm-${PHASAR_LLVM_VERSION}/bin") set(package_manager_llvm "/usr/lib/llvm-${PHASAR_LLVM_VERSION}/bin/") foreach(hint ${default_llvm} ${fallback_llvm} ${user_compiled_llvm} ${package_manager_llvm}) - if ("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.20") - cmake_path(NORMAL_PATH hint OUTPUT_VARIABLE hint) - endif() + cmake_path(NORMAL_PATH hint OUTPUT_VARIABLE hint) list(APPEND binary_hint_paths "${hint}") endforeach() message(STATUS "HINTS to find clang/clang++/opt: ${binary_hint_paths}") @@ -277,10 +275,21 @@ function(add_phasar_library name) add_library(phasar::${component_name} ALIAS ${name}) set_target_properties(${name} PROPERTIES EXPORT_NAME ${component_name} + VERSION ${PHASAR_VERSION} + SOVERSION ${PHASAR_VERSION} ) target_compile_features(${name} PUBLIC cxx_std_20) + if (BUILD_SHARED_LIBS AND UNIX) + target_link_options(${name} INTERFACE "LINKER:-rpath,\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}:\$ORIGIN/../${PHASAR_DEPS_INSTALL_DESTINATION}/lib") + + set_target_properties(${name} PROPERTIES + INSTALL_RPATH + "\$ORIGIN/../${CMAKE_INSTALL_LIBDIR};\$ORIGIN/../${PHASAR_DEPS_INSTALL_DESTINATION}/lib" + ) + endif() + set(install_module) if(PHASAR_LIB_MODULE_FILES) if(PHASAR_BUILD_MODULES) diff --git a/config.h.in b/config.h.in index 926d69b0bf..b8d5423b9a 100644 --- a/config.h.in +++ b/config.h.in @@ -7,6 +7,7 @@ #cmakedefine PAMM_CORE #cmakedefine PAMM_FULL +#cmakedefine PHASAR_THREAD_SAFE_PAMM #cmakedefine DYNAMIC_LOG #cmakedefine BUILD_PHASAR_CLANG diff --git a/config/double-free-config.json b/config/double-free-config.json index 043b392288..e5595cc093 100644 --- a/config/double-free-config.json +++ b/config/double-free-config.json @@ -23,6 +23,17 @@ 0 ] } + }, + { + "name": "_ZdaPv", + "params": { + "source": [ + 0 + ], + "sink": [ + 0 + ] + } } ] } diff --git a/docs/README.dox b/docs/README.dox index 6ae3b786c0..eb2643421b 100644 --- a/docs/README.dox +++ b/docs/README.dox @@ -16,7 +16,7 @@ PhASAR is primarily developed and maintained by the [Secure Software Engineering PhASAR was initially developed by Philipp Dominik Schubert (@pdschubert)(). \b Currently, PhASAR is maintained by -- Fabian Schiebel (@fabianbs96)(fabian.schiebel@iem.fraunhofer.de) +- Fabian Schiebel (@fabianbs96)(fabian.schiebel@uni-paderborn.de) - Sriteja Kummita (@sritejakv) - Lucas Briese (@jusito) - Martin Mory (@MMory)(martin.mory@upb.de) diff --git a/examples/how-to/00-load-llvm-ir/CMakeLists.txt b/examples/how-to/00-load-llvm-ir/CMakeLists.txt index 9d572feff4..9da3739f9d 100644 --- a/examples/how-to/00-load-llvm-ir/CMakeLists.txt +++ b/examples/how-to/00-load-llvm-ir/CMakeLists.txt @@ -11,8 +11,8 @@ target_link_libraries(load-llvm-ir PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_load_llvm_ir - DEPENDS load-llvm-ir - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/simple.ll" + DEPENDS load-llvm-ir LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/simple_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_load_llvm_ir) diff --git a/examples/how-to/01-build-type-hierarchy/CMakeLists.txt b/examples/how-to/01-build-type-hierarchy/CMakeLists.txt index f6e0303099..d545f59128 100644 --- a/examples/how-to/01-build-type-hierarchy/CMakeLists.txt +++ b/examples/how-to/01-build-type-hierarchy/CMakeLists.txt @@ -11,8 +11,8 @@ target_link_libraries(build-type-hierarchy PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_build_type_hierarchy - DEPENDS build-type-hierarchy - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/class_hierarchy.ll" + DEPENDS build-type-hierarchy LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/class_hierarchy_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_build_type_hierarchy) diff --git a/examples/how-to/02-build-call-graph/CMakeLists.txt b/examples/how-to/02-build-call-graph/CMakeLists.txt index 56d5fc25d8..b0b8c67289 100644 --- a/examples/how-to/02-build-call-graph/CMakeLists.txt +++ b/examples/how-to/02-build-call-graph/CMakeLists.txt @@ -14,9 +14,9 @@ target_link_libraries(build-llvm-based-call-graph PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_build_call_graph - DEPENDS build-llvm-based-icfg build-llvm-based-call-graph - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/class_hierarchy.ll" - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/class_hierarchy.ll" + DEPENDS build-llvm-based-icfg build-llvm-based-call-graph LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/class_hierarchy_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/class_hierarchy_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_build_call_graph) diff --git a/examples/how-to/03-create-alias-info/CMakeLists.txt b/examples/how-to/03-create-alias-info/CMakeLists.txt index d47aa2d3a2..634de638dd 100644 --- a/examples/how-to/03-create-alias-info/CMakeLists.txt +++ b/examples/how-to/03-create-alias-info/CMakeLists.txt @@ -9,10 +9,14 @@ find_package(phasar REQUIRED CONFIG) add_executable(create-alias-info main.cpp) target_link_libraries(create-alias-info PRIVATE phasar::phasar) +add_executable(create-alias-info-andersen andersen.cpp) +target_link_libraries(create-alias-info-andersen PRIVATE phasar::phasar) + if (TARGET run_sample_programs) add_custom_target(run_create_alias_info - DEPENDS create-alias-info - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/pointers.ll" + DEPENDS create-alias-info create-alias-info-andersen LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/pointers_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/pointers_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_create_alias_info) diff --git a/examples/how-to/03-create-alias-info/andersen.cpp b/examples/how-to/03-create-alias-info/andersen.cpp new file mode 100644 index 0000000000..266c9b4047 --- /dev/null +++ b/examples/how-to/03-create-alias-info/andersen.cpp @@ -0,0 +1,62 @@ +#include "phasar/PhasarLLVM/ControlFlow.h" +#include "phasar/PhasarLLVM/DB.h" +#include "phasar/PhasarLLVM/Pointer.h" +#include "phasar/PhasarLLVM/Utils.h" + +#include "llvm/IR/InstIterator.h" + +#include + +int main(int Argc, char *Argv[]) { + using namespace std::string_literals; + if (Argc < 2) { + llvm::errs() << "USAGE: create-alias-info-andersen \n"; + return 1; + } + + // Load the IR + psr::LLVMProjectIRDB IRDB(Argv[1]); + if (!IRDB) { + return 1; + } + + // Mapping the entry-points (here, just the main function) to LLVM IR: + auto Entrypoints = psr::getEntryFunctions(IRDB, {"main"s}); + + // Computing the Andersen-stale alias information. + auto Aliases = psr::computeAndersenOTF(IRDB, Entrypoints); + + // The Andersen alias result is compatible with the LLVMAliasIteratorRef + // interface. + psr::LLVMAliasIteratorRef AIt = &Aliases; + + const auto *MainF = IRDB.getFunctionDefinition("main"); + if (!MainF) { + llvm::errs() << "Required function 'main' not found\n"; + return 1; + } + + // Manually printing the alias sets: + + for (const auto &Inst : llvm::instructions(MainF)) { + if (!Inst.getType()->isPointerTy()) { + // For aliasing, we only care about pointers... + continue; + } + + llvm::outs() << "For pointer " << psr::llvmIRToString(&Inst) << ":\n"; + + // Iterate over the aliases of the result of the instruction Inst (first + // parameter) at the program location determined by Inst (second parameter). + // + // Implementations may ignore the second parameter. + Aliases.forallAliasesOf(&Inst, &Inst, [&](const llvm::Value *Alias) { + llvm::outs() << "> aliasing " << psr::llvmIRToShortString(Alias) << '\n'; + + // You can also check, whether two pointers are (potentially) aliasing: + assert(Aliases.mayAlias(&Inst, Alias, &Inst)); + }); + + llvm::outs() << '\n'; + } +} diff --git a/examples/how-to/03-create-alias-info/main.cpp b/examples/how-to/03-create-alias-info/main.cpp index b45e969418..a40f10a77b 100644 --- a/examples/how-to/03-create-alias-info/main.cpp +++ b/examples/how-to/03-create-alias-info/main.cpp @@ -30,6 +30,13 @@ int main(int Argc, char *Argv[]) { // it. psr::LLVMAliasInfoRef ASRef = &AS; + // For APIs that don't need the expressiveness of the LLVMAliasInfoRef, PhASAR + // provides a simpler interface: LLVMAliasIteratorRef. It only provides a + // function forallAliasesOf() that allows invoking a callback for all aliases + // of a pointer; for most applications, this is sufficient. + // Similar to LLVMAliasInfoRef, it is a non-owning reference. + psr::LLVMAliasIteratorRef AIt = &AS; + // You can print and load alias information from/to JSON: AS.printAsJson(); diff --git a/examples/how-to/04-run-ifds-analysis/CMakeLists.txt b/examples/how-to/04-run-ifds-analysis/CMakeLists.txt index 6b8c99365b..3e15f364a7 100644 --- a/examples/how-to/04-run-ifds-analysis/CMakeLists.txt +++ b/examples/how-to/04-run-ifds-analysis/CMakeLists.txt @@ -20,11 +20,11 @@ target_link_libraries(run-ifds-analysis-ifds-solver PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_run_ifds_analysis - DEPENDS run-ifds-analysis-simple run-ifds-analysis-helper-analyses run-ifds-analysis-otf-reporter run-ifds-analysis-ifds-solver - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/taint.ll" - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/taint.ll" - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/taint.ll" - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/taint.ll" + DEPENDS run-ifds-analysis-simple run-ifds-analysis-helper-analyses run-ifds-analysis-otf-reporter run-ifds-analysis-ifds-solver LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_run_ifds_analysis) diff --git a/examples/how-to/05-run-ide-analysis/CMakeLists.txt b/examples/how-to/05-run-ide-analysis/CMakeLists.txt index cc71bd81a8..f164b25ac6 100644 --- a/examples/how-to/05-run-ide-analysis/CMakeLists.txt +++ b/examples/how-to/05-run-ide-analysis/CMakeLists.txt @@ -17,10 +17,10 @@ target_link_libraries(run-ide-analysis-ide-solver PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_run_ide_analysis - DEPENDS run-ide-analysis-simple run-ide-analysis-helper-analyses run-ide-analysis-ide-solver - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/call2.ll" - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/call2.ll" - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/call2.ll" + DEPENDS run-ide-analysis-simple run-ide-analysis-helper-analyses run-ide-analysis-ide-solver LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/call2_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/call2_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/call2_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_run_ide_analysis) diff --git a/examples/how-to/06-run-monoifds-analysis/CMakeLists.txt b/examples/how-to/06-run-monoifds-analysis/CMakeLists.txt new file mode 100644 index 0000000000..87651cc8b3 --- /dev/null +++ b/examples/how-to/06-run-monoifds-analysis/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.14...3.28) + +project(run-monoifds-analysis) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +find_package(phasar REQUIRED CONFIG) + + +add_executable(run-monoifds-analysis-helper-analyses helper-analyses.cpp) +target_link_libraries(run-monoifds-analysis-helper-analyses PRIVATE phasar::phasar) + +add_executable(run-monoifds-analysis-manual manual.cpp) +target_link_libraries(run-monoifds-analysis-manual PRIVATE phasar::phasar) + + +if (TARGET run_sample_programs) + add_custom_target(run_run_monoifds_analysis + DEPENDS run-monoifds-analysis-helper-analyses run-monoifds-analysis-manual LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" + ) + + add_dependencies(run_sample_programs run_run_monoifds_analysis) +endif() diff --git a/examples/how-to/06-run-monoifds-analysis/README.md b/examples/how-to/06-run-monoifds-analysis/README.md new file mode 100644 index 0000000000..1fb5ec1a32 --- /dev/null +++ b/examples/how-to/06-run-monoifds-analysis/README.md @@ -0,0 +1,30 @@ +# Run a MonoIFDS Analysis + +Shows some way, how you can use PhASAR to run an already existing MonoIFDS analysis on a LLVM IR module. +For this example, we selected the `MonoIFDSTaintAnalysis`. + +You may look at the different C++ source files to see, how you can run an MonoIFDS taint analysis using PhASAR. +We suggest to start with the simplest example [helper-analyses.cpp](./helper-analyses.cpp). + +## Build + +This example program can be built using cmake. +It assumes, that you have installed PhASAR on your system. If you did not install PhASAR to a default location, you can specify `-Dphasar_ROOT=your/path/to/phasar` when invoking `cmake`, replacing "your/path/to/phasar" by the actual path where you have installed PhASAR. + +```bash +# Invoked from the 06-run-monoifds-analysis root folder: +$ mkdir -p build && cd build +$ cmake .. +$ cmake --build . +``` + +## Test + +You can test the example program on the target programs from [llvm-hello-world/target](../../llvm-hello-world/target/). + +```bash +# Invoked from the 06-run-monoifds-analysis/build folder: +./run-monoifds-analysis-helper-analyses ../../../llvm-hello-world/target/taint.ll + +./run-monoifds-analysis-manual ../../../llvm-hello-world/target/taint.ll +``` diff --git a/examples/how-to/06-run-monoifds-analysis/helper-analyses.cpp b/examples/how-to/06-run-monoifds-analysis/helper-analyses.cpp new file mode 100644 index 0000000000..2449037199 --- /dev/null +++ b/examples/how-to/06-run-monoifds-analysis/helper-analyses.cpp @@ -0,0 +1,51 @@ +#include "phasar/DataFlow.h" // For MonoIFDSSolver +#include "phasar/PhasarLLVM.h" // For the HelperAnalyses +#include "phasar/PhasarLLVM/DataFlow.h" // For the MonoIFDSTaintAnalysis +#include "phasar/PhasarLLVM/Pointer/FilteredLLVMAliasIterator.h" +#include "phasar/PhasarLLVM/TaintConfig.h" // For the LLVMTaintConfig + +int main(int Argc, char *Argv[]) { + if (Argc < 2) { + llvm::errs() + << "USAGE: run-monoifds-analysis-helper-analyses \n"; + return 1; + } + + using namespace std::string_literals; + std::vector EntryPoints = {"main"s}; + + // Instead of creating all the helper analyses ourselves, we can just use the + // HelperAnalyses class. It will create the necessary information on-demand. + // + // You can customize the underlying algorithms by passing a + // HelperAnalysisConfig as third parameter + psr::HelperAnalyses HA(Argv[1], EntryPoints); + if (!HA.getProjectIRDB()) { + return 1; + } + + // Create the taint configuration + psr::LLVMTaintConfig TC(HA.getProjectIRDB()); + TC.print(); + llvm::outs() << "------------------------\n"; + + // More precise alias-information; techically, this is not required, but it + // helps a lot + psr::FilteredLLVMAliasIterator FAI(HA.getAliasInfo()); + + // Create the taint analysis problem: + psr::monoifds::TaintAnalysis TaintProblem(&TC, &HA.getUsedGlobals(), &FAI); + + // To solve the taint problem, we now create an instance of the + // MonoIFDSSolver. Passing the HelperAnalyses here, lets the solver + // automatically grab the needed information + psr::monoifds::MonoIFDSSolver Solver(&TaintProblem, HA); + + // Solves the taint problem. This may take some time. + Solver.solve(); + + // The monoifds::TaintAnalysis is set-up to use the analysis-printer (see + // ../04-run-ifds-analysis/otf-reporter.cpp). By default, it prints the + // detected leaks into the given llvm::raw_ostream + TaintProblem.emitTextReport(llvm::outs()); +} diff --git a/examples/how-to/06-run-monoifds-analysis/manual.cpp b/examples/how-to/06-run-monoifds-analysis/manual.cpp new file mode 100644 index 0000000000..e6986f7053 --- /dev/null +++ b/examples/how-to/06-run-monoifds-analysis/manual.cpp @@ -0,0 +1,76 @@ +#include "phasar/DataFlow.h" // For MonoIFDSSolver +#include "phasar/PhasarLLVM/ControlFlow.h" // For FunctionCompressor & getEntryFunctions +#include "phasar/PhasarLLVM/DataFlow.h" // For the MonoIFDSTaintAnalysis +#include "phasar/PhasarLLVM/Pointer.h" // For the LLVMAliasSet +#include "phasar/PhasarLLVM/TaintConfig.h" // For the LLVMTaintConfig +#include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" +#include "phasar/PhasarLLVM/Utils/UsedGlobals.h" + +int main(int Argc, char *Argv[]) { + if (Argc < 2) { + llvm::errs() << "USAGE: run-monoifds-analysis-manual \n"; + return 1; + } + + using namespace std::string_literals; + std::vector EntryPoints = {"main"s}; + + // Load the IR + auto IRDB = psr::LLVMProjectIRDB::loadOrExit(Argv[1]); + + // The MonoIFDSTaintAnalysis requires alias information, so create it here + psr::LLVMAliasSet AS(&IRDB); + + // We use a type-hierarchy to build the call-graph (LLVMBasedICFG below) + psr::DIBasedTypeHierarchy TH(IRDB); + + // Create the ICFG + psr::LLVMBasedICFG ICFG(&IRDB, psr::CallGraphAnalysisType::VTA, {"main"}, &TH, + &AS); + + // Assign each reachable llvm::Function in the call-graph a sequential ID. + // This is needed for SCC computation and for the solver + auto Funs = psr::compressFunctions(ICFG.getCallGraph(), + psr::getEntryFunctions(IRDB, EntryPoints)); + + // Compute the call-graph SCCs. + auto CGSCCs = computeCGSCCs(ICFG, Funs); + + // Build a dependency-graph induced by the call-graph, collapsing each SCC to + // a single node + auto SCCC = computeCGSCCCallers(ICFG, Funs, CGSCCs); + + // For each CGSCC, compute which global variables are (transitively) used by + // any function in that SCC + auto UG = psr::computeUsedGlobals(IRDB, Funs, CGSCCs, SCCC); + + // Create the taint configuration + psr::LLVMTaintConfig TC(IRDB); + TC.print(); + llvm::outs() << "------------------------\n"; + + // More precise alias-information; techically, this is not required, but it + // helps a lot + psr::FilteredLLVMAliasIterator FAI(&AS); + + // Create the taint analysis problem: + psr::monoifds::TaintAnalysis TaintProblem(&TC, &UG, &FAI); + + // To solve the taint problem, we now create an instance of the + // MonoIFDSSolver. Passing the HelperAnalyses here, lets the solver + // automatically grab the needed information + psr::monoifds::MonoIFDSSolver Solver(&TaintProblem, &ICFG); + + // Supply the solver with the previously computed helper information. If we + // don't provide this, the solver would compute them on its own once solve() + // is called. + Solver.setCGSCCs(&CGSCCs).setFunctionCompressor(&Funs); + + // Solves the taint problem. This may take some time. + Solver.solve(); + + // The monoifds::TaintAnalysis is set-up to use the analysis-printer (see + // ../04-run-ifds-analysis/otf-reporter.cpp). By default, it prints the + // detected leaks into the given llvm::raw_ostream + TaintProblem.emitTextReport(llvm::outs()); +} diff --git a/examples/how-to/07-write-ifds-analysis/CMakeLists.txt b/examples/how-to/07-write-ifds-analysis/CMakeLists.txt index fe1839e072..73d12d8bc3 100644 --- a/examples/how-to/07-write-ifds-analysis/CMakeLists.txt +++ b/examples/how-to/07-write-ifds-analysis/CMakeLists.txt @@ -12,8 +12,8 @@ target_link_libraries(write-ifds-analysis-simple PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_write_ifds_analysis - DEPENDS write-ifds-analysis-simple - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/taint.ll" + DEPENDS write-ifds-analysis-simple LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_write_ifds_analysis) diff --git a/examples/how-to/07-write-ifds-analysis/simple.cpp b/examples/how-to/07-write-ifds-analysis/simple.cpp index 34ea367b47..b6b07a1b4a 100644 --- a/examples/how-to/07-write-ifds-analysis/simple.cpp +++ b/examples/how-to/07-write-ifds-analysis/simple.cpp @@ -34,13 +34,14 @@ class ExampleTaintAnalysis : public psr::DefaultAliasAwareIFDSProblem { /// Provides the initial seeds, i.e., the pairs that are assumed /// to hold un-conditionally at the beginning of the analysis. /// This is the start state that the IFDS solver will use to start with. - [[nodiscard]] psr::InitialSeeds initialSeeds() override { + [[nodiscard]] psr::InitialSeeds initialSeeds() { psr::InitialSeeds Seeds; psr::LLVMBasedCFG CFG; // Here, we just say that for all entry-functions in the EntryPoints, the // zero-value should hold at the very first statement. - addSeedsForStartingPoints(EntryPoints, IRDB, CFG, Seeds, getZeroValue()); + addSeedsForStartingPoints(EntryPoints, getProjectIRDB(), CFG, Seeds, + getZeroValue()); return Seeds; }; @@ -48,8 +49,8 @@ class ExampleTaintAnalysis : public psr::DefaultAliasAwareIFDSProblem { /// Here, we define special semantics of function-calls that are specified /// outside of the target program. In the case of taint analysis, we need to /// handle sources, sinks and sanitizers here: - [[nodiscard]] FlowFunctionPtrType - getSummaryFlowFunction(n_t CallSite, f_t DestFun) override { + [[nodiscard]] FlowFunctionPtrType getSummaryFlowFunction(n_t CallSite, + f_t DestFun) { const auto *CS = llvm::cast(CallSite); // Process the effects of source or sink functions that are called @@ -60,7 +61,7 @@ class ExampleTaintAnalysis : public psr::DefaultAliasAwareIFDSProblem { if (Gen.empty() && Leak.empty() && Kill.empty()) { // This CallSite apparently is not calling a special source/sink/sanitizer // function. Fallback to the default-behavior. - return DefaultAliasAwareIFDSProblem::getSummaryFlowFunction(CS, DestFun); + return nullptr; } // Since our analysis is alias-aware, we must handle aliasing here: @@ -76,13 +77,13 @@ class ExampleTaintAnalysis : public psr::DefaultAliasAwareIFDSProblem { return Gen; } - if (Leak.count(Source)) { + if (Leak.contains(Source)) { // In case of a sink, we create a leak if one of the leaking parameters // (Leak) is tainted (Source). Leaks.insert(CS); } - if (Kill.count(Source)) { + if (Kill.contains(Source)) { // In case of a sanitizer, we kill tainted values (Source) that flow // into the sanitizied parameters (Kill). return {}; diff --git a/examples/how-to/08-write-ide-analysis/CMakeLists.txt b/examples/how-to/08-write-ide-analysis/CMakeLists.txt index 25d4088d49..2cc2ec736e 100644 --- a/examples/how-to/08-write-ide-analysis/CMakeLists.txt +++ b/examples/how-to/08-write-ide-analysis/CMakeLists.txt @@ -12,8 +12,8 @@ target_link_libraries(write-ide-analysis-simple PRIVATE phasar::phasar) if (TARGET run_sample_programs) add_custom_target(run_write_ide_analysis - DEPENDS write-ide-analysis-simple - COMMAND $ "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/taint.ll" + DEPENDS write-ide-analysis-simple LLFileGeneration + COMMAND $ "${CMAKE_CURRENT_BINARY_DIR}/../llvm-hello-world/target/taint_cpp_dbg.ll" ) add_dependencies(run_sample_programs run_write_ide_analysis) diff --git a/examples/how-to/08-write-ide-analysis/simple.cpp b/examples/how-to/08-write-ide-analysis/simple.cpp index 45443ced08..d8d5ce7444 100644 --- a/examples/how-to/08-write-ide-analysis/simple.cpp +++ b/examples/how-to/08-write-ide-analysis/simple.cpp @@ -54,19 +54,19 @@ class ExampleLinearConstantAnalysis /// to hold un-conditionally at the beginning of the analysis. /// Similar to IFDS, this is the start state that the IDE solver will use to /// start with. - [[nodiscard]] psr::InitialSeeds initialSeeds() override { + [[nodiscard]] psr::InitialSeeds initialSeeds() { psr::InitialSeeds Seeds; psr::LLVMBasedCFG CFG; // Here, we just say that for all entry-functions in the EntryPoints, the // zero-value should hold at the very first statement. - addSeedsForStartingPoints(EntryPoints, IRDB, CFG, Seeds, getZeroValue(), - bottomElement()); + addSeedsForStartingPoints(EntryPoints, getProjectIRDB(), CFG, Seeds, + getZeroValue(), bottomElement()); return Seeds; }; - FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) override { + FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) { if (const auto *Alloca = llvm::dyn_cast(Curr)) { // Freshly allocated variables hold no constant value @@ -88,7 +88,7 @@ class ExampleLinearConstantAnalysis return this->DefaultNoAliasIDEProblem::getNormalFlowFunction(Curr, Succ); } - FlowFunctionPtrType getCallFlowFunction(n_t CallSite, f_t DestFun) override { + FlowFunctionPtrType getCallFlowFunction(n_t CallSite, f_t DestFun) { // We definitely want to re-use as much as possible from the default // call-flow-function auto DefaultFn = @@ -118,7 +118,7 @@ class ExampleLinearConstantAnalysis } FlowFunctionPtrType getRetFlowFunction(n_t CallSite, f_t CalleeFun, - n_t ExitInst, n_t RetSite) override { + n_t ExitInst, n_t RetSite) { auto DefaultFn = this->DefaultNoAliasIDEProblem::getRetFlowFunction( CallSite, CalleeFun, ExitInst, RetSite); @@ -352,8 +352,7 @@ class ExampleLinearConstantAnalysis }; psr::EdgeFunction getNormalEdgeFunction(n_t Curr, d_t CurrNode, - n_t /*Succ*/, - d_t SuccNode) override { + n_t /*Succ*/, d_t SuccNode) { if (isZeroValue(CurrNode) && !isZeroValue(SuccNode)) { // Handle the two cases, where we generate facts from zero: @@ -384,8 +383,11 @@ class ExampleLinearConstantAnalysis } // Attach the arithmetic transformer to this edge - return BinOp{Op, llvm::dyn_cast(Lop), - llvm::dyn_cast(Rop)}; + return BinOp{ + .OpCode = Op, + .LeftConst = llvm::dyn_cast(Lop), + .RightConst = llvm::dyn_cast(Rop), + }; } // Pass everything else as identity @@ -394,7 +396,7 @@ class ExampleLinearConstantAnalysis psr::EdgeFunction getCallEdgeFunction(n_t CallSite, d_t SrcNode, f_t /*DestinationFunction*/, - d_t DestNode) override { + d_t DestNode) { if (isZeroValue(SrcNode) && !isZeroValue(DestNode)) { // If a constant int is passed as parameter, we need to generate the // parameter inside the callee from zero @@ -408,9 +410,10 @@ class ExampleLinearConstantAnalysis return psr::EdgeIdentity{}; } - psr::EdgeFunction - getReturnEdgeFunction(n_t CallSite, f_t /*CalleeFunction*/, n_t ExitStmt, - d_t ExitNode, n_t /*RetSite*/, d_t RetNode) override { + psr::EdgeFunction getReturnEdgeFunction(n_t CallSite, + f_t /*CalleeFunction*/, + n_t ExitStmt, d_t ExitNode, + n_t /*RetSite*/, d_t RetNode) { if (isZeroValue(ExitNode) && RetNode == CallSite) { // If we return a literal constant int, we must generate the corresponding // value at the call-site from zero, i.e., the CallSite itself in case of @@ -428,7 +431,7 @@ class ExampleLinearConstantAnalysis psr::EdgeFunction getCallToRetEdgeFunction(n_t /*CallSite*/, d_t /*CallNode*/, n_t /*RetSite*/, d_t /*RetSiteNode*/, - llvm::ArrayRef /*Callees*/) override { + llvm::ArrayRef /*Callees*/) { // The call-to-return edge-function handles facts that are not affected by // the call. This is usually the identity function. return psr::EdgeIdentity{}; diff --git a/examples/how-to/09-export-callsite-cg/CMakeLists.txt b/examples/how-to/09-export-callsite-cg/CMakeLists.txt new file mode 100644 index 0000000000..2a55249f35 --- /dev/null +++ b/examples/how-to/09-export-callsite-cg/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.14...3.28) + +project(export-callsite-cg) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +find_package(phasar REQUIRED CONFIG) + +add_executable(export-callsite-cg-streaming export_callsite_cg_streaming.cpp) +target_link_libraries(export-callsite-cg-streaming PRIVATE phasar::phasar) diff --git a/examples/how-to/09-export-callsite-cg/README.md b/examples/how-to/09-export-callsite-cg/README.md new file mode 100644 index 0000000000..1eafec8047 --- /dev/null +++ b/examples/how-to/09-export-callsite-cg/README.md @@ -0,0 +1,40 @@ +# Export a Callsite-Level Call Graph + +Exports PhASAR's call graph as callsite-resolved, source-located CSV: one row per +resolved edge, with caller/callee function name plus file/line/column for each +side, written incrementally as edges are discovered. + +This differs from `CallGraph::printAsDot()` / `printAsJson()` in two ways: + +- **Callsite-level.** Each row is a single call instruction resolved to its callee(s), rather than an aggregated function-to-function edge. + This is what, e.g., correlating a static call graph against runtime instrumentation data needs. + The runtime side reports individual call sites, not just "A calls B somewhere". +- **Streaming output.** `exportICFGAsJson()` and `printAsJson()` build one in-memory `nlohmann::json` object holding every edge before writing anything to disk. + On a large enough input (validated against a ~1.2M-edge FFmpeg call graph) this easily goes out-of-memory. + This driver writes each edge to disk immediately and discards it, so memory stays roughly constant regardless of total edge count. + +It also documents a (current) source-location bug it works around: PhASAR's `getSrcCodeInfoFromIR` resolves `File` and `Line` through separate helper calls, +each with its own fallback logic for instructions lacking direct `!dbg` metadata, which can pair a `File` from one resolution path with a `Line` from an unrelated one. + +## Build + +```bash +# Invoked from the 09-export-callsite-cg root folder: +$ mkdir -p build && cd build +$ cmake .. +$ cmake --build . +``` + +## Usage + +```bash +./export-callsite-cg-streaming [cha|rta|vta|otf] +``` + +Output is CSV with one row per resolved call edge: + +``` +caller_function,caller_file,caller_line,caller_column,callee_function,callee_file,callee_line,callee_column,caller_loc_approximate +``` + +`caller_loc_approximate` is `true` when the call instruction had no direct debug location and the row fell back to the enclosing function's declaration site instead of the real call site. diff --git a/examples/how-to/09-export-callsite-cg/export_callsite_cg_streaming.cpp b/examples/how-to/09-export-callsite-cg/export_callsite_cg_streaming.cpp new file mode 100644 index 0000000000..c51d6659c1 --- /dev/null +++ b/examples/how-to/09-export-callsite-cg/export_callsite_cg_streaming.cpp @@ -0,0 +1,152 @@ +#include "phasar/ControlFlow/CallGraphAnalysisType.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/HelperAnalyses.h" +#include "phasar/PhasarLLVM/HelperAnalysisConfig.h" +#include "phasar/PhasarLLVM/Utils/LLVMIRToSrc.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/IR/DebugInfoMetadata.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Instructions.h" +#include "llvm/Support/WithColor.h" +#include "llvm/Support/raw_ostream.h" + +#include + +namespace { +struct ResolvedLoc { + uint32_t Line{}; + uint32_t Column{}; + llvm::StringRef FileName{}; + bool Approximate{}; // true = fell back to enclosing function's declaration + // site, not the real call site + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const ResolvedLoc &Loc) { + OS.write_escaped(Loc.FileName) << ','; + return OS << Loc.Line << ',' << Loc.Column; + } +}; + +constexpr llvm::StringLiteral NoDebugInfo = ""; + +ResolvedLoc resolveLocation(const llvm::Instruction *I) { + if (auto Loc = psr::getDebugLocation(I)) { + return { + .Line = Loc->Line, + .Column = Loc->Column, + .FileName = Loc->File->getFilename(), + .Approximate = false, + }; + } + + // No direct location on this instruction -- fall back to the + // enclosing function's declared file + if (const auto *SP = I->getFunction()->getSubprogram()) { + return { + .Line = SP->getLine(), + .Column = 0, + .FileName = SP->getFile()->getFilename(), + .Approximate = true, + }; + } + return {.FileName = NoDebugInfo, .Approximate = true}; +} + +ResolvedLoc resolveLocation(const llvm::Function *F) { + if (const auto *SP = F->getSubprogram()) { + return { + .Line = SP->getLine(), + .Column = 0, + .FileName = SP->getFile()->getFilename(), + .Approximate = false, + }; + } + return {.FileName = NoDebugInfo, .Approximate = true}; +} +} // namespace + +int main(int Argc, char **Argv) { + if (Argc < 5) { + llvm::errs() << "USAGE: " << Argv[0] + << " [cha|rta|vta|otf] \n"; + return 1; + } + + llvm::StringRef IRFile = Argv[1]; + std::vector EntryPoints = {Argv[2]}; + auto CGTy = psr::toCallGraphAnalysisType(Argv[3]); + llvm::StringRef OutFile = Argv[4]; + + if (CGTy == psr::CallGraphAnalysisType::Invalid) { + llvm::WithColor::error() << "Invalid call-graph type '" << Argv[3] << "'\n"; + return 1; + } + + std::error_code EC; + llvm::raw_fd_ostream OS(OutFile, EC); + if (EC) { + llvm::WithColor::error() + << "While opening output file '" << OutFile << "':\n"; + llvm::WithColor::error() << EC.message() << '\n'; + return 1; + } + + // CSV header: + OS << "caller_function,caller_file,caller_line,caller_column," + "callee_function,callee_file,callee_line,callee_column," + "caller_loc_approximate\n"; + OS.flush(); + + // Basic phasar pipeline: + psr::HelperAnalyses HA{ + std::make_unique( + psr::LLVMProjectIRDB::loadOrExit(IRFile)), + std::move(EntryPoints), + psr::HelperAnalysisConfig{.CGTy = CGTy}, + }; + + auto &ICF = HA.getICFG(); + + size_t EdgeCount = 0; + size_t FnCount = 0; + + // Walk every function PhASAR's call-graph knows about, and ask it for all + // known callers, writing (and forgetting) each result immediately instead of + // accumulating all of them. + ICF.getCallGraph().iterateCallersView([&](const llvm::Function *Fun, + const auto &Callers) { + auto FunName = Fun->getName(); + ++FnCount; + + ResolvedLoc CalleeLoc = resolveLocation(Fun); + for (const auto *CS : Callers) { + ResolvedLoc CallerLoc = resolveLocation(CS); + + OS.write_escaped(CS->getFunction()->getName()) << ',' << CallerLoc << ','; + OS.write_escaped(FunName) << ',' << CalleeLoc << ','; + OS << (CallerLoc.Approximate ? "true" : "false") << '\n'; + + ++EdgeCount; + if (EdgeCount % 50000 == 0) { + OS.flush(); // periodic flush -- makes progress visible on + // disk instead of buffered invisibly + llvm::WithColor::note() + << "[export_callsite_cg_streaming] " << EdgeCount + << " edges written, " << FnCount << '/' + << ICF.getNumVertexFunctions() << " functions processed so far\n"; + } + } + }); + + OS.close(); + + llvm::WithColor::note() << "[export_callsite_cg_streaming] DONE: " + << EdgeCount << " total edges across " << FnCount + << " functions -> " + << (OutFile == "-" ? llvm::StringRef("stdout") + : OutFile) + << "\n"; + return 0; +} diff --git a/examples/how-to/CMakeLists.txt b/examples/how-to/CMakeLists.txt index 5a1ae5b094..134129ab49 100644 --- a/examples/how-to/CMakeLists.txt +++ b/examples/how-to/CMakeLists.txt @@ -11,3 +11,8 @@ foreach(child ${children}) add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/${child}) endif() endforeach() + +find_package(phasar REQUIRED CONFIG) + +message("PHASAR_LLVM_VERSION LLVM-${PHASAR_LLVM_VERSION}") +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../llvm-hello-world ${CMAKE_CURRENT_BINARY_DIR}/llvm-hello-world) diff --git a/examples/how-to/README.md b/examples/how-to/README.md index b84875a609..10c601fc45 100644 --- a/examples/how-to/README.md +++ b/examples/how-to/README.md @@ -11,3 +11,4 @@ Currently supporting: - [x] Run an IDE analysis ([here](./05-run-ide-analysis/README.md)) - [x] Write an IFDS analysis ([here](./07-write-ifds-analysis/README.md)) - [x] Write an IDE analysis ([here](./08-write-ide-analysis/)) +- [x] Export a callsite-level call graph ([here](./09-export-callsite-cg/README.md)) diff --git a/examples/llvm-hello-world/CMakeLists.txt b/examples/llvm-hello-world/CMakeLists.txt index 8ed9e80124..366009c806 100644 --- a/examples/llvm-hello-world/CMakeLists.txt +++ b/examples/llvm-hello-world/CMakeLists.txt @@ -4,7 +4,14 @@ project(llvm-hello-world) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -find_package(LLVM 15 REQUIRED CONFIG) +set(LLVM_HW_LLVM_VERSION 15) +if(PHASAR_LLVM_VERSION) + set(LLVM_HW_LLVM_VERSION ${PHASAR_LLVM_VERSION}) +endif() + +message(STATUS "llvm-hello-world: Use LLVM-${LLVM_HW_LLVM_VERSION}") + +find_package(LLVM ${LLVM_HW_LLVM_VERSION} REQUIRED CONFIG) add_executable(main main.cpp) target_link_libraries(main PRIVATE LLVMCore LLVMIRReader) diff --git a/examples/llvm-hello-world/target/CMakeLists.txt b/examples/llvm-hello-world/target/CMakeLists.txt index f8f25bef38..ef1be4701a 100644 --- a/examples/llvm-hello-world/target/CMakeLists.txt +++ b/examples/llvm-hello-world/target/CMakeLists.txt @@ -3,7 +3,9 @@ add_custom_target(LLFileGeneration ALL) # Use phasar's capabilities to automate the LLVM-IR file generation include(../../../cmake/phasar_macros.cmake) -set(PHASAR_LLVM_VERSION 15) +if (NOT PHASAR_LLVM_VERSION) + set(PHASAR_LLVM_VERSION 15) +endif() file(GLOB target_files RELATIVE ${CMAKE_CURRENT_LIST_DIR} *.cpp) diff --git a/examples/use-phasar-with-fetch-content/CMakeLists.txt b/examples/use-phasar-with-fetch-content/CMakeLists.txt index 026e29fcee..9195fc4fe5 100644 --- a/examples/use-phasar-with-fetch-content/CMakeLists.txt +++ b/examples/use-phasar-with-fetch-content/CMakeLists.txt @@ -11,7 +11,7 @@ include(FetchContent) FetchContent_Declare( phasar GIT_REPOSITORY https://github.com/secure-software-engineering/phasar.git - GIT_TAG development # At best, use a tagged version, such as v2604 + GIT_TAG development # At best, use a tagged version, such as v2610 EXCLUDE_FROM_ALL OVERRIDE_FIND_PACKAGE ) diff --git a/external/CRoaring b/external/CRoaring new file mode 160000 index 0000000000..2e8395f1db --- /dev/null +++ b/external/CRoaring @@ -0,0 +1 @@ +Subproject commit 2e8395f1dbf286d7944a7276195a7c40cbcbfd4a diff --git a/include/phasar/ControlFlow/CFG.h b/include/phasar/ControlFlow/CFG.h index 3200b0c17a..2efe0cb3b1 100644 --- a/include/phasar/ControlFlow/CFG.h +++ b/include/phasar/ControlFlow/CFG.h @@ -8,18 +8,21 @@ *****************************************************************************/ #pragma once +#include "phasar/Utils/Nullable.h" #include "phasar/Utils/TypeTraits.h" #include "llvm/Support/raw_ostream.h" #include +#include #include namespace psr { template concept InstructionClassifier = - requires(const T &IC, typename T::n_t Inst, typename T::n_t Succ) { + requires(const T &IC, typename std::remove_cvref_t::n_t Inst, + typename std::remove_cvref_t::n_t Succ) { { IC.isCallSite(Inst) } -> std::convertible_to; { IC.isFieldLoad(Inst) } -> std::convertible_to; { IC.isFieldStore(Inst) } -> std::convertible_to; @@ -28,27 +31,36 @@ concept InstructionClassifier = }; template -concept CFG = requires(const T &CF, typename T::n_t Inst, typename T::f_t Fun) { - typename T::n_t; - typename T::f_t; +concept CFG = requires(const T &CF, typename std::remove_cvref_t::n_t Inst, + typename std::remove_cvref_t::f_t Fun) { + typename std::remove_cvref_t::n_t; + typename std::remove_cvref_t::f_t; /// Returns the function that contains the given instruction Inst. // TODO: Actually belongs into ProjectIRDB! - { CF.getFunctionOf(Inst) } -> std::convertible_to; + { + CF.getFunctionOf(Inst) + } -> std::convertible_to::f_t>; /// Returns an iterable range of all instructions of the given function that /// are part of the control-flow graph. // TODO: We should have sth like this in the ProjectIRDB as well! - { CF.getAllInstructionsOf(Fun) } -> psr::is_iterable_over_v; + { + CF.getAllInstructionsOf(Fun) + } -> psr::is_iterable_over_v::n_t>; /// Returns an iterable range of all successor instructions of Inst in the /// CFG. /// NOTE: This function is typically being called in a hot part of the /// analysis and should therefore be highly optimized for performance. - { CF.getSuccsOf(Inst) } -> psr::is_iterable_over_v; + { + CF.getSuccsOf(Inst) + } -> psr::is_iterable_over_v::n_t>; /// Returns an iterable range of all starting instructions of the given /// function. For a forward-CFG, this is typically a singleton range. - { CF.getStartPointsOf(Fun) } -> psr::is_iterable_over_v; + { + CF.getStartPointsOf(Fun) + } -> psr::is_iterable_over_v::n_t>; /// Returns whether the given Inst is a root node of the CFG { CF.isStartPoint(Inst) } -> std::convertible_to; @@ -59,32 +71,54 @@ concept CFG = requires(const T &CF, typename T::n_t Inst, typename T::f_t Fun) { requires InstructionClassifier; }; +template +concept CFGOf = + CFG && std::same_as::n_t> && + std::same_as::f_t>; + template concept BidiCFG = - CFG && requires(const T &CF, typename T::n_t Inst, typename T::f_t Fun) { + CFG && requires(const T &CF, typename std::remove_cvref_t::n_t Inst, + typename std::remove_cvref_t::f_t Fun) { /// Returns an iterable range of all predecessor instructions of Inst in /// the CFG - { CF.getPredsOf(Inst) } -> psr::is_iterable_over_v; + { + CF.getPredsOf(Inst) + } -> psr::is_iterable_over_v::n_t>; /// Returns an iterable range of all exit instructions (often return /// instructions) of the given function. For a backward-CFG, this is /// typically a singleton range - { CF.getExitPointsOf(Fun) } -> psr::is_iterable_over_v; + { + CF.getExitPointsOf(Fun) + } -> psr::is_iterable_over_v::n_t>; }; template -concept CFGDump = requires(const T &CF, typename T::n_t Inst, - typename T::f_t Fun, llvm::raw_ostream &OS) { - { CF.getStatementId(Inst) } -> psr::is_string_like_v; - { CF.getFunctionName(Fun) } -> psr::is_string_like_v; - { CF.getDemangledFunctionName(Fun) } -> psr::is_string_like_v; - CF.print(Fun, OS); -}; +concept CFGDump = + requires(const T &CF, typename std::remove_cvref_t::n_t Inst, + typename std::remove_cvref_t::f_t Fun, llvm::raw_ostream &OS) { + { CF.getStatementId(Inst) } -> psr::is_string_like_v; + { CF.getFunctionName(Fun) } -> psr::is_string_like_v; + { CF.getDemangledFunctionName(Fun) } -> psr::is_string_like_v; + CF.print(Fun, OS); + }; template -concept CFGEdgesProvider = requires(const T &CF, typename T::f_t Fun) { +concept CFGEdgesProvider = requires(const T &CF, + typename std::remove_cvref_t::f_t Fun) { { CF.getAllControlFlowEdges(Fun) - } -> psr::is_iterable_over_v>; + } -> psr::is_iterable_over_v::n_t, + typename std::remove_cvref_t::n_t>>; }; + +template +concept IsBlockAwareControlFlow = + requires(const T &CF, typename std::remove_cvref_t::n_t Inst) { + { + CF.getUniqueSuccessor(Inst) + } -> std::convertible_to::n_t>>; + { CF.hasUniquePredecessor(Inst) } -> std::convertible_to; + }; } // namespace psr diff --git a/include/phasar/ControlFlow/CGSCCs.h b/include/phasar/ControlFlow/CGSCCs.h new file mode 100644 index 0000000000..5afe66dc53 --- /dev/null +++ b/include/phasar/ControlFlow/CGSCCs.h @@ -0,0 +1,164 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/ControlFlow/CFG.h" +#include "phasar/ControlFlow/CallGraph.h" +#include "phasar/ControlFlow/ICFG.h" +#include "phasar/Utils/BitSet.h" +#include "phasar/Utils/FunctionId.h" +#include "phasar/Utils/IotaIterator.h" +#include "phasar/Utils/SCCGeneric.h" + +namespace psr { + +// TODO: Use SCCGeneric algorithms here! + +// Note: Use forward edges (i.e., cs->callee), such that the SCC-order reflects +// the bottom-up iteration order. +template C> + requires InstructionClassifier +[[nodiscard]] SCCHolder +computeCGSCCs(const psr::CallGraph &CG, const C &CF, + const FunctionCompressor &Functions) { + + SCCHolder Ret{}; + + auto NumFuns = Functions.size(); + if (!NumFuns) { + return Ret; + } + + Ret.SCCOfNode.resize(NumFuns); + + llvm::SmallVector Disc(NumFuns, UINT32_MAX); + llvm::SmallVector Low(NumFuns, UINT32_MAX); + BitSet OnStack(NumFuns); + BitSet Seen(NumFuns); + + llvm::SmallVector Stack; + uint32_t Time = 0; + + constexpr auto SetMin = [](uint32_t &InOut, uint32_t Other) { + if (Other < InOut) { + InOut = Other; + } + }; + + const auto Dfs = [&](auto &&Dfs, FunctionId CurrNode) -> void { + auto CurrTime = Time++; + Disc[size_t(CurrNode)] = CurrTime; + Low[size_t(CurrNode)] = CurrTime; + Stack.push_back(CurrNode); + OnStack.insert(CurrNode); + + const auto &CurrFun = Functions[CurrNode]; + for (const auto &Inst : CF.getAllInstructionsOf(CurrFun)) { + if (!CF.isCallSite(Inst)) { + continue; + } + + for (const auto &Succ : CG.getCalleesOfCallAt(Inst)) { + auto SuccNode = Functions.get(Succ); + if (Disc[size_t(SuccNode)] == UINT32_MAX) { + // Tree-edge: Not seen yet --> recurse + + Dfs(Dfs, SuccNode); + SetMin(Low[size_t(CurrNode)], Low[size_t(SuccNode)]); + } else if (OnStack.contains(SuccNode)) { + // Back-edge --> circle! + + SetMin(Low[size_t(CurrNode)], Disc[size_t(SuccNode)]); + } + } + } + + if (Low[size_t(CurrNode)] == Disc[size_t(CurrNode)]) { + // Found SCC + + auto SCCIdx = SCCId(Ret.NodesInSCC.size()); + auto &FunsInSCC = Ret.NodesInSCC.emplace_back(); + + assert(!Stack.empty()); + + while (Stack.back() != CurrNode) { + auto Fun = Stack.pop_back_val(); + Ret.SCCOfNode[Fun] = SCCIdx; + OnStack.erase(Fun); + Seen.insert(Fun); + FunsInSCC.push_back(Fun); + } + + auto Fun = Stack.pop_back_val(); + Ret.SCCOfNode[Fun] = SCCIdx; + OnStack.erase(Fun); + Seen.insert(Fun); + FunsInSCC.push_back(Fun); + } + }; + + for (auto FunId : iota(NumFuns)) { + if (!Seen.contains(FunId)) { + Dfs(Dfs, FunId); + } + } + + return Ret; +} + +template + requires InstructionClassifier +[[nodiscard]] SCCHolder +computeCGSCCs(const I &ICF, + const FunctionCompressor &Functions) { + return computeCGSCCs(ICF.getCallGraph(), ICF, Functions); +} + +template +[[nodiscard]] SCCDependencyGraph +computeCGSCCCallers(const psr::CallGraph &CG, const CFGOf auto &CF, + const FunctionCompressor &Functions, + const SCCHolder &SCCs) { + SCCDependencyGraph Ret; + Ret.ChildrenOfSCC.resize(SCCs.size()); + + BitSet> Leaves(SCCs.size(), true); + + for (auto [FunId, Fun] : Functions.enumerate()) { + auto SCC = SCCs.SCCOfNode[FunId]; + + for (const auto &CS : CG.getCallersOf(Fun)) { + const auto &CSFun = CF.getFunctionOf(CS); + if (auto CSFunId = Functions.getOrNull(CSFun)) { + auto CSFunSCC = SCCs.SCCOfNode[*CSFunId]; + if (CSFunSCC != SCC) { + Ret.ChildrenOfSCC[SCC].insert(CSFunSCC); + Leaves.erase(CSFunSCC); + } + } + } + } + + Ret.SCCRoots.reserve(Leaves.size()); + Leaves.foreach ( + [&](auto Leaf) { Ret.SCCRoots.push_back(SCCId(Leaf)); }); + + return Ret; +} + +template +[[nodiscard]] SCCDependencyGraph +computeCGSCCCallers(const I &ICF, + const FunctionCompressor &Functions, + const SCCHolder &SCCs) { + return computeCGSCCCallers(ICF.getCallGraph(), ICF, Functions, SCCs); +} + +} // namespace psr diff --git a/include/phasar/ControlFlow/CallGraph.h b/include/phasar/ControlFlow/CallGraph.h index 67cdc04e38..59409fd82b 100644 --- a/include/phasar/ControlFlow/CallGraph.h +++ b/include/phasar/ControlFlow/CallGraph.h @@ -17,8 +17,10 @@ #include "phasar/Utils/StableVector.h" #include "phasar/Utils/Utilities.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/IR/Function.h" +#include #include #include #include @@ -52,6 +54,24 @@ class CallGraph : public CallGraphBase> { /// Creates a new, empty call-graph CallGraph() noexcept = default; + auto + iterateCalleesView(std::invocable> auto Fn) const { + return psr::foreachInRange( + CalleesAt, [Fn = copyOrRef(Fn)](const auto &Elem) { + const auto &[CS, Callees] = Elem; + return psr::invokeControlFlow(Fn, CS, llvm::ArrayRef(*Callees)); + }); + } + + auto + iterateCallersView(std::invocable> auto Fn) const { + return psr::foreachInRange( + CallersOf, [Fn = copyOrRef(Fn)](const auto &Elem) { + const auto &[CS, Callers] = Elem; + return psr::invokeControlFlow(Fn, CS, llvm::ArrayRef(*Callers)); + }); + } + /// Deserializes a previously computed call-graph template [[nodiscard]] static CallGraph diff --git a/include/phasar/ControlFlow/CallGraphBase.h b/include/phasar/ControlFlow/CallGraphBase.h index 2d39581e3c..186ca426f2 100644 --- a/include/phasar/ControlFlow/CallGraphBase.h +++ b/include/phasar/ControlFlow/CallGraphBase.h @@ -15,6 +15,7 @@ #include "phasar/Utils/Compressor.h" #include "phasar/Utils/GraphTraits.h" #include "phasar/Utils/IotaIterator.h" +#include "phasar/Utils/Macros.h" #include "phasar/Utils/NonNullPtr.h" #include "phasar/Utils/TypeTraits.h" @@ -147,13 +148,15 @@ template class ReverseCGGraph { typename CallGraphTy::f_t>; constexpr ReverseCGGraph( - NonNullPtr CGView, NonNullPtr IRDB, + NonNullPtr CGView PSR_LIFETIMEBOUND, + NonNullPtr IRDB PSR_LIFETIMEBOUND, Compressor FC) noexcept requires(NeedsMapping) : CGView(CGView), IRDB(IRDB), FC(std::move(FC)) {} - constexpr ReverseCGGraph(NonNullPtr CGView, - NonNullPtr IRDB) noexcept + constexpr ReverseCGGraph(NonNullPtr CGView + PSR_LIFETIMEBOUND, + NonNullPtr IRDB PSR_LIFETIMEBOUND) noexcept : CGView(CGView), IRDB(IRDB) { FC.reserve(CGView->getNumVertexFunctions()); for (const auto &Fun : CGView->getAllVertexFunctions()) { diff --git a/include/phasar/ControlFlow/ControlFlowOrder.h b/include/phasar/ControlFlow/ControlFlowOrder.h new file mode 100644 index 0000000000..06751b1471 --- /dev/null +++ b/include/phasar/ControlFlow/ControlFlowOrder.h @@ -0,0 +1,72 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/ControlFlow/CFG.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/Nullable.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/DenseSet.h" + +#include + +namespace psr { + +template struct ControlFlowOrder { + enum class CFGOrderId : uint32_t {}; + + Compressor Order; + + [[nodiscard]] auto begin() const noexcept { + return std::make_reverse_iterator(Order.begin()); + } + [[nodiscard]] auto end() const noexcept { + return std::make_reverse_iterator(Order.end()); + } +}; + +template CFGTy> +void computeCFGOrder(ControlFlowOrder &Into, const CFGTy &CF, const F &Fun) { + llvm::SmallDenseSet Seen; + + const auto Visit = [&](auto &Visit, ByConstRef Inst) { + if (!Seen.insert(Inst).second) { + return; + } + + scope_exit Push = [&]() { Into.Order.insert(Inst); }; + + if constexpr (IsBlockAwareControlFlow) { + if (auto Next = CF.getUniqueSuccessor(Inst)) { + Visit(Visit, unwrapNullable(Next)); + return; + } + } + for (const auto &Succ : CF.getSuccsOf(Inst)) { + Visit(Visit, Succ); + } + }; + + for (const auto &SP : CF.getStartPointsOf(Fun)) { + Visit(Visit, SP); + } +} + +template CFGTy> +[[nodiscard]] inline ControlFlowOrder computeCFGOrder(const CFGTy &CF, + const F &Fun) { + ControlFlowOrder Ret; + computeCFGOrder(Ret, CF, Fun); + return Ret; +} + +} // namespace psr diff --git a/include/phasar/ControlFlow/ICFG.h b/include/phasar/ControlFlow/ICFG.h index d3660fe11f..e4697e74f6 100644 --- a/include/phasar/ControlFlow/ICFG.h +++ b/include/phasar/ControlFlow/ICFG.h @@ -8,6 +8,7 @@ *****************************************************************************/ #pragma once +#include "phasar/ControlFlow/CFG.h" #include "phasar/ControlFlow/CallGraphBase.h" #include "phasar/Utils/Nullable.h" #include "phasar/Utils/TypeTraits.h" @@ -16,18 +17,24 @@ #include "llvm/Support/raw_ostream.h" #include +#include namespace psr { template -concept ICFG = requires(const T &ICF, llvm::StringRef Name, - typename T::n_t Inst, typename T::f_t Fun) { - typename T::f_t; - typename T::n_t; +concept ICFG = CFG && requires(const T &ICF, llvm::StringRef Name, + typename std::remove_cvref_t::n_t Inst, + typename std::remove_cvref_t::f_t Fun) { + typename std::remove_cvref_t::f_t; + typename std::remove_cvref_t::n_t; // TODO: Should not be duplicated with ProjectIRDB - { ICF.getAllFunctions() } -> is_iterable_over_v; + { + ICF.getAllFunctions() + } -> is_iterable_over_v::f_t>; // TODO: Should not be duplicated with ProjectIRDB - { ICF.getFunction(Name) } -> std::convertible_to>; + { + ICF.getFunction(Name) + } -> std::convertible_to::f_t>>; { ICF.isIndirectFunctionCall(Inst) } -> std::convertible_to; { ICF.isVirtualFunctionCall(Inst) } -> std::convertible_to; @@ -37,19 +44,27 @@ concept ICFG = requires(const T &ICF, llvm::StringRef Name, /// Returns an iterable range of all possible callee candidates at the given /// call-site induced by the used call-graph. Same as /// getCallGraph().getCalleesOfCallAt(Inst) - { ICF.getCalleesOfCallAt(Inst) } -> psr::is_iterable_over_v; + { + ICF.getCalleesOfCallAt(Inst) + } -> psr::is_iterable_over_v::f_t>; /// Returns an iterable range of all possible call-site candidates that may /// call the given function induced by the used call-graph. Same as /// getCallGraph().getCallersOf(Fun) - { ICF.getCallersOf(Fun) } -> psr::is_iterable_over_v; + { + ICF.getCallersOf(Fun) + } -> psr::is_iterable_over_v::n_t>; /// Returns an iterable range of all call-instruction in the given function - { ICF.getCallsFromWithin(Fun) } -> psr::is_iterable_over_v; + { + ICF.getCallsFromWithin(Fun) + } -> psr::is_iterable_over_v::n_t>; /// Returns an iterable range of all instructions in all functions of the ICFG /// that are neither call-sites nor start-points of a function // TODO: Get rid of this function - { ICF.allNonCallStartNodes() } -> psr::is_iterable_over_v; + { + ICF.allNonCallStartNodes() + } -> psr::is_iterable_over_v::n_t>; /// The total number of call-sites in the ICFG. Same as /// getCallGraph().getNumVertexCallSites() @@ -61,4 +76,9 @@ concept ICFGDump = requires(const T &ICF, llvm::raw_ostream &OS) { ICF.print(OS); ICF.printAsJson(OS); }; + +template +concept ICFGOf = + ICFG && std::same_as::n_t> && + std::same_as::f_t>; } // namespace psr diff --git a/include/phasar/ControlFlow/SparseCFGBase.h b/include/phasar/ControlFlow/SparseCFGBase.h index 074f829d04..91a9732806 100644 --- a/include/phasar/ControlFlow/SparseCFGBase.h +++ b/include/phasar/ControlFlow/SparseCFGBase.h @@ -34,7 +34,7 @@ template class SparseCFGBase : public CFGBase { template // NOLINTNEXTLINE(readability-identifier-naming) -constexpr bool is_sparse_cfg_v = +concept is_sparse_cfg_v = is_crtp_base_of_v && std::is_same_v && std::is_same_v; diff --git a/include/phasar/ControlFlow/SparseCFGProvider.h b/include/phasar/ControlFlow/SparseCFGProvider.h index f85255e2ee..cdc2515f97 100644 --- a/include/phasar/ControlFlow/SparseCFGProvider.h +++ b/include/phasar/ControlFlow/SparseCFGProvider.h @@ -12,6 +12,7 @@ #include "phasar/Utils/ByRef.h" +#include #include namespace psr { @@ -38,11 +39,19 @@ template class SparseCFGProvider { }; template -// NOLINTNEXTLINE -constexpr bool has_getSparseCFG_v = +concept has_getSparseCFG_v = requires(const T &ICF, typename T::f_t Fun, D Fact) { // ICF.getSparseCFG(Fun, Fact); }; + +template +concept has_advanceToNextUser_v = + requires(const T &ICF, typename T::n_t Inst, D Fact) { + { + ICF.advanceToNextUser(Inst, Fact) + } -> std::convertible_to; + }; + } // namespace psr #endif // PHASAR_CONTROLFLOW_SPARSECFGPROVIDER_H diff --git a/include/phasar/DB.h b/include/phasar/DB.h index 294b4e2bb6..430bd5786b 100644 --- a/include/phasar/DB.h +++ b/include/phasar/DB.h @@ -12,6 +12,5 @@ #include "phasar/Config/phasar-config.h" #include "phasar/DB/ProjectIRDB.h" -#include "phasar/DB/ProjectIRDBBase.h" #endif // PHASAR_DB_H diff --git a/include/phasar/DB/ProjectIRDB.h b/include/phasar/DB/ProjectIRDB.h index c630be0765..290c6fa83e 100644 --- a/include/phasar/DB/ProjectIRDB.h +++ b/include/phasar/DB/ProjectIRDB.h @@ -14,6 +14,7 @@ #include "llvm/ADT/StringRef.h" #include +#include namespace psr { @@ -111,4 +112,19 @@ concept ProjectIRDB = requires ProjectSymbolTable; }; + +template +concept ProjectIRDBPtr = + ProjectIRDB())>>; +template +concept ProjectIRDBConstPtr = + std::is_const_v())>> && + ProjectIRDB())>>; + +// NOLINTNEXTLINE(readability-identifier-naming) +auto IRDBGetFunctionDef(ProjectIRDB auto *IRDB) noexcept { + return [IRDB](llvm::StringRef Name) { + return IRDB->getFunctionDefinition(Name); + }; +} } // namespace psr diff --git a/include/phasar/DB/ProjectIRDBBase.h b/include/phasar/DB/ProjectIRDBBase.h deleted file mode 100644 index 0fccf84f0d..0000000000 --- a/include/phasar/DB/ProjectIRDBBase.h +++ /dev/null @@ -1,194 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2022 Philipp Schubert. - * All rights reserved. This program and the accompanying materials are made - * available under the terms of LICENSE.txt. - * - * Contributors: - * Fabian Schiebel and others - *****************************************************************************/ - -#ifndef PHASAR_DB_PROJECTIRDBBASE_H -#define PHASAR_DB_PROJECTIRDBBASE_H - -#include "phasar/Utils/ByRef.h" -#include "phasar/Utils/Nullable.h" -#include "phasar/Utils/TypeTraits.h" - -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Compiler.h" -#include "llvm/Support/Debug.h" - -namespace psr { -template struct ProjectIRDBTraits { - // using n_t - // using f_t - // using m_t - // using g_t -}; - -/// This class owns the IR code of the target being analyzed and some -/// very important information associated with the IR. -/// When an object of this class is destroyed it will clean up all IR related -/// stuff that is stored in it. -/// -/// m_t - module type -/// f_t - function type -/// n_t - instruction type -/// g_t - global variable type -/// -/// \remark XXX Once we have upgraded to C++20, we might want to use a concept -/// instead... -template -class LLVM_DEPRECATED( - "This CRTP mixin is deprecated in favor of the ProjectIRDB-concept", - "Use ProjectIRDB instead") ProjectIRDBBase { -public: - using n_t = typename ProjectIRDBTraits::n_t; - using f_t = typename ProjectIRDBTraits::f_t; - using m_t = typename ProjectIRDBTraits::m_t; - using g_t = typename ProjectIRDBTraits::g_t; - - /// Returns the managed module - [[nodiscard]] m_t getModule() const noexcept { - assert(isValid()); - return self().getModuleImpl(); - } - - /// Check if debug information are available. - [[nodiscard]] bool debugInfoAvailable() const { - assert(isValid()); - return self().debugInfoAvailableImpl(); - } - - /// Returns a range of all function definitions and declarations available - [[nodiscard]] decltype(auto) getAllFunctions() const { - static_assert( - is_iterable_over_v); - assert(isValid()); - return self().getAllFunctionsImpl(); - } - - // Returns the function if available, nullptr/nullopt otherwise. - [[nodiscard]] Nullable getFunction(llvm::StringRef FunctionName) const { - assert(isValid()); - return self().getFunctionImpl(FunctionName); - } - /// Returns the function's definition if available, null otherwise. - [[nodiscard]] Nullable - getFunctionDefinition(llvm::StringRef FunctionName) const { - assert(isValid()); - return self().getFunctionDefinitionImpl(FunctionName); - } - /// Returns whether the IRDB contains a function with the given name. - [[nodiscard]] bool hasFunction(llvm::StringRef FunctionName) const noexcept { - assert(isValid()); - return self().hasFunctionImpl(FunctionName); - } - - /// Returns the function that contains the given instruction Inst. - /// Each instruction must be part of a function. - [[nodiscard]] f_t getFunctionOf(ByConstRef Inst) const { - assert(isValid()); - return self().getFunctionOfImpl(Inst); - } - - /// Returns the global variable if available, nullptr/nullopt - /// otherwise. - [[nodiscard]] Nullable - getGlobalVariable(llvm::StringRef GlobalVariableName) const { - assert(isValid()); - return self().getGlobalVariableImpl(GlobalVariableName); - } - - /// Returns the global variable's definition if available, nullptr/nullopt - /// otherwise. - [[nodiscard]] Nullable - getGlobalVariableDefinition(llvm::StringRef GlobalVariableName) const { - assert(isValid()); - return self().getGlobalVariableDefinitionImpl(GlobalVariableName); - } - - /// Returns the number of instruction in the managed module. - [[nodiscard]] size_t getNumInstructions() const noexcept { - assert(isValid()); - return self().getNumInstructionsImpl(); - } - /// Returns the number of global variables in the managed module. - [[nodiscard]] size_t getNumGlobals() const noexcept { - assert(isValid()); - return self().getNumGlobalsImpl(); - } - /// Returns the number of functions in the managed module. - [[nodiscard]] size_t getNumFunctions() const noexcept { - assert(isValid()); - return self().getNumFunctionsImpl(); - } - - /// Returns the instruction to the corresponding Id. Returns nullptr/nullopt, - /// if there is no instruction for this Id - [[nodiscard]] Nullable getInstruction(size_t Id) const { - assert(isValid()); - return self().getInstructionImpl(Id); - } - /// Returns an instruction's ID. The instruction must belong to the managed - /// module for this function to work - [[nodiscard]] size_t getInstructionId(n_t Inst) const { - assert(isValid()); - return self().getInstructionIdImpl(Inst); - } - - [[nodiscard]] decltype(auto) getAllInstructions() const { - static_assert( - is_iterable_over_v); - assert(isValid()); - return self().getAllInstructionsImpl(); - } - - /// Sanity check to verify that th IRDB really manages a Module and all - /// functions work properly. - /// All functions of this IRDB require isValid() to return true, if not stated - /// differently -- otherwise, they must not be called. - /// This function (obviously) does not require isValid() to return true. - [[nodiscard]] bool isValid() const noexcept { return self().isValidImpl(); } - - /// Dumps the managed module to llvm::dbgs() if isValid(); otherwise prints - /// "". - /// This function can be invoked from a debugger. - LLVM_DUMP_METHOD void dump() const { - if (!isValid()) { - llvm::dbgs() << "\n"; - llvm::dbgs().flush(); - return; - } - self().dumpImpl(); - } - - ProjectIRDBBase(const ProjectIRDBBase &) = delete; - ProjectIRDBBase &operator=(const ProjectIRDBBase &) = delete; - - ProjectIRDBBase(ProjectIRDBBase &&) noexcept = default; - ProjectIRDBBase &operator=(ProjectIRDBBase &&) noexcept = default; - - ~ProjectIRDBBase() = default; - -private: - friend Derived; - ProjectIRDBBase() noexcept = default; - - Derived &self() noexcept { return static_cast(*this); } - const Derived &self() const noexcept { - return static_cast(*this); - } -}; - -template -// NOLINTNEXTLINE(readability-identifier-naming) -auto IRDBGetFunctionDef(const ProjectIRDBBase *IRDB) noexcept { - return [IRDB](llvm::StringRef Name) { - return IRDB->getFunctionDefinition(Name); - }; -} - -} // namespace psr - -#endif // PHASAR_DB_PROJECTIRDBBASE_H diff --git a/include/phasar/DataFlow.h b/include/phasar/DataFlow.h index 5cd7522f42..686f075db4 100644 --- a/include/phasar/DataFlow.h +++ b/include/phasar/DataFlow.h @@ -17,8 +17,12 @@ #include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" #include "phasar/DataFlow/IfdsIde/EntryPointUtils.h" #include "phasar/DataFlow/IfdsIde/FlowFunctions.h" +#include "phasar/DataFlow/IfdsIde/IDEProblem.h" +#include "phasar/DataFlow/IfdsIde/IDEProblemWrapper.h" #include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/IFDSProblem.h" #include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/IfdsToIdeProblemAdapter.h" #include "phasar/DataFlow/IfdsIde/InitialSeeds.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h" #include "phasar/DataFlow/IfdsIde/Solver/IDESolver.h" @@ -32,6 +36,13 @@ #include "phasar/DataFlow/Mono/IntraMonoProblem.h" #include "phasar/DataFlow/Mono/Solver/InterMonoSolver.h" #include "phasar/DataFlow/Mono/Solver/IntraMonoSolver.h" +#include "phasar/DataFlow/MonoIfds/ArraySetWorkList.h" +#include "phasar/DataFlow/MonoIfds/DataFlowEnvironment.h" +#include "phasar/DataFlow/MonoIfds/IterationStrategy.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSConfig.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSProblem.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSSolver.h" +#include "phasar/DataFlow/MonoIfds/RPOWorkList.h" #include "phasar/DataFlow/PathSensitivity/PathSensitivityConfig.h" #include "phasar/DataFlow/PathSensitivity/PathSensitivityManager.h" diff --git a/include/phasar/DataFlow/HelperAnalyses.h b/include/phasar/DataFlow/HelperAnalyses.h new file mode 100644 index 0000000000..2efe94a7d4 --- /dev/null +++ b/include/phasar/DataFlow/HelperAnalyses.h @@ -0,0 +1,41 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/ControlFlow/ICFG.h" +#include "phasar/Utils/FunctionId.h" +#include "phasar/Utils/SCCGeneric.h" + +#include + +namespace psr { +template +concept CanGetICFG = requires(T &HA) { + { HA.getICFG() } -> ICFG; +}; + +template +concept CanGetICFGOf = requires(T &HA) { + { HA.getICFG() } -> ICFGOf; +}; + +template +concept CanGetCompressedFunctionsOf = requires(T &HA) { + { + HA.getCompressedFunctions() + } -> std::convertible_to &>; +}; + +template +concept CanGetCGSCCs = requires(T &HA) { + { &HA.getCGSCCs() } -> std::convertible_to *>; +}; + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/DefaultEdgeFunctionSingletonCache.h b/include/phasar/DataFlow/IfdsIde/DefaultEdgeFunctionSingletonCache.h index b01e0db168..bcd13661c6 100644 --- a/include/phasar/DataFlow/IfdsIde/DefaultEdgeFunctionSingletonCache.h +++ b/include/phasar/DataFlow/IfdsIde/DefaultEdgeFunctionSingletonCache.h @@ -13,6 +13,8 @@ #include "phasar/DataFlow/IfdsIde/EdgeFunction.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctionSingletonCache.h" +#include + namespace psr { /// Default implementation of EdgeFunctionSingletonCache. @@ -53,6 +55,7 @@ class DefaultEdgeFunctionSingletonCacheImpl void erase(const EdgeFunctionTy &EF) noexcept override { Cache.erase(&EF); } template + requires std::constructible_from [[nodiscard]] EdgeFunction createEdgeFunction(ArgTys &&...Args) { return CachedEdgeFunction{ EdgeFunctionTy{std::forward(Args)...}, this}; diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunction.h b/include/phasar/DataFlow/IfdsIde/EdgeFunction.h index 3f717759e1..1f46902b85 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunction.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunction.h @@ -13,6 +13,7 @@ #include "phasar/DataFlow/IfdsIde/EdgeFunctionSingletonCache.h" #include "phasar/Utils/ByRef.h" #include "phasar/Utils/EmptyBaseOptimizationUtils.h" +#include "phasar/Utils/ErrorFwd.h" #include "phasar/Utils/Macros.h" #include "phasar/Utils/TypeTraits.h" @@ -37,14 +38,28 @@ template class EdgeFunction; template class EdgeFunctionRef; template -concept IsEdgeFunction = - requires(const T &EF, const EdgeFunction &TEEF, - EdgeFunctionRef CEF, typename T::l_t Src) { - typename T::l_t; - { EF.computeTarget(Src) } -> std::convertible_to; - { T::compose(CEF, TEEF) } -> std::same_as>; - { T::join(CEF, TEEF) } -> std::same_as>; - }; +concept IsEdgeFunction = requires(const T &EF, typename T::l_t Src) { + typename T::l_t; + { EF.computeTarget(Src) } -> std::convertible_to; +}; + +template +concept IsEdgeFunctionFor = IsEdgeFunction && requires { + requires std::same_as; +}; + +template +concept HasEFCompose = requires(EdgeFunctionRef EFRef, + const EdgeFunction &EF) { + { + T::compose(EFRef, EF) + } -> std::convertible_to>; +}; +template +concept HasEFJoin = requires(EdgeFunctionRef EFRef, + const EdgeFunction &EF) { + { T::join(EFRef, EF) } -> std::convertible_to>; +}; enum class EdgeFunctionAllocationPolicy { SmallObjectOptimized, @@ -100,21 +115,23 @@ class EdgeFunctionBase { /// \brief Non-null reference to an edge function that is guarenteed to be /// managed by an EdgeFunction object. template -class [[clang::trivial_abi]] EdgeFunctionRef final : EdgeFunctionBase { +class PSR_POINTER(EF) EdgeFunctionRef final : EdgeFunctionBase { template friend class EdgeFunction; public: using l_t = typename EF::l_t; - EdgeFunctionRef(const EdgeFunctionRef &) noexcept = default; - EdgeFunctionRef &operator=(const EdgeFunctionRef &) noexcept = default; - ~EdgeFunctionRef() = default; - - const EF *operator->() const noexcept { return getPtr(Instance); } - const EF *get() const noexcept { return getPtr(Instance); } - const EF &operator*() const noexcept { return *getPtr(Instance); } + [[nodiscard]] constexpr const EF *operator->() const noexcept { + return getPtr(Instance); + } + [[nodiscard]] constexpr const EF *get() const noexcept { + return getPtr(Instance); + } + [[nodiscard]] constexpr const EF &operator*() const noexcept { + return *getPtr(Instance); + } - [[nodiscard]] bool isCached() const noexcept { + [[nodiscard]] constexpr bool isCached() const noexcept { if constexpr (IsSOOCandidate) { return false; } else { @@ -122,7 +139,7 @@ class [[clang::trivial_abi]] EdgeFunctionRef final : EdgeFunctionBase { } } - [[nodiscard]] EdgeFunctionSingletonCache * + [[nodiscard]] constexpr EdgeFunctionSingletonCache * getCacheOrNull() const noexcept { if (isCached()) { return static_cast *>(Instance)->Cache; @@ -148,46 +165,46 @@ class [[clang::trivial_abi]] EdgeFunctionRef final : EdgeFunctionBase { template // -- combined copy and move assignment // NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) -class [[clang::trivial_abi]] EdgeFunction final : EdgeFunctionBase { +class PSR_TRIVIAL_ABI PSR_OWNER() EdgeFunction final : EdgeFunctionBase { public: using l_t = L; // --- Constructors /// Default-initializes the edge-function with nullptr - EdgeFunction() noexcept = default; + constexpr EdgeFunction() noexcept = default; /// Default-initializes the edge-function with nullptr - EdgeFunction(std::nullptr_t) noexcept : EdgeFunction() {} + constexpr EdgeFunction(std::nullptr_t) noexcept : EdgeFunction() {} /// Copy constructor. Increments the ref-count, if not small-object-optimized. EdgeFunction(const EdgeFunction &Other) noexcept : EdgeFunction(Other.EF, Other.VTAndHeapAlloc) {} /// Move constructor. Does not increment the ref-count, but instead leaves the /// moved-from edge function in the nullptr state. - EdgeFunction(EdgeFunction &&Other) noexcept + constexpr EdgeFunction(EdgeFunction &&Other) noexcept : EF(std::exchange(Other.EF, nullptr)), VTAndHeapAlloc( std::exchange(Other.VTAndHeapAlloc, decltype(VTAndHeapAlloc){})) {} /// Standard swap; does not affect ref-counts - void swap(EdgeFunction &Other) noexcept { + constexpr void swap(EdgeFunction &Other) noexcept { std::swap(EF, Other.EF); std::swap(VTAndHeapAlloc, Other.VTAndHeapAlloc); } /// Standard swap; does not affect ref-counts - friend void swap(EdgeFunction &LHS, EdgeFunction &RHS) noexcept { + constexpr friend void swap(EdgeFunction &LHS, EdgeFunction &RHS) noexcept { LHS.swap(RHS); } /// Combined copy- and move assignment. If the assigned-to edge function is /// not null, invokes the destructor on it, before overwriting its content. - EdgeFunction &operator=(EdgeFunction Other) noexcept { + constexpr EdgeFunction &operator=(EdgeFunction Other) noexcept { std::destroy_at(this); return *new (this) EdgeFunction(std::move(Other)); // NOLINT } /// Null-assignment operator. Decrements the ref-count if not /// small-object-optimized or already null. Leaves the assigned-to edge /// function in the nullptr state. - EdgeFunction &operator=(std::nullptr_t) noexcept { + constexpr EdgeFunction &operator=(std::nullptr_t) noexcept { std::destroy_at(this); return *new (this) EdgeFunction(); // NOLINT } @@ -412,15 +429,6 @@ class [[clang::trivial_abi]] EdgeFunction final : EdgeFunctionBase { LHS.VTAndHeapAlloc.getPointer()->equals(LHS.EF, RHS.EF); } - template - requires(!std::is_same_v>) - [[nodiscard]] PSR_DEPRECATED( - "With C++20, we do not need this helper anymore, use operator== instead", - "operator==") bool equals(EdgeFunctionRef Other) - const noexcept { - return *this == Other; - } - template requires(!std::is_same_v>) [[nodiscard]] bool @@ -581,7 +589,9 @@ class [[clang::trivial_abi]] EdgeFunction final : EdgeFunctionBase { /// Gets an opaque identifier for this edge function. Only meant for /// comparisons of object-identity. Do not dereference! - [[nodiscard]] const void *getOpaqueValue() const noexcept { return EF; } + [[nodiscard]] constexpr const void *getOpaqueValue() const noexcept { + return EF; + } /// Gets the cache where this edge function is being cached in. If this edge /// function is not cached (i.e., isCached() returns false), returns nullptr. @@ -646,83 +656,117 @@ class [[clang::trivial_abi]] EdgeFunction final : EdgeFunctionBase { // NOLINTEND(readability-identifier-naming) }; + template + static l_t computeTargetThunk(const void *EF, ByConstRef Source) { + return getPtr(EF)->computeTarget(Source); + } + + template + static EdgeFunction composeThunk(const void *EF, const EdgeFunction &SecondEF, + AllocationPolicy Policy) { + if constexpr (HasEFCompose) { + return ConcreteEF::compose( + EdgeFunctionRef( + EF, Policy == AllocationPolicy::CustomHeapAllocated), + SecondEF); + } else { + composeEFPureVirtualError(llvm::getTypeName(), + llvm::getTypeName()); + } + } + + template + static EdgeFunction joinThunk(const void *EF, const EdgeFunction &OtherEF, + AllocationPolicy Policy) { + if constexpr (HasEFJoin) { + return ConcreteEF::join( + EdgeFunctionRef( + EF, Policy == AllocationPolicy::CustomHeapAllocated), + OtherEF); + } else { + joinEFPureVirtualError(llvm::getTypeName(), + llvm::getTypeName()); + } + } + + template + static bool equalsThunk(const void *EF1, const void *EF2) noexcept { + static_assert(IsEqualityComparable || + std::is_empty_v, + "An EdgeFunction must be equality comparable with " + "operator==. Only if the type is empty, i.e. has no " + "members, the comparison can be inferred."); + if constexpr (IsEqualityComparable) { + return *getPtr(EF1) == *getPtr(EF2); + } else { + return true; + } + } + + template + static void printThunk(const void *EF, llvm::raw_ostream &OS) { + if constexpr (is_llvm_printable_v) { + OS << *getPtr(EF); + } else { + OS << llvm::getTypeName(); + } + } + + template + static bool isConstantThunk(const void *EF) noexcept { + if constexpr (HasIsConstant) { + static_assert( + std::is_nothrow_invocable_v, + "The function isConstant() must be noexcept!"); + return getPtr(EF)->isConstant(); + } else { + return false; + } + } + + template + static void destroyThunk(const void *EF, AllocationPolicy Policy) noexcept { + if constexpr (!IsSOOCandidate) { + if (Policy != AllocationPolicy::CustomHeapAllocated) { + assert(Policy == AllocationPolicy::DefaultHeapAllocated); + delete static_cast *>(EF); + } else { + auto CEF = static_cast *>(EF); + CEF->Cache->erase(CEF->Value); + delete CEF; + } + } + } + + template + static size_t getHashCodeThunk(const void *EF, const void *VT) noexcept { + if constexpr (is_std_hashable_v) { + return std::hash{}(*getPtr(EF)); + } else if constexpr (is_llvm_hashable_v) { + using llvm::hash_value; + return hash_value(*getPtr(EF)); + } else { + return llvm::hash_combine(EF, VT); + } + } + + template + static size_t depthThunk(const void *EF) noexcept { + if constexpr (HasDepth) { + return getPtr(EF)->depth(); + } else { + return 1; + } + } + template static constexpr VTable VTableFor = { - [](const void *EF, ByConstRef Source) { - return getPtr(EF)->computeTarget(Source); - }, - [](const void *EF, const EdgeFunction &SecondEF, - AllocationPolicy Policy) { - return ConcreteEF::compose( - EdgeFunctionRef( - EF, Policy == AllocationPolicy::CustomHeapAllocated), - SecondEF); - }, - [](const void *EF, const EdgeFunction &OtherEF, AllocationPolicy Policy) { - return ConcreteEF::join( - EdgeFunctionRef( - EF, Policy == AllocationPolicy::CustomHeapAllocated), - OtherEF); - }, - [](const void *EF1, const void *EF2) noexcept { - static_assert(IsEqualityComparable || - std::is_empty_v, - "An EdgeFunction must be equality comparable with " - "operator==. Only if the type is empty, i.e. has no " - "members, the comparison can be inferred."); - if constexpr (IsEqualityComparable) { - return *getPtr(EF1) == *getPtr(EF2); - } else { - return true; - } - }, - [](const void *EF, llvm::raw_ostream &OS) { - if constexpr (is_llvm_printable_v) { - OS << *getPtr(EF); - } else { - OS << llvm::getTypeName(); - } - }, - [](const void *EF) noexcept { - if constexpr (HasIsConstant) { - static_assert( - std::is_nothrow_invocable_v, - "The function isConstant() must be noexcept!"); - return getPtr(EF)->isConstant(); - } else { - return false; - } - }, - [](const void *EF, AllocationPolicy Policy) noexcept { - if constexpr (!IsSOOCandidate) { - if (Policy != AllocationPolicy::CustomHeapAllocated) { - assert(Policy == AllocationPolicy::DefaultHeapAllocated); - delete static_cast *>(EF); - } else { - auto CEF = static_cast *>(EF); - CEF->Cache->erase(CEF->Value); - delete CEF; - } - } - }, - [](const void *EF, const void *VT) noexcept -> size_t { - if constexpr (is_std_hashable_v) { - return std::hash{}(*getPtr(EF)); - } else if constexpr (is_llvm_hashable_v) { - using llvm::hash_value; - return hash_value(*getPtr(EF)); - } else { - return llvm::hash_combine(EF, VT); - } - }, - [](const void *EF) noexcept -> size_t { - if constexpr (HasDepth) { - return getPtr(EF)->depth(); - } else { - return 1; - } - }, + &computeTargetThunk, &composeThunk, + &joinThunk, &equalsThunk, + &printThunk, &isConstantThunk, + &destroyThunk, &getHashCodeThunk, + &depthThunk, }; // Utility ctor for (copy) construction. Increments the ref-count if diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunctionStats.h b/include/phasar/DataFlow/IfdsIde/EdgeFunctionStats.h index 448188b48b..14a0cb1451 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunctionStats.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunctionStats.h @@ -10,7 +10,8 @@ #ifndef PHASAR_DATAFLOW_IFDSIDE_EDGEFUNCTIONSTATS_H #define PHASAR_DATAFLOW_IFDSIDE_EDGEFUNCTIONSTATS_H -#include "llvm/ADT/StringRef.h" +#include "phasar/ControlFlow/ICFG.h" + #include "llvm/Support/raw_ostream.h" #include @@ -72,7 +73,7 @@ class EdgeFunctionStats : public detail::EdgeFunctionStatsData { const EdgeFunctionStats &S); private: - template + template friend class IDESolver; constexpr EdgeFunctionStats( diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h b/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h index 6d3df38b30..1c62849f21 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h @@ -24,14 +24,15 @@ namespace psr { template struct EdgeIdentity final { using l_t = L; - [[nodiscard]] ByConstRef computeTarget(ByConstRef Source) const + [[nodiscard]] constexpr ByConstRef + computeTarget(ByConstRef Source) const noexcept(std::is_nothrow_move_constructible_v) { static_assert(std::is_trivially_copyable_v); static_assert(IsEdgeFunction); return Source; } - [[nodiscard]] static EdgeFunction + [[nodiscard]] constexpr static EdgeFunction compose(EdgeFunctionRef /*This*/, const EdgeFunction &SecondFunction) { return SecondFunction; @@ -43,6 +44,9 @@ template struct EdgeIdentity final { friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, EdgeIdentity) { return OS << "EdgeIdentity"; } + + friend constexpr bool operator==(EdgeIdentity, + EdgeIdentity) noexcept = default; }; template struct ConstantEdgeFunction { @@ -50,7 +54,7 @@ template struct ConstantEdgeFunction { using JLattice = JoinLatticeTraits; using value_type = typename NonTopBotValue::type; - [[nodiscard]] l_t computeTarget(ByConstRef /*Source*/) const + [[nodiscard]] constexpr l_t computeTarget(ByConstRef /*Source*/) const noexcept(std::is_nothrow_constructible_v) { static_assert(IsEdgeFunction); return Value; @@ -69,49 +73,48 @@ template struct ConstantEdgeFunction { [[nodiscard]] constexpr bool isConstant() const noexcept { return true; } - // -- constant data member + [[nodiscard]] friend constexpr bool + operator==(ConstantEdgeFunction LHS, ConstantEdgeFunction RHS) noexcept + requires CanEfficientlyPassByValue + { + return LHS.Value == RHS.Value; + } - value_type Value{}; -}; + [[nodiscard]] friend constexpr bool + operator==(const ConstantEdgeFunction &LHS, + const ConstantEdgeFunction &RHS) noexcept + requires(!CanEfficientlyPassByValue) + { + return LHS.Value == RHS.Value; + } -template - requires CanEfficientlyPassByValue> -[[nodiscard]] constexpr bool operator==(ConstantEdgeFunction LHS, - ConstantEdgeFunction RHS) noexcept { - return LHS.Value == RHS.Value; -} + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const ConstantEdgeFunction &Id) { + OS << "ConstantEF"; + if constexpr (is_llvm_printable_v< + typename ConstantEdgeFunction::value_type>) { + OS << '[' << Id.Value << ']'; + } + return OS; + } -template - requires(!CanEfficientlyPassByValue>) -[[nodiscard]] constexpr bool -operator==(const ConstantEdgeFunction &LHS, - const ConstantEdgeFunction &RHS) noexcept { - return LHS.Value == RHS.Value; -} + [[nodiscard]] friend auto hash_value(const ConstantEdgeFunction &CEF) noexcept + requires(is_std_hashable_v::type> || + is_llvm_hashable_v::type>) + { + using value_type = typename ConstantEdgeFunction::value_type; + if constexpr (is_std_hashable_v) { + return std::hash{}(CEF.Value); + } else { + using llvm::hash_value; + return hash_value(CEF.Value); + } + } -template -[[nodiscard]] llvm::raw_ostream & -operator<<(llvm::raw_ostream &OS, ByConstRef> Id) { - OS << "ConstantEF"; - if constexpr (is_llvm_printable_v< - typename ConstantEdgeFunction::value_type>) { - OS << '[' << Id.Value << ']'; - } - return OS; -} + // -- constant data member -template - requires(is_std_hashable_v::type> || - is_llvm_hashable_v::type>) -[[nodiscard]] auto hash_value(const ConstantEdgeFunction &CEF) noexcept { - using value_type = typename ConstantEdgeFunction::value_type; - if constexpr (is_std_hashable_v) { - return std::hash{}(CEF.Value); - } else { - using llvm::hash_value; - return hash_value(CEF.Value); - } -} + value_type Value{}; +}; template struct AllBottom final { using l_t = L; @@ -120,7 +123,8 @@ template struct AllBottom final { [[no_unique_address]] std::conditional_t, EmptyType, l_t> BottomValue; - [[nodiscard]] l_t computeTarget(ByConstRef /*Source*/) const noexcept { + [[nodiscard]] constexpr l_t + computeTarget(ByConstRef /*Source*/) const noexcept { static_assert(std::is_trivially_copyable_v); static_assert(IsEdgeFunction); if constexpr (HasJoinLatticeTraits) { @@ -130,7 +134,7 @@ template struct AllBottom final { } } - [[nodiscard]] static EdgeFunction + [[nodiscard]] constexpr static EdgeFunction compose(EdgeFunctionRef This, const EdgeFunction &SecondFunction) { if (SecondFunction.isConstant()) { @@ -154,7 +158,7 @@ template struct AllBottom final { // return SecondFunction.isConstant() ? SecondFunction : This; } - [[nodiscard]] static EdgeFunction + [[nodiscard]] constexpr static EdgeFunction join(EdgeFunctionRef This, const EdgeFunction & /*OtherFunction*/) { return This; @@ -181,7 +185,8 @@ template struct AllTop final { [[no_unique_address]] std::conditional_t, EmptyType, l_t> TopValue; - [[nodiscard]] l_t computeTarget(ByConstRef /*Source*/) const noexcept { + [[nodiscard]] constexpr l_t + computeTarget(ByConstRef /*Source*/) const noexcept { static_assert(std::is_trivially_copyable_v); static_assert(IsEdgeFunction); if constexpr (HasJoinLatticeTraits) { @@ -197,7 +202,7 @@ template struct AllTop final { return SecondFunction.isConstant() ? SecondFunction : This; } - [[nodiscard]] static EdgeFunction + [[nodiscard]] constexpr static EdgeFunction join(EdgeFunctionRef /*This*/, const EdgeFunction &OtherFunction) { return OtherFunction; @@ -205,7 +210,8 @@ template struct AllTop final { [[nodiscard]] constexpr bool isConstant() const noexcept { return true; } - friend bool operator==(const AllTop &LHS, const AllTop &RHS) noexcept + constexpr friend bool operator==(const AllTop &LHS, + const AllTop &RHS) noexcept requires(!HasJoinLatticeTraits) { return LHS.TopValue == RHS.TopValue; @@ -295,6 +301,10 @@ template struct EdgeFunctionComposer { return First.depth() + Second.depth(); } + friend auto hash_value(const EdgeFunctionComposer &EFC) noexcept { + return llvm::hash_combine(EFC.First, EFC.Second); + } + // -- data members EdgeFunction First{}; @@ -303,11 +313,6 @@ template struct EdgeFunctionComposer { static_assert(HasDepth>); -template -auto hash_value(const EdgeFunctionComposer &EFC) noexcept { - return llvm::hash_combine(EFC.First, EFC.Second); -} - template struct JoinEdgeFunction { using l_t = L; using JLattice = JoinLatticeTraits; @@ -477,8 +482,9 @@ defaultJoinOrNullNoId(const EdgeFunction &This, } template -EdgeFunction EdgeIdentity::join(EdgeFunctionRef This, - const EdgeFunction &OtherFunction) { +inline EdgeFunction +EdgeIdentity::join(EdgeFunctionRef This, + const EdgeFunction &OtherFunction) { if (llvm::isa>(OtherFunction) || llvm::isa>(OtherFunction)) { return This; diff --git a/include/phasar/DataFlow/IfdsIde/EdgeFunctions.h b/include/phasar/DataFlow/IfdsIde/EdgeFunctions.h index e6b4cd2e17..546a0f8dd0 100644 --- a/include/phasar/DataFlow/IfdsIde/EdgeFunctions.h +++ b/include/phasar/DataFlow/IfdsIde/EdgeFunctions.h @@ -25,6 +25,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/Support/raw_ostream.h" +#include #include #include @@ -269,6 +270,244 @@ template class EdgeFunctions { } }; +template +concept EdgeFunctionFactory = requires( + T &EFF, typename T::n_t Curr, typename T::n_t Succ, + typename T::d_t CurrNode, typename T::d_t SuccNode, + typename T::n_t ExitInst, typename T::n_t CallSite, typename T::n_t RetSite, + typename T::f_t CalleeFun, llvm::ArrayRef Callees) { + typename T::EdgeFunctionType; + typename T::n_t; + typename T::d_t; + typename T::f_t; + typename T::l_t; + + /// + /// Also refer to FlowFunctions::getNormalFlowFunction() + /// + /// Describes a value computation problem along a normal (non-call, + /// non-return) intra-procedural exploded supergraph edge. A normal edge + /// function implementation is queried for each edge that has been generated + /// by appling the flow function returned by + /// FlowFunctions::getNormalFlowFunction(). The supergraph edge whose + /// computation is requested is defined by the supergraph nodes CurrNode and + /// SuccNode. + /// + /// Let instruction_1 := Curr, instruction_2 := Succ, and 0 the tautological + /// lambda fact. + /// + /// The concrete implementation of an edge function e is depending on the + /// analysis problem. In the following, we present a brief, contrived example: + /// + /// Consider the following flow function implementation (cf. + /// FlowFunctions::getNormalFlowfunction()): + /// \code + /// f(0) -> {0} // pass the lambda (or zero fact) as identity + /// f(o) -> {o, x} // generate a new fact x from o + /// f(.) -> {.} // pass all other facts that hold before + /// // instruction_1 as identity + /// \endcode + /// + /// The above flow-function implementation corresponds to the following edges + /// in the exploded supergraph. + /// \code + /// 0 o ... + /// | |\ ... + /// Curr := x = instruction_1 o p | | \ ... + /// | | | ... + /// v v v ... + /// 0 o x ... + /// + /// Succ := y = instruction_2 q r + /// \endcode + /// For each edge generated by the respective flow function a normal edge + /// function is queried that describes a value computation. This results in + /// the following queries: + /// + /// \code + /// getNormalEdgeFunction(0, Curr, 0 Succ); + /// getNormalEdgeFunction(o, Curr, o Succ); + /// getNormalEdgeFunction(o, Curr, x Succ); + /// \endcode + { + EFF.getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode) + } -> std::convertible_to; + + /// + /// Also refer to FlowFunctions::getCallFlowFunction() + /// + /// Describes a value computation problem along a call flow. A call edge + /// function is queried for each edge that has been generated by applying the + /// flow function that has been returned by + /// FlowFunctions::getCallFlowFunction. The supergraph edge whose computation + /// is requested is defined by the supergraph nodes SrcNode and DestNode. + /// + /// The concrete implementation of an edge function e is depending on the + /// analysis problem. In the following, we present a brief, contrived example: + /// + /// Consider the following flow function implementation (cf. + /// FlowFunctions::getCallFlowFunction()): + /// \code + /// f(0) -> {0} // pass as identity into the callee target + /// f(o) -> {q} // map actual o into formal q + /// f(p) -> {r} // map actual p into formal r + /// f(.) -> {} // kill all other facts that are not visible to the + /// // callee target + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o p ... + /// \ \ \ ... + /// CallInst := x = CalleeFun(o, p, ...) \ \ +----------------+ + /// \ +---------------- | + /// +-------------+ + | + /// ... | | | + /// ... | | | + /// 0 o p ... | | | + /// | | | + /// | | | + /// | | | + /// Ty CalleeFun(q, r, ...) | | | + /// v v v + /// 0 q r ... + /// + /// start point + /// \endcode + /// For each edge generated by the respective flow function a call edge + /// function is queried that describes a value computation. This results in + /// the following queries: + /// \code + /// getCallEdgeFunction(CallInst, 0, CalleeFun, 0); + /// getCallEdgeFunction(CallInst, o, CalleeFun, q); + /// getCallEdgeFunction(CallInst, p, CalleeFun, r); + /// \endcode + { + EFF.getCallEdgeFunction(CallSite, CurrNode, CalleeFun, SuccNode) + } -> std::convertible_to; + + /// + /// Also refer to FlowFunction::getRetFlowFunction() + /// + /// Describes a value computation problem along a return flow. A return edge + /// function implementation is queried for each edge that has been generated + /// by applying the flow function that has been returned by + /// FlowFunctions::getRetFlowFunction(). The supergraph edge whose computation + /// is requested is defined by the supergraph nodes ExitNode and RetNode. + /// + /// The concrete implementation of an edge function e is depending on the + /// analysis problem. In the following, we present a brief, contrived example: + /// + /// Consider the following flow function implementation (cf. + /// FlowFunctions::getRetFlowFunction()): + /// \code + /// f(0) -> {0} // pass as identity into the callee target + /// f(r) -> {x} // map return value to lhs variable at CallSite + /// f(q) -> {o} // map pointer-typed formal q to actual o + /// f(.) -> {} // kill all other facts that are not visible to the + /// // caller + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o ... + /// + /// CallSite = RetSite := x = CalleeFun(o, ...) + /// +------------------+ + /// +--|---------------+ | + /// +--|--|------------+ | | + /// v v v ... | | | + /// 0 o x ... | | | + /// | | | + /// | | | + /// | | | + /// Ty CalleeFun(q, ...) | | | + /// | | | + /// 0 q r + /// + /// ExitInst := return r + /// \endcode + /// For each edge generated by the respective flow function a return edge + /// function is queried that describes a value computation. This results in + /// the following queries: + /// \code + /// getReturnEdgeFunction(CallSite, CalleeFun, ExitInst, 0, RetSite, 0); + /// getReturnEdgeFunction(CallSite, CalleeFun, ExitInst, q, RetSite, o); + /// getReturnEdgeFunction(CallSite, CalleeFun, ExitInst, r, RetSite, x); + /// \endcode + { + EFF.getReturnEdgeFunction(CallSite, CalleeFun, ExitInst, CurrNode, RetSite, + SuccNode) + } -> std::convertible_to; + + /// + /// Also refer to FlowFunctions::getCallToRetFlowFunction() + /// + /// Describes a value computation problem along data-flows alongsite a + /// CallSite. A return edge function implementation is queried for each edge + /// that has been generated by applying the flow function that has been + /// returned by FlowFunctions::getCallToRetFlowFunction(). The supergraph edge + /// whose computation is requested is defined by the supergraph nodes CallNode + /// and RetSiteNode. + /// + /// The concrete implementation of an edge function e is depending on the + /// analysis problem. In the following, we present a brief, contrived example: + /// + /// Consider the following flow function implementation (cf. + /// FlowFunctions::getCallToRetFlowFunction()): + /// \code + /// f(0) -> {0} // pass lambda as identity alongsite the CallSite + /// f(o) -> {o} // assuming that o is passed by value, it is passed + /// // alongsite the CallSite + /// f(p) -> {} // assuming that p is a pointer-typed value, we need + /// // to kill p, as it will be handled by the call- and + /// // return-flow functions + /// f(.) -> {.} // pass everything that is not involved in the call + /// // as identity + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o ... + /// | | + /// | +-------+ + /// +--------+ | + /// | | + /// CallSite = RetSite := x = CalleeFun(o, p, ...) | | + /// | | + /// +--------+ | + /// | +-------+ + /// v v + /// 0 o x ... + /// \endcode + /// For each edge generated by the respective flow function a call-to-return + /// edge function is queried that describes a value computation. This results + /// in the following queries: + /// + /// getCallToRetEdgeFunction(CallSite, 0, RetSite, 0, {CalleeFun}); + /// getCallToRetEdgeFunction(CallSite, o, RetSite, o, {CalleeFun}); + /// + { + EFF.getCallToRetEdgeFunction(CallSite, CurrNode, RetSite, SuccNode, Callees) + } -> std::convertible_to; + + /// + /// Also refer to FlowFunction::getSummaryFlowFunction() + /// + /// Describes a value computation problem along a summary data flow. A summary + /// edge function implementation is queried for each edge that has been + /// generated by FlowFunctions::getSummaryFlowFunction(). The supergraph edge + /// whose computation is requested is defined by the supergraph nodes CurrNode + /// and SuccNode. + /// + /// The default implementation returns a nullptr to indicate that the + /// mechanism should not be used. + /// + { + EFF.getSummaryEdgeFunction(CallSite, CurrNode, RetSite, SuccNode) + } -> std::convertible_to; +}; + } // namespace psr #endif diff --git a/include/phasar/DataFlow/IfdsIde/FlowFunctions.h b/include/phasar/DataFlow/IfdsIde/FlowFunctions.h index 6c7cc58a87..b932709475 100644 --- a/include/phasar/DataFlow/IfdsIde/FlowFunctions.h +++ b/include/phasar/DataFlow/IfdsIde/FlowFunctions.h @@ -18,10 +18,12 @@ #define PHASAR_DATAFLOW_IFDSIDE_FLOWFUNCTIONS_H #include "phasar/Utils/Macros.h" +#include "phasar/Utils/MaybeUniquePtr.h" #include "phasar/Utils/TypeTraits.h" #include "llvm/ADT/ArrayRef.h" +#include #include #include #include @@ -45,10 +47,11 @@ template > class FlowFunction { public: using FlowFunctionType = FlowFunction; - using FlowFunctionPtrType = std::shared_ptr; + using FlowFunctionPtrType = MaybeUniquePtr; using container_type = Container; using value_type = typename container_type::value_type; + using d_t = D; virtual ~FlowFunction() = default; @@ -66,10 +69,49 @@ template > class FlowFunction { virtual container_type computeTargets(D Source) = 0; }; +template +concept IsFlowFunctionLike = requires(T &FF, typename T::d_t Source) { + typename T::d_t; + typename T::container_type; + requires is_iterable_over_v; + { + FF.computeTargets(Source) + } -> std::convertible_to; +}; + +template +concept IsFlowFunctionPtr = + IsFlowFunctionLike())>>; + +template +concept IsFlowFunctionOf = + IsFlowFunctionLike && std::same_as; + +template +concept IsFlowFunctionPtrOf = + IsFlowFunctionOf())>, D>; + +template +concept IsFlowFunctionOrFFPtr = IsFlowFunctionLike || IsFlowFunctionPtr; + +template +concept IsFlowFunctionOrFFPtrOf = + IsFlowFunctionOf || IsFlowFunctionPtrOf; + +template +using FFContainerType = typename decltype([] { + if constexpr (IsFlowFunctionLike) { + return std::type_identity{}; + } else { + return std::type_identity())>::container_type>{}; + } +}())::type; + /// Helper template to check at compile-time whether a type implements the /// FlowFunction interface, no matter which data-flow fact type it uses. /// -/// Use is_flowfunction_v instead. +/// Use IsFlowFunctionLike instead. template struct IsFlowFunction { template static std::true_type test(const FlowFunction &); @@ -83,11 +125,13 @@ template struct IsFlowFunction { /// Helper template to check at compile-time whether a type implements the /// FlowFunction interface, no matter which data-flow fact type it uses. template -concept is_flowfunction_v = IsFlowFunction::value; // NOLINT +concept is_flowfunction_v + [[deprecated("Use IsFlowFunctionLike instead. It is more flexible")]] = + IsFlowFunction::value; // NOLINT /// Given a flow-function type FF, returns a (smart) pointer type pointing to FF -template -using FlowFunctionPtrTypeOf = std::shared_ptr; +template +using FlowFunctionPtrTypeOf = MaybeUniquePtr; /// Given a dataflow-fact type and optionally a container-type, returns a /// (smart) pointer type pointing to a FlowFunction with the specified @@ -147,6 +191,8 @@ template class FlowFunctionTemplates { using d_t = D; using container_type = Container; + using FlowFunctionPtrType = psr::FlowFunctionPtrType; + /// A flow function that propagates all incoming facts unchanged. /// /// Given a flow-function f = identityFlow(), then for all incoming @@ -160,15 +206,18 @@ template class FlowFunctionTemplates { /// v v v ... /// x1 x2 x3 ... /// \endcode - static auto identityFlow() { + /// \note Returns a non-owning pointer to a function-static singleton + /// (owns()==false). The pointee has program lifetime. Most other factory + /// functions in this class return owning unique_ptrs. + static FlowFunctionPtrType identityFlow() { struct IdFF final : public FlowFunction { container_type computeTargets(d_t Source) override { return {std::move(Source)}; } }; - static auto TheIdentity = std::make_shared(); + static auto TheIdentity = IdFF(); - return TheIdentity; + return &TheIdentity; } /// The most generic flow function. Invokes the passed function object F to @@ -186,7 +235,7 @@ template class FlowFunctionTemplates { /// v v v v v /// x1 x2 x x3 x4 /// \endcode - template static auto lambdaFlow(Fn &&F) { + template static FlowFunctionPtrType lambdaFlow(Fn &&F) { struct LambdaFlow final : public FlowFunction { LambdaFlow(Fn &&F) : Flow(std::forward(F)) {} container_type computeTargets(d_t Source) override { @@ -196,7 +245,7 @@ template class FlowFunctionTemplates { [[no_unique_address]] std::decay_t Flow; }; - return std::make_shared(std::forward(F)); + return std::make_unique(std::forward(F)); } //===----------------------------------------------------------------------===// @@ -225,7 +274,7 @@ template class FlowFunctionTemplates { /// no difference, but in the case of IDE, the corresponding edge functions /// are being joined together potentially lowering precision. If that is an /// issue, use transferFlow instead. - static auto generateFlow(d_t FactToGenerate, d_t From) { + static FlowFunctionPtrType generateFlow(d_t FactToGenerate, d_t From) { struct GenFrom final : public FlowFunction { GenFrom(d_t GenValue, d_t FromValue) : GenValue(std::move(GenValue)), FromValue(std::move(FromValue)) {} @@ -241,7 +290,7 @@ template class FlowFunctionTemplates { d_t FromValue; }; - return std::make_shared(std::move(FactToGenerate), + return std::make_unique(std::move(FactToGenerate), std::move(From)); } @@ -257,7 +306,7 @@ template class FlowFunctionTemplates { /// \endcode template requires std::is_invocable_r_v - static auto generateFlowIf(d_t FactToGenerate, Fn Predicate) { + static FlowFunctionPtrType generateFlowIf(d_t FactToGenerate, Fn Predicate) { struct GenFlowIf final : public FlowFunction { GenFlowIf(d_t GenValue, Fn &&Predicate) : GenValue(std::move(GenValue)), @@ -274,7 +323,7 @@ template class FlowFunctionTemplates { [[no_unique_address]] std::decay_t Predicate; }; - return std::make_shared(std::move(FactToGenerate), + return std::make_unique(std::move(FactToGenerate), std::forward(Predicate)); } @@ -296,7 +345,8 @@ template class FlowFunctionTemplates { /// x w v1 v2 ... vN u /// \endcode template Range = std::initializer_list> - static auto generateManyFlows(Range &&FactsToGenerate, d_t From) { + static FlowFunctionPtrType generateManyFlows(Range &&FactsToGenerate, + d_t From) { struct GenMany final : public FlowFunction { GenMany(container_type &&GenValues, d_t FromValue) : GenValues(std::move(GenValues)), FromValue(std::move(FromValue)) {} @@ -314,7 +364,7 @@ template class FlowFunctionTemplates { d_t FromValue; }; - return std::make_shared(detail::makeContainer( + return std::make_unique(detail::makeContainer( std::forward(FactsToGenerate)), std::move(From)); } @@ -338,7 +388,7 @@ template class FlowFunctionTemplates { /// v v /// u v w ... /// \endcode - static auto killFlow(d_t FactToKill) { + static FlowFunctionPtrType killFlow(d_t FactToKill) { struct KillFlow final : public FlowFunction { KillFlow(d_t KillValue) : KillValue(std::move(KillValue)) {} container_type computeTargets(d_t Source) override { @@ -350,7 +400,7 @@ template class FlowFunctionTemplates { d_t KillValue; }; - return std::make_shared(std::move(FactToKill)); + return std::make_unique(std::move(FactToKill)); } /// A flow function similar to killFlow that stops propagating all dataflow @@ -364,7 +414,7 @@ template class FlowFunctionTemplates { /// \endcode template requires std::is_invocable_r_v - static auto killFlowIf(Fn Predicate) { + static FlowFunctionPtrType killFlowIf(Fn Predicate) { struct KillFlowIf final : public FlowFunction { KillFlowIf(Fn &&Predicate) : Predicate(std::forward(Predicate)) {} @@ -378,7 +428,7 @@ template class FlowFunctionTemplates { [[no_unique_address]] std::decay_t Predicate; }; - return std::make_shared(std::forward(Predicate)); + return std::make_unique(std::forward(Predicate)); } /// A flow function that stops propagating a specific set of dataflow facts @@ -402,7 +452,7 @@ template class FlowFunctionTemplates { /// u v1 v2 ... vN w ... /// \endcode template Range = std::initializer_list> - static auto killManyFlows(Range &&FactsToKill) { + static FlowFunctionPtrType killManyFlows(Range &&FactsToKill) { struct KillMany final : public FlowFunction { KillMany(Container &&KillValues) : KillValues(std::move(KillValues)) {} @@ -416,7 +466,7 @@ template class FlowFunctionTemplates { container_type KillValues; }; - return std::make_shared(detail::makeContainer( + return std::make_unique(detail::makeContainer( std::forward(FactsToKill))); } @@ -426,13 +476,16 @@ template class FlowFunctionTemplates { /// \code /// x, f(x) = {}. /// \endcode - static auto killAllFlows() { + /// \note Returns a non-owning pointer to a function-static singleton + /// (owns()==false). The pointee has program lifetime. Most other factory + /// functions in this class return owning unique_ptrs. + static FlowFunctionPtrType killAllFlows() { struct KillAllFF final : public FlowFunction { Container computeTargets(d_t /*Source*/) override { return Container(); } }; - static auto TheKillAllFlow = std::make_shared(); + static auto TheKillAllFlow = KillAllFF(); - return TheKillAllFlow; + return &TheKillAllFlow; } //===----------------------------------------------------------------------===// @@ -460,7 +513,8 @@ template class FlowFunctionTemplates { /// v v /// x w v u /// \endcode - static auto generateFlowAndKillAllOthers(d_t FactToGenerate, d_t From) { + static FlowFunctionPtrType generateFlowAndKillAllOthers(d_t FactToGenerate, + d_t From) { struct GenFlowAndKillAllOthers final : public FlowFunction { GenFlowAndKillAllOthers(d_t GenValue, d_t FromValue) @@ -477,7 +531,7 @@ template class FlowFunctionTemplates { d_t FromValue; }; - return std::make_shared(std::move(FactToGenerate), + return std::make_unique(std::move(FactToGenerate), std::move(From)); } @@ -500,8 +554,8 @@ template class FlowFunctionTemplates { /// x w v1 v2 ... vN u /// \endcode template Range = std::initializer_list> - static auto generateManyFlowsAndKillAllOthers(Range &&FactsToGenerate, - d_t From) { + static FlowFunctionPtrType + generateManyFlowsAndKillAllOthers(Range &&FactsToGenerate, d_t From) { struct GenManyAndKillAllOthers final : public FlowFunction { GenManyAndKillAllOthers(Container &&GenValues, d_t FromValue) @@ -520,7 +574,7 @@ template class FlowFunctionTemplates { d_t FromValue; }; - return std::make_shared( + return std::make_unique( detail::makeContainer( std::forward(FactsToGenerate)), std::move(From)); @@ -553,7 +607,7 @@ template class FlowFunctionTemplates { /// v v v v ... /// x w v u /// \endcode - static auto transferFlow(d_t FactToGenerate, d_t From) { + static FlowFunctionPtrType transferFlow(d_t FactToGenerate, d_t From) { struct TransferFlow final : public FlowFunction { TransferFlow(d_t GenValue, d_t FromValue) : GenValue(std::move(GenValue)), FromValue(std::move(FromValue)) {} @@ -572,7 +626,7 @@ template class FlowFunctionTemplates { d_t FromValue; }; - return std::make_shared(std::move(FactToGenerate), + return std::make_unique(std::move(FactToGenerate), std::move(From)); } @@ -590,8 +644,8 @@ template class FlowFunctionTemplates { std::is_same_v && std::is_same_v) - auto unionFlows(FlowFunctionPtrTypeOf OneFF, - FlowFunctionPtrTypeOf OtherFF) { + FlowFunctionPtrType unionFlows(FlowFunctionPtrTypeOf OneFF, + FlowFunctionPtrTypeOf OtherFF) { struct UnionFlow final : public FlowFunction { UnionFlow(FlowFunctionPtrTypeOf OneFF, FlowFunctionPtrTypeOf OtherFF) noexcept @@ -613,7 +667,7 @@ template class FlowFunctionTemplates { FlowFunctionPtrTypeOf OtherFF; }; - return std::make_shared(std::move(OneFF), std::move(OtherFF)); + return std::make_unique(std::move(OneFF), std::move(OtherFF)); } }; @@ -875,6 +929,275 @@ class FlowFunctions } }; +template +concept FlowFunctionFactory = + requires(T &FFF, typename T::n_t Curr, typename T::n_t Succ, + typename T::n_t ExitInst, typename T::n_t CallSite, + typename T::n_t RetSite, typename T::f_t CalleeFun, + llvm::ArrayRef Callees) { + typename T::FlowFunctionPtrType; + typename T::d_t; + typename T::f_t; + typename T::n_t; + typename T::container_type; + + requires is_iterable_over_v; + requires IsFlowFunctionPtrOf; + requires std::same_as>; + + /// + /// Describes the effects of the current instruction, i.e. data-flows, + /// along normal (non-call, non-return) instructions. Analysis writers are + /// free to inspect the successor instructions, too, as a lookahead. + /// + /// Let instruction_1 := Curr, instruction_2 := Succ, and 0 the + /// tautological lambda fact. + /// + /// The returned flow function implementation f + /// (FlowFunction::computeTargets()) is applied to each data-flow fact d_i + /// that holds before the current statement under analysis. f's return + /// type is a set of (target) facts that have to be generated from the + /// source fact d_i by the data-flow solver. Each combination of input + /// fact d_i (given as an input to f) and respective output facts (f(d_i)) + /// represents an edge that must be "drawn" to construct the exploded + /// supergraph for the analysis problem to be solved. + /// + /// The concrete implementation of f is depending on the analysis problem. + /// In the following, we present a brief, contrived example: + /// + /// f is applied to each data-flow fact d_i that holds before + /// instruction_1. We assume that f is implemented to produce the + /// following outputs. + /// \code + /// f(0) -> {0} // pass the lambda (or zero fact) as identity + /// f(o) -> {o, x} // generate a new fact x from o + /// f(.) -> {.} // pass all other facts that hold before + /// // instruction_1 as identity + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o ... + /// | |\ ... + /// x = instruction_1 o p | | \ ... + /// | | | ... + /// v v v ... + /// 0 o x ... + /// + /// y = instruction_2 q r + /// \endcode + { + FFF.getNormalFlowFunction(Curr, Succ) + } -> std::convertible_to; + + /// + /// Handles call flows: describes the effects of a function call at + /// callInst to the callee target destFun. If a call instruction has + /// multiple callee targets, for instance, because it is an indirect + /// function call that cannot be analyzed precisely in a static manner, + /// the call flow function will be queried for each callee target. + /// + /// This flow function usually handles parameter passing and maps actual + /// to formal parameters. If an analysis writer does not wish to analyze a + /// given callee target they can return a flow function implementation + /// that kills all data-flow facts (e.g. KillAll) such that call is not + /// followed. A commonly used trick to model the effects of functions that + /// are not present (e.g. library functions such as malloc(), free(), + /// etc.) is to kill all facts at the call to the respective target and + /// plugin the semantics in the call-to-return flow function. In the + /// call-to-return flow function, an analysis writer can check if the + /// function of interest is one of the possible targets and then, return a + /// flow function implementation that describes the special semantics of + /// that function call. + /// + /// Let start_point be the starting point of the callee target CalleeFun. + /// + /// The returned flow function implementation f + /// (FlowFunction::computeTargets()) is applied to each data-flow fact d_i + /// that holds right before the CallInst. f's return type is a set of + /// (target) facts that have to be generated from the source fact d_i by + /// the data-flow solver. Each target fact that is generated will hold + /// before start_point. + /// + /// The concrete implementation of f is depending on the analysis problem. + /// In the following, we present a brief, contrived example: + /// + /// f is applied to each data-flow fact d_i that holds before CallInst. We + /// assume that f is implemented to produce the following outputs. + /// \code + /// f(0) -> {0} // pass as identity into the callee target + /// f(o) -> {q} // map actual o into formal q + /// f(p) -> {r} // map actual p into formal r + /// f(.) -> {} // kill all other facts that are not visible to + /// the + /// // callee target + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o p ... + /// \ \ \ ... + /// x = CalleeFun(o, p, ...) \ \ +----------------+ + /// \ +---------------- | + /// +-------------+ + | + /// ... | | | + /// ... | | | + /// 0 o p ... | | | + /// | | | + /// | | | + /// | | | + /// Ty CalleeFun(q, r, ...) | | | + /// v v v + /// 0 q r ... + /// + /// start point + /// \endcode + { + FFF.getCallFlowFunction(Curr, CalleeFun) + } -> std::convertible_to; + + /// + /// Handles return flows: describes the data-flows from an ExitInst to the + /// corresponding RetSite. + /// + /// This flow function usually handles the returned value of the callee + /// target as well as the parameter mapping back to the caller of + /// CalleeFun for pointer parameters as modifications made by CalleeFun + /// are visible to the caller. Data-flow facts that are not returned or + /// escape via function pointer parameters (or global variables) are + /// usually killed. + /// + /// The returned flow function implementation f + /// (FlowFunction::computeTargets()) is applied to each data-flow fact d_i + /// that holds right before the ExitInst. f's return type is a set of + /// (target) facts that have to be generated from the source fact d_i by + /// the data-flow solver. Each target fact that is generated will hold + /// after CallSite. + /// + /// The concrete implementation of f is depending on the analysis problem. + /// In the following, we present a brief, contrived example: + /// + /// f is applied to each data-flow fact d_i that holds before ExitInst. We + /// assume that f is implemented to produce the following outputs. + /// \code + /// f(0) -> {0} // pass as identity into the callee target + /// f(r) -> {x} // map return value to lhs variable at CallSite + /// f(q) -> {o} // map pointer-typed formal q to actual o + /// f(.) -> {} // kill all other facts that are not visible to + /// the + /// // caller + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o ... + /// + /// x = CalleeFun(o, ...) + /// +------------------+ + /// +--|---------------+ | + /// +--|--|------------+ | | + /// v v v ... | | | + /// 0 o x ... | | | + /// | | | + /// | | | + /// | | | + /// Ty CalleeFun(q, ...) | | | + /// | | | + /// 0 q r ... + /// + /// return r + /// \endcode + { + FFF.getRetFlowFunction(CallSite, CalleeFun, ExitInst, RetSite) + } -> std::convertible_to; + + /// + /// Describes the data-flows alongsite a CallSite. + /// + /// This flow function usually passes all data-flow facts that are not + /// involved in the function call alongsite the CallSite. Data-flow facts + /// that are not actual parameters or passed by value, modifications to + /// those within a callee are not visible in the caller context, are + /// mostly passed as identity. The call-to-return flow function may also + /// be used to describe special semantics (cf. getCallFlowFunction()). + /// + /// The returned flow function implementation f + /// (FlowFunction::computeTargets()) is applied to each data-flow fact d_i + /// that holds right before the CallSite. f's return type is a set of + /// (target) facts that have to be generated from the source fact d_i by + /// the data-flow solver. Each target fact that is generated will hold + /// after CallSite. + /// + /// The concrete implementation of f is depending on the analysis problem. + /// In the following, we present a brief, contrived example: + /// + /// f is applied to each data-flow fact d_i that holds before CallSite. We + /// assume that f is implemented to produce the following outputs. + /// \code + /// f(0) -> {0} // pass lambda as identity alongsite the CallSite + /// f(o) -> {o} // assuming that o is passed by value, it is + /// passed + /// // alongsite the CallSite + /// f(p) -> {} // assuming that p is a pointer-typed value, we + /// need + /// // to kill p, as it will be handled by the call- + /// and + /// // return-flow functions + /// f(.) -> {.} // pass everything that is not involved in the + /// call + /// // as identity + /// \endcode + /// The above implementation corresponds to the following edges in the + /// exploded supergraph. + /// \code + /// 0 o ... + /// | | + /// | +-------+ + /// +--------+ | + /// | | + /// x = CalleeFun(o, p, ...) | | + /// | | + /// +--------+ | + /// | +-------+ + /// v v + /// 0 o x ... + /// \endcode + { + FFF.getCallToRetFlowFunction(CallSite, RetSite, Callees) + } -> std::convertible_to; + + /// + /// May be used to encode special sementics of a given callee target + /// (whose call should not be directly followed by the data-flow solver) + /// similar to the getCallFlowFunction() --> getCallToRetFlowFunction() + /// trick (cf. getCallFlowFunction()). + /// + /// The default implementation returns a nullptr to indicate that the + /// mechanism should not be used. + /// + { + FFF.getSummaryFlowFunction(Curr, CalleeFun) + } -> std::convertible_to; + }; + +template +concept UnbalancedRetSideEffectProvider = requires( + T &FFF, typename T::n_t ExitInst, typename T::n_t CallSite, + typename T::f_t CalleeFun, typename T::d_t Source) { + /// Performs any side-effects of a return-flow-function + /// + /// In case of unbalanced returns (if the option `followReturnsPastSeeds` is + /// activated in the IfdsIdeSolverConfig), we will eventually reach a function + /// that is not called from other functions. Still, we may want to apply a + /// return-flow-function -- just for its side-effects, such as registering a + /// taint + { + FFF.applyUnbalancedRetFlowFunctionSideEffects(CalleeFun, ExitInst, Source) + }; +}; + } // namespace psr #endif diff --git a/include/phasar/DataFlow/IfdsIde/GenericFlowFunction.h b/include/phasar/DataFlow/IfdsIde/GenericFlowFunction.h index a7c9a99124..021d10f2f4 100644 --- a/include/phasar/DataFlow/IfdsIde/GenericFlowFunction.h +++ b/include/phasar/DataFlow/IfdsIde/GenericFlowFunction.h @@ -93,11 +93,6 @@ class GenericFlowFunction { [[nodiscard]] bool operator==(std::nullptr_t) const noexcept { return FF == nullptr; } - [[nodiscard]] bool - operator!=(GenericFlowFunctionView Other) const noexcept { - return !(*this == Other); - } - [[nodiscard]] bool operator!=(std::nullptr_t) const noexcept { return FF; } private: FlowFunctionPtrType FF; diff --git a/include/phasar/DataFlow/IfdsIde/IDEProblem.h b/include/phasar/DataFlow/IfdsIde/IDEProblem.h new file mode 100644 index 0000000000..1211b62b2b --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/IDEProblem.h @@ -0,0 +1,30 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" +#include "phasar/DataFlow/IfdsIde/IFDSProblem.h" +#include "phasar/Utils/JoinLattice.h" +#include "phasar/Utils/SemiRing.h" + +namespace psr { +template +concept IDEProblem = + IFDSProblem && // + EdgeFunctionFactory && // + IsJoinLattice && // + IsSemiRing && // + requires { + requires IdeAnalysisDomain; + requires std::same_as; + requires std::same_as; + }; +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/IDEProblemWrapper.h b/include/phasar/DataFlow/IfdsIde/IDEProblemWrapper.h new file mode 100644 index 0000000000..f2d69f0732 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/IDEProblemWrapper.h @@ -0,0 +1,150 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DataFlow/IfdsIde/EdgeFunction.h" +#include "phasar/DataFlow/IfdsIde/IDEProblem.h" +#include "phasar/DataFlow/IfdsIde/IFDSProblemWrapper.h" +#include "phasar/DataFlow/IfdsIde/IfdsToIdeProblemAdapter.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/SemiRing.h" + +#include "llvm/ADT/ArrayRef.h" + +namespace psr { + +/// \brief A simple wrapper over a pointer to an IFDS/IDE problem. +/// +/// Useful for adding intermediate layers, e.g., IFDS->IDE translation, caching, +/// etc. +template +class IDEProblemWrapper : public IFDSProblemWrapper { + using base_t = IFDSProblemWrapper; + +public: + using ProblemAnalysisDomain = typename ProblemTy::ProblemAnalysisDomain; + using d_t = typename ProblemAnalysisDomain::d_t; + using n_t = typename ProblemAnalysisDomain::n_t; + using f_t = typename ProblemAnalysisDomain::f_t; + using t_t = typename ProblemAnalysisDomain::t_t; + using v_t = typename ProblemAnalysisDomain::v_t; + using l_t = typename ProblemAnalysisDomain::l_t; + using i_t = typename ProblemAnalysisDomain::i_t; + using db_t = typename ProblemAnalysisDomain::db_t; + + using base_t::base_t; + + // --- EdgeFunctionFactory: + + using EdgeFunctionType = typename ProblemTy::EdgeFunctionType; + + [[nodiscard]] constexpr decltype(auto) + getNormalEdgeFunction(ByConstRef Curr, ByConstRef CurrNode, + ByConstRef Succ, ByConstRef SuccNode) { + return this->Problem->getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode); + } + + [[nodiscard]] constexpr decltype(auto) + getCallEdgeFunction(ByConstRef CallSite, ByConstRef CSNode, + ByConstRef CalleeFun, ByConstRef CalleeNode) { + return this->Problem->getCallEdgeFunction(CallSite, CSNode, CalleeFun, + CalleeNode); + } + + [[nodiscard]] constexpr decltype(auto) + getReturnEdgeFunction(ByConstRef CallSite, ByConstRef CalleeFun, + ByConstRef ExitInst, ByConstRef ExitNode, + ByConstRef RetSite, ByConstRef RSNode) { + return this->Problem->getReturnEdgeFunction(CallSite, CalleeFun, ExitInst, + ExitNode, RetSite, RSNode); + } + + [[nodiscard]] constexpr decltype(auto) + getCallToRetEdgeFunction(ByConstRef CallSite, ByConstRef CSNode, + ByConstRef RetSite, ByConstRef RSNode, + llvm::ArrayRef Callees) { + return this->Problem->getCallToRetEdgeFunction(CallSite, CSNode, RetSite, + RSNode, Callees); + } + + [[nodiscard]] constexpr decltype(auto) + getSummaryEdgeFunction(ByConstRef Curr, ByConstRef CurrNode, + ByConstRef Succ, ByConstRef SuccNode) { + return this->Problem->getSummaryEdgeFunction(Curr, CurrNode, Succ, + SuccNode); + } + + // --- IsJoinLattice: + + [[nodiscard]] constexpr decltype(auto) topElement() { + return this->Problem->topElement(); + } + + [[nodiscard]] constexpr decltype(auto) bottomElement() { + return this->Problem->bottomElement(); + } + + [[nodiscard]] constexpr decltype(auto) join(auto &&L, auto &&R) { + return this->Problem->join(PSR_FWD(L), PSR_FWD(R)); + } + + // --- IsSemiRing: + + [[nodiscard]] constexpr decltype(auto) + extend(IsEdgeFunctionFor auto &&First, + IsEdgeFunctionFor auto &&Second) { + return this->Problem->extend(PSR_FWD(First), PSR_FWD(Second)); + } + + [[nodiscard]] constexpr decltype(auto) + combine(IsEdgeFunctionFor auto &&First, + IsEdgeFunctionFor auto &&Second) { + return this->Problem->combine(PSR_FWD(First), PSR_FWD(Second)); + } + + [[nodiscard]] constexpr decltype(auto) identity() { + return this->Problem->identity(); + } + + [[nodiscard]] constexpr decltype(auto) allTopFunction() + requires HasAllTopFunction + { + return this->Problem->allTopFunction(); + } +}; + +template +class IfdsIdeProblemWrapper + : public IfdsToIdeProblemAdapter> { +public: + using IfdsToIdeProblemAdapter< + IFDSProblemWrapper>::IfdsToIdeProblemAdapter; + + using IDEProblemTy = IfdsIdeProblemWrapper; + + [[nodiscard]] constexpr IDEProblem auto &ideProblem() noexcept { + return *this; + } +}; + +template +class IfdsIdeProblemWrapper : public IDEProblemWrapper { +public: + using IDEProblemWrapper::IDEProblemWrapper; + + using IDEProblemTy = ProblemTy; + + [[nodiscard]] constexpr IDEProblem auto &ideProblem() noexcept { + return this->base(); + } +}; + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/IDETabulationProblem.h b/include/phasar/DataFlow/IfdsIde/IDETabulationProblem.h index 80f80f2e6d..cdbacbe796 100644 --- a/include/phasar/DataFlow/IfdsIde/IDETabulationProblem.h +++ b/include/phasar/DataFlow/IfdsIde/IDETabulationProblem.h @@ -14,6 +14,7 @@ #include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" #include "phasar/DataFlow/IfdsIde/EntryPointUtils.h" #include "phasar/DataFlow/IfdsIde/FlowFunctions.h" +#include "phasar/DataFlow/IfdsIde/IDEProblem.h" #include "phasar/DataFlow/IfdsIde/IFDSIDESolverConfig.h" #include "phasar/DataFlow/IfdsIde/IfdsIdeDomain.h" #include "phasar/DataFlow/IfdsIde/InitialSeeds.h" @@ -40,24 +41,6 @@ namespace psr { struct HasNoConfigurationType; -template class AllTopFnProvider { -public: - virtual ~AllTopFnProvider() = default; - /// Returns an edge function that represents the top element of the analysis. - virtual EdgeFunction allTopFunction() = 0; -}; - -template - requires HasJoinLatticeTraits -class AllTopFnProvider { -public: - virtual ~AllTopFnProvider() = default; - /// Returns an edge function that represents the top element of the analysis. - virtual EdgeFunction allTopFunction() { - return AllTop{}; - } -}; - /// \brief The analysis problem interface for IDE problems (solvable by the /// IDESolver). Create a subclass from this and override all pure-virtual /// functions to create your own IDE analysis. @@ -69,8 +52,7 @@ template , public EdgeFunctions, public JoinLattice, - public SemiRing, - public AllTopFnProvider { + public SemiRing { public: using ProblemAnalysisDomain = AnalysisDomainTy; using d_t = typename AnalysisDomainTy::d_t; @@ -104,6 +86,8 @@ class IDETabulationProblem : public FlowFunctions, Printer(std::make_unique::type>()) { assert(IRDB != nullptr); + + static_assert(IDEProblem); } IDETabulationProblem(IDETabulationProblem &&) noexcept = default; diff --git a/include/phasar/DataFlow/IfdsIde/IFDSIDESolverConfig.h b/include/phasar/DataFlow/IfdsIde/IFDSIDESolverConfig.h index 8f2046fcb0..a951232f43 100644 --- a/include/phasar/DataFlow/IfdsIde/IFDSIDESolverConfig.h +++ b/include/phasar/DataFlow/IfdsIde/IFDSIDESolverConfig.h @@ -40,45 +40,71 @@ enum class SolverConfigOptions : uint32_t { }; /// \brief Configuration options for the solving process of IFDS/IDE problems -struct IFDSIDESolverConfig { - IFDSIDESolverConfig() noexcept = default; - IFDSIDESolverConfig(SolverConfigOptions Options) noexcept; +class IFDSIDESolverConfig { +public: + constexpr IFDSIDESolverConfig() noexcept = default; + constexpr IFDSIDESolverConfig(SolverConfigOptions Options) noexcept + : Options(Options) {} /// Returns whether the solver should handle unbalanced returns (default: /// false) - [[nodiscard]] bool followReturnsPastSeeds() const; + [[nodiscard]] constexpr bool followReturnsPastSeeds() const noexcept { + return hasFlag(Options, SolverConfigOptions::FollowReturnsPastSeeds); + } /// Returns whether the solver should automatically insert an identityFlow /// propagation for the special zero value (default: true) - [[nodiscard]] bool autoAddZero() const; + [[nodiscard]] constexpr bool autoAddZero() const noexcept { + return hasFlag(Options, SolverConfigOptions::AutoAddZero); + } /// Returns whether the IDE solver should perform IDE's phase 2 (default: /// true). You may want to turn this off for IFDS analyses. - [[nodiscard]] bool computeValues() const; + [[nodiscard]] constexpr bool computeValues() const noexcept { + return hasFlag(Options, SolverConfigOptions::ComputeValues); + } /// Returns, whether the solver should record all ESG edges (default: false) /// \note This option may severly hurt the solver's performance - [[nodiscard]] bool recordEdges() const; + [[nodiscard]] constexpr bool recordEdges() const noexcept { + return hasFlag(Options, SolverConfigOptions::RecordEdges); + } /// Returns, whether the solver should emit the ESG as DOT graph on the /// command-line (default: false) - [[nodiscard]] bool emitESG() const; + [[nodiscard]] constexpr bool emitESG() const noexcept { + return hasFlag(Options, SolverConfigOptions::EmitESG); + } /// Currently unused - [[nodiscard]] bool computePersistedSummaries() const; + [[nodiscard]] constexpr bool computePersistedSummaries() const noexcept { + return hasFlag(Options, SolverConfigOptions::ComputePersistedSummaries); + } /// \see followReturnsPastSeeds - void setFollowReturnsPastSeeds(bool Set = true); + constexpr void setFollowReturnsPastSeeds(bool Set = true) { + setFlag(Options, SolverConfigOptions::FollowReturnsPastSeeds, Set); + } /// \see autoAddZero - void setAutoAddZero(bool Set = true); + constexpr void setAutoAddZero(bool Set = true) { + setFlag(Options, SolverConfigOptions::AutoAddZero, Set); + } /// \see computeValues - void setComputeValues(bool Set = true); + constexpr void setComputeValues(bool Set = true) { + setFlag(Options, SolverConfigOptions::ComputeValues, Set); + } /// \see recordEdges - void setRecordEdges(bool Set = true); + constexpr void setRecordEdges(bool Set = true) { + setFlag(Options, SolverConfigOptions::RecordEdges, Set); + } /// \see emitESG - void setEmitESG(bool Set = true); + constexpr void setEmitESG(bool Set = true) { + setFlag(Options, SolverConfigOptions::EmitESG, Set); + } /// \see computePersistedSummaries - void setComputePersistedSummaries(bool Set = true); + constexpr void setComputePersistedSummaries(bool Set = true) { + setFlag(Options, SolverConfigOptions::ComputePersistedSummaries, Set); + } - void setConfig(SolverConfigOptions Opt); + constexpr void setConfig(SolverConfigOptions Opt) { Options = Opt; } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const IFDSIDESolverConfig &SC); + IFDSIDESolverConfig SC); private: SolverConfigOptions Options = diff --git a/include/phasar/DataFlow/IfdsIde/IFDSProblem.h b/include/phasar/DataFlow/IfdsIde/IFDSProblem.h new file mode 100644 index 0000000000..0a3c75a545 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/IFDSProblem.h @@ -0,0 +1,57 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DB/ProjectIRDB.h" +#include "phasar/DataFlow/IfdsIde/FlowFunctions.h" +#include "phasar/DataFlow/IfdsIde/IFDSIDESolverConfig.h" +#include "phasar/DataFlow/IfdsIde/IfdsIdeDomain.h" +#include "phasar/DataFlow/IfdsIde/InitialSeeds.h" +#include "phasar/Domain/AnalysisDomain.h" +#include "phasar/Utils/TypeTraits.h" + +#include + +namespace psr { +template +concept IFDSProblem = + FlowFunctionFactory && + requires(T &P, const T &CP, + typename T::ProblemAnalysisDomain::d_t FlowFact) { + typename T::ProblemAnalysisDomain; + requires IfdsAnalysisDomain; + + { CP.isZeroValue(FlowFact) } -> std::convertible_to; + { + CP.getZeroValue() + } -> std::convertible_to; + + { + P.initialSeeds() + } -> std::convertible_to< + InitialSeeds::l_t>>; + + { CP.getProjectIRDB() } -> ProjectIRDBConstPtr; + { CP.getEntryPoints() } -> is_iterable_over_v; + }; + +template +inline IFDSIDESolverConfig getProblemSolverConfig(ProblemTy &Problem) noexcept { + if constexpr (requires { Problem.getIFDSIDESolverConfig(); }) { + return Problem.getIFDSIDESolverConfig(); + } else { + return IFDSIDESolverConfig{}; + } +} + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/IFDSProblemWrapper.h b/include/phasar/DataFlow/IfdsIde/IFDSProblemWrapper.h new file mode 100644 index 0000000000..dc5d7920a0 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/IFDSProblemWrapper.h @@ -0,0 +1,103 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DB/ProjectIRDB.h" +#include "phasar/DataFlow/IfdsIde/IFDSProblem.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/NonNullPtr.h" + +#include "llvm/ADT/ArrayRef.h" + +namespace psr { + +/// \brief A simple wrapper over a pointer to an IFDS problem. +/// +template class IFDSProblemWrapper { +public: + using ProblemAnalysisDomain = typename ProblemTy::ProblemAnalysisDomain; + using d_t = typename ProblemAnalysisDomain::d_t; + using n_t = typename ProblemAnalysisDomain::n_t; + using f_t = typename ProblemAnalysisDomain::f_t; + using t_t = typename ProblemAnalysisDomain::t_t; + using v_t = typename ProblemAnalysisDomain::v_t; + using i_t = typename ProblemAnalysisDomain::i_t; + using db_t = typename ProblemAnalysisDomain::db_t; + + constexpr IFDSProblemWrapper(NonNullPtr Problem) noexcept + : Problem(Problem) {} + constexpr IFDSProblemWrapper(ProblemTy *Problem) noexcept + : Problem(Problem) {} + + // --- IFDSProblem: + + [[nodiscard]] constexpr bool isZeroValue(ByConstRef Fact) const { + return Problem->isZeroValue(Fact); + } + + [[nodiscard]] constexpr decltype(auto) getZeroValue() const { + return Problem->getZeroValue(); + } + + [[nodiscard]] constexpr decltype(auto) initialSeeds() { + return Problem->initialSeeds(); + } + + [[nodiscard]] constexpr ProjectIRDBConstPtr auto getProjectIRDB() const { + return Problem->getProjectIRDB(); + } + + [[nodiscard]] constexpr decltype(auto) getEntryPoints() const { + return Problem->getEntryPoints(); + } + + // --- FlowFunctionFactory: + + using container_type = typename ProblemTy::container_type; + using FlowFunctionPtrType = typename ProblemTy::FlowFunctionPtrType; + + [[nodiscard]] constexpr decltype(auto) + getNormalFlowFunction(ByConstRef Curr, ByConstRef Succ) { + return Problem->getNormalFlowFunction(Curr, Succ); + } + + [[nodiscard]] constexpr decltype(auto) + getCallFlowFunction(ByConstRef Curr, ByConstRef CalleeFun) { + return Problem->getCallFlowFunction(Curr, CalleeFun); + } + + [[nodiscard]] constexpr decltype(auto) + getRetFlowFunction(ByConstRef CallSite, ByConstRef CalleeFun, + ByConstRef ExitInst, ByConstRef RetSite) { + return Problem->getRetFlowFunction(CallSite, CalleeFun, ExitInst, RetSite); + } + + [[nodiscard]] constexpr decltype(auto) + getCallToRetFlowFunction(ByConstRef CallSite, ByConstRef RetSite, + llvm::ArrayRef Callees) { + return Problem->getCallToRetFlowFunction(CallSite, RetSite, Callees); + } + + [[nodiscard]] constexpr decltype(auto) + getSummaryFlowFunction(ByConstRef Curr, ByConstRef CalleeFun) { + return Problem->getSummaryFlowFunction(Curr, CalleeFun); + } + + // --- + + /// The wrapped problem + [[nodiscard]] auto &base() noexcept { return *Problem; } + [[nodiscard]] const auto &base() const noexcept { return *Problem; } + +protected: + NonNullPtr Problem; +}; + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/IfdsIdeDomain.h b/include/phasar/DataFlow/IfdsIde/IfdsIdeDomain.h index bb48c770f7..4b27a33cb7 100644 --- a/include/phasar/DataFlow/IfdsIde/IfdsIdeDomain.h +++ b/include/phasar/DataFlow/IfdsIde/IfdsIdeDomain.h @@ -28,16 +28,16 @@ concept IsEdgeValue = std::is_move_constructible_v && psr::IsEqualityComparable; template -concept IfdsAnalysisDomain = IsAnalysisDomain && requires() { +concept IfdsAnalysisDomain = IsAnalysisDomain && requires { typename T::d_t; typename T::i_t; requires IsDataFlowFact; - requires ICFG; + // requires ICFG; }; template -concept IdeAnalysisDomain = IfdsAnalysisDomain && requires() { +concept IdeAnalysisDomain = IfdsAnalysisDomain && requires { typename T::l_t; requires IsEdgeValue; }; diff --git a/include/phasar/DataFlow/IfdsIde/IfdsIdeProblemMixin.h b/include/phasar/DataFlow/IfdsIde/IfdsIdeProblemMixin.h new file mode 100644 index 0000000000..9c5d2120ff --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/IfdsIdeProblemMixin.h @@ -0,0 +1,135 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DataFlow/IfdsIde/EntryPointUtils.h" +#include "phasar/DataFlow/IfdsIde/FlowFunctions.h" +#include "phasar/DataFlow/IfdsIde/IfdsIdeDomain.h" +#include "phasar/DataFlow/IfdsIde/InitialSeeds.h" +#include "phasar/Domain/AnalysisDomain.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/JoinLattice.h" +#include "phasar/Utils/NonNullPtr.h" +#include "phasar/Utils/SemiRing.h" + +#include + +namespace psr { + +/// \brief A template that lets you inherit recurring APIs into IFDS/IDE +/// problems (solvable by the IterativeIDESolver or the legacy +/// IFDSSolver/IDESolver). Create a subclass from this and implement all +/// missing functions to create your own IFDS/IDE analysis. +/// +/// For more information on how to write an IFDS/IDE analysis see +/// - [Writing an IFDS +/// Analysis](https://github.com/secure-software-engineering/phasar/wiki/Writing-an-IFDS-analysis). +/// - [Writing an IDE +/// Analysis](https://github.com/secure-software-engineering/phasar/wiki/Writing-an-IDE-analysis) +template > +class IfdsIdeProblemMixin + : protected FlowFunctionTemplates, + public DerivedJoinLattice< + typename detail::ValueDomainAdder::l_t>, + public DefaultSemiRing< + typename detail::ValueDomainAdder::l_t>, + public detail::ValueDomainAdder { +protected: + using FFTemplates = + FlowFunctionTemplates; + +public: + using ProblemAnalysisDomain = detail::ValueDomainAdder; + using typename FFTemplates::container_type; + using typename FFTemplates::FlowFunctionPtrType; + using typename ProblemAnalysisDomain::d_t; + using typename ProblemAnalysisDomain::db_t; + using typename ProblemAnalysisDomain::f_t; + using typename ProblemAnalysisDomain::i_t; + using typename ProblemAnalysisDomain::l_t; + using typename ProblemAnalysisDomain::n_t; + using typename ProblemAnalysisDomain::t_t; + using typename ProblemAnalysisDomain::v_t; + using EdgeFunctionType = EdgeFunction; + + [[nodiscard]] constexpr ByConstRef getZeroValue() const noexcept { + return ZeroValue; + } + + [[nodiscard]] constexpr bool + isZeroValue(ByConstRef Fact) const noexcept { + return Fact == ZeroValue; + } + + [[nodiscard]] LLVM_ATTRIBUTE_RETURNS_NONNULL constexpr const auto * + getProjectIRDB() const noexcept { + return IRDB.get(); + } + + [[nodiscard]] constexpr const auto &getEntryPoints() const noexcept { + return EntryPoints; + } + + [[nodiscard]] auto getSummaryFlowFunction(n_t /*CallSite*/, f_t /*DestFun*/) { + return nullptr; + } + + [[nodiscard]] auto getSummaryEdgeFunction(n_t /*Curr*/, + ByConstRef /*CurrNode*/, + n_t /*Succ*/, + ByConstRef /*SuccNode*/) { + return EdgeIdentity{}; + } + +protected: + constexpr IfdsIdeProblemMixin(NonNullPtr IRDB, + std::vector EntryPoints, + d_t ZeroValue) + : IRDB(IRDB), ZeroValue(std::move(ZeroValue)), + EntryPoints(std::move(EntryPoints)) {} + + typename FlowFunctions::FlowFunctionPtrType + generateFromZero(d_t FactToGenerate) { + return FFTemplates::generateFlow(std::move(FactToGenerate), getZeroValue()); + } + + /// Seeds that just start with ZeroValue and bottomElement() at the starting + /// points of each EntryPoint function. + /// Takes the __ALL__ EntryPoint into account. + [[nodiscard]] static InitialSeeds + createDefaultSeeds(auto &&Self) + requires std::is_nothrow_default_constructible_v< + typename AnalysisDomainTy::c_t> + { + static_assert(std::derived_from, + IfdsIdeProblemMixin>); + InitialSeeds Seeds; + typename AnalysisDomainTy::c_t C{}; + + addSeedsForStartingPoints(Self.getEntryPoints(), Self.getProjectIRDB(), C, + Seeds, Self.getZeroValue(), Self.bottomElement()); + + return Seeds; + } + +#if __cpp_explicit_this_parameter >= 202110L + [[nodiscard]] InitialSeeds + createDefaultSeeds(this auto &&Self) { + return createDefaultSeeds(PSR_FWD(Self)); + } +#endif + + NonNullPtr IRDB{}; + d_t ZeroValue{}; + std::vector EntryPoints; +}; +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/IfdsToIdeProblemAdapter.h b/include/phasar/DataFlow/IfdsIde/IfdsToIdeProblemAdapter.h new file mode 100644 index 0000000000..785dab2a38 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/IfdsToIdeProblemAdapter.h @@ -0,0 +1,98 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" +#include "phasar/Domain/BinaryDomain.h" + +#include + +namespace psr { +template class IfdsToIdeProblemAdapter : public Base { +public: + using Base::Base; + + using typename Base::d_t; + using typename Base::f_t; + using typename Base::n_t; + + using l_t = BinaryDomain; + using EdgeFunctionType = EdgeIdentity; + + [[nodiscard]] constexpr decltype(auto) + getNormalEdgeFunction(ByConstRef /*Curr*/, ByConstRef /*CurrNode*/, + ByConstRef /*Succ*/, + ByConstRef /*SuccNode*/) noexcept { + return EdgeFunctionType{}; + } + + [[nodiscard]] constexpr decltype(auto) + getCallEdgeFunction(ByConstRef /*CallSite*/, ByConstRef /*CSNode*/, + ByConstRef /*CalleeFun*/, + ByConstRef /*CalleeNode*/) noexcept { + return EdgeFunctionType{}; + } + + [[nodiscard]] constexpr decltype(auto) getReturnEdgeFunction( + ByConstRef /*CallSite*/, ByConstRef /*CalleeFun*/, + ByConstRef /*ExitInst*/, ByConstRef /*ExitNode*/, + ByConstRef /*RetSite*/, ByConstRef /*RSNode*/) noexcept { + return EdgeFunctionType{}; + } + + [[nodiscard]] constexpr decltype(auto) getCallToRetEdgeFunction( + ByConstRef /*CallSite*/, ByConstRef /*CSNode*/, + ByConstRef /*RetSite*/, ByConstRef /*RSNode*/, + llvm::ArrayRef /*Callees*/) noexcept { + return EdgeFunctionType{}; + } + + [[nodiscard]] constexpr decltype(auto) + getSummaryEdgeFunction(ByConstRef /*Curr*/, ByConstRef /*CurrNode*/, + ByConstRef /*Succ*/, + ByConstRef /*SuccNode*/) noexcept { + return EdgeFunctionType{}; + } + + // --- IsJoinLattice: + + [[nodiscard]] constexpr auto topElement() noexcept { + return std::integral_constant{}; + } + + [[nodiscard]] constexpr auto bottomElement() noexcept { + return std::integral_constant{}; + } + + [[nodiscard]] constexpr l_t join(l_t L, l_t R) noexcept { + if (L != R) { + return bottomElement(); + } + return L; + } + + // --- IsSemiRing: + + [[nodiscard]] constexpr auto extend(EdgeFunctionType /*First*/, + EdgeFunctionType /*Second*/) noexcept { + return EdgeFunctionType{}; + } + + [[nodiscard]] constexpr auto combine(EdgeFunctionType /*First*/, + EdgeFunctionType /*Second*/) noexcept { + return EdgeFunctionType{}; + } + + [[nodiscard]] constexpr auto identity() noexcept { + return EdgeFunctionType{}; + } +}; + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/LegacyIDEProblemWrapper.h b/include/phasar/DataFlow/IfdsIde/LegacyIDEProblemWrapper.h new file mode 100644 index 0000000000..1cf4824e4d --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/LegacyIDEProblemWrapper.h @@ -0,0 +1,255 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" +#include "phasar/DataFlow/IfdsIde/IDEProblem.h" +#include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/IFDSProblem.h" +#include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/InitialSeeds.h" +#include "phasar/Utils/JoinLattice.h" +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/NonNullPtr.h" +#include "phasar/Utils/PointerUtils.h" +#include "phasar/Utils/SemiRing.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" + +#include + +namespace psr { + +namespace detail { +/// \brief Holds everything that is common to both LegacyIDEProblemWrapper +/// specializations: the wrapped Problem pointer, the IFDSProblem/ +/// FlowFunctionFactory forwarding, and the legacy-base bookkeeping +/// (entry-points, solver-config, reports, soundness). +template +class LegacyIDEProblemWrapperBase : public LegacyBaseTy { +protected: + using base_t = LegacyBaseTy; + + static std::vector makeEntryVec(auto &&Range) { + if constexpr (std::is_convertible_v>) { + return PSR_FWD(Range); + } else { + return std::vector(llvm::adl_begin(Range), + llvm::adl_end(Range)); + } + } + +public: + using typename base_t::container_type; + using typename base_t::d_t; + using typename base_t::db_t; + using typename base_t::f_t; + using typename base_t::FlowFunctionPtrType; + using typename base_t::i_t; + using typename base_t::l_t; + using typename base_t::n_t; + using typename base_t::ProblemAnalysisDomain; + using typename base_t::t_t; + using typename base_t::v_t; + + LegacyIDEProblemWrapperBase(NonNullPtr Problem) noexcept + : base_t(getPointerFrom(Problem->getProjectIRDB()), + makeEntryVec(Problem->getEntryPoints()), + Problem->getZeroValue()), + Problem(Problem) { + this->getIFDSIDESolverConfig() = getProblemSolverConfig(*Problem); + } + + LegacyIDEProblemWrapperBase(ProblemTy *Problem) noexcept + : LegacyIDEProblemWrapperBase(NonNullPtr(Problem)) {} + + // --- IFDSProblem: + + [[nodiscard]] bool isZeroValue(d_t Fact) const noexcept final { + return Problem->isZeroValue(Fact); + } + + [[nodiscard]] InitialSeeds initialSeeds() final { + return Problem->initialSeeds(); + } + + void emitTextReport(GenericSolverResults Results, + llvm::raw_ostream &OS = llvm::outs()) final { + if constexpr (requires { Problem->emitTextReport(Results, OS); }) { + Problem->emitTextReport(Results, OS); + } else { + this->base_t::emitTextReport(Results, OS); + } + } + + void emitGraphicalReport(GenericSolverResults Results, + llvm::raw_ostream &OS = llvm::outs()) final { + if constexpr (requires { Problem->emitGraphicalReport(Results, OS); }) { + Problem->emitGraphicalReport(Results, OS); + } else { + this->base_t::emitGraphicalReport(Results, OS); + } + } + + bool setSoundness(Soundness S) final { + if constexpr (requires { Problem->setSoundness(S); }) { + return Problem->setSoundness(S); + } else { + return false; + } + } + + // --- FlowFunctionFactory: + + [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, + n_t Succ) final { + return Problem->getNormalFlowFunction(Curr, Succ); + } + + [[nodiscard]] FlowFunctionPtrType getCallFlowFunction(n_t Curr, + f_t CalleeFun) final { + return Problem->getCallFlowFunction(Curr, CalleeFun); + } + + [[nodiscard]] FlowFunctionPtrType getRetFlowFunction(n_t CallSite, + f_t CalleeFun, + n_t ExitInst, + n_t RetSite) final { + return Problem->getRetFlowFunction(CallSite, CalleeFun, ExitInst, RetSite); + } + + [[nodiscard]] FlowFunctionPtrType + getCallToRetFlowFunction(n_t CallSite, n_t RetSite, + llvm::ArrayRef Callees) final { + return Problem->getCallToRetFlowFunction(CallSite, RetSite, Callees); + } + + [[nodiscard]] FlowFunctionPtrType + getSummaryFlowFunction(n_t Curr, f_t CalleeFun) final { + return Problem->getSummaryFlowFunction(Curr, CalleeFun); + } + +protected: + NonNullPtr Problem; +}; +} // namespace detail + +/// \brief A simple wrapper over a pointer to an IFDS/IDE problem, implementing +/// the legacy IDETabulationProblem +template +class LegacyIDEProblemWrapper + : public detail::LegacyIDEProblemWrapperBase< + ProblemTy, + IFDSTabulationProblem> { + using base_t = detail::LegacyIDEProblemWrapperBase< + ProblemTy, + IFDSTabulationProblem>; + +public: + using base_t::base_t; +}; + +/// \brief A simple wrapper over a pointer to an IFDS/IDE problem, implementing +/// the legacy IDETabulationProblem +template +class LegacyIDEProblemWrapper + : public detail::LegacyIDEProblemWrapperBase< + ProblemTy, + IDETabulationProblem> { + using base_t = detail::LegacyIDEProblemWrapperBase< + ProblemTy, IDETabulationProblem>; + +public: + using base_t::base_t; + using base_t::Problem; + using typename base_t::d_t; + using typename base_t::f_t; + using typename base_t::l_t; + using typename base_t::n_t; + + // --- EdgeFunctionFactory: + + using EdgeFunctionType = EdgeFunction; + + [[nodiscard]] EdgeFunctionType + getNormalEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, d_t SuccNode) final { + return Problem->getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode); + } + + [[nodiscard]] EdgeFunctionType getCallEdgeFunction(n_t CallSite, d_t CSNode, + f_t CalleeFun, + d_t CalleeNode) final { + return Problem->getCallEdgeFunction(CallSite, CSNode, CalleeFun, + CalleeNode); + } + + [[nodiscard]] EdgeFunctionType + getReturnEdgeFunction(n_t CallSite, f_t CalleeFun, n_t ExitInst, d_t ExitNode, + n_t RetSite, d_t RSNode) final { + return Problem->getReturnEdgeFunction(CallSite, CalleeFun, ExitInst, + ExitNode, RetSite, RSNode); + } + + [[nodiscard]] EdgeFunctionType + getCallToRetEdgeFunction(n_t CallSite, d_t CSNode, n_t RetSite, d_t RSNode, + llvm::ArrayRef Callees) final { + return Problem->getCallToRetEdgeFunction(CallSite, CSNode, RetSite, RSNode, + Callees); + } + + [[nodiscard]] EdgeFunctionType + getSummaryEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, d_t SuccNode) final { + return Problem->getSummaryEdgeFunction(Curr, CurrNode, Succ, SuccNode); + } + + // --- IsJoinLattice: + + [[nodiscard]] l_t topElement() final { return Problem->topElement(); } + + [[nodiscard]] l_t bottomElement() final { return Problem->bottomElement(); } + + [[nodiscard]] l_t join(l_t L, l_t R) final { + return Problem->join(std::move(L), std::move(R)); + } + + // --- IsSemiRing: + + [[nodiscard]] EdgeFunctionType extend(const EdgeFunctionType &First, + const EdgeFunctionType &Second) final { + return Problem->extend(First, Second); + } + + [[nodiscard]] EdgeFunctionType combine(const EdgeFunctionType &First, + const EdgeFunctionType &Second) final { + return Problem->combine(First, Second); + } + + [[nodiscard]] EdgeFunctionType identity() final { + return Problem->identity(); + } + + [[nodiscard]] EdgeFunctionType allTopFunction() final { + if constexpr (HasAllTopFunction) { + return Problem->allTopFunction(); + } else if constexpr (HasJoinLatticeTraits) { + return AllTop{}; + } else { + return AllTop{Problem->topElement()}; + } + } +}; + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/DFIExplodedSupergraph.h b/include/phasar/DataFlow/IfdsIde/Solver/DFIExplodedSupergraph.h new file mode 100644 index 0000000000..a10c451760 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/DFIExplodedSupergraph.h @@ -0,0 +1,265 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DataFlow/IfdsIde/Solver/DFISolverResults.h" +#include "phasar/Utils/BitSet.h" +#include "phasar/Utils/CsrGraph.h" +#include "phasar/Utils/FunctionId.h" +#include "phasar/Utils/IntervalReachability.h" +#include "phasar/Utils/SCCGeneric.h" +#include "phasar/Utils/SCCId.h" +#include "phasar/Utils/StrongTypeDef.h" +#include "phasar/Utils/TypedVector.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/SmallVector.h" + +#include +#include +#include +#include +#include + +/// A function entry reached by at least one call +PHASAR_STRONG_TYPEDEF(psr::dfi, uint32_t, EntryId); +/// Index of a vertex among the vertices of its function +PHASAR_STRONG_TYPEDEF(psr::dfi, uint32_t, LocalVertexId); + +namespace psr::detail { + +/// The exploded supergraph under construction by the DFISolver. +/// +/// Works on ids only; evaluating flow functions is up to the solver. Tracks +/// which vertices still need to be expanded and which functions need to be +/// summarized, and schedules this work callees first. +class DFIExplodedSupergraph { +public: + enum class ActionKind { Expand, Summarize }; + + struct Action { + FunctionId Fun{}; + ActionKind Kind{}; + }; + + /// \param CGSCCs The SCCs of the call-graph in reverse topological order, + /// as computed by computeCGSCCs() + /// \param FollowReturnsPastSeeds Whether summarize() reports exits that are + /// reachable without a calling context, see + /// IFDSIDESolverConfig::followReturnsPastSeeds() + explicit DFIExplodedSupergraph(SCCHolder CGSCCs, + bool FollowReturnsPastSeeds); + + /// The next step towards the fixpoint, or std::nullopt if there is none. + /// Prefers callees over callers, and expansion over summarization. + [[nodiscard]] std::optional nextAction(); + + /// Returns the vertex . New vertices are scheduled for + /// expansion. + dfi::VertexId getOrCreateVertex(dfi::InstId Inst, dfi::FactId Fact, + FunctionId Fun, bool IsExit); + + /// Marks Vtx as seed. Returns false, if it was a seed already. + bool addSeed(dfi::VertexId Vtx); + + /// Marks the vertex Vtx, which must be at a start point, as function entry. + dfi::EntryId getOrCreateEntry(dfi::VertexId Vtx); + + /// Removes the vertices scheduled for expansion in Fun, sorted by + /// instruction. The result is valid until the next call of this function. + [[nodiscard]] llvm::ArrayRef takeWorklist(FunctionId Fun); + + /// Records the intra-procedural out-edges of Vtx: call beginEdges(Vtx), then + /// addEdge() for each target, then endEdges(Vtx). Vtx must not be expanded + /// before. Creating vertices in between is fine. + void beginEdges(dfi::VertexId Vtx) { + assert(VtxEdgeBegin[Vtx] == UINT32_MAX && + "beginEdges() called before for Vtx!"); + VtxEdgeBegin[Vtx] = uint32_t(Edges.size()); + } + void addEdge(dfi::VertexId Target) { Edges.push_back(Target); } + void endEdges(dfi::VertexId Vtx) { VtxEdgeEnd[Vtx] = uint32_t(Edges.size()); } + + /// Adds a summary edge from the call vertex CallVtx to the return-site + /// vertex RetVtx. Returns false, if the edge was already there. + bool addSummaryEdge(dfi::VertexId CallVtx, dfi::VertexId RetVtx); + + /// Adds a call edge from CallVtx to Entry. Returns the exits already known + /// to be reachable from Entry; the solver must apply them to CallVtx. The + /// result is invalidated by getOrCreateEntry(). + [[nodiscard]] llvm::ArrayRef addCallEdge(dfi::VertexId CallVtx, + dfi::EntryId Entry); + + /// Records the unbalanced return from the exit vertex Exit to RetVtx, which + /// is reachable without a calling context. RetVtx then behaves as if it was + /// reached from the zero fact at the start of its function. + void addUnbalancedReturn(dfi::VertexId Exit, dfi::VertexId RetVtx); + + /// Computes the same-level reachability from the entries of Fun to its exit + /// vertices and calls Handler(CallVtx, Entry, Exit) for each new + /// (Entry, Exit) pair and each call vertex CallVtx calling Entry. + /// + /// With FollowReturnsPastSeeds, also calls UnbalancedHandler(Exit) for each + /// new exit vertex that is reachable without calling context, i.e., from a + /// seed without callers or from an unbalanced return. The solver must + /// return it to all callers of Fun. + void summarize( + FunctionId Fun, + std::invocable auto Handler, + std::invocable auto UnbalancedHandler) { + indexFunction(Fun); + + for (auto Entry : FunState[Fun].Entries) { + auto NewExits = updateSummaryExits(Fun, Entry); + // Handler may add vertices and summary edges, but no entries or callers + for (size_t I = 0; I != EntryCallers[Entry].size(); ++I) { + for (auto Exit : NewExits) { + std::invoke(Handler, EntryCallers[Entry][I], Entry, Exit); + } + } + } + + if (FollowReturnsPastSeeds) { + // UnbalancedHandler may add vertices, roots and unbalanced returns, but + // no entries or callers + for (auto Exit : updateUnbalancedExits(Fun)) { + std::invoke(UnbalancedHandler, Exit); + } + } + } + + [[nodiscard]] size_t numVertices() const noexcept { return VtxInst.size(); } + [[nodiscard]] dfi::InstId instOf(dfi::VertexId Vtx) const noexcept { + return VtxInst[Vtx]; + } + [[nodiscard]] dfi::FactId factOf(dfi::VertexId Vtx) const noexcept { + return VtxFact[Vtx]; + } + [[nodiscard]] FunctionId funOf(dfi::VertexId Vtx) const noexcept { + return VtxFun[Vtx]; + } + [[nodiscard]] FunctionId funOf(dfi::EntryId Entry) const noexcept { + return VtxFun[EntryVertex[Entry]]; + } + + [[nodiscard]] llvm::ArrayRef seeds() const noexcept { + return SeedVertices; + } + + /// The graph of intra-procedural, summary, call, and unbalanced-return edges + [[nodiscard]] CsrGraph buildSupergraph() const; + + [[nodiscard]] TypedVector + takeVertexInsts() noexcept { + return std::move(VtxInst); + } + [[nodiscard]] TypedVector + takeVertexFacts() noexcept { + return std::move(VtxFact); + } + +private: + struct FunctionState { + TypedVector Vertices; + llvm::SmallVector Worklist; + /// Entries, seeds, and unbalanced-return targets + llvm::SmallVector Roots; + llvm::SmallVector Entries; + llvm::SmallVector Seeds; + /// Targets of unbalanced returns into this function + llvm::SmallVector UnbalancedRoots; + llvm::SmallVector, 0> SummaryEdges; + /// Exit vertices already reported as unbalanced, sorted + llvm::SmallVector UnbalancedExits; + /// Whether the graph changed since the last summarize() + bool Dirty = false; + }; + + void markDirty(FunctionId Fun); + bool addRoot(dfi::VertexId Vtx); + + [[nodiscard]] bool hasCallers(dfi::VertexId Vtx) const; + /// Whether a call reaches the zero fact at a start point of Fun + [[nodiscard]] bool isZeroEntryCalled(FunctionId Fun) const; + + /// Builds LocalIndex for the vertices of Fun, with exits as targets + void indexFunction(FunctionId Fun); + + /// Appends the exits reachable from Src according to LocalIndex to NewExits, + /// unless they are in Known + void collectNewExits(FunctionId Fun, dfi::VertexId Src, + llvm::ArrayRef Known); + + /// Sorts NewExits, removes duplicates, and merges them into Known + void mergeNewExits(llvm::SmallVectorImpl &Known); + + /// Adds the exits reachable from Entry according to LocalIndex to its + /// summary. Returns the newly added ones. As in the IDESolver, the zero + /// entry also reaches whatever the unbalanced-return targets reach. + [[nodiscard]] llvm::ArrayRef + updateSummaryExits(FunctionId Fun, dfi::EntryId Entry); + + /// Adds the exits reachable without calling context according to LocalIndex + /// to the unbalanced exits of Fun. Returns the newly added ones. + [[nodiscard]] llvm::ArrayRef + updateUnbalancedExits(FunctionId Fun); + + [[nodiscard]] llvm::ArrayRef + edgesOf(dfi::VertexId Vtx) const noexcept { + auto Start = VtxEdgeBegin[Vtx]; + auto End = VtxEdgeEnd[Vtx]; + return llvm::ArrayRef(Edges).slice(Start, End - Start); + } + + SCCHolder CGSCCs; + BitSet> PendingSCCs; + TypedVector FunState; + + // Per vertex + llvm::DenseMap, dfi::VertexId> VertexOf; + TypedVector VtxInst; + TypedVector VtxFact; + TypedVector VtxFun; + TypedVector VtxLocalId; + /// Intra-procedural out-edges, excluding summary edges: + /// Edges[VtxEdgeBegin[V] .. VtxEdgeEnd[V]) + TypedVector VtxEdgeBegin; + TypedVector VtxEdgeEnd; + BitSet IsExit; + BitSet IsRoot; + llvm::SmallVector Edges; + llvm::SmallVector SeedVertices; + + // Per entry + llvm::DenseMap EntryOf; + TypedVector EntryVertex; + TypedVector> EntryCallers; + /// Exit vertices reachable from the entry, sorted + TypedVector> SummaryExits; + + llvm::DenseSet> SummaryEdgeSet; + llvm::SmallVector, 0> CallEdges; + llvm::DenseSet> UnbalancedReturnEdges; + bool FollowReturnsPastSeeds{}; + + // Scratch buffers + llvm::SmallVector Batch; + llvm::SmallVector, 0> + LocalEdges; + llvm::SmallVector LocalRoots; + llvm::SmallVector NewExits; + CsrGraph LocalGraph; + IntervalReachabilityBuilder LocalBuilder; + IntervalReachabilityIndex LocalIndex; +}; + +} // namespace psr::detail diff --git a/include/phasar/DataFlow/IfdsIde/Solver/DFISolver.h b/include/phasar/DataFlow/IfdsIde/Solver/DFISolver.h new file mode 100644 index 0000000000..ead185f616 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/DFISolver.h @@ -0,0 +1,448 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/ControlFlow/CGSCCs.h" +#include "phasar/ControlFlow/ICFG.h" +#include "phasar/ControlFlow/SparseCFGProvider.h" +#include "phasar/DataFlow/IfdsIde/IFDSIDESolverConfig.h" +#include "phasar/DataFlow/IfdsIde/IFDSProblem.h" +#include "phasar/DataFlow/IfdsIde/Solver/DFIExplodedSupergraph.h" +#include "phasar/DataFlow/IfdsIde/Solver/DFISolverConfig.h" +#include "phasar/DataFlow/IfdsIde/Solver/DFISolverResults.h" +#include "phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/CsrGraph.h" +#include "phasar/Utils/FunctionId.h" +#include "phasar/Utils/IntervalReachability.h" +#include "phasar/Utils/IotaIterator.h" +#include "phasar/Utils/Lazy.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include +#include +#include + +namespace psr { + +/// Solves an IFDS problem by materializing the realizable part of the exploded +/// supergraph and answering reachability with DFS-interval labels, following +/// "DFI: An Interprocedural Value-Flow Analysis Framework that Scales to Large +/// Codebases" by Hsu, Hetzelt, and Franz (CGO'23). See +/// docs/DFISolver-Design.md. +/// +/// Computes the same results as the IFDS solvers, while evaluating each flow +/// function only once per data-flow fact and instruction. Additionally, with +/// StaticSolverConfigTy::BuildQueryIndex, getReachability() answers +/// context-sensitive reachability queries between arbitrary +/// (instruction, fact) pairs that hold. +template +class DFISolver : public IDESolverAPIMixin< + DFISolver> { + friend IDESolverAPIMixin>; + +public: + using domain_t = typename ProblemTy::ProblemAnalysisDomain; + using d_t = typename domain_t::d_t; + using n_t = typename domain_t::n_t; + using f_t = typename domain_t::f_t; + using i_t = ICFGTy; + using config_t = StaticSolverConfigTy; + + DFISolver(ProblemTy *Problem, const ICFGTy *ICF, + StaticSolverConfigTy Config = {}) + : DFISolver(Problem, ICF, Config, + getProblemSolverConfig(assertNotNull(Problem))) {} + + /// Uses SolverConfig instead of the problem's solver config. Only + /// followReturnsPastSeeds is respected; AutoAddZero is taken from + /// StaticSolverConfigTy. + DFISolver(ProblemTy *Problem, const ICFGTy *ICF, + StaticSolverConfigTy /*Config*/, IFDSIDESolverConfig SolverConfig) + : Problem(&assertNotNull(Problem)), ICF(&assertNotNull(ICF)), + Results(std::make_unique>()), + SolverConfig(SolverConfig) {} + + [[nodiscard]] DFISolverResults getSolverResults() const noexcept { + return DFISolverResults(Results.get()); + } + + [[nodiscard]] OwningDFISolverResults + consumeSolverResults() noexcept { + return OwningDFISolverResults(std::move(Results)); + } + + /// Only valid after solving + [[nodiscard]] DFIReachability getReachability() const noexcept + requires(StaticSolverConfigTy::BuildQueryIndex) + { + return DFIReachability(Results.get()); + } + + void dumpResults(llvm::raw_ostream &OS = llvm::outs()) const { + getSolverResults().dumpResults(OS); + } + + /// Only valid after solving + void emitTextReport(llvm::raw_ostream &OS = llvm::outs()) { + Problem->emitTextReport(getSolverResults(), OS); + } + +private: + using FlowFunctionPtrType = typename ProblemTy::FlowFunctionPtrType; + + struct CalleeInfo { + FunctionId Callee{}; + FlowFunctionPtrType FF{}; + /// Empty for summary flow functions + llvm::SmallVector StartPoints; + bool IsSummaryFF{}; + }; + + // --- IDESolverAPIMixin + + void doInitialize() { + for (const auto &Fun : ICF->getAllFunctions()) { + Funs.getOrInsert(Fun); + } + Graph.emplace(computeCGSCCs(*ICF, Funs), + SolverConfig.followReturnsPastSeeds()); + + auto ZeroId = Results->Facts.getOrInsert(Problem->getZeroValue()); + assert(ZeroId == dfi::FactId::Zero); + (void)ZeroId; + + auto Seeds = Problem->initialSeeds(); + for (const auto &[Inst, SeedFacts] : Seeds.getSeeds()) { + auto Fun = Funs.get(ICF->getFunctionOf(Inst)); + for (const auto &[Fact, Value] : SeedFacts) { + Graph->addSeed(getOrCreateVertex(Inst, Fact, Fun)); + } + } + } + + [[nodiscard]] bool doNext() { + auto Next = Graph->nextAction(); + if (!Next) { + return false; + } + + if (Next->Kind == detail::DFIExplodedSupergraph::ActionKind::Expand) { + expand(Next->Fun); + } else { + auto Fun = Next->Fun; + Graph->summarize( + Fun, + [this](dfi::VertexId CallVtx, dfi::EntryId Entry, + dfi::VertexId Exit) { applySummary(CallVtx, Entry, Exit); }, + [this, Fun](dfi::VertexId Exit) { returnUnbalanced(Fun, Exit); }); + } + return true; + } + + auto doFinalize() & { + finalizeResults(); + return getSolverResults(); + } + + auto doFinalize() && { + finalizeResults(); + return consumeSolverResults(); + } + + // --- Flow functions + + dfi::VertexId getOrCreateVertex(ByConstRef Inst, ByConstRef Fact, + FunctionId Fun) { + return Graph->getOrCreateVertex(Results->Insts.getOrInsert(Inst), + Results->Facts.getOrInsert(Fact), Fun, + ICF->isExitInst(Inst)); + } + + /// Creates the vertex reached by an intra-procedural edge to Succ + dfi::VertexId getOrCreateSuccVertex(ByConstRef Succ, + ByConstRef Fact, FunctionId Fun) { + if constexpr (has_advanceToNextUser_v) { + return getOrCreateVertex(ICF->advanceToNextUser(Succ, Fact), Fact, Fun); + } else { + return getOrCreateVertex(Succ, Fact, Fun); + } + } + + [[nodiscard]] n_t instOf(dfi::VertexId Vtx) const { + return Results->Insts[Graph->instOf(Vtx)]; + } + [[nodiscard]] d_t factOf(dfi::VertexId Vtx) const { + return Results->Facts[Graph->factOf(Vtx)]; + } + + [[nodiscard]] auto computeTargets(const FlowFunctionPtrType &FF, + ByConstRef Fact) { + auto Ret = (*FF).computeTargets(Fact); + if constexpr (StaticSolverConfigTy::AutoAddZero) { + if (Problem->isZeroValue(Fact)) { + Ret.insert(Problem->getZeroValue()); + } + } + return Ret; + } + + /// Adds the summary edges from the call vertex CallVtx that go through the + /// same-level path from Entry to the exit vertex Exit + void applySummary(dfi::VertexId CallVtx, dfi::EntryId Entry, + dfi::VertexId Exit) { + n_t CallSite = instOf(CallVtx); + n_t ExitInst = instOf(Exit); + d_t ExitFact = factOf(Exit); + f_t Callee = Funs[Graph->funOf(Entry)]; + auto Caller = Graph->funOf(CallVtx); + + for (const auto &RetSite : ICF->getReturnSitesOfCallAt(CallSite)) { + auto FF = + Problem->getRetFlowFunction(CallSite, Callee, ExitInst, RetSite); + for (const auto &Fact : computeTargets(FF, ExitFact)) { + Graph->addSummaryEdge(CallVtx, + getOrCreateSuccVertex(RetSite, Fact, Caller)); + } + } + } + + /// Returns the exit vertex Exit of Fun, which is reachable without calling + /// context, to all callers of Fun in the call-graph + void returnUnbalanced(FunctionId Fun, dfi::VertexId Exit) { + n_t ExitInst = instOf(Exit); + d_t ExitFact = factOf(Exit); + f_t Callee = Funs[Fun]; + + bool HasCallers = false; + for (const auto &CallSite : ICF->getCallersOf(Callee)) { + HasCallers = true; + auto Caller = Funs.get(ICF->getFunctionOf(CallSite)); + for (const auto &RetSite : ICF->getReturnSitesOfCallAt(CallSite)) { + auto FF = + Problem->getRetFlowFunction(CallSite, Callee, ExitInst, RetSite); + for (const auto &Fact : computeTargets(FF, ExitFact)) { + Graph->addUnbalancedReturn( + Exit, getOrCreateSuccVertex(RetSite, Fact, Caller)); + } + } + } + + if constexpr (UnbalancedRetSideEffectProvider) { + if (!HasCallers) { + Problem->applyUnbalancedRetFlowFunctionSideEffects(Callee, ExitInst, + ExitFact); + } + } + } + + /// Expands all pending vertices of Fun, batched per instruction, such that + /// each flow function is constructed once per batch. + void expand(FunctionId Fun) { + auto Batch = Graph->takeWorklist(Fun); + for (const auto *It = Batch.begin(), *End = Batch.end(); It != End;) { + auto Inst = Graph->instOf(*It); + const auto *RunEnd = std::find_if(It, End, [this, Inst](auto Vtx) { + return Graph->instOf(Vtx) != Inst; + }); + + n_t Stmt = Results->Insts[Inst]; + llvm::ArrayRef Run(It, RunEnd); + if (ICF->isCallSite(Stmt)) { + expandCall(Stmt, Run, Fun); + } else { + expandNormal(Stmt, Run, Fun); + } + It = RunEnd; + } + } + + void expandNormal(ByConstRef Stmt, llvm::ArrayRef Run, + FunctionId Fun) { + llvm::SmallVector, 2> NormalFFs; + for (const auto &Succ : ICF->getSuccsOf(Stmt)) { + NormalFFs.emplace_back(Succ, Problem->getNormalFlowFunction(Stmt, Succ)); + } + + for (auto Vtx : Run) { + d_t Fact = factOf(Vtx); + Graph->beginEdges(Vtx); + for (const auto &[Succ, FF] : NormalFFs) { + for (const auto &Target : computeTargets(FF, Fact)) { + Graph->addEdge(getOrCreateSuccVertex(Succ, Target, Fun)); + } + } + Graph->endEdges(Vtx); + } + } + + void expandCall(ByConstRef CallSite, llvm::ArrayRef Run, + FunctionId Fun) { + llvm::SmallVector Callees; + for (const auto &Callee : ICF->getCalleesOfCallAt(CallSite)) { + Callees.push_back(Callee); + } + + llvm::SmallVector RetSites; + llvm::SmallVector CallToRetFFs; + for (const auto &RetSite : ICF->getReturnSitesOfCallAt(CallSite)) { + RetSites.push_back(RetSite); + CallToRetFFs.push_back( + Problem->getCallToRetFlowFunction(CallSite, RetSite, Callees)); + } + + llvm::SmallVector CalleeInfos; + for (const auto &Callee : Callees) { + auto &Info = CalleeInfos.emplace_back(); + Info.Callee = Funs.get(Callee); + + if (auto SummaryFF = Problem->getSummaryFlowFunction(CallSite, Callee)) { + Info.FF = std::move(SummaryFF); + Info.IsSummaryFF = true; + continue; + } + + Info.FF = Problem->getCallFlowFunction(CallSite, Callee); + for (const auto &StartPoint : ICF->getStartPointsOf(Callee)) { + Info.StartPoints.push_back(StartPoint); + } + } + + for (auto Vtx : Run) { + d_t Fact = factOf(Vtx); + + Graph->beginEdges(Vtx); + for (const auto &[RetSite, FF] : llvm::zip(RetSites, CallToRetFFs)) { + for (const auto &Target : computeTargets(FF, Fact)) { + Graph->addEdge(getOrCreateSuccVertex(RetSite, Target, Fun)); + } + } + for (const auto &Info : CalleeInfos) { + if (!Info.IsSummaryFF) { + continue; + } + for (const auto &Target : computeTargets(Info.FF, Fact)) { + for (const auto &RetSite : RetSites) { + Graph->addEdge(getOrCreateSuccVertex(RetSite, Target, Fun)); + } + } + } + Graph->endEdges(Vtx); + + for (const auto &Info : CalleeInfos) { + if (Info.IsSummaryFF) { + continue; + } + for (const auto &CalleeFact : computeTargets(Info.FF, Fact)) { + for (const auto &StartPoint : Info.StartPoints) { + auto Entry = Graph->getOrCreateEntry( + getOrCreateVertex(StartPoint, CalleeFact, Info.Callee)); + auto KnownExits = Graph->addCallEdge(Vtx, Entry); + for (auto Exit : KnownExits) { + applySummary(Vtx, Entry, Exit); + } + } + } + } + } + } + + // --- Results + + /// Indexes the reversed supergraph. Its roots are the sinks of the + /// supergraph; all other vertices follow, as vertices on cycles without a + /// path to a sink are not reachable from any sink. + [[nodiscard]] IntervalReachabilityIndex buildBackwardIndex() { + auto Reversed = CsrGraph::reversed(Graph->buildSupergraph()); + auto Roots = std::move(Reversed).roots(); + llvm::append_range(Roots, iota(Reversed.numVertices())); + return IntervalReachabilityBuilder().build(Reversed, Roots); + } + + void finalizeResults() { + auto &R = *Results; + if constexpr (StaticSolverConfigTy::BuildQueryIndex) { + R.IndexDirection = StaticSolverConfigTy::IndexDirection; + R.Index.emplace(lazy{[&] { + if constexpr (StaticSolverConfigTy::IndexDirection == + DFIIndexDirection::Backward) { + return buildBackwardIndex(); + } else { + return IntervalReachabilityBuilder().build( + Graph->buildSupergraph(), Graph->seeds()); + } + }}); + } + + R.VtxInst = Graph->takeVertexInsts(); + R.VtxFact = Graph->takeVertexFacts(); + auto NumVertices = R.VtxInst.size(); + + R.InstOffsets.assign(R.Insts.size() + 1, 0); + for (auto Inst : R.VtxInst) { + ++R.InstOffsets[Inst]; + } + std::partial_sum(R.InstOffsets.begin(), R.InstOffsets.end(), + R.InstOffsets.begin()); + R.VerticesAt.resize_for_overwrite(NumVertices); + for (auto Vtx : llvm::reverse(iota(NumVertices))) { + R.VerticesAt[--R.InstOffsets[R.VtxInst[Vtx]]] = Vtx; + } + for (auto Inst : iota(R.Insts.size())) { + auto Vertices = csrSuccsMut(R.InstOffsets, R.VerticesAt, Inst); + llvm::sort(Vertices, [&R](dfi::VertexId Lhs, dfi::VertexId Rhs) { + return R.VtxFact[Lhs] < R.VtxFact[Rhs]; + }); + } + + Graph.reset(); + Funs = FunctionCompressor(); + } + + ProblemTy *Problem{}; + const ICFGTy *ICF{}; + std::unique_ptr> Results; + IFDSIDESolverConfig SolverConfig; + + /// Only while solving + FunctionCompressor Funs; + std::optional Graph; +}; + +template +DFISolver(ProblemTy *, const ICFGTy *) + -> DFISolver; +template +DFISolver(ProblemTy *, const ICFGTy *, StaticSolverConfigTy) + -> DFISolver; +template +DFISolver(ProblemTy *, const ICFGTy *, StaticSolverConfigTy, + IFDSIDESolverConfig) + -> DFISolver; + +/// Solves the given IFDS problem with the DFISolver and returns the owning +/// results +template +[[nodiscard]] auto solveDFIProblem(ProblemTy &Problem, const ICFG auto &ICF, + StaticSolverConfigTy Config = {}) { + return DFISolver(&Problem, &ICF, Config).solve(); +} + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/DFISolverConfig.h b/include/phasar/DataFlow/IfdsIde/Solver/DFISolverConfig.h new file mode 100644 index 0000000000..882ea24899 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/DFISolverConfig.h @@ -0,0 +1,48 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include + +namespace psr { + +/// The direction in which the DFISolver's reachability index is built. Both +/// directions answer the same queries; they differ in which enumeration is +/// cheap and in the index size. +enum class DFIIndexDirection : uint8_t { + Forward, + Backward, +}; + +/// Static configuration of the DFISolver. Derive from it to change options. +struct DFISolverConfig { + /// Propagate the zero fact along all flow functions, even if the analysis + /// problem does not do so explicitly + static constexpr bool AutoAddZero = true; + /// Build the reachability index required by DFISolver::getReachability() + static constexpr bool BuildQueryIndex = true; + static constexpr DFIIndexDirection IndexDirection = + DFIIndexDirection::Forward; +}; + +/// DFISolverConfig without reachability index; use it if only the IFDS +/// results are of interest +struct DFISolverConfigNoIndex : DFISolverConfig { + static constexpr bool BuildQueryIndex = false; +}; + +/// DFISolverConfig with a backward reachability index; use it if mostly the +/// origins of facts are queried +struct DFISolverConfigBackward : DFISolverConfig { + static constexpr DFIIndexDirection IndexDirection = + DFIIndexDirection::Backward; +}; + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/DFISolverResults.h b/include/phasar/DataFlow/IfdsIde/Solver/DFISolverResults.h new file mode 100644 index 0000000000..63a59080f3 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/DFISolverResults.h @@ -0,0 +1,358 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DataFlow/IfdsIde/Solver/DFISolverConfig.h" +#include "phasar/Domain/BinaryDomain.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/CsrGraph.h" +#include "phasar/Utils/IntervalReachability.h" +#include "phasar/Utils/IotaIterator.h" +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/StrongTypeDef.h" +#include "phasar/Utils/TypedVector.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/// A node of the exploded supergraph, materialized by the DFISolver +PHASAR_STRONG_TYPEDEF(psr::dfi, uint32_t, VertexId); +PHASAR_STRONG_TYPEDEF(psr::dfi, uint32_t, InstId); +/// The zero fact always has id 0 +PHASAR_STRONG_TYPEDEF(psr::dfi, uint32_t, FactId, Zero = 0); + +namespace psr { + +namespace detail { +template struct DFIResultsData { + Compressor Insts; + Compressor Facts; + + TypedVector VtxInst; + TypedVector VtxFact; + + /// The vertices at instruction I are + /// VerticesAt[InstOffsets[I] .. InstOffsets[I+1]), sorted by fact + TypedVector InstOffsets{0}; + llvm::SmallVector VerticesAt; + + std::optional> Index; + DFIIndexDirection IndexDirection = DFIIndexDirection::Forward; +}; + +template class DFISolverResultsBase { +public: + using n_t = N; + using d_t = D; + using l_t = BinaryDomain; + + /// BinaryDomain::BOTTOM if Fact holds at Stmt, BinaryDomain::TOP otherwise + [[nodiscard]] l_t resultAt(ByConstRef Stmt, ByConstRef Fact) const { + return findVertex(Stmt, Fact) ? BinaryDomain::BOTTOM : BinaryDomain::TOP; + } + + [[nodiscard]] std::unordered_map + resultsAt(ByConstRef Stmt, bool StripZero = false) const { + std::unordered_map Ret; + forEachFactAt(Stmt, StripZero, [&Ret](ByConstRef Fact) { + Ret.try_emplace(Fact, BinaryDomain::BOTTOM); + }); + return Ret; + } + + [[nodiscard]] std::set ifdsResultsAt(ByConstRef Stmt) const { + std::set Ret; + forEachFactAt(Stmt, /*StripZero*/ false, + [&Ret](ByConstRef Fact) { Ret.insert(Fact); }); + return Ret; + } + + /// Calls Handler for each fact holding at Stmt + void forEachFactAt(ByConstRef Stmt, bool StripZero, + std::invocable> auto Handler) const { + const auto &Data = data(); + for (auto Vtx : verticesAt(Stmt)) { + auto Fact = Data.VtxFact[Vtx]; + if (StripZero && Fact == dfi::FactId::Zero) { + continue; + } + std::invoke(Handler, Data.Facts[Fact]); + } + } + + /// Number of instructions with results + [[nodiscard]] size_t size() const noexcept { return data().Insts.size(); } + + [[nodiscard]] bool containsNode(ByConstRef Stmt) const { + return data().Insts.getOrNull(Stmt).has_value(); + } + + void foreachResultEntry( + std::invocable> auto Handler) const { + const auto &Data = data(); + for (auto [Inst, Stmt] : Data.Insts.enumerate()) { + for (auto Vtx : verticesAt(Inst)) { + std::invoke(Handler, + std::make_tuple(Stmt, Data.Facts[Data.VtxFact[Vtx]], + BinaryDomain::BOTTOM)); + } + } + } + + void dumpResults(llvm::raw_ostream &OS = llvm::outs()) const { + const auto &Data = data(); + OS << "\n***************************************************************\n" + << "* Raw DFISolver results *\n" + << "***************************************************************\n"; + for (auto [Inst, Stmt] : Data.Insts.enumerate()) { + OS << "\nN: " << NToString(Stmt) << '\n'; + for (auto Vtx : verticesAt(Inst)) { + OS << "\tD: " << DToString(Data.Facts[Data.VtxFact[Vtx]]) << '\n'; + } + } + } + + /// The vertex for Fact at Stmt, if Fact holds there + [[nodiscard]] std::optional + findVertex(ByConstRef Stmt, ByConstRef Fact) const { + const auto &Data = data(); + auto FactId = Data.Facts.getOrNull(Fact); + if (!FactId) { + return std::nullopt; + } + auto Vertices = verticesAt(Stmt); + const auto *It = + std::lower_bound(Vertices.begin(), Vertices.end(), *FactId, + [&Data](dfi::VertexId Vtx, dfi::FactId F) { + return Data.VtxFact[Vtx] < F; + }); + if (It == Vertices.end() || Data.VtxFact[*It] != *FactId) { + return std::nullopt; + } + return *It; + } + + [[nodiscard]] llvm::ArrayRef + verticesAt(ByConstRef Stmt) const { + auto Inst = data().Insts.getOrNull(Stmt); + if (!Inst) { + return {}; + } + return verticesAt(*Inst); + } + + [[nodiscard]] llvm::ArrayRef + verticesAt(dfi::InstId Inst) const { + return csrSuccs(data().InstOffsets, data().VerticesAt, Inst); + } + + [[nodiscard]] n_t instOf(dfi::VertexId Vtx) const { + return data().Insts[data().VtxInst[Vtx]]; + } + [[nodiscard]] d_t factOf(dfi::VertexId Vtx) const { + return data().Facts[data().VtxFact[Vtx]]; + } + +private: + [[nodiscard]] const DFIResultsData &data() const noexcept { + return static_cast(this)->dataImpl(); + } +}; +} // namespace detail + +/// Non-owning view into the results of a DFISolver. Conforms to the interface +/// expected by GenericSolverResults. +template +class [[gsl::Pointer]] DFISolverResults + : public detail::DFISolverResultsBase, N, D> { + friend detail::DFISolverResultsBase, N, D>; + +public: + explicit DFISolverResults(const detail::DFIResultsData *Data + PSR_LIFETIME_CAPTURE_BY(this)) noexcept + : Data(Data) { + assert(Data != nullptr); + } + +private: + [[nodiscard]] const detail::DFIResultsData &dataImpl() const noexcept { + return *Data; + } + + const detail::DFIResultsData *Data{}; +}; + +/// Owning variant of DFISolverResults +template +class OwningDFISolverResults + : public detail::DFISolverResultsBase, N, D> { + friend detail::DFISolverResultsBase, N, D>; + +public: + explicit OwningDFISolverResults( + std::unique_ptr> Data) noexcept + : Data(std::move(Data)) { + assert(this->Data != nullptr); + } + + [[nodiscard]] DFISolverResults get() const & noexcept { + return DFISolverResults(Data.get()); + } + DFISolverResults get() && = delete; + + [[nodiscard]] operator DFISolverResults() const & noexcept { + return get(); + } + operator DFISolverResults() && = delete; + +private: + [[nodiscard]] const detail::DFIResultsData &dataImpl() const noexcept { + return *Data; + } + + std::unique_ptr> Data; +}; + +/// Context-sensitive reachability between the exploded-supergraph nodes that +/// the DFISolver has materialized. +/// +/// From reaches To iff there is a realizable path from From to To that may +/// descend into callees, but does not return beyond the function of From. +/// Every node reaches itself. The nodes reachable from a node are its +/// descendants, the nodes reaching it are its ancestors; both include the node +/// itself. +/// +/// Point queries cost O(log k) in both index directions. Enumerating the +/// descendants (forward index) or ancestors (backward index) is linear in the +/// output; the other enumeration scans all nodes. +template class DFIReachability { +public: + using n_t = N; + using d_t = D; + + explicit DFIReachability(const detail::DFIResultsData *Data) noexcept + : Results(Data), Index(&*Data->Index), Direction(Data->IndexDirection) {} + + /// The node , if it has been materialized, i.e., if Fact holds + /// at Stmt. + [[nodiscard]] std::optional find(ByConstRef Stmt, + ByConstRef Fact) const { + return Results.findVertex(Stmt, Fact); + } + + [[nodiscard]] bool reaches(dfi::VertexId From, + dfi::VertexId To) const noexcept { + if (Direction == DFIIndexDirection::Backward) { + return Index->reaches(To, From); + } + return Index->reaches(From, To); + } + + [[nodiscard]] bool reaches(ByConstRef FromStmt, ByConstRef FromFact, + ByConstRef ToStmt, + ByConstRef ToFact) const { + auto From = find(FromStmt, FromFact); + auto To = find(ToStmt, ToFact); + return From && To && reaches(*From, *To); + } + + /// Calls Handler for each fact at At that is reachable from From + void + forEachDescendantFactAt(dfi::VertexId From, ByConstRef At, + std::invocable> auto Handler) const { + for (auto To : Results.verticesAt(At)) { + if (reaches(From, To)) { + std::invoke(Handler, Results.factOf(To)); + } + } + } + + /// Calls Handler for each fact at At that reaches To + void + forEachAncestorFactAt(dfi::VertexId To, ByConstRef At, + std::invocable> auto Handler) const { + for (auto From : Results.verticesAt(At)) { + if (reaches(From, To)) { + std::invoke(Handler, Results.factOf(From)); + } + } + } + + /// Calls Handler for each node reachable from From + void forEachDescendant( + dfi::VertexId From, + std::invocable, ByConstRef> auto Handler) const { + forEachRelated(From, DFIIndexDirection::Forward, Handler); + } + + /// Calls Handler for each node that reaches To + void forEachAncestor( + dfi::VertexId To, + std::invocable, ByConstRef> auto Handler) const { + forEachRelated(To, DFIIndexDirection::Backward, Handler); + } + + [[nodiscard]] n_t instOf(dfi::VertexId Vtx) const { + return Results.instOf(Vtx); + } + [[nodiscard]] d_t factOf(dfi::VertexId Vtx) const { + return Results.factOf(Vtx); + } + + [[nodiscard]] const IntervalReachabilityIndex & + index() const noexcept { + return *Index; + } + + [[nodiscard]] DFIIndexDirection indexDirection() const noexcept { + return Direction; + } + +private: + /// Calls Handler for each node related to Vtx in direction Dir + void forEachRelated( + dfi::VertexId Vtx, DFIIndexDirection Dir, + std::invocable, ByConstRef> auto &Handler) const { + const auto Report = [&](dfi::VertexId Related) { + std::invoke(Handler, Results.instOf(Related), Results.factOf(Related)); + }; + + if (Dir == Direction) { + Index->forEachReachableTarget(Vtx, Report); + return; + } + + // The index points the other way, so the related nodes are not contiguous + for (auto Other : iota(Index->numVertices())) { + if (Index->reaches(Other, Vtx)) { + Report(Other); + } + } + } + + DFISolverResults Results; + const IntervalReachabilityIndex *Index{}; + DFIIndexDirection Direction{}; +}; + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h b/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h index 09b48c89c5..0217153f80 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h @@ -10,19 +10,21 @@ #ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWEDGEFUNCTIONCACHE_H #define PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWEDGEFUNCTIONCACHE_H -#include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" -#include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/IDEProblem.h" +#include "phasar/DataFlow/IfdsIde/IFDSIDESolverConfig.h" #include "phasar/Utils/EquivalenceClassMap.h" +#include "phasar/Utils/Lazy.h" #include "phasar/Utils/Logger.h" +#include "phasar/Utils/NonNullPtr.h" #include "phasar/Utils/PAMMMacros.h" +#include "phasar/Utils/PointerUtils.h" #include "phasar/Utils/Utilities.h" #include "llvm/ADT/DenseMap.h" #include +#include #include -#include -#include #include #include #include @@ -41,7 +43,7 @@ template class DefaultMapKeyCompressor { using KeyType = KeyT; using CompressedType = KeyT; - [[nodiscard]] inline CompressedType getCompressedID(KeyT Key) { return Key; } + [[nodiscard]] CompressedType getCompressedID(KeyT Key) { return Key; } }; template class MapKeyCompressorCombinator : public Ts... { @@ -54,207 +56,144 @@ class LLVMMapKeyCompressor { using KeyType = const llvm::Value *; using CompressedType = uint32_t; - [[nodiscard]] inline CompressedType getCompressedID(KeyType Key) { - auto Search = Map.find(Key); - if (Search == Map.end()) { - return Map.insert(std::make_pair(Key, Map.size() + 1)).first->getSecond(); - } - return Search->getSecond(); + [[nodiscard]] CompressedType getCompressedID(KeyType Key) { + auto [It, _] = Map.try_emplace(Key, Map.size() + 1); + return It->second; } private: llvm::DenseMap Map{}; }; +namespace detail { +struct FlowEdgeFunctionCachePerf { + PAMM_CATEGORY(FlowEdgeFunctionCache); + + // NOLINTBEGIN + PAMM_COUNTER(NormalFF_Construction, Full); + PAMM_COUNTER(NormalFF_CacheHit, Full); + // Counters for the call flow functions + PAMM_COUNTER(CallFF_Construction, Full); + PAMM_COUNTER(CallFF_CacheHit, Full); + // Counters for return flow functions + PAMM_COUNTER(ReturnFF_Construction, Full); + PAMM_COUNTER(ReturnFF_CacheHit, Full); + // Counters for the call to return flow functions + PAMM_COUNTER(CallToRetFF_Construction, Full); + PAMM_COUNTER(CallToRetFF_CacheHit, Full); + // Counters for the summary flow functions + PAMM_COUNTER(SummaryFF_Construction, Full); + PAMM_COUNTER(SummaryFF_CacheHit, Full); + // Counters for the normal edge functions + PAMM_COUNTER(NormalEF_Construction, Full); + PAMM_COUNTER(NormalEF_CacheHit, Full); + // Counters for the call edge functions + PAMM_COUNTER(CallEF_Construction, Full); + PAMM_COUNTER(CallEF_CacheHit, Full); + // Counters for the return edge functions + PAMM_COUNTER(ReturnEF_Construction, Full); + PAMM_COUNTER(ReturnEF_CacheHit, Full); + // Counters for the call to return edge functions + PAMM_COUNTER(CallToRetEF_Construction, Full); + PAMM_COUNTER(CallToRetEF_CacheHit, Full); + // Counters for the summary edge functions + PAMM_COUNTER(SummaryEF_Construction, Full); + PAMM_COUNTER(SummaryEF_CacheHit, Full); + + // NOLINTEND +}; +} // namespace detail + /** * This class caches flow and edge functions to avoid their reconstruction. * When a flow or edge function must be applied to multiple times, a cached * version is used if existend, otherwise a new one is created and inserted * into the cache. */ -template > -class FlowEdgeFunctionCache { - using IDEProblemType = IDETabulationProblem; - using FlowFunctionPtrType = typename IDEProblemType::FlowFunctionPtrType; - - using n_t = typename AnalysisDomainTy::n_t; - using d_t = typename AnalysisDomainTy::d_t; - using f_t = typename AnalysisDomainTy::f_t; - using t_t = typename AnalysisDomainTy::t_t; - using l_t = typename AnalysisDomainTy::l_t; +template +class FlowEdgeFunctionCache : private detail::FlowEdgeFunctionCachePerf { + using FlowFunctionPtrType = typename ProblemTy::FlowFunctionPtrType; - using DTKeyCompressorType = std::conditional_t< - std::is_base_of_v>, - LLVMMapKeyCompressor, DefaultMapKeyCompressor>; - using NTKeyCompressorType = std::conditional_t< - std::is_base_of_v>, - LLVMMapKeyCompressor, DefaultMapKeyCompressor>; - - using MapKeyCompressorType = std::conditional_t< - std::is_same_v, - NTKeyCompressorType, - MapKeyCompressorCombinator>; - -private: - MapKeyCompressorType KeyCompressor; + using n_t = typename ProblemTy::n_t; + using d_t = typename ProblemTy::d_t; + using f_t = typename ProblemTy::f_t; + using t_t = typename ProblemTy::t_t; + using l_t = typename ProblemTy::l_t; + using container_type = typename ProblemTy::container_type; - using EdgeFuncInstKey = uint64_t; - using EdgeFuncNodeKey = std::conditional_t< - std::is_base_of_v>, uint64_t, - std::pair>; - using InnerEdgeFunctionMapType = - EquivalenceClassMap>; - - IDETabulationProblem &Problem; - // Auto add zero - bool AutoAddZero; - d_t ZV; - - struct NormalEdgeFlowData { - NormalEdgeFlowData(FlowFunctionPtrType Val) - : FlowFuncPtr(std::move(Val)), EdgeFunctionMap{} {} - NormalEdgeFlowData(InnerEdgeFunctionMapType Map) - : FlowFuncPtr(nullptr), EdgeFunctionMap{std::move(Map)} {} - - FlowFunctionPtrType FlowFuncPtr; - InnerEdgeFunctionMapType EdgeFunctionMap; - }; - - // Caches for the flow/edge functions - std::map NormalFunctionCache; - - // Caches for the flow functions - std::map, FlowFunctionPtrType> CallFlowFunctionCache; - std::map, FlowFunctionPtrType> - ReturnFlowFunctionCache; - std::map, FlowFunctionPtrType> - CallToRetFlowFunctionCache; - // Caches for the edge functions - std::map, EdgeFunction> - CallEdgeFunctionCache; - std::map, EdgeFunction> - ReturnEdgeFunctionCache; - std::map - CallToRetEdgeFunctionCache; - std::map, EdgeFunction> - SummaryEdgeFunctionCache; + using FlowFunctionType = FlowFunction; + using EdgeFunctionType = typename ProblemTy::EdgeFunctionType; public: // Ctor allows access to the IDEProblem in order to get access to flow and // edge function factory functions. - FlowEdgeFunctionCache( - IDETabulationProblem &Problem) + FlowEdgeFunctionCache(NonNullPtr Problem) : Problem(Problem), - AutoAddZero(Problem.getIFDSIDESolverConfig().autoAddZero()), - ZV(Problem.getZeroValue()) { - PAMM_GET_INSTANCE; - REG_COUNTER("Normal-FF Construction", 0, Full); - REG_COUNTER("Normal-FF Cache Hit", 0, Full); - // Counters for the call flow functions - REG_COUNTER("Call-FF Construction", 0, Full); - REG_COUNTER("Call-FF Cache Hit", 0, Full); - // Counters for return flow functions - REG_COUNTER("Return-FF Construction", 0, Full); - REG_COUNTER("Return-FF Cache Hit", 0, Full); - // Counters for the call to return flow functions - REG_COUNTER("CallToRet-FF Construction", 0, Full); - REG_COUNTER("CallToRet-FF Cache Hit", 0, Full); - // Counters for the summary flow functions - REG_COUNTER("Summary-FF Construction", 0, Full); - REG_COUNTER("Summary-FF Cache Hit", 0, Full); - // Counters for the normal edge functions - REG_COUNTER("Normal-EF Construction", 0, Full); - REG_COUNTER("Normal-EF Cache Hit", 0, Full); - // Counters for the call edge functions - REG_COUNTER("Call-EF Construction", 0, Full); - REG_COUNTER("Call-EF Cache Hit", 0, Full); - // Counters for the return edge functions - REG_COUNTER("Return-EF Construction", 0, Full); - REG_COUNTER("Return-EF Cache Hit", 0, Full); - // Counters for the call to return edge functions - REG_COUNTER("CallToRet-EF Construction", 0, Full); - REG_COUNTER("CallToRet-EF Cache Hit", 0, Full); - // Counters for the summary edge functions - REG_COUNTER("Summary-EF Construction", 0, Full); - REG_COUNTER("Summary-EF Cache Hit", 0, Full); - } - - ~FlowEdgeFunctionCache() = default; - - FlowEdgeFunctionCache(const FlowEdgeFunctionCache &FEFC) = default; - FlowEdgeFunctionCache &operator=(const FlowEdgeFunctionCache &FEFC) = default; + AutoAddZero(getProblemSolverConfig(*Problem).autoAddZero()), + ZV(Problem->getZeroValue()) {} - FlowEdgeFunctionCache(FlowEdgeFunctionCache &&FEFC) noexcept = default; - FlowEdgeFunctionCache & - operator=(FlowEdgeFunctionCache &&FEFC) noexcept = default; - - FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) { + [[nodiscard]] NonNullPtr getNormalFlowFunction(n_t Curr, + n_t Succ) { assertNotNull(Curr); assertNotNull(Succ); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Normal flow function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Curr Inst : " << NToString(Curr)); PHASAR_LOG_LEVEL(DEBUG, "(N) Succ Inst : " << NToString(Succ))); auto Key = createEdgeFunctionInstKey(Curr, Succ); - auto SearchNormalFlowFunction = NormalFunctionCache.find(Key); - if (SearchNormalFlowFunction != NormalFunctionCache.end()) { + + // operator[] instead of try_emplace: NormalEdgeFlowData holds both the + // flow function ptr and the edge function map for the same (Curr,Succ) + // key, so getNormalEdgeFunction shares this entry via the same lookup. + auto &NormalFE = NormalFunctionCache[std::move(Key)]; + if (!NormalFE.FlowFuncPtr) { + NormalFF_Construction++; + auto FF = Problem->getNormalFlowFunction(Curr, Succ); + NormalFE.FlowFuncPtr = AutoAddZero + ? &ZFFOwner.emplace_back(std::move(FF), ZV) + : std::move(FF); + PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); + } else { PHASAR_LOG_LEVEL(DEBUG, "Flow function fetched from cache"); - INC_COUNTER("Normal-FF Cache Hit", 1, Full); - if (SearchNormalFlowFunction->second.FlowFuncPtr != nullptr) { - return SearchNormalFlowFunction->second.FlowFuncPtr; - } - auto FF = (AutoAddZero) - ? std::make_shared>( - Problem.getNormalFlowFunction(Curr, Succ), ZV) - : Problem.getNormalFlowFunction(Curr, Succ); - SearchNormalFlowFunction->second.FlowFuncPtr = FF; - return FF; + NormalFF_CacheHit++; } - INC_COUNTER("Normal-FF Construction", 1, Full); - auto FF = (AutoAddZero) - ? std::make_shared>( - Problem.getNormalFlowFunction(Curr, Succ), ZV) - : Problem.getNormalFlowFunction(Curr, Succ); - NormalFunctionCache.insert(std::make_pair(Key, NormalEdgeFlowData(FF))); - PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); - return FF; + return getPointerFrom(NormalFE.FlowFuncPtr); } - FlowFunctionPtrType getCallFlowFunction(n_t CallSite, f_t DestFun) { + [[nodiscard]] NonNullPtr getCallFlowFunction(n_t CallSite, + f_t DestFun) { assertNotNull(CallSite); assertNotNull(DestFun); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Call flow function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Stmt : " << NToString(CallSite)); PHASAR_LOG_LEVEL(DEBUG, "(F) Dest Fun : " << FToString(DestFun))); auto Key = std::tie(CallSite, DestFun); - auto SearchCallFlowFunction = CallFlowFunctionCache.find(Key); - if (SearchCallFlowFunction != CallFlowFunctionCache.end()) { + + auto [It, Inserted] = CallFlowFunctionCache.try_emplace( + std::move(Key), lazy{[&] { + CallFF_Construction++; + auto FF = Problem->getCallFlowFunction(CallSite, DestFun); + auto Ret = AutoAddZero ? &ZFFOwner.emplace_back(std::move(FF), ZV) + : std::move(FF); + PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); + return Ret; + }}); + if (!Inserted) { PHASAR_LOG_LEVEL(DEBUG, "Flow function fetched from cache"); - INC_COUNTER("Call-FF Cache Hit", 1, Full); - return SearchCallFlowFunction->second; + CallFF_CacheHit++; } - INC_COUNTER("Call-FF Construction", 1, Full); - auto FF = (AutoAddZero) - ? std::make_shared>( - Problem.getCallFlowFunction(CallSite, DestFun), ZV) - : Problem.getCallFlowFunction(CallSite, DestFun); - CallFlowFunctionCache.insert(std::make_pair(Key, FF)); - PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); - return FF; + + return getPointerFrom(It->second); } - FlowFunctionPtrType getRetFlowFunction(n_t CallSite, f_t CalleeFun, - n_t ExitInst, n_t RetSite) { + [[nodiscard]] NonNullPtr + getRetFlowFunction(n_t CallSite, f_t CalleeFun, n_t ExitInst, n_t RetSite) { assertNotNull(CallSite); assertNotNull(CalleeFun); assertNotNull(ExitInst); assertNotNull(RetSite); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Return flow function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Site : " << NToString(CallSite)); @@ -262,79 +201,83 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(N) Exit Stmt : " << NToString(ExitInst)); PHASAR_LOG_LEVEL(DEBUG, "(N) Ret Site : " << NToString(RetSite))); auto Key = std::tie(CallSite, CalleeFun, ExitInst, RetSite); - auto SearchReturnFlowFunction = ReturnFlowFunctionCache.find(Key); - if (SearchReturnFlowFunction != ReturnFlowFunctionCache.end()) { + + auto [It, Inserted] = ReturnFlowFunctionCache.try_emplace( + std::move(Key), lazy{[&] { + ReturnFF_Construction++; + auto FF = Problem->getRetFlowFunction(CallSite, CalleeFun, ExitInst, + RetSite); + auto Ret = AutoAddZero ? &ZFFOwner.emplace_back(std::move(FF), ZV) + : std::move(FF); + PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); + return Ret; + }}); + if (!Inserted) { PHASAR_LOG_LEVEL(DEBUG, "Flow function fetched from cache"); - INC_COUNTER("Return-FF Cache Hit", 1, Full); - return SearchReturnFlowFunction->second; + ReturnFF_CacheHit++; } - INC_COUNTER("Return-FF Construction", 1, Full); - auto FF = (AutoAddZero) - ? std::make_shared>( - Problem.getRetFlowFunction(CallSite, CalleeFun, - ExitInst, RetSite), - ZV) - : Problem.getRetFlowFunction(CallSite, CalleeFun, ExitInst, - RetSite); - ReturnFlowFunctionCache.insert(std::make_pair(Key, FF)); - PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); - return FF; + return getPointerFrom(It->second); } - FlowFunctionPtrType getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) { + [[nodiscard]] NonNullPtr + getCallToRetFlowFunction(n_t CallSite, n_t RetSite, + llvm::ArrayRef Callees) { assertNotNull(CallSite); assertNotNull(RetSite); assertAllNotNull(Callees); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Call-to-Return flow function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Site : " << NToString(CallSite)); PHASAR_LOG_LEVEL(DEBUG, "(N) Ret Site : " << NToString(RetSite)); PHASAR_LOG_LEVEL(DEBUG, "(F) Callee's : "); - for (auto callee : Callees) { - PHASAR_LOG_LEVEL(DEBUG, " " << FToString(callee)); + for (auto Callee : Callees) { + PHASAR_LOG_LEVEL(DEBUG, " " << FToString(Callee)); };); - auto Key = std::tie(CallSite, RetSite); - auto SearchCallToRetFlowFunction = CallToRetFlowFunctionCache.find(Key); - if (SearchCallToRetFlowFunction != CallToRetFlowFunctionCache.end()) { + auto Key = createEdgeFunctionInstKey(CallSite, RetSite); + + auto &CTRFE = NormalFunctionCache[std::move(Key)]; + if (!CTRFE.FlowFuncPtr) { + CallToRetFF_Construction++; + auto FF = Problem->getCallToRetFlowFunction(CallSite, RetSite, Callees); + CTRFE.FlowFuncPtr = AutoAddZero + ? &ZFFOwner.emplace_back(std::move(FF), ZV) + : std::move(FF); + + PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); + } else { PHASAR_LOG_LEVEL(DEBUG, "Flow function fetched from cache"); - INC_COUNTER("CallToRet-FF Cache Hit", 1, Full); - return SearchCallToRetFlowFunction->second; + CallToRetFF_CacheHit++; + CallToRetFF_CacheHit++; } - INC_COUNTER("CallToRet-FF Construction", 1, Full); - auto FF = - (AutoAddZero) - ? std::make_shared>( - Problem.getCallToRetFlowFunction(CallSite, RetSite, Callees), - ZV) - : Problem.getCallToRetFlowFunction(CallSite, RetSite, Callees); - CallToRetFlowFunctionCache.insert(std::make_pair(Key, FF)); - PHASAR_LOG_LEVEL(DEBUG, "Flow function constructed"); - return FF; + + return getPointerFrom(CTRFE.FlowFuncPtr); } - FlowFunctionPtrType getSummaryFlowFunction(n_t CallSite, f_t DestFun) { + /// \note Unlike the other get*FlowFunction methods, this returns a nullable + /// FlowFunctionPtrType rather than NonNullPtr. A null return means no special + /// summary is available for this call site; the solver falls back to + /// propagating through the callee body. + [[nodiscard]] FlowFunctionPtrType getSummaryFlowFunction(n_t CallSite, + f_t DestFun) { assertNotNull(CallSite); assertNotNull(DestFun); - // PAMM_GET_INSTANCE; - // INC_COUNTER("Summary-FF Construction", 1, Full); + + SummaryFF_Construction++; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Summary flow function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Stmt : " << NToString(CallSite)); PHASAR_LOG_LEVEL(DEBUG, "(F) Dest Mthd : " << FToString(DestFun)); PHASAR_LOG_LEVEL(DEBUG, ' ')); - auto FF = Problem.getSummaryFlowFunction(CallSite, DestFun); + auto FF = Problem->getSummaryFlowFunction(CallSite, DestFun); return FF; } - EdgeFunction getNormalEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, - d_t SuccNode) { + [[nodiscard]] EdgeFunctionType getNormalEdgeFunction(n_t Curr, d_t CurrNode, + n_t Succ, d_t SuccNode) { assertNotNull(Curr); assertNotNull(Succ); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Normal edge function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Curr Inst : " << NToString(Curr)); @@ -343,46 +286,31 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(D) Succ Node : " << DToString(SuccNode))); EdgeFuncInstKey OuterMapKey = createEdgeFunctionInstKey(Curr, Succ); - auto SearchInnerMap = NormalFunctionCache.find(OuterMapKey); - if (SearchInnerMap != NormalFunctionCache.end()) { - auto SearchEdgeFunc = SearchInnerMap->second.EdgeFunctionMap.find( - createEdgeFunctionNodeKey(CurrNode, SuccNode)); - if (SearchEdgeFunc != SearchInnerMap->second.EdgeFunctionMap.end()) { - INC_COUNTER("Normal-EF Cache Hit", 1, Full); - PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); - PHASAR_LOG_LEVEL(DEBUG, - "Provide Edge Function: " << SearchEdgeFunc->second); - return SearchEdgeFunc->second; - } - INC_COUNTER("Normal-EF Construction", 1, Full); - auto EF = Problem.getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode); - - SearchInnerMap->second.EdgeFunctionMap.insert( - createEdgeFunctionNodeKey(CurrNode, SuccNode), EF); - - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; - } - INC_COUNTER("Normal-EF Construction", 1, Full); - auto EF = Problem.getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode); - - NormalFunctionCache.try_emplace( - OuterMapKey, NormalEdgeFlowData(InnerEdgeFunctionMapType{std::make_pair( - createEdgeFunctionNodeKey(CurrNode, SuccNode), EF)})); - - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; + auto &NormalFE = NormalFunctionCache[std::move(OuterMapKey)]; + auto Ret = NormalFE.EdgeFunctionMap.getOrInsertLazy( + createEdgeFunctionNodeKey(CurrNode, SuccNode), + [&] { + NormalEF_Construction++; + auto EF = + Problem->getNormalEdgeFunction(Curr, CurrNode, Succ, SuccNode); + PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); + return EF; + }, + [&] { + NormalEF_CacheHit++; + PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); + }); + PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << Ret); + return Ret; } - EdgeFunction getCallEdgeFunction(n_t CallSite, d_t SrcNode, - f_t DestinationFunction, d_t DestNode) { + [[nodiscard]] EdgeFunctionType getCallEdgeFunction(n_t CallSite, d_t SrcNode, + f_t DestinationFunction, + d_t DestNode) { assertNotNull(CallSite); assertNotNull(DestinationFunction); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Call edge function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Stmt : " << NToString(CallSite)); @@ -392,32 +320,33 @@ class FlowEdgeFunctionCache { "(F) Dest Fun : " << FToString(DestinationFunction)); PHASAR_LOG_LEVEL(DEBUG, "(D) Dest Node : " << DToString(DestNode))); auto Key = std::tie(CallSite, SrcNode, DestinationFunction, DestNode); - auto SearchCallEdgeFunction = CallEdgeFunctionCache.find(Key); - if (SearchCallEdgeFunction != CallEdgeFunctionCache.end()) { - INC_COUNTER("Call-EF Cache Hit", 1, Full); + + auto [It, Inserted] = CallEdgeFunctionCache.try_emplace( + std::move(Key), lazy{[&] { + CallEF_Construction++; + auto Ret = Problem->getCallEdgeFunction( + CallSite, SrcNode, DestinationFunction, DestNode); + + PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); + return Ret; + }}); + if (!Inserted) { + CallEF_CacheHit++; PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); - PHASAR_LOG_LEVEL( - DEBUG, "Provide Edge Function: " << SearchCallEdgeFunction->second); - return SearchCallEdgeFunction->second; } - INC_COUNTER("Call-EF Construction", 1, Full); - auto EF = Problem.getCallEdgeFunction(CallSite, SrcNode, - DestinationFunction, DestNode); - CallEdgeFunctionCache.insert(std::make_pair(Key, EF)); - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; + + PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << It->second); + return It->second; } - EdgeFunction getReturnEdgeFunction(n_t CallSite, f_t CalleeFunction, - n_t ExitInst, d_t ExitNode, - n_t RetSite, d_t RetNode) { + [[nodiscard]] EdgeFunctionType + getReturnEdgeFunction(n_t CallSite, f_t CalleeFunction, n_t ExitInst, + d_t ExitNode, n_t RetSite, d_t RetNode) { assertNotNull(CallSite); assertNotNull(CalleeFunction); assertNotNull(ExitInst); assertNotNull(RetSite); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Return edge function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Site : " << NToString(CallSite)); @@ -429,31 +358,31 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(D) Ret Node : " << DToString(RetNode))); auto Key = std::tie(CallSite, CalleeFunction, ExitInst, ExitNode, RetSite, RetNode); - auto SearchReturnEdgeFunction = ReturnEdgeFunctionCache.find(Key); - if (SearchReturnEdgeFunction != ReturnEdgeFunctionCache.end()) { - INC_COUNTER("Return-EF Cache Hit", 1, Full); + auto [It, Inserted] = ReturnEdgeFunctionCache.try_emplace( + std::move(Key), lazy{[&] { + ReturnEF_Construction++; + auto Ret = Problem->getReturnEdgeFunction( + CallSite, CalleeFunction, ExitInst, ExitNode, RetSite, RetNode); + PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); + return Ret; + }}); + + if (!Inserted) { + ReturnEF_CacheHit++; PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); - PHASAR_LOG_LEVEL( - DEBUG, "Provide Edge Function: " << SearchReturnEdgeFunction->second); - return SearchReturnEdgeFunction->second; } - INC_COUNTER("Return-EF Construction", 1, Full); - auto EF = Problem.getReturnEdgeFunction(CallSite, CalleeFunction, ExitInst, - ExitNode, RetSite, RetNode); - ReturnEdgeFunctionCache.insert(std::make_pair(Key, EF)); - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; + + PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << It->second); + return It->second; } - EdgeFunction getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, - n_t RetSite, d_t RetSiteNode, - llvm::ArrayRef Callees) { + [[nodiscard]] EdgeFunctionType + getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, n_t RetSite, + d_t RetSiteNode, llvm::ArrayRef Callees) { assertNotNull(CallSite); assertNotNull(RetSite); assertAllNotNull(Callees); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Call-to-Return edge function factory call"); @@ -463,53 +392,37 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(N) Ret Site : " << NToString(RetSite)); PHASAR_LOG_LEVEL(DEBUG, "(D) Ret Node : " << DToString(RetSiteNode)); PHASAR_LOG_LEVEL(DEBUG, "(F) Callee's : "); - for (auto callee : Callees) { - PHASAR_LOG_LEVEL(DEBUG, " " << FToString(callee)); + for (auto Callee : Callees) { + PHASAR_LOG_LEVEL(DEBUG, " " << FToString(Callee)); }); EdgeFuncInstKey OuterMapKey = createEdgeFunctionInstKey(CallSite, RetSite); - auto SearchInnerMap = CallToRetEdgeFunctionCache.find(OuterMapKey); - if (SearchInnerMap != CallToRetEdgeFunctionCache.end()) { - auto SearchEdgeFunc = SearchInnerMap->second.find( - createEdgeFunctionNodeKey(CallNode, RetSiteNode)); - if (SearchEdgeFunc != SearchInnerMap->second.end()) { - INC_COUNTER("CallToRet-EF Cache Hit", 1, Full); - PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); - PHASAR_LOG_LEVEL(DEBUG, - "Provide Edge Function: " << SearchEdgeFunc->second); - return SearchEdgeFunc->second; - } - INC_COUNTER("CallToRet-EF Construction", 1, Full); - auto EF = Problem.getCallToRetEdgeFunction(CallSite, CallNode, RetSite, - RetSiteNode, Callees); - - SearchInnerMap->second.insert( - createEdgeFunctionNodeKey(CallNode, RetSiteNode), EF); - - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; - } - - INC_COUNTER("CallToRet-EF Construction", 1, Full); - auto EF = Problem.getCallToRetEdgeFunction(CallSite, CallNode, RetSite, - RetSiteNode, Callees); - - CallToRetEdgeFunctionCache.emplace( - OuterMapKey, - InnerEdgeFunctionMapType{std::make_pair( - createEdgeFunctionNodeKey(CallNode, RetSiteNode), EF)}); - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; + auto &Outer = NormalFunctionCache[std::move(OuterMapKey)]; + + auto Ret = Outer.EdgeFunctionMap.getOrInsertLazy( + std::move(createEdgeFunctionNodeKey(CallNode, RetSiteNode)), + [&] { + CallToRetEF_Construction++; + auto Ret = Problem->getCallToRetEdgeFunction( + CallSite, CallNode, RetSite, RetSiteNode, Callees); + PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); + return Ret; + }, + [&] { + CallToRetEF_CacheHit++; + PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); + }); + PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << Ret); + return Ret; } - EdgeFunction getSummaryEdgeFunction(n_t CallSite, d_t CallNode, - n_t RetSite, d_t RetSiteNode) { + [[nodiscard]] EdgeFunctionType getSummaryEdgeFunction(n_t CallSite, + d_t CallNode, + n_t RetSite, + d_t RetSiteNode) { assertNotNull(CallSite); assertNotNull(RetSite); - PAMM_GET_INSTANCE; IF_LOG_ENABLED( PHASAR_LOG_LEVEL(DEBUG, "Summary edge function factory call"); PHASAR_LOG_LEVEL(DEBUG, "(N) Call Site : " << NToString(CallSite)); @@ -518,88 +431,86 @@ class FlowEdgeFunctionCache { PHASAR_LOG_LEVEL(DEBUG, "(D) Ret Node : " << DToString(RetSiteNode)); PHASAR_LOG_LEVEL(DEBUG, ' ')); auto Key = std::tie(CallSite, CallNode, RetSite, RetSiteNode); - auto SearchSummaryEdgeFunction = SummaryEdgeFunctionCache.find(Key); - if (SearchSummaryEdgeFunction != SummaryEdgeFunctionCache.end()) { - INC_COUNTER("Summary-EF Cache Hit", 1, Full); + auto [It, Inserted] = SummaryEdgeFunctionCache.try_emplace( + std::move(Key), lazy{[&] { + SummaryEF_Construction++; + auto Ret = Problem->getSummaryEdgeFunction(CallSite, CallNode, + RetSite, RetSiteNode); + PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); + return Ret; + }}); + if (!Inserted) { + SummaryEF_CacheHit++; PHASAR_LOG_LEVEL(DEBUG, "Edge function fetched from cache"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " - << SearchSummaryEdgeFunction->second); - return SearchSummaryEdgeFunction->second; } - INC_COUNTER("Summary-EF Construction", 1, Full); - auto EF = Problem.getSummaryEdgeFunction(CallSite, CallNode, RetSite, - RetSiteNode); - SummaryEdgeFunctionCache.insert(std::make_pair(Key, EF)); - PHASAR_LOG_LEVEL(DEBUG, "Edge function constructed"); - PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << EF); - return EF; + + PHASAR_LOG_LEVEL(DEBUG, "Provide Edge Function: " << It->second); + return It->second; } void print() { if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::Full) { - PAMM_GET_INSTANCE; PHASAR_LOG_LEVEL(INFO, "=== Flow-Edge-Function Cache Statistics ==="); PHASAR_LOG_LEVEL(INFO, "Normal-flow function cache hits: " - << GET_COUNTER("Normal-FF Cache Hit")); + << NormalFF_CacheHit.value()); PHASAR_LOG_LEVEL(INFO, "Normal-flow function constructions: " - << GET_COUNTER("Normal-FF Construction")); - PHASAR_LOG_LEVEL(INFO, "Call-flow function cache hits: " - << GET_COUNTER("Call-FF Cache Hit")); + << NormalFF_Construction.value()); + PHASAR_LOG_LEVEL( + INFO, "Call-flow function cache hits: " << CallFF_CacheHit.value()); PHASAR_LOG_LEVEL(INFO, "Call-flow function constructions: " - << GET_COUNTER("Call-FF Construction")); + << CallFF_Construction.value()); PHASAR_LOG_LEVEL(INFO, "Return-flow function cache hits: " - << GET_COUNTER("Return-FF Cache Hit")); + << ReturnFF_CacheHit.value()); PHASAR_LOG_LEVEL(INFO, "Return-flow function constructions: " - << GET_COUNTER("Return-FF Construction")); + << ReturnFF_Construction.value()); PHASAR_LOG_LEVEL(INFO, "Call-to-Return-flow function cache hits: " - << GET_COUNTER("CallToRet-FF Cache Hit")); + << CallToRetFF_CacheHit.value()); PHASAR_LOG_LEVEL(INFO, "Call-to-Return-flow function constructions: " - << GET_COUNTER("CallToRet-FF Construction")); + << CallToRetFF_Construction.value()); PHASAR_LOG_LEVEL(INFO, "Summary-flow function cache hits: " - << GET_COUNTER("Summary-FF Cache Hit")); + << SummaryFF_CacheHit.value()); PHASAR_LOG_LEVEL(INFO, "Summary-flow function constructions: " - << GET_COUNTER("Summary-FF Construction")); - PHASAR_LOG_LEVEL(INFO, - "Total flow function cache hits: " << GET_SUM_COUNT( - {"Normal-FF Cache Hit", "Call-FF Cache Hit", - "Return-FF Cache Hit", "CallToRet-FF Cache Hit"})); + << SummaryFF_Construction.value()); + PHASAR_LOG_LEVEL( + INFO, "Total flow function cache hits: " << pamm::getSumCount( + NormalFF_CacheHit, CallFF_CacheHit, ReturnFF_CacheHit, + CallToRetFF_CacheHit, SummaryFF_CacheHit)); //"Summary-FF Cache Hit"}); - PHASAR_LOG_LEVEL(INFO, "Total flow function constructions: " - << GET_SUM_COUNT({"Normal-FF Construction", "Call-FF Construction", - "Return-FF Construction", - "CallToRet-FF Construction" /*, - "Summary-FF Construction"*/})); + PHASAR_LOG_LEVEL( + INFO, "Total flow function constructions: " << pamm::getSumCount( + NormalFF_Construction, CallFF_Construction, + CallToRetFF_Construction, ReturnFF_Construction, + SummaryFF_Construction)); PHASAR_LOG_LEVEL(INFO, ' '); - PHASAR_LOG_LEVEL(INFO, "Normal edge function cache hits: " - << GET_COUNTER("Normal-EF Cache Hit")); + PHASAR_LOG_LEVEL( + INFO, "Normal edge function cache hits: " << NormalEF_CacheHit); PHASAR_LOG_LEVEL(INFO, "Normal edge function constructions: " - << GET_COUNTER("Normal-EF Construction")); - PHASAR_LOG_LEVEL(INFO, "Call edge function cache hits: " - << GET_COUNTER("Call-EF Cache Hit")); - PHASAR_LOG_LEVEL(INFO, "Call edge function constructions: " - << GET_COUNTER("Call-EF Construction")); - PHASAR_LOG_LEVEL(INFO, "Return edge function cache hits: " - << GET_COUNTER("Return-EF Cache Hit")); + << NormalEF_Construction); + PHASAR_LOG_LEVEL(INFO, + "Call edge function cache hits: " << CallEF_CacheHit); + PHASAR_LOG_LEVEL( + INFO, "Call edge function constructions: " << CallEF_Construction); + PHASAR_LOG_LEVEL( + INFO, "Return edge function cache hits: " << ReturnEF_CacheHit); PHASAR_LOG_LEVEL(INFO, "Return edge function constructions: " - << GET_COUNTER("Return-EF Construction")); + << ReturnEF_Construction); PHASAR_LOG_LEVEL(INFO, "Call-to-Return edge function cache hits: " - << GET_COUNTER("CallToRet-EF Cache Hit")); + << CallToRetEF_CacheHit); PHASAR_LOG_LEVEL(INFO, "Call-to-Return edge function constructions: " - << GET_COUNTER("CallToRet-EF Construction")); - PHASAR_LOG_LEVEL(INFO, "Summary edge function cache hits: " - << GET_COUNTER("Summary-EF Cache Hit")); + << CallToRetEF_Construction); + PHASAR_LOG_LEVEL( + INFO, "Summary edge function cache hits: " << SummaryEF_CacheHit); PHASAR_LOG_LEVEL(INFO, "Summary edge function constructions: " - << GET_COUNTER("Summary-EF Construction")); - PHASAR_LOG_LEVEL(INFO, - "Total edge function cache hits: " << GET_SUM_COUNT( - {"Normal-EF Cache Hit", "Call-EF Cache Hit", - "Return-EF Cache Hit", "CallToRet-EF Cache Hit", - "Summary-EF Cache Hit"})); + << SummaryEF_Construction); PHASAR_LOG_LEVEL( - INFO, "Total edge function constructions: " << GET_SUM_COUNT( - {"Normal-EF Construction", "Call-EF Construction", - "Return-EF Construction", "CallToRet-EF Construction", - "Summary-EF Construction"})); + INFO, "Total edge function cache hits: " << pamm::getSumCount( + NormalEF_CacheHit, CallEF_CacheHit, ReturnEF_CacheHit, + CallToRetEF_CacheHit, SummaryEF_CacheHit)); + PHASAR_LOG_LEVEL( + INFO, + "Total edge function constructions: " << pamm::getSumCount( + NormalEF_Construction, CallEF_Construction, ReturnEF_Construction, + CallToRetEF_Construction, SummaryEF_Construction)); PHASAR_LOG_LEVEL(INFO, "----------------------------------------------"); } else { PHASAR_LOG_LEVEL( @@ -622,19 +533,39 @@ class FlowEdgeFunctionCache { std::invoke(Fn, EF, EdgeFunctionKind::Return); } - for (const auto &[Key, CTRFns] : CallToRetEdgeFunctionCache) { - for (const auto &[Set, EF] : CTRFns) { - std::invoke(Fn, EF, EdgeFunctionKind::CallToReturn); - } - } - for (const auto &[Key, EF] : SummaryEdgeFunctionCache) { std::invoke(Fn, EF, EdgeFunctionKind::Summary); } } private: - inline EdgeFuncInstKey createEdgeFunctionInstKey(n_t Lhs, n_t Rhs) { + using DTKeyCompressorType = std::conditional_t< + std::is_base_of_v>, + LLVMMapKeyCompressor, DefaultMapKeyCompressor>; + using NTKeyCompressorType = std::conditional_t< + std::is_base_of_v>, + LLVMMapKeyCompressor, DefaultMapKeyCompressor>; + + using MapKeyCompressorType = std::conditional_t< + std::is_same_v, + NTKeyCompressorType, + MapKeyCompressorCombinator>; + + using EdgeFuncInstKey = uint64_t; + using EdgeFuncNodeKey = std::conditional_t< + std::is_base_of_v>, uint64_t, + std::pair>; + using InnerEdgeFunctionMapType = + EquivalenceClassMapNG; + + using ZFF = ZeroedFlowFunction; + + struct NormalEdgeFlowData { + FlowFunctionPtrType FlowFuncPtr{}; + InnerEdgeFunctionMapType EdgeFunctionMap{}; + }; + + constexpr EdgeFuncInstKey createEdgeFunctionInstKey(n_t Lhs, n_t Rhs) { uint64_t Val = 0; Val |= KeyCompressor.getCompressedID(Lhs); Val <<= 32; @@ -642,7 +573,7 @@ class FlowEdgeFunctionCache { return Val; } - inline EdgeFuncNodeKey createEdgeFunctionNodeKey(d_t Lhs, d_t Rhs) { + constexpr EdgeFuncNodeKey createEdgeFunctionNodeKey(d_t Lhs, d_t Rhs) { if constexpr (std::is_base_of_v>) { uint64_t Val = 0; Val |= KeyCompressor.getCompressedID(Lhs); @@ -650,9 +581,37 @@ class FlowEdgeFunctionCache { Val |= KeyCompressor.getCompressedID(Rhs); return Val; } else { - return std::make_pair(Lhs, Rhs); + return std::make_pair(std::move(Lhs), std::move(Rhs)); } } + + MapKeyCompressorType KeyCompressor; + + NonNullPtr Problem; + // Auto add zero + bool AutoAddZero{}; + d_t ZV; + + std::deque ZFFOwner; + + // Caches for the normal/CTR flow/edge functions + // NOTE: The key-spaces for normal and CTR functions are disjoint. The solver + // will never call getNormal[...]Function on call-sites, or + // getCallToRet[...]Function on non-call-sites. So, we can use the same cache + // for both here + llvm::DenseMap NormalFunctionCache; + + // Caches for the flow functions + std::map, FlowFunctionPtrType> CallFlowFunctionCache; + std::map, FlowFunctionPtrType> + ReturnFlowFunctionCache; + // Caches for the edge functions + std::map, EdgeFunctionType> + CallEdgeFunctionCache; + std::map, EdgeFunctionType> + ReturnEdgeFunctionCache; + std::map, EdgeFunctionType> + SummaryEdgeFunctionCache; }; } // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h b/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h index 944f711707..0b0d8a2819 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h @@ -1,6 +1,7 @@ #ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWFUNCTIONCACHE_H #define PHASAR_DATAFLOW_IFDSIDE_SOLVER_FLOWFUNCTIONCACHE_H +#include "phasar/DataFlow/IfdsIde/FlowFunctions.h" #include "phasar/DataFlow/IfdsIde/GenericFlowFunction.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowFunctionCacheStats.h" #include "phasar/Utils/ByRef.h" @@ -20,15 +21,6 @@ namespace psr { namespace detail { -template -concept IsFlowFunction = requires(T &FF, D Fact) { - { FF.computeTargets(Fact) } -> is_iterable_over_v; -}; - -template -concept IsFlowFunctionPtr = requires(T FF, D Fact) { - { FF->computeTargets(Fact) } -> is_iterable_over_v; -}; template struct AutoAddZeroFF { FFTy FF; @@ -109,23 +101,18 @@ template struct FlowFunctionCacheBase { std::decay_t().getSummaryFlowFunction( std::declval(), std::declval()))>; - static_assert(detail::IsFlowFunction || - detail::IsFlowFunctionPtr); - static_assert(detail::IsFlowFunction || - detail::IsFlowFunctionPtr); - static_assert(detail::IsFlowFunction || - detail::IsFlowFunctionPtr); - static_assert(detail::IsFlowFunction || - detail::IsFlowFunctionPtr); - static_assert(detail::IsFlowFunction || - detail::IsFlowFunctionPtr || + static_assert(IsFlowFunctionOrFFPtrOf); + static_assert(IsFlowFunctionOrFFPtrOf); + static_assert(IsFlowFunctionOrFFPtrOf); + static_assert(IsFlowFunctionOrFFPtrOf); + static_assert(IsFlowFunctionOrFFPtrOf || std::is_same_v); template static constexpr bool needs_cache_v = - std::is_same_v>, T> || + std::is_same_v>, T> || std::is_same_v, T> || - (detail::IsFlowFunctionPtr && !std::is_pointer_v); + (IsFlowFunctionPtrOf && !std::is_pointer_v); }; } // namespace detail @@ -163,7 +150,7 @@ class FlowFunctionCache using ff_t = std::decay_t; if constexpr (AutoAddZero) { - if constexpr (detail::IsFlowFunctionPtr) { + if constexpr (IsFlowFunctionPtrOf) { if constexpr (needs_cache_v) { return detail::AutoAddZeroFF{ GenericFlowFunctionView(getPointerFrom(FF)), @@ -178,7 +165,7 @@ class FlowFunctionCache Problem.getZeroValue()}; } } else { - if constexpr (detail::IsFlowFunctionPtr) { + if constexpr (IsFlowFunctionPtrOf) { if constexpr (needs_cache_v) { return GenericFlowFunctionView(getPointerFrom(FF)); } else { @@ -276,17 +263,25 @@ class FlowFunctionCache decltype(auto) getSummaryFlowFunction(ProblemTy &Problem, ByConstRef CallSite, ByConstRef Callee, - uint64_t /*CSCalleeId*/) { + uint64_t CSCalleeId) { + // Note: summary-ff can ignore AutoAddZero because it is guaranteed that the + // ctr-ff will propagate 0 if constexpr (needs_cache_v) { - return GenericFlowFunction( - Problem.getSummaryFlowFunction(CallSite, Callee)); + // Most calls have no summary, i.e., a nullptr flow function. So, cannot + // just check for null as empty-marker + auto [It, Inserted] = SummaryFFCache.insert(CSCalleeId, summary_ff_t{}); + if (Inserted) { + It->second = Problem.getSummaryFlowFunction(CallSite, Callee); + } + + if constexpr (std::is_same_v>) { + return GenericFlowFunctionView(It->second); + } else { + return GenericFlowFunctionView(getPointerFrom(It->second)); + } } else { return Problem.getSummaryFlowFunction(CallSite, Callee); } - - /// XXX: The old FECache doesn't cache summary FFs, so refrain from doing - /// this here as well. Enable it, once the user-problems reliably return - /// std::nullptr_t such that caching can be disabled automatically } void clear() noexcept { @@ -302,6 +297,7 @@ class FlowFunctionCache detail::IntraFlowFunctionsMixin::reserveIntraFFs( NumInsts, NumCalls); CallFFCache.reserve(NumCalls); + SummaryFFCache.reserve(NumCalls); RetFFCache.reserve(NumFuns); SimpleRetFFCache.reserve(NumFuns); } diff --git a/include/phasar/DataFlow/IfdsIde/Solver/GenericSolverResults.h b/include/phasar/DataFlow/IfdsIde/Solver/GenericSolverResults.h index 4fa67d88fa..97c3253401 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/GenericSolverResults.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/GenericSolverResults.h @@ -1,6 +1,7 @@ #ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_GENERICSOLVERRESULTS_H #define PHASAR_DATAFLOW_IFDSIDE_SOLVER_GENERICSOLVERRESULTS_H +#include "phasar/Domain/AnalysisDomain.h" #include "phasar/Domain/BinaryDomain.h" #include "phasar/Utils/ByRef.h" @@ -162,6 +163,11 @@ template class GenericSolverResults final { alignas(alignof(void *)) std::array Buffer{}; }; +template +using GenericSolverResultsFor = GenericSolverResults< + typename AnalysisDomainTy::n_t, typename AnalysisDomainTy::d_t, + typename detail::ValueDomainAdder::l_t>; + template bool ifdsEqual(const SR1 &LHS, const SR2 &RHS) { if (LHS.size() != RHS.size()) { diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h index 3f1218d9dc..4ed7a908a6 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolver.h @@ -18,16 +18,17 @@ #define PHASAR_DATAFLOW_IFDSIDE_SOLVER_IDESOLVER_H #include "phasar/Config/Configuration.h" +#include "phasar/ControlFlow/ICFG.h" #include "phasar/ControlFlow/SparseCFGProvider.h" -#include "phasar/DB/ProjectIRDBBase.h" #include "phasar/DataFlow/IfdsIde/EdgeFunction.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctionStats.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" #include "phasar/DataFlow/IfdsIde/FlowFunctions.h" +#include "phasar/DataFlow/IfdsIde/IDEProblem.h" #include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" -#include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" #include "phasar/DataFlow/IfdsIde/InitialSeeds.h" +#include "phasar/DataFlow/IfdsIde/LegacyIDEProblemWrapper.h" #include "phasar/DataFlow/IfdsIde/Solver/ESGEdgeKind.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h" #include "phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h" @@ -41,6 +42,7 @@ #include "phasar/Utils/JoinLattice.h" #include "phasar/Utils/Logger.h" #include "phasar/Utils/Macros.h" +#include "phasar/Utils/NonNullPtr.h" #include "phasar/Utils/Nullable.h" #include "phasar/Utils/PAMMMacros.h" #include "phasar/Utils/Table.h" @@ -49,31 +51,85 @@ #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/StringRef.h" -#include "llvm/Support/TypeName.h" #include "llvm/Support/raw_ostream.h" #include "nlohmann/json.hpp" #include +#include #include #include #include #include #include #include -#include #include namespace psr { +namespace detail { +struct IDESolverPerf { + PAMM_CATEGORY(IDESolver); + + // NOLINTBEGIN + PAMM_COUNTER(Genfacts, Core); + PAMM_COUNTER(Killfacts, Core); + PAMM_COUNTER(Summaryreuse, Core); + PAMM_COUNTER(IntraPathEdges, Core); + PAMM_COUNTER(InterPathEdges, Core); + PAMM_COUNTER(FFQueries, Full); + PAMM_COUNTER(EFQueries, Full); + PAMM_COUNTER(ValuePropagation, Full); + PAMM_COUNTER(ValueComputation, Full); + PAMM_COUNTER(SpecialSummaryFF_Application, Full); + PAMM_COUNTER(SpecialSummaryEF_Queries, Full); + PAMM_COUNTER(JumpFnConstruction, Full); + PAMM_COUNTER(ProcessCall, Full); + PAMM_COUNTER(ProcessNormal, Full); + PAMM_COUNTER(ProcessExit, Full); + + PAMM_HISTOGRAM(DataFlowFacts, Full); + PAMM_HISTOGRAM(PointsTo, Full); + + PAMM_TIMER(DFAPhase1, Full); + PAMM_TIMER(DFAPhase2, Full); + // NOLINTEND +}; + +template +class IDESolverProblemWrapperStorage { +protected: + constexpr IDESolverProblemWrapperStorage() = default; + + template + IDESolverProblemWrapperStorage(ProblemTy *Problem) + : ProblemOwner( + std::make_unique>(Problem)) {} + + template + IDESolverProblemWrapperStorage(ProblemTy *Problem, + IFDSIDESolverConfig SolverConfig) + : IDESolverProblemWrapperStorage(Problem) { + ProblemOwner->setIFDSIDESolverConfig(SolverConfig); + } + + std::unique_ptr> + ProblemOwner{}; +}; +} // namespace detail + /// Solves the given IDETabulationProblem as described in the 1996 paper by /// Sagiv, Horwitz and Reps. To solve the problem, call solve(). Results /// can then be queried by using resultAt() and resultsAt(). template > + typename Container = std::set, + ICFG ICFGTy = typename AnalysisDomainTy::i_t> class IDESolver - : public IDESolverAPIMixin> { - friend IDESolverAPIMixin>; + : private detail::IDESolverProblemWrapperStorage, + private detail::IDESolverPerf, + public IDESolverAPIMixin> { + friend IDESolverAPIMixin>; public: using ProblemTy = IDETabulationProblem; @@ -82,31 +138,59 @@ class IDESolver using l_t = typename AnalysisDomainTy::l_t; using n_t = typename AnalysisDomainTy::n_t; - using i_t = typename AnalysisDomainTy::i_t; + using i_t = ICFGTy; using d_t = typename AnalysisDomainTy::d_t; using f_t = typename AnalysisDomainTy::f_t; using t_t = typename AnalysisDomainTy::t_t; using v_t = typename AnalysisDomainTy::v_t; - template I> - IDESolver(IDETabulationProblem &Problem, - const I *ICF) - : IDEProblem(Problem), ZeroValue(Problem.getZeroValue()), - ICF(&static_cast(*ICF)), SVFG(ICF), - SolverConfig(Problem.getIFDSIDESolverConfig()), - CachedFlowEdgeFunctions(Problem), AllTop(Problem.allTopFunction()), - JumpFn(std::make_shared>()), - Seeds(Problem.initialSeeds()) { - assert(ICF != nullptr); - - if constexpr (has_getSparseCFG_v) { - NextUserOrNullCB = &nextUserOrNullThunk; - } - } - IDESolver(IDETabulationProblem *Problem, const i_t *ICF) - : IDESolver(assertNotNull(Problem), ICF) {} + : IDEProblem(assertNotNull(Problem)), ZeroValue(Problem->getZeroValue()), + ICF(&assertNotNull(ICF)), + SolverConfig(Problem->getIFDSIDESolverConfig()), + CachedFlowEdgeFunctions(Problem), AllTop(Problem->allTopFunction()), + Seeds(Problem->initialSeeds()) {} + + template + [[deprecated( + "Use the other overload of IDESolver() instead, which takes the " + "ide-problem by pointer. This documents better that the solver captures " + "the address without taking ownership")]] IDESolver(ProblemTy &Problem, + const ICFGTy *ICF) + : IDESolver(&Problem, ICF) {} + + template + requires(!std::derived_from< + ProblemTy, + IDETabulationProblem>) + IDESolver(ProblemTy *Problem, const i_t *ICF) + : detail::IDESolverProblemWrapperStorage( + Problem), + IDEProblem(*this->ProblemOwner), ZeroValue(Problem->getZeroValue()), + ICF(&assertNotNull(ICF)), + SolverConfig(this->ProblemOwner->getIFDSIDESolverConfig()), + CachedFlowEdgeFunctions(this->ProblemOwner.get()), + AllTop(this->ProblemOwner->allTopFunction()), + Seeds(Problem->initialSeeds()) {} + + /// Uses SolverConfig instead of the problem's solver config + template + requires(!std::derived_from< + ProblemTy, + IDETabulationProblem>) + IDESolver(ProblemTy *Problem, const i_t *ICF, + IFDSIDESolverConfig SolverConfig) + : detail::IDESolverProblemWrapperStorage( + Problem, SolverConfig), + IDEProblem(*this->ProblemOwner), ZeroValue(Problem->getZeroValue()), + ICF(&assertNotNull(ICF)), + SolverConfig(this->ProblemOwner->getIFDSIDESolverConfig()), + CachedFlowEdgeFunctions(this->ProblemOwner.get()), + AllTop(this->ProblemOwner->allTopFunction()), + Seeds(Problem->initialSeeds()) {} IDESolver(const IDESolver &) = delete; IDESolver &operator=(const IDESolver &) = delete; @@ -215,6 +299,12 @@ class IDESolver getSolverResults().dumpResults(*ICF, OS); } + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const IDESolver &Solver) { + Solver.dumpResults(OS); + return OS; + } + void dumpAllInterPathEdges() { llvm::outs() << "COMPUTED INTER PATH EDGES" << '\n'; auto Interpe = this->computedInterPathEdges.cellSet(); @@ -351,13 +441,19 @@ class IDESolver } protected: - Nullable getNextUserOrNull(ByConstRef Fun, ByConstRef d3, - ByConstRef n) { - if (!NextUserOrNullCB || IDEProblem.isZeroValue(d3)) { + [[nodiscard]] Nullable getNextUserOrNull(ByConstRef Fun, + ByConstRef d3, + ByConstRef n) { + if constexpr (has_getSparseCFG_v) { + if (IDEProblem.isZeroValue(d3)) { + return {}; + } + + auto &&SCFG = ICF->getSparseCFG(Fun, d3); + return SCFG.nextUserOrNull(n); + } else { return {}; } - - return NextUserOrNullCB(SVFG, Fun, d3, n); } /// Lines 13-20 of the algorithm; processing a call site in the caller's @@ -378,15 +474,14 @@ class IDESolver /// @param edge an edge whose target node resembles a method call /// virtual void processCall(const PathEdge Edge) { - PAMM_GET_INSTANCE; - INC_COUNTER("Process Call", 1, Full); + ProcessCall++; PHASAR_LOG_LEVEL(DEBUG, "Process call at target: " << NToString(Edge.getTarget())); d_t d1 = Edge.factAtSource(); n_t n = Edge.getTarget(); // a call node; line 14... d_t d2 = Edge.factAtTarget(); - EdgeFunction f = jumpFunction(Edge); + auto f = jumpFunction(Edge); const auto &ReturnSiteNs = ICF->getReturnSitesOfCallAt(n); const auto &Callees = ICF->getCalleesOfCallAt(n); @@ -396,26 +491,26 @@ class IDESolver PHASAR_LOG_LEVEL(DEBUG, " " << Callee->getName()); } PHASAR_LOG_LEVEL(DEBUG, "Possible return sites:"); - for (auto ret : ReturnSiteNs) { - PHASAR_LOG_LEVEL(DEBUG, " " << NToString(ret)); + for (auto Ret : ReturnSiteNs) { + PHASAR_LOG_LEVEL(DEBUG, " " << NToString(Ret)); } }); bool HasNoCalleeInformation = true; auto &&Fun = ICF->getFunctionOf(n); - auto GetNextUse = [this, &Fun, &n](n_t nPrime, ByConstRef d3) { + auto GetNextUse = [this, &Fun, &n](n_t NPrime, ByConstRef d3) { if (auto &&NextUser = getNextUserOrNull(Fun, d3, n)) { return psr::unwrapNullable(PSR_FWD(NextUser)); } - return nPrime; + return NPrime; }; // for each possible callee for (f_t SCalledProcN : Callees) { // still line 14 // check if a special summary for the called procedure exists - FlowFunctionPtrType SpecialSum = + auto SpecialSum = CachedFlowEdgeFunctions.getSummaryFlowFunction(n, SCalledProcN); // if a special summary is available, treat this as a normal flow @@ -426,14 +521,13 @@ class IDESolver PHASAR_LOG_LEVEL(DEBUG, "Found and process special summary"); for (n_t ReturnSiteN : ReturnSiteNs) { container_type Res = computeSummaryFlowFunction(SpecialSum, d1, d2); - INC_COUNTER("SpecialSummary-FF Application", 1, Full); - ADD_TO_HISTOGRAM("Data-flow facts", Res.size(), 1, Full); + SpecialSummaryFF_Application++; + DataFlowFacts.add(Res.size(), 1); saveEdges(n, ReturnSiteN, d2, Res, ESGEdgeKind::Summary); for (d_t d3 : Res) { - EdgeFunction SumEdgFnE = - CachedFlowEdgeFunctions.getSummaryEdgeFunction(n, d2, - ReturnSiteN, d3); - INC_COUNTER("SpecialSummary-EF Queries", 1, Full); + auto SumEdgFnE = CachedFlowEdgeFunctions.getSummaryEdgeFunction( + n, d2, ReturnSiteN, d3); + SpecialSummaryEF_Queries++; PHASAR_LOG_LEVEL(DEBUG, "Queried Summary Edge Function: " << SumEdgFnE); @@ -447,11 +541,11 @@ class IDESolver } } else { // compute the call-flow function - FlowFunctionPtrType Function = + auto Function = CachedFlowEdgeFunctions.getCallFlowFunction(n, SCalledProcN); - INC_COUNTER("FF Queries", 1, Full); + FFQueries++; container_type Res = computeCallFlowFunction(Function, d1, d2); - ADD_TO_HISTOGRAM("Data-flow facts", Res.size(), 1, Full); + DataFlowFacts.add(Res.size(), 1); // for each callee's start point(s) auto StartPointsOf = ICF->getStartPointsOf(SCalledProcN); if (StartPointsOf.empty()) { @@ -491,31 +585,27 @@ class IDESolver for (const TableCell &Entry : endSummary(SP, d3)) { n_t eP = Entry.getRowKey(); d_t d4 = Entry.getColumnKey(); - EdgeFunction fCalleeSummary = Entry.getValue(); + auto fCalleeSummary = Entry.getValue(); // for each return site for (n_t RetSiteN : ReturnSiteNs) { // compute return-flow function - FlowFunctionPtrType RetFunction = - CachedFlowEdgeFunctions.getRetFlowFunction(n, SCalledProcN, - eP, RetSiteN); - INC_COUNTER("FF Queries", 1, Full); + auto RetFunction = CachedFlowEdgeFunctions.getRetFlowFunction( + n, SCalledProcN, eP, RetSiteN); + FFQueries++; const container_type ReturnedFacts = computeReturnFlowFunction( RetFunction, d3, d4, n, Container{d2}); - ADD_TO_HISTOGRAM("Data-flow facts", ReturnedFacts.size(), 1, - Full); + DataFlowFacts.add(ReturnedFacts.size(), 1); saveEdges(eP, RetSiteN, d4, ReturnedFacts, ESGEdgeKind::Ret); // for each target value of the function for (d_t d5 : ReturnedFacts) { // update the caller-side summary function // get call edge function - EdgeFunction f4 = - CachedFlowEdgeFunctions.getCallEdgeFunction( - n, d2, SCalledProcN, d3); + auto f4 = CachedFlowEdgeFunctions.getCallEdgeFunction( + n, d2, SCalledProcN, d3); PHASAR_LOG_LEVEL(DEBUG, "Queried Call Edge Function: " << f4); // get return edge function - EdgeFunction f5 = - CachedFlowEdgeFunctions.getReturnEdgeFunction( - n, SCalledProcN, eP, d4, RetSiteN, d5); + auto f5 = CachedFlowEdgeFunctions.getReturnEdgeFunction( + n, SCalledProcN, eP, d4, RetSiteN, d5); PHASAR_LOG_LEVEL(DEBUG, "Queried Return Edge Function: " << f5); if (SolverConfig.emitESG()) { @@ -527,14 +617,14 @@ class IDESolver d5)] .push_back(f5); } - INC_COUNTER("EF Queries", 2, Full); + EFQueries++; // compose call * calleeSummary * return edge functions PHASAR_LOG_LEVEL(DEBUG, "Compose: " << f5 << " * " << fCalleeSummary << " * " << f4); PHASAR_LOG_LEVEL(DEBUG, " (return * calleeSummary * call)"); - EdgeFunction fPrime = IDEProblem.extend( + auto fPrime = IDEProblem.extend( IDEProblem.extend(f4, fCalleeSummary), f5); PHASAR_LOG_LEVEL(DEBUG, " = " << fPrime); d_t d5_restoredCtx = restoreContextOnReturnedFact(n, d2, d5); @@ -555,27 +645,25 @@ class IDESolver // line 17-19 of Naeem/Lhotak/Rodriguez // process intra-procedural flows along call-to-return flow functions for (n_t ReturnSiteN : ReturnSiteNs) { - FlowFunctionPtrType CallToReturnFF = - CachedFlowEdgeFunctions.getCallToRetFlowFunction(n, ReturnSiteN, - Callees); - INC_COUNTER("FF Queries", 1, Full); + auto CallToReturnFF = CachedFlowEdgeFunctions.getCallToRetFlowFunction( + n, ReturnSiteN, Callees); + FFQueries++; container_type ReturnFacts = computeCallToReturnFlowFunction(CallToReturnFF, d1, d2); - ADD_TO_HISTOGRAM("Data-flow facts", ReturnFacts.size(), 1, Full); + DataFlowFacts.add(ReturnFacts.size(), 1); saveEdges(n, ReturnSiteN, d2, ReturnFacts, HasNoCalleeInformation ? ESGEdgeKind::SkipUnknownFn : ESGEdgeKind::CallToRet); for (d_t d3 : ReturnFacts) { - EdgeFunction EdgeFnE = - CachedFlowEdgeFunctions.getCallToRetEdgeFunction(n, d2, ReturnSiteN, - d3, Callees); + auto EdgeFnE = CachedFlowEdgeFunctions.getCallToRetEdgeFunction( + n, d2, ReturnSiteN, d3, Callees); PHASAR_LOG_LEVEL(DEBUG, "Queried Call-to-Return Edge Function: " << EdgeFnE); if (SolverConfig.emitESG()) { IntermediateEdgeFunctions[std::make_tuple(n, d2, ReturnSiteN, d3)] .push_back(EdgeFnE); } - INC_COUNTER("EF Queries", 1, Full); + EFQueries++; auto fPrime = IDEProblem.extend(f, EdgeFnE); PHASAR_LOG_LEVEL(DEBUG, "Compose: " << EdgeFnE << " * " << f << " = " << fPrime); @@ -591,27 +679,26 @@ class IDESolver /// @param edge /// virtual void processNormalFlow(PathEdge Edge) { - PAMM_GET_INSTANCE; - INC_COUNTER("Process Normal", 1, Full); + ProcessNormal++; PHASAR_LOG_LEVEL( DEBUG, "Process normal at target: " << NToString(Edge.getTarget())); - EdgeFunction f = jumpFunction(Edge); + auto f = jumpFunction(Edge); auto [d1, n, d2] = Edge.consume(); const auto &Fun = ICF->getFunctionOf(n); for (const auto nPrime : ICF->getSuccsOf(n)) { - FlowFunctionPtrType FlowFunc = - CachedFlowEdgeFunctions.getNormalFlowFunction(n, nPrime); - INC_COUNTER("FF Queries", 1, Full); + auto FlowFunc = CachedFlowEdgeFunctions.getNormalFlowFunction(n, nPrime); + FFQueries++; const container_type Res = computeNormalFlowFunction(FlowFunc, d1, d2); - ADD_TO_HISTOGRAM("Data-flow facts", Res.size(), 1, Full); + DataFlowFacts.add(Res.size(), 1); saveEdges(n, nPrime, d2, Res, ESGEdgeKind::Normal); - for (d_t d3 : Res) { - EdgeFunction g = + for (const d_t &d3 : Res) { + auto g = CachedFlowEdgeFunctions.getNormalEdgeFunction(n, d2, nPrime, d3); + EFQueries++; PHASAR_LOG_LEVEL(DEBUG, "Queried Normal Edge Function: " << g); - EdgeFunction fPrime = IDEProblem.extend(f, g); + auto fPrime = IDEProblem.extend(f, g); auto DestN = [&, &n = n] { if (auto &&NextUser = getNextUserOrNull(Fun, d3, n)) { @@ -627,7 +714,7 @@ class IDESolver } PHASAR_LOG_LEVEL(DEBUG, "Compose: " << g << " * " << f << " = " << fPrime); - INC_COUNTER("EF Queries", 1, Full); + WorkList.emplace_back(PathEdge(d1, DestN, std::move(d3)), std::move(fPrime)); } @@ -635,7 +722,6 @@ class IDESolver } void propagateValueAtStart(const std::pair NAndD, n_t Stmt) { - PAMM_GET_INSTANCE; d_t Fact = NAndD.second; f_t Func = ICF->getFunctionOf(Stmt); for (const n_t CallSite : ICF->getCallsFromWithin(Func)) { @@ -649,21 +735,20 @@ class IDESolver auto fPrime = Entry.second; n_t SP = Stmt; l_t Val = val(SP, Fact); - INC_COUNTER("Value Propagation", 1, Full); + ValuePropagation++; propagateValue(CallSite, dPrime, fPrime.computeTarget(Val)); } } } void propagateValueAtCall(const std::pair NAndD, n_t Stmt) { - PAMM_GET_INSTANCE; d_t Fact = NAndD.second; for (const f_t Callee : ICF->getCalleesOfCallAt(Stmt)) { - FlowFunctionPtrType CallFlowFunction = + auto CallFlowFunction = CachedFlowEdgeFunctions.getCallFlowFunction(Stmt, Callee); - INC_COUNTER("FF Queries", 1, Full); - for (const d_t dPrime : CallFlowFunction->computeTargets(Fact)) { - EdgeFunction EdgeFn = CachedFlowEdgeFunctions.getCallEdgeFunction( + FFQueries++; + for (const d_t &dPrime : CallFlowFunction->computeTargets(Fact)) { + auto EdgeFn = CachedFlowEdgeFunctions.getCallEdgeFunction( Stmt, Fact, Callee, dPrime); PHASAR_LOG_LEVEL(DEBUG, "Queried Call Edge Function: " << EdgeFn); if (SolverConfig.emitESG()) { @@ -672,9 +757,9 @@ class IDESolver .push_back(EdgeFn); } } - INC_COUNTER("EF Queries", 1, Full); + EFQueries++; for (const n_t StartPoint : ICF->getStartPointsOf(Callee)) { - INC_COUNTER("Value Propagation", 1, Full); + ValuePropagation++; propagateValue(StartPoint, dPrime, EdgeFn.computeTarget(val(Stmt, Fact))); } @@ -754,8 +839,7 @@ class IDESolver // should be made a callable at some point void pathEdgeProcessingTask(PathEdge Edge) { - PAMM_GET_INSTANCE; - INC_COUNTER("JumpFn Construction", 1, Full); + JumpFnConstruction++; IF_LOG_LEVEL_ENABLED(DEBUG, { PHASAR_LOG_LEVEL( DEBUG, @@ -805,7 +889,6 @@ class IDESolver // should be made a callable at some point void valueComputationTask(const std::vector &Values) { - PAMM_GET_INSTANCE; for (n_t n : Values) { for (n_t SP : ICF->getStartPointsOf(ICF->getFunctionOf(n))) { using TableCell = typename Table>::Cell; @@ -815,12 +898,12 @@ class IDESolver LookupByTarget.cellSet()) { d_t dPrime = SourceValTargetValAndFunction.getRowKey(); d_t d = SourceValTargetValAndFunction.getColumnKey(); - EdgeFunction fPrime = SourceValTargetValAndFunction.getValue(); + auto fPrime = SourceValTargetValAndFunction.getValue(); l_t TargetVal = val(SP, dPrime); setVal(n, d, IDEProblem.join(val(n, d), fPrime.computeTarget(std::move(TargetVal)))); - INC_COUNTER("Value Computation", 1, Full); + ValueComputation++; } } } @@ -885,7 +968,6 @@ class IDESolver /// Clients should only call this methods if performing synchronization on /// their own. Normally, solve() should be called instead. void submitInitialSeeds() { - PAMM_GET_INSTANCE; // Check if the initial seeds contain the zero value at every starting // point. If not, the zero value needs to be added to allow for correct // solving of the problem. @@ -917,7 +999,7 @@ class IDESolver PHASAR_LOG_LEVEL(DEBUG, "\tFact: " << DToString(Fact)); PHASAR_LOG_LEVEL(DEBUG, "\tValue: " << LToString(Value)); if (!IDEProblem.isZeroValue(Fact)) { - INC_COUNTER("Gen facts", 1, Core); + Genfacts++; } WorkList.emplace_back(PathEdge(Fact, StartPoint, Fact), EdgeIdentity{}); @@ -934,12 +1016,11 @@ class IDESolver /// @param edge an edge whose target node resembles a method exit /// virtual void processExit(const PathEdge Edge) { - PAMM_GET_INSTANCE; - INC_COUNTER("Process Exit", 1, Full); + ProcessExit++; PHASAR_LOG_LEVEL(DEBUG, "Process exit at target: " << NToString(Edge.getTarget())); n_t n = Edge.getTarget(); // an exit node; line 21... - EdgeFunction f = jumpFunction(Edge); + auto f = jumpFunction(Edge); f_t FunctionThatNeedsSummary = ICF->getFunctionOf(n); d_t d1 = Edge.factAtSource(); d_t d2 = Edge.factAtTarget(); @@ -965,28 +1046,26 @@ class IDESolver // for each return site for (n_t RetSiteC : ICF->getReturnSitesOfCallAt(c)) { // compute return-flow function - FlowFunctionPtrType RetFunction = - CachedFlowEdgeFunctions.getRetFlowFunction( - c, FunctionThatNeedsSummary, n, RetSiteC); - INC_COUNTER("FF Queries", 1, Full); + auto RetFunction = CachedFlowEdgeFunctions.getRetFlowFunction( + c, FunctionThatNeedsSummary, n, RetSiteC); + FFQueries++; // for each incoming-call value for (d_t d4 : Entry.second) { const container_type Targets = computeReturnFlowFunction(RetFunction, d1, d2, c, Entry.second); - ADD_TO_HISTOGRAM("Data-flow facts", Targets.size(), 1, Full); + DataFlowFacts.add(Targets.size(), 1); saveEdges(n, RetSiteC, d2, Targets, ESGEdgeKind::Ret); // for each target value at the return site // line 23 for (d_t d5 : Targets) { // compute composed function // get call edge function - EdgeFunction f4 = CachedFlowEdgeFunctions.getCallEdgeFunction( + auto f4 = CachedFlowEdgeFunctions.getCallEdgeFunction( c, d4, ICF->getFunctionOf(n), d1); PHASAR_LOG_LEVEL(DEBUG, "Queried Call Edge Function: " << f4); // get return edge function - EdgeFunction f5 = - CachedFlowEdgeFunctions.getReturnEdgeFunction( - c, ICF->getFunctionOf(n), n, d2, RetSiteC, d5); + auto f5 = CachedFlowEdgeFunctions.getReturnEdgeFunction( + c, ICF->getFunctionOf(n), n, d2, RetSiteC, d5); PHASAR_LOG_LEVEL(DEBUG, "Queried Return Edge Function: " << f5); if (SolverConfig.emitESG()) { for (auto SP : ICF->getStartPointsOf(ICF->getFunctionOf(n))) { @@ -996,13 +1075,12 @@ class IDESolver IntermediateEdgeFunctions[std::make_tuple(n, d2, RetSiteC, d5)] .push_back(f5); } - INC_COUNTER("EF Queries", 2, Full); + EFQueries += 2; // compose call function * function * return function PHASAR_LOG_LEVEL(DEBUG, "Compose: " << f5 << " * " << f << " * " << f4); PHASAR_LOG_LEVEL(DEBUG, " (return * function * call)"); - EdgeFunction fPrime = - IDEProblem.extend(IDEProblem.extend(f4, f), f5); + auto fPrime = IDEProblem.extend(IDEProblem.extend(f4, f), f5); PHASAR_LOG_LEVEL(DEBUG, " = " << fPrime); // for each jump function coming into the call, propagate to // return site using the composed function @@ -1010,7 +1088,7 @@ class IDESolver if (RevLookupResult) { for (size_t I = 0; I < RevLookupResult->get().size(); ++I) { auto ValAndFunc = RevLookupResult->get()[I]; - EdgeFunction f3 = ValAndFunc.second; + auto f3 = ValAndFunc.second; if (f3 != AllTop) { d_t d3 = ValAndFunc.first; d_t d5_restoredCtx = restoreContextOnReturnedFact(c, d4, d5); @@ -1047,24 +1125,22 @@ class IDESolver const auto &Callers = ICF->getCallersOf(FunctionThatNeedsSummary); for (n_t Caller : Callers) { for (n_t RetSiteC : ICF->getReturnSitesOfCallAt(Caller)) { - FlowFunctionPtrType RetFunction = - CachedFlowEdgeFunctions.getRetFlowFunction( - Caller, FunctionThatNeedsSummary, n, RetSiteC); - INC_COUNTER("FF Queries", 1, Full); + auto RetFunction = CachedFlowEdgeFunctions.getRetFlowFunction( + Caller, FunctionThatNeedsSummary, n, RetSiteC); + FFQueries++; const container_type Targets = computeReturnFlowFunction( RetFunction, d1, d2, Caller, Container{ZeroValue}); - ADD_TO_HISTOGRAM("Data-flow facts", Targets.size(), 1, Full); + DataFlowFacts.add(Targets.size(), 1); saveEdges(n, RetSiteC, d2, Targets, ESGEdgeKind::Ret); for (d_t d5 : Targets) { - EdgeFunction f5 = - CachedFlowEdgeFunctions.getReturnEdgeFunction( - Caller, ICF->getFunctionOf(n), n, d2, RetSiteC, d5); + auto f5 = CachedFlowEdgeFunctions.getReturnEdgeFunction( + Caller, ICF->getFunctionOf(n), n, d2, RetSiteC, d5); PHASAR_LOG_LEVEL(DEBUG, "Queried Return Edge Function: " << f5); if (SolverConfig.emitESG()) { IntermediateEdgeFunctions[std::make_tuple(n, d2, RetSiteC, d5)] .push_back(f5); } - INC_COUNTER("EF Queries", 1, Full); + EFQueries++; PHASAR_LOG_LEVEL(DEBUG, "Compose: " << f5 << " * " << f); propagteUnbalancedReturnFlow(RetSiteC, d5, IDEProblem.extend(f, f5), Caller); @@ -1123,14 +1199,13 @@ class IDESolver /// @param d2 The abstraction at the current node /// @return The set of abstractions at the successor node /// - container_type computeNormalFlowFunction(const FlowFunctionPtrType &FlowFunc, - d_t /*d1*/, d_t d2) { + container_type computeNormalFlowFunction(const auto &FlowFunc, d_t /*d1*/, + d_t d2) { return FlowFunc->computeTargets(d2); } - container_type - computeSummaryFlowFunction(const FlowFunctionPtrType &SummaryFlowFunction, - d_t /*d1*/, d_t d2) { + container_type computeSummaryFlowFunction(const auto &SummaryFlowFunction, + d_t /*d1*/, d_t d2) { return SummaryFlowFunction->computeTargets(d2); } @@ -1140,9 +1215,8 @@ class IDESolver /// @param d2 The abstraction at the call site /// @return The set of caller-side abstractions at the callee's start node /// - container_type - computeCallFlowFunction(const FlowFunctionPtrType &CallFlowFunction, - d_t /*d1*/, d_t d2) { + container_type computeCallFlowFunction(const auto &CallFlowFunction, + d_t /*d1*/, d_t d2) { return CallFlowFunction->computeTargets(d2); } @@ -1154,8 +1228,9 @@ class IDESolver /// @param d2 The abstraction at the call site /// @return The set of caller-side abstractions at the return site /// - container_type computeCallToReturnFlowFunction( - const FlowFunctionPtrType &CallToReturnFlowFunction, d_t /*d1*/, d_t d2) { + container_type + computeCallToReturnFlowFunction(const auto &CallToReturnFlowFunction, + d_t /*d1*/, d_t d2) { return CallToReturnFlowFunction->computeTargets(d2); } @@ -1168,10 +1243,9 @@ class IDESolver /// @param callerSideDs The abstractions at the call site /// @return The set of caller-side abstractions at the return site /// - container_type - computeReturnFlowFunction(const FlowFunctionPtrType &RetFlowFunction, - d_t /*d1*/, d_t d2, n_t /*CallSite*/, - const Container & /*CallerSideDs*/) { + container_type computeReturnFlowFunction(const auto &RetFlowFunction, + d_t /*d1*/, d_t d2, n_t /*CallSite*/, + const Container & /*CallerSideDs*/) { return RetFlowFunction->computeTargets(d2); } @@ -1200,7 +1274,7 @@ class IDESolver PHASAR_LOG_LEVEL( DEBUG, "Edge function : " << f << " (result of previous compose)"); - EdgeFunction JumpFnE = [&]() { + auto JumpFnE = [&]() { const auto RevLookupResult = JumpFn->reverseLookup(Target, TargetVal); if (RevLookupResult) { const auto &JumpFnContainer = RevLookupResult->get(); @@ -1215,7 +1289,7 @@ class IDESolver // was found return AllTop; }(); - EdgeFunction fPrime = IDEProblem.combine(JumpFnE, f); + auto fPrime = IDEProblem.combine(JumpFnE, f); bool NewFunction = fPrime != JumpFnE; IF_LOG_LEVEL_ENABLED(DEBUG, { @@ -1389,13 +1463,12 @@ class IDESolver /// generated/killed facts, number of summary-reuses etc. /// void computeAndPrintStatistics() { - PAMM_GET_INSTANCE; // Stores all valid facts at return site in caller context; return-site is // key std::unordered_map> ValidInCallerContext; - size_t NumGenFacts = 0; - size_t NumIntraPathEdges = 0; - size_t NumInterPathEdges = 0; + ptrdiff_t NumGenFacts = 0; + ptrdiff_t NumIntraPathEdges = 0; + ptrdiff_t NumInterPathEdges = 0; // --- Intra-procedural Path Edges --- // d1 --> d2-Set // Case 1: d1 in d2-Set @@ -1419,7 +1492,7 @@ class IDESolver if (ICF->isCallSite(Edge.first)) { ValidInCallerContext[Edge.second].insert(D2s.begin(), D2s.end()); } - IF_LOG_LEVEL_ENABLED(DEBUG, [this](const auto &D2s) { + IF_LOG_LEVEL_ENABLED(DEBUG, [](const auto &D2s) { for (auto D2 : D2s) { PHASAR_LOG_LEVEL(DEBUG, "d2: " << DToString(D2)); } @@ -1464,8 +1537,9 @@ class IDESolver std::set SummaryDSet; EndsummaryTab.get(Edge.second, D2) - .foreachCell([&SummaryDSet](const auto &Row, const auto &Col, - const auto &Val) { + .foreachCell([&SummaryDSet](const auto & /*Row*/, + const auto &Col, + const auto & /*Val*/) { SummaryDSet.insert(Col); }); @@ -1508,46 +1582,40 @@ class IDESolver PHASAR_LOG_LEVEL(DEBUG, " "); } PHASAR_LOG_LEVEL(DEBUG, "SUMMARY REUSE"); - std::size_t TotalSummaryReuse = 0; + ptrdiff_t TotalSummaryReuse = 0; for (const auto &Entry : FSummaryReuse) { PHASAR_LOG_LEVEL(DEBUG, "N1: " << NToString(Entry.first.first)); PHASAR_LOG_LEVEL(DEBUG, "D1: " << DToString(Entry.first.second)); PHASAR_LOG_LEVEL(DEBUG, "#Reuse: " << Entry.second); TotalSummaryReuse += Entry.second; } - INC_COUNTER("Gen facts", NumGenFacts, Core); - INC_COUNTER("Summary-reuse", TotalSummaryReuse, Core); - INC_COUNTER("Intra Path Edges", NumIntraPathEdges, Core); - INC_COUNTER("Inter Path Edges", NumInterPathEdges, Core); + Genfacts += NumGenFacts; + Summaryreuse += TotalSummaryReuse; + IntraPathEdges += NumIntraPathEdges; + InterPathEdges += NumInterPathEdges; PHASAR_LOG_LEVEL(INFO, "----------------------------------------------"); PHASAR_LOG_LEVEL(INFO, "=== Solver Statistics ==="); - PHASAR_LOG_LEVEL(INFO, "#Facts generated : " << GET_COUNTER("Gen facts")); - PHASAR_LOG_LEVEL(INFO, "#Facts killed : " << GET_COUNTER("Kill facts")); - PHASAR_LOG_LEVEL(INFO, - "#Summary-reuse : " << GET_COUNTER("Summary-reuse")); - PHASAR_LOG_LEVEL(INFO, - "#Intra Path Edges: " << GET_COUNTER("Intra Path Edges")); - PHASAR_LOG_LEVEL(INFO, - "#Inter Path Edges: " << GET_COUNTER("Inter Path Edges")); + PHASAR_LOG_LEVEL(INFO, "#Facts generated : " << Genfacts.value()); + PHASAR_LOG_LEVEL(INFO, "#Facts killed : " << Killfacts.value()); + PHASAR_LOG_LEVEL(INFO, "#Summary-reuse : " << Summaryreuse.value()); + PHASAR_LOG_LEVEL(INFO, "#Intra Path Edges: " << IntraPathEdges.value()); + PHASAR_LOG_LEVEL(INFO, "#Inter Path Edges: " << InterPathEdges.value()); if constexpr (PAMM_CURR_SEV_LEVEL >= PAMM_SEVERITY_LEVEL::Full) { - PHASAR_LOG_LEVEL( - INFO, "Flow function query count: " << GET_COUNTER("FF Queries")); - PHASAR_LOG_LEVEL( - INFO, "Edge function query count: " << GET_COUNTER("EF Queries")); + PHASAR_LOG_LEVEL(INFO, + "Flow function query count: " << FFQueries.value()); + PHASAR_LOG_LEVEL(INFO, + "Edge function query count: " << EFQueries.value()); PHASAR_LOG_LEVEL(INFO, "Data-flow value propagation count: " - << GET_COUNTER("Value Propagation")); + << ValuePropagation.value()); PHASAR_LOG_LEVEL(INFO, "Data-flow value computation count: " - << GET_COUNTER("Value Computation")); - PHASAR_LOG_LEVEL(INFO, - "Special flow function usage count: " - << GET_COUNTER("SpecialSummary-FF Application")); + << ValueComputation.value()); + PHASAR_LOG_LEVEL(INFO, "Special flow function usage count: " + << SpecialSummaryFF_Application.value()); PHASAR_LOG_LEVEL(INFO, "Jump function construciton count: " - << GET_COUNTER("JumpFn Construction")); - PHASAR_LOG_LEVEL(INFO, - "Phase I duration: " << PRINT_TIMER("DFA Phase I")); - PHASAR_LOG_LEVEL(INFO, - "Phase II duration: " << PRINT_TIMER("DFA Phase II")); + << JumpFnConstruction.value()); + PHASAR_LOG_LEVEL(INFO, "Phase I duration: " << DFAPhase1.elapsed()); + PHASAR_LOG_LEVEL(INFO, "Phase II duration: " << DFAPhase2.elapsed()); PHASAR_LOG_LEVEL(INFO, "----------------------------------------------"); CachedFlowEdgeFunctions.print(); } @@ -1772,13 +1840,6 @@ class IDESolver } private: - template - static auto nextUserOrNullThunk(const void *SVFG, ByConstRef Fun, - ByConstRef d3, ByConstRef n) { - auto &&SCFG = static_cast(SVFG)->getSparseCFG(Fun, d3); - return SCFG.nextUserOrNull(n); - }; - /// @brief: Allows less-than comparison based on the statement ID. struct StmtLess { const i_t *ICF; @@ -1792,31 +1853,11 @@ class IDESolver /// -- InteractiveIDESolverMixin implementation void doInitialize() { - PAMM_GET_INSTANCE; - REG_COUNTER("Gen facts", 0, Core); - REG_COUNTER("Kill facts", 0, Core); - REG_COUNTER("Summary-reuse", 0, Core); - REG_COUNTER("Intra Path Edges", 0, Core); - REG_COUNTER("Inter Path Edges", 0, Core); - REG_COUNTER("FF Queries", 0, Full); - REG_COUNTER("EF Queries", 0, Full); - REG_COUNTER("Value Propagation", 0, Full); - REG_COUNTER("Value Computation", 0, Full); - REG_COUNTER("SpecialSummary-FF Application", 0, Full); - REG_COUNTER("SpecialSummary-EF Queries", 0, Full); - REG_COUNTER("JumpFn Construction", 0, Full); - REG_COUNTER("Process Call", 0, Full); - REG_COUNTER("Process Normal", 0, Full); - REG_COUNTER("Process Exit", 0, Full); - REG_COUNTER("[Calls] getAliasSet", 0, Full); - REG_HISTOGRAM("Data-flow facts", Full); - REG_HISTOGRAM("Points-to", Full); - PHASAR_LOG_LEVEL(INFO, "IDE solver is solving the specified problem"); PHASAR_LOG_LEVEL(INFO, "Submit initial seeds, construct exploded super graph"); // computations starting here - START_TIMER("DFA Phase I", Full); + DFAPhase1.start(); // We start our analysis and construct exploded supergraph submitInitialSeeds(); @@ -1838,17 +1879,15 @@ class IDESolver } void finalizeInternal() { - PAMM_GET_INSTANCE; - STOP_TIMER("DFA Phase I", Full); + DFAPhase1.stop(); PHASAR_LOG_LEVEL(INFO, "[info]: IDE Phase I completed"); if (SolverConfig.computeValues()) { - START_TIMER("DFA Phase II", Full); + PAMM_SCOPED_TIMER(DFAPhase2); // Computing the final values for the edge functions PHASAR_LOG_LEVEL( INFO, "Compute the final values according to the edge functions"); computeValues(); - STOP_TIMER("DFA Phase II", Full); } PHASAR_LOG_LEVEL(INFO, "Problem solved"); @@ -1875,17 +1914,15 @@ class IDESolver IDETabulationProblem &IDEProblem; d_t ZeroValue; const i_t *ICF; - const void *SVFG; IFDSIDESolverConfig &SolverConfig; - Nullable (*NextUserOrNullCB)(const void *, ByConstRef, - ByConstRef, ByConstRef) = nullptr; std::vector, EdgeFunction>> WorkList; std::vector> ValuePropWL; size_t PathEdgeCount = 0; - FlowEdgeFunctionCache CachedFlowEdgeFunctions; + FlowEdgeFunctionCache> + CachedFlowEdgeFunctions; Table> ComputedIntraPathEdges; @@ -1893,7 +1930,8 @@ class IDESolver EdgeFunction AllTop; - std::shared_ptr> JumpFn; + std::unique_ptr> JumpFn = + std::make_unique>(); std::map, std::vector>> IntermediateEdgeFunctions; @@ -1917,37 +1955,29 @@ class IDESolver std::map, size_t> FSummaryReuse; }; -template -llvm::raw_ostream & -operator<<(llvm::raw_ostream &OS, - const IDESolver &Solver) { - Solver.dumpResults(OS); - return OS; -} - template -IDESolver(Problem &, ICF *) +IDESolver(Problem &, const ICF *) -> IDESolver; + typename Problem::container_type, ICF>; template -IDESolver(Problem *, ICF *) +IDESolver(Problem *, const ICF *) -> IDESolver; + typename Problem::container_type, ICF>; template -using IDESolver_P = IDESolver; - -template -OwningSolverResults -solveIDEProblem( - IDETabulationProblem &Problem, - const std::convertible_to auto - &ICF) { - IDESolver Solver(&Problem, &ICF); +using IDESolver_P + [[deprecated("Let C++ deduce the template arguments of IDESolver, or call " + "solveIDEProblem() instead")]] = + IDESolver; + +template +auto solveIDEProblem(ProblemTy &Problem, const ICFG auto &ICF) + -> OwningSolverResults { + IDESolver Solver(&Problem, &ICF); Solver.solve(); return Solver.consumeSolverResults(); } diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h index b8af90707c..755d6e8684 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IDESolverAPIMixin.h @@ -422,6 +422,9 @@ template class IDESolverAPIMixin { } private: + friend Derived; + constexpr IDESolverAPIMixin() noexcept = default; + [[nodiscard]] constexpr Derived &self() & noexcept { static_assert(std::is_base_of_v, "Invalid CRTP instantiation"); diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h index 59055acdb3..2988b61def 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IFDSSolver.h @@ -17,6 +17,7 @@ #ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_IFDSSOLVER_H #define PHASAR_DATAFLOW_IFDSIDE_SOLVER_IFDSSOLVER_H +#include "phasar/DataFlow/IfdsIde/IFDSProblem.h" #include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" #include "phasar/DataFlow/IfdsIde/Solver/IDESolver.h" #include "phasar/Domain/BinaryDomain.h" @@ -37,26 +38,46 @@ namespace psr { /// IFDSIDESolverConfig#setComputeValues(bool) to disable IDE's /// phase 2. template > -class IFDSSolver - : public IDESolver, Container> { + typename Container = std::set, + ICFG ICFGTy = typename AnalysisDomainTy::i_t> +class IFDSSolver : public IDESolver, + Container, ICFGTy> { + using Base = + IDESolver, Container, ICFGTy>; + public: using ProblemTy = IFDSTabulationProblem; using d_t = typename AnalysisDomainTy::d_t; using n_t = typename AnalysisDomainTy::n_t; - using i_t = typename AnalysisDomainTy::i_t; - - template IfdsDomainTy, - std::convertible_to I> - IFDSSolver(IFDSTabulationProblem &IFDSProblem, - const I *ICF) - : IDESolver>(IFDSProblem, ICF) {} - - template IfdsDomainTy, - std::convertible_to I> - IFDSSolver(IFDSTabulationProblem *IFDSProblem, - const I *ICF) - : IDESolver>(IFDSProblem, ICF) {} + using i_t = ICFGTy; + + template + requires(std::same_as< + WithBinaryValueDomain, + WithBinaryValueDomain>) + [[deprecated( + "Use the other overload of IFDSSolver() instead, which takes the " + "ifds-problem by pointer. This documents better that the solver captures " + "the address without taking ownership")]] IFDSSolver(ProblemTy + &IFDSProblem, + const ICFGTy *ICF) + : Base(IFDSProblem, ICF) {} + + template + requires(std::same_as< + WithBinaryValueDomain, + WithBinaryValueDomain>) + IFDSSolver(ProblemTy *IFDSProblem, const ICFGTy *ICF) + : Base(IFDSProblem, ICF) {} + + /// Uses SolverConfig instead of the problem's solver config + template + requires(std::same_as< + WithBinaryValueDomain, + WithBinaryValueDomain>) + IFDSSolver(ProblemTy *IFDSProblem, const ICFGTy *ICF, + IFDSIDESolverConfig SolverConfig) + : Base(IFDSProblem, ICF, SolverConfig) {} ~IFDSSolver() override = default; @@ -110,26 +131,31 @@ class IFDSSolver }; template -IFDSSolver(Problem &, ICF *) +IFDSSolver(Problem &, const ICF *) + -> IFDSSolver; +template +IFDSSolver(Problem *, const ICF *) -> IFDSSolver; + typename Problem::container_type, ICF>; template -IFDSSolver(Problem *, ICF *) +IFDSSolver(Problem *, const ICF *, IFDSIDESolverConfig) -> IFDSSolver; + typename Problem::container_type, ICF>; template -using IFDSSolver_P = IFDSSolver; - -template -OwningSolverResults -solveIFDSProblem( - IFDSTabulationProblem &Problem, - const std::convertible_to auto - &ICF) { - IFDSSolver Solver(Problem, &ICF); +using IFDSSolver_P + [[deprecated("Let C++ deduce the template arguments of IFDSSolver, or call " + "solveIFDSProblem() instead")]] = + IFDSSolver; + +template +auto solveIFDSProblem(ProblemTy &Problem, const ICFG auto &ICF) + -> OwningSolverResults { + IFDSSolver Solver(&Problem, &ICF); Solver.solve(); return Solver.consumeSolverResults(); } diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h b/include/phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h index c8e746079e..3fe24970e8 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h @@ -3,8 +3,11 @@ #include "phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverResults.h" #include "phasar/Utils/ByRef.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/Utilities.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" #include #include @@ -12,6 +15,11 @@ #include #include +namespace llvm { +class Instruction; +class Value; +} // namespace llvm + namespace psr { namespace detail { @@ -31,7 +39,7 @@ class IdBasedSolverResultsBase { // --v Needed for llvm::mapped_iterator // NOLINTNEXTLINE(readability-const-return-type) - const std::pair + const std::pair, ByConstRef> operator()(ByConstRef Entry) const { return {Results->FactCompressor[Entry.first], Results->ValCompressor[Entry.second]}; @@ -81,6 +89,7 @@ class IdBasedSolverResultsBase { } [[nodiscard]] size_t size() const noexcept { return Row->size(); } + [[nodiscard]] bool empty() const noexcept { return Row->empty(); } private: const detail::IterativeIDESolverResults *Results{}; @@ -174,6 +183,49 @@ class IdBasedSolverResultsBase { } } + /// Returns the data-flow results at the given statement while respecting + /// LLVM's SSA semantics. + /// + /// An example: when a value is loaded and the location loaded from, here + /// variable 'i', is a data-flow fact that holds, then the loaded value '%0' + /// will usually be generated and also holds. However, due to the underlying + /// theory (and respective implementation) this load instruction causes the + /// loaded value to be generated and thus, it will be valid only AFTER the + /// load instruction, i.e., at the successor instruction. + /// + /// %0 = load i32, i32* %i, align 4 + /// + /// This result accessor function returns the results at the successor + /// instruction(s) reflecting that the expression on the left-hand side holds + /// if the expression on the right-hand side holds. + [[nodiscard]] std::unordered_map + resultsAtInLLVMSSA(ByConstRef Stmt, + bool AllowOverapproximation = false) const + requires same_as_decay, llvm::Instruction>; + + /// Returns the L-type result at the given statement for the given data-flow + /// fact while respecting LLVM's SSA semantics. + /// + /// An example: when a value is loaded and the location loaded from, here + /// variable 'i', is a data-flow fact that holds, then the loaded value '%0' + /// will usually be generated and also holds. However, due to the underlying + /// theory (and respective implementation) this load instruction causes the + /// loaded value to be generated and thus, it will be valid only AFTER the + /// load instruction, i.e., at the successor instruction. + /// + /// %0 = load i32, i32* %i, align 4 + /// + /// This result accessor function returns the results at the successor + /// instruction(s) reflecting that the expression on the left-hand side holds + /// if the expression on the right-hand side holds. + [[nodiscard]] l_t resultAtInLLVMSSA(ByConstRef Stmt, d_t Value, + bool AllowOverapproximation = false) const + requires same_as_decay, llvm::Instruction>; + + template + void dumpResults(const ICFGTy &ICF, + llvm::raw_ostream &OS = llvm::outs()) const; + private: [[nodiscard]] constexpr const auto &results() const noexcept { return static_cast(this)->resultsImpl(); @@ -219,6 +271,17 @@ class OwningIdBasedSolverResults assert(this->Results != nullptr); } + [[nodiscard]] IdBasedSolverResults get() const & noexcept { + return IdBasedSolverResults{Results.get()}; + } + IdBasedSolverResults get() && = delete; + + [[nodiscard]] operator IdBasedSolverResults() const & noexcept { + return get(); + } + + operator IdBasedSolverResults() && = delete; + private: [[nodiscard]] constexpr const auto &resultsImpl() const noexcept { assert(Results != nullptr); @@ -227,6 +290,57 @@ class OwningIdBasedSolverResults std::unique_ptr> Results{}; }; + +// For sorting the results in dumpResults() +std::string getMetaDataID(const llvm::Value *V); + +template +template +void detail::IdBasedSolverResultsBase::dumpResults( + const ICFGTy &ICF, llvm::raw_ostream &OS) const { + using f_t = typename ICFGTy::f_t; + + auto ResultEntries = llvm::to_vector(getAllResultEntries()); + + std::ranges::sort(ResultEntries, [](const auto &Lhs, const auto &Rhs) { + const auto &LRow = std::get<0>(Lhs); + const auto &RRow = std::get<0>(Rhs); + if constexpr (std::is_same_v) { + return StringIDLess{}(getMetaDataID(LRow), getMetaDataID(RRow)); + } else { + // If non-LLVM IR is used + return LRow < RRow; + } + }); + + OS << "\n***************************************************************\n" + << "* Raw IterativeIDESolver results *\n" + << "***************************************************************\n"; + + f_t PrevFn = f_t{}; + f_t CurrFn = f_t{}; + + for (const auto &[Row, Column] : ResultEntries) { + if (Column.empty()) { + continue; + } + CurrFn = ICF.getFunctionOf(Row); + if (PrevFn != CurrFn) { + PrevFn = CurrFn; + OS << "\n\n============ Results for function '" + + ICF.getFunctionName(CurrFn) + "' ============\n"; + } + + std::string NString = NToString(Row); + std::string Line(NString.size(), '-'); + OS << "\n\nN: " << NString << "\n---" << Line << '\n'; + + for (const auto &[Col, Val] : Column) { + OS << "\tD: " << DToString(Col) << " | V: " << LToString(Val) << '\n'; + } + } + OS << '\n'; +} } // namespace psr #endif // PHASAR_PHASARLLVM_DATAFLOWSOLVER_IFDSIDE_SOLVER_IDBASEDSOLVERRESULTS_H diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h index bbd370a715..590b4ff686 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h @@ -1,8 +1,10 @@ #ifndef PHASAR_DATAFLOW_IFDSIDE_SOLVER_ITERATIVEIDESOLVER_H #define PHASAR_DATAFLOW_IFDSIDE_SOLVER_ITERATIVEIDESOLVER_H +#include "phasar/ControlFlow/SparseCFGProvider.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctions.h" #include "phasar/DataFlow/IfdsIde/Solver/Compressor.h" +#include "phasar/DataFlow/IfdsIde/Solver/ESGEdgeKind.h" #include "phasar/DataFlow/IfdsIde/Solver/EdgeFunctionCache.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheNG.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowFunctionCache.h" @@ -17,11 +19,13 @@ #include "phasar/Domain/BinaryDomain.h" #include "phasar/Utils/ByRef.h" #include "phasar/Utils/EmptyBaseOptimizationUtils.h" +#include "phasar/Utils/Lazy.h" #include "phasar/Utils/Logger.h" #include "phasar/Utils/Printer.h" #include "phasar/Utils/StableVector.h" #include "phasar/Utils/TableWrappers.h" #include "phasar/Utils/TypeTraits.h" +#include "phasar/Utils/Utilities.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMapInfo.h" @@ -39,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -53,8 +58,9 @@ namespace psr { /// Applications: An Experience Report" /// () by Schiebel, Sattler, /// Schubert, Apel, and Bodden. -template > +template , + ICFG ICFGTy = typename ProblemTy::ProblemAnalysisDomain::i_t> class IterativeIDESolver : private SolverStatsSelector, private detail::IterativeIDESolverResults< @@ -63,14 +69,13 @@ class IterativeIDESolver std::conditional_t>, - public IterativeIDESolverBase< - StaticSolverConfigTy, - typename StaticSolverConfigTy::template EdgeFunctionPtrType< - typename ProblemTy::ProblemAnalysisDomain::l_t>>, + public IterativeIDESolverBase, public IDESolverAPIMixin< - IterativeIDESolver> { + IterativeIDESolver> { - friend IDESolverAPIMixin>; + friend IDESolverAPIMixin< + IterativeIDESolver>; public: using domain_t = typename ProblemTy::ProblemAnalysisDomain; @@ -81,15 +86,13 @@ class IterativeIDESolver using v_t = typename domain_t::v_t; using l_t = std::conditional_t; - using i_t = typename domain_t::i_t; + using i_t = ICFGTy; using config_t = StaticSolverConfigTy; private: - using base_t = IterativeIDESolverBase< - StaticSolverConfigTy, - typename StaticSolverConfigTy::template EdgeFunctionPtrType< - typename domain_t::l_t>>; + using base_t = IterativeIDESolverBase; using base_results_t = detail::IterativeIDESolverResults; @@ -124,31 +127,14 @@ class IterativeIDESolver using typename base_t::summaries_t; - static inline constexpr JumpFunctionGCMode EnableJumpFunctionGC = + static constexpr JumpFunctionGCMode EnableJumpFunctionGC = StaticSolverConfigTy::EnableJumpFunctionGC; - static inline ProblemTy &assertNotNull(ProblemTy *Problem) noexcept { - /// Dereferencing a nullptr is UB, so after initializing this->Problem the - /// null-check might be optimized away to the literal 'true'. - /// However, we still want to pass a pointer to the ctor to make clear that - /// the _reference_ of the problem is captured. - assert(Problem && - "IterativeIDESolver: The IDETabulationProblem must not be null!"); - return *Problem; - } - - static inline const i_t &assertNotNull(const i_t *ICFG) noexcept { - /// Dereferencing a nullptr is UB, so after initializing this->ICFG the - /// null-check might be optimized away to the literal 'true'. - /// However, we still want to pass a pointer to the ctor to make clear that - /// the _reference_ of the problem is captured. - assert(ICFG && "IterativeIDESolver: The ICFG must not be null!"); - return *ICFG; - } - public: - IterativeIDESolver(ProblemTy *Problem, const i_t *ICFG) noexcept - : Problem(assertNotNull(Problem)), ICFG(assertNotNull(ICFG)) {} + IterativeIDESolver(ProblemTy *Problem, const ICFGTy *ICFG, + StaticSolverConfigTy /*Config*/ = {}) noexcept + : Problem(assertNotNull(Problem)), ICFG(assertNotNull(ICFG)), + PathData(StaticSolverConfigTy::initPathData(assertNotNull(Problem))) {} auto solve() & { solveImpl(); @@ -170,54 +156,17 @@ class IterativeIDESolver } void dumpResults(llvm::raw_ostream &OS = llvm::outs()) const { - OS << "\n***************************************************************\n" - << "* Raw IDESolver results *\n" - << "***************************************************************\n"; - auto Cells = this->base_results_t::ValTab_cellVec(); - if (Cells.empty()) { - OS << "No results computed!\n"; - return; - } - - std::sort(Cells.begin(), Cells.end(), [](const auto &Lhs, const auto &Rhs) { - if constexpr (std::is_same_v) { - return StringIDLess{}(getMetaDataID(Lhs.getRowKey()), - getMetaDataID(Rhs.getRowKey())); - } else { - // If non-LLVM IR is used - return Lhs.getRowKey() < Rhs.getRowKey(); - } - }); - - n_t Prev{}; - n_t Curr{}; - f_t PrevFn{}; - f_t CurrFn{}; - - for (const auto &Cell : Cells) { - Curr = Cell.getRowKey(); - CurrFn = ICFG.getFunctionOf(Curr); - if (PrevFn != CurrFn) { - PrevFn = CurrFn; - OS << "\n\n============ Results for function '" + - ICFG.getFunctionName(CurrFn) + "' ============\n"; - } - if (Prev != Curr) { - Prev = Curr; - std::string NString = NToString(Curr); - std::string Line(NString.size(), '-'); - - OS << "\n\nN: " << NString << "\n---" << Line << '\n'; - } - OS << "\tD: " << DToString(Cell.getColumnKey()); - if constexpr (ComputeValues) { - OS << " | V: " << LToString(Cell.getValue()); - } - - OS << '\n'; - } + getSolverResults().dumpResults(ICFG, OS); + } - OS << '\n'; + [[nodiscard]] const auto &getPathAwareData() const & noexcept { + return PathData; + } + [[nodiscard]] auto getPathAwareData() && noexcept { + return std::move(PathData); + } + [[nodiscard]] auto consumePathAwareData() noexcept { + return std::move(PathData); } [[nodiscard]] IterativeIDESolverStats getStats() const noexcept @@ -337,6 +286,10 @@ class IterativeIDESolver } } + // Flow functions are not consulted after Phase I ends; release them early + // to reduce peak memory. Edge functions are kept until + // performValuePropagation clears the whole cache, as they may still be + // needed for summary queries. FECache.clearFlowFunctions(); SourceFactAndFuncToInterJob.clear(); WorkList.clear(); @@ -369,8 +322,6 @@ class IterativeIDESolver } void performDataflowFactPropagation() { - // submitInitialSeeds(); - std::atomic_bool Finished = true; do { /// NOTE: Have a separate function on the worklist to process it, to @@ -418,9 +369,9 @@ class IterativeIDESolver void performValuePropagation() { if constexpr (ComputeValues) { - /// NOTE: We can already clear the EFCache here, as we are not querying - /// any edge function in Phase II; The EFs that are in use are kept alive - /// by their shared_ptr + /// NOTE: Safe to clear here: Phase II only reads EdgeFunction values + /// from the JumpFunctions table, which stores them by value. Neither + /// flow functions nor the EdgeFunctionCache are consulted in Phase II. FECache.clear(); submitInitialValues(); @@ -449,9 +400,9 @@ class IterativeIDESolver void submitInitialSeeds() { auto Seeds = Problem.initialSeeds(); - EdgeFunctionPtrType IdFun = [] { + EdgeFunctionPtrType IdFun = [&] { if constexpr (ComputeValues) { - return EdgeIdentity{}; + return Problem.identity(); } else { return EdgeFunctionPtrType{}; } @@ -462,9 +413,8 @@ class IterativeIDESolver auto Fun = FunCompressor.getOrInsert(ICFG.getFunctionOf(Inst)); for (const auto &[Fact, Val] : SeedMap) { auto FactId = FactCompressor.getOrInsert(Fact); - auto &JumpFns = JumpFunctions[InstId]; - storeResultsAndPropagate(JumpFns, InstId, FactId, FactId, Fun, IdFun); + storeResultsAndPropagate(InstId, FactId, FactId, Fun, IdFun); } } } @@ -514,9 +464,9 @@ class IterativeIDESolver } } - bool storeResultsAndPropagate(SummaryEdges &JumpFns, uint32_t SuccId, - uint32_t SourceFact, uint32_t LocalFact, - uint32_t FunId, EdgeFunctionPtrType LocalEF) + bool storeResultsAndPropagate(uint32_t SuccId, uint32_t SourceFact, + uint32_t LocalFact, uint32_t FunId, + EdgeFunctionPtrType LocalEF) requires ComputeValues { @@ -526,6 +476,14 @@ class IterativeIDESolver return false; } + if constexpr (has_advanceToNextUser_v) { + auto &&FactSucc = ICFG.advanceToNextUser(NodeCompressor[SuccId], + FactCompressor[LocalFact]); + SuccId = NodeCompressor.getOrInsert(PSR_FWD(FactSucc)); + } + + auto &JumpFns = JumpFunctions[SuccId]; + auto &EF = JumpFns.getOrCreate(combineIds(SourceFact, LocalFact)); if (!EF) { EF = std::move(LocalEF); @@ -572,11 +530,19 @@ class IterativeIDESolver return false; } - bool storeResultsAndPropagate(SummaryEdges &JumpFns, uint32_t SuccId, - uint32_t SourceFact, uint32_t LocalFact, - uint32_t FunId, EdgeFunctionPtrType /*LocalEF*/) + bool storeResultsAndPropagate(uint32_t SuccId, uint32_t SourceFact, + uint32_t LocalFact, uint32_t FunId, + EdgeFunctionPtrType /*LocalEF*/) requires(!ComputeValues) { + + if constexpr (has_advanceToNextUser_v) { + auto &&FactSucc = ICFG.advanceToNextUser(NodeCompressor[SuccId], + FactCompressor[LocalFact]); + SuccId = NodeCompressor.getOrInsert(PSR_FWD(FactSucc)); + } + auto &JumpFns = JumpFunctions[SuccId]; + if (JumpFns.insert(combineIds(SourceFact, LocalFact)).second) { WorkList.emplace(PropagationJob{{}, SuccId, SourceFact, LocalFact}); @@ -696,7 +662,7 @@ class IterativeIDESolver combineIds(AtInstructionId, SuccId)) .computeTargets(CSFact); - auto &JumpFns = JumpFunctions[SuccId]; + saveEdges(AtInstruction, Succ, CSFact, Facts, ESGEdgeKind::Normal); for (ByConstRef Fact : Facts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -712,7 +678,7 @@ class IterativeIDESolver } }(); - storeResultsAndPropagate(JumpFns, SuccId, SourceFactId, FactId, FunId, + storeResultsAndPropagate(SuccId, SourceFactId, FactId, FunId, std::move(EF)); } } @@ -744,20 +710,21 @@ class IterativeIDESolver EdgeFunctionPtrType SourceEF, uint32_t FunId) { const auto &Callees = ICFG.getCalleesOfCallAt(AtInstruction); - applyCallToReturnFlow(AtInstruction, AtInstructionId, SourceFactId, - PropagatedFactId, SourceEF, Callees, FunId); + bool HasNoCalleeInformation = + handleOrDeferCallFlow(AtInstruction, AtInstructionId, SourceFactId, + PropagatedFactId, SourceEF, Callees, FunId); - handleOrDeferCallFlow(AtInstruction, AtInstructionId, SourceFactId, - PropagatedFactId, std::move(SourceEF), Callees, - FunId); + applyCallToReturnFlow(AtInstruction, AtInstructionId, SourceFactId, + PropagatedFactId, std::move(SourceEF), Callees, FunId, + HasNoCalleeInformation); } template - void applyCallToReturnFlow(ByConstRef AtInstruction, - uint32_t AtInstructionId, uint32_t SourceFactId, - uint32_t PropagatedFactId, - EdgeFunctionPtrType SourceEF, - const CalleesTy &Callees, uint32_t FunId) { + void + applyCallToReturnFlow(ByConstRef AtInstruction, uint32_t AtInstructionId, + uint32_t SourceFactId, uint32_t PropagatedFactId, + EdgeFunctionPtrType SourceEF, const CalleesTy &Callees, + uint32_t FunId, bool HasNoCalleeInformation) { auto CSFact = FactCompressor[PropagatedFactId]; for (ByConstRef RetSite : ICFG.getReturnSitesOfCallAt(AtInstruction)) { @@ -768,7 +735,9 @@ class IterativeIDESolver combineIds(AtInstructionId, RetSiteId)) .computeTargets(CSFact); - auto &JumpFns = JumpFunctions[RetSiteId]; + saveEdges(AtInstruction, RetSite, CSFact, Facts, + HasNoCalleeInformation ? ESGEdgeKind::SkipUnknownFn + : ESGEdgeKind::CallToRet); for (ByConstRef Fact : Facts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -785,8 +754,8 @@ class IterativeIDESolver } }(); - storeResultsAndPropagate(JumpFns, RetSiteId, SourceFactId, FactId, - FunId, std::move(EF)); + storeResultsAndPropagate(RetSiteId, SourceFactId, FactId, FunId, + std::move(EF)); } } @@ -794,12 +763,13 @@ class IterativeIDESolver } template - void handleOrDeferCallFlow(ByConstRef AtInstruction, + bool handleOrDeferCallFlow(ByConstRef AtInstruction, uint32_t AtInstructionId, uint32_t SourceFactId, uint32_t PropagatedFactId, EdgeFunctionPtrType SourceEF, const CalleesTy &Callees, uint32_t FunId) { auto CSFact = FactCompressor[PropagatedFactId]; + bool HasNoCalleeInformation = true; for (ByConstRef Callee : Callees) { auto CalleeId = FunCompressor.getOrInsert(Callee); auto SummaryFF = @@ -809,9 +779,11 @@ class IterativeIDESolver if (SummaryFF == nullptr) { /// No summary. Start inTRA propagation for the callee and defer /// return-propagation - deferCallFlow(AtInstruction, AtInstructionId, SourceFactId, CSFact, - PropagatedFactId, SourceEF, Callee, CalleeId, FunId); + HasNoCalleeInformation = + deferCallFlow(AtInstruction, AtInstructionId, SourceFactId, CSFact, + PropagatedFactId, SourceEF, Callee, CalleeId, FunId); } else { + HasNoCalleeInformation = false; /// Apply SummaryFF and ignore this CSCallee pair in the /// inter-propagation applySummaryFlow(SummaryFF.computeTargets(CSFact), AtInstruction, @@ -819,6 +791,7 @@ class IterativeIDESolver PropagatedFactId, SourceEF, FunId); } } + return HasNoCalleeInformation; } void countSummaryLinearSearch(size_t SearchLen, size_t NumSummaries) { @@ -869,10 +842,6 @@ class IterativeIDESolver auto ExitId = NodeCompressor.getOrInsert(ExitInst); if constexpr (!UseEndSummaryTab) { - // Summaries = JumpFunctions[ExitId].cellVec([FactId](const auto &Kvp) - // { - // return splitId(Kvp.first).first == FactId; - // }); Summaries = JumpFunctions[ExitId].allOf( [](uint64_t Key) { return splitId(Key).first; }, FactId, [](uint64_t Key) { return splitId(Key).second; }); @@ -890,7 +859,7 @@ class IterativeIDESolver } } - void deferCallFlow(ByConstRef AtInstruction, uint32_t AtInstructionId, + bool deferCallFlow(ByConstRef AtInstruction, uint32_t AtInstructionId, uint32_t SourceFactId, ByConstRef CSFact, uint32_t CSFactId, EdgeFunctionPtrType SourceEF, ByConstRef Callee, uint32_t CalleeId, @@ -901,17 +870,22 @@ class IterativeIDESolver combineIds(AtInstructionId, CalleeId)) .computeTargets(CSFact); - EdgeFunctionPtrType IdEF = [] { + EdgeFunctionPtrType IdEF = [&] { if constexpr (ComputeValues) { - return EdgeIdentity{}; + return Problem.identity(); } else { return EdgeFunctionPtrType{}; } }(); - for (ByConstRef SP : ICFG.getStartPointsOf(Callee)) { + + auto &&StartPoints = ICFG.getStartPointsOf(Callee); + bool HasNoCalleeInformation = std::empty(StartPoints); + + for (ByConstRef SP : StartPoints) { auto SPId = NodeCompressor.getOrInsert(SP); - auto &JumpFn = JumpFunctions[SPId]; - // bool HasResults = !JumpFn.empty(); + + saveEdges(AtInstruction, SP, CSFact, CalleeFacts, ESGEdgeKind::Call); + for (ByConstRef Fact : CalleeFacts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -927,9 +901,7 @@ class IterativeIDESolver } }(); - storeResultsAndPropagate(JumpFn, SPId, FactId, FactId, CalleeId, IdEF); - - // CallWL.insert(combineIds(FactId, CalleeId)); + storeResultsAndPropagate(SPId, FactId, FactId, CalleeId, IdEF); auto It = &AllInterPropagationsOwner.emplace_back(InterPropagationJob{ CallEF, SourceFactId, CalleeId, AtInstructionId, FactId}); @@ -962,6 +934,7 @@ class IterativeIDESolver } } } + return HasNoCalleeInformation; } template @@ -972,7 +945,9 @@ class IterativeIDESolver uint32_t FunId) { for (ByConstRef RetSite : ICFG.getReturnSitesOfCallAt(AtInstruction)) { auto RetSiteId = NodeCompressor.getOrInsert(RetSite); - auto &JumpFns = JumpFunctions[RetSiteId]; + + saveEdges(AtInstruction, RetSite, CSFact, SummaryFacts, + ESGEdgeKind::Summary); for (ByConstRef Fact : SummaryFacts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -989,8 +964,8 @@ class IterativeIDESolver } }(); - storeResultsAndPropagate(JumpFns, RetSiteId, SourceFactId, FactId, - FunId, std::move(EF)); + storeResultsAndPropagate(RetSiteId, SourceFactId, FactId, FunId, + std::move(EF)); } } } @@ -1007,16 +982,17 @@ class IterativeIDESolver Problem, CallSite, Callee, ExitInst, RetSite, combineIds(CSId, ExitId), combineIds(CalleeId, RSId)); - auto &RSJumpFns = JumpFunctions[RSId]; - for (const auto &Summary : Summaries) { uint32_t SummaryFactId{Summary.first}; auto SummaryFact = FactCompressor[SummaryFactId]; auto RetFacts = RetFF.computeTargets(SummaryFact); + + saveEdges(ExitInst, RetSite, SummaryFact, RetFacts, ESGEdgeKind::Ret); + for (ByConstRef RetFact : RetFacts) { auto RetFactId = FactCompressor.getOrInsert(RetFact); - auto EF = [&]() mutable { + auto EF = [&] { if constexpr (ComputeValues) { auto RetEF = FECache.getReturnEdgeFunction( Problem, CallSite, Callee, ExitInst, SummaryFact, RetSite, @@ -1029,8 +1005,8 @@ class IterativeIDESolver } }(); - storeResultsAndPropagate(RSJumpFns, RSId, SourceFact, RetFactId, - CallerId, std::move(EF)); + storeResultsAndPropagate(RSId, SourceFact, RetFactId, CallerId, + std::move(EF)); } } } @@ -1081,10 +1057,6 @@ class IterativeIDESolver /// map we are iterating over is bad if constexpr (!UseEndSummaryTab) { - // Summaries = JumpFunctions[ExitId].cellVec( - // [SPFactId{SPFactId}](const auto &Kvp) { - // return splitId(Kvp.first).first == SPFactId; - // }); Summaries = JumpFunctions[ExitId].allOf( [](uint64_t Key) { return splitId(Key).first; }, SPFactId, [](uint64_t Key) { return splitId(Key).second; }); @@ -1311,8 +1283,6 @@ class IterativeIDESolver } void cleanupInterJobsFor(unsigned FunId) { - /// XXX: Use std::erase_if when upgrading to C++20 - auto Cells = SourceFactAndFuncToInterJob.cells(); for (auto Iter = Cells.begin(), End = Cells.end(); Iter != End;) { auto It = Iter++; @@ -1353,6 +1323,13 @@ class IterativeIDESolver } } + void saveEdges(ByConstRef SourceNode, ByConstRef SinkStmt, + ByConstRef SourceVal, const auto &DestVals, + ESGEdgeKind Kind) { + StaticSolverConfigTy::template saveEdges( + PathData, SourceNode, SinkStmt, SourceVal, DestVals, Kind); + } + static constexpr uint64_t combineIds(uint32_t LHS, uint32_t RHS) noexcept { return (uint64_t(LHS) << 32) | RHS; } @@ -1361,7 +1338,7 @@ class IterativeIDESolver } ProblemTy &Problem; - const i_t &ICFG; + const ICFGTy &ICFG; Compressor FunCompressor{}; @@ -1403,11 +1380,10 @@ class IterativeIDESolver llvm::OwningArrayRef RefCountPerFunction{}; llvm::BitVector CandidateFunctionsForGC{}; - // FlowFunctionCache FFCache{ - // &MRes}; - // EdgeFunctionCache EFCache{&MRes}; - flow_edge_function_cache_t FECache{Problem}; + + [[no_unique_address]] + typename config_t::template PathTrackingData PathData; }; } // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h index 9dfa44edff..07f427aed4 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolverBase.h @@ -27,8 +27,6 @@ class IterativeIDESolverBase { static constexpr bool ComputeValues = StaticSolverConfigTy::ComputeValues; static constexpr bool EnableStatistics = StaticSolverConfigTy::EnableStatistics; - /// NOTE: EdgeFunctionPtrType may be either std::shared_ptr> - /// or llvm::IntrusiveRefCntPtr> once this is supported using EdgeFunctionPtrType = std::conditional_t; diff --git a/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIDESolver.h index b8c0912152..2747c33833 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIDESolver.h @@ -16,11 +16,15 @@ #include "phasar/DataFlow/PathSensitivity/ExplodedSuperGraph.h" #include "phasar/Utils/Logger.h" +#include + namespace psr { template > -class PathAwareIDESolver : public IDESolver { - using base_t = IDESolver; + typename Container = std::set, + ICFG ICFGTy = typename AnalysisDomainTy::i_t> +class PathAwareIDESolver + : public IDESolver { + using base_t = IDESolver; public: using domain_t = AnalysisDomainTy; @@ -29,11 +33,14 @@ class PathAwareIDESolver : public IDESolver { using i_t = typename base_t::i_t; using container_type = typename base_t::container_type; - explicit PathAwareIDESolver( - IDETabulationProblem &Problem, const i_t *ICF) - : base_t(Problem, ICF), ESG(Problem.getZeroValue()) { + template + requires(std::same_as && + std::same_as) + explicit PathAwareIDESolver(ProblemTy *Problem, const i_t *ICF) + : base_t(Problem, ICF), ESG(Problem->getZeroValue()) { - if (Problem.getIFDSIDESolverConfig().autoAddZero()) { + if (getProblemSolverConfig(*Problem).autoAddZero()) { PHASAR_LOG_LEVEL( WARNING, "The PathAwareIDESolver is initialized with the option 'autoAddZero' " @@ -41,6 +48,17 @@ class PathAwareIDESolver : public IDESolver { } } + template + requires( + std::same_as && + std::same_as) + [[deprecated( + "Use the other overload of PathAwareIDESolver() instead, which takes the " + "ide-problem by pointer. This documents better that the solver " + "captures the address without taking ownership")]] + explicit PathAwareIDESolver(ProblemTy &Problem, const i_t *ICF) + : PathAwareIDESolver(&Problem, ICF) {} + [[nodiscard]] const ExplodedSuperGraph & getExplicitESG() const & noexcept { return ESG; @@ -60,9 +78,15 @@ class PathAwareIDESolver : public IDESolver { ExplodedSuperGraph ESG; }; -template -PathAwareIDESolver(ProblemTy &) - -> PathAwareIDESolver; +template +PathAwareIDESolver(ProblemTy &, const ICFGTy *) + -> PathAwareIDESolver; + +template +PathAwareIDESolver(ProblemTy *, const ICFGTy *) + -> PathAwareIDESolver; } // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIterIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIterIDESolver.h new file mode 100644 index 0000000000..48c4536112 --- /dev/null +++ b/include/phasar/DataFlow/IfdsIde/Solver/PathAwareIterIDESolver.h @@ -0,0 +1,63 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h" +#include "phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h" +#include "phasar/DataFlow/PathSensitivity/ExplodedSuperGraph.h" + +namespace psr { +template struct PathAwareIDESolverConfig : public Base { + template + using PathTrackingData = ExplodedSuperGraph; + + template + static PathTrackingData + initPathData(ProblemTy &Problem) { + return ExplodedSuperGraph( + Problem.getZeroValue()); + } + + template + static void saveEdges(PathTrackingData &Data, + ByConstRef Curr, + ByConstRef Succ, + ByConstRef CurrNode, + const auto &SuccNodes, ESGEdgeKind Kind) { + Data.saveEdges(Curr, CurrNode, Succ, SuccNodes, Kind); + } +}; + +template , + ICFG ICFGTy = typename ProblemTy::ProblemAnalysisDomain::i_t> +class PathAwareIterIDESolver + : public IterativeIDESolver< + ProblemTy, PathAwareIDESolverConfig, ICFGTy> { +public: + using IterativeIDESolver, + ICFGTy>::IterativeIDESolver; + + [[nodiscard]] decltype(auto) getExplicitESG() const & noexcept { + return this->getPathAwareData(); + } + + [[nodiscard]] decltype(auto) getExplicitESG() && noexcept { + return std::move(*this).getPathAwareData(); + } +}; + +template +PathAwareIterIDESolver(ProblemTy *, ICFGTy *) + -> PathAwareIterIDESolver, + ICFGTy>; + +} // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h b/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h index 4cf452be2c..86b421fa00 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/StaticIDESolverConfig.h @@ -2,11 +2,14 @@ #define PHASAR_DATAFLOW_IFDSIDE_SOLVER_STATICIDESOLVERCONFIG_H #include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/Solver/ESGEdgeKind.h" #include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCacheNG.h" #include "phasar/DataFlow/IfdsIde/Solver/WorkListTraits.h" +#include "phasar/Utils/EmptyBaseOptimizationUtils.h" #include "phasar/Utils/TableWrappers.h" #include "phasar/Utils/TypeTraits.h" +#include #include #include @@ -25,13 +28,13 @@ enum class JumpFunctionGCMode { struct IDESolverConfigBase { template - static inline constexpr bool IsSimple1d = + static constexpr bool IsSimple1d = sizeof(std::pair) <= 32 && std::is_nothrow_move_constructible_v && std::is_nothrow_move_constructible_v && has_llvm_dense_map_info; template - static inline constexpr bool IsSimpleVal = + static constexpr bool IsSimpleVal = sizeof(T) <= 32 && std::is_nothrow_move_constructible_v && has_llvm_dense_map_info; @@ -51,23 +54,46 @@ struct IDESolverConfigBase { template using EdgeFunctionPtrType = EdgeFunction; - static inline constexpr bool AutoAddZero = true; - static inline constexpr bool EnableStatistics = false; - static inline constexpr JumpFunctionGCMode EnableJumpFunctionGC = + template using PathTrackingData = EmptyType; + + template + static PathTrackingData + initPathData(ProblemTy & /*Problem*/) { + return {}; + } + + template + static void saveEdges(PathTrackingData &Data, + ByConstRef Curr, + ByConstRef Succ, + ByConstRef CurrNode, + const auto &SuccNodes, + std::convertible_to auto Kind) {} + + static constexpr bool AutoAddZero = true; + static constexpr bool EnableStatistics = false; + static constexpr JumpFunctionGCMode EnableJumpFunctionGC = JumpFunctionGCMode::Disabled; - static inline constexpr bool UseEndSummaryTab = false; + static constexpr bool UseEndSummaryTab = true; }; +template struct WithComputeValues; +using IDESolverConfig = WithComputeValues; +using IFDSSolverConfig = WithComputeValues; + template -struct WithComputeValues : Base { +struct PSR_PREFERRED_NAME(IDESolverConfig) + PSR_PREFERRED_NAME(IFDSSolverConfig) WithComputeValues : Base { static constexpr bool ComputeValues = ComputeValuesVal; }; -template struct WithGCMode : Base { - static constexpr JumpFunctionGCMode EnableJumpFunctionGC = GCMode; -}; +template struct WithStats; +using IDESolverConfigWithStats = WithStats; +using IFDSSolverConfigWithStats = WithStats; -template struct WithStats : Base { +template +struct PSR_PREFERRED_NAME(IDESolverConfigWithStats) + PSR_PREFERRED_NAME(IFDSSolverConfigWithStats) WithStats : Base { static constexpr bool EnableStatistics = EnableStats; }; @@ -77,24 +103,23 @@ struct WithWorkList : Base { }; template struct WithEndSummaryTab : Base { - static inline constexpr bool UseEndSummaryTab = UseEST; + static constexpr bool UseEndSummaryTab = UseEST; }; -using IDESolverConfig = WithComputeValues; -using IFDSSolverConfig = WithComputeValues; -using IDESolverConfigWithStats = WithStats; -using IFDSSolverConfigWithStats = WithStats; +template struct WithGCMode; + using IFDSSolverConfigWithStatsAndGC = WithGCMode; +template +struct PSR_PREFERRED_NAME(IFDSSolverConfigWithStatsAndGC) WithGCMode : Base { + static constexpr JumpFunctionGCMode EnableJumpFunctionGC = GCMode; +}; -template -struct DefaultIDESolverConfig : IDESolverConfig {}; +template +struct DefaultIDESolverConfig : IFDSSolverConfig {}; -template - requires std::is_base_of_v< - IFDSTabulationProblem, - ProblemTy> -struct DefaultIDESolverConfig : IFDSSolverConfig {}; +template +struct DefaultIDESolverConfig : IDESolverConfig {}; } // namespace psr diff --git a/include/phasar/DataFlow/IfdsIde/SolverResults.h b/include/phasar/DataFlow/IfdsIde/SolverResults.h index 12cc2a9e6a..52bc985daa 100644 --- a/include/phasar/DataFlow/IfdsIde/SolverResults.h +++ b/include/phasar/DataFlow/IfdsIde/SolverResults.h @@ -40,8 +40,15 @@ namespace psr { std::string getMetaDataID(const llvm::Value *V); namespace detail { + +struct SolverResultsRoot { + PAMM_CATEGORY(SolverResults); + PAMM_TIMER(ResultsDumping, Full); +}; + template -class SolverResultsBase { +class SolverResultsBase : private SolverResultsRoot { + public: using n_t = N; using d_t = D; @@ -160,8 +167,8 @@ class SolverResultsBase { llvm::raw_ostream &OS = llvm::outs()) const { using f_t = typename ICFGTy::f_t; - PAMM_GET_INSTANCE; - START_TIMER("DFA IDE Result Dumping", Full); + PAMM_SCOPED_TIMER(ResultsDumping); + OS << "\n***************************************************************\n" << "* Raw IDESolver results *\n" << "***************************************************************\n"; @@ -202,7 +209,6 @@ class SolverResultsBase { } } OS << '\n'; - STOP_TIMER("DFA IDE Result Dumping", Full); } template diff --git a/include/phasar/DataFlow/MonoIfds/ArraySetWorkList.h b/include/phasar/DataFlow/MonoIfds/ArraySetWorkList.h new file mode 100644 index 0000000000..a2307d05f4 --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/ArraySetWorkList.h @@ -0,0 +1,31 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/Utils/ArraySet.h" + +namespace psr::monoifds { + +/// \brief Simple worklist, based on a de-duplicating queue +template class ArraySetDriver { +public: + void push(ItemT Item) { WL.insert(std::move(Item)); } + + template + LLVM_ATTRIBUTE_ALWAYS_INLINE void run(HandlerT Handler) { + WL.foreach (std::move(Handler)); + } + + [[nodiscard]] constexpr bool empty() const noexcept { return WL.empty(); } + +private: + ArraySet WL; +}; +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/MonoIfds/DataFlowEnvironment.h b/include/phasar/DataFlow/MonoIfds/DataFlowEnvironment.h new file mode 100644 index 0000000000..b32c7091a7 --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/DataFlowEnvironment.h @@ -0,0 +1,35 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/Utils/BitSet.h" +#include "phasar/Utils/StrongTypeDef.h" + +#include "llvm/ADT/DenseMap.h" + +PHASAR_STRONG_TYPEDEF(psr::monoifds, uint32_t, SourceFactId); + +namespace psr::monoifds { + +using SourceFactSet = BitSet; + +/// The local analysis state: TargetFact-->{SourceFact} +/// +/// \tparam D The type of (target-) data-flow facts +template +struct DataFlowEnvironment : llvm::SmallDenseMap { + using llvm::SmallDenseMap::SmallDenseMap; + + // For env-versioning + uint32_t AnalyzedVersion = 0; + uint32_t Version = 1; +}; + +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/MonoIfds/IterationStrategy.h b/include/phasar/DataFlow/MonoIfds/IterationStrategy.h new file mode 100644 index 0000000000..8fab199d90 --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/IterationStrategy.h @@ -0,0 +1,43 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorHandling.h" + +#include + +namespace psr::monoifds { +/// Iteration strategy for intra-procedural propagation +enum class IterationStrategy : uint8_t { + /// Simple de-duplicating queue (See ArraySetDriver) + DedupFIFOQueue, + /// Reverse-Post-Order queue (see TopoFixpointDriver) + TopoPrioQueue, + /// Hybrid of TopoPrioQueue in singleton-CG-SCCs and DedupFIFOQueue for larger + /// CG-SCCs + Hybrid, +}; + +[[nodiscard]] constexpr llvm::StringRef +to_string(IterationStrategy IterStrategy) noexcept { + switch (IterStrategy) { + case IterationStrategy::DedupFIFOQueue: + return "queue"; + case IterationStrategy::TopoPrioQueue: + return "topo"; + case IterationStrategy::Hybrid: + return "hybrid"; + } + llvm_unreachable("All valid IterationStrategy alternatives should be handled " + "in the switch above"); +} + +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/MonoIfds/MonoIFDSConfig.h b/include/phasar/DataFlow/MonoIfds/MonoIFDSConfig.h new file mode 100644 index 0000000000..93297d4c8d --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/MonoIFDSConfig.h @@ -0,0 +1,28 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DataFlow/MonoIfds/IterationStrategy.h" + +namespace psr::monoifds { +/// Dynamic configuration for the MonoIFDSSolver +struct MonoIfdsConfig { + /// Iteration strategy for intra-procedural propagations + IterationStrategy IterStrategy = IterationStrategy::DedupFIFOQueue; + /// Dataflow-Environment versioning. Detects and skips redundant propagations + /// at runtime + bool EnableEnvVersioning = false; + /// Whether to re-schedule the analysis of recursive call-sites whenever the + /// callee's summary changes (true), or defering re-scheduling of call-sites + /// until all callee-local paths have been analyzed (false). + bool EagerReturnPropagation = false; +}; + +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/MonoIfds/MonoIFDSProblem.h b/include/phasar/DataFlow/MonoIfds/MonoIFDSProblem.h new file mode 100644 index 0000000000..0ce752bd34 --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/MonoIFDSProblem.h @@ -0,0 +1,170 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DataFlow/IfdsIde/IfdsIdeDomain.h" +#include "phasar/DataFlow/MonoIfds/DataFlowEnvironment.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/FunctionId.h" +#include "phasar/Utils/SCCId.h" +#include "phasar/Utils/TypeTraits.h" + +#include +#include + +/// \file +/// Defines requirements for an analysis problem that can be solved by the +/// MonoIFDSSolver. +/// +/// Since MonoIFDS solves analyses bottom-up, each CG-SCC is analyzed in +/// isolation. The solver will call Problem.localAnalysis(...) once per SCC and +/// use the returned LocalMonoIFDSProblem to drive the solving process. +/// Although the solver currently is only single-threaded, you can expect that +/// (also in a multi-threaded future) LocalMonoIFDSProblem instances are not +/// accessed by multiple threads at a time, while different instances may be +/// solved on different threads in parallel. + +namespace psr::monoifds { + +/// \brief Defines requirements for a MonoIFDS-compatible analysis domain. +template +concept MonoIFDSAnalysisDomain = IfdsAnalysisDomain; + +/// \brief CG-SCC-local analysis +template +concept LocalMonoIFDSProblem = requires( + T &Problem, DataFlowEnvironment &InOut, + const DataFlowEnvironment &In, typename Dom::n_t Inst, + const typename Dom::n_t &Fact, const typename Dom::f_t &Fun, + Compressor &SeedCompressor) { + /// Intra-procedural data-flow. Input facts are passed-in as InOut; + /// modifications are performed in-place. + /// + /// Corresponds to the $flow()$ function in the paper. + Problem.normalFlow(InOut, Inst); + + /// Intra-procedural data-flow at call-sites. Input facts are passed-in as + /// InOut; modifications are performed in-place. Kills facts that may be + /// strongly updated by the callee. Don't use it to *generate* facts. + /// + /// Corresponds to the $callFlow()$ function in the paper. + Problem.callToRetFlow(InOut, Inst); + + /// Inter-procedural data-flow at exit-statements; Maps callee-facts back + /// to the return-site in the caller. As with normal IFDS, this function + /// will be called for each incoming Fact, that should be mapped back; + /// return-site facts are returned by this function. + /// + /// Corresponds to the $returnVal()$ function in the paper. + { + Problem.returnFlow(Inst, Fact) + } -> psr::is_iterable_over_v; + + /// Inter-procedural data-flow at entry-statements; Maps + /// callee-source-facts back to the call-site in the caller. This function + /// will be called for each source Fact; call-site facts are returned by + /// this function. + /// + /// Corresponds to the $passArgs^{-1}()$ function in the paper. + { + Problem.invCallFlow(Inst, Fact) + } -> psr::is_iterable_over_v; + + /// Applies a pre-computed summary of Fun at Inst into InOut, if + /// applicable. + /// + /// Useful for pre-known taint-propagators and declaration-only library + /// functions. + /// + /// \returns True, iff a summary was applied. This will take precedence + /// over a summary that the solver may have computed for Fun! + { Problem.summaryFlow(In, InOut, Inst, Fun) } -> std::convertible_to; + + /// The special zero-value, aka. $\Lambda$. Always holds. Facts that are + /// generated unconditionally, originate from zero. + /// + /// Note: The solver guarantees that the zero-value always has + /// SourceFactId 0. + { Problem.getZeroValue() } -> std::convertible_to; + + /// Approximates the source-facts that should hold at the entry of Fun. + /// Input the facts in the InOut map as + /// `SeedState[Fact].insert(SeedCompressor.getOrInsert(Fact))` + /// + /// Note: This is assumed to be a (conservative) over-approximation! + Problem.initialSeeds(InOut, SeedCompressor, Fun); + + /// At a call-site Inst calling Fun, invokes the given callback for each + /// fact that should be generated from zero there. + /// + /// Useful for taint sources. + Problem.generateFactsAtCall(Inst, Fun, DummyFn{}); + + /// At a non-call-site Inst, invokes the given callback for each + /// fact that should be generated from zero there. + /// + /// Useful for taint sources. + Problem.generateFacts(Inst, DummyFn{}); + + /// Invokes the given callback for each LeakFact for which the solver + /// should later call onResult(Inst, LeakFact), if LeakFacts holds at + /// Inst. Here, Inst is assumed to be a call-site that may call Fun. + /// + /// Useful for taint sinks. + Problem.requestResultCallbackAtCallSite(Inst, Fun, + DummyFn{}); + + /// Invokes the given callback for each LeakFact for which the solver + /// should later call onResult(Inst, LeakFact), if LeakFacts holds at + /// Inst. + /// + /// Useful for taint sinks. + Problem.requestResultCallback(Inst, DummyFn{}); + + /// Notifies the problem that a previously requested leak-Fact now is + /// known to hold at Inst. + /// + /// Useful for reporting taint leaks. + Problem.onResult(Inst, Fact); +}; + +/// \brief Defines requirements for an analysis problem that can be solved by +/// the MonoIFDSSolver. +template +concept MonoIFDSProblem = + requires(T &Problem, SCCId CurrSCC, + std::pmr::memory_resource *MRes, llvm::raw_ostream &OS) { + /// The analysis domain. Defines the type of data-flow facts, and the IR + /// on which the analysis can be run. + typename T::ProblemAnalysisDomain; + requires MonoIFDSAnalysisDomain; + + /// Create a local analysis for the given SCC. + /// Use the given std::memory_resource to allocate node-based containers, + /// if you have any. + { + Problem.localAnalysis(CurrSCC, MRes) + } -> LocalMonoIFDSProblem; + + /// Pretty-print the analysis results into the given llvm::raw_ostream. + Problem.emitTextReport(OS); + }; + +/// Optional requirement for a MonoIFDSProblem to better filter function +/// summaries. +template +concept HasShouldBeInSummary = + requires(T &Problem, typename T::ProblemAnalysisDomain::d_t ExitFact, + typename T::ProblemAnalysisDomain::n_t ExitInst) { + { + Problem.shouldBeInSummary(ExitFact, ExitInst) + } -> std::convertible_to; + }; +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/MonoIfds/MonoIFDSSolver.h b/include/phasar/DataFlow/MonoIfds/MonoIFDSSolver.h new file mode 100644 index 0000000000..3dc105b3c1 --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/MonoIFDSSolver.h @@ -0,0 +1,851 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/ControlFlow/CFG.h" +#include "phasar/ControlFlow/CGSCCs.h" +#include "phasar/ControlFlow/ControlFlowOrder.h" +#include "phasar/ControlFlow/ICFG.h" +#include "phasar/DataFlow/HelperAnalyses.h" +#include "phasar/DataFlow/MonoIfds/ArraySetWorkList.h" +#include "phasar/DataFlow/MonoIfds/DataFlowEnvironment.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSConfig.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSProblem.h" +#include "phasar/DataFlow/MonoIfds/RPOWorkList.h" +#include "phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/FunctionId.h" +#include "phasar/Utils/HashUtils.h" +#include "phasar/Utils/Lazy.h" +#include "phasar/Utils/Logger.h" +#include "phasar/Utils/MapUtils.h" +#include "phasar/Utils/MaybeUniquePtr.h" +#include "phasar/Utils/Nullable.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/RepeatIterator.h" +#include "phasar/Utils/SCCGeneric.h" +#include "phasar/Utils/TypeTraits.h" +#include "phasar/Utils/TypedVector.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/TypeName.h" + +#include +#include +#include +#include +#include + +namespace psr::monoifds { + +class MonoIFDFSSolverBase { +public: + static constexpr llvm::StringLiteral LogCategory = "MonoIFDSSolver"; +}; + +/// \brief Implements the MonoIFDS algorithm, as presented in "Scaling Bottom-up +/// IFDS Taint Analysis with Optimized Data-flow Encoding" by Schiebel and +/// Bodden. +template +class MonoIFDSSolver : public MonoIFDFSSolverBase { +public: + using n_t = typename ProblemT::ProblemAnalysisDomain::n_t; + using d_t = typename ProblemT::ProblemAnalysisDomain::d_t; + using i_t = ICFGTy; + using f_t = typename ProblemT::ProblemAnalysisDomain::f_t; + + explicit MonoIFDSSolver(ProblemT *Problem, const i_t *ICF, + std::pmr::polymorphic_allocator<> Alloc = + std::pmr::get_default_resource()) + : Problem(&assertNotNull(Problem)), ICF(&assertNotNull(ICF)), + MBufRes(Alloc.resource()) {} + + template HelperAnalysesT> + explicit MonoIFDSSolver(ProblemT *Problem, HelperAnalysesT &HA, + std::pmr::polymorphic_allocator<> Alloc = + std::pmr::get_default_resource()) + : MonoIFDSSolver(Problem, &HA.getICFG(), Alloc) { + if constexpr (CanGetCompressedFunctionsOf) { + setFunctionCompressor(&HA.getCompressedFunctions()); + } + if constexpr (CanGetCGSCCs) { + setCGSCCs(&HA.getCGSCCs()); + } + } + + MonoIFDSSolver &setConfig(MonoIfdsConfig Config) & noexcept { + this->Config = Config; + return *this; + } + + MonoIFDSSolver &setCGSCCs(const SCCHolder *SCCs) & noexcept { + assertNotNull(SCCs); + this->SCCs = SCCs; + return *this; + } + + MonoIFDSSolver &setFunctionCompressor( + const Compressor *Functions) & noexcept { + assertNotNull(Functions); + this->Functions = Functions; + return *this; + } + + void solve(); + + void dumpResults(llvm::raw_ostream &OS) const { + OS << "No Raw-Results Dump available yet!\n"; + } + + void emitTextReport(llvm::raw_ostream &OS) const { + Problem->emitTextReport(OS); + } + +private: + // NOTE: Used the node_hash_map from + // [parallel-hash-map](https://github.com/greg7mdp/parallel-hashmap) here + // for the paper-eval! + template + using node_hash_map = + std::pmr::unordered_map>; + + struct FunctionSummary { + Compressor SourceFactIds; + DataFlowEnvironment EndSummary; + + node_hash_map, SourceFactSet> LeakIf; + + FunctionSummary(std::pmr::memory_resource *MRes) : LeakIf(MRes) {} + }; + + using LocalAnalysis = decltype(std::declval().localAnalysis( + SCCId(), std::declval())); + + struct IntermediateState { + LocalAnalysis LocalProblem; + node_hash_map> PathEdges; + node_hash_map> Incoming; + + llvm::SmallDenseSet HasNewLeaks; + llvm::SmallDenseSet HasNewSummary; + + SCCId CurrSCC; + bool InRecursion; + + IntermediateState(ProblemT *Problem, std::pmr::memory_resource *MRes, + SCCId CurrSCC, bool InRecursion) + : LocalProblem(Problem->localAnalysis(CurrSCC, MRes)), PathEdges(MRes), + Incoming(MRes), CurrSCC(CurrSCC), InRecursion(InRecursion) {} + }; + + struct Mapper { + TypedVector Mapping; + BitSet ComputedMappings; + + explicit Mapper(size_t NumCalleeSrcFacts) { + Mapping.resize(NumCalleeSrcFacts); + ComputedMappings.reserve(NumCalleeSrcFacts); + } + + void reset() { + for (auto &SrcFacts : Mapping) { + SrcFacts.clear(); + } + ComputedMappings.clear(); + } + + const SourceFactSet &getSourceFactsFor(auto &LocalProblem, + const DataFlowEnvironment &In, + const FunctionSummary &CalleeSum, + SourceFactId CalleeSrc, + ByConstRef CallInst) { + auto &Ret = Mapping[CalleeSrc]; + + if (ComputedMappings.tryInsert(CalleeSrc)) { + auto &&CSFacts = LocalProblem.invCallFlow( + CallInst, CalleeSum.SourceFactIds[CalleeSrc]); + + for (const auto *Fact : CSFacts) { + if (const auto *FactSrc = getOrNull(In, Fact)) { + Ret.insertAllOf(*FactSrc); + } + } + } + + return Ret; + } + + void insertAllSrcFactsFor(SourceFactSet &Into, auto &LocalProblem, + const DataFlowEnvironment &In, + const FunctionSummary &CalleeSum, + const SourceFactSet &CalleeSrcs, + ByConstRef CallInst) { + CalleeSrcs.foreach ([&](auto SrcFactId) { + Into.insertAllOf(getSourceFactsFor(LocalProblem, In, CalleeSum, + SrcFactId, CallInst)); + }); + } + + [[nodiscard]] SourceFactSet + getAllSrcFactsFor(auto &LocalProblem, const DataFlowEnvironment &In, + const FunctionSummary &CalleeSum, + const SourceFactSet &CalleeSrcs, + ByConstRef CallInst) { + SourceFactSet Ret; + insertAllSrcFactsFor(Ret, LocalProblem, In, CalleeSum, CalleeSrcs, + CallInst); + return Ret; + } + }; + + void initializeFunctions() { + if (SCCs) { + throw std::logic_error("SCCs without FunctionCompressor?"); + } + + if constexpr (requires() { + { + Problem->getEntryPoints() + } -> psr::is_iterable_over_v; + }) { + Functions = std::make_unique>( + compressFunctions(ICF->getCallGraph(), Problem->getEntryPoints())); + } else if constexpr (requires() { + { + Problem->getEntryPoints() + } -> psr::is_iterable_over_v; + }) { + Functions = std::make_unique>(compressFunctions( + ICF->getCallGraph(), + psr::getEntryFunctions(*ICF, Problem->getEntryPoints()))); + } else { + throw std::logic_error("The analysis problem " + + llvm::getTypeName().str() + + " does not provide getEntryPoints(). So, you " + "must set a FunctionCompressor by calling " + "setFunctionCompressor() on the solver!"); + } + } + + void initializeSCCs() { + assert(Functions && "Functions have been initialized already (see " + "invocation order in initialize())"); + SCCs = std::make_unique>( + computeCGSCCs(ICF->getCallGraph(), *ICF, *Functions)); + } + + void initialize() { + if (!Functions) { + initializeFunctions(); + } + if (!SCCs) { + initializeSCCs(); + } + } + + void computeFixpointForSCC(SCCId CurrSCC, + llvm::ArrayRef CurrFuns) { + + const size_t SCCSize = CurrFuns.size(); + const bool InRecursion = SCCSize > 1; + IntermediateState IState(Problem, &PoolRes, CurrSCC, InRecursion); + + const auto IterStrategy = Config.IterStrategy; + const bool UseTopoFixpointDriver = [=] { + if (IterStrategy == IterationStrategy::DedupFIFOQueue) { + return false; + } + + if (IterStrategy == IterationStrategy::Hybrid) { + // return SCCSize < 20; + return SCCSize == 1; + } + + return true; + }(); + + ControlFlowOrder CFO; + if (UseTopoFixpointDriver) { + for (const auto &Fun : CurrFuns) { + computeCFGOrder(CFO, *ICF, (*Functions)[Fun]); + } + } + + ArraySetDriver DefaultDriver; + TopoFixpointDriver TopoDriver; + + const auto ComputeFixpointWithDriver = + [&](auto &Driver) LLVM_ATTRIBUTE_NOINLINE { + for (auto FunId : llvm::reverse(CurrFuns)) { + const auto *Fun = (*Functions)[FunId]; + submitInitialSeeds(IState, Driver, Summaries[FunId].SourceFactIds, + Fun); + } + Driver.run([&](n_t BlockStart) { + analyzeBlock(IState, Driver, BlockStart); + }); + assert(Driver.empty()); + + // llvm::errs() << '.'; + }; + + const auto RepropagateInRecursion = [&](auto &Driver) { + rescheduleCalls(IState, Driver); + while (!Driver.empty()) { + Driver.run( + [&](n_t BlockStart) { analyzeBlock(IState, Driver, BlockStart); }); + assert(Driver.empty()); + + rescheduleCalls(IState, Driver); + // llvm::errs() << '.'; + } + + assert(IState.HasNewSummary.empty() && + "After repropagating, we should not have any summary " + "applications pending"); + }; + if (UseTopoFixpointDriver) { + ComputeFixpointWithDriver(TopoDriver); + } else { + ComputeFixpointWithDriver(DefaultDriver); + } + + if (!Config.EagerReturnPropagation) { + if (IterStrategy == IterationStrategy::TopoPrioQueue) { + RepropagateInRecursion(TopoDriver); + } else { + RepropagateInRecursion(DefaultDriver); + } + } + + repropagateLeaks(IState, CurrSCC); + } + + /// Lines 1-3 in Algorithm 4 + void submitInitialSeeds(IntermediateState &IState, auto &Driver, + Compressor &SeedCompressor, + ByConstRef Fun) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[submitInitialSeeds]: For fun " << FToString(Fun)); + const auto &SPs = ICF->getStartPointsOf(Fun); + + const auto &Zero = IState.LocalProblem.getZeroValue(); + SeedCompressor.insert(Zero); + assert(SeedCompressor.get(Zero) == SourceFactId(0) && + "The Zero value must always have Id 0!"); + + for (const auto &SP : SPs) { + auto &SeedState = IState.PathEdges[SP]; + SeedState[Zero].insert(SourceFactId(0)); + + IState.LocalProblem.initialSeeds(SeedState, SeedCompressor, Fun); + Driver.push(SP); + } + } + + /// Delayed Line 39 in Algorithm 4 + void rescheduleCalls(IntermediateState &IState, auto &Driver) { + if (!IState.InRecursion) { + return; + } + + const bool EnableEnvVersioning = Config.EnableEnvVersioning; + + for (auto FunId : IState.HasNewSummary) { + IState.HasNewLeaks.erase(FunId); + const auto &Fun = (*Functions)[FunId]; + + for (const auto &CS : getOrDefault(IState.Incoming, Fun)) { + const auto &CSFun = ICF->getFunctionOf(CS); + if (auto CallerId = Functions->getOrNull(CSFun)) { + Driver.push(CS); + if (EnableEnvVersioning) { + IState.PathEdges[CS].Version++; + } + } + } + } + IState.HasNewSummary.clear(); + } + + /// RepropagateLeaks procedure in Algorithm 2 + void repropagateLeaks(IntermediateState &IState, SCCId CurrSCC) { + llvm::SmallDenseSet NewLeaksWL; + while (!IState.HasNewLeaks.empty()) { + NewLeaksWL.swap(IState.HasNewLeaks); + + for (auto FunId : NewLeaksWL) { + handleLeaksForFun(IState, CurrSCC, FunId); + } + NewLeaksWL.clear(); + } + } + + /// Continuation of RepropagateLeaks procedure in Algorithm 2 + void handleLeaksForFun(IntermediateState &IState, SCCId CurrSCC, + FunctionId CurrFun) { + const auto *Fun = (*Functions)[CurrFun]; + const auto &Sum = Summaries[CurrFun]; + + Mapper M(Sum.SourceFactIds.size()); + + for (const auto &CS : ICF->getCallersOf(Fun)) { + auto CallerId = Functions->getOrNull(CS->getFunction()); + if (!CallerId) { + continue; + } + + auto CallerSCC = SCCs->SCCOfNode[*CallerId]; + if (CallerSCC != CurrSCC) { + continue; + } + + M.reset(); + + const auto &In = getOrDefault(IState.PathEdges, CS); + + for (const auto &[CalleeLeak, LeakSrc] : Sum.LeakIf) { + const auto &CSSrc = + M.getAllSrcFactsFor(IState.LocalProblem, In, Sum, LeakSrc, CS); + reportOrPropagateLeak(IState, *CallerId, CalleeLeak.first, + CalleeLeak.second, CSSrc); + } + } + } + + void analyzeBlock(IntermediateState &IState, auto &Driver, + ByConstRef BlockStart) { + + auto &LocalStateRef = IState.PathEdges[BlockStart]; + if (Config.EnableEnvVersioning && + LocalStateRef.AnalyzedVersion >= LocalStateRef.Version) { + // Nothing to be done here + return; + } + + LocalStateRef.AnalyzedVersion = LocalStateRef.Version; + analyzeBlockImpl(IState, Driver, BlockStart, LocalStateRef); + } + + /// Procedure AnalyzeBlock (Lines 8-11+14 in Algorithm 4) + void analyzeBlockImpl(IntermediateState &IState, auto &Driver, + ByConstRef BlockStart, + DataFlowEnvironment LocalState) { + + auto CurrFunId = Functions->get(BlockStart->getFunction()); + + Nullable CurrInst = BlockStart; + + do { + auto Last = unwrapNullable(CurrInst); + + do { + auto Curr = unwrapNullable(CurrInst); + analyzeInstruction(IState, LocalState, CurrFunId, Curr); + Last = Curr; + if constexpr (IsBlockAwareControlFlow) { + CurrInst = ICF->getUniqueSuccessor(Curr); + } else { + const auto &Succs = ICF->getSuccsOf(Curr); + if (Succs.size() == 1) { + CurrInst = Succs[0]; + } else { + CurrInst = {}; + } + } + } while (CurrInst); + + Nullable UniqueSucc{}; + + // We have at least one instruction, so we can safely unwrap here + const auto &Succs = ICF->getSuccsOf(Last); + const auto SuccSz = Succs.size(); + const bool HasSingleSucc = SuccSz == 1; + for (const auto &Succ : Succs) { + bool HasSinglePred = [&]() { + if constexpr (IsBlockAwareControlFlow) { + return ICF->hasUniquePredecessor(Succ); + } + return false; + }(); + + auto [SuccBBStateIt, Inserted] = IState.PathEdges.try_emplace( + Succ, lazy{[&] { + if (HasSingleSucc && !(HasSinglePred && !UniqueSucc)) { + return std::move(LocalState); + } + + return LocalState; + }}); + + if (HasSinglePred) { + // Assign + + if (Inserted || SuccBBStateIt->second != LocalState) { + if (!UniqueSucc) { + UniqueSucc = Succ; + if (!Inserted) { + // Note: Cannot move LocalState here, as we still + // need it in the next iteration + SuccBBStateIt->second = LocalState; + } + + } else { + Driver.push(Succ); + if (!Inserted) { + if (HasSingleSucc) { + SuccBBStateIt->second = std::move(LocalState); + } else { + SuccBBStateIt->second = LocalState; + } + } + } + + SuccBBStateIt->second.Version++; + } + continue; + } + + // Merge + if (Inserted || tryMergeStates(SuccBBStateIt->second, LocalState)) { + SuccBBStateIt->second.Version++; + Driver.push(Succ); + } + } + + if (SuccSz == 0 && Config.EagerReturnPropagation && + ICF->isExitInst(Last)) { + if (IState.HasNewSummary.erase(CurrFunId)) { + rescheduleCallsAtExit(IState, Driver, CurrFunId); + } + } + + CurrInst = UniqueSucc; + } while (CurrInst); + } + + /// Lines 15-20 in Algorithm 4 + void analyzeInstruction(IntermediateState &IState, + DataFlowEnvironment &LocalState, + FunctionId CurrFunId, ByConstRef Inst) { + + if (ICF->isCallSite(Inst)) { + return analyzeCallInst(IState, LocalState, CurrFunId, Inst); + } + + handleSourceSinkConfig(IState, LocalState, CurrFunId, Inst); + + if (ICF->isExitInst(Inst)) { + return analyzeExitInst(IState, LocalState, CurrFunId, Inst); + } + + IState.LocalProblem.normalFlow(LocalState, Inst); + } + + /// Procedure AnalyzeExit (Lines 35-38 in Algorithm 4, Line 39 is delayed to + /// rescheduleCalls()) + void analyzeExitInst(IntermediateState &IState, + DataFlowEnvironment &LocalState, + FunctionId CurrFunId, ByConstRef Inst) { + const bool InRecursion = IState.InRecursion; + bool Changed = false; + + auto &Sum = Summaries[CurrFunId].EndSummary; + + for (auto &&[ExitFact, ExitSrc] : LocalState) { + if constexpr (HasShouldBeInSummary) { + if (!Problem->shouldBeInSummary(ExitFact, Inst)) { + continue; + } + } + + auto [It, Inserted] = Sum.try_emplace(ExitFact, std::move(ExitSrc)); + if (InRecursion) { + Changed |= Inserted || It->second.tryMergeWith(std::move(ExitSrc)); + } else if (!Inserted) { + It->second.insertAllOf(std::move(ExitSrc)); + } + } + + if (Changed /* && InRecursion*/) { + IState.HasNewSummary.insert(CurrFunId); + } + } + + /// Procedure AnalyzeCall (Lines 21-34 in Algorithm 4) + void analyzeCallInst(IntermediateState &IState, + DataFlowEnvironment &LocalState, + FunctionId CurrFunId, ByConstRef Inst) { + + const auto &Callees = ICF->getCalleesOfCallAt(Inst); + + auto CSInfo = handleCallSrcSinksAndMayRecurse(IState, LocalState, Callees, + CurrFunId, Inst); + + if (CSInfo.MayRecurse) { + IState.InRecursion = true; + IState.PathEdges[Inst] = LocalState; + } + + DataFlowEnvironment CollectedSummary; + + for (const auto &CalleeFun : Callees) { + // Collect all data-flows that need to be propagated. Don't update + // LocalState in-place + + if (IState.LocalProblem.summaryFlow(std::as_const(LocalState), + CollectedSummary, Inst, CalleeFun)) { + continue; + } + + auto CalleeId = Functions->get(CalleeFun); + if (ICF->getStartPointsOf(CalleeFun).empty()) { + CSInfo.CanCTR = false; + } + applySummary(IState, std::as_const(LocalState), CollectedSummary, + CalleeId, Inst, CurrFunId); + } + if (CSInfo.CanCTR) { + IState.LocalProblem.callToRetFlow(LocalState, Inst); + } + + mergeStates(LocalState, std::move(CollectedSummary)); + } + + /// Lines 26-32 in Algorithm 4 + void applySummary(IntermediateState &IState, + const DataFlowEnvironment &In, + DataFlowEnvironment &LocalState, FunctionId CalleeId, + n_t Inst, FunctionId CurrFunId) { + const auto &Sum = Summaries[CalleeId]; + Mapper M(Sum.SourceFactIds.size()); + + for (const auto &[SumFact, SumSrc] : Sum.EndSummary) { + auto &&RetFacts = IState.LocalProblem.returnFlow(Inst, SumFact); + if (RetFacts.empty()) { + continue; + } + + const auto &RetSrcFacts = + M.getAllSrcFactsFor(IState.LocalProblem, In, Sum, SumSrc, Inst); + if (RetSrcFacts.empty()) { + continue; + } + + for (const auto *RetFact : RetFacts) { + LocalState[RetFact].insertAllOf(RetSrcFacts); + } + } + + if (CalleeId != CurrFunId) { // Prevent self-insertion + for (const auto &[CalleeLeak, LeakSrc] : Sum.LeakIf) { + const auto &CSSrc = + M.getAllSrcFactsFor(IState.LocalProblem, In, Sum, LeakSrc, Inst); + reportOrPropagateLeak(IState, CurrFunId, CalleeLeak.first, + CalleeLeak.second, CSSrc); + } + } + } + + struct CallSiteInfo { + bool MayRecurse = false; + bool CanCTR = false; + }; + + [[nodiscard]] CallSiteInfo handleCallSrcSinksAndMayRecurse( + IntermediateState &IState, DataFlowEnvironment &LocalState, + const auto &Callees, FunctionId CurrFunId, ByConstRef Inst) { + + const auto &SCCs = *this->SCCs; + const auto CurrSCC = IState.CurrSCC; + + bool MayRecurse = false; + bool CanCTR = !Callees.empty(); + for (f_t CalleeFun : Callees) { + auto CalleeId = Functions->get(CalleeFun); + auto CalleeSCC = SCCs.SCCOfNode[CalleeId]; + if (CalleeSCC == CurrSCC) { + MayRecurse = true; + IState.Incoming[CalleeFun].insert(Inst); + } + + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[handleCallSrcSinksAndMayRecurse]: At call to " + << FToString(CalleeFun)); + + IState.LocalProblem.requestResultCallbackAtCallSite( + Inst, CalleeFun, [&](ByConstRef LeakFact) { + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + "[handleCallSrcSinksAndMayRecurse]: LeakFact: " + << DToString(LeakFact)); + if (const auto *LeakSrc = psr::getOrNull(LocalState, LeakFact)) { + reportOrPropagateLeak(IState, CurrFunId, Inst, LeakFact, + *LeakSrc); + } + }); + + // Generate taints from zero: + IState.LocalProblem.generateFactsAtCall( + Inst, CalleeFun, [&](ByConstRef GenFact) { + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + "[handleCallSrcSinksAndMayRecurse]: GenFact: " + << DToString(GenFact)); + // Note: Assume, this gets called for all relevant aliases as well + LocalState[GenFact].insert(SourceFactId(0)); + }); + } + + return { + .MayRecurse = MayRecurse, + .CanCTR = CanCTR, + }; + } + + void handleSourceSinkConfig(IntermediateState &IState, + DataFlowEnvironment &LocalState, + FunctionId CurrFunId, n_t Inst) { + IState.LocalProblem.requestResultCallback(Inst, [&](const auto &LeakFact) { + if (const auto *LeakSrc = getOrNull(LocalState, LeakFact)) { + reportOrPropagateLeak(IState, CurrFunId, Inst, LeakFact, *LeakSrc); + } + }); + + // Generate taints from zero: + IState.LocalProblem.generateFacts(Inst, [&](const auto &GenFact) { + LocalState[GenFact].insert(SourceFactId(0)); + }); + } + + void rescheduleCallsAtExit(IntermediateState &IState, auto &Driver, + FunctionId CurrFunId) { + const auto &Fun = (*Functions)[CurrFunId]; + const auto EnableEnvVersioning = Config.EnableEnvVersioning; + + for (const auto &CS : getOrDefault(IState.Incoming, Fun)) { + if (auto CallerId = Functions->getOrNull(CS->getFunction())) { + if (EnableEnvVersioning) { + IState.PathEdges[CS].Version++; + } + + Driver.push(CS); + } + } + } + + // PropagateLeaks procedure in Algorithm 4 + void reportOrPropagateLeak(IntermediateState &IState, FunctionId CurrFunId, + n_t LeakInst, d_t LeakFact, SourceFactSet From) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[reportOrPropagateLeak]: " << DToString(LeakFact) + << " AT " + << NToString(LeakInst)); + // The zero fact has always Id 0! + if (From.tryErase(SourceFactId(0))) { + if (Leaks[LeakInst].insert(LeakFact).second) { + IState.LocalProblem.onResult(LeakInst, LeakFact); + } + } + + auto &CurrSum = Summaries[CurrFunId]; + + bool New = + CurrSum.LeakIf[{LeakInst, LeakFact}].tryMergeWith(std::move(From)); + + if (New && IState.InRecursion) { + IState.HasNewLeaks.insert(CurrFunId); + } + } + + static void mergeStates(DataFlowEnvironment &Into, + DataFlowEnvironment &&From) { + if (Into.empty()) { + if (&Into != &From) { + Into = std::move(From); + } + + return; + } + + if (Into.size() < From.size()) { + std::swap(Into, From); + } + + for (auto &[TgtFact, SrcFactIds] : From) { + auto [It, Inserted] = Into.try_emplace(TgtFact, std::move(SrcFactIds)); + if (!Inserted) { + It->second.insertAllOf(std::move(SrcFactIds)); + } + } + } + + [[nodiscard]] static bool + tryMergeStates(DataFlowEnvironment &Into, + const DataFlowEnvironment &From) { + // TODO Handle phis + + if (Into.empty()) { + Into = From; + return !From.empty(); + } + + bool Changed = false; + for (const auto &[TgtFact, SrcFactIds] : From) { + auto [It, Inserted] = Into.try_emplace(TgtFact, SrcFactIds); + Changed |= Inserted || It->second.tryMergeWith(SrcFactIds); + } + + return Changed; + } + + // -- data members + + ProblemT *Problem{}; + const i_t *ICF{}; + + MonoIfdsConfig Config{}; + + std::pmr::monotonic_buffer_resource MBufRes; + // XXX: Make this synchronized when parallelizing! + std::pmr::unsynchronized_pool_resource PoolRes{&MBufRes}; + + MaybeUniquePtr> SCCs{}; + MaybeUniquePtr> Functions{}; + + // --- global analysis state + TypedVector Summaries{}; + llvm::SmallDenseMap> Leaks{}; +}; + +template +void MonoIFDSSolver::solve() { + // Step 1: Check for pre-analysis results: If any of them is null, create them + initialize(); + + // Step 2: Pre-allocate buffers + Summaries.reserve(Functions->size()); + Summaries.append(psr::repeat(&PoolRes, Functions->size())); + + // Step 3: Analyze each CG-SCC in isolation + + for (const auto &[SCC, CurrFuns] : SCCs->NodesInSCC.enumerate()) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[computeFixpointForSCC]: " << SCC.Value << '/' + << SCCs->size()); + computeFixpointForSCC(SCC, CurrFuns); + } +} + +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/MonoIfds/RPOWorkList.h b/include/phasar/DataFlow/MonoIfds/RPOWorkList.h new file mode 100644 index 0000000000..3ee968158f --- /dev/null +++ b/include/phasar/DataFlow/MonoIfds/RPOWorkList.h @@ -0,0 +1,62 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/ControlFlow/ControlFlowOrder.h" +#include "phasar/Utils/ByRef.h" + +#include "llvm/ADT/BitVector.h" + +namespace psr::monoifds { +/// See SootUp's +/// [UniverseSortedPriorityQueue](https://github.com/soot-oss/SootUp/blob/develop/sootup.analysis.intraprocedural/src/main/java/sootup/analysis/intraprocedural/UniverseSortedPriorityQueue.java) +template class TopoFixpointDriver { +public: + TopoFixpointDriver() noexcept = default; + + TopoFixpointDriver(ControlFlowOrder &&CFO) : CFO(std::move(CFO)) { + WorkList.resize(this->CFO.Order.size()); + } + + void push(ByConstRef Item) { + auto IId = CFO.Order.get(Item); + WorkList.set(uint32_t(IId)); + + if (int(IId) > Max) { + Max = int(IId); + } + } + + std::optional pop() { + if (Max < 0) { + return std::nullopt; + } + + auto IId = typename ControlFlowOrder::CFGOrderId(Max); + Max = WorkList.find_prev(Max); + return CFO.Order[IId]; + } + + [[nodiscard]] bool empty() const noexcept { return Max < 0; } + + LLVM_ATTRIBUTE_ALWAYS_INLINE void run(std::invocable auto Handler) { + while (auto Inst = pop()) { + std::invoke(Handler, *Inst); + } + } + + [[nodiscard]] constexpr const auto &getCFO() const noexcept { return CFO; } + +private: + ControlFlowOrder CFO; + llvm::BitVector WorkList; + int Max = -1; +}; +} // namespace psr::monoifds diff --git a/include/phasar/DataFlow/PathSensitivity/PathSensitivityManagerBase.h b/include/phasar/DataFlow/PathSensitivity/PathSensitivityManagerBase.h index 3a58bae1e4..f1f0bf572b 100644 --- a/include/phasar/DataFlow/PathSensitivity/PathSensitivityManagerBase.h +++ b/include/phasar/DataFlow/PathSensitivity/PathSensitivityManagerBase.h @@ -12,6 +12,7 @@ #include "phasar/Utils/AdjacencyList.h" #include "phasar/Utils/Logger.h" +#include "phasar/Utils/TypeTraits.h" #include "phasar/Utils/Utilities.h" #include "llvm/ADT/BitVector.h" @@ -180,7 +181,7 @@ template class PathSensitivityManagerBase { [[nodiscard]] static graph_type reverseDAG(graph_type &&Dag, size_t MaxDepth) { auto Sz = graph_traits_t::size(Dag); - return reverseDAG(std::move(Dag), identity{}, Sz, MaxDepth); + return reverseDAG(std::move(Dag), IdentityFn{}, Sz, MaxDepth); } }; diff --git a/include/phasar/DataFlow/PathSensitivity/PathSensitivityManagerMixin.h b/include/phasar/DataFlow/PathSensitivity/PathSensitivityManagerMixin.h index 63d97c29af..4599435d85 100644 --- a/include/phasar/DataFlow/PathSensitivity/PathSensitivityManagerMixin.h +++ b/include/phasar/DataFlow/PathSensitivity/PathSensitivityManagerMixin.h @@ -192,7 +192,13 @@ class PathSensitivityManagerMixin { return pathsDagToAll(Inst, llvm::ArrayRef(&Fact, 1), Config, PFilter); } - if (auto Next = Inst->getNextNonDebugInstruction()) { + if (auto Next = +#if LLVM_VERSION_MAJOR <= 18 + Inst->getNextNonDebugInstruction() +#else + Inst->getNextNode() +#endif + ) { return pathsDagToAll(Next, llvm::ArrayRef(&Fact, 1), Config, PFilter); } @@ -203,9 +209,11 @@ class PathSensitivityManagerMixin { for (const auto *BB : llvm::successors(Inst)) { const auto *First = &BB->front(); +#if LLVM_VERSION_MAJOR <= 18 if (llvm::isa(First)) { First = First->getNextNonDebugInstruction(); } +#endif if (ESG.getNodeOrNull(First, Fact)) { return pathsDagToAll(First, llvm::ArrayRef(&Fact, 1), Config, PFilter); } diff --git a/include/phasar/DataFlow/WPDS/IfdsIdeRuleProvider.h b/include/phasar/DataFlow/WPDS/IfdsIdeRuleProvider.h new file mode 100644 index 0000000000..5a292cc7df --- /dev/null +++ b/include/phasar/DataFlow/WPDS/IfdsIdeRuleProvider.h @@ -0,0 +1,271 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/ControlFlow/SparseCFGProvider.h" +#include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" +#include "phasar/DataFlow/IfdsIde/IDEProblem.h" +#include "phasar/DataFlow/IfdsIde/IDEProblemWrapper.h" +#include "phasar/DataFlow/IfdsIde/IFDSProblem.h" +#include "phasar/DataFlow/IfdsIde/Solver/FlowEdgeFunctionCache.h" +#include "phasar/DataFlow/WPDS/RuleProvider.h" +#include "phasar/Domain/BinaryDomain.h" +#include "phasar/Utils/ByRef.h" +#include "phasar/Utils/JoinLattice.h" +#include "phasar/Utils/Logger.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/SmallVector.h" + +#include + +namespace psr::wpds { + +namespace detail { +template struct WeightTypeOf { + using type = EdgeIdentity; +}; +template struct WeightTypeOf { + using type = typename ProblemT::EdgeFunctionType; +}; +} // namespace detail + +template > +class IfdsIdeRuleProvider { +public: + using control_location_type = typename ProblemT::d_t; + using stack_element_type = typename ProblemT::n_t; + using weight_type = + typename detail::WeightTypeOf::type; + + static constexpr auto LogCategory = + ComputeWeights ? llvm::StringLiteral("IDERuleProvider") + : llvm::StringLiteral("IFDSRuleProvider"); + + IfdsIdeRuleProvider( + ProblemT *Problem, const ICFGTy *ICF, + std::bool_constant /*unused*/ = {}) noexcept + : Problem(&assertNotNull(Problem)), ICF(&assertNotNull(ICF)) { + static_assert(RuleProvider); + } + + [[nodiscard]] auto getNormalRules(ByConstRef CL, + ByConstRef SE) { + llvm::SmallVector< + std::tuple> + Outs; + + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[getNormalRules]: CL=" << DToString(CL) + << "; SE=" << NToString(SE)); + + if (ICF->isCallSite(SE)) { + auto Callees = ICF->getCalleesOfCallAt(SE); + auto RetSites = ICF->getReturnSitesOfCallAt(SE); + for (const auto &Succ : RetSites) { + auto Facts = FECache.getCallToRetFlowFunction(SE, Succ, Callees) + ->computeTargets(CL); + for (auto &Fct : Facts) { + const auto FctSucc = factSucc(Succ, Fct); + if constexpr (ComputeWeights) { + auto W = + FECache.getCallToRetEdgeFunction(SE, CL, Succ, Fct, Callees); + Outs.emplace_back(std::move(Fct), FctSucc, std::move(W)); + } else { + Outs.emplace_back(std::move(Fct), FctSucc, weight_type{}); + } + } + } + + for (const auto &DestFun : Callees) { + if (auto SumFF = FECache.getSummaryFlowFunction(SE, DestFun)) { + auto Facts = SumFF->computeTargets(CL); + for (const auto &Succ : RetSites) { + for (auto &Fct : Facts) { + const auto FctSucc = factSucc(Succ, Fct); + if constexpr (ComputeWeights) { + auto W = FECache.getSummaryEdgeFunction(SE, CL, Succ, Fct); + Outs.emplace_back(Fct, FctSucc, std::move(W)); + } else { + Outs.emplace_back(Fct, FctSucc, weight_type{}); + } + } + } + } + } + + } else { + for (const auto &Succ : ICF->getSuccsOf(SE)) { + auto Facts = + FECache.getNormalFlowFunction(SE, Succ)->computeTargets(CL); + for (auto &Fct : Facts) { + const auto FctSucc = factSucc(Succ, Fct); + if constexpr (ComputeWeights) { + auto W = FECache.getNormalEdgeFunction(SE, CL, Succ, Fct); + Outs.emplace_back(std::move(Fct), FctSucc, std::move(W)); + } else { + Outs.emplace_back(std::move(Fct), FctSucc, weight_type{}); + } + } + } + } + + return Outs; + } + + [[nodiscard]] auto getPushRules(ByConstRef CL, + ByConstRef SE) { + llvm::SmallVector> + Outs; + if (!ICF->isCallSite(SE)) { + return Outs; + } + + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[getPushRules]: CL=" << DToString(CL) + << "; SE=" << NToString(SE)); + + auto Callees = ICF->getCalleesOfCallAt(SE); + auto RetSites = ICF->getReturnSitesOfCallAt(SE); + for (const auto &DestFun : Callees) { + if (FECache.getSummaryFlowFunction(SE, DestFun)) { + // Handled in getNormalRules() + continue; + } + + auto Facts = FECache.getCallFlowFunction(SE, DestFun)->computeTargets(CL); + auto EntrySEs = ICF->getStartPointsOf(DestFun); + for (auto &&Fct : Facts) { + auto W = [&] { + if constexpr (ComputeWeights) { + return FECache.getCallEdgeFunction(SE, CL, DestFun, Fct); + } else { + return weight_type{}; + } + }(); + for (const auto &Succ : RetSites) { + // Note: No sparsification here, because getPopRules() needs the + // predecessor of Succ + for (const auto &EntrySE : EntrySEs) { + Outs.emplace_back(Fct, Succ, EntrySE, W); + } + } + } + } + + return Outs; + } + + [[nodiscard]] bool hasPopRules(ByConstRef /*CL*/, + ByConstRef SE) { + // TODO: Be more precise here, filtering for facts CL that we actually need + // in the summary. + return ICF->isExitInst(SE); + } + + [[nodiscard]] auto getPopRules(ByConstRef CL, + ByConstRef ExitSE, + ByConstRef RetSiteSE, + ByConstRef /*EntrySE*/ + ) { + llvm::SmallVector> Outs; + + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[getPopRules]: CL=" + << DToString(CL) + << "; ExitSE=" << NToString(ExitSE) + << "; RetSiteSE=" << NToString(RetSiteSE)); + + auto DestFun = ICF->getFunctionOf(ExitSE); + + for (const auto &CS : ICF->getPredsOf(RetSiteSE)) { + if (!ICF->isCallSite(CS)) { + continue; + } + + auto Facts = FECache.getRetFlowFunction(CS, DestFun, ExitSE, RetSiteSE) + ->computeTargets(CL); + for (auto &Fct : Facts) { + if constexpr (ComputeWeights) { + auto W = FECache.getReturnEdgeFunction(CS, DestFun, ExitSE, CL, + RetSiteSE, Fct); + Outs.emplace_back(std::move(Fct), std::move(W)); + } else { + Outs.emplace_back(std::move(Fct), weight_type{}); + } + } + } + + return Outs; + } + + [[nodiscard]] auto initialSeeds() { + llvm::SmallVector< + std::tuple> + Outs; + + for (const auto &[Inst, Facts] : Problem.initialSeeds().getSeeds()) { + for (const auto &[Fact, Val] : Facts) { + if constexpr (ComputeWeights) { + using l_t = typename ProblemT::l_t; + if (Val == Problem.topElement()) { + continue; + } + if (Val == Problem.bottomElement()) { + if constexpr (HasJoinLatticeTraits) { + Outs.emplace_back(Fact, Inst, AllBottom{}); + } else { + Outs.emplace_back(Fact, Inst, AllBottom{Val}); + } + } else { + Outs.emplace_back( + Fact, Inst, + ConstantEdgeFunction{NonTopBotValue::unwrap(Val)}); + } + } else { + Outs.emplace_back(Fact, Inst, weight_type{}); + } + } + } + + return Outs; + } + + [[nodiscard]] constexpr auto &problem() const noexcept { + return Problem.ideProblem(); + } + +private: + [[nodiscard]] auto factSucc(stack_element_type Succ, + ByConstRef CL) { + if constexpr (has_advanceToNextUser_v) { + return ICF->advanceToNextUser(Succ, CL); + } else { + return Succ; + } + } + + IfdsIdeProblemWrapper Problem; + const ICFGTy *ICF{}; + + FlowEdgeFunctionCache::IDEProblemTy> + FECache{&Problem.ideProblem()}; +}; + +template +using IDERuleProvider = IfdsIdeRuleProvider; + +template +using IFDSRuleProvider = IfdsIdeRuleProvider; + +} // namespace psr::wpds diff --git a/include/phasar/DataFlow/WPDS/RuleProvider.h b/include/phasar/DataFlow/WPDS/RuleProvider.h new file mode 100644 index 0000000000..7ef69cff15 --- /dev/null +++ b/include/phasar/DataFlow/WPDS/RuleProvider.h @@ -0,0 +1,64 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/Utils/TypeTraits.h" + +#include "llvm/ADT/ArrayRef.h" + +#include + +namespace psr::wpds { +template +concept RuleProvider = requires(T &RP, typename T::control_location_type CL, + typename T::stack_element_type SE) { + typename T::control_location_type; + typename T::stack_element_type; + typename T::weight_type; + { + RP.getNormalRules(CL, SE) + } -> psr::is_iterable_over_v< + std::tuple>; + + { + RP.getPushRules(CL, SE) + } -> psr::is_iterable_over_v>; + + { + // CurrCL, ExitSE, RetSiteSE, EntrySE + RP.getPopRules(CL, SE, SE, SE) + } -> psr::is_iterable_over_v< + std::tuple>; + + { RP.hasPopRules(CL, SE) } -> std::convertible_to; + + { + RP.initialSeeds() + } -> psr::is_iterable_over_v< + std::tuple>; +}; + +template +concept CanInjectAdditionalPushEdges = + requires(T &RP, typename T::control_location_type CL, + typename T::stack_element_type SE) { + RP.injectAdditionalPushEdges( + CL, SE, + [](llvm::ArrayRef> + Edges, + typename T::weight_type Weight) {}); + }; +} // namespace psr::wpds diff --git a/include/phasar/DataFlow/WPDS/Solver/WPDSSolver.h b/include/phasar/DataFlow/WPDS/Solver/WPDSSolver.h new file mode 100644 index 0000000000..c2c74078a8 --- /dev/null +++ b/include/phasar/DataFlow/WPDS/Solver/WPDSSolver.h @@ -0,0 +1,336 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DataFlow/WPDS/RuleProvider.h" +#include "phasar/DataFlow/WPDS/Solver/WPDSSolverResults.h" +#include "phasar/Utils/Logger.h" +#include "phasar/Utils/NonNullPtr.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/SemiRing.h" +#include "phasar/Utils/TypedVector.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallVector.h" + +#include +#include + +namespace psr { + +template + requires std::same_as +class WPDSSolver : private wpds::detail::SolverResultsData< + typename RuleProviderT::control_location_type, + typename RuleProviderT::stack_element_type, + typename RuleProviderT::weight_type> { + using base_t = wpds::detail::SolverResultsData< + typename RuleProviderT::control_location_type, + typename RuleProviderT::stack_element_type, + typename RuleProviderT::weight_type>; + + using base_t::ESGNodeCompressor; + using base_t::Incoming; + using base_t::PathEdges; + using base_t::Seeds; + +public: + using cl_t = typename RuleProviderT::control_location_type; + using se_t = typename RuleProviderT::stack_element_type; + using weight_t = typename RuleProviderT::weight_type; + using path_edge_t = std::tuple; + + static constexpr llvm::StringLiteral LogCategory = "RuleBasedSolver"; + + explicit WPDSSolver(RuleProviderT *RP, SemiRingT *SR) noexcept + : RP(RP), SR(SR) {} + explicit WPDSSolver(NonNullPtr RP, + NonNullPtr SR) noexcept + : RP(RP), SR(SR) {} + WPDSSolver(RuleProviderT *RP, std::nullptr_t SR) = delete; + WPDSSolver(std::nullptr_t RP, SemiRingT *SR) = delete; + WPDSSolver(std::nullptr_t RP, std::nullptr_t SR) = delete; + + wpds::SolverResults solve() & { + solveImpl(); + return getSolverResults(); + } + wpds::OwningSolverResults solve() && { + solveImpl(); + return consumeSolverResults(); + } + + [[nodiscard]] wpds::SolverResults + getSolverResults() const noexcept PSR_LIFETIMEBOUND { + return {this}; + } + + [[nodiscard]] wpds::OwningSolverResults + consumeSolverResults() noexcept { + return {std::move(static_cast(*this))}; + } + +private: + void solveImpl() { + submitInitialSeeds(); + + while (!WL.empty()) { + auto [CurrPE, CurrWeight] = WL.pop_back_val(); + auto &&[CurrSrc, CurrTarget, CurrTgtFact] = CurrPE; + + for (const auto &[SuccCL, SuccSE, SuccWeight] : + RP->getNormalRules(CurrTgtFact, CurrTarget)) { + propagate({CurrSrc, SuccSE, SuccCL}, + SR->extend(CurrWeight, SuccWeight)); + } + + for (const auto &[SuccFact, RetSite, EntrySE, PushWeight] : + RP->getPushRules(CurrTgtFact, CurrTarget)) { + + if constexpr (requires() { RP->makeSrcFacts(SuccFact); }) { + for (const auto &RealSuccFact : RP->makeSrcFacts(SuccFact)) { + auto Entry = compressNode(RealSuccFact, EntrySE); + + propagate({Entry, EntrySE, SuccFact}, SR->identity()); + auto IncWeight = SR->extend(CurrWeight, PushWeight); + addIncoming(Entry, CurrSrc, RetSite, IncWeight); + applyEndSummary(Entry, CurrSrc, RetSite, IncWeight); + } + } else { + + auto Entry = compressNode(SuccFact, EntrySE); + + propagate({Entry, EntrySE, SuccFact}, SR->identity()); + auto IncWeight = SR->extend(CurrWeight, PushWeight); + addIncoming(Entry, CurrSrc, RetSite, IncWeight); + applyEndSummary(Entry, CurrSrc, RetSite, IncWeight); + } + } + + if constexpr (wpds::CanInjectAdditionalPushEdges) { + RP->injectAdditionalPushEdges( + CurrTgtFact, CurrTarget, + [&](llvm::ArrayRef> Edges, + weight_t W) { + if (!Edges.empty()) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "injectAdditionalPushEdges:"); + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + "injectAdditionalPushEdges: > For CurrTgtFact: " + << DToString(CurrTgtFact) << " AT " + << NToString(CurrTarget)); + } + + if constexpr (requires() { + RP->makeSrcFacts(std::get<0>(Edges.front())); + }) { + // TODO: This is extremely ugly, but it works... + const auto &[FirstCL, FirstEntrySE, FirstRetSite] = + Edges.front(); + for (const auto &RealCL : RP->makeSrcFacts(FirstCL)) { + auto TopmostSrc = CurrSrc; + + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + " > CL" << DToString(RealCL) + << "; EntrySE: " << NToString(FirstEntrySE) + << "; RetSite: " << NToString(FirstRetSite)); + + auto Entry = compressNode(RealCL, FirstEntrySE); + + propagate({Entry, FirstEntrySE, FirstCL}, SR->identity()); + auto IncWeight = + SR->extend(CurrWeight, std::exchange(W, SR->identity())); + addIncoming(Entry, TopmostSrc, FirstRetSite, IncWeight); + applyEndSummary(Entry, TopmostSrc, FirstRetSite, IncWeight); + + TopmostSrc = Entry; + + for (auto &&[CL, EntrySE, RetSite] : Edges.drop_front()) { + + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + " > CL" << DToString(CL) + << "; EntrySE: " << NToString(EntrySE) + << "; RetSite: " << NToString(RetSite)); + + auto Entry = compressNode(CL, EntrySE); + + propagate({Entry, EntrySE, CL}, SR->identity()); + auto IncWeight = SR->extend( + CurrWeight, std::exchange(W, SR->identity())); + addIncoming(Entry, TopmostSrc, RetSite, IncWeight); + applyEndSummary(Entry, TopmostSrc, RetSite, IncWeight); + + TopmostSrc = Entry; + } + } + } else { + auto TopmostSrc = CurrSrc; + for (auto &&[CL, EntrySE, RetSite] : Edges) { + + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + " > CL" << DToString(CL) + << "; EntrySE: " << NToString(EntrySE) + << "; RetSite: " << NToString(RetSite)); + + auto Entry = compressNode(CL, EntrySE); + + propagate({Entry, EntrySE, CL}, SR->identity()); + auto IncWeight = + SR->extend(CurrWeight, std::exchange(W, SR->identity())); + addIncoming(Entry, TopmostSrc, RetSite, IncWeight); + applyEndSummary(Entry, TopmostSrc, RetSite, IncWeight); + + TopmostSrc = Entry; + } + } + }); + } + + if (RP->hasPopRules(CurrTgtFact, CurrTarget)) { + + if (!addEndSummary(CurrSrc, CurrTgtFact, CurrTarget, CurrWeight)) { + continue; + } + + const auto &[_, CurrSource] = ESGNodeCompressor[CurrSrc]; + for (const auto &[CSNode, IncWeight] : Incoming[CurrSrc]) { + auto &&[CSSrc, RetSite] = CSNode; + auto SumExtWeight = SR->extend(IncWeight, CurrWeight); + for (const auto &[PopCL, PopWeight] : + RP->getPopRules(CurrTgtFact, CurrTarget, RetSite, CurrSource)) { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, "Summary Edge:"); + propagate({CSSrc, RetSite, PopCL}, + SR->extend(SumExtWeight, PopWeight)); + } + } + } + } + } + + [[nodiscard]] wpds::ESGNodeId compressNode(cl_t CL, se_t SE) { + auto [Id, Inserted] = + ESGNodeCompressor.insert({std::move(CL), std::move(SE)}); + if (Inserted) { + Incoming.emplace_back(); + EndSummary.emplace_back(); + } + return Id; + } + + void submitInitialSeeds() { + for (const auto &[InitCL, InitSE, InitWeight] : RP->initialSeeds()) { + auto Init = compressNode(InitCL, InitSE); + propagate({Init, InitSE, InitCL}, SR->identity()); + + auto [It, Inserted] = Seeds.try_emplace(Init, InitWeight); + if (!Inserted) { + It->second = SR->combine(It->second, InitWeight); + } + } + } + + void propagate(path_edge_t PE, weight_t Weight) { + auto &&[PESrc, SE, CL] = PE; + + auto [It, Inserted] = + PathEdges[std::pair{CL, SE}].try_emplace(PESrc, Weight); + if (!Inserted) { + Weight = SR->combine(It->second, Weight); + if (It->second != Weight) { + It->second = Weight; + Inserted = true; + + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[propagate]: UPDATED (" + << NToString(ESGNodeCompressor[PESrc].second) + << ", " + << DToString(ESGNodeCompressor[PESrc].first) + << ")\t-->\t(" << NToString(SE) << ", " + << DToString(CL) << ") w/ " << It->second); + } + } else { + PHASAR_LOG_LEVEL_CAT(DEBUG, LogCategory, + "[propagate]: NEW (" + << NToString(ESGNodeCompressor[PESrc].second) + << ", " + << DToString(ESGNodeCompressor[PESrc].first) + << ")\t-->\t(" << NToString(SE) << ", " + << DToString(CL) << ") w/ " << It->second); + } + + if (Inserted) { + WL.emplace_back(std::move(PE), std::move(Weight)); + } + } + + void addIncoming(wpds::ESGNodeId PushNod, wpds::ESGNodeId CSSrc, se_t RetSite, + weight_t PushWeight) { + + PHASAR_LOG_LEVEL_CAT( + DEBUG, LogCategory, + "[addIncoming]: PushFact: " + << DToString(ESGNodeCompressor[PushNod].first) + << ", PushSE: " << NToString(ESGNodeCompressor[PushNod].second) + << "; CurrSource: " << NToString(ESGNodeCompressor[CSSrc].second) + << ", CSSrcFact: " << DToString(ESGNodeCompressor[CSSrc].first) + << "; RetSite: " << NToString(RetSite)); + + auto [It, Inserted] = Incoming[PushNod].try_emplace( + std::pair{CSSrc, std::move(RetSite)}, std::move(PushWeight)); + if (!Inserted) { + It->second = SR->combine(std::move(It->second), std::move(PushWeight)); + } + } + + bool addEndSummary(wpds::ESGNodeId Src, cl_t TgtFact, se_t TgtSE, + weight_t TgtWeight) { + auto [It, Inserted] = EndSummary[Src].try_emplace( + std::pair{std::move(TgtFact), std::move(TgtSE)}, std::move(TgtWeight)); + if (!Inserted) { + auto Merged = SR->combine(It->second, TgtWeight); + if (It->second != Merged) { + It->second = std::move(Merged); + Inserted = true; + } + } + return Inserted; + } + + void applyEndSummary(wpds::ESGNodeId PushNod, wpds::ESGNodeId CSSrc, + se_t RetSite, weight_t PushWeight) { + const auto &Sum = EndSummary[PushNod]; + const auto &PushSE = ESGNodeCompressor[PushNod].second; + for (const auto &[SumNode, SumWeight] : Sum) { + auto &&[SumCL, SumSE] = SumNode; + auto SumExtWeight = SR->extend(PushWeight, SumWeight); + for (const auto &[PopCL, PopWeight] : + RP->getPopRules(SumCL, SumSE, RetSite, PushSE)) { + propagate({CSSrc, RetSite, PopCL}, SR->extend(SumExtWeight, PopWeight)); + } + } + } + + NonNullPtr RP{}; + NonNullPtr SR{}; + + llvm::SmallVector> WL; + + TypedVector, weight_t>> + EndSummary; +}; + +} // namespace psr diff --git a/include/phasar/DataFlow/WPDS/Solver/WPDSSolverResults.h b/include/phasar/DataFlow/WPDS/Solver/WPDSSolverResults.h new file mode 100644 index 0000000000..8240d44402 --- /dev/null +++ b/include/phasar/DataFlow/WPDS/Solver/WPDSSolverResults.h @@ -0,0 +1,397 @@ +#pragma once + +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/MapUtils.h" +#include "phasar/Utils/NonNullPtr.h" +#include "phasar/Utils/Printer.h" +#include "phasar/Utils/SemiRing.h" +#include "phasar/Utils/StrongTypeDef.h" +#include "phasar/Utils/TypeTraits.h" +#include "phasar/Utils/TypedVector.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/STLExtras.h" + +#include +#include + +PHASAR_STRONG_TYPEDEF(psr::wpds, uint32_t, ESGNodeId); + +namespace psr::wpds { + +template struct ComputeWeightsCache { + using weight_t = WeightTy; + + /// Don't call this ctor directly. Use createWeightCache() from the + /// SolverResults instead + explicit ComputeWeightsCache(std::true_type /*For internal use only*/) {} + + llvm::DenseMap> Data{}; +}; + +namespace detail { + +template +struct SolverResultsData { + using cl_t = CLTy; + using se_t = SETy; + using weight_t = WeightTy; + + Compressor, wpds::ESGNodeId> ESGNodeCompressor; + llvm::DenseMap, + llvm::SmallDenseMap> + PathEdges; + TypedVector, weight_t>> + Incoming; + llvm::SmallDenseMap Seeds{}; +}; + +template +class SolverResultsBase { +public: + using cl_t = CLTy; + using se_t = SETy; + using weight_t = WeightTy; + + [[nodiscard]] bool holdsFactAt(ByConstRef Fact, + ByConstRef At) const { + return holdsFactAt({Fact, At}); + } + + [[nodiscard]] bool + holdsFactAt(ByConstRef> FactAt) const { + for (const auto &[Src, Weight] : getOrDefault(self().pathEdges(), FactAt)) { + if (self().incoming()[Src].empty()) { + // Directly generated from zero + return true; + } + } + + return false; + } + + [[nodiscard]] bool isInResultSet(ByConstRef Fact, + ByConstRef At) const { + return isInResultSet({Fact, At}); + } + + [[nodiscard]] bool + isInResultSet(ByConstRef> FactAt) const { + return self().pathEdges().count(FactAt); + } + + bool traversePath( + ByConstRef> FactAt, + std::invocable auto WithPATransition) const { + llvm::SmallVector WL; + llvm::DenseSet Seen; + + for (const auto &[Src, Weight] : getOrDefault(self().pathEdges(), FactAt)) { + WL.emplace_back(Src); + Seen.insert(Src); + + const auto &[SrcCL, SrcSE] = self().esgNodeCompressor()[Src]; + std::invoke(WithPATransition, FactAt.first, FactAt.second, SrcCL, SrcSE, + Weight); + } + + if (WL.empty()) { + // Fact does not hold + return false; + } + + do { + auto Curr = WL.pop_back_val(); + const auto &[CurrCL, CurrSE] = self().esgNodeCompressor()[Curr]; + + const auto &Inc = self().incoming()[Curr]; + if (!Inc.empty()) { + for (const auto &[CSNode, Weight] : Inc) { + const auto &[CSSrc, RetSite] = CSNode; + + const auto &[CSSrcCL, CSSrcSE] = self().esgNodeCompressor()[CSSrc]; + std::invoke(WithPATransition, CurrCL, CurrSE, CSSrcCL, CSSrcSE, + Weight); + + if (Seen.insert(CSSrc).second) { + WL.emplace_back(CSSrc); + } + } + } + } while (!WL.empty()); + + return true; + } + + /// Computes the weight for the given pair, accumulated over all + /// accepting paths starting there. Re-use the WeightCache across calls to + /// this function for better performance. + /// + /// Should only be called if isInResultSet(Fact, At) is true. + [[nodiscard]] weight_t + computeWeightAt(ByConstRef Fact, ByConstRef At, + IsSemiRing auto &SR, + ComputeWeightsCache &WeightCache) const { + + const auto &PEs = getOrDefault(self().pathEdges(), std::pair{Fact, At}); + return mapCombine( + PEs, SR, [this, &SR, &WeightCache](const auto &SrcAndWeight) { + const auto &[Src, Weight] = SrcAndWeight; + auto IncVal = this->computeWeightAtRec(Src, SR, WeightCache); + return SR.extend(std::move(IncVal), Weight); + }); + } + + /// Computes the weight for the given pair, accumulated over all + /// accepting paths starting there. + /// + /// Use the overload of this function taking a ComputeWeightsCache (can be + /// initialized via createWeightCache()) for better batch performance if + /// calling this function multiple times. + /// + /// Should only be called if isInResultSet(Fact, At) is true. + [[nodiscard]] weight_t computeWeightAt(ByConstRef Fact, + ByConstRef At, + IsSemiRing auto &SR) const { + auto WeightCache = createWeightCache(); + return computeWeightAt(Fact, At, SR, WeightCache); + } + + /// Creates and initializes a new ComputeWeightsCache that can be used to + /// speed-up consecutive calls to computeWeightAt() + [[nodiscard]] auto createWeightCache() const { + ComputeWeightsCache WeightCache{std::true_type{}}; + WeightCache.Data.reserve(self().seeds().size()); + + for (const auto &[SeedNod, SeedWeight] : self().seeds()) { + WeightCache.Data.try_emplace(SeedNod, SeedWeight); + } + + return WeightCache; + } + + [[nodiscard]] auto getAllIFDSResultEntries() const { + return llvm::make_filter_range(self().esgNodeCompressor(), + [this](const auto &Entry) { + const auto &[CL, SE] = Entry; + return this->isInResultSet(CL, SE); + }); + } + + void dumpPath(ByConstRef Fact, ByConstRef At, + llvm::raw_ostream &OS); + + void dumpResults(llvm::raw_ostream &OS = llvm::outs()); + +private: + [[nodiscard]] weight_t + computeWeightAtRec(ESGNodeId Src, IsSemiRing auto &SR, + ComputeWeightsCache &WeightCache) const { + auto [It, Inserted] = WeightCache.Data.try_emplace(Src); + if (It->second) { + return *It->second; + } + if (!Inserted) { + // cycle detection + + // TODO: Is that correct? + return SR.identity(); + } + + const auto &Inc = self().incoming()[Src]; + + auto Ret = mapCombine( + Inc, SR, [this, &SR, &WeightCache](const auto &SrcSEAndWeight) { + const auto &[SrcSE, Weight] = SrcSEAndWeight; + const auto &[SrcSrc, _] = SrcSE; + + // XXX: Can we get rid of this recursion? + auto IncVal = this->computeWeightAtRec(SrcSrc, SR, WeightCache); + return SR.extend(std::move(IncVal), Weight); + }); + It->second.emplace(std::move(Ret)); + return *It->second; + } + + [[nodiscard]] weight_t mapCombine(auto &&Range, IsSemiRing auto &SR, + auto Transform) const { + auto It = llvm::adl_begin(Range); + auto End = llvm::adl_end(Range); + + if (It == End) { + return SR.identity(); + } + + auto Ret = std::invoke(Transform, *It); + for (++It; It != End; ++It) { + Ret = SR.combine(std::move(Ret), std::invoke(Transform, *It)); + } + return Ret; + } + + constexpr SolverResultsBase() noexcept = default; + friend Derived; + + [[nodiscard]] const auto &self() const noexcept { + return *static_cast(this); + } +}; + +template +void SolverResultsBase::dumpPath( + ByConstRef Fact, ByConstRef At, llvm::raw_ostream &OS) { + OS << "digraph Path {\n"; + + Compressor> CLC; + const auto AddCL = [&](cl_t CL) -> uint32_t { + auto [Id, Inserted] = CLC.insert(std::pair{CL, false}); + Id++; + if (Inserted) { + OS << " " << Id << "[label=\""; + OS.write_escaped(DToString(CL)) << "\"];\n"; + } + return Id; + }; + + const auto AddSrcCL = [&](cl_t CL) -> uint32_t { + auto [Id, Inserted] = CLC.insert(std::pair{CL, true}); + Id++; + if (Inserted) { + OS << " " << Id << "[label=\"q["; + OS.write_escaped(DToString(CL)) << "]\", style=dashed];\n"; + } + return Id; + }; + + traversePath({Fact, At}, + [&](const auto &TgtCL, const auto &TgtSE, const auto &SrcCL, + const auto & /*SrcSE*/, const auto & /*Weight*/) { + auto CurrCLId = AddCL(TgtCL); + auto SrcCLId = AddSrcCL(SrcCL); + + OS << " " << CurrCLId << "->" << SrcCLId << "[label=\""; + OS.write_escaped(NToString(TgtSE)) << "\"];\n"; + }); + + OS << "}\n"; +} + +template +void SolverResultsBase::dumpResults( + llvm::raw_ostream &OS) { + OS << "digraph RuleBasedSolver {\n"; + OS << " rankdir=LR;\n"; + OS << " node [shape=circle];\n"; + OS << " 0[label=\"ACC\", shape=doublecircle];\n"; + + Compressor> CLC; + const auto AddCL = [&](cl_t CL) -> uint32_t { + auto [Id, Inserted] = CLC.insert(std::pair{CL, false}); + Id++; + if (Inserted) { + OS << " " << Id << "[label=\""; + OS.write_escaped(DToString(CL)) << "\"];\n"; + } + return Id; + }; + + const auto AddSrcCL = [&](cl_t CL) -> uint32_t { + auto [Id, Inserted] = CLC.insert(std::pair{CL, true}); + Id++; + if (Inserted) { + OS << " " << Id << "[label=\"q["; + OS.write_escaped(DToString(CL)) << "]\", style=dashed];\n"; + } + return Id; + }; + for (const auto &[PE, Inner] : self().pathEdges()) { + const auto &[TgtCL, TgtSE] = PE; + auto TgtClId = AddCL(TgtCL); + + for (const auto &[Src, Weight] : Inner) { + const auto &[SrcCL, SrcSE] = self().esgNodeCompressor()[Src]; + auto SrcClId = AddSrcCL(SrcCL); + + OS << " " << TgtClId << "->" << SrcClId << "[label=\""; + OS.write_escaped(NToString(TgtSE)) << "\"];\n"; + + if (SrcSE == TgtSE && SrcCL == TgtCL) { + const auto &Inc = self().incoming()[Src]; + if (!Inc.empty()) { + for (const auto &[CSNode, _] : Inc) { + const auto &[CSSrc, RetSite] = CSNode; + const auto &CSSrcFact = self().esgNodeCompressor()[CSSrc].first; + + auto CSClId = AddSrcCL(CSSrcFact); + + OS << " " << SrcClId << "->" << CSClId << "[label=\""; + OS.write_escaped(NToString(SrcSE)) << "\", style=dashed];\n"; + } + } else { + OS << " " << SrcClId << "->0[label=\""; + OS.write_escaped(NToString(SrcSE)) << "\", style=dashed];\n"; + } + continue; + } + } + } + + OS << "}\n"; +} +} // namespace detail + +template +class OwningSolverResults + : public detail::SolverResultsBase< + OwningSolverResults, CLTy, SETy, WeightTy> { + friend detail::SolverResultsBase, + CLTy, SETy, WeightTy>; + +public: + OwningSolverResults(detail::SolverResultsData &&Data) + : Data(std::move(Data)) {} + +private: + [[nodiscard]] const auto &pathEdges() const noexcept { + return Data.PathEdges; + } + [[nodiscard]] const auto &incoming() const noexcept { return Data.Incoming; } + [[nodiscard]] const auto &esgNodeCompressor() const noexcept { + return Data.ESGNodeCompressor; + } + [[nodiscard]] const auto &seeds() const noexcept { return Data.Seeds; } + + detail::SolverResultsData Data; +}; + +template +class SolverResults + : public detail::SolverResultsBase, + CLTy, SETy, WeightTy> { + friend detail::SolverResultsBase, CLTy, + SETy, WeightTy>; + +public: + constexpr SolverResults( + NonNullPtr> + Data) noexcept + : Data(Data) {} + +private: + [[nodiscard]] const auto &pathEdges() const noexcept { + return Data->PathEdges; + } + [[nodiscard]] const auto &incoming() const noexcept { return Data->Incoming; } + [[nodiscard]] const auto &esgNodeCompressor() const noexcept { + return Data->ESGNodeCompressor; + } + [[nodiscard]] const auto &seeds() const noexcept { return Data->Seeds; } + + NonNullPtr> Data; +}; + +} // namespace psr::wpds diff --git a/include/phasar/Domain/AnalysisDomain.h b/include/phasar/Domain/AnalysisDomain.h index 6715ad0554..a510ab284b 100644 --- a/include/phasar/Domain/AnalysisDomain.h +++ b/include/phasar/Domain/AnalysisDomain.h @@ -13,6 +13,7 @@ #include "phasar/ControlFlow/CFG.h" #include "phasar/Domain/IRDomain.h" +#include #include namespace psr { @@ -29,8 +30,8 @@ concept IsAnalysisDomain = IRDomain && requires() { requires std::same_as; requires std::same_as; - typename T::c_t; - requires CFG; + // typename T::c_t; + // requires CFG; }; /// AnalysisDomain - This class should be specialized by different static @@ -72,7 +73,7 @@ struct AnalysisDomain { // Inter-procedural control flow --- Specifies the type of the // inter-procedural control-flow graph to be used. using i_t = void; - // The ProjectIRDB type to use. Must inherit from the ProjectIRDBBase CRTP + // The ProjectIRDB type to use. Must conform to the ProjectIRDB concept // template using db_t = void; // Lattice element --- Specifies the type of the underlying lattice; the value @@ -84,23 +85,32 @@ struct AnalysisDomain { enum class BinaryDomain; +template +concept HasValueDomain = requires() { + typename T::l_t; + requires(!std::is_void_v); +}; + namespace detail { -template -struct HasBinaryValueDomain : std::false_type {}; -template -struct HasBinaryValueDomain - : std::true_type {}; +template +concept HasBinaryValueDomain = + requires() { requires std::same_as; }; template struct WithBinaryValueDomainExtender : AnalysisDomainTy { using l_t = BinaryDomain; }; +template +using ValueDomainAdder = + std::conditional_t, AnalysisDomainTy, + WithBinaryValueDomainExtender>; + } // namespace detail template using WithBinaryValueDomain = - std::conditional_t::value, + std::conditional_t, AnalysisDomainTy, detail::WithBinaryValueDomainExtender>; diff --git a/include/phasar/Domain/LatticeDomain.h b/include/phasar/Domain/LatticeDomain.h index d9d9c779cd..51bfbe8b07 100644 --- a/include/phasar/Domain/LatticeDomain.h +++ b/include/phasar/Domain/LatticeDomain.h @@ -67,17 +67,17 @@ template struct LatticeDomain : public std::variant { using std::variant::variant; - [[nodiscard]] inline bool isBottom() const noexcept { + [[nodiscard]] constexpr bool isBottom() const noexcept { return std::holds_alternative(*this); } - [[nodiscard]] inline bool isTop() const noexcept { + [[nodiscard]] constexpr bool isTop() const noexcept { return std::holds_alternative(*this); } - [[nodiscard]] inline L *getValueOrNull() noexcept { + [[nodiscard]] constexpr L *getValueOrNull() noexcept { return std::get_if(this); } - [[nodiscard]] inline const L *getValueOrNull() const noexcept { + [[nodiscard]] constexpr const L *getValueOrNull() const noexcept { return std::get_if(this); } @@ -93,126 +93,102 @@ struct LatticeDomain : public std::variant { return hash_value(std::get(LD)); } - [[nodiscard]] inline L &assertGetValue() noexcept { + [[nodiscard]] constexpr L &assertGetValue() noexcept { assert(std::holds_alternative(*this)); return std::get(*this); } - [[nodiscard]] inline const L &assertGetValue() const noexcept { + [[nodiscard]] constexpr const L &assertGetValue() const noexcept { assert(std::holds_alternative(*this)); return std::get(*this); } template - void onValue(TransformFn Transform, ArgsT &&...Args) { + constexpr void onValue(TransformFn Transform, ArgsT &&...Args) { if (auto *Val = getValueOrNull()) { std::invoke(std::move(Transform), *Val, PSR_FWD(Args)...); } } -}; -template -inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const LatticeDomain &LD) { - if (LD.isBottom()) { - return OS << "Bottom"; - } - if (LD.isTop()) { - return OS << "Top"; + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const LatticeDomain &LD) { + if (LD.isBottom()) { + return OS << "Bottom"; + } + if (LD.isTop()) { + return OS << "Top"; + } + + const auto *Val = LD.getValueOrNull(); + assert(Val && "Only alternative remaining is L"); + if constexpr (is_llvm_printable_v) { + return OS << *Val; + } else { + return OS << PrettyPrinter{*Val}; + } } - const auto *Val = LD.getValueOrNull(); - assert(Val && "Only alternative remaining is L"); - if constexpr (is_llvm_printable_v) { - return OS << *Val; - } else { - return OS << PrettyPrinter{*Val}; + friend std::ostream &operator<<(std::ostream &OS, const LatticeDomain &LD) { + llvm::raw_os_ostream ROS(OS); + ROS << LD; + return OS; } -} -template -inline std::ostream &operator<<(std::ostream &OS, const LatticeDomain &LD) { - llvm::raw_os_ostream ROS(OS); - ROS << LD; - return OS; -} + constexpr bool operator==(const LatticeDomain &Rhs) const { + if (this->index() != Rhs.index()) { + return false; + } + if (auto LhsPtr = this->getValueOrNull()) { + /// No need to check whether Rhs is an L; the indices are already the same + return *LhsPtr == *Rhs.getValueOrNull(); + } + return true; + } -template -inline bool operator==(const LatticeDomain &Lhs, - const LatticeDomain &Rhs) { - if (Lhs.index() != Rhs.index()) { + template + requires AreEqualityComparable + constexpr bool operator==(const LL &Rhs) const { + if (auto LVal = this->getValueOrNull()) { + return *LVal == Rhs; + } return false; } - if (auto LhsPtr = Lhs.getValueOrNull()) { - /// No need to check whether Lhs is an L; the indices are already the same - return *LhsPtr == *Rhs.getValueOrNull(); - } - return true; -} -template - requires AreEqualityComparable -inline bool operator==(const LL &Lhs, const LatticeDomain &Rhs) { - if (auto RVal = Rhs.getValueOrNull()) { - return Lhs == *RVal; + constexpr bool operator==(Bottom /*Rhs*/) const noexcept { + return this->isBottom(); } - return false; -} - -template - requires AreEqualityComparable -inline bool operator==(const LatticeDomain &Lhs, const LL &Rhs) { - return Rhs == Lhs; -} -template -inline bool operator==(const LatticeDomain &Lhs, Bottom /*Rhs*/) noexcept { - return Lhs.isBottom(); -} - -template -inline bool operator==(const LatticeDomain &Lhs, Top /*Rhs*/) noexcept { - return Lhs.isTop(); -} - -template -inline bool operator==(Bottom /*Lhs*/, const LatticeDomain &Rhs) noexcept { - return Rhs.isBottom(); -} - -template -inline bool operator==(Top /*Lhs*/, const LatticeDomain &Rhs) noexcept { - return Rhs.isTop(); -} - -template -inline bool operator<(const LatticeDomain &Lhs, - const LatticeDomain &Rhs) { - /// Top < (Lhs::L < Rhs::L) < Bottom - if (Rhs.isTop()) { - return false; + constexpr bool operator==(Top /*Rhs*/) const noexcept { + return this->isTop(); } - if (Lhs.isTop()) { - return true; - } - if (auto LhsPtr = Lhs.getValueOrNull()) { - if (auto RhsPtr = Rhs.getValueOrNull()) { - return *LhsPtr < *RhsPtr; + + constexpr bool operator<(const LatticeDomain &Rhs) const { + /// Top < (Lhs::L < Rhs::L) < Bottom + if (Rhs.isTop()) { + return false; } - } else if (Lhs.isBottom()) { - return false; - } - if (Rhs.isBottom()) { - return true; + if (this->isTop()) { + return true; + } + if (auto LhsPtr = this->getValueOrNull()) { + if (auto RhsPtr = Rhs.getValueOrNull()) { + return *LhsPtr < *RhsPtr; + } + } else if (this->isBottom()) { + return false; + } + if (Rhs.isBottom()) { + return true; + } + llvm_unreachable("All comparison cases should be handled above."); } - llvm_unreachable("All comparison cases should be handled above."); -} +}; template struct JoinLatticeTraits> { using l_t = L; static constexpr Bottom bottom() noexcept { return {}; } static constexpr Top top() noexcept { return {}; } - static LatticeDomain join(ByConstRef> LHS, - ByConstRef> RHS) { + static constexpr LatticeDomain join(ByConstRef> LHS, + ByConstRef> RHS) { // Top < (Lhs::l_t < Rhs::l_t) < Bottom if (LHS.isTop() || LHS == RHS) { return RHS; @@ -242,7 +218,7 @@ template struct NonTopBotValue> { using type = L; - static L unwrap(LatticeDomain Value) noexcept( + constexpr static L unwrap(LatticeDomain Value) noexcept( std::is_nothrow_move_constructible_v) { return std::get(std::move(Value)); } @@ -252,7 +228,7 @@ struct NonTopBotValue> { namespace std { template struct hash> { - size_t operator()(const psr::LatticeDomain &LD) noexcept { + constexpr size_t operator()(const psr::LatticeDomain &LD) noexcept { if (LD.isBottom()) { return SIZE_MAX; } diff --git a/include/phasar/PhasarLLVM/ControlFlow.h b/include/phasar/PhasarLLVM/ControlFlow.h index 7019a511d7..b5efe48f9e 100644 --- a/include/phasar/PhasarLLVM/ControlFlow.h +++ b/include/phasar/PhasarLLVM/ControlFlow.h @@ -11,6 +11,7 @@ #define PHASAR_PHASARLLVM_CONTROLFLOW_H #include "phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h" +#include "phasar/PhasarLLVM/ControlFlow/FunctionCompressor.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedBackwardCFG.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedBackwardICFG.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" @@ -21,5 +22,6 @@ #include "phasar/PhasarLLVM/ControlFlow/Resolver/RTAResolver.h" #include "phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h" #include "phasar/PhasarLLVM/ControlFlow/Resolver/VTAResolver.h" +#include "phasar/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.h" #endif // PHASAR_PHASARLLVM_CONTROLFLOW_H diff --git a/include/phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h b/include/phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h index 78003dcd9b..04c79f4e23 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h +++ b/include/phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h @@ -18,10 +18,18 @@ namespace psr { class LLVMProjectIRDB; +class LLVMBasedICFG; + +/// Returns true, if F is a definition that a client outside the module under +/// analysis is able to call. Used to expand the special entry point "__ALL__". +[[nodiscard]] bool isExternallyCallable(const llvm::Function &F) noexcept; [[nodiscard]] std::vector getEntryFunctions(const LLVMProjectIRDB &IRDB, llvm::ArrayRef EntryPoints); +[[nodiscard]] std::vector +getEntryFunctions(const LLVMBasedICFG &ICF, + llvm::ArrayRef EntryPoints); [[nodiscard]] std::vector getEntryFunctionsMut(LLVMProjectIRDB &IRDB, diff --git a/include/phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h b/include/phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h new file mode 100644 index 0000000000..b9333163e1 --- /dev/null +++ b/include/phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#ifndef PHASAR_PHASARLLVM_CONTROLFLOW_EXTERNCALLBACKMODEL_H +#define PHASAR_PHASARLLVM_CONTROLFLOW_EXTERNCALLBACKMODEL_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/IR/Function.h" + +namespace psr { +class LLVMProjectIRDB; + +/// \brief Provides utilities to model calls through known external callback +/// brokers. +/// +/// The inserted model keeps the original broker call and adds a regular call to +/// the callback argument, so later analyses still see an ordinary call-site. +class ExternCallbackModel { +public: + static constexpr llvm::StringLiteral ModelPrefix = "__psrExternCallbackModel"; + + /// Rewrites all supported external callback call-sites in \p IRDB. + /// + /// \returns the number of rewritten call-sites. + static size_t rewriteCalls(LLVMProjectIRDB &IRDB); + + /// Returns true, if a function was generated by the ExternCallbackModel. + [[nodiscard]] static bool isPhasarGenerated(const llvm::Function &F) noexcept; +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_CONTROLFLOW_EXTERNCALLBACKMODEL_H diff --git a/include/phasar/PhasarLLVM/ControlFlow/FunctionCompressor.h b/include/phasar/PhasarLLVM/ControlFlow/FunctionCompressor.h new file mode 100644 index 0000000000..c104fa187b --- /dev/null +++ b/include/phasar/PhasarLLVM/ControlFlow/FunctionCompressor.h @@ -0,0 +1,20 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/FunctionId.h" + +namespace psr { +Compressor +compressFunctions(const LLVMBasedCallGraph &CG, + llvm::ArrayRef EntryPoints); +} // namespace psr diff --git a/include/phasar/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.h b/include/phasar/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.h index 9f0a7f8dc1..abfe839748 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.h +++ b/include/phasar/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.h @@ -11,8 +11,13 @@ #define PHASAR_PHASARLLVM_CONTROLFLOW_GLOBALCTORSDTORSMODEL_H #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/IR/Function.h" +namespace llvm { +class Module; +} // namespace llvm + namespace psr { class LLVMProjectIRDB; @@ -36,6 +41,9 @@ class GlobalCtorsDtorsModel { static constexpr llvm::StringLiteral UserEntrySelectorName = "__psrCRuntimeUserEntrySelector"; + static constexpr llvm::StringLiteral NonDetValuePrefix = + "__psrCRuntimeNonDetValue"; + /// @brief Function that creates a new global model and inserts it into the /// IRDB. The returned function is the global model that was inserted. /// @param[in, out] IRDB IR database that will have the global model @@ -56,6 +64,11 @@ class GlobalCtorsDtorsModel { buildModel(LLVMProjectIRDB &IRDB, llvm::ArrayRef UserEntryPoints); + /// Returns all functions that are referenced from llvm.global_ctors or + /// llvm.global_dtors. + [[nodiscard]] static llvm::DenseSet + collectGlobalCtorsDtors(const llvm::Module &M); + /// Returns true, if a function was generated by the GlobalCtorsDtorsModel. [[nodiscard]] static bool isPhasarGenerated(const llvm::Function &F) noexcept; }; diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h index 6bc968f4fc..f654a771d3 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h @@ -11,6 +11,7 @@ #define PHASAR_PHASARLLVM_CONTROLFLOW_LLVMBASEDCFG_H_ #include "phasar/ControlFlow/CFGBase.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" @@ -76,7 +77,9 @@ template class LLVMBasedCFGImpl : public CFGBase { [[nodiscard]] bool isExitInstImpl(n_t Inst) const noexcept { return llvm::isa(Inst); } - [[nodiscard]] bool isStartPointImpl(n_t Inst) const noexcept; + [[nodiscard]] bool isStartPointImpl(n_t Inst) const noexcept { + return isStartInst(Inst); + } [[nodiscard]] bool isFieldLoadImpl(n_t Inst) const noexcept; [[nodiscard]] bool isFieldStoreImpl(n_t Inst) const noexcept; [[nodiscard]] bool isFallThroughSuccessorImpl(n_t Inst, diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h index 1503b1d318..e34c906723 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h @@ -24,6 +24,10 @@ class Resolver; /// Constructs a call-graph using the given CGResolver to resolve indirect /// calls. /// +/// This overload does not mutate the IRDB. Use +/// buildLLVMBasedCallGraphWithExternCallbackModels() if PhASAR should insert +/// its model functions first. +/// /// Uses a fixpoint iteration, if /// `CGResolver.mutatesHelperAnalysisInformation()` returns true and the /// soundness S is not Soundness::Unsound. @@ -40,9 +44,24 @@ buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, Soundness S = Soundness::Soundy); +/// May insert model functions for known external callback brokers before the +/// call-graph is built. +/// If CGResolver owns helper-analysis state that should include generated model +/// functions, rewrite the IRDB before constructing that state or use a CGType +/// overload. +[[nodiscard]] LLVMBasedCallGraph +buildLLVMBasedCallGraphWithExternCallbackModels( + LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, + Soundness S = Soundness::Soundy); + /// Constructs a call-graph using the given CGResolver to resolve indirect /// calls. /// +/// This overload does not mutate the IRDB. Use +/// buildLLVMBasedCallGraphWithExternCallbackModels() if PhASAR should insert +/// its model functions first. +/// /// Uses a fixpoint iteration, if /// `CGResolver.mutatesHelperAnalysisInformation()` returns true and the /// soundness S is not Soundness::Unsound. @@ -59,6 +78,27 @@ buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, Soundness S = Soundness::Soundy); +/// May insert model functions for known external callback brokers before the +/// call-graph is built. +/// If CGResolver owns helper-analysis state that should include generated model +/// functions, rewrite the IRDB before constructing that state or use a CGType +/// overload. +[[nodiscard]] LLVMBasedCallGraph +buildLLVMBasedCallGraphWithExternCallbackModels( + LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, Soundness S = Soundness::Soundy); + +/// Kept for compatibility with LLVMBasedICFG. See the constructor of +/// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, +/// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, +/// Soundness, bool) for more information. +[[nodiscard]] LLVMBasedCallGraph +buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, + llvm::ArrayRef EntryPoints, + DIBasedTypeHierarchy *TH, LLVMVFTableProvider &VTP, + LLVMAliasInfoRef PT = nullptr, + Soundness S = Soundness::Soundy); + /// Kept for compatibility with LLVMBasedICFG. See the constructor of /// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, /// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, @@ -70,6 +110,17 @@ buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, LLVMAliasInfoRef PT = nullptr, Soundness S = Soundness::Soundy); +/// Kept for compatibility with LLVMBasedICFG. See the constructor of +/// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, +/// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, +/// Soundness, bool) for more information. +[[nodiscard]] LLVMBasedCallGraph +buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, + llvm::ArrayRef EntryPoints, + DIBasedTypeHierarchy *TH, LLVMVFTableProvider &VTP, + LLVMAliasInfoRef PT = nullptr, + Soundness S = Soundness::Soundy); + /// Kept for compatibility with LLVMBasedICFG. See the constructor of /// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, /// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h index 49b47b98ae..e1aa638a55 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h @@ -20,6 +20,7 @@ #include "phasar/ControlFlow/CallGraph.h" #include "phasar/ControlFlow/CallGraphAnalysisType.h" #include "phasar/ControlFlow/ICFGBase.h" +#include "phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h" #include "phasar/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" @@ -74,6 +75,9 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { /// \param IncludeGlobals Properly include global constructors/destructors /// into the ICFG, if true. Requires to generate artificial functions into the /// IRDB. True by default + /// + /// Note that LLVMBasedICFG may also insert model functions for known external + /// callback brokers, independent of IncludeGlobals. explicit LLVMBasedICFG(LLVMProjectIRDB *IRDB, CallGraphAnalysisType CGType, llvm::ArrayRef EntryPoints = {}, DIBasedTypeHierarchy *TH = nullptr, @@ -132,7 +136,8 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { /// Returns true, if a function was generated by phasar. [[nodiscard]] static bool isPhasarGenerated(const llvm::Function &F) noexcept { - return GlobalCtorsDtorsModel::isPhasarGenerated(F); + return GlobalCtorsDtorsModel::isPhasarGenerated(F) || + ExternCallbackModel::isPhasarGenerated(F); } using CFGBase::print; diff --git a/include/phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h b/include/phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h index b2cba8ae3a..fbbc70d21f 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h +++ b/include/phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h @@ -18,11 +18,11 @@ #define PHASAR_PHASARLLVM_CONTROLFLOW_RESOLVER_RESOLVER_H_ #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" +#include "phasar/PhasarLLVM/Utils/VirtualCallUtils.h" #include "phasar/Utils/MaybeUniquePtr.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/SmallVector.h" -#include "llvm/IR/DerivedTypes.h" #include #include @@ -41,15 +41,6 @@ class LLVMVFTableProvider; class DIBasedTypeHierarchy; enum class CallGraphAnalysisType; -/// Assuming that `CallSite` is a virtual call through a vtable, retrieves the -/// index in the vtable of the virtual function called. -[[nodiscard]] std::optional -getVFTIndex(const llvm::CallBase *CallSite); - -/// Similar to getVFTIndex(), but also returns a pointer to the vtable -[[nodiscard]] std::optional> -getVFTIndexAndVT(const llvm::CallBase *CallSite); - /// Assuming that `CallSite` is a call to a non-static member function, /// retrieves the type of the receiver. Returns nullptr, if the receiver-type /// could not be extracted @@ -64,12 +55,6 @@ getReceiverType(const llvm::CallBase *CallSite); [[nodiscard]] std::string getReceiverTypeName(const llvm::CallBase *CallSite); -/// Checks whether the signature of `DestFun` matches the required withature of -/// `CallSite`, such that `DestFun` qualifies as callee-candidate, if `CallSite` -/// is an indirect/virtual call. -[[nodiscard]] bool isConsistentCall(const llvm::CallBase *CallSite, - const llvm::Function *DestFun); - [[nodiscard]] bool isVirtualCall(const llvm::Instruction *Inst, const LLVMVFTableProvider &VTP); diff --git a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h index f2fc45c0b5..7ed91b0a60 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFG.h @@ -10,6 +10,7 @@ #ifndef PHASAR_PHASARLLVM_CONTROLFLOW_SPARSELLVMBASEDICFG_H #define PHASAR_PHASARLLVM_CONTROLFLOW_SPARSELLVMBASEDICFG_H +#include "phasar/ControlFlow/SparseCFGProvider.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedCFGProvider.h" @@ -55,10 +56,17 @@ class SparseLLVMBasedICFG ~SparseLLVMBasedICFG(); + [[nodiscard]] n_t advanceToNextUser(n_t Succ, const auto &Fact) const { + using psr::valueOf; + return advanceToNextUserImpl(Succ, valueOf(Fact)); + } + private: [[nodiscard]] const SparseLLVMBasedCFG & getSparseCFGImpl(const llvm::Function *Fun, const llvm::Value *Val) const; + [[nodiscard]] n_t advanceToNextUserImpl(n_t Succ, v_t Fact) const; + std::unique_ptr SparseCFGCache; LLVMAliasInfoRef AliasAnalysis; }; diff --git a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.h b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.h index 303bee8fb3..446e2e59d6 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.h +++ b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedICFGView.h @@ -56,6 +56,11 @@ class SparseLLVMBasedICFGView // To make the IDESolver happy... operator const LLVMBasedICFG &() const noexcept { return *ICF; } + [[nodiscard]] n_t advanceToNextUser(n_t Succ, const auto &Fact) const { + using psr::valueOf; + return advanceToNextUserImpl(Succ, valueOf(Fact)); + } + private: [[nodiscard]] FunctionRange getAllFunctionsImpl() const; [[nodiscard]] f_t getFunctionImpl(llvm::StringRef Fun) const; @@ -72,6 +77,10 @@ class SparseLLVMBasedICFGView [[nodiscard]] const SparseLLVMBasedCFG & getSparseCFGImpl(const llvm::Function *Fun, const llvm::Value *Val) const; + [[nodiscard]] n_t advanceToNextUserImpl(n_t Succ, v_t Fact) const; + + [[nodiscard]] size_t getNumCallSitesImpl() const noexcept; + const LLVMBasedICFG *ICF{}; std::unique_ptr SparseCFGCache; LLVMAliasInfoRef AliasAnalysis; diff --git a/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.h b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.h new file mode 100644 index 0000000000..ebc4613fa2 --- /dev/null +++ b/include/phasar/PhasarLLVM/ControlFlow/SparseLLVMControlFlow.h @@ -0,0 +1,73 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/ControlFlow/SparseLLVMBasedCFGProvider.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" + +#include "llvm/IR/CFG.h" +#include "llvm/IR/InstrTypes.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/Value.h" + +namespace psr { +class SparseLLVMControlFlow { +public: + using n_t = const llvm::Instruction *; + using v_t = const llvm::Value *; + + /// \brief Advances Succ to the unique next instruction that may use + /// Fact, based on the given alias information. If Succ may use Fact itself, + /// or if there are potentially many next users, returns Succ. + [[nodiscard]] static n_t advanceToNextUser(n_t Succ, const auto &Fact, + LLVMAliasInfoRef AI) { + using psr::valueOf; + static_assert(std::is_convertible_v); + return advanceToNextUserImpl(Succ, valueOf(Fact), AI); + } + + /// \brief Whether Inst may use Val, based on the given alias information + [[nodiscard]] static bool shouldKeepInst(n_t Inst, v_t Val, + LLVMAliasInfoRef AI); + +private: + [[nodiscard]] static bool isNoopIntrinsic(n_t Inst) { + // isAssumeLikeIntrinsic() alone is not enough: llvm.objectsize and + // llvm.ptr_annotation derive their result from a pointer operand + const auto *II = llvm::dyn_cast(Inst); + return II && II->isAssumeLikeIntrinsic() && II->getType()->isVoidTy(); + } + + [[nodiscard]] static bool isExitInst(n_t Inst) { + return llvm::isa( + Inst); + } + + [[nodiscard]] static n_t advanceToNextUserImpl(n_t Succ, v_t Fact, + LLVMAliasInfoRef AI) { + if (Succ == Fact || isExitInst(Succ) || isStartInst(Succ)) { + return Succ; + } + if (llvm::isa(Succ) && !isNoopIntrinsic(Succ)) { + if (llvm::isa(Fact)) { + return Succ; + } + } + + return advanceToNextUserImplInternal(Succ, Fact, AI); + } + + [[nodiscard]] static n_t advanceToNextUserImplInternal(n_t Succ, v_t Fact, + LLVMAliasInfoRef AI); +}; +} // namespace psr diff --git a/include/phasar/PhasarLLVM/DB/LLVMProjectIRDB.h b/include/phasar/PhasarLLVM/DB/LLVMProjectIRDB.h index 337ac8ce64..cea6d7afbc 100644 --- a/include/phasar/PhasarLLVM/DB/LLVMProjectIRDB.h +++ b/include/phasar/PhasarLLVM/DB/LLVMProjectIRDB.h @@ -10,7 +10,6 @@ #ifndef PHASAR_PHASARLLVM_DB_LLVMPROJECTIRDB_H #define PHASAR_PHASARLLVM_DB_LLVMPROJECTIRDB_H -#include "phasar/DB/ProjectIRDBBase.h" #include "phasar/PhasarLLVM/Utils/LLVMBasedContainerConfig.h" #include "phasar/Utils/MaybeUniquePtr.h" @@ -29,37 +28,21 @@ #include namespace psr { -class LLVMProjectIRDB; -template <> struct ProjectIRDBTraits { +/// \brief Project IR Database that manages a LLVM IR module. +class LLVMProjectIRDB { + +public: using n_t = const llvm::Instruction *; using f_t = const llvm::Function *; using m_t = const llvm::Module *; using g_t = const llvm::GlobalVariable *; -}; -/// \brief Project IR Database that manages a LLVM IR module. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -class LLVMProjectIRDB : public ProjectIRDBBase { -#pragma GCC diagnostic pop - friend ProjectIRDBBase; - -public: /// Reads and parses the given LLVM IR file and owns the resulting IR Module. /// If an error occurs, an error message is written to stderr and subsequent /// calls to isValid() return false. explicit LLVMProjectIRDB(const llvm::Twine &IRFileName); - /// Reads and parses the given LLVM IR file and owns the resulting IR Module. - /// If an error occurs, an error message is written to stderr and subsequent - /// calls to isValid() return false. - [[deprecated("When moving to the next LLVM version, opaque pointers support " - "is removed completely. Please use one of the other " - "constructors of LLVMProjectIRDB.")]] - explicit LLVMProjectIRDB(const llvm::Twine &IRFileName, - bool EnableOpaquePointers); - /// Initializes the new ProjectIRDB with the given IR Module _without_ taking /// ownership. The module is optionally being preprocessed. /// @@ -83,15 +66,6 @@ class LLVMProjectIRDB : public ProjectIRDBBase { /// calls to isValid() return false. explicit LLVMProjectIRDB(llvm::MemoryBufferRef Buf); - /// Parses the given LLVM IR file and owns the resulting IR Module. - /// If an error occurs, an error message is written to stderr and subsequent - /// calls to isValid() return false. - [[deprecated("When moving to the next LLVM version, opaque pointers support " - "is removed completely. Please use one of the other " - "constructors of LLVMProjectIRDB.")]] - explicit LLVMProjectIRDB(llvm::MemoryBufferRef Buf, - bool EnableOpaquePointers); - LLVMProjectIRDB(const LLVMProjectIRDB &) = delete; LLVMProjectIRDB &operator=(const LLVMProjectIRDB &) = delete; @@ -115,27 +89,37 @@ class LLVMProjectIRDB : public ProjectIRDBBase { [[nodiscard]] static LLVMProjectIRDB loadOrExit(const llvm::Twine &IRFileName, bool EnableOpaquePointers) = delete; - /// Also use the const overload - using ProjectIRDBBase::getFunction; /// Non-const overload [[nodiscard]] llvm::Function *getFunction(llvm::StringRef FunctionName) { + assert(isValid()); + return Mod->getFunction(FunctionName); + } + /// Returns the function if available, nullptr/nullopt otherwise. + [[nodiscard]] f_t getFunction(llvm::StringRef FunctionName) const { + assert(isValid()); return Mod->getFunction(FunctionName); } - /// Also use the const overload - using ProjectIRDBBase::getFunctionDefinition; - /// Non-const overload + /// Returns a mutable pointer to the function's definition if available, null + /// otherwise. [[nodiscard]] llvm::Function * getFunctionDefinition(llvm::StringRef FunctionName); - /// Also use the const overload - using ProjectIRDBBase::getModule; - /// Non-const overload + /// Returns the function's definition if available, null otherwise. + [[nodiscard]] f_t getFunctionDefinition(llvm::StringRef FunctionName) const; + + /// Returns the managed module [[nodiscard]] llvm::Module *getModule() noexcept { return Mod.get(); } + /// Returns the managed module + [[nodiscard]] const llvm::Module *getModule() const noexcept { + return Mod.get(); + } + /// Similar to getInstruction(size_t), but is also able to return global /// variables by id [[nodiscard]] const llvm::Value *getValueFromId(size_t Id) const noexcept { + assert(isValid()); return Id < IdToInst.size() ? IdToInst[Id] : nullptr; } @@ -167,48 +151,41 @@ class LLVMProjectIRDB : public ProjectIRDBBase { /// Returns a range of all global variables (and global constants, e.g, string /// literals) in the managed module [[nodiscard]] auto getAllGlobals() const { + assert(isValid()); return llvm::map_range(std::as_const(*Mod).globals(), Ref2PointerConverter{}); } explicit operator bool() const noexcept { return isValid(); } -private: - [[nodiscard]] m_t getModuleImpl() const noexcept { return Mod.get(); } - [[nodiscard]] bool debugInfoAvailableImpl() const; - [[nodiscard]] FunctionRange getAllFunctionsImpl() const { - return llvm::map_range(ProjectIRDBBase::getModule()->functions(), + [[nodiscard]] bool debugInfoAvailable() const; + [[nodiscard]] FunctionRange getAllFunctions() const { + return llvm::map_range(getModule()->functions(), Ref2PointerConverter{}); } - [[nodiscard]] f_t getFunctionImpl(llvm::StringRef FunctionName) const { - return Mod->getFunction(FunctionName); - } - [[nodiscard]] f_t - getFunctionDefinitionImpl(llvm::StringRef FunctionName) const; - [[nodiscard]] bool - hasFunctionImpl(llvm::StringRef FunctionName) const noexcept { + + [[nodiscard]] bool hasFunction(llvm::StringRef FunctionName) const noexcept { + assert(isValid()); return Mod->getFunction(FunctionName) != nullptr; } - [[nodiscard]] f_t getFunctionOfImpl(n_t Inst) const { - assert(Inst != nullptr); - return Inst->getFunction(); - } + [[nodiscard]] g_t getGlobalVariable(llvm::StringRef GlobalVariableName) const; [[nodiscard]] g_t - getGlobalVariableImpl(llvm::StringRef GlobalVariableName) const; - [[nodiscard]] g_t - getGlobalVariableDefinitionImpl(llvm::StringRef GlobalVariableName) const; - [[nodiscard]] size_t getNumInstructionsImpl() const noexcept { + getGlobalVariableDefinition(llvm::StringRef GlobalVariableName) const; + + [[nodiscard]] size_t getNumInstructions() const noexcept { return IdToInst.size() - IdOffset; } - [[nodiscard]] size_t getNumFunctionsImpl() const noexcept { + [[nodiscard]] size_t getNumFunctions() const noexcept { + assert(isValid()); return Mod->size(); } - [[nodiscard]] size_t getNumGlobalsImpl() const noexcept { + [[nodiscard]] size_t getNumGlobals() const noexcept { + assert(isValid()); return Mod->global_size(); } - [[nodiscard]] n_t getInstructionImpl(size_t Id) const noexcept { + [[nodiscard]] n_t getInstruction(size_t Id) const noexcept { // Effectively make use of integer overflow here... if (Id - IdOffset < IdToInst.size() - IdOffset) { return llvm::cast(IdToInst[Id]); @@ -216,21 +193,22 @@ class LLVMProjectIRDB : public ProjectIRDBBase { return n_t{}; } - [[nodiscard]] auto getAllInstructionsImpl() const noexcept { + [[nodiscard]] auto getAllInstructions() const noexcept { return llvm::map_range( llvm::ArrayRef(IdToInst).drop_front(IdOffset), [](const llvm::Value *V) { return llvm::cast(V); }); } - [[nodiscard]] size_t getInstructionIdImpl(n_t Inst) const { + [[nodiscard]] size_t getInstructionId(n_t Inst) const { auto It = InstToId.find(Inst); assert(It != InstToId.end()); return It->second; } - [[nodiscard]] bool isValidImpl() const noexcept; + [[nodiscard]] bool isValid() const noexcept; - void dumpImpl() const; + void dump() const; +private: void initInstructionIds(); /// XXX Later we might get rid of the metadata IDs entirely and therefore of /// the preprocessing as well @@ -250,7 +228,6 @@ class LLVMProjectIRDB : public ProjectIRDBBase { const llvm::Value *fromMetaDataId(const LLVMProjectIRDB &IRDB, llvm::StringRef Id); -extern template class ProjectIRDBBase; } // namespace psr #endif // PHASAR_PHASARLLVM_DB_LLVMPROJECTIRDB_H diff --git a/include/phasar/PhasarLLVM/DataFlow.h b/include/phasar/PhasarLLVM/DataFlow.h index 1c4e6a4611..f3c6b63579 100644 --- a/include/phasar/PhasarLLVM/DataFlow.h +++ b/include/phasar/PhasarLLVM/DataFlow.h @@ -31,5 +31,7 @@ #include "phasar/PhasarLLVM/DataFlow/Mono/Problems/IntraMonoFullConstantPropagation.h" #include "phasar/PhasarLLVM/DataFlow/Mono/Problems/IntraMonoSolverTest.h" #include "phasar/PhasarLLVM/DataFlow/Mono/Problems/IntraMonoUninitVariables.h" +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h" +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h" #endif // PHASAR_PHASARLLVM_DATAFLOW_H diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h index 7688177425..6a5399ceea 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/CFLFieldSensIFDSProblem.h @@ -10,8 +10,8 @@ #ifndef PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_FIELDSENSALLOCSITESAWAREIFDSPROBLEM_H #define PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_FIELDSENSALLOCSITESAWAREIFDSPROBLEM_H -#include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" -#include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/IFDSProblem.h" +#include "phasar/DataFlow/IfdsIde/IfdsIdeProblemMixin.h" #include "phasar/Domain/BinaryDomain.h" #include "phasar/Domain/LatticeDomain.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" @@ -19,8 +19,13 @@ #include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/Utils/Compressor.h" +#include "phasar/Utils/Fn.h" #include "phasar/Utils/Logger.h" #include "phasar/Utils/MapUtils.h" +#include "phasar/Utils/NonNullPtr.h" +#include "phasar/Utils/SmallArraySet.h" +#include "phasar/Utils/StrongTypeDef.h" +#include "phasar/Utils/TableWrappers.h" #include "phasar/Utils/TypeTraits.h" #include "phasar/Utils/TypedVector.h" #include "phasar/Utils/Utilities.h" @@ -28,6 +33,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMapInfo.h" #include "llvm/ADT/FunctionExtras.h" +#include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/IR/Operator.h" @@ -37,21 +43,16 @@ #include #include -namespace psr::cfl_fieldsens { - /// \file /// Implements field-sensitivity after the paper "Boosting the performance /// of alias-aware IFDS analysis with CFL-based environment transformers" by Li /// et al. -// NOLINTNEXTLINE(performance-enum-size) -enum class FieldStringNodeId : uint32_t { - None = 0, -}; +PHASAR_STRONG_TYPEDEF(psr::cfl_fieldsens, uint32_t, FieldStringNodeId, + None = 0); -[[nodiscard]] inline llvm::hash_code hash_value(FieldStringNodeId NId) { - return llvm::hash_value(std::underlying_type_t(NId)); -} +PHASAR_STRONG_TYPEDEF(psr::cfl_fieldsens, uint32_t, KillSetId, Empty = 0); +namespace psr::cfl_fieldsens { struct FieldStringNode { FieldStringNodeId Next{}; @@ -93,6 +94,8 @@ namespace cfl_fieldsens { /// Interns the Store- and Load field-strings class FieldStringManager { public: + static constexpr int32_t TopOffset = INT32_MIN; + FieldStringManager(); [[nodiscard]] FieldStringNodeId intern(FieldStringNode Nod) { @@ -127,44 +130,75 @@ class FieldStringManager { return Depth[NId]; } + [[nodiscard]] KillSetId internKills(SmallArraySet &&Kills) { + return KillsCompressor.getOrInsert(std::move(Kills)); + } + + [[nodiscard]] KillSetId addKill(KillSetId KS, int32_t Offs) { + if (Offs == TopOffset || KillsCompressor[KS].contains(Offs)) { + return KS; + } + + auto Kills = KillsCompressor[KS]; + Kills.insertUnique(Offs); + return KillsCompressor.getOrInsert(std::move(Kills)); + } + + [[nodiscard]] bool isKilledBy(KillSetId KS, int32_t Offs) const { + if (Offs == TopOffset || KS == KillSetId::Empty) { + return false; + } + if (!KillsCompressor.inbounds(KS)) [[unlikely]] { + return false; + } + + return KillsCompressor[KS].contains(Offs); + } + + [[nodiscard]] const auto &kills(KillSetId KS) const { + return KillsCompressor[KS]; + } + + void reserve(size_t ExpectedCapacity) { + NodeCompressor.reserve(ExpectedCapacity); + Depth.reserve(ExpectedCapacity); + } + private: Compressor NodeCompressor{}; TypedVector Depth{}; + Compressor, KillSetId> KillsCompressor{}; }; /// A single CFL Field-Access String consisting of: gep, loads, kills, and /// stores struct AccessPath { - static constexpr int32_t TopOffset = INT32_MIN; + static constexpr int32_t TopOffset = FieldStringManager::TopOffset; FieldStringNodeId Loads{}; FieldStringNodeId Stores{}; - llvm::SmallDenseSet Kills{}; + KillSetId Kills{}; // Add an offset for pending GEPs; INT32_MIN is Top - int32_t Offset = {0}; - int32_t EmptyTombstone = 0; + int32_t Offset{}; [[nodiscard]] bool empty() const noexcept { return Loads == FieldStringNodeId::None && - Stores == FieldStringNodeId::None && Kills.empty() && Offset == 0; - } - - [[nodiscard]] bool kills(int32_t Off) const { - return Off != TopOffset && Kills.contains(Off); + Stores == FieldStringNodeId::None && Kills == KillSetId::Empty && + Offset == 0; } [[nodiscard]] constexpr bool - operator==(const AccessPath &Other) const noexcept { - return EmptyTombstone == Other.EmptyTombstone && Loads == Other.Loads && - Stores == Other.Stores && Kills == Other.Kills; - } + operator==(const AccessPath &Other) const noexcept = default; - bool operator!=(const AccessPath &Other) const noexcept { - return !(*this == Other); + friend constexpr size_t hash_value(const AccessPath &FieldString) noexcept { + size_t HC = 37; + HC = HC * 31 + size_t(FieldString.Loads); + HC = HC * 31 + size_t(FieldString.Stores); + HC = HC * 31 + size_t(FieldString.Kills); + HC = HC * 31 + size_t(FieldString.Offset); + return HC; } - friend size_t hash_value(const AccessPath &FieldString) noexcept; - friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const AccessPath &FieldString); @@ -174,32 +208,27 @@ struct AccessPath { struct AccessPathDMI { static AccessPath getEmptyKey() { AccessPath Ret{}; - Ret.EmptyTombstone = 1; + Ret.Loads = FieldStringNodeId(UINT32_MAX); return Ret; } static AccessPath getTombstoneKey() { AccessPath Ret{}; - Ret.EmptyTombstone = 2; + Ret.Loads = FieldStringNodeId(UINT32_MAX); + Ret.Stores = FieldStringNodeId(UINT32_MAX); return Ret; } - static auto getHashValue(const AccessPath &FieldString) noexcept { + static auto getHashValue(AccessPath FieldString) noexcept { return hash_value(FieldString); } - static bool isEqual(const AccessPath &L, const AccessPath &R) noexcept { - if (L.EmptyTombstone != R.EmptyTombstone) { - return false; - } - if (L.EmptyTombstone) { - return true; - } - return L == R; - } + static bool isEqual(AccessPath L, AccessPath R) noexcept { return L == R; } }; /// An edge-value consisting of a set if CFL field access strings. struct IFDSEdgeValue { - [[clang::require_explicit_initialization]] FieldStringManager *Mgr{}; - llvm::SmallDenseSet Paths; + using container_type = llvm::SmallDenseSet; + + PSR_REQUIRE_EXPLICIT_INITIALIZATION FieldStringManager *Mgr{}; + container_type Paths; static constexpr llvm::StringLiteral LogCategory = "IFDSEdgeValue"; @@ -214,7 +243,7 @@ struct IFDSEdgeValue { return !(*this == Other); } - [[nodiscard]] friend auto hash_value(const IFDSEdgeValue EV) { + [[nodiscard]] friend auto hash_value(const IFDSEdgeValue &EV) { return llvm::hash_combine_range(EV.Paths.begin(), EV.Paths.end()); } @@ -260,14 +289,6 @@ struct IFDSProblemConfig : LLVMIFDSAnalysisDomainDefault { // XXX: more }; -/// Transforms user-defined seeds from usual IFDS seeds to field-sensitive IFDS -/// seeds -[[nodiscard]] InitialSeeds -makeInitialSeeds(const InitialSeeds &UserSeeds, - FieldStringManager &Mgr); - /// Utility to strip off potential pointer-arithmetic from V and accumulating /// the byte-offset. [[nodiscard]] inline std::pair @@ -363,32 +384,173 @@ bool filterFieldSensFacts( return true; } +struct CFLFieldSensEdgeFunctionImpl { + using l_t = LatticeDomain; + PSR_REQUIRE_EXPLICIT_INITIALIZATION IFDSEdgeValue Transform; + PSR_REQUIRE_EXPLICIT_INITIALIZATION uint8_t DepthKLimit{}; + + bool operator==(const CFLFieldSensEdgeFunctionImpl &Other) const noexcept { + assert(DepthKLimit == Other.DepthKLimit); + return Transform == Other.Transform; + } + + friend auto hash_value(const CFLFieldSensEdgeFunctionImpl &EF) noexcept { + return hash_value(EF.Transform); + } + + [[nodiscard]] static auto from(IFDSEdgeValue &&Txn, uint8_t DepthKLimit) { + return CFLFieldSensEdgeFunctionImpl{ + .Transform = std::move(Txn), + .DepthKLimit = DepthKLimit, + }; + } + + [[nodiscard]] static auto from(AccessPath Txn, FieldStringManager &Mgr, + uint8_t DepthKLimit) { + return CFLFieldSensEdgeFunctionImpl{ + .Transform = {.Mgr = &Mgr, .Paths = {Txn}}, + .DepthKLimit = DepthKLimit, + }; + } + + [[nodiscard]] static auto fromEpsilon(uint8_t DepthKLimit, + FieldStringManager &Mgr) { + return CFLFieldSensEdgeFunctionImpl{ + .Transform = IFDSEdgeValue::epsilon(&Mgr), + .DepthKLimit = DepthKLimit, + }; + } +}; + +struct CFLFieldSensEdgeFunction { + using l_t = LatticeDomain; + PSR_REQUIRE_EXPLICIT_INITIALIZATION const CFLFieldSensEdgeFunctionImpl + *Impl{}; + + [[nodiscard]] l_t computeTarget(l_t Source) const { + assert(Impl != nullptr); + Source.onValue(fn<&IFDSEdgeValue::applyTransforms>, Impl->Transform, + Impl->DepthKLimit); + return Source; + } + + constexpr friend bool + operator==(CFLFieldSensEdgeFunction L, + CFLFieldSensEdgeFunction R) noexcept = default; + + friend auto hash_value(CFLFieldSensEdgeFunction EF) noexcept { + return llvm::hash_value(EF.Impl); + } + + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + CFLFieldSensEdgeFunction EF); +}; + +class CFLFieldSensEdgeFunctions + : public IfdsIdeProblemMixin { +public: + static constexpr llvm::StringLiteral LogCategory = "CFLFieldSensIFDSProblem"; + + EdgeFunction getStoreEdgeFunction(d_t CurrNode, d_t SuccNode, + d_t PointerOp, d_t ValueOp, + uint8_t DepthKLimit, + const llvm::DataLayout &DL); + + EdgeFunction getLoadEdgeFunction(d_t CurrNode, d_t PointerOp, + uint8_t DepthKLimit, + const llvm::DataLayout &DL); + + EdgeFunction getNormalEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, + d_t SuccNode); + + EdgeFunction getCallEdgeFunction(n_t CallSite, d_t SrcNode, + f_t DestinationFunction, d_t DestNode); + + EdgeFunction getReturnEdgeFunction(n_t CallSite, f_t CalleeFunction, + n_t ExitStmt, d_t ExitNode, + n_t RetSite, d_t RetNode); + + EdgeFunction getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, + n_t RetSite, d_t RetSiteNode, + llvm::ArrayRef Callees); + + EdgeFunction getSummaryEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, + d_t SuccNode); + + EdgeFunction extend(const EdgeFunction &L, + const EdgeFunction &R); + + EdgeFunction combine(const EdgeFunction &L, + const EdgeFunction &R); + +protected: + template + CFLFieldSensEdgeFunctions(ProblemTy &Problem, + cfl_fieldsens::IFDSProblemConfig &&Config, + uint8_t DepthKLimit = 5) + // entry-points not forwarded; getEntryPoints() overridden in + // CFLFieldSensIFDSProblem + : psr::IfdsIdeProblemMixin(Problem.getProjectIRDB(), {}, + Problem.getZeroValue()), + Config(std::move(Config)), DepthKLimit(DepthKLimit) { + Mgr.reserve(Problem.getProjectIRDB()->getNumInstructions()); + } + + /// Transforms user-defined seeds from usual IFDS seeds to field-sensitive + /// IFDS seeds + [[nodiscard]] InitialSeeds + makeInitialSeeds(const InitialSeeds &UserSeeds); + +private: + using EFConstPtr = const cfl_fieldsens::CFLFieldSensEdgeFunctionImpl *; + using EFResultPtr = llvm::PointerIntPair; + + [[nodiscard]] EdgeFunction + makeEF(cfl_fieldsens::CFLFieldSensEdgeFunctionImpl &&EF); + [[nodiscard]] EFResultPtr + makeEFPtr(cfl_fieldsens::CFLFieldSensEdgeFunctionImpl &&EF); + + cfl_fieldsens::FieldStringManager Mgr{}; + cfl_fieldsens::IFDSProblemConfig Config{}; + + UnorderedSet EFInternCache{}; + + llvm::DenseMap, EFResultPtr> ExtendCache{}; + llvm::DenseMap, EFResultPtr> CombineCache{}; + + uint8_t DepthKLimit = 5; // Original from the paper +}; + } // namespace cfl_fieldsens /// An IFDS-Problem adaptor that makes any field-insensitive IFDS analysis -/// field-sensitive. Just wrap your IFDS problem with -/// FieldSensAllocSitesAwareIFDSProblem and use the IterativeIDESolver instead -/// of the IFDSSolver. +/// field-sensitive. Just wrap your IFDS problem with CFLFieldSensIFDSProblem +/// and use the IterativeIDESolver instead of the IFDSSolver. /// /// The only thing to change in your usual IFDS problem is not to kill data-flow /// facts when only parts of the fields should be killed. This is now handled by -/// the FieldSensAllocSitesAwareIFDSProblem. For that, provide a -/// FieldSensAllocSitesAwareIFDSProblemConfig with a proper KillsAt +/// the CFLFieldSensIFDSProblem. For that, provide a +/// problem definition with a proper killsAt() /// implementation. +template + requires std::same_as class CFLFieldSensIFDSProblem - : public IDETabulationProblem { - using Base = IDETabulationProblem; + : public cfl_fieldsens::CFLFieldSensEdgeFunctions { + using Base = cfl_fieldsens::CFLFieldSensEdgeFunctions; static decltype(cfl_fieldsens::IFDSProblemConfig::KillsAt) - deriveKillsAt(auto *UserProblem) { + deriveKillsAt(ProblemTy *UserProblem) { assert(UserProblem != nullptr); - if constexpr (requires() { + if constexpr (requires { { UserProblem->killsAt() } -> psr::invocable_r, n_t, d_t>; }) { return UserProblem->killsAt(); - } else if constexpr (requires() { + } else if constexpr (requires { { UserProblem->killsAt() } -> std::invocable; @@ -415,26 +577,20 @@ class CFLFieldSensIFDSProblem using typename Base::t_t; using typename Base::v_t; - static constexpr llvm::StringLiteral LogCategory = "CFLFieldSensIFDSProblem"; - /// Constructs an IDETabulationProblem with the usual arguments, forwarded /// from UserProblem explicit CFLFieldSensIFDSProblem( - IFDSTabulationProblem *UserProblem, + ProblemTy *UserProblem, cfl_fieldsens::IFDSProblemConfig Config) noexcept(std::is_nothrow_move_constructible_v) - : Base(assertNotNull(UserProblem).getProjectIRDB(), - assertNotNull(UserProblem).getEntryPoints(), - UserProblem->getZeroValue()), - UserProblem(UserProblem), Config(std::move(Config)) {} + : Base(assertNotNull(UserProblem), std::move(Config)), + UserProblem(UserProblem) {} /// Constructs an IDETabulationProblem with the usual arguments, forwarded /// from UserProblem and tries to automatically derive the config from /// additional functions specified by UserProblem - explicit CFLFieldSensIFDSProblem( - proper_subclass_of< - IFDSTabulationProblem> auto - *UserProblem) + + explicit CFLFieldSensIFDSProblem(ProblemTy *UserProblem) : CFLFieldSensIFDSProblem(UserProblem, cfl_fieldsens::IFDSProblemConfig{ .KillsAt = deriveKillsAt(UserProblem), @@ -445,82 +601,49 @@ class CFLFieldSensIFDSProblem CFLFieldSensIFDSProblem(std::nullptr_t) = delete; + [[nodiscard]] decltype(auto) getEntryPoints() const { + return UserProblem->getEntryPoints(); + } + // XXX: Perhaps we need a way to provide a customization-point to specify gen // offsets to the edge-functions (generating from zero currently always // generates at epsilon!) - [[nodiscard]] InitialSeeds initialSeeds() override { - return cfl_fieldsens::makeInitialSeeds(UserProblem->initialSeeds(), Mgr); + [[nodiscard]] InitialSeeds initialSeeds() { + return this->makeInitialSeeds(UserProblem->initialSeeds()); } - [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, - n_t Succ) override { + [[nodiscard]] decltype(auto) getNormalFlowFunction(n_t Curr, n_t Succ) { return UserProblem->getNormalFlowFunction(Curr, Succ); } - [[nodiscard]] FlowFunctionPtrType - getCallFlowFunction(n_t CallInst, f_t CalleeFun) override { + [[nodiscard]] decltype(auto) getCallFlowFunction(n_t CallInst, + f_t CalleeFun) { return UserProblem->getCallFlowFunction(CallInst, CalleeFun); } - [[nodiscard]] FlowFunctionPtrType - getSummaryFlowFunction(n_t CallInst, f_t CalleeFun) override { + [[nodiscard]] decltype(auto) getSummaryFlowFunction(n_t CallInst, + f_t CalleeFun) { return UserProblem->getSummaryFlowFunction(CallInst, CalleeFun); } - [[nodiscard]] FlowFunctionPtrType getRetFlowFunction(n_t CallSite, - f_t CalleeFun, - n_t ExitInst, - n_t RetSite) override { + [[nodiscard]] decltype(auto) getRetFlowFunction(n_t CallSite, f_t CalleeFun, + n_t ExitInst, n_t RetSite) { return UserProblem->getRetFlowFunction(CallSite, CalleeFun, ExitInst, RetSite); } - [[nodiscard]] FlowFunctionPtrType + [[nodiscard]] decltype(auto) getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override { + llvm::ArrayRef Callees) { return UserProblem->getCallToRetFlowFunction(CallSite, RetSite, Callees); } - EdgeFunction getStoreEdgeFunction(d_t CurrNode, d_t SuccNode, - d_t PointerOp, d_t ValueOp, - uint8_t DepthKLimit, - const llvm::DataLayout &DL); - - EdgeFunction getNormalEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, - d_t SuccNode) override; - - EdgeFunction getCallEdgeFunction(n_t CallSite, d_t SrcNode, - f_t DestinationFunction, - d_t DestNode) override; - - EdgeFunction getReturnEdgeFunction(n_t CallSite, f_t CalleeFunction, - n_t ExitStmt, d_t ExitNode, - n_t RetSite, d_t RetNode) override; - - EdgeFunction - getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, n_t RetSite, - d_t RetSiteNode, - llvm::ArrayRef Callees) override; - - EdgeFunction getSummaryEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, - d_t SuccNode) override; - - EdgeFunction extend(const EdgeFunction &L, - const EdgeFunction &R) override; - - EdgeFunction combine(const EdgeFunction &L, - const EdgeFunction &R) override; - /// The wrapped user-problem [[nodiscard]] const auto &base() const noexcept { return *UserProblem; } private: - IFDSTabulationProblem *UserProblem{}; - cfl_fieldsens::FieldStringManager Mgr{}; - cfl_fieldsens::IFDSProblemConfig Config{}; - - uint8_t DepthKLimit = 5; // Original from the paper + NonNullPtr UserProblem; }; } // namespace psr diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAliasAwareIDEProblem.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAliasAwareIDEProblem.h index 800b996042..3c18f02a88 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAliasAwareIDEProblem.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAliasAwareIDEProblem.h @@ -10,9 +10,8 @@ #ifndef PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_IDEALIASINFOTABULATIONPROBLEM_H #define PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_IDEALIASINFOTABULATIONPROBLEM_H -#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" -#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultNoAliasIDEProblem.h" +#include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include @@ -62,14 +61,14 @@ class IDEAliasAwareDefaultFlowFunctionsImpl template class DefaultAliasAwareIDEProblem - : public IDETabulationProblem, + : public IfdsIdeProblemMixin, protected detail::IDEAliasAwareDefaultFlowFunctionsImpl { public: - using typename IDETabulationProblem::db_t; - using typename IDETabulationProblem::n_t; - using typename IDETabulationProblem::f_t; - using typename IDETabulationProblem::d_t; - using typename IDETabulationProblem::FlowFunctionPtrType; + using typename IfdsIdeProblemMixin::db_t; + using typename IfdsIdeProblemMixin::n_t; + using typename IfdsIdeProblemMixin::f_t; + using typename IfdsIdeProblemMixin::d_t; + using typename IfdsIdeProblemMixin::FlowFunctionPtrType; using detail::IDEAliasAwareDefaultFlowFunctionsImpl::getAliasInfo; @@ -79,85 +78,37 @@ class DefaultAliasAwareIDEProblem /// \note It is useful to use an instance of FilteredAliasSet for the alias /// information to lower suprious aliases explicit DefaultAliasAwareIDEProblem( - const db_t *IRDB, LLVMAliasIteratorRef AS, - std::vector EntryPoints, - std::optional - ZeroValue) noexcept(std::is_nothrow_move_constructible_v) - : IDETabulationProblem(IRDB, std::move(EntryPoints), - ZeroValue), - detail::IDEAliasAwareDefaultFlowFunctionsImpl(AS) {} - - [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, - n_t Succ) override { - return getNormalFlowFunctionImpl(Curr, Succ); - } - - [[nodiscard]] FlowFunctionPtrType - getCallFlowFunction(n_t CallInst, f_t CalleeFun) override { - return getCallFlowFunctionImpl(CallInst, CalleeFun); - } - - [[nodiscard]] FlowFunctionPtrType getRetFlowFunction(n_t CallSite, - f_t CalleeFun, - n_t ExitInst, - n_t RetSite) override { - return getRetFlowFunctionImpl(CallSite, CalleeFun, ExitInst, RetSite); - } - - [[nodiscard]] FlowFunctionPtrType - getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override { - return getCallToRetFlowFunctionImpl(CallSite, RetSite, Callees); - } -}; - -class DefaultAliasAwareIFDSProblem - : public IFDSTabulationProblem, - protected detail::IDEAliasAwareDefaultFlowFunctionsImpl { -public: - using typename IFDSTabulationProblem::d_t; - using typename IFDSTabulationProblem::f_t; - using typename IFDSTabulationProblem::FlowFunctionPtrType; - using typename IFDSTabulationProblem::n_t; - - /// Constructs an IFDSTabulationProblem with the usual arguments + alias - /// information. - /// - /// \note It is useful to use an instance of FilteredAliasSet for the alias - /// information to lower suprious aliases - explicit DefaultAliasAwareIFDSProblem( const db_t *IRDB, LLVMAliasIteratorRef AS, std::vector EntryPoints, d_t ZeroValue) noexcept(std::is_nothrow_move_constructible_v) - : IFDSTabulationProblem(IRDB, std::move(EntryPoints), ZeroValue), + : IfdsIdeProblemMixin(IRDB, std::move(EntryPoints), + std::move(ZeroValue)), detail::IDEAliasAwareDefaultFlowFunctionsImpl(AS) {} - using detail::IDEAliasAwareDefaultFlowFunctionsImpl::getAliasInfo; - - [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, - n_t Succ) override { + [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) { return getNormalFlowFunctionImpl(Curr, Succ); } - [[nodiscard]] FlowFunctionPtrType - getCallFlowFunction(n_t CallInst, f_t CalleeFun) override { + [[nodiscard]] FlowFunctionPtrType getCallFlowFunction(n_t CallInst, + f_t CalleeFun) { return getCallFlowFunctionImpl(CallInst, CalleeFun); } - [[nodiscard]] FlowFunctionPtrType getRetFlowFunction(n_t CallSite, - f_t CalleeFun, - n_t ExitInst, - n_t RetSite) override { + [[nodiscard]] FlowFunctionPtrType + getRetFlowFunction(n_t CallSite, f_t CalleeFun, n_t ExitInst, n_t RetSite) { return getRetFlowFunctionImpl(CallSite, CalleeFun, ExitInst, RetSite); } [[nodiscard]] FlowFunctionPtrType getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override { + llvm::ArrayRef Callees) { return getCallToRetFlowFunctionImpl(CallSite, RetSite, Callees); } }; +using DefaultAliasAwareIFDSProblem = + DefaultAliasAwareIDEProblem; + } // namespace psr #endif diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAllocSitesAwareIDEProblem.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAllocSitesAwareIDEProblem.h index bd0e9bb04e..c9c77fc8e0 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAllocSitesAwareIDEProblem.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultAllocSitesAwareIDEProblem.h @@ -11,6 +11,7 @@ #define PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_DEFAULTALLOCSITESAWAREIDEPROBLEM_H #include "phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultNoAliasIDEProblem.h" +#include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMBasePointerAliasSet.h" @@ -68,7 +69,7 @@ class IDEAllocSitesAwareDefaultFlowFunctionsImpl template class DefaultAllocSitesAwareIDEProblem - : public IDETabulationProblem, + : public IfdsIdeProblemMixin, protected detail::IDEAllocSitesAwareDefaultFlowFunctionsImpl { public: using ProblemAnalysisDomain = AnalysisDomainTy; @@ -81,8 +82,6 @@ class DefaultAllocSitesAwareIDEProblem using i_t = typename AnalysisDomainTy::i_t; using db_t = typename AnalysisDomainTy::db_t; - using ConfigurationTy = HasNoConfigurationType; - using FlowFunctionType = FlowFunction; using FlowFunctionPtrType = typename FlowFunctionType::FlowFunctionPtrType; @@ -94,86 +93,41 @@ class DefaultAllocSitesAwareIDEProblem /// \note It is useful to use an instance of FilteredAliasSet for the alias /// information to lower suprious aliases explicit DefaultAllocSitesAwareIDEProblem( - const ProjectIRDBBase *IRDB, LLVMAliasInfoRef AS, - std::vector EntryPoints, - std::optional - ZeroValue) noexcept(std::is_nothrow_move_constructible_v) - : IDETabulationProblem(IRDB, std::move(EntryPoints), - std::move(ZeroValue)), - detail::IDEAllocSitesAwareDefaultFlowFunctionsImpl(AS) {} - - void disableStrongUpdateStore() noexcept { - this->EnableStrongUpdateStore = false; - } - - [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, - n_t Succ) override { - return getNormalFlowFunctionImpl(Curr, Succ); - } - - [[nodiscard]] FlowFunctionPtrType - getCallFlowFunction(n_t CallInst, f_t CalleeFun) override { - return getCallFlowFunctionImpl(CallInst, CalleeFun); - } - - [[nodiscard]] FlowFunctionPtrType getRetFlowFunction(n_t CallSite, - f_t CalleeFun, - n_t ExitInst, - n_t RetSite) override { - return getRetFlowFunctionImpl(CallSite, CalleeFun, ExitInst, RetSite); - } - - [[nodiscard]] FlowFunctionPtrType - getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override { - return getCallToRetFlowFunctionImpl(CallSite, RetSite, Callees); - } -}; - -class DefaultAllocSitesAwareIFDSProblem - : public IFDSTabulationProblem, - protected detail::IDEAllocSitesAwareDefaultFlowFunctionsImpl { -public: - /// Constructs an IFDSTabulationProblem with the usual arguments + alias - /// information. - /// - /// \note It is useful to use an instance of FilteredAliasSet for the alias - /// information to lower suprious aliases - explicit DefaultAllocSitesAwareIFDSProblem( const db_t *IRDB, LLVMAliasInfoRef AS, std::vector EntryPoints, d_t ZeroValue) noexcept(std::is_nothrow_move_constructible_v) - : IFDSTabulationProblem(IRDB, std::move(EntryPoints), ZeroValue), + : IfdsIdeProblemMixin(IRDB, std::move(EntryPoints), + std::move(ZeroValue)), detail::IDEAllocSitesAwareDefaultFlowFunctionsImpl(AS) {} void disableStrongUpdateStore() noexcept { this->EnableStrongUpdateStore = false; } - [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, - n_t Succ) override { + [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) { return getNormalFlowFunctionImpl(Curr, Succ); } - [[nodiscard]] FlowFunctionPtrType - getCallFlowFunction(n_t CallInst, f_t CalleeFun) override { + [[nodiscard]] FlowFunctionPtrType getCallFlowFunction(n_t CallInst, + f_t CalleeFun) { return getCallFlowFunctionImpl(CallInst, CalleeFun); } - [[nodiscard]] FlowFunctionPtrType getRetFlowFunction(n_t CallSite, - f_t CalleeFun, - n_t ExitInst, - n_t RetSite) override { + [[nodiscard]] FlowFunctionPtrType + getRetFlowFunction(n_t CallSite, f_t CalleeFun, n_t ExitInst, n_t RetSite) { return getRetFlowFunctionImpl(CallSite, CalleeFun, ExitInst, RetSite); } [[nodiscard]] FlowFunctionPtrType getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override { + llvm::ArrayRef Callees) { return getCallToRetFlowFunctionImpl(CallSite, RetSite, Callees); } }; +using DefaultAllocSitesAwareIFDSProblem = + DefaultAllocSitesAwareIDEProblem; + } // namespace psr #endif // PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_DEFAULTALLOCSITESAWAREIDEPROBLEM_H diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultNoAliasIDEProblem.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultNoAliasIDEProblem.h index 63dcfbba78..7388e96b47 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultNoAliasIDEProblem.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultNoAliasIDEProblem.h @@ -11,10 +11,7 @@ #define PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_IDENOALIASINFOTABULATIONPROBLEM_H #include "phasar/DataFlow/IfdsIde/FlowFunctions.h" -#include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" -#include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" -#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" -#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/DataFlow/IfdsIde/IfdsIdeProblemMixin.h" #include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" namespace llvm { @@ -56,75 +53,40 @@ class IDENoAliasDefaultFlowFunctionsImpl { template class DefaultNoAliasIDEProblem - : public IDETabulationProblem, + : public IfdsIdeProblemMixin, protected detail::IDENoAliasDefaultFlowFunctionsImpl { public: - using IDETabulationProblem::IDETabulationProblem; + using IfdsIdeProblemMixin::IfdsIdeProblemMixin; - using typename IDETabulationProblem::f_t; - using typename IDETabulationProblem::FlowFunctionPtrType; - using typename IDETabulationProblem::n_t; + using typename IfdsIdeProblemMixin::f_t; + using typename IfdsIdeProblemMixin::FlowFunctionPtrType; + using typename IfdsIdeProblemMixin::n_t; + using typename IfdsIdeProblemMixin::d_t; - [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, - n_t Succ) override { + [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) { return getNormalFlowFunctionImpl(Curr, Succ); } - [[nodiscard]] FlowFunctionPtrType - getCallFlowFunction(n_t CallInst, f_t CalleeFun) override { + [[nodiscard]] FlowFunctionPtrType getCallFlowFunction(n_t CallInst, + f_t CalleeFun) { return getCallFlowFunctionImpl(CallInst, CalleeFun); } - [[nodiscard]] FlowFunctionPtrType getRetFlowFunction(n_t CallSite, - f_t CalleeFun, - n_t ExitInst, - n_t RetSite) override { - return getRetFlowFunctionImpl(CallSite, CalleeFun, ExitInst, RetSite); - } - [[nodiscard]] FlowFunctionPtrType - getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override { - return getCallToRetFlowFunctionImpl(CallSite, RetSite, Callees); - } -}; - -class DefaultNoAliasIFDSProblem - : public IFDSTabulationProblem, - protected detail::IDENoAliasDefaultFlowFunctionsImpl { -public: - using IFDSTabulationProblem::IFDSTabulationProblem; - - using typename IFDSTabulationProblem::d_t; - using typename IFDSTabulationProblem::f_t; - using typename IFDSTabulationProblem::FlowFunctionPtrType; - using typename IFDSTabulationProblem::l_t; - using typename IFDSTabulationProblem::n_t; - - [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, - n_t Succ) override { - return getNormalFlowFunctionImpl(Curr, Succ); - } - - [[nodiscard]] FlowFunctionPtrType - getCallFlowFunction(n_t CallInst, f_t CalleeFun) override { - return getCallFlowFunctionImpl(CallInst, CalleeFun); - } - - [[nodiscard]] FlowFunctionPtrType getRetFlowFunction(n_t CallSite, - f_t CalleeFun, - n_t ExitInst, - n_t RetSite) override { + getRetFlowFunction(n_t CallSite, f_t CalleeFun, n_t ExitInst, n_t RetSite) { return getRetFlowFunctionImpl(CallSite, CalleeFun, ExitInst, RetSite); } [[nodiscard]] FlowFunctionPtrType getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override { + llvm::ArrayRef Callees) { return getCallToRetFlowFunctionImpl(CallSite, RetSite, Callees); } }; +using DefaultNoAliasIFDSProblem = + DefaultNoAliasIDEProblem; + } // namespace psr #endif diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultReachableAllocationSitesIDEProblem.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultReachableAllocationSitesIDEProblem.h index 5101f6d728..db776694c2 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultReachableAllocationSitesIDEProblem.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultReachableAllocationSitesIDEProblem.h @@ -13,7 +13,7 @@ #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/PhasarLLVM/DataFlow/IfdsIde/DefaultNoAliasIDEProblem.h" -#include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" +#include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" #include "phasar/PhasarLLVM/Pointer/LLVMPointsToInfo.h" // Forward declaration of types for which we only use its pointer or ref type @@ -64,14 +64,14 @@ class IDEReachableAllocationSitesDefaultFlowFunctionsImpl template class DefaultReachableAllocationSitesIDEProblem - : public IDETabulationProblem, + : public IfdsIdeProblemMixin, protected detail::IDEReachableAllocationSitesDefaultFlowFunctionsImpl { public: - using typename IDETabulationProblem::db_t; - using typename IDETabulationProblem::d_t; - using typename IDETabulationProblem::f_t; - using typename IDETabulationProblem::n_t; - using typename IDETabulationProblem::FlowFunctionPtrType; + using typename IfdsIdeProblemMixin::db_t; + using typename IfdsIdeProblemMixin::d_t; + using typename IfdsIdeProblemMixin::f_t; + using typename IfdsIdeProblemMixin::n_t; + using typename IfdsIdeProblemMixin::FlowFunctionPtrType; /// Constructs an IDETabulationProblem with the usual arguments + alias /// information. @@ -79,84 +79,37 @@ class DefaultReachableAllocationSitesIDEProblem /// \note It is useful to use an instance of FilteredAliasSet for the alias /// information to lower suprious aliases explicit DefaultReachableAllocationSitesIDEProblem( - const db_t *IRDB, LLVMPointsToIteratorRef AS, - std::vector EntryPoints, - std::optional - ZeroValue) noexcept(std::is_nothrow_move_constructible_v) - : IDETabulationProblem(IRDB, std::move(EntryPoints), - ZeroValue), - detail::IDEReachableAllocationSitesDefaultFlowFunctionsImpl(AS) {} - - [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, - n_t Succ) override { - return getNormalFlowFunctionImpl(Curr, Succ); - } - - [[nodiscard]] FlowFunctionPtrType - getCallFlowFunction(n_t CallInst, f_t CalleeFun) override { - return getCallFlowFunctionImpl(CallInst, CalleeFun); - } - - [[nodiscard]] FlowFunctionPtrType getRetFlowFunction(n_t CallSite, - f_t CalleeFun, - n_t ExitInst, - n_t RetSite) override { - return getRetFlowFunctionImpl(CallSite, CalleeFun, ExitInst, RetSite); - } - - [[nodiscard]] FlowFunctionPtrType - getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override { - return getCallToRetFlowFunctionImpl(CallSite, RetSite, Callees); - } -}; - -class DefaultReachableAllocationSitesIFDSProblem - : public IFDSTabulationProblem, - protected detail::IDEReachableAllocationSitesDefaultFlowFunctionsImpl { -public: - using typename IFDSTabulationProblem::d_t; - using typename IFDSTabulationProblem::db_t; - using typename IFDSTabulationProblem::f_t; - using typename IFDSTabulationProblem::FlowFunctionPtrType; - using typename IFDSTabulationProblem::n_t; - - /// Constructs an IFDSTabulationProblem with the usual arguments + alias - /// information. - /// - /// \note It is useful to use an instance of FilteredAliasSet for the alias - /// information to lower suprious aliases - explicit DefaultReachableAllocationSitesIFDSProblem( const db_t *IRDB, LLVMPointsToIteratorRef AS, std::vector EntryPoints, d_t ZeroValue) noexcept(std::is_nothrow_move_constructible_v) - : IFDSTabulationProblem(IRDB, std::move(EntryPoints), ZeroValue), + : IfdsIdeProblemMixin(IRDB, std::move(EntryPoints), + std::move(ZeroValue)), detail::IDEReachableAllocationSitesDefaultFlowFunctionsImpl(AS) {} - [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, - n_t Succ) override { + [[nodiscard]] FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) { return getNormalFlowFunctionImpl(Curr, Succ); } - [[nodiscard]] FlowFunctionPtrType - getCallFlowFunction(n_t CallInst, f_t CalleeFun) override { + [[nodiscard]] FlowFunctionPtrType getCallFlowFunction(n_t CallInst, + f_t CalleeFun) { return getCallFlowFunctionImpl(CallInst, CalleeFun); } - [[nodiscard]] FlowFunctionPtrType getRetFlowFunction(n_t CallSite, - f_t CalleeFun, - n_t ExitInst, - n_t RetSite) override { + [[nodiscard]] FlowFunctionPtrType + getRetFlowFunction(n_t CallSite, f_t CalleeFun, n_t ExitInst, n_t RetSite) { return getRetFlowFunctionImpl(CallSite, CalleeFun, ExitInst, RetSite); } [[nodiscard]] FlowFunctionPtrType getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override { + llvm::ArrayRef Callees) { return getCallToRetFlowFunctionImpl(CallSite, RetSite, Callees); } }; +using DefaultReachableAllocationSitesIFDSProblem = + DefaultReachableAllocationSitesIDEProblem; + } // namespace psr #endif diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMFlowFunctions.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMFlowFunctions.h index 9e9257fad6..ef7beb2816 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMFlowFunctions.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMFlowFunctions.h @@ -99,7 +99,7 @@ auto mapFactsAlongsideCallSite(const llvm::CallBase *CallSite, [[no_unique_address]] std::decay_t FactConstructor; }; - return std::make_shared(CallSite, PropagateGlobals, + return std::make_unique(CallSite, PropagateGlobals, std::forward(PropagateArgs), std::forward(FactConstructor)); } @@ -205,7 +205,7 @@ mapFactsToCallee(const llvm::CallBase *CallSite, const llvm::Function *DestFun, [[no_unique_address]] std::decay_t FactConstructor; }; - return std::make_shared(CallSite, DestFun, PropagateGlobals, + return std::make_unique(CallSite, DestFun, PropagateGlobals, PropagateZeroToCallee, std::forward(PropagateArgumentWithSource), std::forward(FactConstructor)); @@ -315,7 +315,7 @@ FlowFunctionPtrType mapFactsToCaller( [[no_unique_address]] std::decay_t PostProcess; }; - return std::make_shared( + return std::make_unique( CallSite, ExitInst, PropagateGlobals, std::forward(PropagateParameter), std::forward(PropagateRet), std::forward(FactConstructor), @@ -364,7 +364,7 @@ strongUpdateStore(const llvm::StoreInst *Store, Fn &&GeneratePointerOpIf) { [[no_unique_address]] std::decay_t Pred; }; - return std::make_shared( + return std::make_unique( Store->getPointerOperand(), BasePtrOp, std::forward(GeneratePointerOpIf)); } @@ -389,7 +389,7 @@ strongUpdateStore(const llvm::StoreInst *Store, Fn &&GeneratePointerOpIf) { [[no_unique_address]] std::decay_t Pred; }; - return std::make_shared( + return std::make_unique( Store, std::forward(GeneratePointerOpIf)); } @@ -439,7 +439,7 @@ strongUpdateStore(const llvm::StoreInst *Store) { const llvm::Value *BasePtrOp; }; - return std::make_shared(Store, BasePtrOp); + return std::make_unique(Store, BasePtrOp); } struct StrongUpdateFlow @@ -460,7 +460,7 @@ strongUpdateStore(const llvm::StoreInst *Store) { const llvm::StoreInst *Store; }; - return std::make_shared(Store); + return std::make_unique(Store); } } // namespace psr diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMSolverResults.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMSolverResults.h index 6a9c60e45d..26f58b36b4 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMSolverResults.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMSolverResults.h @@ -10,6 +10,7 @@ #ifndef PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_LLVMSOLVERRESULTS_H #define PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_LLVMSOLVERRESULTS_H +#include "phasar/DataFlow/IfdsIde/Solver/IdBasedSolverResults.h" #include "phasar/DataFlow/IfdsIde/SolverResults.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" #include "phasar/Utils/JoinLattice.h" @@ -28,93 +29,20 @@ namespace psr::detail { /// does not work for backwards analyses. /// The right way would be to ask the ICFG, but we don't have a reference to it /// here yet (TODO!) -template -auto SolverResultsBase::resultsAtInLLVMSSA( - ByConstRef Stmt, bool AllowOverapproximation, bool StripZero) const - -> std::unordered_map - requires same_as_decay, llvm::Instruction> -{ - std::unordered_map Result = [this, Stmt, AllowOverapproximation]() { - if (Stmt->getType()->isVoidTy()) { - return self().Results.row(Stmt); - } - if (!Stmt->getNextNode()) { - auto GetStartRow = [this](const llvm::BasicBlock *BB) -> decltype(auto) { - const auto *First = &BB->front(); - if (llvm::isa(First)) { - First = First->getNextNonDebugInstruction(); - } - return self().Results.row(First); - }; - - // We have reached the end of a BasicBlock. If there is a successor BB - // that only has one predecessor, we are lucky and can just take results - // from there - for (const llvm::BasicBlock *Succ : llvm::successors(Stmt)) { - if (Succ->hasNPredecessors(1)) { - return GetStartRow(Succ); - } - } - - if (!AllowOverapproximation) { - llvm::report_fatal_error("[resultsAtInLLVMSSA]: Cannot precisely " - "collect the results at instruction " + - llvm::Twine(llvmIRToString(Stmt))); - } - - // There is no successor with only one predecessor. - // All we can do is merge the results from all successors to get a sound - // overapproximation. This is not optimal and may be replaced in the - // future. - PHASAR_LOG_LEVEL(WARNING, "[resultsAtInLLVMSSA]: Cannot precisely " - "collect the results at instruction " - << llvmIRToString(Stmt) - << ". Use a sound, but potentially " - "imprecise overapproximation"); - std::unordered_map Ret; - for (const llvm::BasicBlock *Succ : llvm::successors(Stmt)) { - const auto &Row = GetStartRow(Succ); - for (const auto &[Fact, Value] : Row) { - auto [It, Inserted] = Ret.try_emplace(Fact, Value); - if (!Inserted && Value != It->second) { - if constexpr (HasJoinLatticeTraits) { - It->second = JoinLatticeTraits::join(It->second, Value); - } else { - // We have no way of correctly merging, so set the value to the - // default constructed l_t hoping it marks BOTTOM. - It->second = l_t(); - } - } - } - } - return Ret; - } - assert(Stmt->getNextNode() && "Expected to find a valid successor node!"); - return self().Results.row(Stmt->getNextNode()); - }(); - if (StripZero) { - Result.erase(self().ZV); - } - return Result; -} - -template - -auto SolverResultsBase::resultAtInLLVMSSA( - ByConstRef Stmt, d_t Value, bool AllowOverapproximation) const -> l_t - requires same_as_decay, llvm::Instruction> -{ +[[nodiscard]] auto resultsAtInLLVMSSAImpl(const auto &Self, const auto &Stmt, + bool AllowOverapproximation) { if (Stmt->getType()->isVoidTy()) { - return self().Results.get(Stmt, Value); + return Self.row(Stmt); } if (!Stmt->getNextNode()) { - auto GetStartVal = [this, - &Value](const llvm::BasicBlock *BB) -> decltype(auto) { + auto GetStartRow = [&Self](const llvm::BasicBlock *BB) -> decltype(auto) { const auto *First = &BB->front(); +#if LLVM_VERSION_MAJOR <= 18 if (llvm::isa(First)) { First = First->getNextNonDebugInstruction(); } - return self().Results.get(First, Value); +#endif + return Self.row(First); }; // We have reached the end of a BasicBlock. If there is a successor BB @@ -122,7 +50,7 @@ auto SolverResultsBase::resultAtInLLVMSSA( // from there for (const llvm::BasicBlock *Succ : llvm::successors(Stmt)) { if (Succ->hasNPredecessors(1)) { - return GetStartVal(Succ); + return GetStartRow(Succ); } } @@ -136,28 +64,24 @@ auto SolverResultsBase::resultAtInLLVMSSA( // All we can do is merge the results from all successors to get a sound // overapproximation. This is not optimal and may be replaced in the // future. - PHASAR_LOG_LEVEL(WARNING, "[resultAtInLLVMSSA]: Cannot precisely " + PHASAR_LOG_LEVEL(WARNING, "[resultsAtInLLVMSSA]: Cannot precisely " "collect the results at instruction " - << *Stmt + << llvmIRToString(Stmt) << ". Use a sound, but potentially " "imprecise overapproximation"); - auto It = llvm::succ_begin(Stmt); - auto End = llvm::succ_end(Stmt); - l_t Ret{}; - if (It != End) { - Ret = GetStartVal(*It); - for (++It; It != End; ++It) { - const auto &Val = GetStartVal(*It); - if constexpr (HasJoinLatticeTraits) { - Ret = JoinLatticeTraits::join(Ret, Val); - if (Ret == JoinLatticeTraits::bottom()) { - break; - } - } else { - if (Ret != Val) { + std::remove_cvref_t Ret; + using l_t = typename decltype(Ret)::mapped_type; + for (const llvm::BasicBlock *Succ : llvm::successors(Stmt)) { + const auto &Row = GetStartRow(Succ); + for (const auto &[Fact, Value] : Row) { + auto [It, Inserted] = Ret.try_emplace(Fact, Value); + if (!Inserted && Value != It->second) { + if constexpr (HasJoinLatticeTraits) { + It->second = JoinLatticeTraits::join(It->second, Value); + } else { // We have no way of correctly merging, so set the value to the // default constructed l_t hoping it marks BOTTOM. - return {}; + It->second = l_t(); } } } @@ -165,7 +89,121 @@ auto SolverResultsBase::resultAtInLLVMSSA( return Ret; } assert(Stmt->getNextNode() && "Expected to find a valid successor node!"); - return self().Results.get(Stmt->getNextNode(), Value); + return Self.row(Stmt->getNextNode()); +} + +[[nodiscard]] auto resultAtInLLVMSSAImpl(const auto &Self, const auto &Stmt, + const auto &Fact, + bool AllowOverapproximation) { + if (Stmt->getType()->isVoidTy()) { + return Self.resultAt(Stmt, Fact); + } + + if (auto Next = Stmt->getNextNode()) { + return Self.resultAt(Next, Fact); + } + + auto GetStartVal = [&Self, + &Fact](const llvm::BasicBlock *BB) -> decltype(auto) { + const auto *First = &BB->front(); +#if LLVM_VERSION_MAJOR <= 18 + if (llvm::isa(First)) { + First = First->getNextNonDebugInstruction(); + } +#endif + return Self.resultAt(First, Fact); + }; + + // We have reached the end of a BasicBlock. If there is a successor BB + // that only has one predecessor, we are lucky and can just take results + // from there + for (const llvm::BasicBlock *Succ : llvm::successors(Stmt)) { + if (Succ->hasNPredecessors(1)) { + return GetStartVal(Succ); + } + } + + if (!AllowOverapproximation) { + llvm::report_fatal_error("[resultsAtInLLVMSSA]: Cannot precisely " + "collect the results at instruction " + + llvm::Twine(llvmIRToString(Stmt))); + } + + // There is no successor with only one predecessor. + // All we can do is merge the results from all successors to get a sound + // overapproximation. This is not optimal and may be replaced in the + // future. + PHASAR_LOG_LEVEL(WARNING, "[resultAtInLLVMSSA]: Cannot precisely " + "collect the results at instruction " + << *Stmt + << ". Use a sound, but potentially " + "imprecise overapproximation"); + auto It = llvm::succ_begin(Stmt); + auto End = llvm::succ_end(Stmt); + + using l_t = std::remove_cvref_t; + + l_t Ret{}; + if (It != End) { + Ret = GetStartVal(*It); + for (++It; It != End; ++It) { + const auto &Val = GetStartVal(*It); + if constexpr (HasJoinLatticeTraits) { + Ret = JoinLatticeTraits::join(Ret, Val); + if (Ret == JoinLatticeTraits::bottom()) { + break; + } + } else { + if (Ret != Val) { + // We have no way of correctly merging, so set the value to the + // default constructed l_t hoping it marks BOTTOM. + return l_t{}; + } + } + } + } + return Ret; +} + +template +auto SolverResultsBase::resultsAtInLLVMSSA( + ByConstRef Stmt, bool AllowOverapproximation, bool StripZero) const + -> std::unordered_map + requires same_as_decay, llvm::Instruction> +{ + std::unordered_map Result = + resultsAtInLLVMSSAImpl(self(), Stmt, AllowOverapproximation); + if (StripZero) { + Result.erase(self().ZV); + } + return Result; +} + +template +auto SolverResultsBase::resultAtInLLVMSSA( + ByConstRef Stmt, d_t Value, bool AllowOverapproximation) const -> l_t + requires same_as_decay, llvm::Instruction> +{ + return resultAtInLLVMSSAImpl(self(), Stmt, Value, AllowOverapproximation); +} + +template +auto IdBasedSolverResultsBase::resultsAtInLLVMSSA( + ByConstRef Stmt, bool AllowOverapproximation) const + -> std::unordered_map + requires same_as_decay, llvm::Instruction> +{ + return resultsAtInLLVMSSAImpl(static_cast(*this), Stmt, + AllowOverapproximation); +} + +template +auto IdBasedSolverResultsBase::resultAtInLLVMSSA( + ByConstRef Stmt, d_t Value, bool AllowOverapproximation) const -> l_t + requires same_as_decay, llvm::Instruction> +{ + return resultAtInLLVMSSAImpl(static_cast(*this), Stmt, Value, + AllowOverapproximation); } } // namespace psr::detail diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/Helpers.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/Helpers.h index 862af3e477..ebe958cb73 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/Helpers.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/Helpers.h @@ -37,10 +37,10 @@ using LeakMap_t = std::unordered_map>; /// Have an own function for creating a flow/edge-function instance to allow -/// fast migration to memory-management schemes other than std::shared_ptr +/// fast migration to memory-management schemes other than std::unique_ptr template -inline std::shared_ptr makeFF(Args &&...Arguments) { - return std::make_shared(std::forward(Arguments)...); +inline std::unique_ptr makeFF(Args &&...Arguments) { + return std::make_unique(std::forward(Arguments)...); } } // namespace psr::XTaint diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/XTaintAnalysisBase.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/XTaintAnalysisBase.h index eda4bd6e4e..0d72760b8a 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/XTaintAnalysisBase.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/XTaintAnalysisBase.h @@ -10,6 +10,9 @@ #ifndef PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_PROBLEMS_EXTENDEDTAINTANALYSIS_XTAINTANALYSISBASE_H #define PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_PROBLEMS_EXTENDEDTAINTANALYSIS_XTAINTANALYSISBASE_H +#include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/AbstractMemoryLocation.h" +#include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/ExtendedTaintAnalysis/AbstractMemoryLocationFactory.h" + #include "llvm/ADT/SmallPtrSet.h" namespace llvm { @@ -26,8 +29,9 @@ namespace psr::XTaint { class AnalysisBase { protected: const LLVMTaintConfig *TSF; + AbstractMemoryLocationFactory FactFactory; - explicit AnalysisBase(const LLVMTaintConfig *TSF) noexcept; + explicit AnalysisBase(const LLVMTaintConfig *TSF, size_t NumInstructions); using SourceConfigTy = llvm::SmallPtrSet; using SinkConfigTy = llvm::SmallPtrSet; @@ -51,6 +55,10 @@ class AnalysisBase { [[nodiscard]] SanitizerConfigTy getSanitizerConfigAt(const llvm::Instruction *Inst, const llvm::Function *Callee = nullptr) const; + + [[nodiscard]] AbstractMemoryLocation createZeroValue() const { + return FactFactory.getOrCreateZero(); + } }; } // namespace psr::XTaint diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.h index f91866af7f..3f1349ceb1 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.h @@ -10,8 +10,9 @@ #ifndef PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_PROBLEMS_IDEEXTENDEDTAINTANALYSIS_H #define PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_PROBLEMS_IDEEXTENDEDTAINTANALYSIS_H -#include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" -#include "phasar/Domain/LatticeDomain.h" +#include "phasar/DataFlow/IfdsIde/IFDSIDESolverConfig.h" +#include "phasar/DataFlow/IfdsIde/IfdsIdeProblemMixin.h" +#include "phasar/DataFlow/IfdsIde/InitialSeeds.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMZeroValue.h" @@ -24,21 +25,17 @@ #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h" #include "phasar/PhasarLLVM/Utils/BasicBlockOrdering.h" +#include "phasar/Utils/WithAnalysisPrinterMixin.h" -#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallBitVector.h" #include "llvm/IR/GlobalVariable.h" -#include "llvm/IR/InstIterator.h" -#include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Value.h" #include "llvm/Support/Casting.h" #include #include -#include #include #include -#include namespace psr { @@ -48,26 +45,35 @@ struct IDEExtendedTaintAnalysisDomain : public LLVMAnalysisDomainDefault { /// sanitizer on the current path, Bottom means sanitized on all paths. using l_t = XTaint::EdgeDomain; }; + +template <> +struct DefaultAnalysisPrinterSelector + : type_identity< + DefaultLLVMAnalysisPrinter> {}; + namespace XTaint { /// \brief An IDE-based taint analysis that uses k-limited field-access paths to /// achieve field sensitivity class IDEExtendedTaintAnalysis - : public IDETabulationProblem, - public AnalysisBase { - using base_t = IDETabulationProblem; + : public AnalysisBase, + public IfdsIdeProblemMixin, + public WithAnalysisPrinterMixin { + using base_t = IfdsIdeProblemMixin; public: - using typename IDETabulationProblem::n_t; - using typename IDETabulationProblem::f_t; - using typename IDETabulationProblem::d_t; - using typename IDETabulationProblem::l_t; - using typename IDETabulationProblem< - IDEExtendedTaintAnalysisDomain>::FlowFunctionPtrType; + using typename base_t::d_t; + using typename base_t::f_t; + using typename base_t::FlowFunctionPtrType; + using typename base_t::l_t; + using typename base_t::n_t; using EdgeFunctionType = EdgeFunction; using config_callback_t = LLVMTaintConfig::TaintDescriptionCallBackTy; + // Need SemiRing::identity; below static identity() conflicts + using base_t::identity; + private: struct SourceSinkInfo { llvm::SmallBitVector SourceIndices, SinkIndices; @@ -93,12 +99,12 @@ class IDEExtendedTaintAnalysis const llvm::Instruction *CurrInst, bool AddGlobals = true); - [[nodiscard]] static inline bool equivalent(d_t LHS, d_t RHS) { + [[nodiscard]] static bool equivalent(d_t LHS, d_t RHS) { return LHS->equivalent(RHS); } - [[nodiscard]] static inline bool equivalentExceptPointerArithmetics(d_t LHS, - d_t RHS) { + [[nodiscard]] static bool equivalentExceptPointerArithmetics(d_t LHS, + d_t RHS) { return LHS->equivalentExceptPointerArithmetics(RHS); } @@ -181,105 +187,73 @@ class IDEExtendedTaintAnalysis std::vector EntryPoints, unsigned Bound, bool DisableStrongUpdates, GetDomTree &&GDT = DefaultDominatorTreeAnalysis{}) - : base_t(IRDB, std::move(EntryPoints), std::nullopt), AnalysisBase(TSF), - PT(PT), ICF(ICF), BBO(std::forward(GDT)), - FactFactory(IRDB->getNumInstructions()), + : AnalysisBase(TSF, IRDB->getNumInstructions()), + base_t(IRDB, std::move(EntryPoints), createZeroValue()), PT(PT), + ICF(ICF), BBO(std::forward(GDT)), DL(IRDB->getModule()->getDataLayout()), Bound(Bound), PostProcessed(DisableStrongUpdates), DisableStrongUpdates(DisableStrongUpdates) { assert(PT); assert(ICF != nullptr); - initializeZeroValue(createZeroValue()); FactFactory.setDataLayout(DL); - this->getIFDSIDESolverConfig().setAutoAddZero(false); + getIFDSIDESolverConfig().setAutoAddZero(false); /// TODO: Once we have better AliasInfo, do a dynamic_cast over PT and /// set HasPreciseAliasInfo accordingly } - ~IDEExtendedTaintAnalysis() override = default; - // Flow functions - FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) override; + FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ); - FlowFunctionPtrType getCallFlowFunction(n_t CallStmt, f_t DestFun) override; + FlowFunctionPtrType getCallFlowFunction(n_t CallStmt, f_t DestFun); FlowFunctionPtrType getRetFlowFunction(n_t CallSite, f_t CalleeFun, - n_t ExitStmt, n_t RetSite) override; + n_t ExitStmt, n_t RetSite); - FlowFunctionPtrType - getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override; + FlowFunctionPtrType getCallToRetFlowFunction(n_t CallSite, n_t RetSite, + llvm::ArrayRef Callees); - FlowFunctionPtrType getSummaryFlowFunction(n_t CallStmt, - f_t DestFun) override; + FlowFunctionPtrType getSummaryFlowFunction(n_t CallStmt, f_t DestFun); // Edge functions EdgeFunctionType getNormalEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, - d_t SuccNode) override; + d_t SuccNode); EdgeFunctionType getCallEdgeFunction(n_t CallInst, d_t SrcNode, f_t CalleeFun, - d_t DestNode) override; + d_t DestNode); EdgeFunctionType getReturnEdgeFunction(n_t CallSite, f_t CalleeFun, n_t ExitInst, d_t ExitNode, - n_t RetSite, d_t RetNode) override; + n_t RetSite, d_t RetNode); - EdgeFunctionType - getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, n_t RetSite, - d_t RetSiteNode, - llvm::ArrayRef Callees) override; + EdgeFunctionType getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, + n_t RetSite, d_t RetSiteNode, + llvm::ArrayRef Callees); EdgeFunctionType getSummaryEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, - d_t SuccNode) override; + d_t SuccNode); // Misc - InitialSeeds initialSeeds() override; + InitialSeeds initialSeeds(); - [[nodiscard]] d_t createZeroValue() const; - - [[nodiscard]] bool isZeroValue(d_t Fact) const noexcept override; + [[nodiscard]] bool isZeroValue(d_t Fact) const noexcept; // Printing functions void emitTextReport(GenericSolverResults SR, - llvm::raw_ostream &OS = llvm::outs()) override; - -private: - LLVMAliasInfoRef PT{}; - const LLVMBasedICFG *ICF{}; - - /// Save all leaks here that were found using the IFDS part if the analysis. - /// Hence, this map may contain sanitized facts. - XTaint::LeakMap_t Leaks; + llvm::raw_ostream &OS = llvm::outs()); - // Used for determining whether a dataflow fact is still tained or already - // sanitized - BasicBlockOrdering BBO; + // --- - AbstractMemoryLocationFactory FactFactory; - const llvm::DataLayout &DL; - -#ifdef XTAINT_DIAGNOSTICS - llvm::DenseSet allTaintedValues; -#endif - - /// The k-limit for field-access paths - unsigned Bound; - - /// Does the Leaks map still contain sanitized facts? - bool PostProcessed = false; - - bool DisableStrongUpdates = false; - - bool HasPreciseAliasInfo = false; + [[nodiscard]] IFDSIDESolverConfig &getIFDSIDESolverConfig() noexcept { + return SolverConfig; + } -public: BasicBlockOrdering &getBasicBlockOrdering() { return BBO; } /// Return a map from llvm::Instruction to sets of leaks (llvm::Values) that @@ -299,7 +273,7 @@ class IDEExtendedTaintAnalysis /// This function does NOT involve a post-processing step. LeakMap_t &getAllLeaks() { return Leaks; } - [[nodiscard]] inline size_t getNumDataflowFacts() const { + [[nodiscard]] size_t getNumDataflowFacts() const { return FactFactory.size(); } #ifdef XTAINT_DIAGNOSTICS @@ -309,6 +283,36 @@ class IDEExtendedTaintAnalysis return FactFactory.getNumOverApproximatedFacts(); } #endif + +private: + LLVMAliasInfoRef PT{}; + const LLVMBasedICFG *ICF{}; + + /// Save all leaks here that were found using the IFDS part if the analysis. + /// Hence, this map may contain sanitized facts. + XTaint::LeakMap_t Leaks; + + // Used for determining whether a dataflow fact is still tained or already + // sanitized + BasicBlockOrdering BBO; + + const llvm::DataLayout &DL; + +#ifdef XTAINT_DIAGNOSTICS + llvm::DenseSet allTaintedValues; +#endif + + IFDSIDESolverConfig SolverConfig{}; + + /// The k-limit for field-access paths + unsigned Bound; + + /// Does the Leaks map still contain sanitized facts? + bool PostProcessed = false; + + bool DisableStrongUpdates = false; + + bool HasPreciseAliasInfo = false; }; } // namespace XTaint diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEFeatureTaintAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEFeatureTaintAnalysis.h index f1e28fb426..b56095e03d 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEFeatureTaintAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEFeatureTaintAnalysis.h @@ -13,6 +13,7 @@ #include "phasar/DataFlow/IfdsIde/DefaultEdgeFunctionSingletonCache.h" #include "phasar/DataFlow/IfdsIde/EdgeFunction.h" #include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/IfdsIdeProblemMixin.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" @@ -79,22 +80,18 @@ struct IDEFeatureTaintEdgeFact { /// Checks whether this set contains no facts. /// Note: Top also counts as empty - [[nodiscard]] inline bool empty() const noexcept { - return isTop() || Taints.none(); - } + [[nodiscard]] bool empty() const noexcept { return isTop() || Taints.none(); } /// Returns the number of facts in this set. /// Note: Top counts as empty - [[nodiscard]] inline size_t size() const noexcept { + [[nodiscard]] size_t size() const noexcept { if (isTop()) { return 0; } return Taints.count(); } - [[nodiscard]] inline bool isBottom() const noexcept { return Taints.empty(); } - [[nodiscard]] inline bool isTop() const noexcept { - return Taints.isInvalid(); - } + [[nodiscard]] bool isBottom() const noexcept { return Taints.empty(); } + [[nodiscard]] bool isTop() const noexcept { return Taints.isInvalid(); } [[nodiscard]] friend llvm::hash_code hash_value(const IDEFeatureTaintEdgeFact &BV) noexcept { @@ -170,16 +167,16 @@ struct IDEFeatureTaintEdgeFact { [[nodiscard]] std::string LToString(const IDEFeatureTaintEdgeFact &EdgeFact); template <> struct JoinLatticeTraits { - inline static IDEFeatureTaintEdgeFact top() { + static IDEFeatureTaintEdgeFact top() { IDEFeatureTaintEdgeFact Ret{}; return Ret; } - inline static IDEFeatureTaintEdgeFact bottom() { + static IDEFeatureTaintEdgeFact bottom() { // TODO return 0; } - inline static IDEFeatureTaintEdgeFact join(const IDEFeatureTaintEdgeFact &L, - const IDEFeatureTaintEdgeFact &R) { + static IDEFeatureTaintEdgeFact join(const IDEFeatureTaintEdgeFact &L, + const IDEFeatureTaintEdgeFact &R) { if (L.isTop()) { return R; } @@ -273,7 +270,7 @@ class FeatureTaintGenerator { }; class IDEFeatureTaintAnalysis - : public IDETabulationProblem { + : public IfdsIdeProblemMixin { public: IDEFeatureTaintAnalysis(const LLVMProjectIRDB *IRDB, LLVMAliasInfoRef PT, @@ -306,62 +303,56 @@ class IDEFeatureTaintAnalysis operator=(IDEFeatureTaintAnalysis &&) noexcept = delete; // The EF Caches are incomplete, so move the dtor into the .cpp - ~IDEFeatureTaintAnalysis() override; + ~IDEFeatureTaintAnalysis(); ////////////////////////////////////////////////////////////////////////////// /// Flow Functions ////////////////////////////////////////////////////////////////////////////// - FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) override; + FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ); - FlowFunctionPtrType getCallFlowFunction(n_t CallSite, f_t DestFun) override; + FlowFunctionPtrType getCallFlowFunction(n_t CallSite, f_t DestFun); FlowFunctionPtrType getRetFlowFunction(n_t CallSite, f_t CalleeFun, - n_t ExitInst, n_t RetSite) override; - FlowFunctionPtrType - getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override; - FlowFunctionPtrType getSummaryFlowFunction(n_t CallSite, - f_t DestFun) override; + n_t ExitInst, n_t RetSite); + FlowFunctionPtrType getCallToRetFlowFunction(n_t CallSite, n_t RetSite, + llvm::ArrayRef Callees); + FlowFunctionPtrType getSummaryFlowFunction(n_t CallSite, f_t DestFun); ////////////////////////////////////////////////////////////////////////////// /// Edge Functions ////////////////////////////////////////////////////////////////////////////// EdgeFunction getNormalEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, - d_t SuccNode) override; + d_t SuccNode); EdgeFunction getCallEdgeFunction(n_t CallSite, d_t SrcNode, - f_t DestinationFunction, - d_t DestNode) override; + f_t DestinationFunction, d_t DestNode); EdgeFunction getReturnEdgeFunction(n_t CallSite, f_t CalleeFunction, n_t ExitStmt, d_t ExitNode, - n_t RetSite, d_t RetNode) override; + n_t RetSite, d_t RetNode); - EdgeFunction - getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, n_t RetSite, - d_t RetSiteNode, - llvm::ArrayRef Callees) override; + EdgeFunction getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, + n_t RetSite, d_t RetSiteNode, + llvm::ArrayRef Callees); EdgeFunction getSummaryEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, - d_t SuccNode) override; + d_t SuccNode); ////////////////////////////////////////////////////////////////////////////// /// Misc ////////////////////////////////////////////////////////////////////////////// - InitialSeeds initialSeeds() override; - - bool isZeroValue(d_t FlowFact) const noexcept override; + InitialSeeds initialSeeds(); void emitTextReport(GenericSolverResults SR, - llvm::raw_ostream &OS = llvm::outs()) override; + llvm::raw_ostream &OS = llvm::outs()); EdgeFunction extend(const EdgeFunction &FirstEF, - const EdgeFunction &SecondEF) override; + const EdgeFunction &SecondEF); EdgeFunction combine(const EdgeFunction &FirstEF, - const EdgeFunction &OtherEF) override; + const EdgeFunction &OtherEF); private: FeatureTaintGenerator TaintGen; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/AllBot.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/AllBot.h index fa0984801b..a8ef5cef36 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/AllBot.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/AllBot.h @@ -18,11 +18,11 @@ namespace psr::glca { // struct AllBot { // using type = AllBottom; -// static std::shared_ptr getInstance(); +// static std::unique_ptr getInstance(); // static bool isBot(const EdgeFunction *EdgeFn, // bool NonRec = false); // static bool -// isBot(const std::shared_ptr> &EdgeFn, +// isBot(const std::unique_ptr> &EdgeFn, // bool NonRec = false); // }; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.h index 608933f3a1..9d61a44ea9 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEGeneralizedLCA/IDEGeneralizedLCA.h @@ -53,22 +53,22 @@ class IDEGeneralizedLCA : public IDETabulationProblem { IDEGeneralizedLCA(const LLVMProjectIRDB *IRDB, const LLVMBasedICFG *ICF, std::vector EntryPoints, size_t MaxSetSize); - std::shared_ptr> getNormalFlowFunction(n_t Curr, - n_t Succ) override; + MaybeUniquePtr> getNormalFlowFunction(n_t Curr, + n_t Succ) override; - std::shared_ptr> getCallFlowFunction(n_t CallStmt, - f_t DestMthd) override; + MaybeUniquePtr> getCallFlowFunction(n_t CallStmt, + f_t DestMthd) override; - std::shared_ptr> getRetFlowFunction(n_t CallSite, - f_t CalleeMthd, - n_t ExitStmt, - n_t RetSite) override; + MaybeUniquePtr> getRetFlowFunction(n_t CallSite, + f_t CalleeMthd, + n_t ExitStmt, + n_t RetSite) override; - std::shared_ptr> + MaybeUniquePtr> getCallToRetFlowFunction(n_t CallSite, n_t RetSite, llvm::ArrayRef Callees) override; - std::shared_ptr> + MaybeUniquePtr> getSummaryFlowFunction(n_t CallStmt, f_t DestMthd) override; InitialSeeds initialSeeds() override; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h index 0e14c90f54..8ca5d6955f 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEInstInteractionAnalysis.h @@ -13,8 +13,9 @@ #include "phasar/DataFlow/IfdsIde/DefaultEdgeFunctionSingletonCache.h" #include "phasar/DataFlow/IfdsIde/EdgeFunction.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" -#include "phasar/DataFlow/IfdsIde/FlowFunctions.h" -#include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/IfdsIdeProblemMixin.h" +#include "phasar/DataFlow/IfdsIde/InitialSeeds.h" +#include "phasar/DataFlow/IfdsIde/Solver/GenericSolverResults.h" #include "phasar/DataFlow/IfdsIde/SolverResults.h" #include "phasar/Domain/LatticeDomain.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" @@ -24,7 +25,6 @@ #include "phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMZeroValue.h" #include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" -#include "phasar/PhasarLLVM/Pointer/LLVMPointsToUtils.h" #include "phasar/PhasarLLVM/Utils/LLVMIRToSrc.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" #include "phasar/Utils/BitVectorSet.h" @@ -34,7 +34,6 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Twine.h" -#include "llvm/IR/Argument.h" #include "llvm/IR/Attributes.h" #include "llvm/IR/Constant.h" #include "llvm/IR/Constants.h" @@ -45,15 +44,12 @@ #include "llvm/IR/Instructions.h" #include "llvm/IR/Value.h" #include "llvm/Support/Casting.h" -#include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_os_ostream.h" #include "llvm/Support/raw_ostream.h" #include #include -#include -#include #include #include #include @@ -130,13 +126,12 @@ class IDEIIAFlowFact { /// location than the other one. [[nodiscard]] bool flowFactEqual(const IDEIIAFlowFact &Other) const; - [[nodiscard]] inline const llvm::Value *getBase() const { return BaseVal; } - [[nodiscard]] inline llvm::SmallVector + [[nodiscard]] const llvm::Value *getBase() const { return BaseVal; } + [[nodiscard]] llvm::SmallVector getField() const { return FieldDesc; } - [[nodiscard]] inline constexpr unsigned getKLimit() const { return KLimit; } + [[nodiscard]] constexpr unsigned getKLimit() const { return KLimit; } void print(llvm::raw_ostream &OS, bool IsForDebug = false) const; @@ -146,7 +141,7 @@ class IDEIIAFlowFact { bool operator==(const llvm::Value *V) const; - inline operator const llvm::Value *() { return BaseVal; } + operator const llvm::Value *() const { return BaseVal; } [[nodiscard]] std::string str() const { std::string Ret; @@ -154,20 +149,20 @@ class IDEIIAFlowFact { print(OS); return Ret; } -}; -inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const IDEIIAFlowFact &FlowFact) { - FlowFact.print(OS); - return OS; -} + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const IDEIIAFlowFact &FlowFact) { + FlowFact.print(OS); + return OS; + } -inline std::ostream &operator<<(std::ostream &OS, - const IDEIIAFlowFact &FlowFact) { - llvm::raw_os_ostream Rso(OS); - FlowFact.print(Rso); - return OS; -} + friend std::ostream &operator<<(std::ostream &OS, + const IDEIIAFlowFact &FlowFact) { + llvm::raw_os_ostream Rso(OS); + FlowFact.print(Rso); + return OS; + } +}; } // namespace psr @@ -210,15 +205,15 @@ struct IDEInstInteractionAnalysisDomain : public LLVMAnalysisDomainDefault { template class IDEInstInteractionAnalysisT - : public IDETabulationProblem< + : public IfdsIdeProblemMixin< IDEInstInteractionAnalysisDomain> { - using IDETabulationProblem< + using IfdsIdeProblemMixin< IDEInstInteractionAnalysisDomain>::generateFromZero; public: using AnalysisDomainTy = IDEInstInteractionAnalysisDomain; - using IDETabProblemType = IDETabulationProblem; + using IDETabProblemType = IfdsIdeProblemMixin; using typename IDETabProblemType::container_type; using typename IDETabProblemType::FlowFunctionPtrType; @@ -240,7 +235,7 @@ class IDEInstInteractionAnalysisT const LLVMProjectIRDB *IRDB, const LLVMBasedICFG *ICF, LLVMAliasInfoRef PT, std::vector EntryPoints = {"main"}, std::function EdgeFactGenerator = nullptr) - : IDETabulationProblem( + : IfdsIdeProblemMixin( IRDB, std::move(EntryPoints), createZeroValue()), ICF(ICF), PT(PT), EdgeFactGen(std::move(EdgeFactGenerator)) { assert(ICF != nullptr); @@ -249,19 +244,17 @@ class IDEInstInteractionAnalysisT // IIAAKillOrReplaceEF::initEdgeFunctionCleaner(); } - ~IDEInstInteractionAnalysisT() override = default; - /// Offer a special hook to the user that allows to generate additional /// edge facts on-the-fly. Above the generator function, the ordinary /// edge facts are generated according to the usual edge functions. - inline void registerEdgeFactGenerator( + void registerEdgeFactGenerator( std::function EdgeFactGenerator) { EdgeFactGen = std::move(EdgeFactGenerator); } // start formulating our analysis by specifying the parts required for IFDS - FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t /* Succ */) override { + FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t /* Succ */) { // Generate all local variables // // Flow function: @@ -483,8 +476,7 @@ class IDEInstInteractionAnalysisT }); } - inline FlowFunctionPtrType getCallFlowFunction(n_t CallSite, - f_t DestFun) override { + FlowFunctionPtrType getCallFlowFunction(n_t CallSite, f_t DestFun) { if (this->ICF->isHeapAllocatingFunction(DestFun)) { // Kill add facts and model the effects in getCallToRetFlowFunction(). return this->killAllFlows(); @@ -521,9 +513,8 @@ class IDEInstInteractionAnalysisT return MapFactsToCalleeFF; } - inline FlowFunctionPtrType getRetFlowFunction(n_t CallSite, f_t /*CalleeFun*/, - n_t ExitInst, - n_t /* RetSite */) override { + FlowFunctionPtrType getRetFlowFunction(n_t CallSite, f_t /*CalleeFun*/, + n_t ExitInst, n_t /* RetSite */) { // Map return value back to the caller. If pointer parameters hold at the // end of a callee function generate all of those in the caller context. if (CallSite == nullptr) { @@ -546,9 +537,8 @@ class IDEInstInteractionAnalysisT return MapFactsToCallerFF; } - inline FlowFunctionPtrType - getCallToRetFlowFunction(n_t CallSite, n_t /* RetSite */, - llvm::ArrayRef Callees) override { + FlowFunctionPtrType getCallToRetFlowFunction(n_t CallSite, n_t /* RetSite */, + llvm::ArrayRef Callees) { // Model call to heap allocating functions (new, new[], malloc, etc.) -- // only model direct calls, though. if (Callees.size() == 1) { @@ -619,13 +609,7 @@ class IDEInstInteractionAnalysisT }); } - inline FlowFunctionPtrType - getSummaryFlowFunction(n_t /* CallSite */, f_t /* DestFun */) override { - // Do not use user-crafted summaries. - return nullptr; - } - - inline InitialSeeds initialSeeds() override { + InitialSeeds initialSeeds() { InitialSeeds Seeds; forallStartingPoints(this->EntryPoints, ICF, [this, &Seeds](n_t SP) { @@ -653,20 +637,19 @@ class IDEInstInteractionAnalysisT return Seeds; } - [[nodiscard]] inline d_t createZeroValue() const { + [[nodiscard]] d_t createZeroValue() const { // Create a special value to represent the zero value! return LLVMZeroValue::getInstance(); } - inline bool isZeroValue(d_t d) const noexcept override { - return isZeroValueImpl(d); - } + using IfdsIdeProblemMixin< + IDEInstInteractionAnalysisDomain>::isZeroValue; // In addition provide specifications for the IDE parts. - inline EdgeFunctionType getStrongUpdateStoreEF(const llvm::StoreInst *Store, - d_t CurrNode, d_t SuccNode, - l_t UserEdgeFacts) { + EdgeFunctionType getStrongUpdateStoreEF(const llvm::StoreInst *Store, + d_t CurrNode, d_t SuccNode, + l_t UserEdgeFacts) { // Overriding edge: obtain labels from value to be stored (and may add // UserEdgeFacts, if any). @@ -762,9 +745,8 @@ class IDEInstInteractionAnalysisT "\n> CurrNode: " + CurrNode.str() + "\n> SuccNode: " + SuccNode.str()); } - inline EdgeFunctionType getNormalEdgeFunction(n_t Curr, d_t CurrNode, - n_t /* Succ */, - d_t SuccNode) override { + EdgeFunctionType getNormalEdgeFunction(n_t Curr, d_t CurrNode, n_t /* Succ */, + d_t SuccNode) { PHASAR_LOG_LEVEL(DFADEBUG, "Process edge: " << llvmIRToShortString(CurrNode) << " --" << llvmIRToString(Curr) << "--> " @@ -814,9 +796,9 @@ class IDEInstInteractionAnalysisT return EdgeIdentity{}; } - inline EdgeFunctionType getCallEdgeFunction(n_t CallSite, d_t SrcNode, - f_t /* DestinationMethod */, - d_t DestNode) override { + EdgeFunctionType getCallEdgeFunction(n_t CallSite, d_t SrcNode, + f_t /* DestinationMethod */, + d_t DestNode) { // Handle the case in which a parameter that has been artificially // introduced by the compiler is passed. Such a value must be generated from // the zero value, to reflact the fact that the data flows from the callee @@ -848,9 +830,9 @@ class IDEInstInteractionAnalysisT return EdgeIdentity{}; } - inline EdgeFunctionType - getReturnEdgeFunction(n_t CallSite, f_t /* CalleeMethod */, n_t ExitInst, - d_t ExitNode, n_t /* RetSite */, d_t RetNode) override { + EdgeFunctionType getReturnEdgeFunction(n_t CallSite, f_t /* CalleeMethod */, + n_t ExitInst, d_t ExitNode, + n_t /* RetSite */, d_t RetNode) { // Handle the case in which constant data is returned, e.g. ret i32 42. // // Let c be the return instruction's corresponding call site. @@ -879,10 +861,9 @@ class IDEInstInteractionAnalysisT return EdgeIdentity{}; } - inline EdgeFunctionType - getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, n_t /* RetSite */, - d_t RetSiteNode, - llvm::ArrayRef Callees) override { + EdgeFunctionType getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, + n_t /* RetSite */, d_t RetSiteNode, + llvm::ArrayRef Callees) { // Check if the user has registered a fact generator function l_t UserEdgeFacts = bvSetFrom(invoke_or_default(EdgeFactGen, CallSite)); @@ -900,7 +881,7 @@ class IDEInstInteractionAnalysisT // // 0 // \ - // %i = call H \ \x.x \cup { commit of('%i = call H') } + // %i = call H \ \x.x \cup { commit of('%i = call H') } // v // i // @@ -933,15 +914,10 @@ class IDEInstInteractionAnalysisT return EdgeIdentity{}; } - inline EdgeFunctionType - getSummaryEdgeFunction(n_t /* CallSite */, d_t /* CallNode */, - n_t /* RetSite */, d_t /* RetSiteNode */) override { - // Do not use user-crafted summaries. - return nullptr; + [[nodiscard]] l_t join(const l_t &Lhs, const l_t &Rhs) { + return joinImpl(Lhs, Rhs); } - inline l_t join(l_t Lhs, l_t Rhs) override { return joinImpl(Lhs, Rhs); } - // Provide some handy helper edge functions to improve reuse. // Edge function that kills all labels in a set (and may replaces them with @@ -952,17 +928,6 @@ class IDEInstInteractionAnalysisT l_t computeTarget(ByConstRef /* Src */) const { return Replacement; } - static EdgeFunction - compose(EdgeFunctionRef /*This*/, - const EdgeFunction /*SecondFunction*/) { - llvm::report_fatal_error("Implemented in 'extend'"); - } - - static EdgeFunction join(EdgeFunctionRef /*This*/, - const EdgeFunction & /*OtherFunction*/) { - llvm::report_fatal_error("Implemented in 'combine'"); - } - bool operator==(const IIAAKillOrReplaceEF &Other) const noexcept { return Replacement == Other.Replacement; } @@ -1006,17 +971,6 @@ class IDEInstInteractionAnalysisT return IDEInstInteractionAnalysisT::joinImpl(Src, Data); } - static EdgeFunction - compose(EdgeFunctionRef /*This*/, - const EdgeFunction & /*SecondFunction*/) { - llvm::report_fatal_error("Implemented in 'extend'"); - } - - static EdgeFunction join(EdgeFunctionRef /*This*/, - const EdgeFunction & /*OtherFunction*/) { - llvm::report_fatal_error("Implemented in 'combine'"); - } - bool operator==(const IIAAAddLabelsEF &Other) const noexcept { return Data == Other.Data; } @@ -1050,7 +1004,7 @@ class IDEInstInteractionAnalysisT } EdgeFunction extend(const EdgeFunction &FirstFunction, - const EdgeFunction &SecondFunction) override { + const EdgeFunction &SecondFunction) { if (auto Default = defaultComposeOrNull(FirstFunction, SecondFunction)) { return Default; } @@ -1070,7 +1024,7 @@ class IDEInstInteractionAnalysisT } EdgeFunction combine(const EdgeFunction &FirstFunction, - const EdgeFunction &OtherFunction) override { + const EdgeFunction &OtherFunction) { /// XXX: Here, we underapproximate joins with EdgeIdentity if (llvm::isa>(FirstFunction)) { return OtherFunction; @@ -1108,7 +1062,7 @@ class IDEInstInteractionAnalysisT } void emitTextReport(GenericSolverResults SR, - llvm::raw_ostream &OS = llvm::outs()) override { + llvm::raw_ostream &OS = llvm::outs()) { OS << "\n====================== IDE-Inst-Interaction-Analysis Report " "======================\n"; // if (!IRDB->debugInfoAvailable()) { @@ -1142,7 +1096,7 @@ class IDEInstInteractionAnalysisT /// Computes all variables where a result set has been computed using the /// edge functions (and respective value domain). - inline std::unordered_set + [[nodiscard]] std::unordered_set getAllVariables(GenericSolverResults /* Solution */) const { std::unordered_set Variables; // collect all variables that are available @@ -1167,7 +1121,7 @@ class IDEInstInteractionAnalysisT /// Computes all variables for which an empty set has been computed using the /// edge functions (and respective value domain). - inline std::unordered_set getAllVariablesWithEmptySetValue( + [[nodiscard]] std::unordered_set getAllVariablesWithEmptySetValue( GenericSolverResults Solution) const { return removeVariablesWithoutEmptySetValue(Solution, getAllVariables(Solution)); @@ -1192,7 +1146,7 @@ class IDEInstInteractionAnalysisT } } - static inline l_t joinImpl(ByConstRef Lhs, ByConstRef Rhs) { + static l_t joinImpl(ByConstRef Lhs, ByConstRef Rhs) { if (Lhs.isTop() || Rhs.isBottom()) { return Rhs; } @@ -1207,7 +1161,7 @@ class IDEInstInteractionAnalysisT private: /// Filters out all variables that had a non-empty set during edge functions /// computations. - inline std::unordered_set removeVariablesWithoutEmptySetValue( + std::unordered_set removeVariablesWithoutEmptySetValue( GenericSolverResults Solution, std::unordered_set Variables) const { // Check the solver results and remove all variables for which a diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.h index 7407473e40..0ceb12f52c 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDELinearConstantAnalysis.h @@ -10,7 +10,9 @@ #ifndef PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_PROBLEMS_IDELINEARCONSTANTANALYSIS_H #define PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_PROBLEMS_IDELINEARCONSTANTANALYSIS_H -#include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/IDEProblem.h" +#include "phasar/DataFlow/IfdsIde/IfdsIdeProblemMixin.h" +#include "phasar/DataFlow/IfdsIde/Solver/GenericSolverResults.h" #include "phasar/Domain/LatticeDomain.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" @@ -19,8 +21,6 @@ #include "llvm/Support/raw_ostream.h" #include -#include -#include #include namespace llvm { @@ -39,23 +39,15 @@ struct IDELinearConstantAnalysisDomain : public LLVMAnalysisDomainDefault { // NOLINTNEXTLINE(cppcoreguidelines-special-member-functions) class IDELinearConstantAnalysis - : public IDETabulationProblem { -public: - using IDETabProblemType = - IDETabulationProblem; - using typename IDETabProblemType::d_t; - using typename IDETabProblemType::f_t; - using typename IDETabProblemType::i_t; - using typename IDETabProblemType::l_t; - using typename IDETabProblemType::n_t; - using typename IDETabProblemType::t_t; - using typename IDETabProblemType::v_t; + : public IfdsIdeProblemMixin { + using base_t = IfdsIdeProblemMixin; +public: IDELinearConstantAnalysis(const LLVMProjectIRDB *IRDB, const LLVMBasedICFG *ICF, std::vector EntryPoints = {"main"}); - ~IDELinearConstantAnalysis() override; + ~IDELinearConstantAnalysis(); struct LCAResult { LCAResult() = default; @@ -64,7 +56,7 @@ class IDELinearConstantAnalysis std::map VariableToValue; std::vector IRTrace; void print(llvm::raw_ostream &OS) const; - inline bool operator==(const LCAResult &Rhs) const { + bool operator==(const LCAResult &Rhs) const { return SrcNode == Rhs.SrcNode && VariableToValue == Rhs.VariableToValue && IRTrace == Rhs.IRTrace; } @@ -76,45 +68,38 @@ class IDELinearConstantAnalysis // start formulating our analysis by specifying the parts required for IFDS - FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) override; + FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ); - FlowFunctionPtrType getCallFlowFunction(n_t CallSite, f_t DestFun) override; + FlowFunctionPtrType getCallFlowFunction(n_t CallSite, f_t DestFun); FlowFunctionPtrType getRetFlowFunction(n_t CallSite, f_t CalleeFun, - n_t ExitInst, n_t RetSite) override; - - FlowFunctionPtrType - getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override; - - FlowFunctionPtrType getSummaryFlowFunction(n_t Curr, f_t CalleeFun) override; + n_t ExitInst, n_t RetSite); - [[nodiscard]] InitialSeeds initialSeeds() override; + FlowFunctionPtrType getCallToRetFlowFunction(n_t CallSite, n_t RetSite, + llvm::ArrayRef Callees); - [[nodiscard]] d_t createZeroValue() const; + FlowFunctionPtrType getSummaryFlowFunction(n_t Curr, f_t CalleeFun); - [[nodiscard]] bool isZeroValue(d_t Fact) const noexcept override; + [[nodiscard]] InitialSeeds initialSeeds(); // in addition provide specifications for the IDE parts EdgeFunction getNormalEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, - d_t SuccNode) override; + d_t SuccNode); EdgeFunction getCallEdgeFunction(n_t CallSite, d_t SrcNode, - f_t DestinationFunction, - d_t DestNode) override; + f_t DestinationFunction, d_t DestNode); EdgeFunction getReturnEdgeFunction(n_t CallSite, f_t CalleeFunction, n_t ExitStmt, d_t ExitNode, - n_t RetSite, d_t RetNode) override; + n_t RetSite, d_t RetNode); - EdgeFunction - getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, n_t RetSite, - d_t RetSiteNode, - llvm::ArrayRef Callees) override; + EdgeFunction getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, + n_t RetSite, d_t RetSiteNode, + llvm::ArrayRef Callees); EdgeFunction getSummaryEdgeFunction(n_t Curr, d_t CurrNode, n_t Succ, - d_t SuccNode) override; + d_t SuccNode); // Helper functions @@ -122,7 +107,7 @@ class IDELinearConstantAnalysis getLCAResults(GenericSolverResults SR); void emitTextReport(GenericSolverResults SR, - llvm::raw_ostream &OS = llvm::outs()) override; + llvm::raw_ostream &OS = llvm::outs()); private: const LLVMBasedICFG *ICF{}; diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.h index b01abd38fd..0731d9b10d 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDETypeStateAnalysis.h @@ -13,7 +13,7 @@ #include "phasar/DataFlow/IfdsIde/EdgeFunction.h" #include "phasar/DataFlow/IfdsIde/EdgeFunctionUtils.h" #include "phasar/DataFlow/IfdsIde/FlowFunctions.h" -#include "phasar/DataFlow/IfdsIde/IDETabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/IfdsIdeProblemMixin.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" @@ -24,6 +24,7 @@ #include "phasar/Utils/JoinLattice.h" #include "phasar/Utils/Logger.h" #include "phasar/Utils/Printer.h" +#include "phasar/Utils/WithAnalysisPrinterMixin.h" #include "llvm/ADT/StringRef.h" #include "llvm/Demangle/Demangle.h" @@ -62,9 +63,6 @@ class IDETypeStateAnalysisBase IDETypeStateAnalysisBase & operator=(const IDETypeStateAnalysisBase &) noexcept = delete; -protected: - IDETypeStateAnalysisBase(LLVMAliasInfoRef PT) noexcept : PT(PT) {} - using typename IDETypeStateAnalysisBaseCommon::container_type; using typename IDETypeStateAnalysisBaseCommon::d_t; using typename IDETypeStateAnalysisBaseCommon::f_t; @@ -81,6 +79,9 @@ class IDETypeStateAnalysisBase llvm::ArrayRef Callees); FlowFunctionPtrType getSummaryFlowFunction(n_t CallSite, f_t DestFun); +protected: + IDETypeStateAnalysisBase(LLVMAliasInfoRef PT) noexcept : PT(PT) {} + // --- Utilities [[nodiscard]] virtual bool @@ -148,12 +149,14 @@ struct IDETypeStateAnalysisDomain : public LLVMAnalysisDomainDefault { template class IDETypeStateAnalysis - : public IDETabulationProblem< + : public IfdsIdeProblemMixin< IDETypeStateAnalysisDomain>, - private detail::IDETypeStateAnalysisBase { + public WithAnalysisPrinterMixin< + IDETypeStateAnalysisDomain>, + public detail::IDETypeStateAnalysisBase { public: using IDETabProblemType = - IDETabulationProblem>; + IfdsIdeProblemMixin>; using typename IDETabProblemType::container_type; using typename IDETabProblemType::d_t; using typename IDETabProblemType::f_t; @@ -354,51 +357,20 @@ class IDETypeStateAnalysis // start formulating our analysis by specifying the parts required for IFDS - FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) override { - return detail::IDETypeStateAnalysisBase::getNormalFlowFunction(Curr, Succ); - } - - FlowFunctionPtrType getCallFlowFunction(n_t CallSite, f_t DestFun) override { - return detail::IDETypeStateAnalysisBase::getCallFlowFunction(CallSite, - DestFun); - } - - FlowFunctionPtrType getRetFlowFunction(n_t CallSite, f_t CalleeFun, - n_t ExitStmt, n_t RetSite) override { - - return detail::IDETypeStateAnalysisBase::getRetFlowFunction( - CallSite, CalleeFun, ExitStmt, RetSite); - } - - FlowFunctionPtrType - getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override { - return detail::IDETypeStateAnalysisBase::getCallToRetFlowFunction( - CallSite, RetSite, Callees); - } - - FlowFunctionPtrType getSummaryFlowFunction(n_t CallSite, - f_t DestFun) override { - return detail::IDETypeStateAnalysisBase::getSummaryFlowFunction(CallSite, - DestFun); - } - - InitialSeeds initialSeeds() override { - return this->createDefaultSeeds(); + InitialSeeds initialSeeds() { + return IDETabProblemType::createDefaultSeeds(*this); } [[nodiscard]] d_t createZeroValue() const { return LLVMZeroValue::getInstance(); } - [[nodiscard]] bool isZeroValue(d_t Fact) const noexcept override { - return LLVMZeroValue::isLLVMZeroValue(Fact); - } + using detail::IDETypeStateAnalysisBase::getSummaryFlowFunction; // in addition provide specifications for the IDE parts EdgeFunction getNormalEdgeFunction(n_t Curr, d_t CurrNode, n_t /*Succ*/, - d_t SuccNode) override { + d_t SuccNode) { // Set alloca instructions of target type to uninitialized. if (const auto *Alloca = llvm::dyn_cast(Curr)) { if (hasMatchingType(Alloca)) { @@ -412,22 +384,20 @@ class IDETypeStateAnalysis EdgeFunction getCallEdgeFunction(n_t /*CallSite*/, d_t /*SrcNode*/, f_t /*DestinationFunction*/, - d_t /*DestNode*/) override { + d_t /*DestNode*/) { return EdgeIdentity{}; } EdgeFunction getReturnEdgeFunction(n_t /*CallSite*/, f_t /*CalleeFunction*/, n_t /*ExitInst*/, d_t /*ExitNode*/, - n_t /*RetSite*/, - d_t /*RetNode*/) override { + n_t /*RetSite*/, d_t /*RetNode*/) { return EdgeIdentity{}; } - EdgeFunction - getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, n_t /*RetSite*/, - d_t RetSiteNode, - llvm::ArrayRef Callees) override { + EdgeFunction getCallToRetEdgeFunction(n_t CallSite, d_t CallNode, + n_t /*RetSite*/, d_t RetSiteNode, + llvm::ArrayRef Callees) { const auto *CS = llvm::cast(CallSite); for (const auto *Callee : Callees) { std::string DemangledFname = llvm::demangle(Callee->getName().str()); @@ -436,7 +406,7 @@ class IDETypeStateAnalysis // We apply the same edge function for the return value, i.e. callsite. if (TSD->isFactoryFunction(DemangledFname)) { PHASAR_LOG_LEVEL(DEBUG, "Processing factory function"); - if (isZeroValue(CallNode) && RetSiteNode == CS) { + if (this->isZeroValue(CallNode) && RetSiteNode == CS) { return TSConstant{ TSD->getNextState(DemangledFname, TSD->uninit(), CS), TSD}; } @@ -459,15 +429,9 @@ class IDETypeStateAnalysis return EdgeIdentity{}; } - EdgeFunction getSummaryEdgeFunction(n_t /*CallSite*/, d_t /*CallNode*/, - n_t /*RetSite*/, - d_t /*RetSiteNode*/) override { - return nullptr; - } - - l_t topElement() override { return TSD->top(); } + l_t topElement() { return TSD->top(); } - l_t bottomElement() override { return TSD->bottom(); } + l_t bottomElement() { return TSD->bottom(); } /** * We have a lattice with BOTTOM representing all information @@ -477,7 +441,7 @@ class IDETypeStateAnalysis * * @note Only one-level lattice's are handled currently */ - l_t join(l_t Lhs, l_t Rhs) override { + l_t join(l_t Lhs, l_t Rhs) { if (Lhs == Rhs) { return Lhs; } @@ -490,14 +454,6 @@ class IDETypeStateAnalysis return TSD->bottom(); } - EdgeFunction allTopFunction() override { - if constexpr (HasJoinLatticeTraits) { - return AllTop{}; - } else { - return AllTop{topElement()}; - } - } - [[nodiscard]] bool isAPIFunction(llvm::StringRef Name) const noexcept override { return TSD->isAPIFunction(Name); @@ -514,7 +470,7 @@ class IDETypeStateAnalysis } void emitTextReport(GenericSolverResults SR, - llvm::raw_ostream &OS = llvm::outs()) override { + llvm::raw_ostream &OS = llvm::outs()) { for (const auto &F : this->IRDB->getAllFunctions()) { for (const auto &BB : *F) { @@ -535,7 +491,7 @@ class IDETypeStateAnalysis } } - this->Printer->onFinalize(OS); + this->printer().onFinalize(OS); } [[nodiscard]] bool @@ -561,10 +517,6 @@ IDETypeStateAnalysis(const LLVMProjectIRDB *, LLVMAliasInfoRef, std::vector EntryPoints) -> IDETypeStateAnalysis; -// class CSTDFILEIOTypeStateDescription; - -// extern template class IDETypeStateAnalysis; - } // namespace psr #endif diff --git a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.h index a6025c6254..d563ae06f7 100644 --- a/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.h +++ b/include/phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IFDSTaintAnalysis.h @@ -10,12 +10,14 @@ #ifndef PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_PROBLEMS_IFDSTAINTANALYSIS_H #define PHASAR_PHASARLLVM_DATAFLOW_IFDSIDE_PROBLEMS_IFDSTAINTANALYSIS_H -#include "phasar/DataFlow/IfdsIde/IFDSTabulationProblem.h" +#include "phasar/DataFlow/IfdsIde/IfdsIdeProblemMixin.h" +#include "phasar/DataFlow/IfdsIde/InitialSeeds.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/PhasarLLVM/Utils/LLVMFunctionDataFlowFacts.h" +#include "phasar/Utils/WithAnalysisPrinterMixin.h" #include #include @@ -36,11 +38,12 @@ class LLVMTaintConfig; * dedicated sink functions. A leak is reported once a tainted value * reached a sink function. * - * @see TaintConfiguration on how to specify your own + * @see LLVMTaintConfig on how to specify your own * taint-sensitive source and sink functions. */ class IFDSTaintAnalysis - : public IFDSTabulationProblem { + : public IfdsIdeProblemMixin, + public WithAnalysisPrinterMixin { struct KillsAtFn { const IFDSTaintAnalysis *Self{}; @@ -55,40 +58,30 @@ class IFDSTaintAnalysis /// Holds all leaks found during the analysis std::map> Leaks; - /** - * - * @param icfg - * @param TSF - * @param EntryPoints - */ IFDSTaintAnalysis(const LLVMProjectIRDB *IRDB, LLVMAliasInfoRef PT, const LLVMTaintConfig *Config, std::vector EntryPoints = {"main"}, bool TaintMainArgs = true, bool EnableStrongUpdateStore = true); - FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ) override; + FlowFunctionPtrType getNormalFlowFunction(n_t Curr, n_t Succ); - FlowFunctionPtrType getCallFlowFunction(n_t CallSite, f_t DestFun) override; + FlowFunctionPtrType getCallFlowFunction(n_t CallSite, f_t DestFun); FlowFunctionPtrType getRetFlowFunction(n_t CallSite, f_t CalleeFun, - n_t ExitStmt, n_t RetSite) override; + n_t ExitStmt, n_t RetSite); - FlowFunctionPtrType - getCallToRetFlowFunction(n_t CallSite, n_t RetSite, - llvm::ArrayRef Callees) override; + FlowFunctionPtrType getCallToRetFlowFunction(n_t CallSite, n_t RetSite, + llvm::ArrayRef Callees); - FlowFunctionPtrType getSummaryFlowFunction(n_t CallSite, - f_t DestFun) override; + FlowFunctionPtrType getSummaryFlowFunction(n_t CallSite, f_t DestFun); - InitialSeeds initialSeeds() override; + InitialSeeds initialSeeds(); [[nodiscard]] d_t createZeroValue() const; - bool isZeroValue(d_t FlowFact) const noexcept override; - void emitTextReport(GenericSolverResults SR, - llvm::raw_ostream &OS = llvm::outs()) override; + llvm::raw_ostream &OS = llvm::outs()); [[nodiscard]] bool isInteresting(const llvm::Instruction *Inst) const noexcept; diff --git a/include/phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h b/include/phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h new file mode 100644 index 0000000000..70fa8b49b3 --- /dev/null +++ b/include/phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h @@ -0,0 +1,64 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" +#include "phasar/Utils/HashUtils.h" +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/UsedGlobalsHolder.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/SmallVector.h" + +#include + +namespace llvm { +class Function; +class Value; +class GlobalVariable; +class Instruction; +} // namespace llvm + +namespace psr::monoifds { + +class AliasCache { +public: + static constexpr llvm::StringLiteral LogCategory = "monoifds::AliasCache"; + + // Passed AI should already be FilteredAliasSet or similar + explicit AliasCache( + LLVMAliasIteratorRef AI PSR_LIFETIME_CAPTURE_BY(this), + llvm::function_ref SkipSeedsCallBack + PSR_LIFETIME_CAPTURE_BY(this), + const UsedGlobalsHolder::GlobalSet + *PermittedGlobals PSR_LIFETIME_CAPTURE_BY(this), + std::pmr::memory_resource *MRes PSR_LIFETIME_CAPTURE_BY(this)) + : AI(AI), SkipSeedsCallBack(SkipSeedsCallBack), + PermittedGlobals(&assertNotNull(PermittedGlobals)), + Cache(&assertNotNull(MRes)) {} + + [[nodiscard]] llvm::ArrayRef + getAliasSet(const llvm::Value *Fact, const llvm::Instruction *At); + +private: + // NOTE: Used the node_hash_map from + // [parallel-hash-map](https://github.com/greg7mdp/parallel-hashmap) here + // for the paper-eval! + using node_hash_map = std::pmr::unordered_map< + std::pair, + llvm::SmallVector, PairHash>; + + LLVMAliasIteratorRef AI; + llvm::function_ref SkipSeedsCallBack; + const UsedGlobalsHolder::GlobalSet + *PermittedGlobals{}; + node_hash_map Cache; +}; +} // namespace psr::monoifds diff --git a/include/phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h b/include/phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h new file mode 100644 index 0000000000..a786668d69 --- /dev/null +++ b/include/phasar/PhasarLLVM/DataFlow/MonoIfds/Problems/MonoIFDSTaintAnalysis.h @@ -0,0 +1,132 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/DataFlow/MonoIfds/DataFlowEnvironment.h" +#include "phasar/DataFlow/MonoIfds/MonoIFDSProblem.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" // for concept checking +#include "phasar/PhasarLLVM/DataFlow/IfdsIde/LLVMZeroValue.h" +#include "phasar/PhasarLLVM/DataFlow/MonoIfds/AliasCache.h" +#include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" +#include "phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h" +#include "phasar/PhasarLLVM/Utils/DataFlowAnalysisType.h" +#include "phasar/PhasarLLVM/Utils/LLVMAnalysisPrinter.h" +#include "phasar/Utils/Compressor.h" +#include "phasar/Utils/MaybeUniquePtr.h" +#include "phasar/Utils/NullAnalysisPrinter.h" +#include "phasar/Utils/SCCId.h" +#include "phasar/Utils/UsedGlobalsHolder.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/STLFunctionalExtras.h" + +#include +#include + +namespace psr::monoifds { + +/// Implementation of a generic taint analysis to be solved by the +/// MonoIFDSSolver. Conforms to the MonoIFDSProblem concept. +class TaintAnalysis : public LLVMIFDSAnalysisDomainDefault { +public: + using ProblemAnalysisDomain = LLVMIFDSAnalysisDomainDefault; + + TaintAnalysis( + const LLVMTaintConfig *Config, + const UsedGlobalsHolder *UsedGlobals, + LLVMAliasIteratorRef AI) + : Config(&assertNotNull(Config)), + UsedGlobals(&assertNotNull(UsedGlobals)), AI(AI) { + static_assert(MonoIFDSProblem); + } + + void setAnalysisPrinter( + MaybeUniquePtr> P) { + if (P) { + Printer = std::move(P); + } else { + Printer = NullAnalysisPrinter::getInstance(); + } + } + + struct LocalAnalysis { + TaintAnalysis *TA{}; + AliasCache AC; + SCCId CurrSCC; + + void normalFlow(DataFlowEnvironment &InOut, n_t Curr); + void callToRetFlow(DataFlowEnvironment &InOut, n_t Curr); + [[nodiscard]] llvm::SmallVector returnFlow(n_t CallSite, d_t Fact); + [[nodiscard]] llvm::SmallVector invCallFlow(n_t CallSite, d_t Fact); + [[nodiscard]] std::false_type + summaryFlow(const DataFlowEnvironment & /*In*/, + DataFlowEnvironment & /*Out*/, n_t /*Curr*/, + f_t /*Callee*/) { + // No propagators defined so far + return {}; + } + + [[nodiscard]] d_t getZeroValue() const { + return LLVMZeroValue::getInstance(); + } + + void initialSeeds(DataFlowEnvironment &SeedState, + Compressor &SeedCompressor, f_t Fun); + + void generateFactsAtCall(n_t CS, f_t Callee, + llvm::function_ref GenFact); + void generateFacts(n_t CS, llvm::function_ref GenFact) { + // XXX: Implement (was not necessary for paper eval) + } + void + requestResultCallbackAtCallSite(n_t CS, f_t Callee, + llvm::function_ref LeakFact); + void requestResultCallback(n_t Inst, + llvm::function_ref LeakFact) { + // XXX: Implement (was not necessary for paper eval) + } + void onResult(n_t Inst, d_t Fact) { + TA->Printer->onResult(Inst, Fact, + DataFlowAnalysisType::IFDSTaintAnalysis); + } + }; + + [[nodiscard]] LocalAnalysis localAnalysis(SCCId CurrSCC, + std::pmr::memory_resource *MRes) { + return LocalAnalysis{ + .TA = this, + .AC = AliasCache(AI, + Config->getRegisteredSkipSeedsCallBack() + ? (llvm::function_ref) + Config->getRegisteredSkipSeedsCallBack() + : llvm::function_ref{}, + &UsedGlobals->GlobsPerSCC[CurrSCC], MRes), + .CurrSCC = CurrSCC, + }; + } + + void emitTextReport(llvm::raw_ostream &OS) const { + OS << "\n----- Found the following leaks -----\n"; + Printer->onFinalize(OS); + } + + // Optional API function: Filter out facts that do not need to go into a + // procedure summary + [[nodiscard]] bool shouldBeInSummary(d_t ExitFact, n_t ExitInst); + +private: + MaybeUniquePtr> Printer = + std::make_unique>(); + const LLVMTaintConfig *Config{}; + const UsedGlobalsHolder *UsedGlobals{}; + LLVMAliasIteratorRef AI; +}; +} // namespace psr::monoifds diff --git a/include/phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h b/include/phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h index 45bf95f948..6979fcced9 100644 --- a/include/phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h +++ b/include/phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h @@ -11,6 +11,7 @@ #define PHASAR_PHASARLLVM_DOMAIN_LLVMANALYSISDOMAIN_H #include "phasar/Domain/AnalysisDomain.h" +#include "phasar/Domain/BinaryDomain.h" #include "phasar/PhasarLLVM/Utils/LLVMAnalysisPrinter.h" #include "phasar/Utils/DefaultAnalysisPrinterSelector.h" #include "phasar/Utils/TypeTraits.h" @@ -56,6 +57,8 @@ struct LLVMAnalysisDomainDefault : public AnalysisDomain { using LLVMIFDSAnalysisDomainDefault = WithBinaryValueDomain; +static_assert(std::same_as); + extern template class DefaultLLVMAnalysisPrinter; template <> diff --git a/include/phasar/PhasarLLVM/HelperAnalyses.h b/include/phasar/PhasarLLVM/HelperAnalyses.h index 156e1622bf..8f82b85d8e 100644 --- a/include/phasar/PhasarLLVM/HelperAnalyses.h +++ b/include/phasar/PhasarLLVM/HelperAnalyses.h @@ -16,6 +16,7 @@ #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasSetData.h" #include "phasar/Pointer/UnionFindAliasAnalysisType.h" +#include "phasar/Utils/FunctionId.h" #include "llvm/ADT/Twine.h" @@ -25,6 +26,7 @@ namespace llvm { class Module; +class GlobalVariable; } // namespace llvm namespace psr { @@ -32,7 +34,9 @@ class LLVMProjectIRDB; class DIBasedTypeHierarchy; class LLVMBasedICFG; class LLVMBasedCFG; -class LLVMAliasSet; +template struct SCCHolder; +template struct SCCDependencyGraph; +template struct UsedGlobalsHolder; class HelperAnalyses { // NOLINT(cppcoreguidelines-special-member-functions) public: @@ -59,6 +63,9 @@ class HelperAnalyses { // NOLINT(cppcoreguidelines-special-member-functions) explicit HelperAnalyses(std::unique_ptr IRModule, std::vector EntryPoints, HelperAnalysisConfig Config = {}); + explicit HelperAnalyses(std::unique_ptr IRDB, + std::vector EntryPoints, + HelperAnalysisConfig Config = {}); ~HelperAnalyses() noexcept; [[nodiscard]] LLVMProjectIRDB &getProjectIRDB(); @@ -66,6 +73,16 @@ class HelperAnalyses { // NOLINT(cppcoreguidelines-special-member-functions) [[nodiscard]] DIBasedTypeHierarchy &getTypeHierarchy(); [[nodiscard]] LLVMBasedICFG &getICFG(); [[nodiscard]] LLVMBasedCFG &getCFG(); + [[nodiscard]] FunctionCompressor & + getCompressedFunctions(); + [[nodiscard]] const SCCHolder &getCGSCCs(); + [[nodiscard]] const SCCDependencyGraph &getCGSCCCallers(); + [[nodiscard]] const UsedGlobalsHolder & + getUsedGlobals(); + [[nodiscard]] const std::vector & + getEntryPoints() const noexcept { + return EntryPoints; + } private: std::unique_ptr IRDB; @@ -73,6 +90,10 @@ class HelperAnalyses { // NOLINT(cppcoreguidelines-special-member-functions) std::unique_ptr TH; std::unique_ptr ICF; std::unique_ptr CFG; + std::unique_ptr> FC; + std::unique_ptr> SCCs; + std::unique_ptr> SCCCallers; + std::unique_ptr> UsedGlobals; // IRDB std::string IRFile; diff --git a/include/phasar/PhasarLLVM/Passes/GeneralStatisticsAnalysis.h b/include/phasar/PhasarLLVM/Passes/GeneralStatisticsAnalysis.h index aede3166e2..07e4ab2131 100644 --- a/include/phasar/PhasarLLVM/Passes/GeneralStatisticsAnalysis.h +++ b/include/phasar/PhasarLLVM/Passes/GeneralStatisticsAnalysis.h @@ -75,10 +75,10 @@ struct GeneralStatistics { std::string ModuleName{}; void printAsJson(llvm::raw_ostream &OS = llvm::outs()) const; -}; -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, - const GeneralStatistics &Statistics); + friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const GeneralStatistics &Statistics); +}; /** * This class uses the Module Pass Mechanism of LLVM to compute diff --git a/include/phasar/PhasarLLVM/Pointer.h b/include/phasar/PhasarLLVM/Pointer.h index be473838bc..713572dfb8 100644 --- a/include/phasar/PhasarLLVM/Pointer.h +++ b/include/phasar/PhasarLLVM/Pointer.h @@ -12,10 +12,17 @@ #include "phasar/Config/phasar-config.h" // for PHASAR_USE_SVF #include "phasar/PhasarLLVM/Pointer/AliasAnalysisView.h" +#include "phasar/PhasarLLVM/Pointer/AndersenOTFAA.h" #include "phasar/PhasarLLVM/Pointer/FilteredLLVMAliasSet.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" +#include "phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointsToInfo.h" #include "phasar/PhasarLLVM/Pointer/LLVMPointsToUtils.h" +#include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h" +#include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAliasSet.h" +#include "phasar/PhasarLLVM/Pointer/MemSSAUtils.h" #ifdef PHASAR_USE_SVF #include "phasar/PhasarLLVM/Pointer/SVF/SVFPointsToSet.h" diff --git a/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h b/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h new file mode 100644 index 0000000000..055a0d7b07 --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/AndersenOTFAA.h @@ -0,0 +1,168 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h" +#include "phasar/PhasarLLVM/Pointer/LLVMUnionFindAA.h" +#include "phasar/Pointer/RawAliasSet.h" +#include "phasar/Pointer/UnionFindAA.h" +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/MaybeUniquePtr.h" +#include "phasar/Utils/NonNullPtr.h" +#include "phasar/Utils/Soundness.h" +#include "phasar/Utils/TypedVector.h" +#include "phasar/Utils/ValueCompressor.h" + +#include "llvm/ADT/ArrayRef.h" + +#include +#include +#include + +namespace llvm { +class Function; +} // namespace llvm + +namespace psr { + +class LLVMProjectIRDB; + +/// Opt-in call-string context-sensitivity for \c AndersenOTFSolver. +/// +/// The call-string k-limit is fixed at 1: a selected function gets one set of +/// PAG nodes per call-site that reaches it. Non-selected functions keep a +/// single set of nodes shared by all callers, exactly as before. +struct ContextSensitivityOptions { + enum class Mode : uint8_t { + Off, ///< Root context only; identical to the insensitive solver. + Manual, ///< Only functions matching \c AllowList. + Dynamic, ///< \c AllowList plus functions observed as precision-critical. + All, ///< Every function, until \c MaxContextualNodes is reached. + }; + + Mode SelectionMode = Mode::Off; + /// Function-name globs (\c llvm::GlobPattern). \c DenyList wins over + /// \c AllowList. + std::vector AllowList{}; + std::vector DenyList{}; + /// Hard cap on context-qualified PAG nodes. Once reached, no function is + /// newly selected and no already-selected function gets a further context: + /// sound, just less precise. This is the knob that bounds run time; solve + /// time grows super-linearly in the node count, so raising it is not a + /// proportional trade. + size_t MaxContextualNodes = 20'000; + /// Cap on distinct calling contexts per function; further call sites fall + /// back to the shared root context. A selected function costs one clone of + /// its whole body per context, so without this a single hot function can + /// consume \c MaxContextualNodes on its own. + unsigned MaxContextsPerFunction = 32; + /// \c Mode::Dynamic only: functions with more LLVM instructions than this + /// are never selected. Cloning a large body per context is expensive, and + /// large functions are rarely the point where callers merge. + unsigned MaxContextualFunctionSize = 256; + /// \c Mode::Dynamic only: tighter size limit for the weaker signal where + /// the merged parameters never leave the function body. Off by default: + /// that signal's only payoff is formal-vs-formal aliasing inside the body, + /// which \c buildResult unions back together across contexts anyway. + unsigned MaxLocalMergeFunctionSize = 0; + + [[nodiscard]] constexpr bool isOff() const noexcept { + return SelectionMode == Mode::Off; + } +}; + +/// Alias-analysis result for the Andersen-style OTF points-to analysis. +/// +/// Two values may-alias iff their points-to sets share at least one abstract +/// object. Satisfies \c RawAAResult so it can be wrapped by +/// \c LLVMUnionFindAliasIterator. +struct AndersenOTFResult { + TypedVector> AliasSets; + LLVMBasedCallGraph CG; + + [[nodiscard]] static constexpr bool isCached() noexcept { return true; } + [[nodiscard]] constexpr size_t size() const noexcept { + return AliasSets.size(); + } + + [[nodiscard]] RawAliasSet + getRawAliasSet(ValueId Var) const noexcept { + if (!AliasSets.inbounds(Var)) { + return {}; + } + return AliasSets[Var]; + } + + [[nodiscard]] bool mayAlias(ValueId Var1, ValueId Var2) const noexcept { + if (Var1 == Var2) { + return true; + } + if (!AliasSets.inbounds(Var1)) { + return false; + } + return AliasSets[Var1].contains(Var2); + } +}; + +static_assert(RawAAResult); + +/// Andersen-style inclusion-based points-to analysis that co-refines the call +/// graph and points-to sets in a single fixpoint. +/// +/// Unlike the staged pipeline (resolver → PA), this solver owns its own +/// function-worklist loop: direct calls add callees immediately; indirect +/// calls are resolved as \c pts(fp) grows. +/// +/// Context-sensitivity is opt-in via \c ContextSensitivityOptions and off by +/// default. +class AndersenOTFSolver { +public: + explicit AndersenOTFSolver(const LLVMProjectIRDB &IRDB PSR_LIFETIMEBOUND, + llvm::ArrayRef Entries + PSR_LIFETIMEBOUND, + ValueCompressor &VC PSR_LIFETIMEBOUND, + Soundness S = Soundness::Soundy, + ContextSensitivityOptions CSOpts = {}) noexcept; + + /// Run the full OTF fixpoint and return the alias-analysis result. + [[nodiscard]] AndersenOTFResult solve(); + +private: + struct SolverData; + + NonNullPtr IRDB; + llvm::ArrayRef Entries; + NonNullPtr> VC; + Soundness S; + ContextSensitivityOptions CSOpts; +}; + +// ---- Factory functions ------------------------------------------------ + +/// Runs the Andersen OTF fixpoint and returns the raw alias-analysis result +/// (no LLVM-value wrapping). If \p VC is null, a fresh one is allocated. +[[nodiscard]] AndersenOTFResult +computeAndersenOTFRaw(const LLVMProjectIRDB &IRDB, + llvm::ArrayRef EntryPoints, + MaybeUniquePtr> VC = nullptr, + Soundness S = Soundness::Soundy, + ContextSensitivityOptions CSOpts = {}); + +/// Runs the Andersen OTF fixpoint and returns an \c LLVMUnionFindAliasIterator +/// that implements \c IsLLVMAliasIterator. +[[nodiscard]] LLVMRawAliasIterator +computeAndersenOTF(const LLVMProjectIRDB &IRDB, + llvm::ArrayRef EntryPoints, + MaybeUniquePtr> VC = nullptr, + Soundness S = Soundness::Soundy, + ContextSensitivityOptions CSOpts = {}); + +} // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/CachedLLVMAliasIterator.h b/include/phasar/PhasarLLVM/Pointer/CachedLLVMAliasIterator.h index 130c11f8f8..cd771e0087 100644 --- a/include/phasar/PhasarLLVM/Pointer/CachedLLVMAliasIterator.h +++ b/include/phasar/PhasarLLVM/Pointer/CachedLLVMAliasIterator.h @@ -12,8 +12,10 @@ #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" #include "phasar/Pointer/AliasAnalysisType.h" +#include "phasar/Pointer/AliasInfoBase.h" #include "phasar/Pointer/AliasInfoTraits.h" #include "phasar/Pointer/AliasSetOwner.h" +#include "phasar/Utils/AnalysisProperties.h" #include "llvm/IR/Function.h" @@ -31,7 +33,8 @@ struct AliasInfoTraits /// \note Currently assumes that the underlying alias information is /// flow-insensitive and the granularity of different alias-information per /// instruction is actually at function-level -class CachedLLVMAliasIterator { +class CachedLLVMAliasIterator + : public AnalysisPropertiesMixin { public: using alias_traits_t = AliasInfoTraits; using n_t = alias_traits_t::n_t; @@ -44,11 +47,12 @@ class CachedLLVMAliasIterator { // --- API Functions: - [[nodiscard]] inline bool isInterProcedural() const noexcept { + [[nodiscard]] constexpr bool isInterProcedural() const noexcept { return false; // No idea, so be conservative here }; - [[nodiscard]] AliasAnalysisType getAliasAnalysisType() const noexcept { + [[nodiscard]] constexpr AliasAnalysisType + getAliasAnalysisType() const noexcept { return AliasAnalysisType::Invalid; // No idea } @@ -83,7 +87,8 @@ class CachedLLVMAliasIterator { void printAsJson(llvm::raw_ostream &OS = llvm::outs()) const; - [[nodiscard]] AnalysisProperties getAnalysisProperties() const noexcept { + [[nodiscard]] constexpr AnalysisProperties + getAnalysisProperties() const noexcept { return AnalysisProperties::None; } @@ -98,18 +103,18 @@ class CachedLLVMAliasIterator { }; struct ReachableAllocationSitesKeyDMI { - inline static ReachableAllocationSitesKey getEmptyKey() noexcept { + static ReachableAllocationSitesKey getEmptyKey() noexcept { return {{}, llvm::DenseMapInfo::getEmptyKey()}; } - inline static ReachableAllocationSitesKey getTombstoneKey() noexcept { + static ReachableAllocationSitesKey getTombstoneKey() noexcept { return {{}, llvm::DenseMapInfo::getTombstoneKey()}; } - inline static auto getHashValue(ReachableAllocationSitesKey Key) noexcept { + static auto getHashValue(ReachableAllocationSitesKey Key) noexcept { return llvm::hash_combine(Key.FunAndIntraProcOnly.getOpaqueValue(), Key.Value); } - inline static bool isEqual(ReachableAllocationSitesKey Key1, - ReachableAllocationSitesKey Key2) noexcept { + static bool isEqual(ReachableAllocationSitesKey Key1, + ReachableAllocationSitesKey Key2) noexcept { return Key1.FunAndIntraProcOnly == Key2.FunAndIntraProcOnly && Key1.Value == Key2.Value; } @@ -124,6 +129,8 @@ class CachedLLVMAliasIterator { ReachableAllocationSitesKeyDMI> ReachableAllocationSitesMap; }; + +static_assert(IsAliasInfo); } // namespace psr #endif // PHASAR_PHASARLLVM_POINTER_CACHEDALIASITERATOR_H diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h b/include/phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h new file mode 100644 index 0000000000..59ec22d22c --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/LLVMGlobalInitCache.h @@ -0,0 +1,91 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/ValueCompressor.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Operator.h" +#include "llvm/Support/Casting.h" + +#include +#include + +namespace psr { + +/// Memoised walker for global-variable pointer initializers. +/// +/// Traverses a \c llvm::Constant initializer and collects the \c ValueId of +/// every pointer-typed sub-constant it contains (direct pointer, GEP base, +/// or pointer elements of an aggregate). Results are cached so shared +/// sub-expressions are not revisited. +/// +/// Create one instance per analysis run; it is tied to a single +/// \c ValueCompressor via the \p GetVar callback. +struct GlobalInitCache { + std::unordered_map> + Cache; + + /// Returns the \c ValueId slice for all pointer-typed constants reachable + /// from \p Const. \p GetVar maps an \c llvm::Value* to a \c ValueId + /// (typically \c getOrInsertVar). + template GetVarFn> + [[nodiscard]] llvm::ArrayRef getOrCreate(const llvm::Constant *Const, + GetVarFn &&GetVar) { + if (definitelyContainsNoPointer(Const)) { + return {}; + } + + auto [It, Inserted] = Cache.try_emplace(Const); + if (!Inserted) { + return It->second; + } + auto &Vec = It->second; + + if (llvm::isa(Const)) { + return {}; + } + + if (const auto *CGep = llvm::dyn_cast(Const)) { + // TODO: Properly handle constant GEPs + return getOrCreate(llvm::cast(CGep->getPointerOperand()), + GetVar); + } + + if (Const->getType()->isPointerTy()) { + Vec.push_back(std::invoke(GetVar, Const)); + return Vec; + } + + // TODO: Get rid of the recursion + + if (const auto *Agg = llvm::dyn_cast(Const)) { + if (Agg->getType()->isArrayTy() && + definitelyContainsNoPointer(Agg->getType()->getArrayElementType())) { + return {}; + } + for (size_t I = 0, N = Agg->getNumOperands(); I < N; ++I) { + const auto *Elem = llvm::cast( + Agg->getAggregateElement(I)->stripPointerCastsAndAliases()); + auto Sub = getOrCreate(Elem, GetVar); + Vec.append(Sub.begin(), Sub.end()); + } + } + + // TODO: more + + return Vec; + } +}; + +} // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h b/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h index 7f118a7d4b..3679bb6cb7 100644 --- a/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h +++ b/include/phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h @@ -13,10 +13,13 @@ #include "phasar/PhasarLLVM/Domain/LLVMAnalysisDomain.h" #include "phasar/Pointer/PointerAssignmentGraph.h" +#include "llvm/ADT/FunctionExtras.h" + namespace llvm { class Instruction; class Value; class Function; +class MemorySSA; } // namespace llvm namespace psr { @@ -103,19 +106,40 @@ struct LLVMPAGDomain : LLVMAnalysisDomainDefault { /// Traverses all functions in the given \c LLVMProjectIRDB and emits PAG /// nodes and edges to the provided \c PBStrategyRef. /// -/// Achieves basic field sensitivity by handlking constant GEPs in an ad-hoc +/// Achieves basic field sensitivity by handling constant GEPs in an ad-hoc /// manner. /// /// An optional \c LLVMFunctionDataFlowFacts object can be supplied to apply /// pre-computed library summaries at potentially declaration-only calls. +/// +/// An optional \c MemSSAProviderFn enables flow-sensitive load edges: for each +/// load instruction the builder queries the provider for the function's +/// \c MemorySSA and connects only the reaching stores rather than all stores to +/// the same base pointer. Use \c withBuiltinMemSSA() when no external analysis +/// manager is available. class LLVMPAGBuilder : public PAGBuilder { public: + /// Callback that maps a function to its \c MemorySSA. The returned pointer + /// must remain valid for the duration of \c buildPAG. Returning null + /// disables flow-sensitivity for that function. + using MemSSAProviderFn = + llvm::unique_function; + constexpr LLVMPAGBuilder() noexcept = default; - /// \param MLSum Pre-computed library summary facts. If non-null, callee + + /// \param MLSum Pre-computed library summary facts. If non-null, callee /// analysis for matched library functions is replaced by these summaries. - constexpr LLVMPAGBuilder( - const library_summary::LLVMFunctionDataFlowFacts *MLSum) noexcept - : MLSum(MLSum) {} + /// \param MemSSAProvider Optional per-function MemorySSA provider for + /// flow-sensitive load handling. + LLVMPAGBuilder(const library_summary::LLVMFunctionDataFlowFacts *MLSum, + MemSSAProviderFn MemSSAProvider = nullptr) noexcept + : MLSum(MLSum), MemSSAProvider(std::move(MemSSAProvider)) {} + + /// Returns a builder that constructs \c DominatorTree + \c BasicAA + + /// \c MemorySSA internally for each function on demand. No prior alias + /// analysis or \c FunctionAnalysisManager required. + [[nodiscard]] static LLVMPAGBuilder withBuiltinMemSSA( + const library_summary::LLVMFunctionDataFlowFacts *MLSum = nullptr); void buildPAG(const LLVMProjectIRDB &IRDB, ValueCompressor &VC, pag::PBStrategyRef Strategy) override; @@ -124,6 +148,7 @@ class LLVMPAGBuilder : public PAGBuilder { struct PAGBuildData; const library_summary::LLVMFunctionDataFlowFacts *MLSum{}; + MemSSAProviderFn MemSSAProvider{}; }; } // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h b/include/phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h new file mode 100644 index 0000000000..67070c196d --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/LLVMPointerSemantics.h @@ -0,0 +1,170 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/Utilities.h" + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/GlobalVariable.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Type.h" +#include "llvm/IR/Value.h" +#include "llvm/Support/Casting.h" + +#include + +/// LLVM-level questions that LLVMPAGBuilder and AndersenOTFSolver must answer +/// identically. They emit different node and edge kinds, but must not disagree +/// on which IR constructs carry pointers. + +namespace psr { + +/// Whether a value of the pointer-free type \p Ty is laid out such that it +/// could hold pointer bit patterns. +[[nodiscard]] inline bool mayHidePointer(const llvm::DataLayout &DL, + const llvm::Type *Ty) { + if (Ty->isIntegerTy(DL.getPointerSizeInBits())) { + return true; + } + if (const auto *Arr = llvm::dyn_cast(Ty)) { + return mayHidePointer(DL, Arr->getElementType()); + } + const auto *Struct = llvm::dyn_cast(Ty); + return Struct && !Struct->isOpaque() && !Struct->elements().empty() && + llvm::all_of(Struct->elements(), [&DL](const llvm::Type *ElemTy) { + return mayHidePointer(DL, ElemTy); + }); +} + +/// Whether Ptr is a memory-location (alloca or global), accessed as an +/// integer. +/// +/// Useful for handling atomicrmw of pointers, which clang punns to i64, and +/// for ABI-coerced aggregates (see PunnedABICache). +[[nodiscard]] inline bool isPunnedPointerAccess(const llvm::DataLayout &DL, + const llvm::Value *Ptr, + const llvm::Type *AccessedTy) { + if (!mayHidePointer(DL, AccessedTy)) { + return false; + } + const llvm::Value *Base = Ptr->stripPointerCastsAndAliases(); + if (const auto *A = llvm::dyn_cast(Base)) { + return !definitelyContainsNoPointer(A->getAllocatedType()); + } + if (const auto *G = llvm::dyn_cast(Base)) { + return !definitelyContainsNoPointer(G->getValueType()); + } + return false; +} + +/// The memory access to model for a load, store, atomicrmw or cmpxchg. +struct LLVMMemoryAccess { + const llvm::Instruction *Instr{}; + const llvm::Value *Pointer{}; + /// Null if the access only reads. + const llvm::Value *StoredValue{}; + /// Null if the access only writes; \c Instr itself for an atomic. + const llvm::Instruction *LoadedInto{}; + bool Punned{}; + + /// The value whose type decides whether a pointer is transferred. For a + /// cmpxchg that is the new value, not the { ty, i1 } result. + [[nodiscard]] const llvm::Value *transferredValue() const noexcept { + return StoredValue ? StoredValue : LoadedInto; + } + + /// Whether the access has to be modeled at all. Gating on + /// definitelyContainsNoPointer alone would drop punned accesses. + [[nodiscard]] bool mayTransferPointer() const { + return Punned || !definitelyContainsNoPointer(transferredValue()); + } +}; + +/// Decomposes \p I, or returns nullopt if it is not a memory access. Field- +/// insensitively an atomicrmw is a store of the new value plus a load of the +/// old one; cmpxchg likewise, into its { ty, i1 } result. +/// +/// The result still has to pass mayTransferPointer(). +[[nodiscard]] inline std::optional +asMemoryAccess(const llvm::Instruction &I, const llvm::DataLayout &DL) { + const auto Make = [&DL, &I](const llvm::Value *Ptr, + const llvm::Value *StoredValue, + const llvm::Instruction *LoadedInto) { + const auto *Transferred = StoredValue ? StoredValue : LoadedInto; + return LLVMMemoryAccess{ + .Instr = &I, + .Pointer = Ptr, + .StoredValue = StoredValue, + .LoadedInto = LoadedInto, + .Punned = isPunnedPointerAccess(DL, Ptr, Transferred->getType()), + }; + }; + + if (const auto *S = llvm::dyn_cast(&I)) { + return Make(S->getPointerOperand(), S->getValueOperand(), nullptr); + } + if (const auto *L = llvm::dyn_cast(&I)) { + return Make(L->getPointerOperand(), nullptr, L); + } + if (const auto *RMW = llvm::dyn_cast(&I)) { + return Make(RMW->getPointerOperand(), RMW->getValOperand(), RMW); + } + if (const auto *CX = llvm::dyn_cast(&I)) { + return Make(CX->getPointerOperand(), CX->getNewValOperand(), CX); + } + return std::nullopt; +} + +/// Recognizes ABI-coerced boundary values: values that carry a pointer +/// although their type has none. +/// +/// A small pointer-carrying struct is passed and returned in registers: +/// +/// For whatever reason, on AArch64 clang punns such nested pointers as i64 +/// instead of ptr. This here is a best-effort approcach to keep pointer +/// data-flows in such situations. +class PunnedABICache { +public: + explicit PunnedABICache(const llvm::DataLayout *DL) noexcept + : DL(&assertNotNull(DL)) {} + + /// Whether \p V -- an argument, returned value or call result of \p F -- + /// carries a pointer that its type does not reveal. + [[nodiscard]] bool isCoercedPointer(const llvm::Value *V, + const llvm::Function *F) { + return !llvm::isa(V) && + definitelyContainsNoPointer(V->getType()) && + mayHidePointer(*DL, V->getType()) && punsPointers(F); + } + +private: + bool punsPointers(const llvm::Function *F) { + auto [It, Inserted] = Cache.try_emplace(F, false); + if (Inserted) { + It->second = llvm::any_of(llvm::instructions(*F), [this](const auto &I) { + const auto Access = asMemoryAccess(I, *DL); + return Access && Access->Punned; + }); + } + return It->second; + } + + const llvm::DataLayout *DL; + llvm::DenseMap Cache; +}; + +} // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h b/include/phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h new file mode 100644 index 0000000000..f0aafcb3c3 --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h @@ -0,0 +1,301 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Pointer/AliasResult.h" +#include "phasar/Pointer/RawAAResult.h" +#include "phasar/Utils/MapUtils.h" +#include "phasar/Utils/NonNullPtr.h" + +#include "llvm/IR/Instructions.h" + +namespace psr { + +/// Returns a \c ValueId handler suitable for \c RawAliasSet::foreach() that +/// maps each alias \c ValueId back to all of its underlying \c llvm::Value* +/// (via \p VC), forwarding non-null values to \p Callback. +constexpr std::invocable auto +llvmRawAliasHandler(const ValueCompressor &VC, + std::invocable auto Callback) { + return [&VC, Callback{std::move(Callback)}](ValueId Alias) { + for (auto V : VC.id2vars(Alias)) { + if (const auto *LLVMVar = V.valueOrNull()) [[likely]] { + std::invoke(Callback, LLVMVar); + } + } + }; +} + +/// CRTP mixin that adds the LLVM alias-iterator interface to a class that +/// holds a \c RawAAResult and a \c ValueCompressor. +/// +/// Provides \c forallAliasesOf(), \c mayAlias(), and \c alias() overloads +/// accepting both \c llvm::Value* and \c ValueId arguments. Results are +/// reported as \c llvm::Value* via the stored \c ValueCompressor. +/// +/// The derived class must expose a \c VC member (pointer to a +/// \c ValueCompressor). +/// +/// \tparam Derived The CRTP derived class. +/// \tparam AAResT A type satisfying \c RawAAResult. +template + requires RawAAResult> +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) +struct LLVMRawAliasIteratorMixin { + [[no_unique_address]] AAResT AARes; + + using v_t = const llvm::Value *; + using n_t = const llvm::Instruction *; + + [[nodiscard]] decltype(auto) getRawAliasSet(ValueId ValId) const { + return AARes.getRawAliasSet(ValId); + } + + [[nodiscard]] const auto &base() const noexcept { return AARes; } + + void + forallAliasesOf(ValueId VId, const auto & /*Inst*/, + std::invocable auto Callback) const { + const auto &RawAliases = AARes.getRawAliasSet(VId); + RawAliases.foreach (llvmRawAliasHandler(*self().VC, copyOrRef(Callback))); + } + + void + forallAliasesOf(const llvm::Value *Ptr, const auto &Inst, + std::invocable auto Callback) const { + if (auto ValId = self().VC->getOrNull(Ptr)) { + forallAliasesOf(*ValId, Inst, copyOrRef(Callback)); + } + } + + [[nodiscard]] bool mayAlias(ValueId Ptr1, ValueId Ptr2) const { + return AARes.mayAlias(Ptr1, Ptr2); + } + + [[nodiscard]] bool mayAlias(ValueId Ptr1, ValueId Ptr2, + const auto & /*AtInstruction*/) const { + return AARes.mayAlias(Ptr1, Ptr2); + } + + [[nodiscard]] bool mayAlias(const llvm::Value *Ptr1, + const llvm::Value *Ptr2) const { + auto ValId1 = self().VC->getOrNull(Ptr1); + auto ValId2 = self().VC->getOrNull(Ptr2); + + return ValId1 && ValId2 && mayAlias(*ValId1, *ValId2); + } + + [[nodiscard]] bool mayAlias(const llvm::Value *Ptr1, const llvm::Value *Ptr2, + const auto & /*AtInstruction*/) const { + return mayAlias(Ptr1, Ptr2); + } + + [[nodiscard]] AliasResult alias(const llvm::Value *Ptr1, + const llvm::Value *Ptr2, + const auto &AtInstruction) const { + auto ValId1 = self().VC->getOrNull(Ptr1); + auto ValId2 = self().VC->getOrNull(Ptr2); + if (!ValId1 || !ValId2) { + return AliasResult::NoAlias; + } + if (*ValId1 == *ValId2) { + if (Ptr1 == Ptr2) { + return AliasResult::MustAlias; + } + return !llvm::isa(Ptr1) && + !llvm::isa(Ptr2) + ? AliasResult::MustAlias + : AliasResult::MayAlias; + } + return mayAlias(*ValId1, *ValId2, AtInstruction) ? AliasResult::MayAlias + : AliasResult::NoAlias; + } + + [[nodiscard]] constexpr const Derived &self() const noexcept { + return *static_cast(this); + } +}; + +template + requires RawAAResult> +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) +struct LLVMRawAliasIterator + : public LLVMRawAliasIteratorMixin, AAResT> { + MaybeUniquePtr> VC; + + constexpr LLVMRawAliasIterator( + AAResT &&AARes, + MaybeUniquePtr> VC PSR_LIFETIMEBOUND) + : psr::LLVMRawAliasIteratorMixin, + AAResT>{PSR_FWD(AARes)}, + VC(std::move(VC)) {} +}; + +template +LLVMRawAliasIterator(AAResT, MaybeUniquePtr>) + -> LLVMRawAliasIterator; +template +LLVMRawAliasIterator(AAResT, const ValueCompressor *) + -> LLVMRawAliasIterator; + +namespace detail { +class LLVMLocalRawAliasIteratorBase { +public: + explicit LLVMLocalRawAliasIteratorBase( + const ValueCompressor &VC); + +protected: + llvm::DenseMap> GlobalsOrInFun; +}; +} // namespace detail + +/// CRTP mixin adding a function-local view on top of a global +/// \c RawAAResult. +/// +/// Extends \c LLVMRawAliasIteratorMixin with \c forallAliasesOf() +/// overloads that accept an \c llvm::Function* or \c llvm::Instruction* +/// context. When a non-null context is provided, alias sets are intersected +/// with the set of variables that are visible in that function (globals plus +/// locals defined in that function), giving a function-local result even +/// though the underlying analysis is interprocedural. +template +class LLVMLocalRawAliasIteratorMixin + : public detail::LLVMLocalRawAliasIteratorBase { +public: + LLVMLocalRawAliasIteratorMixin(AAResT &&AARes, + const ValueCompressor &VC) + : detail::LLVMLocalRawAliasIteratorBase(VC), AARes(PSR_FWD(AARes)) {} + + [[nodiscard]] decltype(auto) getRawAliasSet(ValueId ValId) const { + return AARes.getRawAliasSet(ValId); + } + + [[nodiscard]] auto getRawAliasSet(ValueId ValId, + const llvm::Function *Context) const { + auto Vars = AARes.getRawAliasSet(ValId); + if (Context) { + Vars &= getOrDefault(GlobalsOrInFun, Context); + } + return Vars; + } + + [[nodiscard]] const auto &base() const noexcept { return AARes; } + + [[nodiscard]] auto getRawAliasSet(ValueId ValId, + const llvm::Instruction *Context) const { + return getRawAliasSet(ValId, getFunction(Context)); + } + + void forallAliasesOf(ValueId VId, const llvm::Function *Context, + std::invocable auto WithAlias) { + const auto AliasHandler = + llvmRawAliasHandler(*self().VC, copyOrRef(WithAlias)); + + auto &&RawVars = AARes.getRawAliasSet(VId); + if (Context) { + auto Vars = PSR_FWD(RawVars); + Vars &= getOrDefault(GlobalsOrInFun, Context); + Vars.foreach (AliasHandler); + } else { + RawVars.foreach (AliasHandler); + } + } + + void forallAliasesOf(const llvm::Value *Val, const llvm::Function *Context, + std::invocable auto WithAlias) { + if (auto ValId = self().VC->getOrNull(Val)) { + forallAliasesOf(*ValId, Context, copyOrRef(WithAlias)); + } + } + + void forallAliasesOf(ValueId ValId, const llvm::Instruction *AtInstruction, + std::invocable auto WithAlias) { + forallAliasesOf(ValId, psr::getFunction(AtInstruction), + copyOrRef(WithAlias)); + } + + void forallAliasesOf(const llvm::Value *Val, + const llvm::Instruction *AtInstruction, + std::invocable auto WithAlias) { + forallAliasesOf(Val, psr::getFunction(AtInstruction), copyOrRef(WithAlias)); + } + + void forallAliasesOf(const llvm::Value *Val, + std::invocable auto WithAlias) { + forallAliasesOf(Val, psr::getFunction(Val), copyOrRef(WithAlias)); + } + + [[nodiscard]] bool + mayAlias(ValueId ValId1, ValueId ValId2, + const llvm::Instruction * /*AtInstruction*/ = nullptr) const { + // XXX: Should we filter by AtInstruction-context here as well? + return AARes.mayAlias(ValId1, ValId2); + } + + [[nodiscard]] bool + mayAlias(const llvm::Value *Ptr1, const llvm::Value *Ptr2, + const llvm::Instruction * /*AtInstruction*/ = nullptr) const { + auto ValId1 = self().VC->getOrNull(Ptr1); + auto ValId2 = self().VC->getOrNull(Ptr2); + + // XXX: Should we filter by AtInstruction-context here as well? + return ValId1 && ValId2 && AARes.mayAlias(*ValId1, *ValId2); + } + + [[nodiscard]] AliasResult alias(const llvm::Value *Ptr1, + const llvm::Value *Ptr2, + const auto &AtInstruction) const { + auto ValId1 = self().VC->getOrNull(Ptr1); + auto ValId2 = self().VC->getOrNull(Ptr2); + if (!ValId1 || !ValId2) { + return AliasResult::NoAlias; + } + if (*ValId1 == *ValId2) { + if (Ptr1 == Ptr2) { + return AliasResult::MustAlias; + } + return !llvm::isa(Ptr1) && + !llvm::isa(Ptr2) + ? AliasResult::MustAlias + : AliasResult::MayAlias; + } + return mayAlias(*ValId1, *ValId2, AtInstruction) ? AliasResult::MayAlias + : AliasResult::NoAlias; + } + + [[nodiscard]] constexpr const Derived &self() const noexcept { + return *static_cast(this); + } + +private: + AAResT AARes; +}; + +template +class LLVMLocalRawAliasIterator + : public LLVMLocalRawAliasIteratorMixin, + AAResT> { + friend LLVMLocalRawAliasIteratorMixin, + AAResT>; + +public: + LLVMLocalRawAliasIterator(AAResT &&AARes, + NonNullPtr> VC + PSR_LIFETIMEBOUND) + : LLVMLocalRawAliasIteratorMixin, + AAResT>(PSR_FWD(AARes), *VC), + VC(VC) {} + +private: + NonNullPtr> VC; +}; +} // namespace psr diff --git a/include/phasar/PhasarLLVM/Pointer/LLVMRawAliasSet.h b/include/phasar/PhasarLLVM/Pointer/LLVMRawAliasSet.h new file mode 100644 index 0000000000..4042f0da29 --- /dev/null +++ b/include/phasar/PhasarLLVM/Pointer/LLVMRawAliasSet.h @@ -0,0 +1,352 @@ +#pragma once + +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointerAssignmentGraph.h" +#include "phasar/PhasarLLVM/Pointer/LLVMPointsToUtils.h" +#include "phasar/PhasarLLVM/Pointer/LLVMRawAAResults.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Pointer/AliasAnalysisType.h" +#include "phasar/Pointer/AliasInfoTraits.h" +#include "phasar/Pointer/AliasResult.h" +#include "phasar/Pointer/AliasSetOwner.h" +#include "phasar/Pointer/RawAAResult.h" +#include "phasar/Pointer/RawAliasSet.h" +#include "phasar/Pointer/UnionFindAliasAnalysisType.h" +#include "phasar/Utils/AnalysisProperties.h" +#include "phasar/Utils/Macros.h" +#include "phasar/Utils/TypedVector.h" +#include "phasar/Utils/ValueCompressor.h" + +#include "llvm/IR/GlobalVariable.h" +#include "llvm/IR/Instructions.h" +#include "llvm/Support/TypeName.h" + +#include +#include + +namespace llvm { +class Value; +class Instruction; +class Function; +} // namespace llvm + +namespace psr { + +class LLVMRawAliasSet; +class LLVMProjectIRDB; + +template <> +struct AliasInfoTraits + : DefaultAATraits {}; + +struct LLVMRawAliasSetBase { + using traits_t = AliasInfoTraits; + using n_t = traits_t::n_t; + using v_t = traits_t::v_t; + using AliasSetTy = traits_t::AliasSetTy; + using AliasSetPtrTy = traits_t::AliasSetPtrTy; + using AllocationSiteSetPtrTy = traits_t::AllocationSiteSetPtrTy; + + /// Whether alias sets are reported globally or filtered to the function + /// containing the query instruction. + enum class AnalysisLocality : uint8_t { + /// All aliases across all functions are reported. + Global, + /// Aliases are intersected with variables visible in the querying function + /// (globals + function-local values). + FunctionLocal, + }; + + struct Config { + /// The specific union-find analysis variant to run (default: + /// \c BotCtxIndSens — bottom-up, context- and indirection-sensitive). + UnionFindAliasAnalysisType AType = + UnionFindAliasAnalysisType::BotCtxIndSens; + /// Controls whether alias sets are scoped to the querying function. + AnalysisLocality ALocality = AnalysisLocality::Global; + }; +}; + +[[nodiscard]] llvm::StringRef +to_string(LLVMRawAliasSetBase::AnalysisLocality ALoc) noexcept; + +/// Concrete \c IsAliasInfo implementation backed by a raw alias +/// analysis. Provides convenience constructors to invoke union-find-based +/// analyses, but can be instantiated with *any* analysis result that conforms +/// to \c RawAAResult . +/// +/// Alias sets are materialized lazily on first query and cached per \c ValueId +/// in \c AliasSets. +/// +/// \note When \c AnalysisLocality::FunctionLocal is selected, alias sets are +/// filtered to variables visible in the function that contains the query +/// instruction. The per-\c ValueId cache does **not** account for the +/// instruction context, so the first caller's function wins — do not mix +/// queries to local pointers from different functions for the same value in +/// local mode. +class LLVMRawAliasSet : public LLVMRawAliasSetBase, + public AnalysisPropertiesMixin { +public: + explicit LLVMRawAliasSet(const LLVMProjectIRDB *IRDB, + const LLVMBasedCallGraph &BaseCG, Config Cfg, + ValueCompressor *VC); + explicit LLVMRawAliasSet(const LLVMProjectIRDB *IRDB, + const LLVMBasedCallGraph &BaseCG, Config Cfg) + : LLVMRawAliasSet(IRDB, BaseCG, Cfg, nullptr) {} + explicit LLVMRawAliasSet(const LLVMProjectIRDB *IRDB, + const LLVMBasedCallGraph &BaseCG) + : LLVMRawAliasSet(IRDB, BaseCG, Config{}, nullptr) {} + + template + explicit LLVMRawAliasSet(AAResT &&AARes, + MaybeUniquePtr> VC, + AnalysisProperties Props = {}) + : Props(Props) { + assert(VC != nullptr); + AliasSets.resize(VC->size()); + // XXX: Support locality + + this->AARes = std::make_unique< + AAResultModel>>( + std::move(VC), PSR_FWD(AARes)); + } + + [[nodiscard]] constexpr std::true_type isInterProcedural() const noexcept { + return {}; + }; + + [[nodiscard]] constexpr std::integral_constant + getAliasAnalysisType() const noexcept { + return {}; + }; + + [[nodiscard]] constexpr AnalysisProperties + getAnalysisProperties() const noexcept { + return Props; + } + + [[nodiscard]] constexpr AliasResult alias(v_t V1, v_t V2, n_t I) const { + assert(isValid()); + return AARes->alias(V1, V2, I); + } + + void foreachAliasOf(v_t V, n_t I, + llvm::function_ref WithAlias) const { + assert(isValid()); + AARes->forallAliasesOf(V, I, WithAlias); + } + + [[nodiscard]] AliasSetPtrTy getAliasSet(v_t V, n_t I) { + assert(isValid()); + auto ValId = AARes->VC->getOrNull(V); + if (!ValId) { + return getEmptyAliasSet(); + } + + assert(AliasSets.inbounds(*ValId)); + if (!AliasSets[*ValId]) [[unlikely]] { + AliasSets[*ValId] = AARes->constructAliasSet(*ValId, I, Owner); + } + + return AliasSets[*ValId]; + } + + [[nodiscard]] AllocationSiteSetPtrTy + getReachableAllocationSites(v_t V, bool IntraProcOnly, n_t I) const { + assert(isValid()); + auto ValId = AARes->VC->getOrNull(V); + if (!ValId) { + return std::make_unique(); + } + + return AARes->constructReachableAllocSites(V, *ValId, IntraProcOnly, I); + } + + [[nodiscard]] bool isInReachableAllocationSites( + const llvm::Value *V, const llvm::Value *PotentialValue, + bool IntraProcOnly, const llvm::Instruction *I) const { + assert(isValid()); + if (!psr::isInterestingPointer(V)) { + return false; + } + + if (!psr::isInReachableAllocationSitesTy(V, PotentialValue, + IntraProcOnly)) { + return false; + } + + return alias(V, PotentialValue, I) != AliasResult::NoAlias; + } + + void print(llvm::raw_ostream &OS) const; + void printAsJson(llvm::raw_ostream &OS) const; + + [[nodiscard]] bool isValid() const noexcept { + return AARes != nullptr && AARes->VC != nullptr && + AARes->VC->size() == AliasSets.size(); + } + +private: + struct AAResultConcept { + MaybeUniquePtr> VC; + std::optional> AllocationSites{}; + + AAResultConcept( + MaybeUniquePtr> VC) noexcept + : VC(std::move(VC)) {} + virtual ~AAResultConcept() = default; + + virtual void forallAliasesOf(v_t Ptr, n_t Inst, + llvm::function_ref Callback) = 0; + + virtual AliasResult alias(v_t Ptr1, v_t Ptr2, n_t AtInstruction) = 0; + + virtual AliasSetPtrTy + constructAliasSet(ValueId ValId, n_t Inst, + AliasSetOwner &Owner) = 0; + + virtual AllocationSiteSetPtrTy + constructReachableAllocSites(v_t V, ValueId ValId, bool IntraProcOnly, + n_t Inst) = 0; + + virtual void print(llvm::raw_ostream &OS, Config Cfg) const = 0; + }; + + static bool isPotentialAllocSite(const llvm::Value *Val) { + if (!Val->getType()->isPointerTy()) { + return false; + } + if (llvm::isa( + Val)) { + return true; + } + if (const auto *Call = llvm::dyn_cast(Val)) { + return Call->getCalledFunction() && + psr::isHeapAllocatingFunction(Call->getCalledFunction()); + } + return false; + } + + template