diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h index 3878aa1557..9570ad1bad 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h @@ -89,6 +89,9 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { Soundness S = Soundness::Soundy, bool IncludeGlobals = true); + explicit LLVMBasedICFG(LLVMProjectIRDB *IRDB, + const nlohmann::json &SerializedCG); + ~LLVMBasedICFG(); LLVMBasedICFG(const LLVMBasedICFG &) = delete; diff --git a/include/phasar/PhasarLLVM/HelperAnalyses.h b/include/phasar/PhasarLLVM/HelperAnalyses.h index 07b09878d7..ebaabdcbf8 100644 --- a/include/phasar/PhasarLLVM/HelperAnalyses.h +++ b/include/phasar/PhasarLLVM/HelperAnalyses.h @@ -33,6 +33,7 @@ class HelperAnalyses { // NOLINT(cppcoreguidelines-special-member-functions) std::optional PrecomputedPTS, AliasAnalysisType PTATy, bool AllowLazyPTS, std::vector EntryPoints, + std::optional PrecomputedCG, CallGraphAnalysisType CGTy, Soundness SoundnessLevel, bool AutoGlobalSupport) noexcept; @@ -69,6 +70,7 @@ class HelperAnalyses { // NOLINT(cppcoreguidelines-special-member-functions) bool AllowLazyPTS{}; // ICF + std::optional PrecomputedCG; std::vector EntryPoints; CallGraphAnalysisType CGTy{}; Soundness SoundnessLevel{}; diff --git a/include/phasar/PhasarLLVM/HelperAnalysisConfig.h b/include/phasar/PhasarLLVM/HelperAnalysisConfig.h index d35195e763..21b52a958f 100644 --- a/include/phasar/PhasarLLVM/HelperAnalysisConfig.h +++ b/include/phasar/PhasarLLVM/HelperAnalysisConfig.h @@ -21,6 +21,7 @@ namespace psr { struct HelperAnalysisConfig { std::optional PrecomputedPTS = std::nullopt; + std::optional PrecomputedCG = std::nullopt; AliasAnalysisType PTATy = AliasAnalysisType::CFLAnders; CallGraphAnalysisType CGTy = CallGraphAnalysisType::OTF; Soundness SoundnessLevel = Soundness::Soundy; diff --git a/lib/Controller/AnalysisController.cpp b/lib/Controller/AnalysisController.cpp index 5e9d1413d4..c3ad51db1b 100644 --- a/lib/Controller/AnalysisController.cpp +++ b/lib/Controller/AnalysisController.cpp @@ -9,6 +9,7 @@ #include "phasar/Controller/AnalysisController.h" +#include "phasar//Utils/NlohmannLogging.h" #include "phasar/AnalysisStrategy/Strategies.h" #include "phasar/Controller/AnalysisControllerEmitterOptions.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" @@ -184,6 +185,10 @@ void AnalysisController::emitRequestedHelperAnalysisResults() { WithResultFileOrStdout("/psr-cg.txt", [this](auto &OS) { HA.getICFG().print(OS); }); } + if (EmitterOptions & AnalysisControllerEmitterOptions::EmitCGAsJson) { + WithResultFileOrStdout( + "/psr-cg.json", [this](auto &OS) { OS << HA.getICFG().getAsJson(); }); + } if (EmitterOptions & (AnalysisControllerEmitterOptions::EmitStatisticsAsJson | diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp index 974988ae6d..eac1f5a2ce 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp @@ -366,9 +366,8 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, llvm::ArrayRef EntryPoints, LLVMTypeHierarchy *TH, LLVMAliasInfoRef PT, Soundness S, bool IncludeGlobals) - : TH(TH) { + : IRDB(IRDB), TH(TH) { assert(IRDB != nullptr); - this->IRDB = IRDB; Builder B{IRDB, this, PT}; LLVMAliasInfo PTOwn; @@ -398,6 +397,52 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, << std::chrono::steady_clock::now().time_since_epoch().count()); } +LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, + const nlohmann::json &SerializedCG) + : IRDB(IRDB) { + assert(IRDB != nullptr); + + PHASAR_LOG_LEVEL_CAT(DEBUG, "LLVMBasedICFG", + "Load precomputed call-graph from JSON"); + + auto It = SerializedCG.find(PhasarConfig::JsonCallGraphID().str()); + + if (It == SerializedCG.end()) { + PHASAR_LOG_LEVEL_CAT(ERROR, "LLVMBasedICFG", + "Cannot deserialize call-graph from JSON: No key '" + << PhasarConfig::JsonCallGraphID() << "' present"); + return; + } + + const auto &Edges = It.value(); + + CallersOf.reserve(Edges.size()); + CalleesAt.reserve(Edges.size()); + VertexFunctions.reserve(Edges.size()); + + for (const auto &[FunName, CallerIDs] : Edges.items()) { + const auto *Fun = IRDB->getFunction(FunName); + if (!Fun) { + PHASAR_LOG_LEVEL_CAT(WARNING, "LLVMBasedICFG", + "Invalid function name: " << FunName); + continue; + } + auto *CEdges = addFunctionVertex(Fun); + CEdges->reserve(CallerIDs.size()); + + for (const auto &JId : CallerIDs) { + auto Id = JId.get(); + const auto *CS = IRDB->getInstruction(Id); + if (!CS) { + PHASAR_LOG_LEVEL_CAT(WARNING, "LLVMBasedICFG", + "Invalid CAll-Instruction Id: " << Id); + } + + addCallEdge(CS, Fun); + } + } +} + LLVMBasedICFG::~LLVMBasedICFG() = default; [[nodiscard]] FunctionRange LLVMBasedICFG::getAllFunctionsImpl() const { @@ -504,22 +549,12 @@ void LLVMBasedICFG::printImpl(llvm::raw_ostream &OS) const { [[nodiscard]] nlohmann::json LLVMBasedICFG::getAsJsonImpl() const { nlohmann::json J; - for (size_t Vtx = 0, VtxEnd = VertexFunctions.size(); Vtx != VtxEnd; ++Vtx) { - auto VtxFunName = VertexFunctions[Vtx]->getName().str(); - J[PhasarConfig::JsonCallGraphID().str()][VtxFunName] = - nlohmann::json::array(); - - for (const auto &Inst : llvm::instructions(VertexFunctions[Vtx])) { - if (!llvm::isa(Inst)) { - continue; - } + auto &Edges = J[PhasarConfig::JsonCallGraphID().str()]; + for (const auto &[Fun, Callers] : CallersOf) { + auto &JCallers = Edges[Fun->getName().str()]; - if (auto It = CalleesAt.find(&Inst); It != CalleesAt.end()) { - for (const auto *Succ : *It->second) { - J[PhasarConfig::JsonCallGraphID().str()][VtxFunName].push_back( - Succ->getName().str()); - } - } + for (const auto *CS : *Callers) { + JCallers.push_back(IRDB->getInstructionId(CS)); } } diff --git a/lib/PhasarLLVM/DB/LLVMProjectIRDB.cpp b/lib/PhasarLLVM/DB/LLVMProjectIRDB.cpp index 17f05d6c81..d06b52acc0 100644 --- a/lib/PhasarLLVM/DB/LLVMProjectIRDB.cpp +++ b/lib/PhasarLLVM/DB/LLVMProjectIRDB.cpp @@ -260,5 +260,5 @@ const llvm::Value *psr::fromMetaDataId(const LLVMProjectIRDB &IRDB, } auto IdNr = ParseInt(Id); - return IdNr ? IRDB.getInstruction(*IdNr) : nullptr; + return IdNr ? IRDB.getValueFromId(*IdNr) : nullptr; } diff --git a/lib/PhasarLLVM/HelperAnalyses.cpp b/lib/PhasarLLVM/HelperAnalyses.cpp index 42700df9d4..e0d13925e0 100644 --- a/lib/PhasarLLVM/HelperAnalyses.cpp +++ b/lib/PhasarLLVM/HelperAnalyses.cpp @@ -13,11 +13,13 @@ HelperAnalyses::HelperAnalyses(std::string IRFile, std::optional PrecomputedPTS, AliasAnalysisType PTATy, bool AllowLazyPTS, std::vector EntryPoints, + std::optional PrecomputedCG, CallGraphAnalysisType CGTy, Soundness SoundnessLevel, bool AutoGlobalSupport) noexcept : IRFile(std::move(IRFile)), PrecomputedPTS(std::move(PrecomputedPTS)), PTATy(PTATy), AllowLazyPTS(AllowLazyPTS), + PrecomputedCG(std::move(PrecomputedCG)), EntryPoints(std::move(EntryPoints)), CGTy(CGTy), SoundnessLevel(SoundnessLevel), AutoGlobalSupport(AutoGlobalSupport) {} @@ -26,8 +28,10 @@ HelperAnalyses::HelperAnalyses(std::string IRFile, HelperAnalysisConfig Config) noexcept : IRFile(std::move(IRFile)), PrecomputedPTS(std::move(Config.PrecomputedPTS)), PTATy(Config.PTATy), - AllowLazyPTS(Config.AllowLazyPTS), EntryPoints(std::move(EntryPoints)), - CGTy(Config.CGTy), SoundnessLevel(Config.SoundnessLevel), + AllowLazyPTS(Config.AllowLazyPTS), + PrecomputedCG(std::move(Config.PrecomputedCG)), + EntryPoints(std::move(EntryPoints)), CGTy(Config.CGTy), + SoundnessLevel(Config.SoundnessLevel), AutoGlobalSupport(Config.AutoGlobalSupport) {} HelperAnalyses::HelperAnalyses(const llvm::Twine &IRFile, @@ -70,10 +74,14 @@ LLVMTypeHierarchy &HelperAnalyses::getTypeHierarchy() { LLVMBasedICFG &HelperAnalyses::getICFG() { if (!ICF) { - ICF = std::make_unique( - &getProjectIRDB(), CGTy, std::move(EntryPoints), &getTypeHierarchy(), - CGTy == CallGraphAnalysisType::OTF ? &getAliasInfo() : nullptr, - SoundnessLevel, AutoGlobalSupport); + if (PrecomputedCG.has_value()) { + ICF = std::make_unique(&getProjectIRDB(), *PrecomputedCG); + } else { + ICF = std::make_unique( + &getProjectIRDB(), CGTy, std::move(EntryPoints), &getTypeHierarchy(), + CGTy == CallGraphAnalysisType::OTF ? &getAliasInfo() : nullptr, + SoundnessLevel, AutoGlobalSupport); + } } return *ICF; diff --git a/lib/PhasarLLVM/Pointer/LLVMAliasSet.cpp b/lib/PhasarLLVM/Pointer/LLVMAliasSet.cpp index 2e7badc4da..b50121f618 100644 --- a/lib/PhasarLLVM/Pointer/LLVMAliasSet.cpp +++ b/lib/PhasarLLVM/Pointer/LLVMAliasSet.cpp @@ -100,7 +100,8 @@ LLVMAliasSet::LLVMAliasSet(LLVMProjectIRDB *IRDB, assert(IRDB != nullptr); // Assume, we already have validated the json schema - llvm::outs() << "Load precomputed points-to info from JSON\n"; + PHASAR_LOG_LEVEL_CAT(DEBUG, "LLVMAliasSet", + "Load precomputed points-to info from JSON"); const auto &Sets = SerializedPTS.at("AliasSets"); assert(Sets.is_array()); diff --git a/tools/phasar-cli/phasar-cli.cpp b/tools/phasar-cli/phasar-cli.cpp index 1ac2672d70..ef221b8cd3 100644 --- a/tools/phasar-cli/phasar-cli.cpp +++ b/tools/phasar-cli/phasar-cli.cpp @@ -169,7 +169,6 @@ cl::opt cl::opt ProjectIdOpt("project-id", cl::desc("Project id used for output"), - cl::init("default-phasar-project"), cl::cat(PsrCat), cl::Hidden); PSR_SHORTLONG_OPTION(OutDirOpt, std::string, "O", "out", @@ -233,6 +232,12 @@ cl::opt "via emit-pta-as-json from the given file"), cl::cat(PsrCat)); +cl::opt LoadCGFromJsonOpt( + "load-cg-from-json", + cl::desc("Load the persisted call-graph previously exported via " + "emit-cg-as-json from the given file"), + cl::cat(PsrCat)); + PSR_SHORTLONG_OPTION(PammOutOpt, std::string, "A", "pamm-out", "Filename for PAMM's gathered data", cl::init("PAMM_data.json"), cl::cat(PsrCat), cl::Hidden); @@ -344,6 +349,15 @@ int main(int Argc, const char **Argv) { return 1; } + if (ProjectIdOpt.empty()) { + ProjectIdOpt = std::filesystem::path(ModuleOpt.getValue()) + .filename() + .replace_extension(); + if (ProjectIdOpt.empty()) { + ProjectIdOpt = "default-phasar-project"; + } + } + validateParamModule(); validateParamOutput(); validateParamPointerAnalysis(); @@ -385,6 +399,15 @@ int main(int Argc, const char **Argv) { if (EmitCGAsDotOpt) { EmitterOptions |= AnalysisControllerEmitterOptions::EmitCGAsDot; } + if (EmitCGAsJsonOpt) { + EmitterOptions |= AnalysisControllerEmitterOptions::EmitCGAsJson; + } + if (EmitCGAsTextOpt) { + llvm::errs() + << "ERROR: emit-cg-as-text is currently not supported. Did you mean " + "emit-cg-as-dot? For reversible serialization use emit-cg-as-json\n"; + return 1; + } if (EmitPTAAsTextOpt) { EmitterOptions |= AnalysisControllerEmitterOptions::EmitPTAAsText; } @@ -410,9 +433,16 @@ int main(int Argc, const char **Argv) { std::optional PrecomputedAliasSet; if (!LoadPTAFromJsonOpt.empty()) { + PHASAR_LOG_LEVEL(INFO, "Load AliasInfo from file: " << LoadCGFromJsonOpt); PrecomputedAliasSet = readJsonFile(LoadPTAFromJsonOpt); } + std::optional PrecomputedCallGraph; + if (!LoadCGFromJsonOpt.empty()) { + PHASAR_LOG_LEVEL(INFO, "Load CallGraph from file: " << LoadCGFromJsonOpt); + PrecomputedCallGraph = readJsonFile(LoadCGFromJsonOpt); + } + if (EntryOpt.empty()) { EntryOpt.push_back("main"); } @@ -421,7 +451,8 @@ int main(int Argc, const char **Argv) { HelperAnalyses HA(std::move(ModuleOpt.getValue()), std::move(PrecomputedAliasSet), AliasTypeOpt, !AnalysisController::needsToEmitPTA(EmitterOptions), - EntryOpt, CGTypeOpt, SoundnessOpt, AutoGlobalsOpt); + EntryOpt, std::move(PrecomputedCallGraph), CGTypeOpt, + SoundnessOpt, AutoGlobalsOpt); AnalysisController Controller( HA, DataFlowAnalysisOpt, {AnalysisConfigOpt.getValue()}, EntryOpt, diff --git a/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt b/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt index a7d45fd5fa..04cb11d947 100644 --- a/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt +++ b/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt @@ -9,6 +9,7 @@ set(ControlFlowSources LLVMBasedBackwardICFGTest.cpp LLVMBasedICFGExportTest.cpp LLVMBasedICFGGlobCtorDtorTest.cpp + LLVMBasedICFGSerializationTest.cpp ) foreach(TEST_SRC ${ControlFlowSources}) diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGSerializationTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGSerializationTest.cpp new file mode 100644 index 0000000000..ec1d476da0 --- /dev/null +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGSerializationTest.cpp @@ -0,0 +1,155 @@ + +#include "phasar/ControlFlow/CallGraphAnalysisType.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" + +#include "TestConfig.h" +#include "gtest/gtest.h" + +class LLVMBasedICFGGSerializationTest : public ::testing::Test { +protected: + static constexpr auto PathToLLFiles = PHASAR_BUILD_SUBFOLDER("call_graphs/"); + + void serAndDeser(const llvm::Twine &IRFile) { + using namespace std::string_literals; + + psr::LLVMProjectIRDB IRDB(PathToLLFiles + IRFile); + + psr::LLVMBasedICFG ICF(&IRDB, psr::CallGraphAnalysisType::OTF, {"main"s}); + auto Ser = ICF.getAsJson(); + + psr::LLVMBasedICFG Deser(&IRDB, Ser); + + compareResults(ICF, Deser); + } + + void compareResults(const psr::LLVMBasedICFG &Orig, + const psr::LLVMBasedICFG &Deser) { + EXPECT_EQ(Orig.getAllVertexFunctions().size(), + Deser.getAllVertexFunctions().size()); + + { + llvm::DenseSet DeserFuns( + Deser.getAllVertexFunctions().begin(), + Deser.getAllVertexFunctions().end()); + for (const auto *Fun : Orig.getAllVertexFunctions()) { + EXPECT_TRUE(DeserFuns.contains(Fun)) + << "Deserialized ICFG does not contain vertex function " + << Fun->getName().str(); + } + } + + for (const auto *Fun : Orig.getAllVertexFunctions()) { + + const auto &Calls = Orig.getCallsFromWithin(Fun); + + for (const auto *CS : Calls) { + llvm::DenseSet DeserCallees( + Deser.getCalleesOfCallAt(CS).begin(), + Deser.getCalleesOfCallAt(CS).end()); + EXPECT_EQ(Orig.getCalleesOfCallAt(CS).size(), DeserCallees.size()); + + for (const auto *OrigCallee : Orig.getCalleesOfCallAt(CS)) { + EXPECT_TRUE(DeserCallees.contains(OrigCallee)) + << "Deserialized ICFG does not contain call to " + << OrigCallee->getName().str() << " from " + << psr::llvmIRToString(CS); + } + } + } + } +}; + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG01) { + serAndDeser("static_callsite_1_c.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG02) { + serAndDeser("static_callsite_2_c.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG03) { + serAndDeser("static_callsite_3_c.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG04) { + serAndDeser("static_callsite_4_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG05) { + serAndDeser("static_callsite_5_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG06) { + serAndDeser("static_callsite_6_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG07) { + serAndDeser("static_callsite_7_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG08) { + serAndDeser("static_callsite_8_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG09) { + serAndDeser("static_callsite_9_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG10) { + serAndDeser("static_callsite_10_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG11) { + serAndDeser("static_callsite_11_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG12) { + serAndDeser("static_callsite_12_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFG13) { + serAndDeser("static_callsite_13_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFGV1) { + serAndDeser("virtual_call_1_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFGV2) { + serAndDeser("virtual_call_2_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFGV3) { + serAndDeser("virtual_call_3_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFGV4) { + serAndDeser("virtual_call_4_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFGV5) { + serAndDeser("virtual_call_5_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFGV6) { + serAndDeser("virtual_call_6_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFGV7) { + serAndDeser("virtual_call_7_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFGV8) { + serAndDeser("virtual_call_8_cpp.ll"); +} + +TEST_F(LLVMBasedICFGGSerializationTest, SerICFGV9) { + serAndDeser("virtual_call_9_cpp.ll"); +} + +int main(int Argc, char **Argv) { + ::testing::InitGoogleTest(&Argc, Argv); + return RUN_ALL_TESTS(); +}