diff --git a/.clang-tidy b/.clang-tidy index a3209d4c13..d7b7d2d702 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -21,6 +21,7 @@ Checks: '-*, -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, @@ -38,6 +39,7 @@ Checks: '-*, -modernize-use-trailing-return-type, -modernize-avoid-c-arrays, performance-*, + -performance-enum-size, clang-analyzer-* ' diff --git a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h index e2736176dd..585e674899 100644 --- a/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h +++ b/include/phasar/DataFlow/IfdsIde/Solver/IterativeIDESolver.h @@ -4,6 +4,7 @@ #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" @@ -18,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" @@ -40,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -133,7 +137,8 @@ class IterativeIDESolver public: IterativeIDESolver(ProblemTy *Problem, const ICFGTy *ICFG, StaticSolverConfigTy /*Config*/ = {}) noexcept - : Problem(assertNotNull(Problem)), ICFG(assertNotNull(ICFG)) {} + : Problem(assertNotNull(Problem)), ICFG(assertNotNull(ICFG)), + PathData(StaticSolverConfigTy::initPathData(assertNotNull(Problem))) {} auto solve() & { solveImpl(); @@ -158,6 +163,16 @@ class IterativeIDESolver getSolverResults().dumpResults(ICFG, OS); } + [[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 requires EnableStatistics { @@ -651,6 +666,8 @@ class IterativeIDESolver combineIds(AtInstructionId, SuccId)) .computeTargets(CSFact); + saveEdges(AtInstruction, Succ, CSFact, Facts, ESGEdgeKind::Normal); + for (ByConstRef Fact : Facts) { auto FactId = FactCompressor.getOrInsert(Fact); auto EF = [&] { @@ -697,20 +714,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)) { @@ -721,6 +739,10 @@ class IterativeIDESolver combineIds(AtInstructionId, RetSiteId)) .computeTargets(CSFact); + saveEdges(AtInstruction, RetSite, CSFact, Facts, + HasNoCalleeInformation ? ESGEdgeKind::SkipUnknownFn + : ESGEdgeKind::CallToRet); + for (ByConstRef Fact : Facts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -745,12 +767,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 = @@ -760,9 +783,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, @@ -770,6 +795,7 @@ class IterativeIDESolver PropagatedFactId, SourceEF, FunId); } } + return HasNoCalleeInformation; } void countSummaryLinearSearch(size_t SearchLen, size_t NumSummaries) { @@ -837,7 +863,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, @@ -855,9 +881,15 @@ class IterativeIDESolver 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); + saveEdges(AtInstruction, SP, CSFact, CalleeFacts, ESGEdgeKind::Call); + for (ByConstRef Fact : CalleeFacts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -906,6 +938,7 @@ class IterativeIDESolver } } } + return HasNoCalleeInformation; } template @@ -917,6 +950,9 @@ class IterativeIDESolver for (ByConstRef RetSite : ICFG.getReturnSitesOfCallAt(AtInstruction)) { auto RetSiteId = NodeCompressor.getOrInsert(RetSite); + saveEdges(AtInstruction, RetSite, CSFact, SummaryFacts, + ESGEdgeKind::Summary); + for (ByConstRef Fact : SummaryFacts) { auto FactId = FactCompressor.getOrInsert(Fact); @@ -954,6 +990,9 @@ class IterativeIDESolver 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); @@ -1288,6 +1327,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; } @@ -1339,6 +1385,9 @@ class IterativeIDESolver llvm::BitVector CandidateFunctionsForGC{}; 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/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 e4fe5b6ca5..1a13e724fe 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 @@ -51,6 +54,22 @@ struct IDESolverConfigBase { template using EdgeFunctionPtrType = EdgeFunction; + 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 = diff --git a/include/phasar/Utils/Lazy.h b/include/phasar/Utils/Lazy.h index 6abe6d7a95..3d03bee182 100644 --- a/include/phasar/Utils/Lazy.h +++ b/include/phasar/Utils/Lazy.h @@ -27,6 +27,11 @@ template struct lazy { std::is_nothrow_invocable_v) { return std::invoke(std::move(F)); } + + constexpr operator std::invoke_result_t() & noexcept( + std::is_nothrow_invocable_v) { + return std::invoke(F); + } }; template lazy(FF) -> lazy>; diff --git a/unittests/PhasarLLVM/DataFlow/PathSensitivity/CMakeLists.txt b/unittests/PhasarLLVM/DataFlow/PathSensitivity/CMakeLists.txt index 11f6cc1e5d..6457edffc5 100644 --- a/unittests/PhasarLLVM/DataFlow/PathSensitivity/CMakeLists.txt +++ b/unittests/PhasarLLVM/DataFlow/PathSensitivity/CMakeLists.txt @@ -1,5 +1,6 @@ if(PHASAR_USE_Z3) add_phasar_unittest(PathTracingTest.cpp) + add_phasar_unittest(PathAwareIterIDESolverTest.cpp) target_link_libraries(PathTracingTest LINK_PUBLIC diff --git a/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathAwareIterIDESolverTest.cpp b/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathAwareIterIDESolverTest.cpp new file mode 100644 index 0000000000..ba0f66d67a --- /dev/null +++ b/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathAwareIterIDESolverTest.cpp @@ -0,0 +1,131 @@ +#include "phasar/DataFlow/IfdsIde/Solver/PathAwareIterIDESolver.h" + +#include "phasar/DataFlow/IfdsIde/Solver/PathAwareIDESolver.h" +#include "phasar/DataFlow/PathSensitivity/FlowPath.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/DataFlow/IfdsIde/Problems/IDEExtendedTaintAnalysis.h" +#include "phasar/PhasarLLVM/DataFlow/PathSensitivity/LLVMPathConstraints.h" +#include "phasar/PhasarLLVM/DataFlow/PathSensitivity/Z3BasedPathSensitivityConfig.h" +#include "phasar/PhasarLLVM/DataFlow/PathSensitivity/Z3BasedPathSensitvityManager.h" +#include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" +#include "phasar/PhasarLLVM/TaintConfig/LLVMTaintConfig.h" +#include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" + +#include "llvm/ADT/StringRef.h" + +#include "TestConfig.h" +#include "gtest/gtest.h" + +namespace { +struct PathAwareIterIDESolverTest + : public ::testing::TestWithParam { + + static constexpr auto PathToLlFiles = PHASAR_BUILD_SUBFOLDER("path_tracing/"); +}; + +using n_t = const llvm::Instruction *; + +static void comparePaths(const psr::FlowPathSequence &Old, + const psr::FlowPathSequence &New) { + std::set MatchingIndices; + auto Matches = [&New, &MatchingIndices](const psr::FlowPath >) { + size_t Idx = 0; + for (const auto &Path : New) { + psr::scope_exit IncIdx = [&Idx] { ++Idx; }; + if (Path.size() != GT.size()) { + continue; + } + bool Match = llvm::equal(Path, GT); + + if (Match) { + MatchingIndices.insert(Idx); + return true; + } + } + + return false; + }; + + for (const auto > : Old) { + EXPECT_TRUE(Matches(GT)) + << "No match found for " << psr::PrettyPrinter{GT} + << "; MatchingIndices.size() = " << MatchingIndices.size() + << "; NewPaths.size() = " << New.size(); + } + + EXPECT_EQ(MatchingIndices.size(), New.size()); + + if (MatchingIndices.size() != New.size()) { + for (size_t I = 0; I < New.size(); ++I) { + if (MatchingIndices.contains(I)) { + continue; + } + + llvm::errs() << "> PATH NOT IN GT: " + << psr::PrettyPrinter{llvm::map_range( + New[I], + [](const auto *Inst) { + return psr::getMetaDataID(Inst); + })} + << '\n'; + } + } +} + +TEST_P(PathAwareIterIDESolverTest, RegressionAgainstIDESolver) { + auto IRDB = psr::LLVMProjectIRDB::loadOrExit(PathToLlFiles + GetParam()); + + auto *Main = IRDB.getFunctionDefinition("main"); + ASSERT_NE(Main, nullptr); + auto *LastInst = &Main->back().back(); + llvm::outs() << "Target instruction: " << psr::llvmIRToString(LastInst) + << '\n'; + + psr::DIBasedTypeHierarchy TH(IRDB); + psr::LLVMAliasSet PT(&IRDB); + psr::LLVMBasedICFG ICFG(&IRDB, psr::CallGraphAnalysisType::OTF, {"main"}, &TH, + &PT, psr::Soundness::Soundy, + /*IncludeGlobals*/ false); + + psr::LLVMTaintConfig Config(IRDB); + psr::IDEExtendedTaintAnalysis<3, false> Analysis(&IRDB, &ICFG, &PT, Config, + {"main"}); + psr::PathAwareIDESolver OldSolver(&Analysis, &ICFG); + OldSolver.solve(); + + psr::PathAwareIterIDESolver NewSolver(&Analysis, &ICFG); + NewSolver.solve(); + + const auto &OldESG = OldSolver.getExplicitESG(); + const auto &NewESG = NewSolver.getExplicitESG(); + + psr::LLVMPathConstraints LPC; + + psr::Z3BasedPathSensitivityManager + OldPSM(&OldESG, psr::Z3BasedPathSensitivityConfig(), &LPC); + psr::Z3BasedPathSensitivityManager + NewPSM(&NewESG, psr::Z3BasedPathSensitivityConfig(), &LPC); + + auto OldPaths = OldPSM.pathsTo(LastInst, Analysis.getZeroValue()); + auto NewPaths = NewPSM.pathsTo(LastInst, Analysis.getZeroValue()); + + comparePaths(OldPaths, NewPaths); +} + +constexpr std::string_view IRFiles[] = { + "inter_01_cpp.ll", "inter_02_cpp.ll", "inter_03_cpp.ll", "inter_04_cpp.ll", + "inter_05_cpp.ll", "inter_06_cpp.ll", "inter_07_cpp.ll", "inter_08_cpp.ll", + "inter_09_cpp.ll", "inter_10_cpp.ll", "inter_11_cpp.ll", "inter_12_cpp.ll", +}; + +INSTANTIATE_TEST_SUITE_P(PathSensitivity, PathAwareIterIDESolverTest, + ::testing::ValuesIn(IRFiles)); + +} // namespace + +// main function for the test case +int main(int Argc, char **Argv) { + ::testing::InitGoogleTest(&Argc, Argv); + return RUN_ALL_TESTS(); +} diff --git a/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathTracingTest.cpp b/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathTracingTest.cpp index 76261020e9..8fb53f5cb7 100644 --- a/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathTracingTest.cpp +++ b/unittests/PhasarLLVM/DataFlow/PathSensitivity/PathTracingTest.cpp @@ -87,7 +87,7 @@ class PathTracingTest : public ::testing::Test { {"main"}, &TH, &PT, psr::Soundness::Soundy, /*IncludeGlobals*/ false); psr::IDELinearConstantAnalysis LCAProblem(IRDB.get(), &ICFG, {"main"}); - psr::PathAwareIDESolver LCASolver(LCAProblem, &ICFG); + psr::PathAwareIDESolver LCASolver(&LCAProblem, &ICFG); LCASolver.solve(); if (PrintDump) { // IRDB->print(); @@ -122,7 +122,7 @@ class PathTracingTest : public ::testing::Test { psr::LLVMTaintConfig Config(*IRDB); psr::IDEExtendedTaintAnalysis<3, false> Analysis(IRDB.get(), &ICFG, &PT, Config, {"main"}); - psr::PathAwareIDESolver Solver(Analysis, &ICFG); + psr::PathAwareIDESolver Solver(&Analysis, &ICFG); Solver.solve(); auto *Main = IRDB->getFunctionDefinition("main"); @@ -183,7 +183,7 @@ class PathTracingTest : public ::testing::Test { if (MatchingIndices.size() != AnalyzedPaths.size()) { for (size_t I = 0; I < AnalyzedPaths.size(); ++I) { - if (MatchingIndices.count(I)) { + if (MatchingIndices.contains(I)) { continue; }