From 8e146d8f3f4bbeba50fdc125dc6587924c11efe2 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 3 Apr 2025 09:57:04 +0200 Subject: [PATCH 001/125] Typo --- Passes/ContractVerifierPostCall.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Passes/ContractVerifierPostCall.cpp b/Passes/ContractVerifierPostCall.cpp index 0794d0d..9ea741d 100644 --- a/Passes/ContractVerifierPostCall.cpp +++ b/Passes/ContractVerifierPostCall.cpp @@ -32,7 +32,7 @@ PreservedAnalyses ContractVerifierPostCallPass::run(Module &M, for (ContractManagerAnalysis::LinearizedContract const& C : DB.LinearizedContracts) { for (std::shared_ptr const& Expr : C.Post) { if (*Expr->Status != Fulfillment::UNKNOWN) continue; - // Contract has a precondition + // Contract has a postcondition std::string err; CallStatus result; switch (Expr->OP->type()) { From d80a594da4560f57921a3b221692f7088e5aa992 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 3 Apr 2025 14:52:07 +0200 Subject: [PATCH 002/125] Add .vscode to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 570fd59..9226101 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ install/ .cache/ compile_commands.json .antlr/ +.vscode/ From 42f8710ec410a069ee14d535c827b20939d38c43 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 3 Apr 2025 15:17:16 +0200 Subject: [PATCH 003/125] Test param error impl --- CMakeLists.txt | 3 +- Grammars/ContractLexer.g4 | 8 ++ Grammars/ContractParser.g4 | 9 +- Include/ContractTree.hpp | 11 ++- Include/Contracts.h | 32 +++++++ LangCode/ContractDataVisitor.cpp | 15 ++++ LangCode/ContractDataVisitor.hpp | 1 + Passes/ContractManager.cpp | 10 +++ Passes/ContractManager.hpp | 2 + Passes/ContractVerifierParam.cpp | 139 +++++++++++++++++++++++++++++++ Passes/ContractVerifierParam.hpp | 19 +++++ Passes/Registrar.cpp | 5 ++ Scripts/clangContracts.cpp | 2 +- 13 files changed, 249 insertions(+), 7 deletions(-) create mode 100644 Passes/ContractVerifierParam.cpp create mode 100644 Passes/ContractVerifierParam.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fe856ad..76c2860 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,8 +80,9 @@ add_llvm_pass_plugin(CoVerPlugin Passes/ContractVerifierPreCall.cpp Passes/ContractVerifierPostCall.cpp Passes/ContractVerifierRelease.cpp - Passes/Instrument.cpp + Passes/ContractVerifierParam.cpp Passes/ContractPostProcess.cpp + Passes/Instrument.cpp Utils/ContractPassUtility.cpp Include/ContractPassUtility.hpp ) diff --git a/Grammars/ContractLexer.g4 b/Grammars/ContractLexer.g4 index e2efa00..609b42e 100644 --- a/Grammars/ContractLexer.g4 +++ b/Grammars/ContractLexer.g4 @@ -7,6 +7,7 @@ ContractMarkerExpFail: 'CONTRACTXFAIL'; ContractMarkerExpSucc: 'CONTRACTXSUCC'; PreMarker: 'PRE'; PostMarker: 'POST'; +ParamMarker: 'PARAM'; TagMarker: 'TAGS'; MsgMarker: 'MSG'; ScopePrefix: '{'; @@ -34,5 +35,12 @@ OPCall: 'call!'; OPCallTag: 'call_tag!'; OPRelease1: 'no!'; OPRelease2: 'until!'; +OPParam: 'param!'; OPPrefix: '('; OPPostfix: ')'; + +ParamForbidEq: '!='; +ParamGt: '>'; +ParamGtEq: '>='; +ParamLt: '<'; +ParamLtEq: '<='; diff --git a/Grammars/ContractParser.g4 b/Grammars/ContractParser.g4 index 2a82e31..b94e531 100644 --- a/Grammars/ContractParser.g4 +++ b/Grammars/ContractParser.g4 @@ -15,13 +15,14 @@ functags: TagMarker ScopePrefix tagUnit (ListSep tagUnit)* ScopePostfix; tagUnit: Variable (OPPrefix NatNum OPPostfix)?; -expression: primitive | composite; +expression: callOp | releaseOp | paramOp; -primitive: readOp | writeOp | callOp; readOp: OPRead OPPrefix (Deref | AddrOf)? NatNum OPPostfix; writeOp: OPWrite OPPrefix (Deref | AddrOf)? NatNum OPPostfix; varMap: (callP=NatNum | TagParam) MapSep (Deref | AddrOf)? contrP=NatNum; callOp: (OPCall | OPCallTag) OPPrefix Variable (ListSep varMap)* OPPostfix; +paramOp: OPParam OPPrefix NatNum MapSep paramReq (ListSep paramReq)* OPPostfix; +paramReq: (ParamForbidEq | ParamGt | ParamGtEq | ParamLt | ParamLtEq) Variable; -composite: releaseOp; -releaseOp: OPRelease1 OPPrefix forbidden=primitive OPPostfix OPRelease2 OPPrefix until=callOp OPPostfix; +relForbidden: readOp | writeOp | callOp; +releaseOp: OPRelease1 OPPrefix forbidden=relForbidden OPPostfix OPRelease2 OPPrefix until=callOp OPPostfix; diff --git a/Include/ContractTree.hpp b/Include/ContractTree.hpp index 47b0579..85b2528 100644 --- a/Include/ContractTree.hpp +++ b/Include/ContractTree.hpp @@ -14,7 +14,7 @@ #include namespace ContractTree { - enum struct OperationType { READ, WRITE, CALL, CALLTAG, RELEASE }; + enum struct OperationType { READ, WRITE, CALL, CALLTAG, RELEASE, PARAM }; enum struct ParamAccess { NORMAL, DEREF, ADDROF }; struct Operation { virtual ~Operation() = default; @@ -34,6 +34,15 @@ namespace ContractTree { WriteOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; virtual const OperationType type() const override { return OperationType::WRITE; }; }; + enum Comparator { + NEQ, GT, GTEQ, LT, LTEQ + }; + struct ParamOperation : Operation { + ParamOperation(int _idx, std::vector> _reqs) : idx{_idx}, reqs{_reqs} {}; + const int idx; + const std::vector> reqs; + virtual const OperationType type() const override { return OperationType::PARAM; }; + }; struct CallParam { int callP; bool callPisTagVar; diff --git a/Include/Contracts.h b/Include/Contracts.h index 5a5d4cd..b973fc8 100644 --- a/Include/Contracts.h +++ b/Include/Contracts.h @@ -1,5 +1,37 @@ #pragma once +#include +#include + +// Convenience Annotation Macros +// Example: int f() CONTRACT( ); #define CONTRACT(...) __attribute__((annotate("CONTRACT{" #__VA_ARGS__ "}"))) #define CONTRACTXF(...) __attribute__((annotate("CONTRACTXFAIL{" #__VA_ARGS__ "}"))) #define CONTRACTXS(...) __attribute__((annotate("CONTRACTXSUCC{" #__VA_ARGS__ "}"))) + +// Contract Value Names Definitions + +// DO NOT USE THE STRUCT DEFINITIONS DIRECTLY! +// Use macros CONTRACT_VALUE_PAIR and CONTRACT_PTR_PAIR instead +struct ContractValuePair { + const char* name; + int64_t value; +} __attribute__((packed)) typedef ContractValuePair_t; +struct ContractPtrPair { + const char* name; + void* ptr; +} __attribute__((packed)) typedef ContractPtrPair_t; + + +#define CONCAT_IMPL( x, y ) x##y +#define MACRO_CONCAT( x, y ) CONCAT_IMPL( x, y ) + +// Define a name for a constant value +// Example: CONTRACT_VALUE_PAIR(zero,0) +#define CONTRACT_VALUE_PAIR(x,y) \ + ContractValuePair_t MACRO_CONCAT(ContractValueInfo_, __COUNTER__ ) __attribute__((used)) = {#x, (int64_t)y}; + +// Define a name for a variable address +// Example: CONTRACT_PTR_PAIR(myvar,myvar) where myvar is a variable +#define CONTRACT_PTR_PAIR(x,y) \ + ContractPtrPair_t MACRO_CONCAT(ContractPtrInfo_, __COUNTER__ ) __attribute__((used)) = {#x, &y}; diff --git a/LangCode/ContractDataVisitor.cpp b/LangCode/ContractDataVisitor.cpp index 5d4eba8..7f3f394 100644 --- a/LangCode/ContractDataVisitor.cpp +++ b/LangCode/ContractDataVisitor.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include using namespace ContractTree; @@ -86,6 +87,20 @@ std::any ContractDataVisitor::visitWriteOp(ContractParser::WriteOpContext *ctx) std::shared_ptr op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); return op; } +std::any ContractDataVisitor::visitParamOp(ContractParser::ParamOpContext *ctx) { + std::vector> reqs; + for (ContractParser::ParamReqContext* req : ctx->paramReq()) { + Comparator comp; + if (req->ParamForbidEq()) comp = Comparator::NEQ; + if (req->ParamGt()) comp = Comparator::GT; + if (req->ParamGtEq()) comp = Comparator::GTEQ; + if (req->ParamLt()) comp = Comparator::LT; + if (req->ParamLtEq()) comp = Comparator::LTEQ; + reqs.push_back({comp, req->Variable()->getText()}); + } + std::shared_ptr op = std::make_shared(std::stoi(ctx->NatNum()->getText()), reqs); + return op; +} std::any ContractDataVisitor::visitCallOp(ContractParser::CallOpContext *ctx) { std::vector params; for (ContractParser::VarMapContext* param : ctx->varMap()) { diff --git a/LangCode/ContractDataVisitor.hpp b/LangCode/ContractDataVisitor.hpp index a1c107a..f4af1ef 100644 --- a/LangCode/ContractDataVisitor.hpp +++ b/LangCode/ContractDataVisitor.hpp @@ -13,6 +13,7 @@ class ContractDataVisitor : public ContractParserBaseVisitor { std::any visitExpression(ContractParser::ExpressionContext *ctx) override; std::any visitReadOp(ContractParser::ReadOpContext *ctx) override; std::any visitWriteOp(ContractParser::WriteOpContext *ctx) override; + std::any visitParamOp(ContractParser::ParamOpContext *ctx) override; std::any visitCallOp(ContractParser::CallOpContext *ctx) override; std::any visitReleaseOp(ContractParser::ReleaseOpContext *ctx) override; diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index 3c988d5..e34823d 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -51,6 +51,16 @@ ContractManagerAnalysis::ContractDatabase ContractManagerAnalysis::run(Module &M extractFromAnnotations(M); extractFromFunction(M); + // Annotations done, now add value pairs to database + for (GlobalVariable& GV : M.globals()) { + if (GV.getName().starts_with("ContractValueInfo_") || GV.getName().starts_with("ContractPtrInfo_")) { + Constant* data = GV.getInitializer(); + StringRef name = dyn_cast(dyn_cast(data->getOperand(0))->getInitializer())->getAsCString(); + Value* val = data->getOperand(1); + curDatabase.ContractVariableData[name.str()] = { val, GV.getName().starts_with("ContractPtrInfo_") }; + } + } + std::stringstream s; s << "CoVer: Parsed contracts after " << std::fixed << std::chrono::duration(std::chrono::system_clock::now() - curDatabase.start_time).count() << "s\n"; errs() << s.str(); diff --git a/Passes/ContractManager.hpp b/Passes/ContractManager.hpp index d55f22d..cde74e6 100644 --- a/Passes/ContractManager.hpp +++ b/Passes/ContractManager.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "ContractTree.hpp" @@ -39,6 +40,7 @@ class ContractManagerAnalysis : public AnalysisInfoMixin Contracts; // For postprocessing only std::vector LinearizedContracts; // For verification passes std::map> Tags; + std::map> ContractVariableData; std::chrono::time_point start_time; bool allowMultiReports = false; Json::Value processedReports; diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp new file mode 100644 index 0000000..34bafbd --- /dev/null +++ b/Passes/ContractVerifierParam.cpp @@ -0,0 +1,139 @@ +#include "ContractVerifierParam.hpp" +#include "ContractManager.hpp" +#include "ContractTree.hpp" +#include "ContractPassUtility.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace llvm; +using namespace ContractTree; + +PreservedAnalyses ContractVerifierParamPass::run(Module &M, + ModuleAnalysisManager &AM) { + ContractManagerAnalysis::ContractDatabase DB = AM.getResult(M); + MAM = &AM; + for (ContractManagerAnalysis::LinearizedContract const& C : DB.LinearizedContracts) { + for (const std::shared_ptr Expr : C.Pre) { + if (*Expr->Status != Fulfillment::UNKNOWN) continue; + // Contract has a precondition + std::string err; + if (Expr->OP->type() != OperationType::PARAM) continue; + const ParamOperation* ParamOp = dynamic_cast(Expr->OP.get()); + C.DebugInfo->push_back("[ContractVerifierParam] Attempting to verify expression: " + Expr->ExprStr); + for (std::pair req : ParamOp->reqs) { + // First, check against value database + if (!DB.ContractVariableData.contains(req.second)) { + errs() << "Undefined contract value identifier \"" << req.second << "\"!\n"; + errs() << "Requirement will not be analysed!\n"; + errs() << DB.ContractVariableData.begin()->first << " " << req.second << "\n"; + errs() << DB.ContractVariableData.begin()->first.length() << " " << req.second.length() << "\n"; + continue; + } + // Perform the check on each callsite + for (const User* U : C.F->users()) { + if (const CallBase* CB = dyn_cast(U)) { + std::string errInfo = ""; + Fulfillment f = checkParamReq(DB.ContractVariableData[req.second].first, CB->getArgOperand(ParamOp->idx), req.first, DB.ContractVariableData[req.second].second, errInfo); + if (f == Fulfillment::BROKEN) { + *Expr->Status = Fulfillment::BROKEN; + if (!errInfo.empty()) { + Expr->ErrorInfo->push_back({ + .error_id = "Param", + .text = errInfo + std::format("\nParameter Index: {:d}\nContract Value Name: {:s}", ParamOp->idx, req.second), + .references = {ContractPassUtility::getFileReference(CB)}, + }); + } + } + } + } + } + if (*Expr->Status == Fulfillment::UNKNOWN) *Expr->Status = Fulfillment::FULFILLED; + } + } + + return PreservedAnalyses::all(); +} + +std::string createCompErr(const Comparator comp, const ConstantInt* callCI, const ConstantInt* valueCI) { + SmallString<10> tmpCs; + callCI->getValue().toStringSigned(tmpCs); + std::string callCs = tmpCs.c_str(); + tmpCs = ""; + valueCI->getValue().toStringSigned(tmpCs); + std::string valueCs = tmpCs.c_str(); + switch (comp) { + case Comparator::NEQ: + return "Parameter matches forbidden value (" + callCs + ")!"; + case Comparator::GT: + return "Call parameter value (" + callCs + ") not greater than contract value (" + valueCs + ")!"; + case Comparator::GTEQ: + return "Call parameter value (" + callCs + ") not greater or equal to contract value (" + valueCs + ")!"; + case Comparator::LT: + return "Call parameter value (" + callCs + ") not less than contract value (" + valueCs + ")!"; + case Comparator::LTEQ: + return "Call parameter value (" + callCs + ") not less or equal to contract value (" + valueCs + ")!"; + } +} + +Fulfillment compareCI(const ConstantInt* CI, const ConstantInt* CI2, Comparator comp) { + switch (comp) { + case Comparator::NEQ: + return !APInt::isSameValue(CI->getValue(), CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; + case Comparator::GTEQ: + return CI->getValue().sge(CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; + case Comparator::GT: + return CI->getValue().sgt(CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; + case Comparator::LTEQ: + return CI->getValue().sle(CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; + case Comparator::LT: + return CI->getValue().slt(CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; + } +} + +Fulfillment ContractVerifierParamPass::checkParamReq(const Value* var, const Value* callVal, Comparator comp, bool isPtr, std::string& ErrInfo) { + if (comp == Comparator::NEQ && isPtr) { + if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { + ErrInfo = "Parameter matches or is alias to forbidden pointer value!"; + return Fulfillment::BROKEN; + } + #warning TODO maybe unknown? Depends on analysis confidence + return Fulfillment::FULFILLED; + } else if (comp != Comparator::NEQ && isPtr) { + errs() << "Attempt to compare pointers! Not performing analysis\n"; + return Fulfillment::UNKNOWN; + } + // Ensured that !isPtr, can get constinfo if present + if (const ConstantInt* callCI = dyn_cast(callVal)) { + if (const ConstantInt* varCI = dyn_cast(var)) { + Fulfillment f = compareCI(callCI, varCI, comp); + if (f == Fulfillment::BROKEN) { + ErrInfo = createCompErr(comp, callCI, varCI); + } + return f; + } + } + + return Fulfillment::UNKNOWN; +} diff --git a/Passes/ContractVerifierParam.hpp b/Passes/ContractVerifierParam.hpp new file mode 100644 index 0000000..b749203 --- /dev/null +++ b/Passes/ContractVerifierParam.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "ContractTree.hpp" +#include "llvm/IR/PassManager.h" +#include + +namespace llvm { + +class ContractVerifierParamPass : public PassInfoMixin { + public: + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + + private: + ModuleAnalysisManager* MAM; + + ContractTree::Fulfillment checkParamReq(const Value* var, const Value* contrVal, ContractTree::Comparator comp, bool isPtr, std::string& ErrInfo); +}; + +} // namespace llvm diff --git a/Passes/Registrar.cpp b/Passes/Registrar.cpp index f4fa132..24283b9 100644 --- a/Passes/Registrar.cpp +++ b/Passes/Registrar.cpp @@ -7,6 +7,7 @@ #include "ContractVerifierPreCall.hpp" #include "ContractVerifierPostCall.hpp" #include "ContractVerifierRelease.hpp" +#include "ContractVerifierParam.hpp" #include "ContractPostProcess.hpp" #include "Instrument.hpp" @@ -26,6 +27,10 @@ namespace { MPM.addPass(ContractVerifierReleasePass()); return true; } + if (Name == "contractVerifierParam") { + MPM.addPass(ContractVerifierParamPass()); + return true; + } if (Name == "contractPostProcess") { MPM.addPass(ContractPostProcessingPass()); return true; diff --git a/Scripts/clangContracts.cpp b/Scripts/clangContracts.cpp index 045c5f0..7609e62 100644 --- a/Scripts/clangContracts.cpp +++ b/Scripts/clangContracts.cpp @@ -285,7 +285,7 @@ int main(int argc, const char** argv) { execSafe("llvm-link" + bitcode_files + " -o " + tmpfile); // Call LLVM passes - std::string passlist = "function(sroa),contractVerifierPreCall,contractVerifierPostCall,contractVerifierRelease,contractPostProcess"; + std::string passlist = "function(sroa),contractVerifierPreCall,contractVerifierPostCall,contractVerifierRelease,contractVerifierParam,contractPostProcess"; if (!opt_level.empty()) { passlist += ",default<" + opt_level.substr(1) + ">"; // opt_level substr cuts "-" from "-O" } From 79d1b1f95973461e5f0473f457a2c768cc30ea6f Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 9 Apr 2025 15:35:29 +0200 Subject: [PATCH 004/125] Fix some issues with parameter check pass --- Passes/ContractVerifierParam.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index 34bafbd..ecb13f8 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -42,6 +42,7 @@ PreservedAnalyses ContractVerifierParamPass::run(Module &M, if (Expr->OP->type() != OperationType::PARAM) continue; const ParamOperation* ParamOp = dynamic_cast(Expr->OP.get()); C.DebugInfo->push_back("[ContractVerifierParam] Attempting to verify expression: " + Expr->ExprStr); + Fulfillment resf = Fulfillment::FULFILLED; for (std::pair req : ParamOp->reqs) { // First, check against value database if (!DB.ContractVariableData.contains(req.second)) { @@ -57,7 +58,7 @@ PreservedAnalyses ContractVerifierParamPass::run(Module &M, std::string errInfo = ""; Fulfillment f = checkParamReq(DB.ContractVariableData[req.second].first, CB->getArgOperand(ParamOp->idx), req.first, DB.ContractVariableData[req.second].second, errInfo); if (f == Fulfillment::BROKEN) { - *Expr->Status = Fulfillment::BROKEN; + resf = Fulfillment::BROKEN; if (!errInfo.empty()) { Expr->ErrorInfo->push_back({ .error_id = "Param", @@ -69,7 +70,7 @@ PreservedAnalyses ContractVerifierParamPass::run(Module &M, } } } - if (*Expr->Status == Fulfillment::UNKNOWN) *Expr->Status = Fulfillment::FULFILLED; + *Expr->Status = resf; } } From 6832592e62844b330d5bfe4f9fc2906cd346f908 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 10 Apr 2025 13:52:45 +0200 Subject: [PATCH 005/125] Refine param analysis and add example contracts --- Grammars/ContractLexer.g4 | 1 + Grammars/ContractParser.g4 | 2 +- Include/ContractTree.hpp | 2 +- LangCode/ContractDataVisitor.cpp | 3 +- Passes/ContractVerifierParam.cpp | 64 ++++++++++++++++------- Scripts/gen_mpi_contr_h.py | 90 ++++++++++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 22 deletions(-) diff --git a/Grammars/ContractLexer.g4 b/Grammars/ContractLexer.g4 index 609b42e..a422f37 100644 --- a/Grammars/ContractLexer.g4 +++ b/Grammars/ContractLexer.g4 @@ -44,3 +44,4 @@ ParamGt: '>'; ParamGtEq: '>='; ParamLt: '<'; ParamLtEq: '<='; +ParamEqExcept: '^='; diff --git a/Grammars/ContractParser.g4 b/Grammars/ContractParser.g4 index b94e531..addfd9c 100644 --- a/Grammars/ContractParser.g4 +++ b/Grammars/ContractParser.g4 @@ -22,7 +22,7 @@ writeOp: OPWrite OPPrefix (Deref | AddrOf)? NatNum OPPostfix; varMap: (callP=NatNum | TagParam) MapSep (Deref | AddrOf)? contrP=NatNum; callOp: (OPCall | OPCallTag) OPPrefix Variable (ListSep varMap)* OPPostfix; paramOp: OPParam OPPrefix NatNum MapSep paramReq (ListSep paramReq)* OPPostfix; -paramReq: (ParamForbidEq | ParamGt | ParamGtEq | ParamLt | ParamLtEq) Variable; +paramReq: (ParamEqExcept | ParamForbidEq | ParamGt | ParamGtEq | ParamLt | ParamLtEq) value=(Variable | NatNum); relForbidden: readOp | writeOp | callOp; releaseOp: OPRelease1 OPPrefix forbidden=relForbidden OPPostfix OPRelease2 OPPrefix until=callOp OPPostfix; diff --git a/Include/ContractTree.hpp b/Include/ContractTree.hpp index 85b2528..429d61e 100644 --- a/Include/ContractTree.hpp +++ b/Include/ContractTree.hpp @@ -35,7 +35,7 @@ namespace ContractTree { virtual const OperationType type() const override { return OperationType::WRITE; }; }; enum Comparator { - NEQ, GT, GTEQ, LT, LTEQ + NEQ, GT, GTEQ, LT, LTEQ, EXEQ }; struct ParamOperation : Operation { ParamOperation(int _idx, std::vector> _reqs) : idx{_idx}, reqs{_reqs} {}; diff --git a/LangCode/ContractDataVisitor.cpp b/LangCode/ContractDataVisitor.cpp index 7f3f394..4e7b27a 100644 --- a/LangCode/ContractDataVisitor.cpp +++ b/LangCode/ContractDataVisitor.cpp @@ -96,7 +96,8 @@ std::any ContractDataVisitor::visitParamOp(ContractParser::ParamOpContext *ctx) if (req->ParamGtEq()) comp = Comparator::GTEQ; if (req->ParamLt()) comp = Comparator::LT; if (req->ParamLtEq()) comp = Comparator::LTEQ; - reqs.push_back({comp, req->Variable()->getText()}); + if (req->ParamEqExcept()) comp = Comparator::EXEQ; + reqs.push_back({comp, req->value->getText()}); } std::shared_ptr op = std::make_shared(std::stoi(ctx->NatNum()->getText()), reqs); return op; diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index ecb13f8..129d38c 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include #include @@ -44,32 +46,43 @@ PreservedAnalyses ContractVerifierParamPass::run(Module &M, C.DebugInfo->push_back("[ContractVerifierParam] Attempting to verify expression: " + Expr->ExprStr); Fulfillment resf = Fulfillment::FULFILLED; for (std::pair req : ParamOp->reqs) { - // First, check against value database - if (!DB.ContractVariableData.contains(req.second)) { - errs() << "Undefined contract value identifier \"" << req.second << "\"!\n"; - errs() << "Requirement will not be analysed!\n"; - errs() << DB.ContractVariableData.begin()->first << " " << req.second << "\n"; - errs() << DB.ContractVariableData.begin()->first.length() << " " << req.second.length() << "\n"; - continue; + Value* var; + try { + // First, check if constant value provided + int ivalue = std::stoi(req.second); + var = ConstantInt::get(Type::getInt64Ty(M.getContext()), ivalue); + } catch(std::exception& e) { + // Otherwise, check against value database + if (!DB.ContractVariableData.contains(req.second)) { + errs() << "Undefined non-constint contract value identifier \"" << req.second << "\"!\n"; + errs() << "Requirement will not be analysed!\n"; + continue; + } + var = DB.ContractVariableData[req.second].first; } // Perform the check on each callsite for (const User* U : C.F->users()) { if (const CallBase* CB = dyn_cast(U)) { std::string errInfo = ""; - Fulfillment f = checkParamReq(DB.ContractVariableData[req.second].first, CB->getArgOperand(ParamOp->idx), req.first, DB.ContractVariableData[req.second].second, errInfo); + Fulfillment f = checkParamReq(var, CB->getArgOperand(ParamOp->idx), req.first, DB.ContractVariableData[req.second].second, errInfo); if (f == Fulfillment::BROKEN) { resf = Fulfillment::BROKEN; if (!errInfo.empty()) { Expr->ErrorInfo->push_back({ .error_id = "Param", - .text = errInfo + std::format("\nParameter Index: {:d}\nContract Value Name: {:s}", ParamOp->idx, req.second), + .text = std::format("{:s} Parameter Index: {:d}, Contract Value Name: {:s}", errInfo, ParamOp->idx, req.second), .references = {ContractPassUtility::getFileReference(CB)}, }); } } + if (f == Fulfillment::FULFILLED && req.first == Comparator::EXEQ) { + // Parameter fulfills exception value. Stop checking this parameter + goto exit_param_analysis; + } } } } + exit_param_analysis: *Expr->Status = resf; } } @@ -95,13 +108,15 @@ std::string createCompErr(const Comparator comp, const ConstantInt* callCI, cons return "Call parameter value (" + callCs + ") not less than contract value (" + valueCs + ")!"; case Comparator::LTEQ: return "Call parameter value (" + callCs + ") not less or equal to contract value (" + valueCs + ")!"; + case Comparator::EXEQ: + llvm_unreachable("Exception Equal should not trigger an error!"); } } Fulfillment compareCI(const ConstantInt* CI, const ConstantInt* CI2, Comparator comp) { switch (comp) { case Comparator::NEQ: - return !APInt::isSameValue(CI->getValue(), CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; + return CI->getValue().getSExtValue() != CI2->getValue().getSExtValue() ? Fulfillment::FULFILLED : Fulfillment::BROKEN; case Comparator::GTEQ: return CI->getValue().sge(CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; case Comparator::GT: @@ -110,20 +125,31 @@ Fulfillment compareCI(const ConstantInt* CI, const ConstantInt* CI2, Comparator return CI->getValue().sle(CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; case Comparator::LT: return CI->getValue().slt(CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; + case Comparator::EXEQ: + return CI->getValue().getSExtValue() == CI2->getValue().getSExtValue() ? Fulfillment::FULFILLED : Fulfillment::UNKNOWN; } } Fulfillment ContractVerifierParamPass::checkParamReq(const Value* var, const Value* callVal, Comparator comp, bool isPtr, std::string& ErrInfo) { - if (comp == Comparator::NEQ && isPtr) { - if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { - ErrInfo = "Parameter matches or is alias to forbidden pointer value!"; - return Fulfillment::BROKEN; + if (isPtr) { + switch (comp) { + case Comparator::NEQ: + if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { + ErrInfo = "Parameter matches or is alias to forbidden pointer value!"; + return Fulfillment::BROKEN; + } + return Fulfillment::FULFILLED; + case Comparator::EXEQ: + if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { + ErrInfo = "Note: Parameter matches or is alias to exception value"; + return Fulfillment::FULFILLED; + } + // Not an exception. Continue analysis, so far no info gained + return Fulfillment::UNKNOWN; + default: + errs() << "Attempt to compare pointers! Not performing parameter analysis\n"; + return Fulfillment::UNKNOWN; } - #warning TODO maybe unknown? Depends on analysis confidence - return Fulfillment::FULFILLED; - } else if (comp != Comparator::NEQ && isPtr) { - errs() << "Attempt to compare pointers! Not performing analysis\n"; - return Fulfillment::UNKNOWN; } // Ensured that !isPtr, can get constinfo if present if (const ConstantInt* callCI = dyn_cast(callVal)) { diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 75da942..ad88a37 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -297,6 +297,80 @@ def add_contract(func: str, scope: str, contr: str): for func, tag_idx in tag_typeuse: add_contract(func, "TAGS", f"type_use({tag_idx})") +# Parameter Errors +paramerror_comm = [ + ("MPI_Send", 5), + ("MPI_Recv", 5), + ("MPI_Sendrecv", 10), + ("MPI_Allgather", 6), + ("MPI_Cart_get", 0), +] +for func, idx in paramerror_comm: + add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_COMM_NULL) MSG \"Communicator is invalid\"") + +# MPI_PROC_NULL uses negative special value in OpenMPI, add exception for it +paramerror_rank_send = [ + ("MPI_Send", 3), + ("MPI_Get", 3), + ("MPI_Reduce", 5), +] +for func, idx in paramerror_rank_send: + add_contract(func, "PRE", f"param!({idx}:^=MPI_PROC_NULL,>=0) MSG \"Rank is invalid\"") + +# MPI_ANY_SOURCE uses negative special value in OpenMPI, add exception for it +paramerror_rank_recv = [ + ("MPI_Recv", 3), +] +for func, idx in paramerror_rank_recv: + add_contract(func, "PRE", f"param!({idx}:^=MPI_PROC_NULL,^=MPI_ANY_SOURCE,>=0) MSG \"Rank is invalid\"") + +# MPI_STATUSES_IGNORE == NULL == MPI_STATUS_IGNORE in OpenMPI. Add exception for MPI_STATUS_IGNORE +paramerror_status = [ + ("MPI_Recv", 6) +] +for func, idx in paramerror_status: + add_contract(func, "PRE", f"param!({idx}:^=MPI_STATUS_IGNORE,!=NULL,!=MPI_STATUSES_IGNORE) MSG \"Status is invalid\"") + +paramerror_null = [ + ("MPI_Initialized", 0), + ("MPI_Send", 0), + ("MPI_Recv", 0), + ("MPI_Sendrecv", 0), + ("MPI_Sendrecv", 5), + ("MPI_Win_create", 0), + ("MPI_Win_allocate", 4), + ("MPI_Get", 0), +] +for func, idx in paramerror_null: + add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_IN_PLACE) MSG \"Parameter is null\"") + +paramerror_datatype = [ + ("MPI_Get", 6), + ("MPI_Bcast", 2), +] +for func, idx in paramerror_datatype: + add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_DATATYPE_NULL) MSG \"Data type is invalid\"") + +paramerror_tag_send = [ + ("MPI_Send", 4), + ("MPI_Isend", 4), +] +for func, idx in paramerror_tag_send: + add_contract(func, "PRE", f"param!({idx}:!=MPI_ANY_TAG,>=0) MSG \"Tag is invalid\"") + +paramerror_tag_recv = [ + ("MPI_Recv", 4), + ("MPI_Irecv", 4), +] +for func, idx in paramerror_tag_recv: + add_contract(func, "PRE", f"param!({idx}:^=MPI_ANY_TAG,!=MPI_DATATYPE_NULL) MSG \"Tag is invalid\"") + +paramerror_op = [ + ("MPI_Reduce", 4), +] +for func, idx in paramerror_op: + add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_OP_NULL) MSG \"Operation is invalid\"") + # Output file boilerplate_header_c = f""" // Automatically generated by {os.path.basename(__file__)} @@ -304,11 +378,27 @@ def add_contract(func: str, scope: str, contr: str): // Identifier: {ver_identifier} #pragma once + #include "Contracts.h" #include #define MACRO_SAFETY(x) (x) +// (Constint) Values needed for contracts +CONTRACT_VALUE_PAIR(MPI_PROC_NULL, MPI_PROC_NULL) +CONTRACT_VALUE_PAIR(MPI_ANY_SOURCE, MPI_ANY_SOURCE) +CONTRACT_VALUE_PAIR(MPI_ANY_TAG, MPI_ANY_TAG) + +// (Ptr) Values needed for contracts +CONTRACT_PTR_PAIR(NULL, *NULL) +CONTRACT_PTR_PAIR(MPI_IN_PLACE,*MPI_IN_PLACE) +CONTRACT_PTR_PAIR(MPI_REQUEST_NULL,*MPI_REQUEST_NULL) +CONTRACT_PTR_PAIR(MPI_OP_NULL,*MPI_OP_NULL) +CONTRACT_PTR_PAIR(MPI_COMM_NULL,*MPI_COMM_NULL) +CONTRACT_PTR_PAIR(MPI_STATUSES_IGNORE,*MPI_STATUSES_IGNORE) +CONTRACT_PTR_PAIR(MPI_STATUS_IGNORE,*MPI_STATUS_IGNORE) +CONTRACT_PTR_PAIR(MPI_DATATYPE_NULL,*MPI_DATATYPE_NULL) + """ header_output_c = boilerplate_header_c From 4345ac203afc238240214fc8040f7325e9fb3f1f Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 15 May 2025 15:51:56 +0200 Subject: [PATCH 006/125] Remove weird check --- Scripts/gen_mpi_contr_h.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index ad88a37..c34a99d 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -331,6 +331,7 @@ def add_contract(func: str, scope: str, contr: str): for func, idx in paramerror_status: add_contract(func, "PRE", f"param!({idx}:^=MPI_STATUS_IGNORE,!=NULL,!=MPI_STATUSES_IGNORE) MSG \"Status is invalid\"") +# Buffer should never be null paramerror_null = [ ("MPI_Initialized", 0), ("MPI_Send", 0), @@ -344,6 +345,7 @@ def add_contract(func: str, scope: str, contr: str): for func, idx in paramerror_null: add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_IN_PLACE) MSG \"Parameter is null\"") +# Datatype should not be null when doing communication paramerror_datatype = [ ("MPI_Get", 6), ("MPI_Bcast", 2), @@ -351,6 +353,7 @@ def add_contract(func: str, scope: str, contr: str): for func, idx in paramerror_datatype: add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_DATATYPE_NULL) MSG \"Data type is invalid\"") +# When doing P2P sends, the tag should never be MPI_ANY_TAG paramerror_tag_send = [ ("MPI_Send", 4), ("MPI_Isend", 4), @@ -358,13 +361,7 @@ def add_contract(func: str, scope: str, contr: str): for func, idx in paramerror_tag_send: add_contract(func, "PRE", f"param!({idx}:!=MPI_ANY_TAG,>=0) MSG \"Tag is invalid\"") -paramerror_tag_recv = [ - ("MPI_Recv", 4), - ("MPI_Irecv", 4), -] -for func, idx in paramerror_tag_recv: - add_contract(func, "PRE", f"param!({idx}:^=MPI_ANY_TAG,!=MPI_DATATYPE_NULL) MSG \"Tag is invalid\"") - +# MPI_OP_NULL should not be used for concrete operations paramerror_op = [ ("MPI_Reduce", 4), ] From 0c297c91fee94a4fc0bf513d24d3d40fa4f1a50a Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 16 May 2025 12:33:07 +0200 Subject: [PATCH 007/125] Alloc track --- CMakeLists.txt | 1 + Grammars/ContractLexer.g4 | 1 + Grammars/ContractParser.g4 | 7 +- Include/ContractPassUtility.hpp | 5 ++ Include/ContractTree.hpp | 6 +- LangCode/ContractDataVisitor.cpp | 25 +++--- LangCode/ContractDataVisitor.hpp | 4 +- Passes/ContractVerifierAlloc.cpp | 137 +++++++++++++++++++++++++++++++ Passes/ContractVerifierAlloc.hpp | 37 +++++++++ Passes/Registrar.cpp | 5 ++ Scripts/clangContracts.cpp | 2 +- Scripts/gen_mpi_contr_h.py | 19 +++++ Utils/ContractPassUtility.cpp | 16 ++++ 13 files changed, 242 insertions(+), 23 deletions(-) create mode 100644 Passes/ContractVerifierAlloc.cpp create mode 100644 Passes/ContractVerifierAlloc.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 76c2860..194dfc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,7 @@ add_llvm_pass_plugin(CoVerPlugin Passes/ContractVerifierPostCall.cpp Passes/ContractVerifierRelease.cpp Passes/ContractVerifierParam.cpp + Passes/ContractVerifierAlloc.cpp Passes/ContractPostProcess.cpp Passes/Instrument.cpp Utils/ContractPassUtility.cpp diff --git a/Grammars/ContractLexer.g4 b/Grammars/ContractLexer.g4 index a422f37..29a5c70 100644 --- a/Grammars/ContractLexer.g4 +++ b/Grammars/ContractLexer.g4 @@ -36,6 +36,7 @@ OPCallTag: 'call_tag!'; OPRelease1: 'no!'; OPRelease2: 'until!'; OPParam: 'param!'; +OPAlloc: 'alloc!'; OPPrefix: '('; OPPostfix: ')'; diff --git a/Grammars/ContractParser.g4 b/Grammars/ContractParser.g4 index addfd9c..0e77564 100644 --- a/Grammars/ContractParser.g4 +++ b/Grammars/ContractParser.g4 @@ -15,14 +15,13 @@ functags: TagMarker ScopePrefix tagUnit (ListSep tagUnit)* ScopePostfix; tagUnit: Variable (OPPrefix NatNum OPPostfix)?; -expression: callOp | releaseOp | paramOp; +expression: callOp | releaseOp | paramOp | rwOp; // rwOp only makes sense for alloc though -readOp: OPRead OPPrefix (Deref | AddrOf)? NatNum OPPostfix; -writeOp: OPWrite OPPrefix (Deref | AddrOf)? NatNum OPPostfix; +rwOp: (OPRead | OPWrite | OPAlloc) OPPrefix (Deref | AddrOf)? NatNum OPPostfix; varMap: (callP=NatNum | TagParam) MapSep (Deref | AddrOf)? contrP=NatNum; callOp: (OPCall | OPCallTag) OPPrefix Variable (ListSep varMap)* OPPostfix; paramOp: OPParam OPPrefix NatNum MapSep paramReq (ListSep paramReq)* OPPostfix; paramReq: (ParamEqExcept | ParamForbidEq | ParamGt | ParamGtEq | ParamLt | ParamLtEq) value=(Variable | NatNum); -relForbidden: readOp | writeOp | callOp; +relForbidden: rwOp | callOp; releaseOp: OPRelease1 OPPrefix forbidden=relForbidden OPPostfix OPRelease2 OPPrefix until=callOp OPPostfix; diff --git a/Include/ContractPassUtility.hpp b/Include/ContractPassUtility.hpp index 72e96e5..2613af7 100644 --- a/Include/ContractPassUtility.hpp +++ b/Include/ContractPassUtility.hpp @@ -66,6 +66,11 @@ namespace ContractPassUtility { * Get Pointer operand of a load, store, GEPinst *or GEPOp*. Last one would not work on normal getPointerOperand! */ const Value* betterGetPointerOperand(const Value* V); + + /* + * Check if V is definitely allocated + */ + bool isTrivialAlloc(const Value* V); }; template diff --git a/Include/ContractTree.hpp b/Include/ContractTree.hpp index 429d61e..d94d725 100644 --- a/Include/ContractTree.hpp +++ b/Include/ContractTree.hpp @@ -14,7 +14,7 @@ #include namespace ContractTree { - enum struct OperationType { READ, WRITE, CALL, CALLTAG, RELEASE, PARAM }; + enum struct OperationType { READ, WRITE, ALLOC, CALL, CALLTAG, RELEASE, PARAM }; enum struct ParamAccess { NORMAL, DEREF, ADDROF }; struct Operation { virtual ~Operation() = default; @@ -34,6 +34,10 @@ namespace ContractTree { WriteOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; virtual const OperationType type() const override { return OperationType::WRITE; }; }; + struct AllocOperation : RWOperation { + AllocOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; + virtual const OperationType type() const override { return OperationType::ALLOC; }; + }; enum Comparator { NEQ, GT, GTEQ, LT, LTEQ, EXEQ }; diff --git a/LangCode/ContractDataVisitor.cpp b/LangCode/ContractDataVisitor.cpp index 4e7b27a..25d15f1 100644 --- a/LangCode/ContractDataVisitor.cpp +++ b/LangCode/ContractDataVisitor.cpp @@ -69,22 +69,17 @@ std::any ContractDataVisitor::visitExpression(ContractParser::ExpressionContext return ContractExpression(ctx->getText(), opPtr); } -std::any ContractDataVisitor::visitReadOp(ContractParser::ReadOpContext *ctx) { +std::any ContractDataVisitor::visitRwOp(ContractParser::RwOpContext *ctx) { ParamAccess acc = ParamAccess::NORMAL; - if (ctx->Deref()) - acc = ParamAccess::DEREF; - if (ctx->AddrOf()) - acc = ParamAccess::ADDROF; - std::shared_ptr op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); - return op; -} -std::any ContractDataVisitor::visitWriteOp(ContractParser::WriteOpContext *ctx) { - ParamAccess acc = ParamAccess::NORMAL; - if (ctx->Deref()) - acc = ParamAccess::DEREF; - if (ctx->AddrOf()) - acc = ParamAccess::ADDROF; - std::shared_ptr op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); + if (ctx->Deref()) acc = ParamAccess::DEREF; + if (ctx->AddrOf()) acc = ParamAccess::ADDROF; + std::shared_ptr op; + if (ctx->OPRead()) + op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); + else if (ctx->OPWrite()) + op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); + else if (ctx->OPAlloc()) + op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); return op; } std::any ContractDataVisitor::visitParamOp(ContractParser::ParamOpContext *ctx) { diff --git a/LangCode/ContractDataVisitor.hpp b/LangCode/ContractDataVisitor.hpp index f4af1ef..169557f 100644 --- a/LangCode/ContractDataVisitor.hpp +++ b/LangCode/ContractDataVisitor.hpp @@ -3,6 +3,7 @@ #include #include +#include "ContractParser.h" #include "ContractParserBaseVisitor.h" class ContractDataVisitor : public ContractParserBaseVisitor { @@ -11,8 +12,7 @@ class ContractDataVisitor : public ContractParserBaseVisitor { std::any visitExprList(ContractParser::ExprListContext *ctx) override; std::any visitExprFormula(ContractParser::ExprFormulaContext *ctx) override; std::any visitExpression(ContractParser::ExpressionContext *ctx) override; - std::any visitReadOp(ContractParser::ReadOpContext *ctx) override; - std::any visitWriteOp(ContractParser::WriteOpContext *ctx) override; + std::any visitRwOp(ContractParser::RwOpContext *ctx) override; std::any visitParamOp(ContractParser::ParamOpContext *ctx) override; std::any visitCallOp(ContractParser::CallOpContext *ctx) override; std::any visitReleaseOp(ContractParser::ReleaseOpContext *ctx) override; diff --git a/Passes/ContractVerifierAlloc.cpp b/Passes/ContractVerifierAlloc.cpp new file mode 100644 index 0000000..7ecafdd --- /dev/null +++ b/Passes/ContractVerifierAlloc.cpp @@ -0,0 +1,137 @@ +#include "ContractVerifierAlloc.hpp" + +#include "ContractPassUtility.hpp" +#include "ContractTree.hpp" +#include "ContractManager.hpp" +#include +#include +#include +#include +#include +#include + +using namespace llvm; +using namespace ContractTree; + +PreservedAnalyses ContractVerifierAllocPass::run(Module &M, + ModuleAnalysisManager &AM) { + DB = &AM.getResult(M); + MAM = &AM; + + // First, build list of all allocating funcs + for (ContractManagerAnalysis::LinearizedContract const& C : DB->LinearizedContracts) { + for (const std::shared_ptr Expr : C.Post) { + if (Expr->OP->type() != OperationType::ALLOC) continue; + const AllocOperation* AllocOp = dynamic_cast(Expr->OP.get()); + if (!AllocFuncs.contains(C.F)) AllocFuncs[C.F] = {}; + AllocFuncs[C.F].insert(AllocOp); + *Expr->Status = Fulfillment::FULFILLED; // Always fulfilled. + } + } + + // Now, do analysis + for (ContractManagerAnalysis::LinearizedContract const& C : DB->LinearizedContracts) { + for (const std::shared_ptr Expr : C.Pre) { + if (*Expr->Status != Fulfillment::UNKNOWN) continue; + // Contract has a precondition + if (Expr->OP->type() != OperationType::ALLOC) continue; + const AllocOperation* AllocOp = dynamic_cast(Expr->OP.get()); + C.DebugInfo->push_back("[ContractVerifierAlloc] Attempting to verify expression: " + Expr->ExprStr); + std::string err; + AllocStatusVal val = checkAllocReq(AllocOp, M, C.F, err); + if (!err.empty()) { + errs() << err << "\n"; + *Expr->Status = Fulfillment::BROKEN; + return PreservedAnalyses::all(); + } + *Expr->Status = val == AllocStatusVal::ERROR ? Fulfillment::BROKEN : Fulfillment::FULFILLED; + } + } + return PreservedAnalyses::all(); +} + +struct IterTypeAlloc { + std::vector err; + std::vector dbg; + int param; + ParamAccess acc; + const Function* F; +}; + +ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocStat(AllocStatus cur, const Instruction* I, void* data) { + if (cur.CurVal == AllocStatusVal::ERROR) return cur; + if (cur.CurVal == AllocStatusVal::ALLOC) return cur; + + IterTypeAlloc* Data = static_cast(data); + + if (const CallBase* CB = dyn_cast(I)) { + if (AllocFuncs.contains(CB->getCalledFunction())) { + for (const AllocOperation* alloc : AllocFuncs[CB->getCalledFunction()]) { + #warning TODO different access patterns + cur.candidate.insert(CB->getArgOperand(alloc->contrP)); + } + // Dont return here! Maybe it also is contr sup + } + if (CB->getCalledFunction() == Data->F) { + // Found contract supplier. Check if param is allocated + if (ContractPassUtility::isTrivialAlloc(CB->getArgOperand(Data->param))) { + cur.CurVal = AllocStatusVal::ALLOC; + return cur; + } + // Not trivial, check if explicitly allocated + for (const Value* Candidate : cur.candidate) { + if (ContractPassUtility::checkParamMatch(CB->getArgOperand(Data->param), Candidate, Data->acc, MAM)) { + // Success! + cur.CurVal = AllocStatusVal::ALLOC; + return cur; + } + } + // Any required parameter not used by any candidate + //appendDebugStr(Data->Target, Data->isTag, CB, cur.candidate, Data->err); + cur.CurVal = AllocStatusVal::ERROR; + return cur; + } + } + // Not a call. Just forward info + return cur; +} + +std::pair ContractVerifierAllocPass::mergeAllocStat(AllocStatus prev, AllocStatus cur, const Instruction* I, void* data) { + std::set intersect; + std::set_intersection(prev.candidate.begin(), prev.candidate.end(), cur.candidate.begin(), cur.candidate.end(), + std::inserter(intersect, intersect.begin())); + + AllocStatus merge = { std::max(prev.CurVal, cur.CurVal), intersect }; + + return { merge, merge.CurVal > prev.CurVal }; +} + +ContractVerifierAllocPass::AllocStatusVal ContractVerifierAllocPass::checkAllocReq(const AllocOperation* AllocOp, Module const& M, const Function* F, std::string& err) { + const Function* mainF = M.getFunction("main"); + if (!mainF) { + err = "Cannot find main function, cannot construct path to check precall!"; + return AllocStatusVal::ERROR; + } + const Instruction* Entry = mainF->getEntryBlock().getFirstNonPHI(); + + AllocStatus init = { AllocStatusVal::UNDEF, {}}; + IterTypeAlloc data = { {}, {}, AllocOp->contrP, AllocOp->contrParamAccess, F }; + auto bound_transfer = std::bind(&ContractVerifierAllocPass::transferAllocStat, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); + auto bound_merge = std::bind(&ContractVerifierAllocPass::mergeAllocStat, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + std::map AnalysisInfo = ContractPassUtility::GenericWorklist(Entry, bound_transfer, bound_merge, &data, init); + + //C.DebugInfo->insert(C.DebugInfo->end(), data.dbg.begin(), data.dbg.end()); + //Expr.ErrorInfo->insert(Expr.ErrorInfo->end(), data.err.begin(), data.err.end()); + + // Take max over all analysis info + // Correct usage will not contain error + AllocStatusVal res = AllocStatusVal::ALLOC; + for (std::pair AI : AnalysisInfo) { + if (const CallBase* CB = dyn_cast(AI.first)) { + if (CB->getCalledFunction() == F) { + res = std::max(AI.second.CurVal, res); + } + } + } + return res; +} diff --git a/Passes/ContractVerifierAlloc.hpp b/Passes/ContractVerifierAlloc.hpp new file mode 100644 index 0000000..f636aa1 --- /dev/null +++ b/Passes/ContractVerifierAlloc.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "ContractTree.hpp" +#include "ContractManager.hpp" +#include "llvm/IR/PassManager.h" +#include +#include +#include +#include + +using namespace ContractTree; + +namespace llvm { + +class ContractVerifierAllocPass : public PassInfoMixin { + public: + enum struct AllocStatusVal { ALLOC, UNDEF, ERROR }; + struct AllocStatus { + AllocStatusVal CurVal; + std::set candidate; + }; + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); + + private: + ContractManagerAnalysis::ContractDatabase* DB; + ModuleAnalysisManager* MAM; + + std::map> AllocFuncs; + + AllocStatus transferAllocStat(AllocStatus s, const Instruction* I, void* data); + std::pair mergeAllocStat(AllocStatus prev, AllocStatus cur, const Instruction* I, void* data); + + AllocStatusVal checkAllocReq(const AllocOperation* AllocOp, Module const& M, const Function* F, std::string& err); + +}; + +} // namespace llvm diff --git a/Passes/Registrar.cpp b/Passes/Registrar.cpp index 24283b9..66f1a2d 100644 --- a/Passes/Registrar.cpp +++ b/Passes/Registrar.cpp @@ -4,6 +4,7 @@ #include #include "ContractManager.hpp" +#include "ContractVerifierAlloc.hpp" #include "ContractVerifierPreCall.hpp" #include "ContractVerifierPostCall.hpp" #include "ContractVerifierRelease.hpp" @@ -31,6 +32,10 @@ namespace { MPM.addPass(ContractVerifierParamPass()); return true; } + if (Name == "contractVerifierAlloc") { + MPM.addPass(ContractVerifierAllocPass()); + return true; + } if (Name == "contractPostProcess") { MPM.addPass(ContractPostProcessingPass()); return true; diff --git a/Scripts/clangContracts.cpp b/Scripts/clangContracts.cpp index 7609e62..93833ad 100644 --- a/Scripts/clangContracts.cpp +++ b/Scripts/clangContracts.cpp @@ -285,7 +285,7 @@ int main(int argc, const char** argv) { execSafe("llvm-link" + bitcode_files + " -o " + tmpfile); // Call LLVM passes - std::string passlist = "function(sroa),contractVerifierPreCall,contractVerifierPostCall,contractVerifierRelease,contractVerifierParam,contractPostProcess"; + std::string passlist = "function(sroa),contractVerifierPreCall,contractVerifierPostCall,contractVerifierRelease,contractVerifierParam,contractVerifierAlloc,contractPostProcess"; if (!opt_level.empty()) { passlist += ",default<" + opt_level.substr(1) + ">"; // opt_level substr cuts "-" from "-O" } diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index c34a99d..5809d57 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -345,6 +345,25 @@ def add_contract(func: str, scope: str, contr: str): for func, idx in paramerror_null: add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_IN_PLACE) MSG \"Parameter is null\"") +# Comm buffer should be allocated +paramerror_null = [ + ("MPI_Send", 0), + ("MPI_Recv", 0), + ("MPI_Sendrecv", 0), + ("MPI_Sendrecv", 5), + ("MPI_Get", 0), + ("MPI_Put", 0), +] +for func, idx in paramerror_null: + function_contracts[func]["PRE"].append(f"alloc!(&{idx}) MSG \"Buffer is not allocated\"") + +allocators = [ + ("MPI_Win_allocate", 0), +] +for func, idx in allocators: + function_contracts[func]["POST"].append(f"alloc!({idx})") + + # Datatype should not be null when doing communication paramerror_datatype = [ ("MPI_Get", 6), diff --git a/Utils/ContractPassUtility.cpp b/Utils/ContractPassUtility.cpp index ea74e28..d16dfc1 100644 --- a/Utils/ContractPassUtility.cpp +++ b/Utils/ContractPassUtility.cpp @@ -149,6 +149,22 @@ FileReference getFileReference(const Instruction* I) { }; } +bool isTrivialAlloc(const Value* V) { + // First possibility: Its a global alloc, trivially fulfilled + if (isa(V)) { + return true; + } + // Second possibility: Its a stack var, trivially fulfilled + const Value* tmp = V; + while (isa(tmp)) { + tmp = getPointerOperand(tmp); + } + if (isa(tmp)) return true; + + // Not trivially allocated + return false; +} + bool checkCalledApplies(const CallBase* CB, const StringRef Target, bool isTag, std::map> Tags) { if (!isTag) { if (CB->getCalledOperand()->getName().empty()) { From 9c82e4151e5756b350d25025ecc9fdb85583ca54 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 16 May 2025 13:28:23 +0200 Subject: [PATCH 008/125] Abstract access patterns for alloc --- Passes/ContractVerifierAlloc.cpp | 17 +++++++++++++---- Passes/ContractVerifierAlloc.hpp | 2 +- Scripts/gen_mpi_contr_h.py | 7 +++++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Passes/ContractVerifierAlloc.cpp b/Passes/ContractVerifierAlloc.cpp index 7ecafdd..459e4e8 100644 --- a/Passes/ContractVerifierAlloc.cpp +++ b/Passes/ContractVerifierAlloc.cpp @@ -64,11 +64,19 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS IterTypeAlloc* Data = static_cast(data); + // Propagate allocations + if (const StoreInst* SI = dyn_cast(I)) { + if (cur.candidate.contains(SI->getValueOperand())) { + cur.candidate[SI->getPointerOperand()] = ParamAccess::ADDROF; + } + } + if (const CallBase* CB = dyn_cast(I)) { if (AllocFuncs.contains(CB->getCalledFunction())) { for (const AllocOperation* alloc : AllocFuncs[CB->getCalledFunction()]) { #warning TODO different access patterns - cur.candidate.insert(CB->getArgOperand(alloc->contrP)); + if (alloc->contrP == 99) cur.candidate[CB] = alloc->contrParamAccess; + else cur.candidate[CB->getArgOperand(alloc->contrP)] = alloc->contrParamAccess; } // Dont return here! Maybe it also is contr sup } @@ -79,8 +87,9 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS return cur; } // Not trivial, check if explicitly allocated - for (const Value* Candidate : cur.candidate) { - if (ContractPassUtility::checkParamMatch(CB->getArgOperand(Data->param), Candidate, Data->acc, MAM)) { + for (std::pair Candidate : cur.candidate) { + CB->getArgOperand(Data->param)->print(errs()); errs() << "\n"; + if (ContractPassUtility::checkParamMatch(CB->getArgOperand(Data->param), Candidate.first, Candidate.second, MAM)) { // Success! cur.CurVal = AllocStatusVal::ALLOC; return cur; @@ -97,7 +106,7 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS } std::pair ContractVerifierAllocPass::mergeAllocStat(AllocStatus prev, AllocStatus cur, const Instruction* I, void* data) { - std::set intersect; + std::map intersect; std::set_intersection(prev.candidate.begin(), prev.candidate.end(), cur.candidate.begin(), cur.candidate.end(), std::inserter(intersect, intersect.begin())); diff --git a/Passes/ContractVerifierAlloc.hpp b/Passes/ContractVerifierAlloc.hpp index f636aa1..e1fb528 100644 --- a/Passes/ContractVerifierAlloc.hpp +++ b/Passes/ContractVerifierAlloc.hpp @@ -17,7 +17,7 @@ class ContractVerifierAllocPass : public PassInfoMixin candidate; + std::map candidate; }; PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 5809d57..50beb9b 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -355,13 +355,13 @@ def add_contract(func: str, scope: str, contr: str): ("MPI_Put", 0), ] for func, idx in paramerror_null: - function_contracts[func]["PRE"].append(f"alloc!(&{idx}) MSG \"Buffer is not allocated\"") + add_contract(func, "PRE", f"alloc!({idx}) MSG \"Buffer is not allocated\"") allocators = [ ("MPI_Win_allocate", 0), ] for func, idx in allocators: - function_contracts[func]["POST"].append(f"alloc!({idx})") + add_contract(func, "POST", f"alloc!(&{idx})") # Datatype should not be null when doing communication @@ -415,6 +415,9 @@ def add_contract(func: str, scope: str, contr: str): CONTRACT_PTR_PAIR(MPI_STATUS_IGNORE,*MPI_STATUS_IGNORE) CONTRACT_PTR_PAIR(MPI_DATATYPE_NULL,*MPI_DATATYPE_NULL) +void* calloc(size_t num, size_t size) CONTRACT( POST {{ alloc!(99) }}); +void* malloc(size_t size) CONTRACT( POST {{ alloc!(99) }}); + """ header_output_c = boilerplate_header_c From affa6ba029b3880b0277435ecb10e5b7477dccbd Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 16 May 2025 14:03:50 +0200 Subject: [PATCH 009/125] Fix FP for sendrecv MPI_IN_PLACE --- Scripts/gen_mpi_contr_h.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 50beb9b..c302b10 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -337,7 +337,6 @@ def add_contract(func: str, scope: str, contr: str): ("MPI_Send", 0), ("MPI_Recv", 0), ("MPI_Sendrecv", 0), - ("MPI_Sendrecv", 5), ("MPI_Win_create", 0), ("MPI_Win_allocate", 4), ("MPI_Get", 0), @@ -345,6 +344,9 @@ def add_contract(func: str, scope: str, contr: str): for func, idx in paramerror_null: add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_IN_PLACE) MSG \"Parameter is null\"") +# Allow MPI_IN_PLACE for recv buffer of sendrecv +add_contract("MPI_Sendrecv", "PRE", f"param!(5:^=MPI_IN_PLACE,!=NULL) MSG \"Buffer is null\"") + # Comm buffer should be allocated paramerror_null = [ ("MPI_Send", 0), From d0c5f9063842f5934df19e39b882ff3c4f30a627 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 16 May 2025 14:32:32 +0200 Subject: [PATCH 010/125] Fix mpi win alloc param --- Scripts/gen_mpi_contr_h.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index c302b10..69a4141 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -360,7 +360,7 @@ def add_contract(func: str, scope: str, contr: str): add_contract(func, "PRE", f"alloc!({idx}) MSG \"Buffer is not allocated\"") allocators = [ - ("MPI_Win_allocate", 0), + ("MPI_Win_allocate", 4), ] for func, idx in allocators: add_contract(func, "POST", f"alloc!(&{idx})") From f3dc7c6b7dcce872efe3f20f0d4f7ecd5d6edb7b Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 16 May 2025 14:32:58 +0200 Subject: [PATCH 011/125] Remove debug output --- Passes/ContractVerifierAlloc.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Passes/ContractVerifierAlloc.cpp b/Passes/ContractVerifierAlloc.cpp index 459e4e8..2affe21 100644 --- a/Passes/ContractVerifierAlloc.cpp +++ b/Passes/ContractVerifierAlloc.cpp @@ -88,7 +88,6 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS } // Not trivial, check if explicitly allocated for (std::pair Candidate : cur.candidate) { - CB->getArgOperand(Data->param)->print(errs()); errs() << "\n"; if (ContractPassUtility::checkParamMatch(CB->getArgOperand(Data->param), Candidate.first, Candidate.second, MAM)) { // Success! cur.CurVal = AllocStatusVal::ALLOC; From 6c1ad829d00fced97ae46b3f8888299567631d02 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 18 Sep 2025 12:54:17 +0200 Subject: [PATCH 012/125] Fix VerifierAlloc for newer llvm --- Passes/ContractVerifierAlloc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Passes/ContractVerifierAlloc.cpp b/Passes/ContractVerifierAlloc.cpp index 2affe21..4412a9e 100644 --- a/Passes/ContractVerifierAlloc.cpp +++ b/Passes/ContractVerifierAlloc.cpp @@ -120,7 +120,7 @@ ContractVerifierAllocPass::AllocStatusVal ContractVerifierAllocPass::checkAllocR err = "Cannot find main function, cannot construct path to check precall!"; return AllocStatusVal::ERROR; } - const Instruction* Entry = mainF->getEntryBlock().getFirstNonPHI(); + const Instruction* Entry = &*mainF->getEntryBlock().getFirstNonPHIIt(); AllocStatus init = { AllocStatusVal::UNDEF, {}}; IterTypeAlloc data = { {}, {}, AllocOp->contrP, AllocOp->contrParamAccess, F }; From c79f21cff1d873343654214752726553eafb0d32 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 18 Sep 2025 14:34:35 +0200 Subject: [PATCH 013/125] Cleanup param verification --- Include/Contracts.h | 16 ++------- Passes/ContractManager.cpp | 9 +++-- Passes/ContractManager.hpp | 2 +- Passes/ContractVerifierParam.cpp | 62 ++++++++++++++++---------------- Passes/ContractVerifierParam.hpp | 3 +- Scripts/gen_mpi_contr_h.py | 20 +++++------ 6 files changed, 53 insertions(+), 59 deletions(-) diff --git a/Include/Contracts.h b/Include/Contracts.h index b973fc8..3a25938 100644 --- a/Include/Contracts.h +++ b/Include/Contracts.h @@ -12,16 +12,11 @@ // Contract Value Names Definitions // DO NOT USE THE STRUCT DEFINITIONS DIRECTLY! -// Use macros CONTRACT_VALUE_PAIR and CONTRACT_PTR_PAIR instead +// Use CONTRACT_VALUE_PAIR macro instead struct ContractValuePair { const char* name; - int64_t value; + void* value; } __attribute__((packed)) typedef ContractValuePair_t; -struct ContractPtrPair { - const char* name; - void* ptr; -} __attribute__((packed)) typedef ContractPtrPair_t; - #define CONCAT_IMPL( x, y ) x##y #define MACRO_CONCAT( x, y ) CONCAT_IMPL( x, y ) @@ -29,9 +24,4 @@ struct ContractPtrPair { // Define a name for a constant value // Example: CONTRACT_VALUE_PAIR(zero,0) #define CONTRACT_VALUE_PAIR(x,y) \ - ContractValuePair_t MACRO_CONCAT(ContractValueInfo_, __COUNTER__ ) __attribute__((used)) = {#x, (int64_t)y}; - -// Define a name for a variable address -// Example: CONTRACT_PTR_PAIR(myvar,myvar) where myvar is a variable -#define CONTRACT_PTR_PAIR(x,y) \ - ContractPtrPair_t MACRO_CONCAT(ContractPtrInfo_, __COUNTER__ ) __attribute__((used)) = {#x, &y}; + ContractValuePair_t MACRO_CONCAT(ContractValueInfo_, __COUNTER__ ) __attribute__((used)) = {#x, (void*)y}; diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index e34823d..a20db7f 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -53,11 +53,16 @@ ContractManagerAnalysis::ContractDatabase ContractManagerAnalysis::run(Module &M // Annotations done, now add value pairs to database for (GlobalVariable& GV : M.globals()) { - if (GV.getName().starts_with("ContractValueInfo_") || GV.getName().starts_with("ContractPtrInfo_")) { + if (GV.getName().starts_with("ContractValueInfo_")) { Constant* data = GV.getInitializer(); StringRef name = dyn_cast(dyn_cast(data->getOperand(0))->getInitializer())->getAsCString(); Value* val = data->getOperand(1); - curDatabase.ContractVariableData[name.str()] = { val, GV.getName().starts_with("ContractPtrInfo_") }; + if (ConstantExpr* CE = dyn_cast(data->getOperand(1))) { + if (isa(CE->getAsInstruction())) { + val = CE->getOperand(0); + } + } + curDatabase.ContractVariableData[name.str()] = val; } } diff --git a/Passes/ContractManager.hpp b/Passes/ContractManager.hpp index cde74e6..474dfb0 100644 --- a/Passes/ContractManager.hpp +++ b/Passes/ContractManager.hpp @@ -40,7 +40,7 @@ class ContractManagerAnalysis : public AnalysisInfoMixin Contracts; // For postprocessing only std::vector LinearizedContracts; // For verification passes std::map> Tags; - std::map> ContractVariableData; + std::map ContractVariableData; std::chrono::time_point start_time; bool allowMultiReports = false; Json::Value processedReports; diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index 129d38c..c152087 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -45,35 +44,38 @@ PreservedAnalyses ContractVerifierParamPass::run(Module &M, const ParamOperation* ParamOp = dynamic_cast(Expr->OP.get()); C.DebugInfo->push_back("[ContractVerifierParam] Attempting to verify expression: " + Expr->ExprStr); Fulfillment resf = Fulfillment::FULFILLED; - for (std::pair req : ParamOp->reqs) { - Value* var; - try { - // First, check if constant value provided - int ivalue = std::stoi(req.second); - var = ConstantInt::get(Type::getInt64Ty(M.getContext()), ivalue); - } catch(std::exception& e) { - // Otherwise, check against value database - if (!DB.ContractVariableData.contains(req.second)) { - errs() << "Undefined non-constint contract value identifier \"" << req.second << "\"!\n"; - errs() << "Requirement will not be analysed!\n"; - continue; - } - var = DB.ContractVariableData[req.second].first; - } - // Perform the check on each callsite - for (const User* U : C.F->users()) { - if (const CallBase* CB = dyn_cast(U)) { + + // Perform the check on each callsite + for (const User* U : C.F->users()) { + if (const CallBase* CB = dyn_cast(U)) { + for (std::pair req : ParamOp->reqs) { + // Figure out value(s) to check against + Value* var; + try { + // First, check if constant value provided + int ivalue = std::stoi(req.second); + var = ConstantInt::get(Type::getInt64Ty(M.getContext()), ivalue); + } catch(std::exception& e) { + // Otherwise, check against value database + if (!DB.ContractVariableData.contains(req.second)) { + errs() << "Undefined non-constint contract value identifier \"" << req.second << "\"!\n"; + errs() << "Requirement will not be analysed!\n"; + continue; + } + var = DB.ContractVariableData[req.second]; + } + + // Perform check std::string errInfo = ""; - Fulfillment f = checkParamReq(var, CB->getArgOperand(ParamOp->idx), req.first, DB.ContractVariableData[req.second].second, errInfo); + Fulfillment f = checkParamReq(var, CB->getArgOperand(ParamOp->idx), req.first, errInfo); if (f == Fulfillment::BROKEN) { resf = Fulfillment::BROKEN; - if (!errInfo.empty()) { - Expr->ErrorInfo->push_back({ - .error_id = "Param", - .text = std::format("{:s} Parameter Index: {:d}, Contract Value Name: {:s}", errInfo, ParamOp->idx, req.second), - .references = {ContractPassUtility::getFileReference(CB)}, - }); - } + Expr->ErrorInfo->push_back({ + .error_id = "Param", + .text = std::format("{:s} Parameter Index: {:d}, Contract Value: {:s}", errInfo.empty() ? "Parameter error detected!" : errInfo, ParamOp->idx, req.second), + .references = {ContractPassUtility::getFileReference(CB)}, + }); + goto exit_param_analysis; } if (f == Fulfillment::FULFILLED && req.first == Comparator::EXEQ) { // Parameter fulfills exception value. Stop checking this parameter @@ -130,8 +132,8 @@ Fulfillment compareCI(const ConstantInt* CI, const ConstantInt* CI2, Comparator } } -Fulfillment ContractVerifierParamPass::checkParamReq(const Value* var, const Value* callVal, Comparator comp, bool isPtr, std::string& ErrInfo) { - if (isPtr) { +Fulfillment ContractVerifierParamPass::checkParamReq(const Value* var, const Value* callVal, Comparator comp, std::string& ErrInfo) { + if (callVal->getType()->isPointerTy()) { switch (comp) { case Comparator::NEQ: if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { @@ -141,7 +143,7 @@ Fulfillment ContractVerifierParamPass::checkParamReq(const Value* var, const Val return Fulfillment::FULFILLED; case Comparator::EXEQ: if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { - ErrInfo = "Note: Parameter matches or is alias to exception value"; + ErrInfo = "Note: Parameter matches or is alias to exception value."; return Fulfillment::FULFILLED; } // Not an exception. Continue analysis, so far no info gained diff --git a/Passes/ContractVerifierParam.hpp b/Passes/ContractVerifierParam.hpp index b749203..85e34a9 100644 --- a/Passes/ContractVerifierParam.hpp +++ b/Passes/ContractVerifierParam.hpp @@ -12,8 +12,7 @@ class ContractVerifierParamPass : public PassInfoMixin Date: Mon, 9 Mar 2026 10:27:48 +0100 Subject: [PATCH 014/125] Fix compilation when disabling unity build, and disable unity build in debug builds --- Dynamic/Analyses/PostCallAnalysis.h | 6 +++--- Dynamic/Analyses/PreCallAnalysis.h | 6 +++--- Dynamic/Analyses/ReleaseAnalysis.h | 6 +++--- Dynamic/CMakeLists.txt | 2 +- Dynamic/DynamicUtils.h | 6 ++++++ 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Dynamic/Analyses/PostCallAnalysis.h b/Dynamic/Analyses/PostCallAnalysis.h index 73316d6..85ad49d 100644 --- a/Dynamic/Analyses/PostCallAnalysis.h +++ b/Dynamic/Analyses/PostCallAnalysis.h @@ -10,9 +10,9 @@ struct PostCallAnalysis : public BaseAnalysis { PostCallAnalysis(void const* func_supplier, CallOp_t* callop); PostCallAnalysis(void const* func_supplier, CallTagOp_t* callop); - inline __attribute__((always_inline)) Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); - inline __attribute__((always_inline)) Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } - inline __attribute__((always_inline)) Fulfillment exitCBImpl(CodePtr const& location); + ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } + ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location); constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false}; } diff --git a/Dynamic/Analyses/PreCallAnalysis.h b/Dynamic/Analyses/PreCallAnalysis.h index 1d4bcbd..8e7953d 100644 --- a/Dynamic/Analyses/PreCallAnalysis.h +++ b/Dynamic/Analyses/PreCallAnalysis.h @@ -12,9 +12,9 @@ struct PreCallAnalysis : BaseAnalysis { PreCallAnalysis(void const* func_supplier, CallOp_t* callop); PreCallAnalysis(void const* func_supplier, CallTagOp_t* callop); - inline __attribute__((always_inline)) Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); - inline __attribute__((always_inline)) Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } - inline __attribute__((always_inline)) Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::INACTIVE; }; + ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } + ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::INACTIVE; }; constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false}; } diff --git a/Dynamic/Analyses/ReleaseAnalysis.h b/Dynamic/Analyses/ReleaseAnalysis.h index a96e296..fded71c 100644 --- a/Dynamic/Analyses/ReleaseAnalysis.h +++ b/Dynamic/Analyses/ReleaseAnalysis.h @@ -8,9 +8,9 @@ struct ReleaseAnalysis : BaseAnalysis { public: ReleaseAnalysis(void const* func_supplier, ReleaseOp_t* rOP); - inline __attribute__((always_inline)) Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); - inline __attribute__((always_inline)) Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite); - inline __attribute__((always_inline)) Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; + ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite); + ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; CallBacks requiredCallbacksImpl() const; diff --git a/Dynamic/CMakeLists.txt b/Dynamic/CMakeLists.txt index d326171..814b014 100644 --- a/Dynamic/CMakeLists.txt +++ b/Dynamic/CMakeLists.txt @@ -7,7 +7,7 @@ add_library(CoVerDynamicAnalyzer STATIC ) set_property(TARGET CoVerDynamicAnalyzer PROPERTY CXX_STANDARD 20) -set_property(TARGET CoVerDynamicAnalyzer PROPERTY UNITY_BUILD ON) +set_property(TARGET CoVerDynamicAnalyzer PROPERTY UNITY_BUILD "$>") target_include_directories(CoVerDynamicAnalyzer PUBLIC ../Include/) set_property(TARGET CoVerDynamicAnalyzer PROPERTY CXX_VISIBILITY_PRESET hidden) diff --git a/Dynamic/DynamicUtils.h b/Dynamic/DynamicUtils.h index 128bbee..688cd53 100644 --- a/Dynamic/DynamicUtils.h +++ b/Dynamic/DynamicUtils.h @@ -10,6 +10,12 @@ #include #include +#ifdef NDEBUG +#define ANALYSIS_PREAMBLE inline __attribute__((always_inline)) +#else +#define ANALYSIS_PREAMBLE +#endif + using CodePtr = void const*; struct ConcreteParam { void const* value; From c63faef7533a80ec177a39ec99d72bbb3cbd2c02 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 9 Mar 2026 10:38:48 +0100 Subject: [PATCH 015/125] Merge Formula and OperationType Easier to handle in DynamicAnalysis.h --- Include/ContractTree.hpp | 19 +++++++++---------- Passes/ContractVerifierAlloc.cpp | 4 ++-- Passes/ContractVerifierParam.cpp | 2 +- Passes/ContractVerifierPostCall.cpp | 6 +++--- Passes/ContractVerifierPreCall.cpp | 6 +++--- Passes/ContractVerifierRelease.cpp | 26 +++++++++++++------------- Passes/Instrument.cpp | 20 +++++++++++++------- 7 files changed, 44 insertions(+), 39 deletions(-) diff --git a/Include/ContractTree.hpp b/Include/ContractTree.hpp index d94d725..9d3e841 100644 --- a/Include/ContractTree.hpp +++ b/Include/ContractTree.hpp @@ -14,11 +14,11 @@ #include namespace ContractTree { - enum struct OperationType { READ, WRITE, ALLOC, CALL, CALLTAG, RELEASE, PARAM }; + enum struct FormulaType { AND, OR, XOR, READ, WRITE, ALLOC, CALL, CALLTAG, RELEASE, PARAM }; enum struct ParamAccess { NORMAL, DEREF, ADDROF }; struct Operation { virtual ~Operation() = default; - virtual const OperationType type() const = 0; + virtual const FormulaType type() const = 0; }; struct RWOperation : Operation { const int contrP; @@ -28,15 +28,15 @@ namespace ContractTree { }; struct ReadOperation : RWOperation { ReadOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; - virtual const OperationType type() const override { return OperationType::READ; }; + virtual const FormulaType type() const override { return FormulaType::READ; }; }; struct WriteOperation : RWOperation { WriteOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; - virtual const OperationType type() const override { return OperationType::WRITE; }; + virtual const FormulaType type() const override { return FormulaType::WRITE; }; }; struct AllocOperation : RWOperation { AllocOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; - virtual const OperationType type() const override { return OperationType::ALLOC; }; + virtual const FormulaType type() const override { return FormulaType::ALLOC; }; }; enum Comparator { NEQ, GT, GTEQ, LT, LTEQ, EXEQ @@ -45,7 +45,7 @@ namespace ContractTree { ParamOperation(int _idx, std::vector> _reqs) : idx{_idx}, reqs{_reqs} {}; const int idx; const std::vector> reqs; - virtual const OperationType type() const override { return OperationType::PARAM; }; + virtual const FormulaType type() const override { return FormulaType::PARAM; }; }; struct CallParam { int callP; @@ -57,22 +57,21 @@ namespace ContractTree { CallOperation(std::string _func, std::vector _params) : Function{_func}, Params{_params} {}; const std::string Function; const std::vector Params; - virtual const OperationType type() const override { return OperationType::CALL; }; + virtual const FormulaType type() const override { return FormulaType::CALL; }; }; struct CallTagOperation : CallOperation { CallTagOperation(std::string _func, std::vector _params) : CallOperation(_func, _params) {}; - virtual const OperationType type() const override { return OperationType::CALLTAG; }; + virtual const FormulaType type() const override { return FormulaType::CALLTAG; }; }; struct ReleaseOperation : Operation { ReleaseOperation(std::shared_ptr opNo, std::shared_ptr opUntil) : Forbidden{opNo}, Until{opUntil} {}; const std::shared_ptr Forbidden; const std::shared_ptr Until; - virtual const OperationType type() const override { return OperationType::RELEASE; }; + virtual const FormulaType type() const override { return FormulaType::RELEASE; }; }; enum struct Fulfillment { FULFILLED, UNKNOWN, BROKEN }; inline const std::string FulfillmentStr(Fulfillment f) { return std::vector{ "Fulfilled", "Unknown", "Violated"}[(int)f]; }; - enum struct FormulaType { AND = 5, OR = 6, XOR = 7 }; struct ContractFormula { ContractFormula(std::vector> _cF, std::string _str, FormulaType _type) : Children(_cF), ExprStr(_str), type(_type) {} ContractFormula(std::string _str) : ExprStr(_str) {} diff --git a/Passes/ContractVerifierAlloc.cpp b/Passes/ContractVerifierAlloc.cpp index 4412a9e..bfa1f90 100644 --- a/Passes/ContractVerifierAlloc.cpp +++ b/Passes/ContractVerifierAlloc.cpp @@ -21,7 +21,7 @@ PreservedAnalyses ContractVerifierAllocPass::run(Module &M, // First, build list of all allocating funcs for (ContractManagerAnalysis::LinearizedContract const& C : DB->LinearizedContracts) { for (const std::shared_ptr Expr : C.Post) { - if (Expr->OP->type() != OperationType::ALLOC) continue; + if (Expr->OP->type() != FormulaType::ALLOC) continue; const AllocOperation* AllocOp = dynamic_cast(Expr->OP.get()); if (!AllocFuncs.contains(C.F)) AllocFuncs[C.F] = {}; AllocFuncs[C.F].insert(AllocOp); @@ -34,7 +34,7 @@ PreservedAnalyses ContractVerifierAllocPass::run(Module &M, for (const std::shared_ptr Expr : C.Pre) { if (*Expr->Status != Fulfillment::UNKNOWN) continue; // Contract has a precondition - if (Expr->OP->type() != OperationType::ALLOC) continue; + if (Expr->OP->type() != FormulaType::ALLOC) continue; const AllocOperation* AllocOp = dynamic_cast(Expr->OP.get()); C.DebugInfo->push_back("[ContractVerifierAlloc] Attempting to verify expression: " + Expr->ExprStr); std::string err; diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index c152087..61467b0 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -40,7 +40,7 @@ PreservedAnalyses ContractVerifierParamPass::run(Module &M, if (*Expr->Status != Fulfillment::UNKNOWN) continue; // Contract has a precondition std::string err; - if (Expr->OP->type() != OperationType::PARAM) continue; + if (Expr->OP->type() != FormulaType::PARAM) continue; const ParamOperation* ParamOp = dynamic_cast(Expr->OP.get()); C.DebugInfo->push_back("[ContractVerifierParam] Attempting to verify expression: " + Expr->ExprStr); Fulfillment resf = Fulfillment::FULFILLED; diff --git a/Passes/ContractVerifierPostCall.cpp b/Passes/ContractVerifierPostCall.cpp index 9ea741d..ad0a1f4 100644 --- a/Passes/ContractVerifierPostCall.cpp +++ b/Passes/ContractVerifierPostCall.cpp @@ -36,11 +36,11 @@ PreservedAnalyses ContractVerifierPostCallPass::run(Module &M, std::string err; CallStatus result; switch (Expr->OP->type()) { - case OperationType::CALL: - case OperationType::CALLTAG: { + case FormulaType::CALL: + case FormulaType::CALLTAG: { const CallOperation* cOP = static_cast(Expr->OP.get()); C.DebugInfo->push_back("[ContractVerifierPostCall] Attempting to verify expression: " + Expr->ExprStr); - result = checkPostCall(cOP, C, *Expr, cOP->type() == OperationType::CALLTAG, M, err); + result = checkPostCall(cOP, C, *Expr, cOP->type() == FormulaType::CALLTAG, M, err); break; } default: continue; diff --git a/Passes/ContractVerifierPreCall.cpp b/Passes/ContractVerifierPreCall.cpp index 42f478d..e0b752b 100644 --- a/Passes/ContractVerifierPreCall.cpp +++ b/Passes/ContractVerifierPreCall.cpp @@ -38,11 +38,11 @@ PreservedAnalyses ContractVerifierPreCallPass::run(Module &M, std::string err; CallStatusVal result; switch (Expr->OP->type()) { - case OperationType::CALL: - case OperationType::CALLTAG: { + case FormulaType::CALL: + case FormulaType::CALLTAG: { const CallOperation* cOP = static_cast(Expr->OP.get()); C.DebugInfo->push_back("[ContractVerifierPreCall] Attempting to verify expression: " + Expr->ExprStr); - result = checkPreCall(cOP, C, *Expr, cOP->type() == OperationType::CALLTAG, M, err); + result = checkPreCall(cOP, C, *Expr, cOP->type() == FormulaType::CALLTAG, M, err); break; } default: continue; diff --git a/Passes/ContractVerifierRelease.cpp b/Passes/ContractVerifierRelease.cpp index 8eafa80..995773c 100644 --- a/Passes/ContractVerifierRelease.cpp +++ b/Passes/ContractVerifierRelease.cpp @@ -40,7 +40,7 @@ PreservedAnalyses ContractVerifierReleasePass::run(Module &M, std::string err; bool result = false; switch (Expr->OP->type()) { - case OperationType::RELEASE: { + case FormulaType::RELEASE: { const ReleaseOperation& relOP = static_cast(*Expr->OP); C.DebugInfo->push_back("[ContractVerifierRelease] Attempting to verify expression: " + Expr->ExprStr); result = checkRelease(relOP, C, *Expr, M, err) == ReleaseStatus::FULFILLED; @@ -65,7 +65,7 @@ PreservedAnalyses ContractVerifierReleasePass::run(Module &M, struct IterTypeRelease { std::vector err; std::vector dbg; - OperationType forbiddenType; + FormulaType forbiddenType; std::vector param; std::string releaseFunc; std::vector releaseParam; @@ -125,10 +125,10 @@ ContractVerifierReleasePass::ReleaseStatus ContractVerifierReleasePass::transfer } switch (Data->forbiddenType) { - case ContractTree::OperationType::CALL: - case ContractTree::OperationType::CALLTAG: + case ContractTree::FormulaType::CALL: + case ContractTree::FormulaType::CALLTAG: if (const CallBase* CB = dyn_cast(I)) { - if (ContractPassUtility::checkCalledApplies(CB, std::any_cast(Data->param[0]), Data->forbiddenType == ContractTree::OperationType::CALLTAG, Data->Tags)) { + if (ContractPassUtility::checkCalledApplies(CB, std::any_cast(Data->param[0]), Data->forbiddenType == ContractTree::FormulaType::CALLTAG, Data->Tags)) { // Found forbidden function. Current status is unknown, if we find forbidden parameter (and one is specified) this is an error const std::vector forbidParams = std::any_cast>(Data->param[1]); if (forbidParams.empty()) return ReleaseStatus::ERROR_UNFULFILLED; @@ -143,12 +143,12 @@ ContractVerifierReleasePass::ReleaseStatus ContractVerifierReleasePass::transfer } } break; - case ContractTree::OperationType::READ: + case ContractTree::FormulaType::READ: if (const LoadInst* LI = dyn_cast(I)) { RWHelper(LI); } break; - case ContractTree::OperationType::WRITE: + case ContractTree::FormulaType::WRITE: if (const StoreInst* SI = dyn_cast(I)) { RWHelper(SI); } @@ -174,16 +174,16 @@ std::pair ContractVerifierRelea ContractVerifierReleasePass::ReleaseStatus ContractVerifierReleasePass::checkRelease(const ContractTree::ReleaseOperation relOp, ContractManagerAnalysis::LinearizedContract const& C, ContractExpression const& Expr, const Module& M, std::string& error) { // Figure out release parameters - OperationType forbiddenType = relOp.Forbidden->type(); + FormulaType forbiddenType = relOp.Forbidden->type(); std::vector param; switch (forbiddenType) { - case ContractTree::OperationType::CALLTAG: - case ContractTree::OperationType::CALL: + case ContractTree::FormulaType::CALLTAG: + case ContractTree::FormulaType::CALL: param.push_back(static_cast(*relOp.Forbidden).Function); param.push_back(static_cast(*relOp.Forbidden).Params); break; - case ContractTree::OperationType::READ: - case ContractTree::OperationType::WRITE: + case ContractTree::FormulaType::READ: + case ContractTree::FormulaType::WRITE: param.push_back(static_cast(*relOp.Forbidden).contrP); param.push_back(static_cast(*relOp.Forbidden).contrParamAccess); break; @@ -193,7 +193,7 @@ ContractVerifierReleasePass::ReleaseStatus ContractVerifierReleasePass::checkRel } bool isTagRel = false; - if (relOp.Until->type() == OperationType::CALLTAG) { + if (relOp.Until->type() == FormulaType::CALLTAG) { isTagRel = true; } std::string releaseFunc = static_cast(*relOp.Until).Function; diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index fc1fa46..ee6cb4a 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -248,19 +248,25 @@ Constant* InstrumentPass::createFormulaGlobal(Module& M, std::shared_ptr op) { Constant* data = Null_Const; - std::string name; + std::string name = "UNKNOWN"; switch (op->type()) { - case OperationType::READ: - case OperationType::WRITE: { + case FormulaType::AND: + case FormulaType::OR: + case FormulaType::XOR: + // Should not happen here! + errs() << "Unexpected connective in createOperationGlobal!\n"; + break; + case FormulaType::READ: + case FormulaType::WRITE: { std::shared_ptr rwOP = static_pointer_cast(op); - Constant* isWrite = ConstantInt::getBool(Bool_Type, op->type() == OperationType::WRITE); + Constant* isWrite = ConstantInt::getBool(Bool_Type, op->type() == FormulaType::WRITE); ConstantInt* const_paramacc = ConstantInt::get(Int_Type, (int)rwOP->contrParamAccess); ConstantInt* const_idx = ConstantInt::get(Int_Type, (int)rwOP->contrP); data = ConstantStruct::get(RWOp_Type, {const_idx, const_paramacc, isWrite}); name = "CONTR_RWOP"; break; } - case OperationType::CALL: { + case FormulaType::CALL: { std::shared_ptr cOP = static_pointer_cast(op); Function* F = M.getFunction(cOP->Function) ? M.getFunction(cOP->Function) : M.getFunction(StringRef(cOP->Function).lower() + "_"); if (!F) WithColor::warning() << "Specified function \"" << cOP->Function << "\" in calloperation does not exist or unused in module. This may cause issues for instrumentation.\n"; @@ -271,7 +277,7 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr cOP = static_pointer_cast(op); data = createConstantGlobal(M, ConstantDataArray::getString(M.getContext(), cOP->Function), "CONTR_TAG_STR_" + cOP->Function); std::pair paramGlobal = createParamList(M, cOP->Params); @@ -279,7 +285,7 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr rOP = static_pointer_cast(op); Constant* forbidden_op = createOperationGlobal(M, rOP->Forbidden); Constant* forb_type = ConstantInt::get(Int_Type, (int64_t)rOP->Forbidden->type()); From 4bbd731d9a767c7e1bfc77a1fef9cc1e98215f19 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 9 Mar 2026 10:39:08 +0100 Subject: [PATCH 016/125] Fix dynamic analysis instr --- Include/DynamicAnalysis.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Include/DynamicAnalysis.h b/Include/DynamicAnalysis.h index e06abd4..77599f1 100644 --- a/Include/DynamicAnalysis.h +++ b/Include/DynamicAnalysis.h @@ -54,7 +54,20 @@ struct ReleaseOp_t { }; // Number must match those defined in enums in ContractTree.hpp (operation + connective)! -enum ContractConnective : int32_t { UNARY_READ = 0, UNARY_WRITE = 1, UNARY_CALL = 2, UNARY_CALLTAG = 3, UNARY_RELEASE = 4, AND = 5, OR = 6, XOR = 7 }; +enum ContractConnective : int32_t { + // Connectives + AND, + OR, + XOR, + // Operations + UNARY_READ, + UNARY_WRITE, + UNARY_ALLOC, + UNARY_CALL, + UNARY_CALLTAG, + UNARY_RELEASE, + UNARY_PARAM +}; struct ContractFormula_t { ContractFormula_t* children; int32_t num_children; From d8c9a67d93a0d87819741082074b1c05ff236923 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 9 Mar 2026 14:26:55 +0100 Subject: [PATCH 017/125] Fix missing newline in test --- Tests/c/Correct-P2P.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/c/Correct-P2P.c b/Tests/c/Correct-P2P.c index 30f0ca0..a1fcd82 100644 --- a/Tests/c/Correct-P2P.c +++ b/Tests/c/Correct-P2P.c @@ -17,7 +17,7 @@ int main(int argc, char** argv) { buf[0] = 42; if (rank == 0) { MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &req); - printf("Buf: %d", buf[0]); + printf("Buf: %d\n", buf[0]); } else { MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); } From 873f20903077b08700793db5bd42f5796aeed26e Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 9 Mar 2026 14:39:42 +0100 Subject: [PATCH 018/125] Add contracts --- Scripts/gen_mpi_contr_h.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index fa4abd1..1014dff 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -335,7 +335,9 @@ def add_contract(func: str, scope: str, contr: str): paramerror_null = [ ("MPI_Initialized", 0), ("MPI_Send", 0), + ("MPI_Isend", 0), ("MPI_Recv", 0), + ("MPI_Irecv", 0), ("MPI_Sendrecv", 0), ("MPI_Win_create", 0), ("MPI_Win_allocate", 4), From fff94c925df8c604f3d3ffc0eec1b56d68cc2807 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 9 Mar 2026 15:03:11 +0100 Subject: [PATCH 019/125] Add dynamic param checks --- Dynamic/Analyses/ParamAnalysis.cpp | 35 +++++++++++++++++++++++++++ Dynamic/Analyses/ParamAnalysis.h | 26 ++++++++++++++++++++ Dynamic/CMakeLists.txt | 1 + Dynamic/Hooks.hpp | 14 ++++++++--- Include/DynamicAnalysis.h | 10 ++++++++ Passes/Instrument.cpp | 38 +++++++++++++++++++++++++++++- Passes/Instrument.hpp | 2 ++ 7 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 Dynamic/Analyses/ParamAnalysis.cpp create mode 100644 Dynamic/Analyses/ParamAnalysis.h diff --git a/Dynamic/Analyses/ParamAnalysis.cpp b/Dynamic/Analyses/ParamAnalysis.cpp new file mode 100644 index 0000000..6adae5e --- /dev/null +++ b/Dynamic/Analyses/ParamAnalysis.cpp @@ -0,0 +1,35 @@ +#include "ParamAnalysis.h" +#include "BaseAnalysis.h" +#include "DynamicAnalysis.h" +#include "../DynamicUtils.h" + +#include + +Fulfillment ParamAnalysis::functionCBImpl(void* const& func, CallsiteInfo const& callsite) { + if (func != func_supplier) return Fulfillment::UNKNOWN; + + for (const ParamReq_t* req : param_requirements) { + switch (req->comparator) { + case Comparator::NEQ: + if (callsite.params[idx].value != req->value) continue; + return Fulfillment::VIOLATED; + case Comparator::GTEQ: + if (callsite.params[idx].value >= req->value) continue; + return Fulfillment::VIOLATED; + case Comparator::GT: + if (callsite.params[idx].value > req->value) continue; + return Fulfillment::VIOLATED; + case Comparator::LTEQ: + if (callsite.params[idx].value <= req->value) continue; + return Fulfillment::VIOLATED; + case Comparator::LT: + if (callsite.params[idx].value < req->value) continue; + return Fulfillment::VIOLATED; + case Comparator::EXEQ: + // EXEQ is the exception (pun), it overrides other forbidden values. + if (callsite.params[idx].value == req->value) return Fulfillment::FULFILLED; + continue; + } + } + __builtin_unreachable(); +} diff --git a/Dynamic/Analyses/ParamAnalysis.h b/Dynamic/Analyses/ParamAnalysis.h new file mode 100644 index 0000000..3df2a7d --- /dev/null +++ b/Dynamic/Analyses/ParamAnalysis.h @@ -0,0 +1,26 @@ +#pragma once + +#include "BaseAnalysis.h" +#include "DynamicAnalysis.h" +#include + +struct ParamAnalysis : BaseAnalysis { + public: + ParamAnalysis(void const* _func_supplier, ParamOp_t* paramop) : idx(paramop->idx), func_supplier(_func_supplier) { + for (int i = 0; i < paramop->num_reqs; i++) { + param_requirements.push_back(¶mop->requirements[i]); + } + } + + ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } + ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; // Evidently none of the callsites were erroneous + + constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false}; } + + private: + // Configuration + void const* func_supplier; + int const idx; + std::vector param_requirements; +}; diff --git a/Dynamic/CMakeLists.txt b/Dynamic/CMakeLists.txt index 814b014..0da171f 100644 --- a/Dynamic/CMakeLists.txt +++ b/Dynamic/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(CoVerDynamicAnalyzer STATIC + Analyses/ParamAnalysis.cpp Analyses/PostCallAnalysis.cpp Analyses/PreCallAnalysis.cpp Analyses/ReleaseAnalysis.cpp diff --git a/Dynamic/Hooks.hpp b/Dynamic/Hooks.hpp index f99d9aa..81ba842 100644 --- a/Dynamic/Hooks.hpp +++ b/Dynamic/Hooks.hpp @@ -12,6 +12,7 @@ #include "Analyses/BaseAnalysis.h" #include "DynamicUtils.h" +#include "Analyses/ParamAnalysis.h" #include "Analyses/PreCallAnalysis.h" #include "Analyses/PostCallAnalysis.h" #include "Analyses/ReleaseAnalysis.h" @@ -26,7 +27,7 @@ namespace { std::unordered_map> contrs; - using AnalysisVariant = std::variant; + using AnalysisVariant = std::variant; struct AnalysisPair { ContractFormula_t* formula; @@ -143,6 +144,9 @@ namespace { if (isPre) DynamicUtils::createMessage("Did not expect releaseop in precond!"); else addAnalysis(form, func_supplier, (ReleaseOp_t*)form->data); break; + case UNARY_PARAM: + if (isPre) addAnalysis(form, func_supplier, (ParamOp_t*)form->data); + else DynamicUtils::createMessage("Did not expect paramop in postcond!"); default: DynamicUtils::createMessage("Unknown top-level operation!"); break; @@ -158,15 +162,19 @@ namespace { ErrorMessage msg; msg.msg = {std::string("Operation Message (if defined) or contract string: ") + form->msg}; switch (form->conn) { + #warning this should really be in the analyses themselves case UNARY_CALL: case UNARY_CALLTAG: { CallTagOp_t* cOP = (CallTagOp_t*)form->data; msg.msg.push_back(std::string("Did not find call to ") + cOP->target_tag); break; } + case UNARY_PARAM: { + msg.msg.push_back("Invalid param!"); + break; + } case UNARY_RELEASE: { - ReleaseOp_t* rOP = (ReleaseOp_t*)form->data; - msg.msg.push_back(std::string("Found forbidden operation!")); + msg.msg.push_back("Found forbidden operation!"); break; } default: __builtin_unreachable(); diff --git a/Include/DynamicAnalysis.h b/Include/DynamicAnalysis.h index 77599f1..4d1fd4f 100644 --- a/Include/DynamicAnalysis.h +++ b/Include/DynamicAnalysis.h @@ -29,6 +29,7 @@ struct CallParam_t { int32_t contrP; ParamAccess accType; }; +enum Comparator : int32_t { NEQ, GT, GTEQ, LT, LTEQ, EXEQ }; struct RWOp_t { int32_t idx; @@ -52,6 +53,15 @@ struct ReleaseOp_t { void** forbidden_op; int32_t forbidden_op_kind; }; +struct ParamReq_t { + const Comparator comparator; + const void* value; +}; +struct ParamOp_t { + const int32_t idx; + const ParamReq_t* requirements; + const int32_t num_reqs; +}; // Number must match those defined in enums in ContractTree.hpp (operation + connective)! enum ContractConnective : int32_t { diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index ee6cb4a..a2ecbc5 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -285,7 +285,7 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr rOP = static_pointer_cast(op); Constant* forbidden_op = createOperationGlobal(M, rOP->Forbidden); Constant* forb_type = ConstantInt::get(Int_Type, (int64_t)rOP->Forbidden->type()); @@ -294,6 +294,36 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr pOP = static_pointer_cast(op); + std::vector reqCs; + for (std::pair req : pOP->reqs) { + Constant* var = Null_Const; + try { + int ivalue = std::stoi(req.second); + var = ConstantInt::get(Type::getInt64Ty(M.getContext()), ivalue); + var = ConstantExpr::getIntToPtr(var, Ptr_Type); + } catch(std::exception& e) { + if (!DB->ContractVariableData.contains(req.second)) { + errs() << "Undefined non-constint contract value identifier \"" << req.second << "\"!\n"; + errs() << "Param Requirement will not be instrumented!\n"; + continue; + } + if (isa(DB->ContractVariableData[req.second])) var = (Constant*)DB->ContractVariableData[req.second]; + if (isa(var)) var = ConstantExpr::getIntToPtr(var, Ptr_Type); + if (!isa(var)) { + errs() << "Weird param error in instr pass\n"; + } + } + reqCs.push_back(ConstantStruct::get(ParamReq_Type, {ConstantInt::get(Int_Type, req.first), var})); + } + Constant* reqsC = ConstantArray::get(ArrayType::get(ParamReq_Type, reqCs.size()), reqCs); + reqsC = createConstantGlobalUnique(M, reqsC, "CONTR_PARAM_REQS"); + data = ConstantStruct::get(ParamOp_Type, {ConstantInt::get(Int_Type, pOP->idx), reqsC, ConstantInt::get(Int_Type, reqCs.size())}); + name = "CONTR_PARAMOP"; + break; + } } return createConstantGlobalUnique(M, data, name); } @@ -334,6 +364,9 @@ void InstrumentPass::createTypes(Module& M) { RWOp_Type = StructType::create(M.getContext(), "RWOp_t"); RWOp_Type->setBody({Int_Type, Int_Type, Bool_Type}); // idx, paramaccess, isWrite + ParamOp_Type = StructType::create(M.getContext(), "ParamOp_t"); + ParamOp_Type->setBody({Int_Type, Ptr_Type, Int_Type}); // idx, list of reqs, num reqs + // Composite Types Tag_Type = StructType::create(M.getContext(), "Tag_t"); Tag_Type->setBody({Ptr_Type, Int_Type}); // tag str, param num @@ -350,6 +383,9 @@ void InstrumentPass::createTypes(Module& M) { Ref_Type = StructType::create(M.getContext(), "Reference_t"); Ref_Type->setBody({Ptr_Type, Ptr_Type}); // char* file ref, char* type + ParamReq_Type = StructType::create(M.getContext(), "ParamReq_t"); + ParamReq_Type->setBody({Int_Type, Ptr_Type}); // Comparator, Value + DB_Type = StructType::create(M.getContext(), "ContractDB_t"); DB_Type->setBody({Ptr_Type, Int_Type, Tags_Type, Ptr_Type, Int_Type}); // contract list, num elems, tag container, reference list, num refs } diff --git a/Passes/Instrument.hpp b/Passes/Instrument.hpp index 189ca48..b52a8fa 100644 --- a/Passes/Instrument.hpp +++ b/Passes/Instrument.hpp @@ -61,8 +61,10 @@ class InstrumentPass : public PassInfoMixin { StructType* CallTagOp_Type; StructType* ReleaseOp_Type; StructType* RWOp_Type; + StructType* ParamOp_Type; StructType* Contract_Type; StructType* Ref_Type; + StructType* ParamReq_Type; Constant* Null_Const; // Helpers From ee46c5941e6bcc152be240c9102648eb0875f6be Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 9 Mar 2026 15:03:21 +0100 Subject: [PATCH 020/125] Add more contracts --- Scripts/gen_mpi_contr_h.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 1014dff..123bb8e 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -300,7 +300,9 @@ def add_contract(func: str, scope: str, contr: str): # Parameter Errors paramerror_comm = [ ("MPI_Send", 5), + ("MPI_Isend", 5), ("MPI_Recv", 5), + ("MPI_Irecv", 5), ("MPI_Sendrecv", 10), ("MPI_Allgather", 6), ("MPI_Cart_get", 0), From 900150525134e31e44dcb0e1d2dfa41d15724b53 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 10 Mar 2026 16:45:42 +0100 Subject: [PATCH 021/125] Fix broken contract --- Scripts/gen_mpi_contr_h.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 123bb8e..f243799 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -328,7 +328,7 @@ def add_contract(func: str, scope: str, contr: str): # MPI_STATUSES_IGNORE == NULL == MPI_STATUS_IGNORE in OpenMPI. Add exception for MPI_STATUS_IGNORE paramerror_status = [ - ("MPI_Recv", 6) + ("MPI_Wait", 1), ] for func, idx in paramerror_status: add_contract(func, "PRE", f"param!({idx}:^=MPI_STATUS_IGNORE,!=NULL,!=MPI_STATUSES_IGNORE) MSG \"Status is invalid\"") From 8970617e6adfce3ebf218d7d40e18db4c31b3721 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 11 Mar 2026 12:38:15 +0100 Subject: [PATCH 022/125] Set wrap target in terminal as well --- .vscode/settings.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2161cc5..d5c37c1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,4 +18,9 @@ "COVER_WRAP_TARGET_mpicc": "~/Desktop/ompi/install/bin/mpicc", "COVER_WRAP_TARGET_mpicxx": "~/Desktop/ompi/install/bin/mpicxx", }, + "terminal.integrated.env.linux": { + "COVER_WRAP_TARGET_mpifort": "~/Desktop/ompi/install/bin/mpifort", + "COVER_WRAP_TARGET_mpicc": "~/Desktop/ompi/install/bin/mpicc", + "COVER_WRAP_TARGET_mpicxx": "~/Desktop/ompi/install/bin/mpicxx", + }, } From ef2152200d26b972e9dbacf265a58bb04319a661 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 11 Mar 2026 12:43:47 +0100 Subject: [PATCH 023/125] Add support for named constants in Fortran param checks --- Dynamic/Analyses/ParamAnalysis.cpp | 18 ++++-- Dynamic/DynamicUtils.cpp | 18 +++--- Dynamic/DynamicUtils.h | 3 + Include/Contracts.F90 | 14 ++++- Include/DynamicAnalysis.h | 1 + Passes/ContractManager.cpp | 79 ++++++++++++++++++++++--- Passes/ContractManager.hpp | 4 +- Passes/ContractVerifierParam.cpp | 93 ++++++++++++++++++++---------- Passes/ContractVerifierParam.hpp | 3 +- Passes/Instrument.cpp | 15 +++-- Scripts/gen_mpi_contr_h.py | 77 ++++++++++++++++++++----- 11 files changed, 246 insertions(+), 79 deletions(-) diff --git a/Dynamic/Analyses/ParamAnalysis.cpp b/Dynamic/Analyses/ParamAnalysis.cpp index 6adae5e..611bb35 100644 --- a/Dynamic/Analyses/ParamAnalysis.cpp +++ b/Dynamic/Analyses/ParamAnalysis.cpp @@ -9,25 +9,31 @@ Fulfillment ParamAnalysis::functionCBImpl(void* const& func, CallsiteInfo const& if (func != func_supplier) return Fulfillment::UNKNOWN; for (const ParamReq_t* req : param_requirements) { + const void* act_req = req->value; + const void* act_callp = callsite.params[idx].value; + if (req->need_deref) { + act_req = (const void*)DynamicUtils::TruncateBits(*(int64_t*)act_req, callsite.params[idx].size); + act_callp = (const void*)DynamicUtils::TruncateBits(*(int64_t*)act_callp, callsite.params[idx].size); + } switch (req->comparator) { case Comparator::NEQ: - if (callsite.params[idx].value != req->value) continue; + if (act_callp != act_req) continue; return Fulfillment::VIOLATED; case Comparator::GTEQ: - if (callsite.params[idx].value >= req->value) continue; + if (act_callp >= act_req) continue; return Fulfillment::VIOLATED; case Comparator::GT: - if (callsite.params[idx].value > req->value) continue; + if (act_callp > act_req) continue; return Fulfillment::VIOLATED; case Comparator::LTEQ: - if (callsite.params[idx].value <= req->value) continue; + if (act_callp <= act_req) continue; return Fulfillment::VIOLATED; case Comparator::LT: - if (callsite.params[idx].value < req->value) continue; + if (act_callp < act_req) continue; return Fulfillment::VIOLATED; case Comparator::EXEQ: // EXEQ is the exception (pun), it overrides other forbidden values. - if (callsite.params[idx].value == req->value) return Fulfillment::FULFILLED; + if (act_callp == act_req) return Fulfillment::FULFILLED; continue; } } diff --git a/Dynamic/DynamicUtils.cpp b/Dynamic/DynamicUtils.cpp index 2ee976c..6841524 100644 --- a/Dynamic/DynamicUtils.cpp +++ b/Dynamic/DynamicUtils.cpp @@ -33,19 +33,19 @@ namespace { } return result; } +} + +namespace DynamicUtils { + std::unordered_map> func_to_tags; + std::unordered_map> tags_to_func; - uint64_t truncate_bits(uintptr_t const val, int const bit_width) { + uint64_t TruncateBits(uintptr_t const val, int const bit_width) { // Mask off unused bits if (bit_width < 64) return val & ((1ULL << bit_width) - 1); return val; } -} - -namespace DynamicUtils { - std::unordered_map> func_to_tags; - std::unordered_map> tags_to_func; void Initialize(ContractDB_t const* DB) { // Initialize Tags @@ -62,11 +62,11 @@ namespace DynamicUtils { bool checkParamMatch(ParamAccess const& acc, ConcreteParam const& contrP, ConcreteParam const& callP) { switch (acc) { case ParamAccess::NORMAL: - return truncate_bits((uintptr_t)contrP.value, contrP.size) == truncate_bits((uintptr_t)callP.value, callP.size); + return TruncateBits((uintptr_t)contrP.value, contrP.size) == TruncateBits((uintptr_t)callP.value, callP.size); case ParamAccess::DEREF: - return truncate_bits(*(uintptr_t const*)contrP.value, callP.size) == (uintptr_t)callP.value; + return TruncateBits(*(uintptr_t const*)contrP.value, callP.size) == (uintptr_t)callP.value; case ParamAccess::ADDROF: - return truncate_bits(*(uintptr_t const*)callP.value, contrP.size) == (uintptr_t)contrP.value; + return TruncateBits(*(uintptr_t const*)callP.value, contrP.size) == (uintptr_t)contrP.value; } __builtin_unreachable(); } diff --git a/Dynamic/DynamicUtils.h b/Dynamic/DynamicUtils.h index 688cd53..7c7f0f7 100644 --- a/Dynamic/DynamicUtils.h +++ b/Dynamic/DynamicUtils.h @@ -44,6 +44,9 @@ struct std::hash { namespace DynamicUtils { // Initialize Utils void Initialize(ContractDB_t const* DB); + + // Truncate bits from a raw byte value + uint64_t TruncateBits(uintptr_t const val, int const bit_width); // Check if two parameters match bool checkParamMatch(ParamAccess const& acc, ConcreteParam const& contrP, ConcreteParam const& callP); diff --git a/Include/Contracts.F90 b/Include/Contracts.F90 index 2fbca59..508b0cd 100644 --- a/Include/Contracts.F90 +++ b/Include/Contracts.F90 @@ -1,11 +1,19 @@ -! This module defined Declare_Contract, -! which can then be used for the contract declarations by -! passing the function/subroutine as well as the contract string +! This module defines helper functions for contract declarations for CoVer module contract_helper interface + ! Declare_Contract allows the definition of contracts. + ! First argument must be the API function to apply the contract to. + ! Second argument must be the contract string literal subroutine Declare_Contract(funcPtr, contrString) procedure() :: funcPtr character(len=*), intent(in) :: contrString end subroutine + ! Declare_Value allows exposing constant values to contracts for parameter checking. + ! First argument must be a string literal and will be the name used in the relevant contracts + ! Second argument is the value itself. + subroutine Declare_Value(name, value) + character(len=*), intent(in) :: name + class(*), optional, intent(in) :: value(..) + end subroutine end interface end module diff --git a/Include/DynamicAnalysis.h b/Include/DynamicAnalysis.h index 4d1fd4f..68333ff 100644 --- a/Include/DynamicAnalysis.h +++ b/Include/DynamicAnalysis.h @@ -56,6 +56,7 @@ struct ReleaseOp_t { struct ParamReq_t { const Comparator comparator; const void* value; + const bool need_deref; }; struct ParamOp_t { const int32_t idx; diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index a20db7f..07b2400 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -62,7 +62,7 @@ ContractManagerAnalysis::ContractDatabase ContractManagerAnalysis::run(Module &M val = CE->getOperand(0); } } - curDatabase.ContractVariableData[name.str()] = val; + addValueDefinition(name.str(), val); } } @@ -76,7 +76,7 @@ ContractManagerAnalysis::ContractDatabase ContractManagerAnalysis::run(Module &M void ContractManagerAnalysis::extractFromAnnotations(const Module& M) { GlobalVariable* Annotations = M.getGlobalVariable("llvm.global.annotations"); if (Annotations == nullptr) { - errs() << "Note: No string annotations found.\n"; + errs() << "Note: No contract annotations found in Function declarations.\n"; return; } @@ -99,17 +99,17 @@ void ContractManagerAnalysis::extractFromFunction(Module& M) { errs() << "Contract definition by function body failed, function body not found!\n"; continue; } - const BasicBlock& BB = F.getEntryBlock(); // Exactly one basic block allowed, so this is ok - for (const Instruction& I : BB) { - if (const CallBase* CB = dyn_cast(&I)) { + BasicBlock& BB = F.getEntryBlock(); // Exactly one basic block allowed, so this is ok + for (Instruction& I : BB) { + if (CallBase* CB = dyn_cast(&I)) { // Only care about this intrinsic #warning TODO probably should figure out a less hacky way. if (CB->getCalledFunction()->getName() != "llvm.memmove.p0.p0.i64" && CB->getCalledFunction()->getName() != "llvm.memcpy.p0.p0.i64") continue; // Add CONTRACT { ... } brace. Its explicitly needed for C(++) to make sure we are not parsing irrelevant stuff, // but for fortran its already implicit in declare_contract, making it superfluous - std::string CallStr = "CONTRACT { " + ((ConstantDataArray*)((GlobalVariable*)CB->getArgOperand(1))->getInitializer())->getAsString().str() + " }"; + std::string CallStr = ((ConstantDataArray*)((GlobalVariable*)CB->getArgOperand(1))->getInitializer())->getAsString().str(); // Call is from memmove -> insertvalue -> extractvalue -> funccall. on -O0, and memcpy -> funccall on -O1 and above - const CallBase* ContrCall = (CallBase*)(isa(*CB->getArgOperand(0)->user_begin()) ? *CB->getArgOperand(0)->user_begin() : *CB->getArgOperand(0)->user_begin()->user_begin()->user_begin()->user_begin()); + CallBase* ContrCall = (CallBase*)(isa(*CB->getArgOperand(0)->user_begin()) ? *CB->getArgOperand(0)->user_begin() : *CB->getArgOperand(0)->user_begin()->user_begin()->user_begin()->user_begin()); if (ContrCall->getCalledOperand()->getName() == "declare_contract_") { const Function* ContrSup = (Function*)ContrCall->getArgOperand(0); if (ContrSup->hasOneUser()) continue; // Only used here where the contract is defined. No need to verify. @@ -123,7 +123,63 @@ void ContractManagerAnalysis::extractFromFunction(Module& M) { } } if (!has_callsite) continue; - addContract(CallStr, (Function*)(ContrCall->getArgOperand(0))); + addContract("CONTRACT { " + CallStr + " }", (Function*)(ContrCall->getArgOperand(0))); + } else if (ContrCall->getCalledOperand()->getName() == "declare_value_") { + #warning really super duper should find out a less hacky way + // If contr value is nullptr, its trivial + if (ContrCall->getArgOperand(1) == ConstantPointerNull::getNullValue(PointerType::get(M.getContext(), 0))) { + addValueDefinition(CallStr, ContrCall->getArgOperand(1)); + continue; + } + + // This is the start of a dirty heuristic. If it breaks in the future, good luck to you + // Start at the third instr after memmove. This skips the string parameter init (putting it in a struct with its length) + Instruction* ImportantInst = CB->getNextNode()->getNextNode()->getNextNode(); + Value* result = nullptr; + + // Possibility 1: StoreInst saving an integer into a ptr, where the integer is what we want + if (StoreInst* SI = dyn_cast(ImportantInst)) { + if (isa(SI->getValueOperand())) { + result = SI->getValueOperand(); + } + } + + // Possibility 2: SExtInst, then stuff, then an InsertValueInst with index 0 where that is what we want + if (SExtInst* SEI = dyn_cast(ImportantInst)) { + for (Instruction* cur = SEI; cur && !isa(cur); cur = cur->getNextNode()) { + if (InsertValueInst* IVI = dyn_cast(cur)) { + if (IVI->hasIndices() && IVI->getIndices()[0] == 0) { + result = IVI->getInsertedValueOperand(); + } + } + } + } + + // Possibility 3: GEPInst, directly after a StoreInst or LoadInst with correct value + if (GetElementPtrInst* GEP = dyn_cast(ImportantInst)) { + if (StoreInst* SI = dyn_cast(GEP->getNextNode())) { + result = SI->getValueOperand(); + } else if (LoadInst* LI = dyn_cast(GEP->getNextNode())) { + result = LI->getPointerOperand(); + } + } + + // Check for Fortran mangling stuff + if (!result) { + errs() << "Could not decipher call to Declare_Value for \n" << CallStr << "!\n"; + } else { + if (result->getName().starts_with("_QQ") && isa(result)) { + StringRef result_stem = result->getName().split('.').first; + for (GlobalVariable& GV : M.globals()) { + if (GV.getName().starts_with(result_stem)) { + errs() << "Note: Added " << GV.getName() << " as mangled alias to " << result->getName() << " for contract value " << CallStr << "\n"; + addValueDefinition(CallStr, &GV); + } + } + } else { + addValueDefinition(CallStr, result); + } + } } } } @@ -134,6 +190,9 @@ void ContractManagerAnalysis::extractFromFunction(Module& M) { // Remove unneeded functions for (Function* F : to_remove) F->eraseFromParent(); + if (to_remove.empty()) { + errs() << "Note: No contract declarations found using Declare_Contract calls\n"; + } } void ContractManagerAnalysis::addContract(std::string contract, Function* F) { @@ -166,6 +225,10 @@ void ContractManagerAnalysis::addContract(std::string contract, Function* F) { curDatabase.Tags[newCtr.F].insert(curDatabase.Tags[newCtr.F].end(), newCtr.Data.Tags.begin(), newCtr.Data.Tags.end()); } +void ContractManagerAnalysis::addValueDefinition(std::string name, Value* val) { + curDatabase.ContractVariableData[name].insert(val); +} + const std::vector> ContractManagerAnalysis::linearizeContractFormula(const std::shared_ptr contrF) { if (contrF->Children.empty()) { return { std::static_pointer_cast(contrF) }; diff --git a/Passes/ContractManager.hpp b/Passes/ContractManager.hpp index 474dfb0..85d07b0 100644 --- a/Passes/ContractManager.hpp +++ b/Passes/ContractManager.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -40,7 +41,7 @@ class ContractManagerAnalysis : public AnalysisInfoMixin Contracts; // For postprocessing only std::vector LinearizedContracts; // For verification passes std::map> Tags; - std::map ContractVariableData; + std::map> ContractVariableData; std::chrono::time_point start_time; bool allowMultiReports = false; Json::Value processedReports; @@ -60,6 +61,7 @@ class ContractManagerAnalysis : public AnalysisInfoMixin #include #include +#include +#include +#include #include #include +#include #include #include #include @@ -46,15 +50,15 @@ PreservedAnalyses ContractVerifierParamPass::run(Module &M, Fulfillment resf = Fulfillment::FULFILLED; // Perform the check on each callsite - for (const User* U : C.F->users()) { - if (const CallBase* CB = dyn_cast(U)) { + for (User* U : C.F->users()) { + if (CallBase* CB = dyn_cast(U)) { for (std::pair req : ParamOp->reqs) { // Figure out value(s) to check against - Value* var; + std::set vars; try { // First, check if constant value provided int ivalue = std::stoi(req.second); - var = ConstantInt::get(Type::getInt64Ty(M.getContext()), ivalue); + vars = {ConstantInt::get(Type::getInt64Ty(M.getContext()), ivalue)}; } catch(std::exception& e) { // Otherwise, check against value database if (!DB.ContractVariableData.contains(req.second)) { @@ -62,12 +66,12 @@ PreservedAnalyses ContractVerifierParamPass::run(Module &M, errs() << "Requirement will not be analysed!\n"; continue; } - var = DB.ContractVariableData[req.second]; + vars = DB.ContractVariableData[req.second]; } // Perform check std::string errInfo = ""; - Fulfillment f = checkParamReq(var, CB->getArgOperand(ParamOp->idx), req.first, errInfo); + Fulfillment f = checkParamReq(vars, CB, ParamOp->idx, req.first, errInfo); if (f == Fulfillment::BROKEN) { resf = Fulfillment::BROKEN; Expr->ErrorInfo->push_back({ @@ -132,35 +136,62 @@ Fulfillment compareCI(const ConstantInt* CI, const ConstantInt* CI2, Comparator } } -Fulfillment ContractVerifierParamPass::checkParamReq(const Value* var, const Value* callVal, Comparator comp, std::string& ErrInfo) { - if (callVal->getType()->isPointerTy()) { - switch (comp) { - case Comparator::NEQ: - if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { - ErrInfo = "Parameter matches or is alias to forbidden pointer value!"; - return Fulfillment::BROKEN; +Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, CallBase* call, int idx, Comparator comp, std::string& ErrInfo) { + for (Value* var : vars) { + Value* callVal = call->getArgOperand(idx); + if (AllocaInst* AI = dyn_cast(callVal)) { + for (Instruction* cur = call->getPrevNode(); cur && !isa(cur); cur = cur->getPrevNode()) { + if (GetElementPtrInst* GEP = dyn_cast(cur)) { + if (GEP->getPointerOperand() != callVal) continue; + if (LoadInst* LI = dyn_cast(cur->getNextNode())) { + callVal = LI->getPointerOperand(); + } } - return Fulfillment::FULFILLED; - case Comparator::EXEQ: - if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { - ErrInfo = "Note: Parameter matches or is alias to exception value."; + } + } + if (callVal->getType()->isPointerTy()) { + switch (comp) { + case Comparator::NEQ: + if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { + ErrInfo = "Parameter matches or is alias to forbidden pointer value!"; + return Fulfillment::BROKEN; + } return Fulfillment::FULFILLED; - } - // Not an exception. Continue analysis, so far no info gained - return Fulfillment::UNKNOWN; - default: - errs() << "Attempt to compare pointers! Not performing parameter analysis\n"; - return Fulfillment::UNKNOWN; + case Comparator::EXEQ: + if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { + ErrInfo = "Note: Parameter matches or is alias to exception value."; + return Fulfillment::FULFILLED; + } + // Not an exception. Continue analysis, so far no info gained + continue; + default: + // Check if we can salvage this and get a constant int result still, even if IR says its a pointer at that point + if (Instruction* I = dyn_cast(callVal)) { + MemoryDependenceResults& MDR = MAM->getResult(*I->getModule()).getManager().getResult(*I->getFunction()); + MemoryLocation Loc = MemoryLocation::getForArgument(call, idx, MAM->getResult(*I->getModule()).getManager().getResult(*call->getFunction())); + MemDepResult x = MDR.getPointerDependencyFrom(Loc, true, call->getIterator(), call->getParent()); + if (x.getInst()) { + if (StoreInst* S = dyn_cast(x.getInst())) { + if (isa(S->getValueOperand())) { + callVal = S->getValueOperand(); + break; + } + } + } + } + errs() << "Attempt to compare pointers! Not performing parameter analysis\n"; + return Fulfillment::UNKNOWN; + } } - } - // Ensured that !isPtr, can get constinfo if present - if (const ConstantInt* callCI = dyn_cast(callVal)) { - if (const ConstantInt* varCI = dyn_cast(var)) { - Fulfillment f = compareCI(callCI, varCI, comp); - if (f == Fulfillment::BROKEN) { - ErrInfo = createCompErr(comp, callCI, varCI); + // Ensured that !isPtr, can get constinfo if present + if (const ConstantInt* callCI = dyn_cast(callVal)) { + if (const ConstantInt* varCI = dyn_cast(var)) { + Fulfillment f = compareCI(callCI, varCI, comp); + if (f == Fulfillment::BROKEN) { + ErrInfo = createCompErr(comp, callCI, varCI); + return f; + } } - return f; } } diff --git a/Passes/ContractVerifierParam.hpp b/Passes/ContractVerifierParam.hpp index 85e34a9..e1cb7ad 100644 --- a/Passes/ContractVerifierParam.hpp +++ b/Passes/ContractVerifierParam.hpp @@ -3,6 +3,7 @@ #include "ContractTree.hpp" #include "llvm/IR/PassManager.h" #include +#include namespace llvm { @@ -12,7 +13,7 @@ class ContractVerifierParamPass : public PassInfoMixin vars, CallBase* call, int idx, ContractTree::Comparator comp, std::string& ErrInfo); }; } // namespace llvm diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index a2ecbc5..5564a17 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -304,19 +304,22 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptrContractVariableData.contains(req.second)) { errs() << "Undefined non-constint contract value identifier \"" << req.second << "\"!\n"; errs() << "Param Requirement will not be instrumented!\n"; continue; } - if (isa(DB->ContractVariableData[req.second])) var = (Constant*)DB->ContractVariableData[req.second]; - if (isa(var)) var = ConstantExpr::getIntToPtr(var, Ptr_Type); - if (!isa(var)) { - errs() << "Weird param error in instr pass\n"; + for (Value* V : DB->ContractVariableData[req.second]) { + if (isa(V)) var = (Constant*)V; + if (isa(var)) var = ConstantExpr::getIntToPtr(var, Ptr_Type); + if (!isa(var)) { + errs() << "Weird param error in instr pass\n"; + } + reqCs.push_back(ConstantStruct::get(ParamReq_Type, {ConstantInt::get(Int_Type, req.first), var, ConstantInt::get(Bool_Type, var->getName().starts_with("_QQ"))})); } } - reqCs.push_back(ConstantStruct::get(ParamReq_Type, {ConstantInt::get(Int_Type, req.first), var})); } Constant* reqsC = ConstantArray::get(ArrayType::get(ParamReq_Type, reqCs.size()), reqCs); reqsC = createConstantGlobalUnique(M, reqsC, "CONTR_PARAM_REQS"); @@ -384,7 +387,7 @@ void InstrumentPass::createTypes(Module& M) { Ref_Type->setBody({Ptr_Type, Ptr_Type}); // char* file ref, char* type ParamReq_Type = StructType::create(M.getContext(), "ParamReq_t"); - ParamReq_Type->setBody({Int_Type, Ptr_Type}); // Comparator, Value + ParamReq_Type->setBody({Int_Type, Ptr_Type, Bool_Type}); // Comparator, Value DB_Type = StructType::create(M.getContext(), "ContractDB_t"); DB_Type->setBody({Ptr_Type, Int_Type, Tags_Type, Ptr_Type, Int_Type}); // contract list, num elems, tag container, reference list, num refs diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index f243799..8a132a8 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -393,6 +393,66 @@ def add_contract(func: str, scope: str, contr: str): for func, idx in paramerror_op: add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_OP_NULL) MSG \"Operation is invalid\"") +def get_param_values(lang: str) -> str: + param_values = { + "NULL": { + "c": "NULL", + "fort": "NULL()", + }, + "MPI_PROC_NULL": { + "c": "MPI_PROC_NULL", + "fort": "MPI_PROC_NULL", + }, + "MPI_ANY_SOURCE": { + "c": "MPI_ANY_SOURCE", + "fort": "MPI_ANY_SOURCE", + }, + "MPI_ANY_TAG": { + "c": "MPI_ANY_TAG", + "fort": "MPI_ANY_TAG", + }, + "MPI_IN_PLACE": { + "c": "MPI_IN_PLACE", + "fort": "MPI_IN_PLACE", + }, + "MPI_REQUEST_NULL": { + "c": "MPI_REQUEST_NULL", + "fort": "MPI_REQUEST_NULL", + }, + "MPI_OP_NULL": { + "c": "MPI_OP_NULL", + "fort": "MPI_OP_NULL", + }, + "MPI_COMM_NULL": { + "c": "MPI_COMM_NULL", + "fort": "MPI_COMM_NULL", + }, + "MPI_STATUS_IGNORE": { + "c": "MPI_STATUS_IGNORE", + "fort": "MPI_STATUS_IGNORE", + }, + "MPI_STATUSES_IGNORE": { + "c": "MPI_STATUSES_IGNORE", + "fort": "MPI_STATUSES_IGNORE", + }, + "MPI_DATATYPE_NULL": { + "c": "MPI_DATATYPE_NULL", + "fort": "MPI_DATATYPE_NULL", + } + } + output_pre = "Constant values needed for contracts\n" + output_templ = "" + output_str = "" + if lang == "c": + output_pre = "// " + output_pre + output_templ = "CONTRACT_VALUE_PAIR(@VAL_NAME@,@VAL_DATA@)" + if lang == "fort": + output_pre = "! " + output_pre + output_templ = "call Declare_Value(\"@VAL_NAME@\",@VAL_DATA@)" + for val_name, val_data in param_values.items(): + output_str += output_templ.replace("@PRE@", output_pre).replace("@VAL_NAME@", val_name).replace("@VAL_DATA@", val_data[lang]) + "\n" + return output_pre + output_str + # Output file boilerplate_header_c = f""" // Automatically generated by {os.path.basename(__file__)} @@ -406,18 +466,7 @@ def add_contract(func: str, scope: str, contr: str): #define MACRO_SAFETY(x) (x) -// Constant values needed for contracts -CONTRACT_VALUE_PAIR(MPI_PROC_NULL, MPI_PROC_NULL) -CONTRACT_VALUE_PAIR(MPI_ANY_SOURCE, MPI_ANY_SOURCE) -CONTRACT_VALUE_PAIR(MPI_ANY_TAG, MPI_ANY_TAG) -CONTRACT_VALUE_PAIR(NULL, NULL) -CONTRACT_VALUE_PAIR(MPI_IN_PLACE,MPI_IN_PLACE) -CONTRACT_VALUE_PAIR(MPI_REQUEST_NULL,MPI_REQUEST_NULL) -CONTRACT_VALUE_PAIR(MPI_OP_NULL,MPI_OP_NULL) -CONTRACT_VALUE_PAIR(MPI_COMM_NULL,MPI_COMM_NULL) -CONTRACT_VALUE_PAIR(MPI_STATUSES_IGNORE,MPI_STATUSES_IGNORE) -CONTRACT_VALUE_PAIR(MPI_STATUS_IGNORE,MPI_STATUS_IGNORE) -CONTRACT_VALUE_PAIR(MPI_DATATYPE_NULL,MPI_DATATYPE_NULL) +{get_param_values("c")} void* calloc(size_t num, size_t size) CONTRACT( POST {{ alloc!(99) }}); void* malloc(size_t size) CONTRACT( POST {{ alloc!(99) }}); @@ -435,8 +484,8 @@ def add_contract(func: str, scope: str, contr: str): use contract_helper """ -header_output_fort = boilerplate_header_fort + " use mpi\n implicit none\n" -header_output_fort_f08 = boilerplate_header_fort + " use mpi_f08\n implicit none\n" +header_output_fort = boilerplate_header_fort + f" use mpi\n implicit none\n\n{get_param_values("fort")}\n\n" +header_output_fort_f08 = boilerplate_header_fort + f" use mpi_f08\n implicit none\n\n{get_param_values("fort")}\n\n" header_output_fort_f08ts = header_output_fort_f08 exclude_fortran = [ From ada9be5772c47fb0c148e44771b5c1a2b34f1cf6 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 11 Mar 2026 12:45:09 +0100 Subject: [PATCH 024/125] Consistently use implicit none in fortran tests --- Tests/fort/Correct-Minimal.F90 | 1 + Tests/fort/Correct-P2P.F90 | 2 ++ Tests/fort/PostCall-MissingFinalize.F90 | 1 + Tests/fort/PreCall-MissingInit.F90 | 1 + Tests/fort/Release-DataRace.F90 | 2 ++ 5 files changed, 7 insertions(+) diff --git a/Tests/fort/Correct-Minimal.F90 b/Tests/fort/Correct-Minimal.F90 index 5d6ac1e..c17abcc 100644 --- a/Tests/fort/Correct-Minimal.F90 +++ b/Tests/fort/Correct-Minimal.F90 @@ -3,6 +3,7 @@ program CorrectMinimal use mpi_f08 implicit none + integer :: ierr call MPI_Init(ierr) diff --git a/Tests/fort/Correct-P2P.F90 b/Tests/fort/Correct-P2P.F90 index 5a49f2e..e5c99e7 100644 --- a/Tests/fort/Correct-P2P.F90 +++ b/Tests/fort/Correct-P2P.F90 @@ -2,6 +2,8 @@ program main use mpi_f08 + implicit none + integer :: rank integer, pointer :: buf(:) type(MPI_Request) :: req diff --git a/Tests/fort/PostCall-MissingFinalize.F90 b/Tests/fort/PostCall-MissingFinalize.F90 index 2f45881..e58464f 100644 --- a/Tests/fort/PostCall-MissingFinalize.F90 +++ b/Tests/fort/PostCall-MissingFinalize.F90 @@ -3,6 +3,7 @@ program PostCallMissingFinalize use mpi_f08 implicit none + integer :: ierr call MPI_Init(ierr) diff --git a/Tests/fort/PreCall-MissingInit.F90 b/Tests/fort/PreCall-MissingInit.F90 index 2f02295..ca1afd9 100644 --- a/Tests/fort/PreCall-MissingInit.F90 +++ b/Tests/fort/PreCall-MissingInit.F90 @@ -3,6 +3,7 @@ program PreCallMissingInit use mpi_f08 implicit none + integer :: ierr call MPI_Finalize(ierr) diff --git a/Tests/fort/Release-DataRace.F90 b/Tests/fort/Release-DataRace.F90 index ae0dfac..25c2322 100644 --- a/Tests/fort/Release-DataRace.F90 +++ b/Tests/fort/Release-DataRace.F90 @@ -2,6 +2,8 @@ program main use mpi_f08 + implicit none + integer :: rank integer, pointer :: buf(:) type(MPI_Request) :: req From 00a6691bb687e66b4ba6c3f893dcae9d572b06c6 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 11 Mar 2026 12:59:06 +0100 Subject: [PATCH 025/125] Add test for param --- Tests/CMakeLists.txt | 1 + Tests/c/Param-InvalidComm.c | 38 ++++++++++++++++++++++++++++++++ Tests/c/Release-DataRace.c | 1 + Tests/fort/Param-InvalidComm.F90 | 36 ++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 Tests/c/Param-InvalidComm.c create mode 100644 Tests/fort/Param-InvalidComm.F90 diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 5340296..c5bd252 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -12,3 +12,4 @@ add_cover_test(Correct-P2P) add_cover_test(PostCall-MissingFinalize) add_cover_test(PreCall-MissingInit) add_cover_test(Release-DataRace) +add_cover_test(Param-InvalidComm) diff --git a/Tests/c/Param-InvalidComm.c b/Tests/c/Param-InvalidComm.c new file mode 100644 index 0000000..cb2783c --- /dev/null +++ b/Tests/c/Param-InvalidComm.c @@ -0,0 +1,38 @@ +// RUN: %clangContracts %run_common + +#include +#include +#include + +int main(int argc, char** argv) { + int rank; + int* buf; + MPI_Request req; + + MPI_Init(NULL, NULL); + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + buf = (int*)malloc(sizeof(int)); + buf[0] = 42; + if (rank == 0) { + MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_NULL, &req); + printf("Buf: %d\n", buf[0]); + } else { + MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); + } + MPI_Wait(&req, MPI_STATUS_IGNORE); + + MPI_Finalize(); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK: Contract violation detected! +// CHECK: Communicator is invalid +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK: Contract violation detected! +// CHECK: Communicator is invalid +// Dont check for analysis finished, MPI implementation might crash. diff --git a/Tests/c/Release-DataRace.c b/Tests/c/Release-DataRace.c index 37a368b..fc14a92 100644 --- a/Tests/c/Release-DataRace.c +++ b/Tests/c/Release-DataRace.c @@ -1,5 +1,6 @@ // RUN: %clangContracts %run_common +#include #include #include diff --git a/Tests/fort/Param-InvalidComm.F90 b/Tests/fort/Param-InvalidComm.F90 new file mode 100644 index 0000000..301b43d --- /dev/null +++ b/Tests/fort/Param-InvalidComm.F90 @@ -0,0 +1,36 @@ +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: rank + integer, pointer :: buf(:) + type(MPI_Request) :: req + + call MPI_Init() + + call MPI_Comm_rank(MPI_COMM_WORLD, rank) + + allocate(buf(1)) + buf(1) = 42 + if (rank == 0) then + call MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_NULL, req) + print *, "Buf: ", buf(1) + else + call MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, req) + end if + call MPI_Wait(req, MPI_STATUS_IGNORE) + + call MPI_Finalize() +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK: Contract violation detected! +! CHECK: Communicator is invalid +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK: Contract violation detected! +! CHECK: Communicator is invalid +! Dont check if analysis finished, MPI implementation might crash. From 5067069a7dbd02f58b4dbe909392b9b41e4a75e3 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 11 Mar 2026 13:16:15 +0100 Subject: [PATCH 026/125] Better integration with IDE --- .gitignore | 1 - .vscode/settings.json | 1 + Tests/CMakeLists.txt | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 9226101..570fd59 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ install/ .cache/ compile_commands.json .antlr/ -.vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json index d5c37c1..b069d3f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,4 +23,5 @@ "COVER_WRAP_TARGET_mpicc": "~/Desktop/ompi/install/bin/mpicc", "COVER_WRAP_TARGET_mpicxx": "~/Desktop/ompi/install/bin/mpicxx", }, + "cmake.ctest.testSuiteDelimiter": "-|::", } diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index c5bd252..3ee068b 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -2,8 +2,8 @@ set(COVER_INSTALLED_BINARIES_DIR "${CMAKE_INSTALL_PREFIX}/bin") configure_file(lit.cfg.in ${CMAKE_CURRENT_LIST_DIR}/lit.cfg @ONLY) function(add_cover_test TEST_NAME) - add_test(NAME "${TEST_NAME}_c" COMMAND lit --verbose ${CMAKE_CURRENT_LIST_DIR}/c/${TEST_NAME}.c) - add_test(NAME "${TEST_NAME}_fort" COMMAND lit --verbose ${CMAKE_CURRENT_LIST_DIR}/fort/${TEST_NAME}.F90) + add_test(NAME "C::${TEST_NAME}" COMMAND lit --verbose ${CMAKE_CURRENT_LIST_DIR}/c/${TEST_NAME}.c) + add_test(NAME "Fortran::${TEST_NAME}" COMMAND lit --verbose ${CMAKE_CURRENT_LIST_DIR}/fort/${TEST_NAME}.F90) endfunction() add_cover_test(Correct-Minimal) From c45ab9c94e0dd599a0a0ecf672f1a60c83d4d8fe Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 11 Mar 2026 13:21:15 +0100 Subject: [PATCH 027/125] Fix error maybe probably --- Dynamic/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Dynamic/CMakeLists.txt b/Dynamic/CMakeLists.txt index 0da171f..5640d65 100644 --- a/Dynamic/CMakeLists.txt +++ b/Dynamic/CMakeLists.txt @@ -8,7 +8,11 @@ add_library(CoVerDynamicAnalyzer STATIC ) set_property(TARGET CoVerDynamicAnalyzer PROPERTY CXX_STANDARD 20) -set_property(TARGET CoVerDynamicAnalyzer PROPERTY UNITY_BUILD "$>") +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set_property(TARGET CoVerDynamicAnalyzer PROPERTY UNITY_BUILD OFF) +else() + set_property(TARGET CoVerDynamicAnalyzer PROPERTY UNITY_BUILD ON) +endif() target_include_directories(CoVerDynamicAnalyzer PUBLIC ../Include/) set_property(TARGET CoVerDynamicAnalyzer PROPERTY CXX_VISIBILITY_PRESET hidden) From 83cf2decd3121c4e824833f69384bc541511f204 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 11 Mar 2026 13:52:33 +0100 Subject: [PATCH 028/125] Add missing break --- Dynamic/Hooks.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Dynamic/Hooks.hpp b/Dynamic/Hooks.hpp index 81ba842..71178e4 100644 --- a/Dynamic/Hooks.hpp +++ b/Dynamic/Hooks.hpp @@ -147,6 +147,7 @@ namespace { case UNARY_PARAM: if (isPre) addAnalysis(form, func_supplier, (ParamOp_t*)form->data); else DynamicUtils::createMessage("Did not expect paramop in postcond!"); + break; default: DynamicUtils::createMessage("Unknown top-level operation!"); break; From 346c395c5f383f9f248efb4c3941f471854746bc Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 11 Mar 2026 13:56:54 +0100 Subject: [PATCH 029/125] Fix UB --- Dynamic/Analyses/ParamAnalysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dynamic/Analyses/ParamAnalysis.cpp b/Dynamic/Analyses/ParamAnalysis.cpp index 611bb35..120df91 100644 --- a/Dynamic/Analyses/ParamAnalysis.cpp +++ b/Dynamic/Analyses/ParamAnalysis.cpp @@ -37,5 +37,5 @@ Fulfillment ParamAnalysis::functionCBImpl(void* const& func, CallsiteInfo const& continue; } } - __builtin_unreachable(); + return Fulfillment::UNKNOWN; } From a6fe3a578db06d7cc4959444925870aceb364492 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 11 Mar 2026 14:37:19 +0100 Subject: [PATCH 030/125] Add more contracts --- Scripts/gen_mpi_contr_h.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 8a132a8..8630a55 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -354,7 +354,9 @@ def add_contract(func: str, scope: str, contr: str): # Comm buffer should be allocated paramerror_null = [ ("MPI_Send", 0), + ("MPI_Isend", 0), ("MPI_Recv", 0), + ("MPI_Irecv", 0), ("MPI_Sendrecv", 0), ("MPI_Sendrecv", 5), ("MPI_Get", 0), From 959894cada639e33be22f520665f04c9013efde6 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 11 Mar 2026 14:37:38 +0100 Subject: [PATCH 031/125] Remove commented code --- Passes/ContractVerifierAlloc.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Passes/ContractVerifierAlloc.cpp b/Passes/ContractVerifierAlloc.cpp index bfa1f90..2dbe714 100644 --- a/Passes/ContractVerifierAlloc.cpp +++ b/Passes/ContractVerifierAlloc.cpp @@ -128,9 +128,6 @@ ContractVerifierAllocPass::AllocStatusVal ContractVerifierAllocPass::checkAllocR auto bound_merge = std::bind(&ContractVerifierAllocPass::mergeAllocStat, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); std::map AnalysisInfo = ContractPassUtility::GenericWorklist(Entry, bound_transfer, bound_merge, &data, init); - //C.DebugInfo->insert(C.DebugInfo->end(), data.dbg.begin(), data.dbg.end()); - //Expr.ErrorInfo->insert(Expr.ErrorInfo->end(), data.err.begin(), data.err.end()); - // Take max over all analysis info // Correct usage will not contain error AllocStatusVal res = AllocStatusVal::ALLOC; From e0bd03dc79f15151360c3567348a813b2fd5b962 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 12 Mar 2026 09:42:59 +0100 Subject: [PATCH 032/125] Move some output to debug only --- Passes/ContractManager.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index 07b2400..a06bc9f 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -172,7 +173,6 @@ void ContractManagerAnalysis::extractFromFunction(Module& M) { StringRef result_stem = result->getName().split('.').first; for (GlobalVariable& GV : M.globals()) { if (GV.getName().starts_with(result_stem)) { - errs() << "Note: Added " << GV.getName() << " as mangled alias to " << result->getName() << " for contract value " << CallStr << "\n"; addValueDefinition(CallStr, &GV); } } @@ -226,6 +226,11 @@ void ContractManagerAnalysis::addContract(std::string contract, Function* F) { } void ContractManagerAnalysis::addValueDefinition(std::string name, Value* val) { + if (IS_DEBUG) { + WithColor(errs(), HighlightColor::Remark) << "[ContractManager] IR Value \""; + val->print(WithColor(errs(), HighlightColor::Remark)), + WithColor(errs(), HighlightColor::Remark) << "\" stored for contract value \"" << name << "\"\n"; + } curDatabase.ContractVariableData[name].insert(val); } From 2392b61f02f4e5d9fc4c26ed104c1261e40aa786 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 12 Mar 2026 11:56:00 +0100 Subject: [PATCH 033/125] Adjust trivialalloc --- Include/ContractPassUtility.hpp | 3 +++ Passes/ContractManager.cpp | 2 ++ Utils/ContractPassUtility.cpp | 33 ++++++++++++++++++++++++++------- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/Include/ContractPassUtility.hpp b/Include/ContractPassUtility.hpp index 2613af7..6baa4e5 100644 --- a/Include/ContractPassUtility.hpp +++ b/Include/ContractPassUtility.hpp @@ -28,6 +28,9 @@ using namespace llvm; #define IS_DEBUG (getenv(DEBUG_ENV) != NULL && atoi(getenv(DEBUG_ENV)) == 1) namespace ContractPassUtility { + // Called automatically by ContractManager + void Initialize(Module& M); + template using TransferFunction = std::function; template diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index a06bc9f..ae60a47 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -71,6 +71,8 @@ ContractManagerAnalysis::ContractDatabase ContractManagerAnalysis::run(Module &M s << "CoVer: Parsed contracts after " << std::fixed << std::chrono::duration(std::chrono::system_clock::now() - curDatabase.start_time).count() << "s\n"; errs() << s.str(); + ContractPassUtility::Initialize(M); + return curDatabase; } diff --git a/Utils/ContractPassUtility.cpp b/Utils/ContractPassUtility.cpp index d16dfc1..3ca0a81 100644 --- a/Utils/ContractPassUtility.cpp +++ b/Utils/ContractPassUtility.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,9 @@ using namespace llvm; // To only warn once if a CB is calling an unknown function static std::set UnknownCalledParam; +// For language-specific stuff +static bool isFort = false; + const Value* ContractPassUtility::betterGetPointerOperand(const Value* V) { const Value* b = getPointerOperand(V); if (b == nullptr) { @@ -122,6 +126,10 @@ int resolveFunctionDifference(const Value** A, const Value** B) { namespace ContractPassUtility { +void Initialize(Module& M) { + isFort = M.getFunction("_QQmain"); +} + std::optional getLineNumber(const Instruction* I) { if (const DebugLoc& N = I->getDebugLoc()) { return N.getLine(); @@ -149,9 +157,20 @@ FileReference getFileReference(const Instruction* I) { }; } -bool isTrivialAlloc(const Value* V) { +bool isTrivialAlloc(Value const* V) { // First possibility: Its a global alloc, trivially fulfilled - if (isa(V)) { + if (GlobalVariable const* GV = dyn_cast(V)) { + if (isFort) { + SmallVector dbg_arr; + GV->getDebugInfo(dbg_arr); + if (!dbg_arr.empty()) { + if (DICompositeType* T = dyn_cast(dbg_arr[0]->getVariable()->getType())) { + if (T->getDataLocationExp()) { + return false; + } + } + } + } return true; } // Second possibility: Its a stack var, trivially fulfilled @@ -159,7 +178,9 @@ bool isTrivialAlloc(const Value* V) { while (isa(tmp)) { tmp = getPointerOperand(tmp); } - if (isa(tmp)) return true; + + // alloca is trivial alloc only in C + if (isa(tmp) && !isFort) return true; // Not trivially allocated return false; @@ -206,9 +227,7 @@ bool checkParamMatch(const Value* contrP, const Value* callP, ContractTree::Para } // Only use DSA for Fortran - const bool use_dsa = (isa(contrP) && dyn_cast(contrP)->getModule()->getFunction("_QQmain")) || - (isa(callP) && dyn_cast(callP)->getModule()->getFunction("_QQmain")); - if (!use_dsa) { + if (!isFort) { // Resolve function differences. // If one is a global, this does not matter, so check if they are instructions first if (isa(source) && isa(target)) { @@ -244,7 +263,7 @@ bool checkParamMatch(const Value* contrP, const Value* callP, ContractTree::Para if (source == target) return true; - if (use_dsa) { + if (isFort) { std::shared_ptr steens = MAM->getResult(*getModule(contrP)); if (steens->hasNodeForValue(source) && steens->hasNodeForValue(target)) { DSNodeHandle sourceNode = steens->getNodeForValue(source); From f13d6b691d90cc3700af603d3e250e0403248e1c Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 12 Mar 2026 14:39:24 +0100 Subject: [PATCH 034/125] Semi-functioning alloc --- Passes/ContractVerifierAlloc.cpp | 37 ++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/Passes/ContractVerifierAlloc.cpp b/Passes/ContractVerifierAlloc.cpp index 2dbe714..9cbbbac 100644 --- a/Passes/ContractVerifierAlloc.cpp +++ b/Passes/ContractVerifierAlloc.cpp @@ -67,7 +67,41 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS // Propagate allocations if (const StoreInst* SI = dyn_cast(I)) { if (cur.candidate.contains(SI->getValueOperand())) { - cur.candidate[SI->getPointerOperand()] = ParamAccess::ADDROF; + cur.candidate[SI->getPointerOperand()] = I->getModule()->getFunction("_QQmain") ? ParamAccess::NORMAL : ParamAccess::ADDROF; + } + } + + // Propagate allocations - Pretty much just Fortran from here... + if (const CallBase* CB = dyn_cast(I)) { + if (CB->getCalledFunction() && CB->getCalledFunction()->getName().starts_with("llvm.memcpy.p0.p0")) { + Value* src = CB->getArgOperand(1); + Value* dest = CB->getArgOperand(0); + if (ContractPassUtility::isTrivialAlloc(src)) { + cur.candidate[dest] = ParamAccess::NORMAL; + } + } + if (CB->getCalledFunction() && CB->getCalledFunction()->getName() == "_FortranAPointerAllocate") { + cur.candidate[CB->getArgOperand(0)] = ParamAccess::NORMAL; + } + } + if (const LoadInst* LI = dyn_cast(I)) { + if (ContractPassUtility::isTrivialAlloc(LI->getPointerOperand()) || cur.candidate.contains(LI->getPointerOperand())) { + cur.candidate[LI] = cur.candidate[LI->getPointerOperand()]; + } + } + if (const GetElementPtrInst* GEP = dyn_cast(I)) { + if (cur.candidate.contains(GEP->getPointerOperand())) { + cur.candidate[GEP] = cur.candidate[GEP->getPointerOperand()]; + } + } + if (const InsertValueInst* IVI = dyn_cast(I)) { + if (cur.candidate.contains(IVI->getInsertedValueOperand())) { + cur.candidate[IVI] = cur.candidate[IVI->getInsertedValueOperand()]; + } + } + if (const ExtractValueInst* EVI = dyn_cast(I)) { + if (cur.candidate.contains(EVI->getAggregateOperand())) { + cur.candidate[EVI] = cur.candidate[EVI->getAggregateOperand()]; } } @@ -95,7 +129,6 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS } } // Any required parameter not used by any candidate - //appendDebugStr(Data->Target, Data->isTag, CB, cur.candidate, Data->err); cur.CurVal = AllocStatusVal::ERROR; return cur; } From b4b711df3e7ef616a3d9279fb03ff30a11c2836d Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 12 Mar 2026 15:50:19 +0100 Subject: [PATCH 035/125] Fix FP in allocanalysis for C --- Passes/ContractVerifierAlloc.cpp | 2 +- Tests/CMakeLists.txt | 1 + Tests/c/Correct-P2PStackBuf.c | 34 ++++++++++++++++++++++++++++++ Tests/fort/Correct-P2PStackBuf.F90 | 32 ++++++++++++++++++++++++++++ Utils/ContractPassUtility.cpp | 24 +++++++++++++++++++-- 5 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 Tests/c/Correct-P2PStackBuf.c create mode 100644 Tests/fort/Correct-P2PStackBuf.F90 diff --git a/Passes/ContractVerifierAlloc.cpp b/Passes/ContractVerifierAlloc.cpp index 9cbbbac..3bc065f 100644 --- a/Passes/ContractVerifierAlloc.cpp +++ b/Passes/ContractVerifierAlloc.cpp @@ -95,7 +95,7 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS } } if (const InsertValueInst* IVI = dyn_cast(I)) { - if (cur.candidate.contains(IVI->getInsertedValueOperand())) { + if (cur.candidate.contains(IVI->getInsertedValueOperand()) || ContractPassUtility::isTrivialAlloc(IVI->getInsertedValueOperand())) { cur.candidate[IVI] = cur.candidate[IVI->getInsertedValueOperand()]; } } diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 3ee068b..52681e9 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -8,6 +8,7 @@ endfunction() add_cover_test(Correct-Minimal) add_cover_test(Correct-P2P) +add_cover_test(Correct-P2PStackBuf) add_cover_test(PostCall-MissingFinalize) add_cover_test(PreCall-MissingInit) diff --git a/Tests/c/Correct-P2PStackBuf.c b/Tests/c/Correct-P2PStackBuf.c new file mode 100644 index 0000000..e6d33ca --- /dev/null +++ b/Tests/c/Correct-P2PStackBuf.c @@ -0,0 +1,34 @@ +// RUN: %clangContracts %run_common + +#include +#include +#include + +int main(int argc, char** argv) { + int rank; + int buf; + MPI_Request req; + + MPI_Init(NULL, NULL); + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + if (rank == 0) { + MPI_Isend(&buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &req); + printf("Buf: %d\n", buf); + } else { + MPI_Irecv(&buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); + } + MPI_Wait(&req, MPI_STATUS_IGNORE); + + MPI_Finalize(); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK-NOT: Contract violation detected! +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK-NOT: Contract violation detected! +// CHECK: Analysis finished. diff --git a/Tests/fort/Correct-P2PStackBuf.F90 b/Tests/fort/Correct-P2PStackBuf.F90 new file mode 100644 index 0000000..15e857a --- /dev/null +++ b/Tests/fort/Correct-P2PStackBuf.F90 @@ -0,0 +1,32 @@ +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: rank + integer :: buf(1) + type(MPI_Request) :: req + + call MPI_Init() + + call MPI_Comm_rank(MPI_COMM_WORLD, rank) + + if (rank == 0) then + call MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, req) + print *, "Buf: ", buf(1) + else + call MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, req) + end if + call MPI_Wait(req, MPI_STATUS_IGNORE) + + call MPI_Finalize() +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK-NOT: Contract violation detected! +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK-NOT: Contract violation detected! +! CHECK: Analysis finished. diff --git a/Utils/ContractPassUtility.cpp b/Utils/ContractPassUtility.cpp index 3ca0a81..be9f617 100644 --- a/Utils/ContractPassUtility.cpp +++ b/Utils/ContractPassUtility.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -179,8 +180,27 @@ bool isTrivialAlloc(Value const* V) { tmp = getPointerOperand(tmp); } - // alloca is trivial alloc only in C - if (isa(tmp) && !isFort) return true; + // Stack Variables + if (AllocaInst const* AI = dyn_cast(tmp)) { + if (isFort) { + // Fortran will do weird pointer / metadata stuff -> Need to check if stack thingy has external data location + Instruction const* dbgdeclare = AI; + while (dbgdeclare->getNextNode() && isa(dbgdeclare)) dbgdeclare = dbgdeclare->getNextNode(); + for (DbgRecord const& DR : dbgdeclare->getDbgRecordRange()) { + if (DbgVariableRecord const* DVR = dyn_cast(&DR)) { + if (DVR->getAddress() != AI) continue; + if (DICompositeType const* T = dyn_cast(DVR->getVariable()->getType())) { + if (!T->getDataLocationExp()) { + return true; + } + } + } + } + } else { + // C will just put the stack var in the function like a normal program -> already allocated + return true; + } + } // Not trivially allocated return false; From 610f218b90b37119558633df3cf36cb193cde804 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 12 Mar 2026 16:17:06 +0100 Subject: [PATCH 036/125] Fix IDE test path parsing --- .vscode/settings.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index b069d3f..bd78cce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,15 @@ ] } }, + "cmake.ctest.failurePatterns": [ + { + "regexp": "(.+):(\\d+):(\\d+): error: CHECK: (.+)", + "file": 1, + "line": 2, + "column": 3, + "message": 4, + }, + ], "terminal.integrated.defaultProfile.linux": "CoVer Wrapper Bash", "cmake.configureArgs": [ "-DMPI_HOME=/home/archuser/Desktop/ompi/install", From cb9d74e1789785422fa6092ab97f1f1fafffe191 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 13 Mar 2026 13:25:48 +0100 Subject: [PATCH 037/125] Allow specifying memory free ops for alloctracking --- Grammars/ContractLexer.g4 | 1 + Grammars/ContractParser.g4 | 2 +- Include/ContractTree.hpp | 6 +++++- Include/DynamicAnalysis.h | 1 + LangCode/ContractDataVisitor.cpp | 2 ++ 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Grammars/ContractLexer.g4 b/Grammars/ContractLexer.g4 index 29a5c70..f0c9c96 100644 --- a/Grammars/ContractLexer.g4 +++ b/Grammars/ContractLexer.g4 @@ -37,6 +37,7 @@ OPRelease1: 'no!'; OPRelease2: 'until!'; OPParam: 'param!'; OPAlloc: 'alloc!'; +OPFree: 'free!'; OPPrefix: '('; OPPostfix: ')'; diff --git a/Grammars/ContractParser.g4 b/Grammars/ContractParser.g4 index 0e77564..fd728b1 100644 --- a/Grammars/ContractParser.g4 +++ b/Grammars/ContractParser.g4 @@ -17,7 +17,7 @@ tagUnit: Variable (OPPrefix NatNum OPPostfix)?; expression: callOp | releaseOp | paramOp | rwOp; // rwOp only makes sense for alloc though -rwOp: (OPRead | OPWrite | OPAlloc) OPPrefix (Deref | AddrOf)? NatNum OPPostfix; +rwOp: (OPRead | OPWrite | OPAlloc | OPFree) OPPrefix (Deref | AddrOf)? NatNum OPPostfix; varMap: (callP=NatNum | TagParam) MapSep (Deref | AddrOf)? contrP=NatNum; callOp: (OPCall | OPCallTag) OPPrefix Variable (ListSep varMap)* OPPostfix; paramOp: OPParam OPPrefix NatNum MapSep paramReq (ListSep paramReq)* OPPostfix; diff --git a/Include/ContractTree.hpp b/Include/ContractTree.hpp index 9d3e841..df53b92 100644 --- a/Include/ContractTree.hpp +++ b/Include/ContractTree.hpp @@ -14,7 +14,7 @@ #include namespace ContractTree { - enum struct FormulaType { AND, OR, XOR, READ, WRITE, ALLOC, CALL, CALLTAG, RELEASE, PARAM }; + enum struct FormulaType { AND, OR, XOR, READ, WRITE, ALLOC, FREE, CALL, CALLTAG, RELEASE, PARAM }; enum struct ParamAccess { NORMAL, DEREF, ADDROF }; struct Operation { virtual ~Operation() = default; @@ -38,6 +38,10 @@ namespace ContractTree { AllocOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; virtual const FormulaType type() const override { return FormulaType::ALLOC; }; }; + struct FreeOperation : RWOperation { + FreeOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; + virtual const FormulaType type() const override { return FormulaType::FREE; }; + }; enum Comparator { NEQ, GT, GTEQ, LT, LTEQ, EXEQ }; diff --git a/Include/DynamicAnalysis.h b/Include/DynamicAnalysis.h index 68333ff..8168390 100644 --- a/Include/DynamicAnalysis.h +++ b/Include/DynamicAnalysis.h @@ -74,6 +74,7 @@ enum ContractConnective : int32_t { UNARY_READ, UNARY_WRITE, UNARY_ALLOC, + UNARY_FREE, UNARY_CALL, UNARY_CALLTAG, UNARY_RELEASE, diff --git a/LangCode/ContractDataVisitor.cpp b/LangCode/ContractDataVisitor.cpp index 25d15f1..c1ce8a4 100644 --- a/LangCode/ContractDataVisitor.cpp +++ b/LangCode/ContractDataVisitor.cpp @@ -80,6 +80,8 @@ std::any ContractDataVisitor::visitRwOp(ContractParser::RwOpContext *ctx) { op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); else if (ctx->OPAlloc()) op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); + else if (ctx->OPFree()) + op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); return op; } std::any ContractDataVisitor::visitParamOp(ContractParser::ParamOpContext *ctx) { From c40a27bc683982766f4476f55dd9c5dda02d9571 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 13 Mar 2026 14:04:44 +0100 Subject: [PATCH 038/125] Add tests for MPI_Win_allocate Useful once alloc tracking ready --- Tests/CMakeLists.txt | 1 + Tests/c/Correct-WinAlloc.c | 36 ++++++++++++++++++++++++++++++ Tests/fort/Correct-WinAlloc.F90 | 39 +++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 Tests/c/Correct-WinAlloc.c create mode 100644 Tests/fort/Correct-WinAlloc.F90 diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 52681e9..4de1c19 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -9,6 +9,7 @@ endfunction() add_cover_test(Correct-Minimal) add_cover_test(Correct-P2P) add_cover_test(Correct-P2PStackBuf) +add_cover_test(Correct-WinAlloc) add_cover_test(PostCall-MissingFinalize) add_cover_test(PreCall-MissingInit) diff --git a/Tests/c/Correct-WinAlloc.c b/Tests/c/Correct-WinAlloc.c new file mode 100644 index 0000000..9f0ee57 --- /dev/null +++ b/Tests/c/Correct-WinAlloc.c @@ -0,0 +1,36 @@ +// RUN: %clangContracts %run_common + +#include +#include +#include + +int main(int argc, char** argv) { + int rank; + int* buf; + MPI_Win win; + + MPI_Init(NULL, NULL); + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + MPI_Win_allocate(sizeof(int), sizeof(int), MPI_INFO_NULL, MPI_COMM_WORLD, &buf, &win); + buf[0] = 42; + + MPI_Win_fence(0, win); + if (rank == 0) { + MPI_Put(buf, 1, MPI_INT, 1, sizeof(int), 1, MPI_INT, win); + printf("Buf: %d\n", buf[0]); + } + MPI_Win_fence(0, win); + + MPI_Finalize(); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK-NOT: Contract violation detected! +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK-NOT: Contract violation detected! +// CHECK: Analysis finished. diff --git a/Tests/fort/Correct-WinAlloc.F90 b/Tests/fort/Correct-WinAlloc.F90 new file mode 100644 index 0000000..235fcb4 --- /dev/null +++ b/Tests/fort/Correct-WinAlloc.F90 @@ -0,0 +1,39 @@ +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + use iso_c_binding + implicit none + + integer :: rank + type(c_ptr) :: winbuf + integer, pointer :: buf(:) + integer :: integer_size + type(MPI_Win) :: win + + call MPI_Init() + + call MPI_Type_size(MPI_INT, integer_size) + call MPI_Comm_rank(MPI_COMM_WORLD, rank) + + call MPI_Win_allocate(int(integer_size, kind=mpi_address_kind), integer_size, MPI_INFO_NULL, MPI_COMM_WORLD, winbuf, win) + call c_f_pointer(winbuf, buf, [1]) + buf(1) = 42 + + call MPI_Win_fence(0, win) + if (rank == 0) then + call MPI_Put(buf, 1, MPI_INT, 1, int(integer_size, kind=mpi_address_kind), 1, MPI_INT, win) + print *, "Buf: ", buf(1) + end if + call MPI_Win_fence(0, win) + + call MPI_Finalize() +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK-NOT: Contract violation detected! +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK-NOT: Contract violation detected! +! CHECK: Analysis finished. From 1c77f238454807dbc6feed8e97ba968b33428684 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 13 Mar 2026 14:14:18 +0100 Subject: [PATCH 039/125] Support use-after-free error detection --- Passes/ContractVerifierAlloc.cpp | 89 +++++++++++++++++++------------- Passes/ContractVerifierAlloc.hpp | 69 +++++++++++++++++++++++-- Scripts/gen_mpi_contr_h.py | 18 ++++++- 3 files changed, 132 insertions(+), 44 deletions(-) diff --git a/Passes/ContractVerifierAlloc.cpp b/Passes/ContractVerifierAlloc.cpp index 3bc065f..5defadc 100644 --- a/Passes/ContractVerifierAlloc.cpp +++ b/Passes/ContractVerifierAlloc.cpp @@ -21,10 +21,17 @@ PreservedAnalyses ContractVerifierAllocPass::run(Module &M, // First, build list of all allocating funcs for (ContractManagerAnalysis::LinearizedContract const& C : DB->LinearizedContracts) { for (const std::shared_ptr Expr : C.Post) { - if (Expr->OP->type() != FormulaType::ALLOC) continue; - const AllocOperation* AllocOp = dynamic_cast(Expr->OP.get()); - if (!AllocFuncs.contains(C.F)) AllocFuncs[C.F] = {}; - AllocFuncs[C.F].insert(AllocOp); + switch (Expr->OP->type()) { + case FormulaType::ALLOC: + if (!AllocFuncs.contains(C.F)) AllocFuncs[C.F] = {}; + AllocFuncs[C.F].insert(static_cast(Expr->OP.get())); + break; + case FormulaType::FREE: + if (!FreeFuncs.contains(C.F)) FreeFuncs[C.F] = {}; + FreeFuncs[C.F].insert(static_cast(Expr->OP.get())); + break; + default: continue; + } *Expr->Status = Fulfillment::FULFILLED; // Always fulfilled. } } @@ -65,52 +72,65 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS IterTypeAlloc* Data = static_cast(data); // Propagate allocations - if (const StoreInst* SI = dyn_cast(I)) { - if (cur.candidate.contains(SI->getValueOperand())) { - cur.candidate[SI->getPointerOperand()] = I->getModule()->getFunction("_QQmain") ? ParamAccess::NORMAL : ParamAccess::ADDROF; + if (StoreInst const* SI = dyn_cast(I)) { + if (cur.hasAllocInfo(SI->getValueOperand())) { + cur.addCopy(SI->getPointerOperand(), SI->getValueOperand(), I->getModule()->getFunction("_QQmain") ? ParamAccess::NORMAL : ParamAccess::ADDROF); } } // Propagate allocations - Pretty much just Fortran from here... - if (const CallBase* CB = dyn_cast(I)) { + if (CallBase const* CB = dyn_cast(I)) { if (CB->getCalledFunction() && CB->getCalledFunction()->getName().starts_with("llvm.memcpy.p0.p0")) { Value* src = CB->getArgOperand(1); Value* dest = CB->getArgOperand(0); if (ContractPassUtility::isTrivialAlloc(src)) { - cur.candidate[dest] = ParamAccess::NORMAL; + cur.addAllocatedValue(dest); } } - if (CB->getCalledFunction() && CB->getCalledFunction()->getName() == "_FortranAPointerAllocate") { - cur.candidate[CB->getArgOperand(0)] = ParamAccess::NORMAL; + } + if (LoadInst const* LI = dyn_cast(I)) { + if (ContractPassUtility::isTrivialAlloc(LI->getPointerOperand())) { + cur.addAllocatedValue(LI); + } else if (cur.hasAllocInfo(LI->getPointerOperand())) { + cur.addCopy(LI, LI->getPointerOperand(), cur.getAllocInfo(LI->getPointerOperand()).acc); } } - if (const LoadInst* LI = dyn_cast(I)) { - if (ContractPassUtility::isTrivialAlloc(LI->getPointerOperand()) || cur.candidate.contains(LI->getPointerOperand())) { - cur.candidate[LI] = cur.candidate[LI->getPointerOperand()]; + if (GetElementPtrInst const* GEP = dyn_cast(I)) { + if (cur.hasAllocInfo(GEP->getPointerOperand())) { + cur.addCopy(GEP, GEP->getPointerOperand(), cur.getAllocInfo(GEP->getPointerOperand()).acc); } } - if (const GetElementPtrInst* GEP = dyn_cast(I)) { - if (cur.candidate.contains(GEP->getPointerOperand())) { - cur.candidate[GEP] = cur.candidate[GEP->getPointerOperand()]; + if (InsertValueInst const* IVI = dyn_cast(I)) { + if (cur.hasAllocInfo(IVI->getInsertedValueOperand())) { + cur.addCopy(IVI, IVI->getInsertedValueOperand(), cur.getAllocInfo(IVI->getInsertedValueOperand()).acc); + } else if (ContractPassUtility::isTrivialAlloc(IVI->getInsertedValueOperand())) { + cur.addAllocatedValue(IVI); } } - if (const InsertValueInst* IVI = dyn_cast(I)) { - if (cur.candidate.contains(IVI->getInsertedValueOperand()) || ContractPassUtility::isTrivialAlloc(IVI->getInsertedValueOperand())) { - cur.candidate[IVI] = cur.candidate[IVI->getInsertedValueOperand()]; + if (ExtractValueInst const* EVI = dyn_cast(I)) { + if (cur.hasAllocInfo(EVI->getAggregateOperand())) { + cur.addCopy(EVI, EVI->getAggregateOperand(), cur.getAllocInfo(EVI->getAggregateOperand()).acc); } } - if (const ExtractValueInst* EVI = dyn_cast(I)) { - if (cur.candidate.contains(EVI->getAggregateOperand())) { - cur.candidate[EVI] = cur.candidate[EVI->getAggregateOperand()]; + if (IntToPtrInst const* ITPI = dyn_cast(I)) { + if (cur.hasAllocInfo(ITPI->getOperand(0))) { + cur.addCopy(ITPI, ITPI->getOperand(0), cur.getAllocInfo(ITPI->getOperand(0)).acc); } } if (const CallBase* CB = dyn_cast(I)) { - if (AllocFuncs.contains(CB->getCalledFunction())) { - for (const AllocOperation* alloc : AllocFuncs[CB->getCalledFunction()]) { + if (AllocFuncs.contains(CB->getCalledOperand())) { + for (const AllocOperation* alloc : AllocFuncs[CB->getCalledOperand()]) { #warning TODO different access patterns - if (alloc->contrP == 99) cur.candidate[CB] = alloc->contrParamAccess; - else cur.candidate[CB->getArgOperand(alloc->contrP)] = alloc->contrParamAccess; + if (alloc->contrP == 99) cur.addAllocatedValue(CB, alloc->contrParamAccess); + else cur.addAllocatedValue(CB->getArgOperand(alloc->contrP), alloc->contrParamAccess); + } + // Dont return here! Maybe it also is contr sup + } + if (FreeFuncs.contains(CB->getCalledFunction())) { + for (const FreeOperation* freeOp : FreeFuncs[CB->getCalledFunction()]) { + #warning TODO perform free, remove stuff from candidate tree + cur.freeValue(CB->getArgOperand(freeOp->contrP)); } // Dont return here! Maybe it also is contr sup } @@ -121,8 +141,8 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS return cur; } // Not trivial, check if explicitly allocated - for (std::pair Candidate : cur.candidate) { - if (ContractPassUtility::checkParamMatch(CB->getArgOperand(Data->param), Candidate.first, Candidate.second, MAM)) { + for (std::pair Candidate : cur.candidates()) { + if (ContractPassUtility::checkParamMatch(CB->getArgOperand(Data->param), Candidate.first, Candidate.second.acc, MAM)) { // Success! cur.CurVal = AllocStatusVal::ALLOC; return cur; @@ -138,13 +158,8 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS } std::pair ContractVerifierAllocPass::mergeAllocStat(AllocStatus prev, AllocStatus cur, const Instruction* I, void* data) { - std::map intersect; - std::set_intersection(prev.candidate.begin(), prev.candidate.end(), cur.candidate.begin(), cur.candidate.end(), - std::inserter(intersect, intersect.begin())); - - AllocStatus merge = { std::max(prev.CurVal, cur.CurVal), intersect }; - - return { merge, merge.CurVal > prev.CurVal }; + AllocStatus intersect = cur.intersect(prev); + return {intersect, intersect.CurVal > prev.CurVal}; } ContractVerifierAllocPass::AllocStatusVal ContractVerifierAllocPass::checkAllocReq(const AllocOperation* AllocOp, Module const& M, const Function* F, std::string& err) { @@ -155,7 +170,7 @@ ContractVerifierAllocPass::AllocStatusVal ContractVerifierAllocPass::checkAllocR } const Instruction* Entry = &*mainF->getEntryBlock().getFirstNonPHIIt(); - AllocStatus init = { AllocStatusVal::UNDEF, {}}; + AllocStatus init; IterTypeAlloc data = { {}, {}, AllocOp->contrP, AllocOp->contrParamAccess, F }; auto bound_transfer = std::bind(&ContractVerifierAllocPass::transferAllocStat, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); auto bound_merge = std::bind(&ContractVerifierAllocPass::mergeAllocStat, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); diff --git a/Passes/ContractVerifierAlloc.hpp b/Passes/ContractVerifierAlloc.hpp index e1fb528..9ca5f27 100644 --- a/Passes/ContractVerifierAlloc.hpp +++ b/Passes/ContractVerifierAlloc.hpp @@ -3,10 +3,12 @@ #include "ContractTree.hpp" #include "ContractManager.hpp" #include "llvm/IR/PassManager.h" +#include #include -#include #include +#include #include +#include using namespace ContractTree; @@ -15,9 +17,65 @@ namespace llvm { class ContractVerifierAllocPass : public PassInfoMixin { public: enum struct AllocStatusVal { ALLOC, UNDEF, ERROR }; - struct AllocStatus { - AllocStatusVal CurVal; - std::map candidate; + class AllocStatus { + public: struct AllocInfo; + private: std::map candidate_set; + public: + struct AllocInfo { + std::set parents; + std::set children; + ParamAccess acc; + bool operator==(const AllocInfo other) const { + return true; + } + }; + AllocStatusVal CurVal = AllocStatusVal::UNDEF; + AllocInfo const& getAllocInfo(Value const* V) const { return candidate_set.at(V); } + bool hasAllocInfo(Value const* V) const { return candidate_set.contains(V); } + auto candidates() const { + return std::ranges::subrange(candidate_set.cbegin(), candidate_set.cend()); + } + void addCopy(Value const* Copy, Value const* Parent, ParamAccess acc) { + if (candidate_set.contains(Copy)) { + candidate_set[Copy].parents.insert(Parent); + } else { + candidate_set[Copy] = {{Parent}, {}, acc}; + candidate_set[Parent].children.insert(Copy); + } + } + void addAllocatedValue(Value const* V, ParamAccess acc = ParamAccess::NORMAL) { + candidate_set[V] = {{}, {}, acc}; + } + void freeValue(Value const* V) { + if (!hasAllocInfo(V)) return; + std::set to_erase = {V}; + while (!to_erase.empty()) { + V = *to_erase.begin(); + for (Value const* C : candidate_set[V].children) { + candidate_set[C].parents.erase(V); + if (candidate_set[C].parents.empty()) to_erase.insert(C); + } + candidate_set.erase(V); + to_erase.erase(V); + } + } + AllocStatus intersect(AllocStatus const& other) { + std::map candidate_intersect; + for (std::pair Candidate : this->candidates()) { + if (other.hasAllocInfo(Candidate.first)) { + AllocInfo AIres; + AIres.acc = Candidate.second.acc; + AllocInfo AI = other.getAllocInfo(Candidate.first); + std::set_intersection(AI.children.begin(), AI.children.end(), Candidate.second.children.begin(), Candidate.second.children.end(), std::inserter(AIres.children, AIres.children.end())); + std::set_intersection(AI.parents.begin(), AI.parents.end(), Candidate.second.parents.begin(), Candidate.second.parents.end(), std::inserter(AIres.parents, AIres.parents.end())); + candidate_intersect[Candidate.first] = AIres; + } + } + AllocStatus res; + res.candidate_set = candidate_intersect; + res.CurVal = std::max(this->CurVal, other.CurVal); + return res; + } }; PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); @@ -25,7 +83,8 @@ class ContractVerifierAllocPass : public PassInfoMixin> AllocFuncs; + std::map> AllocFuncs; + std::map> FreeFuncs; AllocStatus transferAllocStat(AllocStatus s, const Instruction* I, void* data); std::pair mergeAllocStat(AllocStatus prev, AllocStatus cur, const Instruction* I, void* data); diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 8630a55..c88591c 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -473,6 +473,8 @@ def get_param_values(lang: str) -> str: void* calloc(size_t num, size_t size) CONTRACT( POST {{ alloc!(99) }}); void* malloc(size_t size) CONTRACT( POST {{ alloc!(99) }}); +void free(void*) CONTRACT( POST {{ free!(0) }}); + """ header_output_c = boilerplate_header_c @@ -486,8 +488,20 @@ def get_param_values(lang: str) -> str: use contract_helper """ -header_output_fort = boilerplate_header_fort + f" use mpi\n implicit none\n\n{get_param_values("fort")}\n\n" -header_output_fort_f08 = boilerplate_header_fort + f" use mpi_f08\n implicit none\n\n{get_param_values("fort")}\n\n" +fortran_lang_intrinsics = """ + ! Contracts for intrinsics - Mainly for allocation tracking + interface + subroutine FortAlloc() bind(c, name="_FortranAPointerAllocate") + end subroutine FortAlloc + subroutine FortFree() bind(c, name="_FortranAPointerDeallocate") + end subroutine FortFree + end interface + call Declare_Contract(FortAlloc, \"POST { alloc!(0) }\") + call Declare_Contract(FortFree, \"POST { free!(0) }\") +""" + +header_output_fort = boilerplate_header_fort + f" use mpi\n implicit none\n\n{fortran_lang_intrinsics}\n\n{get_param_values("fort")}\n\n" +header_output_fort_f08 = boilerplate_header_fort + f" use mpi_f08\n implicit none\n\n{fortran_lang_intrinsics}\n\n{get_param_values("fort")}\n\n" header_output_fort_f08ts = header_output_fort_f08 exclude_fortran = [ From 21be3073f7e473bce8ec1705e6d34fc799813ac6 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 17 Mar 2026 15:11:59 +0100 Subject: [PATCH 040/125] Add fallback for static DEREF op --- Utils/ContractPassUtility.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Utils/ContractPassUtility.cpp b/Utils/ContractPassUtility.cpp index be9f617..709384a 100644 --- a/Utils/ContractPassUtility.cpp +++ b/Utils/ContractPassUtility.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -268,8 +269,11 @@ bool checkParamMatch(const Value* contrP, const Value* callP, ContractTree::Para case ContractTree::ParamAccess::DEREF: // Contr has a pointer, call has value. // If interproc, diff should be -1 if already resolved - if (diff == 0) - target = getLoadStorePointerOperand(target); + if (diff == 0) { + Value const* V = getLoadStorePointerOperand(target); + if (!V && IS_DEBUG) WithColor(errs(), HighlightColor::String) << "Note: Static deref failed, falling back to orig.\n"; + target = V ? V : target; + } else if (diff != -1) return false; break; case ContractTree::ParamAccess::ADDROF: From 5006cf0fabe84a5994f1d31602681af0f6baeccb Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 17 Mar 2026 15:13:07 +0100 Subject: [PATCH 041/125] Allow rwop as operation (useful for dyn analysis later) --- Include/ContractTree.hpp | 6 +++--- Include/DynamicAnalysis.h | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Include/ContractTree.hpp b/Include/ContractTree.hpp index df53b92..c5611fa 100644 --- a/Include/ContractTree.hpp +++ b/Include/ContractTree.hpp @@ -14,7 +14,7 @@ #include namespace ContractTree { - enum struct FormulaType { AND, OR, XOR, READ, WRITE, ALLOC, FREE, CALL, CALLTAG, RELEASE, PARAM }; + enum struct FormulaType { AND, OR, XOR, RWOP, READ, WRITE, ALLOC, FREE, CALL, CALLTAG, RELEASE, PARAM }; enum struct ParamAccess { NORMAL, DEREF, ADDROF }; struct Operation { virtual ~Operation() = default; @@ -23,8 +23,8 @@ namespace ContractTree { struct RWOperation : Operation { const int contrP; const ParamAccess contrParamAccess; - protected: - RWOperation(int _contrP, ParamAccess _acc) : contrP(_contrP), contrParamAccess(_acc) {}; + virtual const FormulaType type() const override { return FormulaType::RWOP; }; + RWOperation(int _contrP, ParamAccess _acc) : contrP(_contrP), contrParamAccess(_acc) {}; }; struct ReadOperation : RWOperation { ReadOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; diff --git a/Include/DynamicAnalysis.h b/Include/DynamicAnalysis.h index 8168390..0dec1f5 100644 --- a/Include/DynamicAnalysis.h +++ b/Include/DynamicAnalysis.h @@ -71,6 +71,7 @@ enum ContractConnective : int32_t { OR, XOR, // Operations + UNARY_RWOP, UNARY_READ, UNARY_WRITE, UNARY_ALLOC, From c7ce8d5aebf87ab37aa998caa2a864a03a18a593 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 17 Mar 2026 15:52:09 +0100 Subject: [PATCH 042/125] Concretize removal of mapping paramacc better for fortran --- Scripts/gen_mpi_contr_h.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index c88591c..89a9b9b 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -569,7 +569,7 @@ def create_f08_contract(func: str, contract: str, support_ts: bool): header_output_c += ");\n\n" if func in exclude_fortran or "c2f" in func or "f2c" in func or "f082c" in func or "c2f08" in func or func.endswith("_c") or func.endswith("_fromint") or func.endswith("_toint"): continue # Only defined for C # Now: Fortran - contract_str_fort = contract_str.replace('"', '""').replace("\n", "").replace(" ", "").replace("*", "").replace("&", "").replace("read!(", "read!(*").replace("write!(", "write!(*") + contract_str_fort = contract_str.replace('"', '""').replace("\n", "").replace(" ", "").replace(":*", ":").replace(":&", ":") if func not in exclude_fortran_nof08: header_output_fort += " call Declare_Contract(" + func + ", \"" + contract_str_fort + "\")\n" # Fortran f08(ts) From 6569000f685d0642626fe15b4456752d7fb088d1 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 18 Mar 2026 12:49:14 +0100 Subject: [PATCH 043/125] Stricter string check --- Passes/Instrument.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 5564a17..3f7cf21 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -542,7 +542,7 @@ bool InstrumentPass::checkIsStrParam(Value const* V) { // Now, check if its a global string if (GlobalVariable const* GV = dyn_cast(V)) { Constant const* Init = GV->getInitializer(); - return Init && isa(Init->getType()) && dyn_cast(Init->getType())->getElementType() == IntegerType::get(V->getContext(), 8); + return Init && !Init->isZeroValue() && isa(Init->getType()) && dyn_cast(Init->getType())->getElementType() == IntegerType::get(V->getContext(), 8); } return false; } From 2bd079d2efff02efb765a5974253ee16fdff41ca Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 18 Mar 2026 14:39:00 +0100 Subject: [PATCH 044/125] Integrate actual call into dyn callback --- Dynamic/Analyses/BaseAnalysis.h | 5 ++- Dynamic/Analyses/ParamAnalysis.cpp | 12 +++--- Dynamic/Analyses/ParamAnalysis.h | 4 +- Dynamic/Analyses/PostCallAnalysis.cpp | 2 +- Dynamic/Analyses/PostCallAnalysis.h | 4 +- Dynamic/Analyses/PreCallAnalysis.cpp | 2 +- Dynamic/Analyses/PreCallAnalysis.h | 4 +- Dynamic/Analyses/ReleaseAnalysis.cpp | 4 +- Dynamic/Analyses/ReleaseAnalysis.h | 2 +- Dynamic/CMakeLists.txt | 6 +++ Dynamic/DynamicUtils.h | 1 + Dynamic/Hooks.cpp | 54 +++++++++++++++++++++++++-- Dynamic/Hooks.hpp | 12 +++--- Include/DynamicAnalysis.h | 2 +- Passes/ContractVerifierAlloc.cpp | 2 +- Passes/Instrument.cpp | 39 +++++++++++++------ Scripts/clangContracts.cpp | 2 +- 17 files changed, 114 insertions(+), 43 deletions(-) diff --git a/Dynamic/Analyses/BaseAnalysis.h b/Dynamic/Analyses/BaseAnalysis.h index 29fa389..112dacf 100644 --- a/Dynamic/Analyses/BaseAnalysis.h +++ b/Dynamic/Analyses/BaseAnalysis.h @@ -6,7 +6,8 @@ enum struct Fulfillment { FULFILLED, UNKNOWN, VIOLATED, INACTIVE }; struct CallBacks { - bool FUNCTION; + bool FUNCTION_PRE; + bool FUNCTION_POST; bool MEMORY_R; bool MEMORY_W; }; @@ -19,7 +20,7 @@ class BaseAnalysis { public: // Event handlers. Return non-unknown if analysis is resolved and no longer needs to be analysed. // onFunctionCall does not forward return address, as it is included in callsiteinfo - inline Fulfillment onFunctionCall(CodePtr const& location, void* const& func, CallsiteInfo const& callsite) { return static_cast(this)->functionCBImpl(func, callsite); }; + inline Fulfillment onFunctionCall(CodePtr const& location, void* const& func, bool const isPre, CallsiteInfo const& callsite) { return static_cast(this)->functionCBImpl(func, isPre, callsite); }; inline Fulfillment onMemoryAccess(CodePtr const& location, void const* const& memory, bool const& isWrite) { return static_cast(this)->memoryCBImpl(std::forward(location), memory, isWrite); }; inline Fulfillment onProgramExit(CodePtr const& location) { return static_cast(this)->exitCBImpl(std::forward(location)); }; diff --git a/Dynamic/Analyses/ParamAnalysis.cpp b/Dynamic/Analyses/ParamAnalysis.cpp index 120df91..59d0760 100644 --- a/Dynamic/Analyses/ParamAnalysis.cpp +++ b/Dynamic/Analyses/ParamAnalysis.cpp @@ -5,7 +5,7 @@ #include -Fulfillment ParamAnalysis::functionCBImpl(void* const& func, CallsiteInfo const& callsite) { +Fulfillment ParamAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { if (func != func_supplier) return Fulfillment::UNKNOWN; for (const ParamReq_t* req : param_requirements) { @@ -18,19 +18,19 @@ Fulfillment ParamAnalysis::functionCBImpl(void* const& func, CallsiteInfo const& switch (req->comparator) { case Comparator::NEQ: if (act_callp != act_req) continue; - return Fulfillment::VIOLATED; + references.push_back(callsite.location); return Fulfillment::VIOLATED; case Comparator::GTEQ: if (act_callp >= act_req) continue; - return Fulfillment::VIOLATED; + references.push_back(callsite.location); return Fulfillment::VIOLATED; case Comparator::GT: if (act_callp > act_req) continue; - return Fulfillment::VIOLATED; + references.push_back(callsite.location); return Fulfillment::VIOLATED; case Comparator::LTEQ: if (act_callp <= act_req) continue; - return Fulfillment::VIOLATED; + references.push_back(callsite.location); return Fulfillment::VIOLATED; case Comparator::LT: if (act_callp < act_req) continue; - return Fulfillment::VIOLATED; + references.push_back(callsite.location); return Fulfillment::VIOLATED; case Comparator::EXEQ: // EXEQ is the exception (pun), it overrides other forbidden values. if (act_callp == act_req) return Fulfillment::FULFILLED; diff --git a/Dynamic/Analyses/ParamAnalysis.h b/Dynamic/Analyses/ParamAnalysis.h index 3df2a7d..049b1cc 100644 --- a/Dynamic/Analyses/ParamAnalysis.h +++ b/Dynamic/Analyses/ParamAnalysis.h @@ -12,11 +12,11 @@ struct ParamAnalysis : BaseAnalysis { } } - ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite); ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; // Evidently none of the callsites were erroneous - constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false}; } + constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false, false}; } private: // Configuration diff --git a/Dynamic/Analyses/PostCallAnalysis.cpp b/Dynamic/Analyses/PostCallAnalysis.cpp index d084c41..3d77258 100644 --- a/Dynamic/Analyses/PostCallAnalysis.cpp +++ b/Dynamic/Analyses/PostCallAnalysis.cpp @@ -22,7 +22,7 @@ PostCallAnalysis::PostCallAnalysis(void const* _func_supplier, CallTagOp_t* call target_funcs = DynamicUtils::getFunctionsForTag(callop->target_tag); } -Fulfillment PostCallAnalysis::functionCBImpl(void* const& func, CallsiteInfo const& callsite) { +Fulfillment PostCallAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { for (void const* const& target_func : target_funcs) { if (target_func == func) { // Target function found, maybe analysis success diff --git a/Dynamic/Analyses/PostCallAnalysis.h b/Dynamic/Analyses/PostCallAnalysis.h index 85ad49d..132b3c3 100644 --- a/Dynamic/Analyses/PostCallAnalysis.h +++ b/Dynamic/Analyses/PostCallAnalysis.h @@ -10,11 +10,11 @@ struct PostCallAnalysis : public BaseAnalysis { PostCallAnalysis(void const* func_supplier, CallOp_t* callop); PostCallAnalysis(void const* func_supplier, CallTagOp_t* callop); - ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite); ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location); - constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false}; } + constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false, false}; } private: void SharedInit(void const* _func_supplier, const char* target_str, CallParam_t *params, int64_t num_params); diff --git a/Dynamic/Analyses/PreCallAnalysis.cpp b/Dynamic/Analyses/PreCallAnalysis.cpp index 0235018..8f6aa57 100644 --- a/Dynamic/Analyses/PreCallAnalysis.cpp +++ b/Dynamic/Analyses/PreCallAnalysis.cpp @@ -22,7 +22,7 @@ PreCallAnalysis::PreCallAnalysis(void const* _func_supplier, CallTagOp_t* callop target_funcs = DynamicUtils::getFunctionsForTag(callop->target_tag); } -Fulfillment PreCallAnalysis::functionCBImpl(void* const& func, CallsiteInfo const& callsite) { +Fulfillment PreCallAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { for (void const* const& target_func : target_funcs) { if (target_func == func) { // Possible match for precall diff --git a/Dynamic/Analyses/PreCallAnalysis.h b/Dynamic/Analyses/PreCallAnalysis.h index 8e7953d..039d506 100644 --- a/Dynamic/Analyses/PreCallAnalysis.h +++ b/Dynamic/Analyses/PreCallAnalysis.h @@ -12,11 +12,11 @@ struct PreCallAnalysis : BaseAnalysis { PreCallAnalysis(void const* func_supplier, CallOp_t* callop); PreCallAnalysis(void const* func_supplier, CallTagOp_t* callop); - ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite); ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::INACTIVE; }; - constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false}; } + constexpr CallBacks requiredCallbacksImpl() const { return {true, false, false, false}; } private: void SharedInit(void const* _func_supplier, const char* target_str, CallParam_t *params, int64_t num_params); diff --git a/Dynamic/Analyses/ReleaseAnalysis.cpp b/Dynamic/Analyses/ReleaseAnalysis.cpp index 1bd2955..51339ff 100644 --- a/Dynamic/Analyses/ReleaseAnalysis.cpp +++ b/Dynamic/Analyses/ReleaseAnalysis.cpp @@ -50,10 +50,10 @@ ReleaseAnalysis::ReleaseAnalysis(void const* _func_supplier, ReleaseOp_t* rOP) { CallBacks ReleaseAnalysis::requiredCallbacksImpl() const { if (!forbIsRW) return {true, false, false}; RWOp_t* rwOp = (RWOp_t*)forbiddenOp; - return {true, !rwOp->isWrite, rwOp->isWrite}; + return {true, false, !rwOp->isWrite, rwOp->isWrite}; } -Fulfillment ReleaseAnalysis::functionCBImpl(void* const& func, CallsiteInfo const& callsite) { +Fulfillment ReleaseAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { if (!forbiddenCallsites.empty()) { // First, check if release for (void const* const& rel_func : rel_funcs) { diff --git a/Dynamic/Analyses/ReleaseAnalysis.h b/Dynamic/Analyses/ReleaseAnalysis.h index fded71c..28f143c 100644 --- a/Dynamic/Analyses/ReleaseAnalysis.h +++ b/Dynamic/Analyses/ReleaseAnalysis.h @@ -8,7 +8,7 @@ struct ReleaseAnalysis : BaseAnalysis { public: ReleaseAnalysis(void const* func_supplier, ReleaseOp_t* rOP); - ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite); ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite); ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; diff --git a/Dynamic/CMakeLists.txt b/Dynamic/CMakeLists.txt index 5640d65..f4b2fda 100644 --- a/Dynamic/CMakeLists.txt +++ b/Dynamic/CMakeLists.txt @@ -15,11 +15,17 @@ else() endif() target_include_directories(CoVerDynamicAnalyzer PUBLIC ../Include/) +find_package(PkgConfig REQUIRED) +pkg_check_modules(FFI REQUIRED libffi) +target_include_directories(CoVerDynamicAnalyzer PRIVATE "${FFI_INCLUDE_DIRS}") + set_property(TARGET CoVerDynamicAnalyzer PROPERTY CXX_VISIBILITY_PRESET hidden) set_property(TARGET CoVerDynamicAnalyzer PROPERTY C_VISIBILITY_PRESET hidden) set_property(TARGET CoVerDynamicAnalyzer PROPERTY VISIBILITY_INLINES_HIDDEN ON) target_compile_options(CoVerDynamicAnalyzer PUBLIC -fno-rtti -fno-exceptions -fomit-frame-pointer -fno-stack-protector) +target_link_libraries(CoVerDynamicAnalyzer libffi) +target_include_directories(CoVerDynamicAnalyzer PRIVATE "${FFI_LIBRARIES}") if (CMAKE_ADDR2LINE) target_compile_definitions(CoVerDynamicAnalyzer PUBLIC CMAKE_ADDR2LINE="${CMAKE_ADDR2LINE}") diff --git a/Dynamic/DynamicUtils.h b/Dynamic/DynamicUtils.h index 7c7f0f7..652d7b0 100644 --- a/Dynamic/DynamicUtils.h +++ b/Dynamic/DynamicUtils.h @@ -25,6 +25,7 @@ struct ConcreteParam { struct CallsiteInfo { CodePtr location; std::vector params; + void const* retval = nullptr; bool operator==(CallsiteInfo const& other) const { return this->location == other.location && params == other.params; } diff --git a/Dynamic/Hooks.cpp b/Dynamic/Hooks.cpp index f166936..0f6d170 100644 --- a/Dynamic/Hooks.cpp +++ b/Dynamic/Hooks.cpp @@ -11,6 +11,8 @@ #include #include +#include + #include "DynamicAnalysis.h" #include "DynamicUtils.h" @@ -88,19 +90,63 @@ extern "C" void __attribute__((visibility("default"))) PPDCV_Initialize(int32_t* DynamicUtils::createMessage("Finished Initializing!"); } -extern "C" void __attribute__((visibility("default"))) PPDCV_FunctionCallback(bool isRef, void* function, int32_t num_params, ...) { +ffi_type* getFFIType(int32_t size) { + switch (size) { + case 0: return &ffi_type_void; + case 16: return &ffi_type_uint16; + case 32: return &ffi_type_uint32; + case 64: return &ffi_type_pointer; + } + __builtin_unreachable(); +} + +extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(bool isRef, void* function, int32_t ret_size, int32_t num_params, ...) { CallsiteInfo callsite = { .location = __builtin_return_address(0) }; + callsite.params.reserve(num_params); std::va_list list; + static std::vector ffi_arg_types; + static std::vector ffi_arg_values_ptr; + static std::vector ffi_arg_values_store; + ffi_arg_types.reserve(num_params); + ffi_arg_types.clear(); + ffi_arg_values_store.reserve(num_params); + ffi_arg_values_store.clear(); + ffi_arg_values_ptr.reserve(num_params); + ffi_arg_values_ptr.clear(); + va_start(list, num_params); for (int i = 0; i < num_params; i++) { uint32_t param_size = va_arg(list,uint32_t); - void const* param_val = va_arg(list,void*); - callsite.params.push_back({param_val, param_size}); + void* param_val = va_arg(list,void*); + if (param_size >> 16) { + // Need to deref value first + callsite.params.push_back({*(void**)param_val, param_size & 0xFF}); + } else { + callsite.params.push_back({param_val, param_size & 0xFF}); + } + ffi_arg_types.push_back(getFFIType((param_size & 0xFF00) >> 8)); + ffi_arg_values_store.push_back(param_val); + ffi_arg_values_ptr.push_back(&ffi_arg_values_store[i]); } va_end(list); // Run event handlers and remove analysis if done - HANDLE_CALLBACK(analyses_with_funcCB, onFunctionCall, function, callsite); + HANDLE_CALLBACK(analyses_with_funcPreCB, onFunctionCall, function, true, callsite); + + // Call the intercepted function + ffi_cif cif; + void* res = nullptr; + if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, num_params, getFFIType(ret_size), ffi_arg_types.data()) == FFI_OK) { + ffi_call(&cif, FFI_FN(function), &res, ffi_arg_values_ptr.data()); + } else { + DynamicUtils::out() << "CRITICAL: Failed to prepare libffi CIF!\n"; + } + + // Run event handlers again for the postCBs, including the newly returned value + callsite.retval = res; + HANDLE_CALLBACK(analyses_with_funcPostCB, onFunctionCall, function, false, callsite); + + return res; } extern "C" void __attribute__((visibility("default"))) PPDCV_MemRCallback(bool isRef, void const* buf) { diff --git a/Dynamic/Hooks.hpp b/Dynamic/Hooks.hpp index 71178e4..fdc9755 100644 --- a/Dynamic/Hooks.hpp +++ b/Dynamic/Hooks.hpp @@ -42,7 +42,8 @@ namespace { std::unordered_map formula_parents; std::unordered_map toplevel_to_contract; std::vector all_analyses; - std::vector analyses_with_funcCB; + std::vector analyses_with_funcPreCB; + std::vector analyses_with_funcPostCB; std::vector analyses_with_memRCB; std::vector analyses_with_memWCB; std::unordered_map> analysis_references; @@ -58,12 +59,13 @@ namespace { CallBacks reqCB = fastVisit([&](auto& analysis) { return analysis->requiredCallbacks(); }, new_pair.analysis); - if (reqCB.FUNCTION) analyses_with_funcCB.push_back(new_pair); + if (reqCB.FUNCTION_PRE) analyses_with_funcPreCB.push_back(new_pair); + if (reqCB.FUNCTION_POST) analyses_with_funcPostCB.push_back(new_pair); if (reqCB.MEMORY_R) analyses_with_memRCB.push_back(new_pair); if (reqCB.MEMORY_W) analyses_with_memWCB.push_back(new_pair); } - #define HANDLE_CALLBACK(pairs, CB, ...) \ + #define HANDLE_CALLBACK(pairs, CB, ...) {\ void const* location = __builtin_return_address(0);\ if (isRef) visitedLocs.insert(location);\ _Pragma("unroll(5)") for (auto it = pairs.begin(); it < pairs.end();) { \ @@ -77,7 +79,7 @@ namespace { }\ return ++it;\ }, it->analysis);\ - } + }} void validateState(ContractFormula_t* form) { if (formula_parents[form] && contract_status.contains(formula_parents[form])) return; // If parent already decided return early @@ -158,7 +160,7 @@ namespace { } ErrorMessage recurseCreateErrorMsg(ContractFormula_t* form) { - if (contract_status[form] != Fulfillment::VIOLATED) return {}; + if (!contract_status.contains(form) || contract_status[form] != Fulfillment::VIOLATED) return {}; if (form->num_children == 0) { ErrorMessage msg; msg.msg = {std::string("Operation Message (if defined) or contract string: ") + form->msg}; diff --git a/Include/DynamicAnalysis.h b/Include/DynamicAnalysis.h index 0dec1f5..cb3de61 100644 --- a/Include/DynamicAnalysis.h +++ b/Include/DynamicAnalysis.h @@ -115,7 +115,7 @@ extern "C" { // Callback function declarations void PPDCV_Initialize(int32_t* argc, char*** argv, ContractDB_t const* DB); -void PPDCV_FunctionCallback(bool isRel, void* function, int32_t num_params, ...); // relevancy, funcptr, num params, then sizeof param and param each +void* PPDCV_FunctionCallback(bool isRel, void* function, int32_t ret_size, int32_t num_params, ...); // relevancy, funcptr, num params, then sizeof param and param each void PPDCV_MemRCallback(bool const isRel, void const* buf); void PPDCV_MemWCallback(bool const isRel, void const* buf); diff --git a/Passes/ContractVerifierAlloc.cpp b/Passes/ContractVerifierAlloc.cpp index 5defadc..621845a 100644 --- a/Passes/ContractVerifierAlloc.cpp +++ b/Passes/ContractVerifierAlloc.cpp @@ -142,7 +142,7 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS } // Not trivial, check if explicitly allocated for (std::pair Candidate : cur.candidates()) { - if (ContractPassUtility::checkParamMatch(CB->getArgOperand(Data->param), Candidate.first, Candidate.second.acc, MAM)) { + if (ContractPassUtility::checkParamMatch(Candidate.first, CB->getArgOperand(Data->param), Candidate.second.acc, MAM)) { // Success! cur.CurVal = AllocStatusVal::ALLOC; return cur; diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 3f7cf21..42dd303 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -118,15 +118,16 @@ PreservedAnalyses InstrumentPass::run(Module &M, initFunc->setLinkage(GlobalValue::ExternalWeakLinkage); Value* Vargc = mainF->getArg(0); Value* Vargv = mainF->getArg(1); - Value* argcptr = new AllocaInst(Int_Type, 0, "argc_ptr", mainF->getEntryBlock().getFirstNonPHIOrDbg()); - Value* argvptr = new AllocaInst(Ptr_Type, 0, "argv_ptr", mainF->getEntryBlock().getFirstNonPHIOrDbg()); + AllocaInst* argcptr = new AllocaInst(Int_Type, 0, "argc_ptr", mainF->getEntryBlock().getFirstNonPHIOrDbg()); + AllocaInst* argvptr = new AllocaInst(Ptr_Type, 0, "argv_ptr", argcptr->getIterator()); CallInst* initFuncCI = CallInst::Create(initFuncCallee, {argcptr, argvptr, GlobalDB}); - initFuncCI->insertBefore(mainF->getEntryBlock().getFirstNonPHIOrDbgOrAlloca()); + initFuncCI->insertAfter(argcptr->getIterator()); + instrument_ignore.insert({argcptr, argvptr}); instrument_ignore.insert(new StoreInst(Vargc, argcptr, initFuncCI->getIterator())); instrument_ignore.insert(new StoreInst(Vargv, argvptr, initFuncCI->getIterator())); // Create callback function for rel func call - // Call sig: Function ptr, num operands, vararg list of operands. Format: {int64-as-bool isptr, size of param, param} for each param. - FunctionType* FunctionCBType = FunctionType::get(Void_Type, {Bool_Type, Ptr_Type, Int_Type}, true); + // Call sig: isRel, Function ptr, ret size, num operands, vararg list of operands. Format: {int64-as-bool isptr, size of param, param} for each param. + FunctionType* FunctionCBType = FunctionType::get(Ptr_Type, {Bool_Type, Ptr_Type, Int_Type, Int_Type}, true); callbackFuncCallee = M.getOrInsertFunction("PPDCV_FunctionCallback", FunctionCBType, fnAttr); Function* callbackFunc = dyn_cast(callbackFuncCallee.getCallee()); callbackFunc->setLinkage(GlobalValue::ExternalWeakLinkage); @@ -257,7 +258,8 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr rwOP = static_pointer_cast(op); Constant* isWrite = ConstantInt::getBool(Bool_Type, op->type() == FormulaType::WRITE); ConstantInt* const_paramacc = ConstantInt::get(Int_Type, (int)rwOP->contrParamAccess); @@ -438,7 +440,7 @@ std::pair InstrumentPass::createParamList(Module& M, std::vec if (params.empty()) return { Null_Const, 0 }; std::vector paramConsts; for (CallParam param : params) { - Constant* pConst = ConstantStruct::get(Param_Type, {ConstantInt::get(Int_Type, param.callP), ConstantInt::getBool(Bool_Type, param.callPisTagVar), ConstantInt::get(Int_Type, param.contrP), ConstantInt::get(Int_Type, (int64_t)param.contrParamAccess)}); + Constant* pConst = ConstantStruct::get(Param_Type, {ConstantInt::get(Int_Type, param.callP), ConstantInt::getBool(Bool_Type, param.callPisTagVar), ConstantInt::get(Int_Type, param.contrP), ConstantInt::get(Int_Type, (int32_t)param.contrParamAccess)}); paramConsts.push_back(pConst); } ArrayType* paramArr_Type = ArrayType::get(Param_Type, paramConsts.size()); @@ -458,6 +460,14 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { int skipnum = 0; std::vector params; params.push_back(callsite->getCalledOperand()); // First param is funcptr + + // Get return value size + if (isC && callsite->getType()->isSized()) { + params.push_back(ConstantInt::get(Int_Type, callsite->getDataLayout().getTypeStoreSizeInBits(callsite->getType()))); + } else { + params.push_back(ConstantInt::get(Int_Type, 0)); + } + params.push_back(ConstantInt::get(Int_Type, callsite->arg_size())); for (Use const& U : callsite->args()) { Value* actual_param = U; @@ -466,7 +476,8 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { // Store size of data type if (isC) { - params.push_back(ConstantInt::get(Int_Type, callsite->getDataLayout().getTypeStoreSizeInBits(U->getType()))); + int size_act = callsite->getDataLayout().getTypeStoreSizeInBits(U->getType()); + params.push_back(ConstantInt::get(Int_Type, (size_act << 8) | (size_act & 0xFF))); // Store actual parameter, making sure to cast if necessary if (!U->getType()->isPointerTy()) { if (U->getType()->isFloatingPointTy()) { @@ -486,11 +497,14 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { } // All parameters are sent as pointers. Need to check exact size using dbg info DIType const* param_type = Dbg->getType()->getTypeArray()[cur_argno + 1]; // Offset by one, first is ret val - params.push_back(ConstantInt::get(Int_Type, param_type->getSizeInBits() == 0 || isa(actual_param) ? 64 : param_type->getSizeInBits())); - // On Fortran, deref if param is an allocate/ptr buffer + int size_act = param_type->getSizeInBits() == 0 || isa(actual_param) ? 64 : param_type->getSizeInBits(); + int size_call = callsite->getDataLayout().getTypeStoreSizeInBits(U->getType()); + // On Fortran, deref if param is an allocate/ptr buffer. + // Indicate with magic bit if (param_type->getTag() == (dwarf::Tag)DW_TAG_array_type) { - actual_param = new LoadInst(Ptr_Type, actual_param, "", callsite->getIterator()); + size_call = size_call | 1 << 8; } + params.push_back(ConstantInt::get(Int_Type, (size_call << 8) | (size_act & 0xFF))); } else { errs() << "ERROR: Could not perform instrumentation! Unable to get debug info for function \"" << callsite->getCalledOperand()->getName() << "\""; } @@ -507,7 +521,8 @@ void InstrumentPass::insertCBIfNeeded(FunctionCallee FC, std::vector pa params.insert(params.begin(), ConstantInt::getBool(Bool_Type, isRelevant(I))); CallInst* callbackCI = CallInst::Create(FC, params); callbackCI->setDebugLoc(I->getDebugLoc()); - callbackCI->insertBefore(I->getIterator()); + if (isa(I)) ReplaceInstWithInst(I, callbackCI); + else callbackCI->insertBefore(I->getIterator()); } bool InstrumentPass::isRelevant(Instruction const* I) const { diff --git a/Scripts/clangContracts.cpp b/Scripts/clangContracts.cpp index 93833ad..c06f12f 100644 --- a/Scripts/clangContracts.cpp +++ b/Scripts/clangContracts.cpp @@ -300,6 +300,6 @@ int main(int argc, const char** argv) { // Finalize executable execSafe("llc -filetype=obj --relocation-model=pic " + opt_level + " " + tmpfile + ".opt -o " + tmpfile + ".opt.o"); - execSafe(WrapTarget + " -fPIC -lm -ldl -lpthread -g -I\"@CONTR_INCLUDE_PATH@\"" + rem_args.first + " " + tmpfile + ".opt.o" + dest_arg); + execSafe(WrapTarget + " -fPIC -lm -ldl -lffi -lpthread -g -I\"@CONTR_INCLUDE_PATH@\"" + rem_args.first + " " + tmpfile + ".opt.o" + dest_arg); return 0; } From 03690c717b408c7d54921b6156cdbb2bb9968475 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 18 Mar 2026 14:42:55 +0100 Subject: [PATCH 045/125] Fix broken contracts --- Scripts/gen_mpi_contr_h.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 89a9b9b..308c8af 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -369,7 +369,7 @@ def add_contract(func: str, scope: str, contr: str): ("MPI_Win_allocate", 4), ] for func, idx in allocators: - add_contract(func, "POST", f"alloc!(&{idx})") + add_contract(func, "POST", f"alloc!(*{idx})") # Datatype should not be null when doing communication @@ -496,7 +496,7 @@ def get_param_values(lang: str) -> str: subroutine FortFree() bind(c, name="_FortranAPointerDeallocate") end subroutine FortFree end interface - call Declare_Contract(FortAlloc, \"POST { alloc!(0) }\") + call Declare_Contract(FortAlloc, \"POST { alloc!(*0) }\") call Declare_Contract(FortFree, \"POST { free!(0) }\") """ From 78330f2b4545de51e6d40878e17d692127ec2eaf Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 18 Mar 2026 14:46:33 +0100 Subject: [PATCH 046/125] Dont send uninit data in test Not a bug, but easier to inspect if something does go wrong --- Tests/c/Correct-P2PStackBuf.c | 2 +- Tests/fort/Correct-P2PStackBuf.F90 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/c/Correct-P2PStackBuf.c b/Tests/c/Correct-P2PStackBuf.c index e6d33ca..e6727a6 100644 --- a/Tests/c/Correct-P2PStackBuf.c +++ b/Tests/c/Correct-P2PStackBuf.c @@ -6,7 +6,7 @@ int main(int argc, char** argv) { int rank; - int buf; + int buf = 42; MPI_Request req; MPI_Init(NULL, NULL); diff --git a/Tests/fort/Correct-P2PStackBuf.F90 b/Tests/fort/Correct-P2PStackBuf.F90 index 15e857a..d9d9e56 100644 --- a/Tests/fort/Correct-P2PStackBuf.F90 +++ b/Tests/fort/Correct-P2PStackBuf.F90 @@ -5,7 +5,7 @@ program main implicit none integer :: rank - integer :: buf(1) + integer :: buf(1) = 42 type(MPI_Request) :: req call MPI_Init() From 893824afc45f322fe7311ccd9f2c0ecfbb5bb764 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 18 Mar 2026 14:47:15 +0100 Subject: [PATCH 047/125] Add secondary error reporting --- Dynamic/Hooks.hpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Dynamic/Hooks.hpp b/Dynamic/Hooks.hpp index fdc9755..52cad4b 100644 --- a/Dynamic/Hooks.hpp +++ b/Dynamic/Hooks.hpp @@ -82,7 +82,14 @@ namespace { }} void validateState(ContractFormula_t* form) { - if (formula_parents[form] && contract_status.contains(formula_parents[form])) return; // If parent already decided return early + if (formula_parents[form] && contract_status.contains(formula_parents[form])) { + if (contract_status[formula_parents[form]] == Fulfillment::VIOLATED && !formula_parents[formula_parents[form]]) { + // Another child of top-level formula violated. Two possible errors, though maybe just a symptom of the first one. + DynamicUtils::out() << "Note: Possible secondary issue detected! This may be a true FP or a side effect of the previous report.\n"; + formatError(recurseCreateErrorMsg(form)); + } + return; // If parent already decided return early + } if (contract_status[form] != Fulfillment::VIOLATED && !(contract_status[form] == Fulfillment::FULFILLED && formula_parents[form] && formula_parents[form]->conn == XOR)) return; From 8c7ec9845c74dc2595db3b79a55d0cd0c5d18354 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 19 Mar 2026 13:24:38 +0100 Subject: [PATCH 048/125] Add allocation size specifiers Not useful statically, but will be useful to avoid FPs once dynamic lands --- Grammars/ContractLexer.g4 | 5 +++++ Grammars/ContractParser.g4 | 7 ++++++- Include/ContractTree.hpp | 10 +++++++++- LangCode/ContractDataVisitor.cpp | 23 +++++++++++++++++++---- LangCode/ContractDataVisitor.hpp | 1 + Scripts/gen_mpi_contr_h.py | 5 +++-- 6 files changed, 43 insertions(+), 8 deletions(-) diff --git a/Grammars/ContractLexer.g4 b/Grammars/ContractLexer.g4 index f0c9c96..bdc5f51 100644 --- a/Grammars/ContractLexer.g4 +++ b/Grammars/ContractLexer.g4 @@ -28,6 +28,8 @@ TagParam: '$'; Deref: '*'; AddrOf: '&'; +MarkArg: '_arg'; + // All ops must end with '!' to differentiate from variables OPRead: 'read!'; OPWrite: 'write!'; @@ -38,8 +40,11 @@ OPRelease2: 'until!'; OPParam: 'param!'; OPAlloc: 'alloc!'; OPFree: 'free!'; + OPPrefix: '('; OPPostfix: ')'; +RWOffsetPrefix: '['; +RWOffsetSuffix: ']'; ParamForbidEq: '!='; ParamGt: '>'; diff --git a/Grammars/ContractParser.g4 b/Grammars/ContractParser.g4 index fd728b1..caf803b 100644 --- a/Grammars/ContractParser.g4 +++ b/Grammars/ContractParser.g4 @@ -17,7 +17,12 @@ tagUnit: Variable (OPPrefix NatNum OPPostfix)?; expression: callOp | releaseOp | paramOp | rwOp; // rwOp only makes sense for alloc though -rwOp: (OPRead | OPWrite | OPAlloc | OPFree) OPPrefix (Deref | AddrOf)? NatNum OPPostfix; +natExpr: NatNum | NatNum MarkArg; +multExpr: Deref mathExpr; +mathOp: multExpr; +mathExpr: natExpr mathOp?; + +rwOp: (OPRead | OPWrite | OPAlloc | OPFree) OPPrefix (Deref | AddrOf)? arg_index=NatNum (RWOffsetPrefix alloc_size=mathExpr RWOffsetSuffix)? OPPostfix; varMap: (callP=NatNum | TagParam) MapSep (Deref | AddrOf)? contrP=NatNum; callOp: (OPCall | OPCallTag) OPPrefix Variable (ListSep varMap)* OPPostfix; paramOp: OPParam OPPrefix NatNum MapSep paramReq (ListSep paramReq)* OPPostfix; diff --git a/Include/ContractTree.hpp b/Include/ContractTree.hpp index c5611fa..46afe9c 100644 --- a/Include/ContractTree.hpp +++ b/Include/ContractTree.hpp @@ -16,6 +16,13 @@ namespace ContractTree { enum struct FormulaType { AND, OR, XOR, RWOP, READ, WRITE, ALLOC, FREE, CALL, CALLTAG, RELEASE, PARAM }; enum struct ParamAccess { NORMAL, DEREF, ADDROF }; + enum struct MathType { UNARY_VALUE, MULT }; + struct MathExpr { + int value; + bool isArgValue; + MathType type; + std::shared_ptr other = nullptr; + }; struct Operation { virtual ~Operation() = default; virtual const FormulaType type() const = 0; @@ -35,7 +42,8 @@ namespace ContractTree { virtual const FormulaType type() const override { return FormulaType::WRITE; }; }; struct AllocOperation : RWOperation { - AllocOperation(int _contrP, ParamAccess _acc) : RWOperation(_contrP, _acc) {}; + const std::shared_ptr size; + AllocOperation(int _contrP, ParamAccess _acc, std::shared_ptr _size) : RWOperation(_contrP, _acc), size(_size) {}; virtual const FormulaType type() const override { return FormulaType::ALLOC; }; }; struct FreeOperation : RWOperation { diff --git a/LangCode/ContractDataVisitor.cpp b/LangCode/ContractDataVisitor.cpp index c1ce8a4..141ee98 100644 --- a/LangCode/ContractDataVisitor.cpp +++ b/LangCode/ContractDataVisitor.cpp @@ -69,19 +69,34 @@ std::any ContractDataVisitor::visitExpression(ContractParser::ExpressionContext return ContractExpression(ctx->getText(), opPtr); } +std::any ContractDataVisitor::visitMathExpr(ContractParser::MathExprContext* ctx) { + MathExpr expr; + expr.isArgValue = ctx->natExpr()->MarkArg(); + expr.value = std::stoi(ctx->natExpr()->NatNum()->getText()); + if (ctx->mathOp()) { + if (ctx->mathOp()->multExpr()) { + expr.type = MathType::MULT; + expr.other = std::any_cast>(visitMathExpr(ctx->mathOp()->multExpr()->mathExpr())); + } + } else { + expr.type = MathType::UNARY_VALUE; + } + return std::make_shared(expr); +} + std::any ContractDataVisitor::visitRwOp(ContractParser::RwOpContext *ctx) { ParamAccess acc = ParamAccess::NORMAL; if (ctx->Deref()) acc = ParamAccess::DEREF; if (ctx->AddrOf()) acc = ParamAccess::ADDROF; std::shared_ptr op; if (ctx->OPRead()) - op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); + op = std::make_shared(std::stoi(ctx->arg_index->getText()), acc); else if (ctx->OPWrite()) - op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); + op = std::make_shared(std::stoi(ctx->arg_index->getText()), acc); else if (ctx->OPAlloc()) - op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); + op = std::make_shared(std::stoi(ctx->arg_index->getText()), acc, ctx->alloc_size ? std::any_cast>(visitMathExpr(ctx->alloc_size)) : std::make_shared(0, false, MathType::UNARY_VALUE)); else if (ctx->OPFree()) - op = std::make_shared(std::stoi(ctx->NatNum()->getText()), acc); + op = std::make_shared(std::stoi(ctx->arg_index->getText()), acc); return op; } std::any ContractDataVisitor::visitParamOp(ContractParser::ParamOpContext *ctx) { diff --git a/LangCode/ContractDataVisitor.hpp b/LangCode/ContractDataVisitor.hpp index 169557f..9c63f1a 100644 --- a/LangCode/ContractDataVisitor.hpp +++ b/LangCode/ContractDataVisitor.hpp @@ -12,6 +12,7 @@ class ContractDataVisitor : public ContractParserBaseVisitor { std::any visitExprList(ContractParser::ExprListContext *ctx) override; std::any visitExprFormula(ContractParser::ExprFormulaContext *ctx) override; std::any visitExpression(ContractParser::ExpressionContext *ctx) override; + std::any visitMathExpr(ContractParser::MathExprContext* ctx) override; std::any visitRwOp(ContractParser::RwOpContext *ctx) override; std::any visitParamOp(ContractParser::ParamOpContext *ctx) override; std::any visitCallOp(ContractParser::CallOpContext *ctx) override; diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 308c8af..8e0d7ac 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -470,8 +470,9 @@ def get_param_values(lang: str) -> str: {get_param_values("c")} -void* calloc(size_t num, size_t size) CONTRACT( POST {{ alloc!(99) }}); -void* malloc(size_t size) CONTRACT( POST {{ alloc!(99) }}); + +void* calloc(size_t num, size_t size) CONTRACT( POST {{ alloc!(99[ 0 _arg * 1 _arg]) }}); +void* malloc(size_t size) CONTRACT( POST {{ alloc!(99[0 _arg]) }}); void free(void*) CONTRACT( POST {{ free!(0) }}); From c15660fd1315ce5aee7e82806cad50d6c00539c2 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 20 Mar 2026 16:09:03 +0100 Subject: [PATCH 049/125] Remove obsolete warning --- Passes/ContractVerifierAlloc.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Passes/ContractVerifierAlloc.cpp b/Passes/ContractVerifierAlloc.cpp index 621845a..6b4c9bd 100644 --- a/Passes/ContractVerifierAlloc.cpp +++ b/Passes/ContractVerifierAlloc.cpp @@ -121,7 +121,6 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS if (const CallBase* CB = dyn_cast(I)) { if (AllocFuncs.contains(CB->getCalledOperand())) { for (const AllocOperation* alloc : AllocFuncs[CB->getCalledOperand()]) { - #warning TODO different access patterns if (alloc->contrP == 99) cur.addAllocatedValue(CB, alloc->contrParamAccess); else cur.addAllocatedValue(CB->getArgOperand(alloc->contrP), alloc->contrParamAccess); } From 774ecf691247acde73687f50402fa0d6c8a2ac6b Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 20 Mar 2026 16:18:18 +0100 Subject: [PATCH 050/125] Offload basictypes to analysis --- CMakeLists.txt | 1 + Passes/BasicTypes.cpp | 22 ++++++++ Passes/BasicTypes.hpp | 33 ++++++++++++ Passes/Instrument.cpp | 123 ++++++++++++++++++------------------------ Passes/Instrument.hpp | 7 +-- 5 files changed, 110 insertions(+), 76 deletions(-) create mode 100644 Passes/BasicTypes.cpp create mode 100644 Passes/BasicTypes.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 194dfc4..15cb543 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ target_compile_options (ContractLanguage PUBLIC -fexceptions) add_llvm_pass_plugin(CoVerPlugin Passes/Registrar.cpp + Passes/BasicTypes.cpp Passes/ContractManager.cpp Passes/ContractVerifierPreCall.cpp Passes/ContractVerifierPostCall.cpp diff --git a/Passes/BasicTypes.cpp b/Passes/BasicTypes.cpp new file mode 100644 index 0000000..74f427f --- /dev/null +++ b/Passes/BasicTypes.cpp @@ -0,0 +1,22 @@ +#include "BasicTypes.hpp" + +#include +#include + +using namespace llvm; + +BasicTypesAnalysis::BasicTypes BasicTypesAnalysis::run(Module &M, ModuleAnalysisManager &AM) { + BasicTypes types; + + // Basic Types + types.Ptr_Type = PointerType::get(M.getContext(), 0); + types.Int_Type = IntegerType::get(M.getContext(), 32); + types.Int64_Type = IntegerType::get(M.getContext(), 64); + types.Bool_Type = IntegerType::get(M.getContext(), 1); + types.Void_Type = Type::getVoidTy(M.getContext()); + + // Basic Constants + types.Null_Const = ConstantPointerNull::getNullValue(types.Ptr_Type); + + return types; +} diff --git a/Passes/BasicTypes.hpp b/Passes/BasicTypes.hpp new file mode 100644 index 0000000..c052c58 --- /dev/null +++ b/Passes/BasicTypes.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace llvm { + +class BasicTypesAnalysis : public AnalysisInfoMixin { + public: + static inline llvm::AnalysisKey Key; + //Result Type + struct Result { + PointerType* Ptr_Type; + IntegerType* Bool_Type; + Constant* getBool(bool x) { return ConstantInt::getBool(Bool_Type, x); } + IntegerType* Int_Type; + ConstantInt* getInt(int x) { return ConstantInt::get(Int_Type, x); } + IntegerType* Int64_Type; + ConstantInt* getInt64(int x) { return ConstantInt::get(Int64_Type, x); } + Type* Void_Type; + Constant* Null_Const; + bool invalidate(Module &, PreservedAnalyses const&, ModuleAnalysisManager::Invalidator const&) const { + return false; + } + } typedef BasicTypes; + // Run Analysis + BasicTypes run(Module &M, ModuleAnalysisManager &AM); +}; + +} diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 42dd303..90ee52f 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -49,6 +49,7 @@ static cl::opt ClInstrumentType( PreservedAnalyses InstrumentPass::run(Module &M, ModuleAnalysisManager &AM) { DB = &AM.getResult(M); + Basic_Types = AM.getResult(M); Function* mainF = M.getFunction("main"); if (!mainF) return PreservedAnalyses::all(); // No point @@ -84,7 +85,7 @@ PreservedAnalyses InstrumentPass::run(Module &M, err_msgs.push_back(msg); } - // Generic Types and consts + // Contract Types and consts createTypes(M); // Create Tag globals @@ -111,30 +112,16 @@ PreservedAnalyses InstrumentPass::run(Module &M, fnAttr = fnAttr.addFnAttribute(M.getContext(), Attribute::WillReturn); fnAttr = fnAttr.addFnAttribute(M.getContext(), Attribute::NoCallback); - // Create initialization routine for tool - FunctionType* InitCBType = FunctionType::get(Void_Type, {Ptr_Type, Ptr_Type, Ptr_Type}, false); - FunctionCallee initFuncCallee = M.getOrInsertFunction("PPDCV_Initialize", InitCBType, fnAttr); - Function* initFunc = dyn_cast(initFuncCallee.getCallee()); - initFunc->setLinkage(GlobalValue::ExternalWeakLinkage); - Value* Vargc = mainF->getArg(0); - Value* Vargv = mainF->getArg(1); - AllocaInst* argcptr = new AllocaInst(Int_Type, 0, "argc_ptr", mainF->getEntryBlock().getFirstNonPHIOrDbg()); - AllocaInst* argvptr = new AllocaInst(Ptr_Type, 0, "argv_ptr", argcptr->getIterator()); - CallInst* initFuncCI = CallInst::Create(initFuncCallee, {argcptr, argvptr, GlobalDB}); - initFuncCI->insertAfter(argcptr->getIterator()); - instrument_ignore.insert({argcptr, argvptr}); - instrument_ignore.insert(new StoreInst(Vargc, argcptr, initFuncCI->getIterator())); - instrument_ignore.insert(new StoreInst(Vargv, argvptr, initFuncCI->getIterator())); // Create callback function for rel func call // Call sig: isRel, Function ptr, ret size, num operands, vararg list of operands. Format: {int64-as-bool isptr, size of param, param} for each param. - FunctionType* FunctionCBType = FunctionType::get(Ptr_Type, {Bool_Type, Ptr_Type, Int_Type, Int_Type}, true); + FunctionType* FunctionCBType = FunctionType::get(Basic_Types.Ptr_Type, {Basic_Types.Bool_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Int_Type}, true); callbackFuncCallee = M.getOrInsertFunction("PPDCV_FunctionCallback", FunctionCBType, fnAttr); Function* callbackFunc = dyn_cast(callbackFuncCallee.getCallee()); callbackFunc->setLinkage(GlobalValue::ExternalWeakLinkage); // Create callback function for RW // Call sig: int64-as-bool isWrite, mem ptr - FunctionType* FunctionRWType = FunctionType::get(Void_Type, {Bool_Type, Ptr_Type}, false); + FunctionType* FunctionRWType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Bool_Type, Basic_Types.Ptr_Type}, false); callbackRCallee = M.getOrInsertFunction("PPDCV_MemRCallback", FunctionRWType, fnAttr); Function* callbackR = dyn_cast(callbackRCallee.getCallee()); callbackR->setLinkage(GlobalValue::ExternalWeakLinkage); @@ -157,7 +144,7 @@ Constant* InstrumentPass::createTagGlobal(Module& M) { int count = 0; for (std::pair> functags : DB->Tags) { for (TagUnit tag : functags.second) { - Constant* param = ConstantInt::get(Int_Type, tag.param ? *tag.param : -1); + Constant* param = ConstantInt::get(Basic_Types.Int_Type, tag.param ? *tag.param : -1); Constant* str = ConstantDataArray::getString(M.getContext(), tag.tag); GlobalVariable* strGlobal = createConstantGlobal(M, str, "CONTR_TAG_STR_" + tag.tag); Constant* TagC = ConstantStruct::get(Tag_Type, {strGlobal,param}); @@ -168,13 +155,13 @@ Constant* InstrumentPass::createTagGlobal(Module& M) { } // Create global const arrays for the tags - ArrayType* ArrFuncTy = ArrayType::get(Ptr_Type, count); + ArrayType* ArrFuncTy = ArrayType::get(Basic_Types.Ptr_Type, count); ArrayType* ArrTagTy = ArrayType::get(Tag_Type, count); GlobalVariable* ptrFuncs = createConstantGlobal(M, ConstantArray::get(ArrFuncTy, funcs), "CONTR_TAG_ARRAY_PTRS"); GlobalVariable* ptrTags = createConstantGlobal(M, ConstantArray::get(ArrTagTy, tags), "CONTR_TAG_ARRAY_TAGS"); // Full tag map structure - Constant* TagsStruct = ConstantStruct::get(Tags_Type, {ptrFuncs, ptrTags, ConstantInt::get(Int_Type, count)}); + Constant* TagsStruct = ConstantStruct::get(Tags_Type, {ptrFuncs, ptrTags, ConstantInt::get(Basic_Types.Int_Type, count)}); return TagsStruct; } @@ -214,19 +201,19 @@ std::pair InstrumentPass::createContractsGlobal(Module& M) { Constant* InstrumentPass::createScopeGlobal(Module& M, std::vector> forms) { std::vector formsConst; static Constant* scopeMsgConst = createConstantGlobal(M, ConstantDataArray::getString(M.getContext(), "Full Scope"), "CONTR_SCOPE_STR_"); - if (forms.empty()) return Null_Const; + if (forms.empty()) return Basic_Types.Null_Const; for (std::shared_ptr form : forms) { formsConst.push_back(createFormulaGlobal(M, form)); } ArrayType* ArrPreCond = ArrayType::get(Formula_Type, forms.size()); GlobalVariable* Sublevel = createConstantGlobalUnique(M, ConstantArray::get(ArrPreCond, formsConst), std::string("CONTR_SCOPECONDITIONS")); - return createConstantGlobalUnique(M, ConstantStruct::get(Formula_Type, { Sublevel, ConstantInt::get(Int_Type, forms.size()), ConstantInt::get(Int_Type, (int64_t)FormulaType::AND), scopeMsgConst, Null_Const}), "CONTR_SCOPE"); + return createConstantGlobalUnique(M, ConstantStruct::get(Formula_Type, { Sublevel, Basic_Types.getInt(forms.size()), Basic_Types.getInt((int64_t)FormulaType::AND), scopeMsgConst, Basic_Types.Null_Const}), "CONTR_SCOPE"); } Constant* InstrumentPass::createFormulaGlobal(Module& M, std::shared_ptr form) { - Constant* op_const = Null_Const; - Constant* children = Null_Const; - Constant* msg = Null_Const; + Constant* op_const = Basic_Types.Null_Const; + Constant* children = Basic_Types.Null_Const; + Constant* msg = Basic_Types.Null_Const; std::string descriptor = form->Message ? form->Message->text : form->ExprStr; msg = createConstantGlobal(M, ConstantDataArray::getString(M.getContext(), descriptor), "CONTR_MSG_" + descriptor); int64_t connective; @@ -244,11 +231,11 @@ Constant* InstrumentPass::createFormulaGlobal(Module& M, std::shared_ptrChildren.size()), ConstantInt::get(Int_Type, connective), msg, op_const}); + return ConstantStruct::get(Formula_Type, {children, Basic_Types.getInt(form->Children.size()), Basic_Types.getInt(connective), msg, op_const}); } Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr op) { - Constant* data = Null_Const; + Constant* data = Basic_Types.Null_Const; std::string name = "UNKNOWN"; switch (op->type()) { case FormulaType::AND: @@ -261,9 +248,9 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr rwOP = static_pointer_cast(op); - Constant* isWrite = ConstantInt::getBool(Bool_Type, op->type() == FormulaType::WRITE); - ConstantInt* const_paramacc = ConstantInt::get(Int_Type, (int)rwOP->contrParamAccess); - ConstantInt* const_idx = ConstantInt::get(Int_Type, (int)rwOP->contrP); + Constant* isWrite = Basic_Types.getBool(op->type() == FormulaType::WRITE); + ConstantInt* const_paramacc = Basic_Types.getInt((int)rwOP->contrParamAccess); + ConstantInt* const_idx = Basic_Types.getInt((int)rwOP->contrP); data = ConstantStruct::get(RWOp_Type, {const_idx, const_paramacc, isWrite}); name = "CONTR_RWOP"; break; @@ -275,7 +262,7 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptrFunction); std::pair paramGlobal = createParamList(M, cOP->Params); - data = ConstantStruct::get(CallOp_Type, {createConstantGlobal(M, funcStr, "CONTR_FUNC_STR_" + cOP->Function), paramGlobal.first, ConstantInt::get(Int_Type, paramGlobal.second), F ? F : Null_Const}); + data = ConstantStruct::get(CallOp_Type, {createConstantGlobal(M, funcStr, "CONTR_FUNC_STR_" + cOP->Function), paramGlobal.first, Basic_Types.getInt(paramGlobal.second), F ? F : Basic_Types.Null_Const}); name = "CONTR_CALLOP"; break; } @@ -283,16 +270,16 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr cOP = static_pointer_cast(op); data = createConstantGlobal(M, ConstantDataArray::getString(M.getContext(), cOP->Function), "CONTR_TAG_STR_" + cOP->Function); std::pair paramGlobal = createParamList(M, cOP->Params); - data = ConstantStruct::get(CallTagOp_Type, {data, paramGlobal.first, ConstantInt::get(Int_Type, paramGlobal.second)}); + data = ConstantStruct::get(CallTagOp_Type, {data, paramGlobal.first, Basic_Types.getInt(paramGlobal.second)}); name = "CONTR_CALLTAGOP"; break; } case FormulaType::RELEASE: { std::shared_ptr rOP = static_pointer_cast(op); Constant* forbidden_op = createOperationGlobal(M, rOP->Forbidden); - Constant* forb_type = ConstantInt::get(Int_Type, (int64_t)rOP->Forbidden->type()); + Constant* forb_type = Basic_Types.getInt((int64_t)rOP->Forbidden->type()); Constant* release_op = createOperationGlobal(M, rOP->Until); - Constant* release_type = ConstantInt::get(Int_Type, (int64_t)rOP->Until->type()); + Constant* release_type = Basic_Types.getInt((int64_t)rOP->Until->type()); data = ConstantStruct::get(ReleaseOp_Type, {release_op, release_type, forbidden_op, forb_type}); name = "CONTR_RELEASE"; break; @@ -301,12 +288,12 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr pOP = static_pointer_cast(op); std::vector reqCs; for (std::pair req : pOP->reqs) { - Constant* var = Null_Const; + Constant* var = Basic_Types.Null_Const; try { int ivalue = std::stoi(req.second); - var = ConstantInt::get(Type::getInt64Ty(M.getContext()), ivalue); - var = ConstantExpr::getIntToPtr(var, Ptr_Type); - reqCs.push_back(ConstantStruct::get(ParamReq_Type, {ConstantInt::get(Int_Type, req.first), var, ConstantInt::get(Bool_Type, false)})); + var = Basic_Types.getInt64(ivalue); + var = ConstantExpr::getIntToPtr(var, Basic_Types.Ptr_Type); + reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.first), var, Basic_Types.getBool(false)})); } catch(std::exception& e) { if (!DB->ContractVariableData.contains(req.second)) { errs() << "Undefined non-constint contract value identifier \"" << req.second << "\"!\n"; @@ -315,17 +302,17 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptrContractVariableData[req.second]) { if (isa(V)) var = (Constant*)V; - if (isa(var)) var = ConstantExpr::getIntToPtr(var, Ptr_Type); + if (isa(var)) var = ConstantExpr::getIntToPtr(var, Basic_Types.Ptr_Type); if (!isa(var)) { errs() << "Weird param error in instr pass\n"; } - reqCs.push_back(ConstantStruct::get(ParamReq_Type, {ConstantInt::get(Int_Type, req.first), var, ConstantInt::get(Bool_Type, var->getName().starts_with("_QQ"))})); + reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.first), var, Basic_Types.getBool(var->getName().starts_with("_QQ"))})); } } } Constant* reqsC = ConstantArray::get(ArrayType::get(ParamReq_Type, reqCs.size()), reqCs); reqsC = createConstantGlobalUnique(M, reqsC, "CONTR_PARAM_REQS"); - data = ConstantStruct::get(ParamOp_Type, {ConstantInt::get(Int_Type, pOP->idx), reqsC, ConstantInt::get(Int_Type, reqCs.size())}); + data = ConstantStruct::get(ParamOp_Type, {Basic_Types.getInt(pOP->idx), reqsC, Basic_Types.getInt(reqCs.size())}); name = "CONTR_PARAMOP"; break; } @@ -346,53 +333,47 @@ GlobalVariable* InstrumentPass::createConstantGlobal(Module& M, Constant* C, std } void InstrumentPass::createTypes(Module& M) { - // Basic Types - Ptr_Type = PointerType::get(M.getContext(), 0); - Int_Type = IntegerType::get(M.getContext(), 32); - Bool_Type = IntegerType::get(M.getContext(), 1); - Null_Const = ConstantPointerNull::getNullValue(Ptr_Type); - Void_Type = Type::getVoidTy(M.getContext()); - // Operations Param_Type = StructType::create(M.getContext(), "CallParam_t"); - Param_Type->setBody({Int_Type, Bool_Type, Int_Type, Int_Type}); // call param, bool param is tag ref, contr param, acc type + Param_Type->setBody({Basic_Types.Int_Type, Basic_Types.Bool_Type, Basic_Types.Int_Type, Basic_Types.Int_Type}); // call param, bool param is tag ref, contr param, acc type CallOp_Type = StructType::create(M.getContext(), "CallOp_t"); - CallOp_Type->setBody({Ptr_Type, Ptr_Type, Int_Type, Ptr_Type}); // char* Function Name, list of params, num of params, Function Pointer + CallOp_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type}); // char* Function Name, list of params, num of params, Function Pointer CallTagOp_Type = StructType::create(M.getContext(), "CallTagOp_t"); - CallTagOp_Type->setBody({Ptr_Type, Ptr_Type, Int_Type}); // char* Tag name, list of params, num of params + CallTagOp_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // char* Tag name, list of params, num of params ReleaseOp_Type = StructType::create(M.getContext(), "ReleaseOp_t"); - ReleaseOp_Type->setBody({Ptr_Type, Int_Type, Ptr_Type, Int_Type}); // void* release op, relop type, void* forbidden op, forbop type + ReleaseOp_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // void* release op, relop type, void* forbidden op, forbop type RWOp_Type = StructType::create(M.getContext(), "RWOp_t"); - RWOp_Type->setBody({Int_Type, Int_Type, Bool_Type}); // idx, paramaccess, isWrite + RWOp_Type->setBody({Basic_Types.Int_Type, Basic_Types.Int_Type, Basic_Types.Bool_Type}); // idx, paramaccess, isWrite ParamOp_Type = StructType::create(M.getContext(), "ParamOp_t"); - ParamOp_Type->setBody({Int_Type, Ptr_Type, Int_Type}); // idx, list of reqs, num reqs + ParamOp_Type->setBody({Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // idx, list of reqs, num reqs + // Composite Types Tag_Type = StructType::create(M.getContext(), "Tag_t"); - Tag_Type->setBody({Ptr_Type, Int_Type}); // tag str, param num + Tag_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // tag str, param num Formula_Type = StructType::create(M.getContext(), "ContractFormula_t"); - Formula_Type->setBody({Ptr_Type, Int_Type, Int_Type, Ptr_Type, Ptr_Type}); // Children, number of children, connective, message char*, expression data ptr + Formula_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}); // Children, number of children, connective, message char*, expression data ptr Contract_Type = StructType::create(M.getContext(), "Contract_t"); - Contract_Type->setBody({Ptr_Type, Ptr_Type, Ptr_Type, Ptr_Type}); // Precondition ptr, Postcondition ptr, contr supplier ptr, supplier name + Contract_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}); // Precondition ptr, Postcondition ptr, contr supplier ptr, supplier name Tags_Type = StructType::create(M.getContext(), "TagsMap_t"); - Tags_Type->setBody({Ptr_Type, Ptr_Type, Int_Type}); // Funcptr list, Tag + param struct list, num elems + Tags_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // Funcptr list, Tag + param struct list, num elems Ref_Type = StructType::create(M.getContext(), "Reference_t"); - Ref_Type->setBody({Ptr_Type, Ptr_Type}); // char* file ref, char* type + Ref_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}); // char* file ref, char* type ParamReq_Type = StructType::create(M.getContext(), "ParamReq_t"); - ParamReq_Type->setBody({Int_Type, Ptr_Type, Bool_Type}); // Comparator, Value + ParamReq_Type->setBody({Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Bool_Type}); // Comparator, Value DB_Type = StructType::create(M.getContext(), "ContractDB_t"); - DB_Type->setBody({Ptr_Type, Int_Type, Tags_Type, Ptr_Type, Int_Type}); // contract list, num elems, tag container, reference list, num refs + DB_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Int_Type, Tags_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // contract list, num elems, tag container, reference list, num refs } void InstrumentPass::instrumentFunctions(Module &M) { @@ -437,10 +418,10 @@ void InstrumentPass::instrumentRW(Module &M) { } std::pair InstrumentPass::createParamList(Module& M, std::vector params) { - if (params.empty()) return { Null_Const, 0 }; + if (params.empty()) return { Basic_Types.Null_Const, 0 }; std::vector paramConsts; for (CallParam param : params) { - Constant* pConst = ConstantStruct::get(Param_Type, {ConstantInt::get(Int_Type, param.callP), ConstantInt::getBool(Bool_Type, param.callPisTagVar), ConstantInt::get(Int_Type, param.contrP), ConstantInt::get(Int_Type, (int32_t)param.contrParamAccess)}); + Constant* pConst = ConstantStruct::get(Param_Type, {Basic_Types.getInt(param.callP), Basic_Types.getBool(param.callPisTagVar), Basic_Types.getInt(param.contrP), Basic_Types.getInt((int32_t)param.contrParamAccess)}); paramConsts.push_back(pConst); } ArrayType* paramArr_Type = ArrayType::get(Param_Type, paramConsts.size()); @@ -463,12 +444,12 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { // Get return value size if (isC && callsite->getType()->isSized()) { - params.push_back(ConstantInt::get(Int_Type, callsite->getDataLayout().getTypeStoreSizeInBits(callsite->getType()))); + params.push_back(Basic_Types.getInt(callsite->getDataLayout().getTypeStoreSizeInBits(callsite->getType()))); } else { - params.push_back(ConstantInt::get(Int_Type, 0)); + params.push_back(Basic_Types.getInt(0)); } - params.push_back(ConstantInt::get(Int_Type, callsite->arg_size())); + params.push_back(Basic_Types.getInt(callsite->arg_size())); for (Use const& U : callsite->args()) { Value* actual_param = U; int const cur_argno = callsite->getArgOperandNo(&U); @@ -477,14 +458,14 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { // Store size of data type if (isC) { int size_act = callsite->getDataLayout().getTypeStoreSizeInBits(U->getType()); - params.push_back(ConstantInt::get(Int_Type, (size_act << 8) | (size_act & 0xFF))); + params.push_back(Basic_Types.getInt((size_act << 8) | (size_act & 0xFF))); // Store actual parameter, making sure to cast if necessary if (!U->getType()->isPointerTy()) { if (U->getType()->isFloatingPointTy()) { - actual_param = CastInst::Create(Instruction::CastOps::BitCast, actual_param, Int_Type, "", callsite->getIterator()); + actual_param = CastInst::Create(Instruction::CastOps::BitCast, actual_param, Basic_Types.Int_Type, "", callsite->getIterator()); } // Now, actual pointer cast - actual_param = CastInst::Create(Instruction::CastOps::IntToPtr, actual_param, Ptr_Type, "", callsite->getIterator()); + actual_param = CastInst::Create(Instruction::CastOps::IntToPtr, actual_param, Basic_Types.Ptr_Type, "", callsite->getIterator()); } } else { if (Function const* F = dyn_cast(callsite->getCalledOperand())) { @@ -504,7 +485,7 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { if (param_type->getTag() == (dwarf::Tag)DW_TAG_array_type) { size_call = size_call | 1 << 8; } - params.push_back(ConstantInt::get(Int_Type, (size_call << 8) | (size_act & 0xFF))); + params.push_back(Basic_Types.getInt((size_call << 8) | (size_act & 0xFF))); } else { errs() << "ERROR: Could not perform instrumentation! Unable to get debug info for function \"" << callsite->getCalledOperand()->getName() << "\""; } @@ -518,7 +499,7 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { void InstrumentPass::insertCBIfNeeded(FunctionCallee FC, std::vector params, Instruction* I) { if (!isRelevant(I) && (isa(I) || isa(I)) && ClInstrumentType.starts_with("filtered")) return; - params.insert(params.begin(), ConstantInt::getBool(Bool_Type, isRelevant(I))); + params.insert(params.begin(), Basic_Types.getBool(isRelevant(I))); CallInst* callbackCI = CallInst::Create(FC, params); callbackCI->setDebugLoc(I->getDebugLoc()); if (isa(I)) ReplaceInstWithInst(I, callbackCI); diff --git a/Passes/Instrument.hpp b/Passes/Instrument.hpp index b52a8fa..f91b502 100644 --- a/Passes/Instrument.hpp +++ b/Passes/Instrument.hpp @@ -10,6 +10,7 @@ #include #include #include +#include "BasicTypes.hpp" #include "ContractManager.hpp" #include "ContractTree.hpp" #include "ErrorMessage.h" @@ -48,10 +49,7 @@ class InstrumentPass : public PassInfoMixin { std::vector mentioned_funcs; // Filled by callops (non-tag) in createOperation // Types - PointerType* Ptr_Type; - IntegerType* Bool_Type; - IntegerType* Int_Type; - Type* Void_Type; + BasicTypesAnalysis::BasicTypes Basic_Types; StructType* Formula_Type; StructType* DB_Type; StructType* Tag_Type; @@ -65,7 +63,6 @@ class InstrumentPass : public PassInfoMixin { StructType* Contract_Type; StructType* Ref_Type; StructType* ParamReq_Type; - Constant* Null_Const; // Helpers bool checkIsStrParam(Value const* I); From 7b969bfaeb325bf6bc4206487c7e0cae1575c707 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 20 Mar 2026 16:53:21 +0100 Subject: [PATCH 051/125] Fix compilation --- Passes/Instrument.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 90ee52f..9a70a08 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -104,7 +104,7 @@ PreservedAnalyses InstrumentPass::run(Module &M, // Package database GlobalVariable* GlobalDB = dyn_cast(M.getOrInsertGlobal("CONTR_DB", DB_Type)); - Constant* CDB = ConstantStruct::get(DB_Type, {ContractsVal, ConstantInt::get(Int_Type, num_contrs), TagVal, ReferencesVal, ConstantInt::get(Int_Type, num_refs)}); + Constant* CDB = ConstantStruct::get(DB_Type, {ContractsVal, Basic_Types.getInt(num_contrs), TagVal, ReferencesVal, Basic_Types.getInt(num_refs)}); GlobalDB->setInitializer(CDB); AttributeList fnAttr; From 91d585d38db893299b62a9f359114ddca6a256c5 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 23 Mar 2026 10:57:35 +0100 Subject: [PATCH 052/125] Add MD to BasicTypes --- Passes/BasicTypes.cpp | 15 +++++++++++++++ Passes/BasicTypes.hpp | 40 +++++++++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/Passes/BasicTypes.cpp b/Passes/BasicTypes.cpp index 74f427f..a7a00f7 100644 --- a/Passes/BasicTypes.cpp +++ b/Passes/BasicTypes.cpp @@ -1,5 +1,7 @@ #include "BasicTypes.hpp" +#include +#include #include #include @@ -8,15 +10,28 @@ using namespace llvm; BasicTypesAnalysis::BasicTypes BasicTypesAnalysis::run(Module &M, ModuleAnalysisManager &AM) { BasicTypes types; + DIBuilder DIB(M); + // Basic Types types.Ptr_Type = PointerType::get(M.getContext(), 0); + types.TypeToMD[types.Ptr_Type] = DIB.createBasicType("void*", 64, dwarf::DW_TAG_pointer_type); + types.Int_Type = IntegerType::get(M.getContext(), 32); + types.TypeToMD[types.Int_Type] = DIB.createBasicType("int32_t", 32, dwarf::DW_ATE_signed); + types.Int64_Type = IntegerType::get(M.getContext(), 64); + types.TypeToMD[types.Int64_Type] = DIB.createBasicType("int64_t", 64, dwarf::DW_ATE_signed); + types.Bool_Type = IntegerType::get(M.getContext(), 1); + types.TypeToMD[types.Bool_Type] = DIB.createBasicType("bool", 1, dwarf::DW_ATE_boolean); + types.Void_Type = Type::getVoidTy(M.getContext()); + types.TypeToMD[types.Void_Type] = nullptr; // Basic Constants types.Null_Const = ConstantPointerNull::getNullValue(types.Ptr_Type); + DIB.finalize(); + return types; } diff --git a/Passes/BasicTypes.hpp b/Passes/BasicTypes.hpp index c052c58..a0dd278 100644 --- a/Passes/BasicTypes.hpp +++ b/Passes/BasicTypes.hpp @@ -4,7 +4,10 @@ #include #include #include +#include #include +#include +#include namespace llvm { @@ -12,19 +15,30 @@ class BasicTypesAnalysis : public AnalysisInfoMixin { public: static inline llvm::AnalysisKey Key; //Result Type - struct Result { - PointerType* Ptr_Type; - IntegerType* Bool_Type; - Constant* getBool(bool x) { return ConstantInt::getBool(Bool_Type, x); } - IntegerType* Int_Type; - ConstantInt* getInt(int x) { return ConstantInt::get(Int_Type, x); } - IntegerType* Int64_Type; - ConstantInt* getInt64(int x) { return ConstantInt::get(Int64_Type, x); } - Type* Void_Type; - Constant* Null_Const; - bool invalidate(Module &, PreservedAnalyses const&, ModuleAnalysisManager::Invalidator const&) const { - return false; - } + class Result { + friend class BasicTypesAnalysis; + public: + PointerType* Ptr_Type; + IntegerType* Bool_Type; + Constant* getBool(bool x) { return ConstantInt::getBool(Bool_Type, x); } + IntegerType* Int_Type; + ConstantInt* getInt(int x) { return ConstantInt::get(Int_Type, x); } + IntegerType* Int64_Type; + ConstantInt* getInt64(int x) { return ConstantInt::get(Int64_Type, x); } + Type* Void_Type; + Constant* Null_Const; + bool invalidate(Module &, PreservedAnalyses const&, ModuleAnalysisManager::Invalidator const&) const { + return false; + } + Metadata* getMDForType(Type const* T) const { + if (TypeToMD.contains(T)) return TypeToMD.at(T); + errs() << "BasicTypes: Queried unknown type "; + T->print(errs()); + errs() << "!\n"; + return nullptr; + } + private: + std::map TypeToMD; } typedef BasicTypes; // Run Analysis BasicTypes run(Module &M, ModuleAnalysisManager &AM); From 8f0f840dc03c6392b1996bd7647405dcf82928ba Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 23 Mar 2026 12:50:46 +0100 Subject: [PATCH 053/125] Some oopsies --- Passes/Instrument.cpp | 2 +- Passes/Registrar.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 9a70a08..1c3b3aa 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -405,7 +405,7 @@ void InstrumentPass::instrumentRW(Module &M) { if (GlobalVariable const* GV = dyn_cast(GEPOp->getPointerOperand())) { SmallVector dbg_arr; GV->getDebugInfo(dbg_arr); - if (!isC && !dbg_arr.empty() && dbg_arr[0]->getVariable()->getType()->getTag() == (dwarf::Tag)DW_TAG_array_type) { + if (!isC && !dbg_arr.empty() && dbg_arr[0]->getVariable()->getType()->getTag() == dwarf::DW_TAG_array_type) { continue; } } diff --git a/Passes/Registrar.cpp b/Passes/Registrar.cpp index 66f1a2d..5ca39fc 100644 --- a/Passes/Registrar.cpp +++ b/Passes/Registrar.cpp @@ -48,6 +48,7 @@ namespace { }; void MAMHook(ModuleAnalysisManager &MAM) { + MAM.registerPass([&] { return BasicTypesAnalysis(); }); MAM.registerPass([&] { return ContractManagerAnalysis(); }); }; From 92dee50e737d778440e3662242193423fb959921 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 23 Mar 2026 13:20:29 +0100 Subject: [PATCH 054/125] Better static err reports for alloc --- Passes/ContractVerifierAlloc.cpp | 18 +++++++++++++----- Passes/ContractVerifierAlloc.hpp | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Passes/ContractVerifierAlloc.cpp b/Passes/ContractVerifierAlloc.cpp index 6b4c9bd..0bf2c24 100644 --- a/Passes/ContractVerifierAlloc.cpp +++ b/Passes/ContractVerifierAlloc.cpp @@ -3,6 +3,7 @@ #include "ContractPassUtility.hpp" #include "ContractTree.hpp" #include "ContractManager.hpp" +#include #include #include #include @@ -45,7 +46,7 @@ PreservedAnalyses ContractVerifierAllocPass::run(Module &M, const AllocOperation* AllocOp = dynamic_cast(Expr->OP.get()); C.DebugInfo->push_back("[ContractVerifierAlloc] Attempting to verify expression: " + Expr->ExprStr); std::string err; - AllocStatusVal val = checkAllocReq(AllocOp, M, C.F, err); + AllocStatusVal val = checkAllocReq(AllocOp, C, *Expr, M, C.F, err); if (!err.empty()) { errs() << err << "\n"; *Expr->Status = Fulfillment::BROKEN; @@ -58,10 +59,9 @@ PreservedAnalyses ContractVerifierAllocPass::run(Module &M, } struct IterTypeAlloc { - std::vector err; + std::vector err; std::vector dbg; int param; - ParamAccess acc; const Function* F; }; @@ -149,6 +149,11 @@ ContractVerifierAllocPass::AllocStatus ContractVerifierAllocPass::transferAllocS } // Any required parameter not used by any candidate cur.CurVal = AllocStatusVal::ERROR; + Data->err.push_back({ + .error_id = "Alloc", + .text = std::format("Value in argument index {} of {} in {} is not allocated!", Data->param, Data->F->getName().str(), ContractPassUtility::getInstrLocStr(CB)), + .references = {ContractPassUtility::getFileReference(CB)}, + }); return cur; } } @@ -161,7 +166,7 @@ std::pair ContractVerifierAllocPass return {intersect, intersect.CurVal > prev.CurVal}; } -ContractVerifierAllocPass::AllocStatusVal ContractVerifierAllocPass::checkAllocReq(const AllocOperation* AllocOp, Module const& M, const Function* F, std::string& err) { +ContractVerifierAllocPass::AllocStatusVal ContractVerifierAllocPass::checkAllocReq(const AllocOperation* AllocOp, ContractManagerAnalysis::LinearizedContract const& C, ContractExpression const& Expr, Module const& M, const Function* F, std::string& err) { const Function* mainF = M.getFunction("main"); if (!mainF) { err = "Cannot find main function, cannot construct path to check precall!"; @@ -170,11 +175,14 @@ ContractVerifierAllocPass::AllocStatusVal ContractVerifierAllocPass::checkAllocR const Instruction* Entry = &*mainF->getEntryBlock().getFirstNonPHIIt(); AllocStatus init; - IterTypeAlloc data = { {}, {}, AllocOp->contrP, AllocOp->contrParamAccess, F }; + IterTypeAlloc data = { {}, {}, AllocOp->contrP, F }; auto bound_transfer = std::bind(&ContractVerifierAllocPass::transferAllocStat, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); auto bound_merge = std::bind(&ContractVerifierAllocPass::mergeAllocStat, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); std::map AnalysisInfo = ContractPassUtility::GenericWorklist(Entry, bound_transfer, bound_merge, &data, init); + C.DebugInfo->insert(C.DebugInfo->end(), data.dbg.begin(), data.dbg.end()); + Expr.ErrorInfo->insert(Expr.ErrorInfo->end(), data.err.begin(), data.err.end()); + // Take max over all analysis info // Correct usage will not contain error AllocStatusVal res = AllocStatusVal::ALLOC; diff --git a/Passes/ContractVerifierAlloc.hpp b/Passes/ContractVerifierAlloc.hpp index 9ca5f27..eceecc6 100644 --- a/Passes/ContractVerifierAlloc.hpp +++ b/Passes/ContractVerifierAlloc.hpp @@ -89,7 +89,7 @@ class ContractVerifierAllocPass : public PassInfoMixin mergeAllocStat(AllocStatus prev, AllocStatus cur, const Instruction* I, void* data); - AllocStatusVal checkAllocReq(const AllocOperation* AllocOp, Module const& M, const Function* F, std::string& err); + AllocStatusVal checkAllocReq(const AllocOperation* AllocOp, ContractManagerAnalysis::LinearizedContract const& C, ContractExpression const& Expr, Module const& M, const Function* F, std::string& err); }; From fa81c9142c6d5f5945262f94ce1fd1b5e3c24d1f Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 23 Mar 2026 15:08:59 +0100 Subject: [PATCH 055/125] Add dynamic allocation analysis --- CMakeLists.txt | 8 ++ Dynamic/Analyses/AllocAnalysis.cpp | 47 +++++++ Dynamic/Analyses/AllocAnalysis.h | 34 +++++ Dynamic/CMakeLists.txt | 1 + Dynamic/Hooks.hpp | 15 ++- Include/DynamicAnalysis.h | 22 ++++ Intrinsics/CMakeLists.txt | 17 +++ Intrinsics/Intrinsics.c | 43 +++++++ Passes/ContractManager.cpp | 36 ++++-- Passes/ContractManager.hpp | 2 +- Passes/ContractVerifierAlloc.cpp | 5 +- Passes/Instrument.cpp | 136 +++++++++++++++++--- Passes/Instrument.hpp | 13 +- Passes/Intrinsics.cpp | 184 +++++++++++++++++++++++++++ Passes/Intrinsics.hpp | 26 ++++ Passes/Registrar.cpp | 6 + Scripts/clangContracts.cpp | 4 +- Scripts/gen_mpi_contr_h.py | 24 +++- Tests/CMakeLists.txt | 2 + Tests/c/Alloc-BufNotAllocated.c | 38 ++++++ Tests/c/Alloc-BufUseAfterFree.c | 39 ++++++ Tests/fort/Alloc-BufNotAllocated.F90 | 36 ++++++ Tests/fort/Alloc-BufUseAfterFree.F90 | 37 ++++++ 23 files changed, 730 insertions(+), 45 deletions(-) create mode 100644 Dynamic/Analyses/AllocAnalysis.cpp create mode 100644 Dynamic/Analyses/AllocAnalysis.h create mode 100644 Intrinsics/CMakeLists.txt create mode 100644 Intrinsics/Intrinsics.c create mode 100644 Passes/Intrinsics.cpp create mode 100644 Passes/Intrinsics.hpp create mode 100644 Tests/c/Alloc-BufNotAllocated.c create mode 100644 Tests/c/Alloc-BufUseAfterFree.c create mode 100644 Tests/fort/Alloc-BufNotAllocated.F90 create mode 100644 Tests/fort/Alloc-BufUseAfterFree.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 15cb543..12dc5f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,7 @@ add_llvm_pass_plugin(CoVerPlugin Passes/ContractVerifierParam.cpp Passes/ContractVerifierAlloc.cpp Passes/ContractPostProcess.cpp + Passes/Intrinsics.cpp Passes/Instrument.cpp Utils/ContractPassUtility.cpp Include/ContractPassUtility.hpp @@ -139,6 +140,12 @@ install( WORLD_READ ) +## +## CoVer Intrinsics definitions +## + +add_subdirectory(Intrinsics) + ## ## Dynamic project ## @@ -199,6 +206,7 @@ endif(MPI_C_FOUND AND Python_FOUND) set(CONTR_PLUGIN_PATH "${CMAKE_INSTALL_PREFIX}/lib/CoVerPlugin.so") set(COVER_DYNAMIC_ANALYSER_PATH ${CMAKE_INSTALL_PREFIX}/lib/libCoVerDynamicAnalyzer.a) +set(COVER_INTRINSICS_LIB_PATH ${CMAKE_INSTALL_PREFIX}/lib/libCoVerIntrinsics.a) set(DSA_PLUGIN_PATH "${CMAKE_INSTALL_PREFIX}/lib/DSA.so") set(CONTR_INCLUDE_PATH "${CMAKE_INSTALL_PREFIX}/include") diff --git a/Dynamic/Analyses/AllocAnalysis.cpp b/Dynamic/Analyses/AllocAnalysis.cpp new file mode 100644 index 0000000..bbec211 --- /dev/null +++ b/Dynamic/Analyses/AllocAnalysis.cpp @@ -0,0 +1,47 @@ +#include "AllocAnalysis.h" +#include "BaseAnalysis.h" +#include "DynamicAnalysis.h" +#include "../DynamicUtils.h" + +#include +#include +#include +#include + +Fulfillment AllocAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { + if (!isPre) { + if (mem_allocators.contains(func)) { + uintptr_t alloc = (uintptr_t)(mem_allocators[func]->rwOp->idx == 99 ? callsite.retval : callsite.params[mem_allocators[func]->rwOp->idx].value); + if (mem_allocators[func]->rwOp->accType == ParamAccess::DEREF) alloc = (uintptr_t)*(void**)alloc; + MathExpr_t const* cur = mem_allocators[func]->size; + size_t res = cur->isArgValue ? (size_t)callsite.params[cur->value].value : cur->value; + while (cur->other != nullptr) { + switch (cur->type) { + case UNARY_VALUE: + break; + case MULT: + res *= cur->other->isArgValue ? (size_t)callsite.params[cur->other->value].value : cur->other->value; + break; + } + cur = cur->other; + } + allocated[alloc] = res; + } + else if (mem_deallocators.contains(func)) { + if (mem_deallocators[func]->rwOp->idx == 99) allocated.erase((uintptr_t const)callsite.retval); + else allocated.erase((uintptr_t const)callsite.params[mem_deallocators[func]->rwOp->idx].value); + } + return Fulfillment::UNKNOWN; + } else { + if (func == func_supplier) { + uintptr_t ptr = (uintptr_t)callsite.params[idx].value; + for (std::pair alloc_ptr : allocated) { + if ((uintptr_t)alloc_ptr.first == ptr) return Fulfillment::UNKNOWN; + if (ptr >= alloc_ptr.first && ptr < alloc_ptr.first + alloc_ptr.second) return Fulfillment::UNKNOWN; + } + references.push_back(callsite.location); + return Fulfillment::VIOLATED; + } + return Fulfillment::UNKNOWN; + } +} diff --git a/Dynamic/Analyses/AllocAnalysis.h b/Dynamic/Analyses/AllocAnalysis.h new file mode 100644 index 0000000..925c187 --- /dev/null +++ b/Dynamic/Analyses/AllocAnalysis.h @@ -0,0 +1,34 @@ +#pragma once + +#include "BaseAnalysis.h" +#include "DynamicAnalysis.h" +#include +#include +#include + +struct AllocAnalysis : BaseAnalysis { + public: + AllocAnalysis(void const* _func_supplier, AllocOp_t* allocop) : idx(allocop->idx), acc(allocop->accType), func_supplier(_func_supplier) { + for (int i = 0; i < allocop->num_allocators; i++) { + mem_allocators[allocop->allocators[i].func] = &allocop->allocators[i]; + } + for (int i = 0; i < allocop->num_deallocators; i++) { + mem_deallocators[allocop->deallocators[i].func] = &allocop->deallocators[i]; + } + } + + ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } + ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; // Evidently none of the callsites were erroneous + + constexpr CallBacks requiredCallbacksImpl() const { return {true, true, false, false}; } + + private: + // Configuration + void const* func_supplier; + int const idx; + ParamAccess const acc; + std::unordered_map allocated; + std::unordered_map mem_allocators; + std::unordered_map mem_deallocators; +}; diff --git a/Dynamic/CMakeLists.txt b/Dynamic/CMakeLists.txt index f4b2fda..afbc759 100644 --- a/Dynamic/CMakeLists.txt +++ b/Dynamic/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(CoVerDynamicAnalyzer STATIC Analyses/ParamAnalysis.cpp + Analyses/AllocAnalysis.cpp Analyses/PostCallAnalysis.cpp Analyses/PreCallAnalysis.cpp Analyses/ReleaseAnalysis.cpp diff --git a/Dynamic/Hooks.hpp b/Dynamic/Hooks.hpp index 52cad4b..390e650 100644 --- a/Dynamic/Hooks.hpp +++ b/Dynamic/Hooks.hpp @@ -12,6 +12,7 @@ #include "Analyses/BaseAnalysis.h" #include "DynamicUtils.h" +#include "Analyses/AllocAnalysis.h" #include "Analyses/ParamAnalysis.h" #include "Analyses/PreCallAnalysis.h" #include "Analyses/PostCallAnalysis.h" @@ -27,7 +28,7 @@ namespace { std::unordered_map> contrs; - using AnalysisVariant = std::variant; + using AnalysisVariant = std::variant; struct AnalysisPair { ContractFormula_t* formula; @@ -83,7 +84,7 @@ namespace { void validateState(ContractFormula_t* form) { if (formula_parents[form] && contract_status.contains(formula_parents[form])) { - if (contract_status[formula_parents[form]] == Fulfillment::VIOLATED && !formula_parents[formula_parents[form]]) { + if (contract_status[form] == Fulfillment::VIOLATED && contract_status[formula_parents[form]] == Fulfillment::VIOLATED && !formula_parents[formula_parents[form]]) { // Another child of top-level formula violated. Two possible errors, though maybe just a symptom of the first one. DynamicUtils::out() << "Note: Possible secondary issue detected! This may be a true FP or a side effect of the previous report.\n"; formatError(recurseCreateErrorMsg(form)); @@ -157,6 +158,12 @@ namespace { if (isPre) addAnalysis(form, func_supplier, (ParamOp_t*)form->data); else DynamicUtils::createMessage("Did not expect paramop in postcond!"); break; + case UNARY_ALLOC: + if (isPre) addAnalysis(form, func_supplier, (AllocOp_t*)form->data); + break; // Normal to have alloc in post, but unused. + case UNARY_FREE: + if (isPre) DynamicUtils::createMessage("Did not expect freeop in precondition!"); + break; // Normal to find free in post, but unused. default: DynamicUtils::createMessage("Unknown top-level operation!"); break; @@ -183,6 +190,10 @@ namespace { msg.msg.push_back("Invalid param!"); break; } + case UNARY_ALLOC: { + msg.msg.push_back("Buffer not allocated or use-after-free!"); + break; + } case UNARY_RELEASE: { msg.msg.push_back("Found forbidden operation!"); break; diff --git a/Include/DynamicAnalysis.h b/Include/DynamicAnalysis.h index cb3de61..9febcf0 100644 --- a/Include/DynamicAnalysis.h +++ b/Include/DynamicAnalysis.h @@ -31,6 +31,15 @@ struct CallParam_t { }; enum Comparator : int32_t { NEQ, GT, GTEQ, LT, LTEQ, EXEQ }; +// Number must match those defined in ContractTree.hpp! +enum MathType : int32_t { UNARY_VALUE, MULT }; +struct MathExpr_t { + int32_t const value; + bool const isArgValue; + MathType const type; + MathExpr_t const* other = nullptr; +}; + struct RWOp_t { int32_t idx; ParamAccess accType; @@ -63,6 +72,19 @@ struct ParamOp_t { const ParamReq_t* requirements; const int32_t num_reqs; }; +struct MemOpFunc_t { + const void* func; + const RWOp_t* rwOp; + const MathExpr_t* size = 0; +}; +struct AllocOp_t { + const int32_t idx; + const ParamAccess accType; + MemOpFunc_t* allocators; + int32_t num_allocators; + MemOpFunc_t* deallocators; + int32_t num_deallocators; +}; // Number must match those defined in enums in ContractTree.hpp (operation + connective)! enum ContractConnective : int32_t { diff --git a/Intrinsics/CMakeLists.txt b/Intrinsics/CMakeLists.txt new file mode 100644 index 0000000..3381b3b --- /dev/null +++ b/Intrinsics/CMakeLists.txt @@ -0,0 +1,17 @@ +add_library(CoVerIntrinsics STATIC + Intrinsics.c +) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set_property(TARGET CoVerIntrinsics PROPERTY UNITY_BUILD OFF) +else() + set_property(TARGET CoVerIntrinsics PROPERTY UNITY_BUILD ON) +endif() + +set_property(TARGET CoVerIntrinsics PROPERTY C_VISIBILITY_PRESET hidden) +set_property(TARGET CoVerIntrinsics PROPERTY VISIBILITY_INLINES_HIDDEN ON) + +target_compile_options(CoVerIntrinsics PUBLIC -fno-rtti -fno-exceptions -fomit-frame-pointer -fno-stack-protector) + +set_property(TARGET CoVerIntrinsics PROPERTY POSITION_INDEPENDENT_CODE ON) +install(TARGETS CoVerIntrinsics) diff --git a/Intrinsics/Intrinsics.c b/Intrinsics/Intrinsics.c new file mode 100644 index 0000000..a598cc9 --- /dev/null +++ b/Intrinsics/Intrinsics.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +// Stack allocation intrinsics (i.e. for IR allocas) +void __attribute__((visibility("default"))) CoVer_AllocStack(void const* ptr) {}; +void __attribute__((visibility("default"))) CoVer_FreeStack(void const* ptr) {}; + +// Global "allocation" intrinsic (i.e. for Global variables or pseudoglobals in fortran) +void __attribute__((visibility("default"))) CoVer_RegisterGlobal(void const* ptr, int64_t size) {}; + +// Fortran intrinsics - allocate(), wrapped by CoVer_FPointerAllocate +// Example IR for allocate(buf(2,2)): +// call void @_FortranAPointerSetBounds(ptr @_QFEbuf, i32 0, i64 2, i64 2), !dbg !82 +// call void @_FortranAPointerSetBounds(ptr @_QFEbuf, i32 1, i64 2, i64 2), !dbg !82 +// %23 = call i32 @_FortranAPointerAllocate(ptr @_QFEbuf, i1 false, ptr null, ptr @_QQclX3a61d3c3006198469069f977f45ff921, i32 15), !dbg !82 +void __attribute__((weak)) _FortranAPointerSetBounds(void*, int32_t, int64_t, int64_t); +int32_t __attribute__((weak)) _FortranAPointerAllocate(void*, bool, void*, void*, int32_t); +int32_t __attribute__((weak)) _FortranAPointerDeallocate(void*, bool, void*, void*, int32_t); + +int32_t __attribute__((visibility("default"))) CoVer_FPointerAllocate(void* ptr, int64_t size, int num_dims, ...) { + va_list list; + va_start(list, num_dims); + for (int i = 0; i < num_dims; i++) { + int dim_idx = va_arg(list, int32_t); + int64_t start_idx = va_arg(list, int64_t); + int64_t end_idx = va_arg(list, int64_t); + _FortranAPointerSetBounds(ptr, dim_idx, start_idx, end_idx); + } + bool palloc_arg1 = va_arg(list, int); + void* palloc_arg2 = va_arg(list, void*); + void* fileArg = va_arg(list, void*); + int32_t palloc_arg4 = va_arg(list, int32_t); + return _FortranAPointerAllocate(ptr, palloc_arg1, palloc_arg2, fileArg, palloc_arg4); +} + +// Fortran intrinsics - deallocate(), wrapped by CoVer_FPointerDeallocate +// Example IR for deallocate(buf): +// %54 = call i32 @_FortranAPointerDeallocate(ptr @_QFEbuf, i1 false, ptr null, ptr @_QQclX67d9a87547f1793fa4a21c08cb286920, i32 18), !dbg !92 +int32_t __attribute__((visibility("default"))) CoVer_FPointerDeallocate(void* ptr, bool arg1, void* arg2, void* fileArg, int32_t arg4) { + return _FortranAPointerDeallocate(ptr, arg1, arg2, fileArg, arg4); +} diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index ae60a47..ed8bc33 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -11,8 +11,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -67,9 +69,8 @@ ContractManagerAnalysis::ContractDatabase ContractManagerAnalysis::run(Module &M } } - std::stringstream s; - s << "CoVer: Parsed contracts after " << std::fixed << std::chrono::duration(std::chrono::system_clock::now() - curDatabase.start_time).count() << "s\n"; - errs() << s.str(); + std::string timestr = std::format("CoVer: Parsed contracts after {}s\n", std::chrono::duration(std::chrono::system_clock::now() - curDatabase.start_time).count()); + errs() << timestr; ContractPassUtility::Initialize(M); @@ -112,20 +113,31 @@ void ContractManagerAnalysis::extractFromFunction(Module& M) { // but for fortran its already implicit in declare_contract, making it superfluous std::string CallStr = ((ConstantDataArray*)((GlobalVariable*)CB->getArgOperand(1))->getInitializer())->getAsString().str(); // Call is from memmove -> insertvalue -> extractvalue -> funccall. on -O0, and memcpy -> funccall on -O1 and above - CallBase* ContrCall = (CallBase*)(isa(*CB->getArgOperand(0)->user_begin()) ? *CB->getArgOperand(0)->user_begin() : *CB->getArgOperand(0)->user_begin()->user_begin()->user_begin()->user_begin()); + Instruction const* cur = CB->getNextNode(); + while (cur && !isa(cur)) cur = cur->getNextNode(); + if (!cur) { + errs() << "CRITICAL: Unable to determine a contract definition!\n"; + continue; + } + CallBase const* ContrCall = dyn_cast(cur); if (ContrCall->getCalledOperand()->getName() == "declare_contract_") { const Function* ContrSup = (Function*)ContrCall->getArgOperand(0); - if (ContrSup->hasOneUser()) continue; // Only used here where the contract is defined. No need to verify. - bool has_callsite = false; - for (const User* U : ContrSup->users() ) { - if (const CallBase* CB = dyn_cast(U)) { - if (CB->getCalledOperand() == ContrSup) { - has_callsite = true; - break; + if (!ContrSup->getName().starts_with("CoVer_")) { + // Check if this function is actually used in the code apart from the contract definition. + // If not, no need to analyse this contract and can safely skip it. + // Exception: CoVer intrinsics (CoVer_*), such as stack var tracking pseudofunc. + if (ContrSup->hasOneUser()) continue; + bool has_callsite = false; + for (const User* U : ContrSup->users() ) { + if (const CallBase* CB = dyn_cast(U)) { + if (CB->getCalledOperand() == ContrSup) { + has_callsite = true; + break; + } } } + if (!has_callsite) continue; } - if (!has_callsite) continue; addContract("CONTRACT { " + CallStr + " }", (Function*)(ContrCall->getArgOperand(0))); } else if (ContrCall->getCalledOperand()->getName() == "declare_value_") { #warning really super duper should find out a less hacky way diff --git a/Passes/ContractManager.hpp b/Passes/ContractManager.hpp index 85d07b0..4b9b1df 100644 --- a/Passes/ContractManager.hpp +++ b/Passes/ContractManager.hpp @@ -50,7 +50,7 @@ class ContractManagerAnalysis : public AnalysisInfoMixin(I)) { - if (AllocFuncs.contains(CB->getCalledOperand())) { + // Check for alloc/freefunc. Stack alloc better handled by isTriviallyAlloc. + if (AllocFuncs.contains(CB->getCalledOperand()) && CB->getCalledOperand()->getName() != "CoVer_AllocStack" && CB->getCalledOperand()->getName() != "CoVer_RegisterGlobal") { for (const AllocOperation* alloc : AllocFuncs[CB->getCalledOperand()]) { if (alloc->contrP == 99) cur.addAllocatedValue(CB, alloc->contrParamAccess); else cur.addAllocatedValue(CB->getArgOperand(alloc->contrP), alloc->contrParamAccess); } // Dont return here! Maybe it also is contr sup } - if (FreeFuncs.contains(CB->getCalledFunction())) { + if (FreeFuncs.contains(CB->getCalledFunction()) && CB->getCalledOperand()->getName() != "CoVer_FreeStack") { for (const FreeOperation* freeOp : FreeFuncs[CB->getCalledFunction()]) { #warning TODO perform free, remove stuff from candidate tree cur.freeValue(CB->getArgOperand(freeOp->contrP)); diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 1c3b3aa..625820f 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -29,9 +30,10 @@ #include #include #include +#include #include #include -#include +#include #include #include #include @@ -129,6 +131,23 @@ PreservedAnalyses InstrumentPass::run(Module &M, Function* callbackW = dyn_cast(callbackWCallee.getCallee()); callbackW->setLinkage(GlobalValue::ExternalWeakLinkage); + // Finally the init routine + FunctionType* InitCBType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}, false); + initFuncCallee = M.getOrInsertFunction("PPDCV_Initialize", InitCBType, fnAttr); + Function* initFunc = dyn_cast(initFuncCallee.getCallee()); + initFunc->setLinkage(GlobalValue::ExternalWeakLinkage); + + // Create initialization routine for tool + Value* Vargc = mainF->getArg(0); + Value* Vargv = mainF->getArg(1); + AllocaInst* argcptr = new AllocaInst(Basic_Types.Int_Type, 0, "argc_ptr", mainF->getEntryBlock().getFirstNonPHIOrDbg()); + AllocaInst* argvptr = new AllocaInst(Basic_Types.Ptr_Type, 0, "argv_ptr", argcptr->getIterator()); + CallInst* initFuncCI = CallInst::Create(initFuncCallee, {argcptr, argvptr, GlobalDB}); + initFuncCI->insertAfter(argcptr->getIterator()); + instrument_ignore.insert({argcptr, argvptr}); + instrument_ignore.insert(new StoreInst(Vargc, argcptr, initFuncCI->getIterator())); + instrument_ignore.insert(new StoreInst(Vargv, argvptr, initFuncCI->getIterator())); + // Create callbacks if (ClInstrumentType != "funconly") instrumentRW(M); @@ -316,10 +335,60 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr allocators; + static std::vector deallocators; + static Constant* allocs_C = Basic_Types.Null_Const; + static Constant* deallocs_C = Basic_Types.Null_Const; + std::shared_ptr allocOp = std::static_pointer_cast(op); + if (allocators.empty() && deallocators.empty()) { + for (ContractManagerAnalysis::LinearizedContract const& C : DB->LinearizedContracts) { + for (const std::shared_ptr Expr : C.Post) { + switch (Expr->OP->type()) { + case FormulaType::ALLOC: + case FormulaType::FREE: { + std::shared_ptr rwOp = std::make_shared(*std::static_pointer_cast(Expr->OP)); + Constant* rwOp_C = createOperationGlobal(M, rwOp); + Constant* memop_C = ConstantStruct::get(MemOpFunc_Type, {C.F, rwOp_C, Expr->OP->type() == FormulaType::ALLOC ? createMathExprGlobal(M, std::static_pointer_cast(Expr->OP)->size) : Basic_Types.Null_Const}); + if (Expr->OP->type() == FormulaType::ALLOC) allocators.push_back(memop_C); + else if (Expr->OP->type() == FormulaType::FREE) deallocators.push_back(memop_C); + else llvm_unreachable("Unexpected type when constructing alloc/free inst!"); + break; + } + default: continue; + } + } + } + static ArrayType* allocators_Type = ArrayType::get(MemOpFunc_Type, allocators.size()); + allocs_C = ConstantArray::get(allocators_Type, allocators); + allocs_C = createConstantGlobal(M, allocs_C, "CONTR_ALLOCATOR_LIST"); + static ArrayType* deallocators_Type = ArrayType::get(MemOpFunc_Type, deallocators.size()); + deallocs_C = ConstantArray::get(deallocators_Type, deallocators); + deallocs_C = createConstantGlobal(M, deallocs_C, "CONTR_DEALLOCATOR_LIST"); + } + data = ConstantStruct::get(AllocOp_Type, {Basic_Types.getInt(allocOp->contrP), Basic_Types.getInt((int32_t)allocOp->contrParamAccess), + allocs_C, Basic_Types.getInt(allocators.size()), + deallocs_C, Basic_Types.getInt(deallocators.size())}); + name = "CONTR_ALLOCOP"; + break; + } + case FormulaType::FREE: break; } return createConstantGlobalUnique(M, data, name); } +Constant* InstrumentPass::createMathExprGlobal(Module& M, std::shared_ptr expr) { + Constant* val = Basic_Types.getInt(expr->value); + Constant* isArg = Basic_Types.getBool(expr->isArgValue); + Constant* type = Basic_Types.getInt((int32_t)expr->type); + Constant* other = Basic_Types.Null_Const; + if (expr->type != MathType::UNARY_VALUE) { + other = createMathExprGlobal(M, expr->other); + } + Constant* result = ConstantStruct::get(MathExpr_Type, {val, isArg, type, other}); + return createConstantGlobalUnique(M, result, "CONT_MATHEXPR"); +} + GlobalVariable* InstrumentPass::createConstantGlobalUnique(Module& M, Constant* C, std::string name) { static uint64_t globals_counter = 0; // For name uniqueness return createConstantGlobal(M, C, name + "_" + std::to_string(globals_counter++)); @@ -352,6 +421,8 @@ void InstrumentPass::createTypes(Module& M) { ParamOp_Type = StructType::create(M.getContext(), "ParamOp_t"); ParamOp_Type->setBody({Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // idx, list of reqs, num reqs + AllocOp_Type = StructType::create(M.getContext(), "AllocOp_t"); + AllocOp_Type->setBody({Basic_Types.Int_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // idx, accType, list of allocators, num allocs, list of deallocs, num deallocs // Composite Types Tag_Type = StructType::create(M.getContext(), "Tag_t"); @@ -366,12 +437,18 @@ void InstrumentPass::createTypes(Module& M) { Tags_Type = StructType::create(M.getContext(), "TagsMap_t"); Tags_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // Funcptr list, Tag + param struct list, num elems + MathExpr_Type = StructType::create(M.getContext(), "MathExpr_t"); + MathExpr_Type->setBody({Basic_Types.Int_Type, Basic_Types.Bool_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type}); // int val, bool isarg, math type, other math + Ref_Type = StructType::create(M.getContext(), "Reference_t"); Ref_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}); // char* file ref, char* type ParamReq_Type = StructType::create(M.getContext(), "ParamReq_t"); ParamReq_Type->setBody({Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Bool_Type}); // Comparator, Value + MemOpFunc_Type = StructType::create(M.getContext(), "MemOpFunc_t"); + MemOpFunc_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}); // Func, rwOp, size mathexpr + DB_Type = StructType::create(M.getContext(), "ContractDB_t"); DB_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Int_Type, Tags_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // contract list, num elems, tag container, reference list, num refs } @@ -438,7 +515,7 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { } } for (CallBase* callsite : callsites) { - int skipnum = 0; + int stringnum = 0; std::vector params; params.push_back(callsite->getCalledOperand()); // First param is funcptr @@ -453,7 +530,6 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { for (Use const& U : callsite->args()) { Value* actual_param = U; int const cur_argno = callsite->getArgOperandNo(&U); - if (cur_argno >= callsite->arg_size() - skipnum) break; // Store size of data type if (isC) { @@ -470,24 +546,46 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { } else { if (Function const* F = dyn_cast(callsite->getCalledOperand())) { DISubprogram const* Dbg = F->getSubprogram(); - if (checkIsStrParam(U)) skipnum++; - if (Dbg->getType()->getTypeArray()->getNumOperands() <= cur_argno + 1) { - errs() << "Warning: During instrumentation, likely string param missed during detection. Normal if optimizations enabled.\n"; - errs() << "If unsure, check if function " << F->getName() << " has more than " << skipnum << " string arguments.\n"; - break; - } - // All parameters are sent as pointers. Need to check exact size using dbg info - DIType const* param_type = Dbg->getType()->getTypeArray()[cur_argno + 1]; // Offset by one, first is ret val - int size_act = param_type->getSizeInBits() == 0 || isa(actual_param) ? 64 : param_type->getSizeInBits(); - int size_call = callsite->getDataLayout().getTypeStoreSizeInBits(U->getType()); - // On Fortran, deref if param is an allocate/ptr buffer. - // Indicate with magic bit - if (param_type->getTag() == (dwarf::Tag)DW_TAG_array_type) { - size_call = size_call | 1 << 8; + int size_call = 0; + int size_act = 0; + // Vararg intrinsics have to be handled specially due to missing debug info on vararg + if (F->getName() == "CoVer_FPointerAllocate") { + // Prefix - 0, 1 ptr and size, 2 num_dims + switch (cur_argno) { + case 0: + case 1: size_act = size_call = 64; break; + case 2: size_act = size_call = 32; + } + if (!size_act) { + // End - _FortranAPointerAllocate params + if (cur_argno == callsite->arg_size() - 4) size_act = size_call = 32; + if (cur_argno == callsite->arg_size() - 3) size_act = size_call = 64; + if (cur_argno == callsite->arg_size() - 2) size_act = size_call = 64; + if (cur_argno == callsite->arg_size() - 1) size_act = size_call = 32; + } + if (!size_act) { + // Middle - Descriptors for dims + int tmp = cur_argno - 3; + if (tmp % 3 == 0) size_act = size_call = 32; + else size_act = size_call = 64; + } + } else { + if (cur_argno >= callsite->arg_size() - stringnum) { + size_act = size_call = callsite->getParent()->getDataLayout().getTypeAllocSize(actual_param->getType()); + } else { + if (checkIsStrParam(U)) stringnum++; + // All parameters are sent as pointers. Need to check exact size using dbg info + DIType const* param_type = Dbg->getType()->getTypeArray()[cur_argno + 1]; // Offset by one, first is ret val + size_act = param_type->getSizeInBits() == 0 || isa(actual_param) ? 64 : param_type->getSizeInBits(); + size_call = callsite->getDataLayout().getTypeStoreSizeInBits(U->getType()); + // On Fortran, deref if param is an allocate/ptr buffer. + // Indicate with magic bit + if (param_type->getTag() == dwarf::DW_TAG_array_type) { + size_call = size_call | 1 << 8; + } + } } params.push_back(Basic_Types.getInt((size_call << 8) | (size_act & 0xFF))); - } else { - errs() << "ERROR: Could not perform instrumentation! Unable to get debug info for function \"" << callsite->getCalledOperand()->getName() << "\""; } } params.push_back(actual_param); diff --git a/Passes/Instrument.hpp b/Passes/Instrument.hpp index f91b502..d4543a6 100644 --- a/Passes/Instrument.hpp +++ b/Passes/Instrument.hpp @@ -30,6 +30,7 @@ class InstrumentPass : public PassInfoMixin { Constant* createFormulaGlobal(Module& M, std::shared_ptr form); Constant* createOperationGlobal(Module& M, std::shared_ptr op); std::pair createParamList(Module& M, std::vector p); + Constant* createMathExprGlobal(Module& M, std::shared_ptr expr); // Auxiliary GlobalVariable* createConstantGlobalUnique(Module& M, Constant* C, std::string name); @@ -37,14 +38,15 @@ class InstrumentPass : public PassInfoMixin { void createTypes(Module& M); // Instrumentation - void instrumentFunctions(Module &M); + FunctionCallee initFuncCallee; + FunctionCallee callbackFuncCallee; + FunctionCallee callbackRCallee; + FunctionCallee callbackWCallee; void instrumentRW(Module &M); + void instrumentFunctions(Module &M); void insertFunctionInstrCallback(Function* CB); void insertCBIfNeeded(FunctionCallee FC, std::vector params, Instruction* I); bool isRelevant(Instruction const* I) const; - FunctionCallee callbackFuncCallee; - FunctionCallee callbackRCallee; - FunctionCallee callbackWCallee; std::set already_instrumented; std::vector mentioned_funcs; // Filled by callops (non-tag) in createOperation @@ -54,12 +56,15 @@ class InstrumentPass : public PassInfoMixin { StructType* DB_Type; StructType* Tag_Type; StructType* Tags_Type; + StructType* MathExpr_Type; StructType* Param_Type; + StructType* MemOpFunc_Type; StructType* CallOp_Type; StructType* CallTagOp_Type; StructType* ReleaseOp_Type; StructType* RWOp_Type; StructType* ParamOp_Type; + StructType* AllocOp_Type; StructType* Contract_Type; StructType* Ref_Type; StructType* ParamReq_Type; diff --git a/Passes/Intrinsics.cpp b/Passes/Intrinsics.cpp new file mode 100644 index 0000000..184aa09 --- /dev/null +++ b/Passes/Intrinsics.cpp @@ -0,0 +1,184 @@ +#include "Intrinsics.hpp" +#include "BasicTypes.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace llvm; + +PreservedAnalyses IntrinsicsPass::run(Module &M, ModuleAnalysisManager &AM) { + Basic_Types = AM.getResult(M); + createCallees(M); + instrumentIntrinsics(M); + return PreservedAnalyses::all(); +} + +void IntrinsicsPass::createCallees(Module& M) { + // Get callee for alloca instr + FunctionType* FunctionAllocStackType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Ptr_Type}, false); + allocStackCallee = calleeHelper(M, "CoVer_AllocStack", FunctionAllocStackType); + + FunctionType* FunctionFreeStackType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Ptr_Type}, false); + freeStackCallee = calleeHelper(M, "CoVer_FreeStack", FunctionFreeStackType); + + // Get callee for global vals + FunctionType* FunctionGlobalRegType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Ptr_Type, Basic_Types.Int64_Type}, false); + globalRegCallee = calleeHelper(M, "CoVer_RegisterGlobal", FunctionGlobalRegType); + + // Get callee for fort intrinsics + FunctionType* FunctionFAllocIntrinsicType = FunctionType::get(Basic_Types.Int_Type, {Basic_Types.Ptr_Type, Basic_Types.Int64_Type, Basic_Types.Int_Type}, true); + fallocPointerCallee = calleeHelper(M, "CoVer_FPointerAllocate", FunctionFAllocIntrinsicType); + + FunctionType* FunctionFDeallocIntrinsicType = FunctionType::get(Basic_Types.Int_Type, {Basic_Types.Ptr_Type, Basic_Types.Bool_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}, false); + fdeallocPointerCallee = calleeHelper(M, "CoVer_FPointerDeallocate", FunctionFDeallocIntrinsicType); +} + +void IntrinsicsPass::instrumentIntrinsics(Module& M) { + // Instrument all allocas + for (Function& F : M) { + std::vector stack_vars; + for (BasicBlock& BB : F) { + for (Instruction& I : BB) { + if (AllocaInst* AI = dyn_cast(&I)) { + stack_vars.push_back(AI); + CallInst* intrinsicCI = CallInst::Create(allocStackCallee, {AI}); + intrinsicCI->setDebugLoc(I.getDebugLoc()); + intrinsicCI->insertAfter(AI); + } + } + } + EscapeEnumerator EE(F, "", false); + while (IRBuilder<>* IRB = EE.Next()) { + for (AllocaInst* AI : stack_vars) { + IRB->CreateCall(freeStackCallee, {AI}); + } + } + } + + // Instrument global vars + if (M.getFunction("main")) { + auto Entry = M.getFunction("main")->getEntryBlock().getFirstNonPHIOrDbgOrAlloca(); + for (GlobalVariable& GV : M.globals()) { + if (!GV.hasInitializer()) continue; + if (GV.hasPrivateLinkage() || GV.hasComdat()) continue; + if (GV.getName().starts_with("llvm.") || GV.getName().starts_with("ContractValueInfo")) continue; + size_t gv_size = GV.getParent()->getDataLayout().getTypeAllocSize(GV.getInitializer()->getType()); + CallInst::Create(globalRegCallee, {&GV, Basic_Types.getInt64(gv_size)}, "", Entry); + } + } + + // Instrument Fortran allocate() + Function* fortAllocate = M.getFunction("_FortranAPointerAllocate"); + if (fortAllocate) { + for (User* U : fortAllocate->users()) { + if (CallBase* CB = dyn_cast(U)) { + // Need to figure out the size. + struct DimInfo { + std::vector params; + Value* size; + }; + std::vector dim_params; + CallBase* cur = dyn_cast(CB->getPrevNode()); + while (cur) { + if (cur->getCalledOperand() != M.getFunction("_FortranAPointerSetBounds")) break; + Value* lower = cur->getArgOperand(2); + Value* upper = cur->getArgOperand(3); + Value* one = Basic_Types.getInt64(1); + Instruction* diff = BinaryOperator::CreateSub(upper, lower, "", CB->getIterator()); + Instruction* res = BinaryOperator::CreateAdd(diff, one, "", CB->getIterator()); + std::vector args; + // Skip 0, which is just the pointer + args.push_back(cur->getArgOperand(1)); + args.push_back(cur->getArgOperand(2)); + args.push_back(cur->getArgOperand(3)); + dim_params.push_back({args, res}); + CallBase* prev = cur; + cur = cur->getPrevNode() ? dyn_cast(cur->getPrevNode()) : nullptr; + prev->eraseFromParent(); + } + Value* total_elems = Basic_Types.getInt64(1); + for (DimInfo const& info : dim_params) { + total_elems = BinaryOperator::CreateMul(total_elems, info.size, "", CB->getIterator()); + } + // Finally, multiply by base type size + // First, get base type size from global descriptor + GetElementPtrInst* GEP = GetElementPtrInst::Create(CB->getArgOperand(0)->getType(), CB->getArgOperand(0), {Basic_Types.getInt(1)}); + GEP->insertBefore(CB->getIterator()); + LoadInst* BaseSize = new LoadInst(Basic_Types.Int64_Type, GEP, "", false, CB->getIterator()); + total_elems = BinaryOperator::CreateMul(total_elems, BaseSize, "", CB->getIterator()); + std::vector intrinsicparams = {CB->getArgOperand(0), total_elems, Basic_Types.getInt(dim_params.size())}; + std::ranges::reverse_view rv{dim_params}; // Reverse to set bounds in correct order + for (DimInfo const& info : rv) { + for (Value* arg : info.params) intrinsicparams.push_back(arg); + } + + intrinsicparams.push_back(CB->getArgOperand(1)); + intrinsicparams.push_back(CB->getArgOperand(2)); + intrinsicparams.push_back(CB->getArgOperand(3)); + intrinsicparams.push_back(CB->getArgOperand(4)); + CallInst* intrinsicCI = CallInst::Create(fallocPointerCallee, intrinsicparams); + ReplaceInstWithInst(CB, intrinsicCI); + } + } + } + + // Instrument Fortran deallocate() + Function* fortDeallocate = M.getFunction("_FortranAPointerDeallocate"); + if (fortDeallocate) { + for (User* U : fortDeallocate->users()) { + if (CallBase* CB = dyn_cast(U)) { + if (CB->getCalledOperand() != fortDeallocate) continue; + CB->setCalledFunction(fdeallocPointerCallee); + } + } + } +} + +FunctionCallee IntrinsicsPass::calleeHelper(Module& M, std::string name, FunctionType* fnType) { + AttributeList fnAttr; + fnAttr = fnAttr.addFnAttribute(M.getContext(), Attribute::NoUnwind); + fnAttr = fnAttr.addFnAttribute(M.getContext(), Attribute::WillReturn); + fnAttr = fnAttr.addFnAttribute(M.getContext(), Attribute::NoCallback); + + FunctionCallee res = M.getOrInsertFunction(name + "_TMP", fnType, fnAttr); + if (M.getFunction(name)) { + Function* old = M.getFunction(name); + old->replaceAllUsesWith(res.getCallee()); + old->eraseFromParent(); + } + res.getCallee()->setName(name); + + // Create debug info + DIBuilder DIB(M); + SmallVector MDTypes; + for (Type const* T : fnType->subtypes()) { + MDTypes.push_back(Basic_Types.getMDForType(T)); + } + DISubroutineType* SRT = DIB.createSubroutineType(DIB.getOrCreateTypeArray(MDTypes)); + DISubprogram* SP = DIB.createFunction(*M.debug_compile_units_begin(), name, name, M.debug_compile_units_begin()->getFile(), 0, SRT, 0); + + // Finalize + Function* F = dyn_cast(res.getCallee()); + F->setLinkage(GlobalValue::ExternalLinkage); + F->setSubprogram(SP); + + DIB.finalize(); + return res; +} diff --git a/Passes/Intrinsics.hpp b/Passes/Intrinsics.hpp new file mode 100644 index 0000000..54e9137 --- /dev/null +++ b/Passes/Intrinsics.hpp @@ -0,0 +1,26 @@ +#include "BasicTypes.hpp" +#include +#include +#include +#include + +namespace llvm { + +class IntrinsicsPass : public PassInfoMixin { + public: + PreservedAnalyses run(Module& M, ModuleAnalysisManager &AM); + private: + BasicTypesAnalysis::BasicTypes Basic_Types; + + FunctionCallee calleeHelper(Module& M, std::string name, FunctionType* type); + void createCallees(Module& M); + FunctionCallee allocStackCallee; + FunctionCallee freeStackCallee; + FunctionCallee globalRegCallee; + FunctionCallee fallocPointerCallee; + FunctionCallee fdeallocPointerCallee; + + void instrumentIntrinsics(Module& M); +}; + +} diff --git a/Passes/Registrar.cpp b/Passes/Registrar.cpp index 5ca39fc..9d94884 100644 --- a/Passes/Registrar.cpp +++ b/Passes/Registrar.cpp @@ -3,6 +3,7 @@ #include #include +#include "BasicTypes.hpp" #include "ContractManager.hpp" #include "ContractVerifierAlloc.hpp" #include "ContractVerifierPreCall.hpp" @@ -10,6 +11,7 @@ #include "ContractVerifierRelease.hpp" #include "ContractVerifierParam.hpp" #include "ContractPostProcess.hpp" +#include "Intrinsics.hpp" #include "Instrument.hpp" using namespace llvm; @@ -40,6 +42,10 @@ namespace { MPM.addPass(ContractPostProcessingPass()); return true; } + if (Name == "instrumentIntrinsics") { + MPM.addPass(IntrinsicsPass()); + return true; + } if (Name == "instrumentContracts") { MPM.addPass(InstrumentPass()); return true; diff --git a/Scripts/clangContracts.cpp b/Scripts/clangContracts.cpp index c06f12f..46b373e 100644 --- a/Scripts/clangContracts.cpp +++ b/Scripts/clangContracts.cpp @@ -285,7 +285,7 @@ int main(int argc, const char** argv) { execSafe("llvm-link" + bitcode_files + " -o " + tmpfile); // Call LLVM passes - std::string passlist = "function(sroa),contractVerifierPreCall,contractVerifierPostCall,contractVerifierRelease,contractVerifierParam,contractVerifierAlloc,contractPostProcess"; + std::string passlist = "function(sroa),instrumentIntrinsics,contractVerifierPreCall,contractVerifierPostCall,contractVerifierRelease,contractVerifierParam,contractVerifierAlloc,contractPostProcess"; if (!opt_level.empty()) { passlist += ",default<" + opt_level.substr(1) + ">"; // opt_level substr cuts "-" from "-O" } @@ -300,6 +300,6 @@ int main(int argc, const char** argv) { // Finalize executable execSafe("llc -filetype=obj --relocation-model=pic " + opt_level + " " + tmpfile + ".opt -o " + tmpfile + ".opt.o"); - execSafe(WrapTarget + " -fPIC -lm -ldl -lffi -lpthread -g -I\"@CONTR_INCLUDE_PATH@\"" + rem_args.first + " " + tmpfile + ".opt.o" + dest_arg); + execSafe(WrapTarget + " -fPIC -lm -ldl -lffi -lpthread -g -I\"@CONTR_INCLUDE_PATH@\"" + rem_args.first + " " + tmpfile + ".opt.o @COVER_INTRINSICS_LIB_PATH@ " + dest_arg); return 0; } diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 8e0d7ac..16c884e 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -470,6 +470,11 @@ def get_param_values(lang: str) -> str: {get_param_values("c")} +void __attribute__((weak)) CoVer_AllocStack(void* ptr) CONTRACT( POST {{ alloc!(0) }}) {{}}; +void __attribute__((weak)) CoVer_FreeStack(void* ptr) CONTRACT( POST {{ free!(0) }}) {{}}; + +void __attribute__((weak)) CoVer_RegisterGlobal(void* ptr, int64_t size) CONTRACT( POST {{ alloc!(0[ 1 _arg ]) }}) {{}}; + void* calloc(size_t num, size_t size) CONTRACT( POST {{ alloc!(99[ 0 _arg * 1 _arg]) }}); void* malloc(size_t size) CONTRACT( POST {{ alloc!(99[0 _arg]) }}); @@ -492,13 +497,26 @@ def get_param_values(lang: str) -> str: fortran_lang_intrinsics = """ ! Contracts for intrinsics - Mainly for allocation tracking interface - subroutine FortAlloc() bind(c, name="_FortranAPointerAllocate") + ! Fortran language intrinsics + subroutine FortAlloc() bind(c, name="CoVer_FPointerAllocate") end subroutine FortAlloc - subroutine FortFree() bind(c, name="_FortranAPointerDeallocate") + subroutine FortFree() bind(c, name="CoVer_FPointerDeallocate") end subroutine FortFree + + ! CoVer intrinsics + subroutine CoVer_AllocStack(ptr) bind(c, name="CoVer_AllocStack") + integer, pointer :: ptr + end subroutine CoVer_AllocStack + subroutine CoVer_FreeStack() bind(c, name="CoVer_FreeStack") + end subroutine CoVer_FreeStack + subroutine CoVer_RegisterGlobal() bind(c, name="CoVer_RegisterGlobal") + end subroutine CoVer_RegisterGlobal end interface - call Declare_Contract(FortAlloc, \"POST { alloc!(*0) }\") + call Declare_Contract(FortAlloc, \"POST { alloc!(*0[1 _arg]) }\") call Declare_Contract(FortFree, \"POST { free!(0) }\") + call Declare_Contract(CoVer_AllocStack, \"POST { alloc!(0) }\") + call Declare_Contract(CoVer_FreeStack, \"POST { free!(0) }\") + call Declare_Contract(CoVer_RegisterGlobal, \"POST { alloc!(0) }\") """ header_output_fort = boilerplate_header_fort + f" use mpi\n implicit none\n\n{fortran_lang_intrinsics}\n\n{get_param_values("fort")}\n\n" diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 4de1c19..bc49e0d 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -15,3 +15,5 @@ add_cover_test(PostCall-MissingFinalize) add_cover_test(PreCall-MissingInit) add_cover_test(Release-DataRace) add_cover_test(Param-InvalidComm) +add_cover_test(Alloc-BufNotAllocated) +add_cover_test(Alloc-BufUseAfterFree) diff --git a/Tests/c/Alloc-BufNotAllocated.c b/Tests/c/Alloc-BufNotAllocated.c new file mode 100644 index 0000000..4ec4fa5 --- /dev/null +++ b/Tests/c/Alloc-BufNotAllocated.c @@ -0,0 +1,38 @@ +// RUN: %clangContracts %run_common + +#include +#include +#include + +int main(int argc, char** argv) { + int rank; + int* buf; + MPI_Request req; + + MPI_Init(NULL, NULL); + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + if (rank == 0) { + MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &req); + printf("Buf: %d\n", buf[0]); + } else { + buf = (int*)malloc(sizeof(int)); + buf[0] = 42; + MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); + } + MPI_Wait(&req, MPI_STATUS_IGNORE); + + MPI_Finalize(); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK: Contract violation detected! +// CHECK: Buffer is not allocated +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK: Contract violation detected! +// CHECK: Buffer is not allocated +// Dont check for analysis finished, MPI implementation might crash. diff --git a/Tests/c/Alloc-BufUseAfterFree.c b/Tests/c/Alloc-BufUseAfterFree.c new file mode 100644 index 0000000..7417b6c --- /dev/null +++ b/Tests/c/Alloc-BufUseAfterFree.c @@ -0,0 +1,39 @@ +// RUN: %clangContracts %run_common + +#include +#include +#include + +int main(int argc, char** argv) { + int rank; + int* buf; + MPI_Request req; + + MPI_Init(NULL, NULL); + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + buf = (int*)malloc(sizeof(int)); + buf[0] = 42; + if (rank == 0) { + free(buf); + MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &req); + printf("Buf: %d\n", buf[0]); + } else { + MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); + } + MPI_Wait(&req, MPI_STATUS_IGNORE); + + MPI_Finalize(); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK: Contract violation detected! +// CHECK: Buffer is not allocated +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK: Contract violation detected! +// CHECK: Buffer is not allocated +// Dont check for analysis finished, MPI implementation might crash. diff --git a/Tests/fort/Alloc-BufNotAllocated.F90 b/Tests/fort/Alloc-BufNotAllocated.F90 new file mode 100644 index 0000000..7161a89 --- /dev/null +++ b/Tests/fort/Alloc-BufNotAllocated.F90 @@ -0,0 +1,36 @@ +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: rank + integer, pointer :: buf(:) + type(MPI_Request) :: req + + call MPI_Init() + + call MPI_Comm_rank(MPI_COMM_WORLD, rank) + + if (rank == 0) then + call MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, req) + print *, "Buf: ", buf(1) + else + allocate(buf(1)) + buf(1) = 42 + call MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, req) + end if + call MPI_Wait(req, MPI_STATUS_IGNORE) + + call MPI_Finalize() +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK: Contract violation detected! +! CHECK: Buffer is not allocated +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK: Contract violation detected! +! CHECK: Buffer is not allocated +! Dont check for analysis finished, MPI implementation might crash. diff --git a/Tests/fort/Alloc-BufUseAfterFree.F90 b/Tests/fort/Alloc-BufUseAfterFree.F90 new file mode 100644 index 0000000..04cfd6b --- /dev/null +++ b/Tests/fort/Alloc-BufUseAfterFree.F90 @@ -0,0 +1,37 @@ +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: rank + integer, pointer :: buf(:) + type(MPI_Request) :: req + + call MPI_Init() + + call MPI_Comm_rank(MPI_COMM_WORLD, rank) + + allocate(buf(1)) + buf(1) = 42 + if (rank == 0) then + deallocate(buf) + call MPI_Isend(buf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, req) + print *, "Buf: ", buf(1) + else + call MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, req) + end if + call MPI_Wait(req, MPI_STATUS_IGNORE) + + call MPI_Finalize() +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK: Contract violation detected! +! CHECK: Buffer is not allocated +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK: Contract violation detected! +! CHECK: Buffer is not allocated +! Dont check for analysis finished, MPI implementation might crash. From 4228b1485ad473fdb747eb23d26ce1ad6b057d53 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 23 Mar 2026 15:23:05 +0100 Subject: [PATCH 056/125] Add corr alloc tests for offset access to buffer --- Tests/CMakeLists.txt | 1 + Tests/c/Correct-P2POffset.c | 36 ++++++++++++++++++++++++++++++++ Tests/fort/Correct-P2POffset.F90 | 34 ++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 Tests/c/Correct-P2POffset.c create mode 100644 Tests/fort/Correct-P2POffset.F90 diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index bc49e0d..410267d 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -10,6 +10,7 @@ add_cover_test(Correct-Minimal) add_cover_test(Correct-P2P) add_cover_test(Correct-P2PStackBuf) add_cover_test(Correct-WinAlloc) +add_cover_test(Correct-P2POffset) add_cover_test(PostCall-MissingFinalize) add_cover_test(PreCall-MissingInit) diff --git a/Tests/c/Correct-P2POffset.c b/Tests/c/Correct-P2POffset.c new file mode 100644 index 0000000..c32069c --- /dev/null +++ b/Tests/c/Correct-P2POffset.c @@ -0,0 +1,36 @@ +// RUN: %clangContracts %run_common + +#include +#include +#include + +int main(int argc, char** argv) { + int rank; + int* buf; + MPI_Request req; + + MPI_Init(NULL, NULL); + + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + buf = (int*)malloc(sizeof(int)*3); + buf[2] = 42; + if (rank == 0) { + MPI_Isend(&buf[1], 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &req); + printf("Buf: %d\n", buf[2]); + } else { + MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); + } + MPI_Wait(&req, MPI_STATUS_IGNORE); + + MPI_Finalize(); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK-NOT: Contract violation detected! +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK-NOT: Contract violation detected! +// CHECK: Analysis finished. diff --git a/Tests/fort/Correct-P2POffset.F90 b/Tests/fort/Correct-P2POffset.F90 new file mode 100644 index 0000000..af9e135 --- /dev/null +++ b/Tests/fort/Correct-P2POffset.F90 @@ -0,0 +1,34 @@ +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: rank + integer, pointer :: buf(:) + type(MPI_Request) :: req + + call MPI_Init() + + call MPI_Comm_rank(MPI_COMM_WORLD, rank) + + allocate(buf(3)) + buf(3) = 42 + if (rank == 0) then + call MPI_Isend(buf(3), 1, MPI_INT, 1, 0, MPI_COMM_WORLD, req) + print *, "Buf: ", buf(3) + else + call MPI_Irecv(buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, req) + end if + call MPI_Wait(req, MPI_STATUS_IGNORE) + + call MPI_Finalize() +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK-NOT: Contract violation detected! +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK-NOT: Contract violation detected! +! CHECK: Analysis finished. From e451d1a25082ba44c6524fdf09e103db87d0c19d Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 25 Mar 2026 14:22:20 +0100 Subject: [PATCH 057/125] Cleanup --- Grammars/ContractParser.g4 | 2 +- Include/ContractTree.hpp | 2 +- LangCode/ContractDataVisitor.cpp | 3 +-- Passes/ContractVerifierParam.hpp | 2 +- Passes/Instrument.cpp | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Grammars/ContractParser.g4 b/Grammars/ContractParser.g4 index caf803b..e48aa93 100644 --- a/Grammars/ContractParser.g4 +++ b/Grammars/ContractParser.g4 @@ -17,7 +17,7 @@ tagUnit: Variable (OPPrefix NatNum OPPostfix)?; expression: callOp | releaseOp | paramOp | rwOp; // rwOp only makes sense for alloc though -natExpr: NatNum | NatNum MarkArg; +natExpr: NatNum MarkArg?; multExpr: Deref mathExpr; mathOp: multExpr; mathExpr: natExpr mathOp?; diff --git a/Include/ContractTree.hpp b/Include/ContractTree.hpp index 46afe9c..4fab69f 100644 --- a/Include/ContractTree.hpp +++ b/Include/ContractTree.hpp @@ -19,7 +19,7 @@ namespace ContractTree { enum struct MathType { UNARY_VALUE, MULT }; struct MathExpr { int value; - bool isArgValue; + bool isArg; MathType type; std::shared_ptr other = nullptr; }; diff --git a/LangCode/ContractDataVisitor.cpp b/LangCode/ContractDataVisitor.cpp index 141ee98..1f0a1c5 100644 --- a/LangCode/ContractDataVisitor.cpp +++ b/LangCode/ContractDataVisitor.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include using namespace ContractTree; @@ -71,7 +70,7 @@ std::any ContractDataVisitor::visitExpression(ContractParser::ExpressionContext std::any ContractDataVisitor::visitMathExpr(ContractParser::MathExprContext* ctx) { MathExpr expr; - expr.isArgValue = ctx->natExpr()->MarkArg(); + expr.isArg = ctx->natExpr()->MarkArg(); expr.value = std::stoi(ctx->natExpr()->NatNum()->getText()); if (ctx->mathOp()) { if (ctx->mathOp()->multExpr()) { diff --git a/Passes/ContractVerifierParam.hpp b/Passes/ContractVerifierParam.hpp index e1cb7ad..25e6425 100644 --- a/Passes/ContractVerifierParam.hpp +++ b/Passes/ContractVerifierParam.hpp @@ -13,7 +13,7 @@ class ContractVerifierParamPass : public PassInfoMixin vars, CallBase* call, int idx, ContractTree::Comparator comp, std::string& ErrInfo); + ContractTree::Fulfillment checkParamReq(std::set vars, CallBase* call, int idx, ContractTree::Comparator const& comp, std::string& ErrInfo); }; } // namespace llvm diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 625820f..1cbfdc0 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -379,7 +379,7 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr expr) { Constant* val = Basic_Types.getInt(expr->value); - Constant* isArg = Basic_Types.getBool(expr->isArgValue); + Constant* isArg = Basic_Types.getBool(expr->isArg); Constant* type = Basic_Types.getInt((int32_t)expr->type); Constant* other = Basic_Types.Null_Const; if (expr->type != MathType::UNARY_VALUE) { From 55827e7169aa0b99b821e886df450f1acc530086 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 25 Mar 2026 14:46:12 +0100 Subject: [PATCH 058/125] Improve param error detection --- Dynamic/Analyses/ParamAnalysis.cpp | 3 +- Grammars/ContractParser.g4 | 2 +- Include/ContractTree.hpp | 11 +++-- Include/DynamicAnalysis.h | 1 + LangCode/ContractDataVisitor.cpp | 7 ++-- Passes/ContractVerifierParam.cpp | 21 +++++----- Passes/Instrument.cpp | 16 ++++---- Scripts/gen_mpi_contr_h.py | 4 +- Tests/CMakeLists.txt | 1 + Tests/c/Param-SendRecvOverlap.c | 54 ++++++++++++++++++++++++ Tests/fort/Param-SendRecvOverlap.F90 | 61 ++++++++++++++++++++++++++++ 11 files changed, 152 insertions(+), 29 deletions(-) create mode 100644 Tests/c/Param-SendRecvOverlap.c create mode 100644 Tests/fort/Param-SendRecvOverlap.F90 diff --git a/Dynamic/Analyses/ParamAnalysis.cpp b/Dynamic/Analyses/ParamAnalysis.cpp index 59d0760..ac8d0b7 100644 --- a/Dynamic/Analyses/ParamAnalysis.cpp +++ b/Dynamic/Analyses/ParamAnalysis.cpp @@ -3,13 +3,14 @@ #include "DynamicAnalysis.h" #include "../DynamicUtils.h" +#include #include Fulfillment ParamAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { if (func != func_supplier) return Fulfillment::UNKNOWN; for (const ParamReq_t* req : param_requirements) { - const void* act_req = req->value; + const void* act_req = req->isArg ? callsite.params[(int64_t)req->value].value: req->value; const void* act_callp = callsite.params[idx].value; if (req->need_deref) { act_req = (const void*)DynamicUtils::TruncateBits(*(int64_t*)act_req, callsite.params[idx].size); diff --git a/Grammars/ContractParser.g4 b/Grammars/ContractParser.g4 index e48aa93..4ab8e74 100644 --- a/Grammars/ContractParser.g4 +++ b/Grammars/ContractParser.g4 @@ -26,7 +26,7 @@ rwOp: (OPRead | OPWrite | OPAlloc | OPFree) OPPrefix (Deref | AddrOf)? arg_index varMap: (callP=NatNum | TagParam) MapSep (Deref | AddrOf)? contrP=NatNum; callOp: (OPCall | OPCallTag) OPPrefix Variable (ListSep varMap)* OPPostfix; paramOp: OPParam OPPrefix NatNum MapSep paramReq (ListSep paramReq)* OPPostfix; -paramReq: (ParamEqExcept | ParamForbidEq | ParamGt | ParamGtEq | ParamLt | ParamLtEq) value=(Variable | NatNum); +paramReq: (ParamEqExcept | ParamForbidEq | ParamGt | ParamGtEq | ParamLt | ParamLtEq) (value=Variable | (value=NatNum MarkArg?)); relForbidden: rwOp | callOp; releaseOp: OPRelease1 OPPrefix forbidden=relForbidden OPPostfix OPRelease2 OPPrefix until=callOp OPPostfix; diff --git a/Include/ContractTree.hpp b/Include/ContractTree.hpp index 4fab69f..4615fbc 100644 --- a/Include/ContractTree.hpp +++ b/Include/ContractTree.hpp @@ -53,10 +53,15 @@ namespace ContractTree { enum Comparator { NEQ, GT, GTEQ, LT, LTEQ, EXEQ }; + struct ParamRequirement { + Comparator comp; + std::string value; + bool isArg; + }; struct ParamOperation : Operation { - ParamOperation(int _idx, std::vector> _reqs) : idx{_idx}, reqs{_reqs} {}; - const int idx; - const std::vector> reqs; + ParamOperation(int _idx) : idx{_idx} {}; + int idx; + std::vector reqs; virtual const FormulaType type() const override { return FormulaType::PARAM; }; }; struct CallParam { diff --git a/Include/DynamicAnalysis.h b/Include/DynamicAnalysis.h index 9febcf0..0f09c55 100644 --- a/Include/DynamicAnalysis.h +++ b/Include/DynamicAnalysis.h @@ -65,6 +65,7 @@ struct ReleaseOp_t { struct ParamReq_t { const Comparator comparator; const void* value; + const bool isArg; const bool need_deref; }; struct ParamOp_t { diff --git a/LangCode/ContractDataVisitor.cpp b/LangCode/ContractDataVisitor.cpp index 1f0a1c5..c87b0b5 100644 --- a/LangCode/ContractDataVisitor.cpp +++ b/LangCode/ContractDataVisitor.cpp @@ -99,7 +99,7 @@ std::any ContractDataVisitor::visitRwOp(ContractParser::RwOpContext *ctx) { return op; } std::any ContractDataVisitor::visitParamOp(ContractParser::ParamOpContext *ctx) { - std::vector> reqs; + ParamOperation pOP(std::stoi(ctx->NatNum()->getText())); for (ContractParser::ParamReqContext* req : ctx->paramReq()) { Comparator comp; if (req->ParamForbidEq()) comp = Comparator::NEQ; @@ -108,10 +108,9 @@ std::any ContractDataVisitor::visitParamOp(ContractParser::ParamOpContext *ctx) if (req->ParamLt()) comp = Comparator::LT; if (req->ParamLtEq()) comp = Comparator::LTEQ; if (req->ParamEqExcept()) comp = Comparator::EXEQ; - reqs.push_back({comp, req->value->getText()}); + pOP.reqs.push_back({comp, req->value->getText(), req->MarkArg() != nullptr}); } - std::shared_ptr op = std::make_shared(std::stoi(ctx->NatNum()->getText()), reqs); - return op; + return std::static_pointer_cast(std::make_shared(pOP)); } std::any ContractDataVisitor::visitCallOp(ContractParser::CallOpContext *ctx) { std::vector params; diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index 2ce5095..d53e6ab 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -52,36 +52,37 @@ PreservedAnalyses ContractVerifierParamPass::run(Module &M, // Perform the check on each callsite for (User* U : C.F->users()) { if (CallBase* CB = dyn_cast(U)) { - for (std::pair req : ParamOp->reqs) { + for (ParamRequirement const& req : ParamOp->reqs) { // Figure out value(s) to check against std::set vars; try { // First, check if constant value provided - int ivalue = std::stoi(req.second); - vars = {ConstantInt::get(Type::getInt64Ty(M.getContext()), ivalue)}; + int ivalue = std::stoi(req.value); + if (req.isArg) vars = {CB->getArgOperand(ivalue)}; + else vars = {ConstantInt::get(Type::getInt64Ty(M.getContext()), ivalue)}; } catch(std::exception& e) { // Otherwise, check against value database - if (!DB.ContractVariableData.contains(req.second)) { - errs() << "Undefined non-constint contract value identifier \"" << req.second << "\"!\n"; + if (!DB.ContractVariableData.contains(req.value)) { + errs() << "Undefined non-constint contract value identifier \"" << req.value << "\"!\n"; errs() << "Requirement will not be analysed!\n"; continue; } - vars = DB.ContractVariableData[req.second]; + vars = DB.ContractVariableData[req.value]; } // Perform check std::string errInfo = ""; - Fulfillment f = checkParamReq(vars, CB, ParamOp->idx, req.first, errInfo); + Fulfillment f = checkParamReq(vars, CB, ParamOp->idx, req.comp, errInfo); if (f == Fulfillment::BROKEN) { resf = Fulfillment::BROKEN; Expr->ErrorInfo->push_back({ .error_id = "Param", - .text = std::format("{:s} Parameter Index: {:d}, Contract Value: {:s}", errInfo.empty() ? "Parameter error detected!" : errInfo, ParamOp->idx, req.second), + .text = std::format("{:s} Parameter Index: {:d}, Contract Value: {:s}", errInfo.empty() ? "Parameter error detected!" : errInfo, ParamOp->idx, req.value), .references = {ContractPassUtility::getFileReference(CB)}, }); goto exit_param_analysis; } - if (f == Fulfillment::FULFILLED && req.first == Comparator::EXEQ) { + if (f == Fulfillment::FULFILLED && req.comp == Comparator::EXEQ) { // Parameter fulfills exception value. Stop checking this parameter goto exit_param_analysis; } @@ -136,7 +137,7 @@ Fulfillment compareCI(const ConstantInt* CI, const ConstantInt* CI2, Comparator } } -Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, CallBase* call, int idx, Comparator comp, std::string& ErrInfo) { +Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, CallBase* call, int idx, Comparator const& comp, std::string& ErrInfo) { for (Value* var : vars) { Value* callVal = call->getArgOperand(idx); if (AllocaInst* AI = dyn_cast(callVal)) { diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 1cbfdc0..f6930ff 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -306,26 +306,26 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr pOP = static_pointer_cast(op); std::vector reqCs; - for (std::pair req : pOP->reqs) { + for (ParamRequirement const& req : pOP->reqs) { Constant* var = Basic_Types.Null_Const; try { - int ivalue = std::stoi(req.second); + int ivalue = std::stoi(req.value); var = Basic_Types.getInt64(ivalue); var = ConstantExpr::getIntToPtr(var, Basic_Types.Ptr_Type); - reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.first), var, Basic_Types.getBool(false)})); + reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(false)})); } catch(std::exception& e) { - if (!DB->ContractVariableData.contains(req.second)) { - errs() << "Undefined non-constint contract value identifier \"" << req.second << "\"!\n"; + if (!DB->ContractVariableData.contains(req.value)) { + errs() << "Undefined non-constint contract value identifier \"" << req.value << "\"!\n"; errs() << "Param Requirement will not be instrumented!\n"; continue; } - for (Value* V : DB->ContractVariableData[req.second]) { + for (Value* V : DB->ContractVariableData[req.value]) { if (isa(V)) var = (Constant*)V; if (isa(var)) var = ConstantExpr::getIntToPtr(var, Basic_Types.Ptr_Type); if (!isa(var)) { errs() << "Weird param error in instr pass\n"; } - reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.first), var, Basic_Types.getBool(var->getName().starts_with("_QQ"))})); + reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(var->getName().starts_with("_QQ"))})); } } } @@ -444,7 +444,7 @@ void InstrumentPass::createTypes(Module& M) { Ref_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}); // char* file ref, char* type ParamReq_Type = StructType::create(M.getContext(), "ParamReq_t"); - ParamReq_Type->setBody({Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Bool_Type}); // Comparator, Value + ParamReq_Type->setBody({Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Bool_Type, Basic_Types.Bool_Type}); // Comparator, Value, isArg, need_deref MemOpFunc_Type = StructType::create(M.getContext(), "MemOpFunc_t"); MemOpFunc_Type->setBody({Basic_Types.Ptr_Type, Basic_Types.Ptr_Type, Basic_Types.Ptr_Type}); // Func, rwOp, size mathexpr diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 16c884e..013ec70 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -348,8 +348,8 @@ def add_contract(func: str, scope: str, contr: str): for func, idx in paramerror_null: add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_IN_PLACE) MSG \"Parameter is null\"") -# Allow MPI_IN_PLACE for recv buffer of sendrecv -add_contract("MPI_Sendrecv", "PRE", f"param!(5:^=MPI_IN_PLACE,!=NULL) MSG \"Buffer is null\"") +# Allow MPI_IN_PLACE for recv buffer of sendrecv, but dont allow same as send buf +add_contract("MPI_Sendrecv", "PRE", f"param!(5:^=MPI_IN_PLACE,!=NULL,!=0 _arg) MSG \"Buffer is null or same as send buffer\"") # Comm buffer should be allocated paramerror_null = [ diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 410267d..b159a2e 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -16,5 +16,6 @@ add_cover_test(PostCall-MissingFinalize) add_cover_test(PreCall-MissingInit) add_cover_test(Release-DataRace) add_cover_test(Param-InvalidComm) +add_cover_test(Param-SendRecvOverlap) add_cover_test(Alloc-BufNotAllocated) add_cover_test(Alloc-BufUseAfterFree) diff --git a/Tests/c/Param-SendRecvOverlap.c b/Tests/c/Param-SendRecvOverlap.c new file mode 100644 index 0000000..85abcef --- /dev/null +++ b/Tests/c/Param-SendRecvOverlap.c @@ -0,0 +1,54 @@ +// Copied from MPI-BugBench: InvalidParam-Buffer-mpi_sendrecv-001 + +// RUN: %clangContracts %run_common + +#include +#include +#include +#include +#include + +int main(int argc, char **argv) { + int nprocs = -1; + int rank = -1; + + MPI_Init(&argc, &argv); + MPI_Comm_size(MPI_COMM_WORLD, &nprocs); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (nprocs < 2) + printf( + "MBB ERROR: This test needs at least 2 processes to produce a bug!\n"); + + int *buf = (int *)calloc(10, sizeof(int)); + + int *recv_buf = (int *)calloc(10, sizeof(int)); + + if (rank == 0) { + /*MBBERROR_BEGIN*/ MPI_Sendrecv(buf, 10, MPI_INT, 1, 0, buf, 10, MPI_INT, 1, + 0, MPI_COMM_WORLD, + MPI_STATUS_IGNORE); /*MBBERROR_END*/ + } + if (rank == 1) { + /*MBBERROR_BEGIN*/ MPI_Sendrecv(buf, 10, MPI_INT, 0, 0, buf, 10, MPI_INT, 0, + 0, MPI_COMM_WORLD, + MPI_STATUS_IGNORE); /*MBBERROR_END*/ + } + + free(buf); + + free(recv_buf); + + MPI_Finalize(); + printf("Rank %d finished normally\n", rank); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK: Contract violation detected! +// CHECK: Buffer is null or same as send buffer +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK: Contract violation detected! +// CHECK: Buffer is null or same as send buffer +// Dont check for analysis finished, MPI implementation might crash. diff --git a/Tests/fort/Param-SendRecvOverlap.F90 b/Tests/fort/Param-SendRecvOverlap.F90 new file mode 100644 index 0000000..2afc2da --- /dev/null +++ b/Tests/fort/Param-SendRecvOverlap.F90 @@ -0,0 +1,61 @@ +! Copied from MPI-BugBench: InvalidParam-Buffer-mpi_sendrecv-001 + +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: ierr + integer :: nprocs = -1 + integer :: rank = -1 + integer :: double_size + integer :: integer_size + integer :: logical_size + integer :: i ! Loop index used by some tests + integer, pointer :: buf(:) + integer, pointer :: recv_buf(:) + + call MPI_Init(ierr) + call MPI_Comm_size(MPI_COMM_WORLD, nprocs, ierr) + call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr) + if (nprocs .lt. 2) then + print *, "MBB ERROR: This test needs at least 2 processes to produce a bug!\n" + end if + + call mpi_type_size(MPI_DOUBLE_PRECISION, double_size, ierr) + call mpi_type_size(MPI_INTEGER, integer_size, ierr) + call mpi_type_size(MPI_LOGICAL, logical_size, ierr) + + allocate (buf(0:(10) - 1)) + + allocate (recv_buf(0:(10) - 1)) + + if (rank == 0) then +! MBBERROR_BEGIN + call MPI_Sendrecv(buf, 10, MPI_INTEGER, 1, 0, buf, 10, MPI_INTEGER, 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE, ierr) +! MBBERROR_END + end if + if (rank == 1) then +! MBBERROR_BEGIN + call MPI_Sendrecv(buf, 10, MPI_INTEGER, 0, 0, buf, 10, MPI_INTEGER, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE, ierr) +! MBBERROR_END + end if + + deallocate (buf) + + deallocate (recv_buf) + + call MPI_Finalize(ierr) + print *, "Rank ", rank, " finished normally" +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK: Contract violation detected! +! CHECK: Buffer is null or same as send buffer +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK: Contract violation detected! +! CHECK: Buffer is null or same as send buffer +! Dont check if analysis finished, MPI implementation might crash. From 7cc0c3ce09f82a4323bf140c03d0cb6a2c54926d Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 25 Mar 2026 16:14:32 +0100 Subject: [PATCH 059/125] Remove dwarf dependency --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 44cb7b5..2bc0368 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ See [Usage](./Docs/Usage.md) - LLVM 21 or newer - Java (Build dependency only) -- dwarf.h (Ubuntu: libdw-dev, build dependency only) Optionally: - ANTLR4 (provided if not installed) From 6b46ff7ca1ca0d08186e0596bbd9ead253a267ed Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 26 Mar 2026 13:18:23 +0100 Subject: [PATCH 060/125] Fix dynamic det of int param error comparisons --- Dynamic/Analyses/ParamAnalysis.cpp | 44 ++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/Dynamic/Analyses/ParamAnalysis.cpp b/Dynamic/Analyses/ParamAnalysis.cpp index ac8d0b7..85c45f0 100644 --- a/Dynamic/Analyses/ParamAnalysis.cpp +++ b/Dynamic/Analyses/ParamAnalysis.cpp @@ -15,27 +15,41 @@ Fulfillment ParamAnalysis::functionCBImpl(void* const& func, bool const isPre, C if (req->need_deref) { act_req = (const void*)DynamicUtils::TruncateBits(*(int64_t*)act_req, callsite.params[idx].size); act_callp = (const void*)DynamicUtils::TruncateBits(*(int64_t*)act_callp, callsite.params[idx].size); + } else { + act_callp = (const void*)DynamicUtils::TruncateBits((uintptr_t)act_callp, callsite.params[idx].size); } switch (req->comparator) { - case Comparator::NEQ: - if (act_callp != act_req) continue; - references.push_back(callsite.location); return Fulfillment::VIOLATED; - case Comparator::GTEQ: - if (act_callp >= act_req) continue; - references.push_back(callsite.location); return Fulfillment::VIOLATED; - case Comparator::GT: - if (act_callp > act_req) continue; - references.push_back(callsite.location); return Fulfillment::VIOLATED; - case Comparator::LTEQ: - if (act_callp <= act_req) continue; - references.push_back(callsite.location); return Fulfillment::VIOLATED; - case Comparator::LT: - if (act_callp < act_req) continue; - references.push_back(callsite.location); return Fulfillment::VIOLATED; case Comparator::EXEQ: // EXEQ is the exception (pun), it overrides other forbidden values. if (act_callp == act_req) return Fulfillment::FULFILLED; continue; + case Comparator::NEQ: + if (act_callp != act_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + // Smaller/Larger comparisons dont make sense for pointers. Assume ints from here + default: + switch (callsite.params[idx].size) { + case 8: act_callp = (void*)(int64_t)(int8_t)(uintptr_t)act_callp; break; + case 16: act_callp = (void*)(int64_t)(int16_t)(uintptr_t)act_callp; break; + case 32: act_callp = (void*)(int64_t)(int32_t)(uintptr_t)act_callp; break; + } + switch (req->comparator) { + case Comparator::GTEQ: + if ((int64_t)act_callp >= (int64_t)act_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + case Comparator::GT: + if ((int64_t)act_callp > (int64_t)act_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + case Comparator::LTEQ: + if ((int64_t)act_callp <= (int64_t)act_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + case Comparator::LT: + if ((int64_t)act_callp < (int64_t)act_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + case Comparator::EXEQ: + case Comparator::NEQ: + __builtin_unreachable(); + } } } return Fulfillment::UNKNOWN; From abc982a0924f8ad8389d990041bb05e54c0a6e79 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 26 Mar 2026 15:49:04 +0100 Subject: [PATCH 061/125] Fix short circuit EXEQ not working statically --- Passes/ContractVerifierParam.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index d53e6ab..1986563 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -123,15 +123,15 @@ std::string createCompErr(const Comparator comp, const ConstantInt* callCI, cons Fulfillment compareCI(const ConstantInt* CI, const ConstantInt* CI2, Comparator comp) { switch (comp) { case Comparator::NEQ: - return CI->getValue().getSExtValue() != CI2->getValue().getSExtValue() ? Fulfillment::FULFILLED : Fulfillment::BROKEN; + return CI->getValue().getSExtValue() != CI2->getValue().getSExtValue() ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; case Comparator::GTEQ: - return CI->getValue().sge(CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; + return CI->getValue().sge(CI2->getValue()) ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; case Comparator::GT: - return CI->getValue().sgt(CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; + return CI->getValue().sgt(CI2->getValue()) ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; case Comparator::LTEQ: - return CI->getValue().sle(CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; + return CI->getValue().sle(CI2->getValue()) ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; case Comparator::LT: - return CI->getValue().slt(CI2->getValue()) ? Fulfillment::FULFILLED : Fulfillment::BROKEN; + return CI->getValue().slt(CI2->getValue()) ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; case Comparator::EXEQ: return CI->getValue().getSExtValue() == CI2->getValue().getSExtValue() ? Fulfillment::FULFILLED : Fulfillment::UNKNOWN; } @@ -191,7 +191,7 @@ Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, Call if (f == Fulfillment::BROKEN) { ErrInfo = createCompErr(comp, callCI, varCI); return f; - } + } else if (f == Fulfillment::FULFILLED) return f; } } } From 3bafba6fdf4e84681a0150d5b8892984a142313f Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 26 Mar 2026 15:49:51 +0100 Subject: [PATCH 062/125] Fix dynamic param again --- Dynamic/Analyses/ParamAnalysis.cpp | 54 +++++++++++++++--------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/Dynamic/Analyses/ParamAnalysis.cpp b/Dynamic/Analyses/ParamAnalysis.cpp index 85c45f0..ac9d82b 100644 --- a/Dynamic/Analyses/ParamAnalysis.cpp +++ b/Dynamic/Analyses/ParamAnalysis.cpp @@ -6,50 +6,50 @@ #include #include +static constexpr int64_t sign_extend(uintptr_t const ptr, int const size) { + switch (size) { + case 8: return (int64_t)(int8_t)ptr; break; + case 16: return (int64_t)(int16_t)ptr; break; + case 32: return (int64_t)(int32_t)ptr; break; + } + return ptr; +} + Fulfillment ParamAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { if (func != func_supplier) return Fulfillment::UNKNOWN; for (const ParamReq_t* req : param_requirements) { - const void* act_req = req->isArg ? callsite.params[(int64_t)req->value].value: req->value; - const void* act_callp = callsite.params[idx].value; + void const* act_req = req->isArg ? callsite.params[(int64_t)req->value].value: req->value; + void const* act_callp = callsite.params[idx].value; if (req->need_deref) { act_req = (const void*)DynamicUtils::TruncateBits(*(int64_t*)act_req, callsite.params[idx].size); act_callp = (const void*)DynamicUtils::TruncateBits(*(int64_t*)act_callp, callsite.params[idx].size); } else { act_callp = (const void*)DynamicUtils::TruncateBits((uintptr_t)act_callp, callsite.params[idx].size); } + int64_t const int_req = (int64_t const)act_req; + int64_t const int_callp = sign_extend((uintptr_t)act_callp, callsite.params[idx].size); switch (req->comparator) { case Comparator::EXEQ: // EXEQ is the exception (pun), it overrides other forbidden values. - if (act_callp == act_req) return Fulfillment::FULFILLED; + if (act_callp == act_req || int_callp == int_req) return Fulfillment::FULFILLED; continue; case Comparator::NEQ: - if (act_callp != act_req) continue; + if (act_callp != act_req && int_callp != int_req) continue; references.push_back(callsite.location); return Fulfillment::VIOLATED; // Smaller/Larger comparisons dont make sense for pointers. Assume ints from here - default: - switch (callsite.params[idx].size) { - case 8: act_callp = (void*)(int64_t)(int8_t)(uintptr_t)act_callp; break; - case 16: act_callp = (void*)(int64_t)(int16_t)(uintptr_t)act_callp; break; - case 32: act_callp = (void*)(int64_t)(int32_t)(uintptr_t)act_callp; break; - } - switch (req->comparator) { - case Comparator::GTEQ: - if ((int64_t)act_callp >= (int64_t)act_req) continue; - references.push_back(callsite.location); return Fulfillment::VIOLATED; - case Comparator::GT: - if ((int64_t)act_callp > (int64_t)act_req) continue; - references.push_back(callsite.location); return Fulfillment::VIOLATED; - case Comparator::LTEQ: - if ((int64_t)act_callp <= (int64_t)act_req) continue; - references.push_back(callsite.location); return Fulfillment::VIOLATED; - case Comparator::LT: - if ((int64_t)act_callp < (int64_t)act_req) continue; - references.push_back(callsite.location); return Fulfillment::VIOLATED; - case Comparator::EXEQ: - case Comparator::NEQ: - __builtin_unreachable(); - } + case Comparator::GTEQ: + if (int_callp >= int_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + case Comparator::GT: + if (int_callp > int_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + case Comparator::LTEQ: + if (int_callp <= int_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; + case Comparator::LT: + if (int_callp < int_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; } } return Fulfillment::UNKNOWN; From 278c6c7a74e87af4c360239a2b1e72ef60acda01 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 26 Mar 2026 18:02:25 +0100 Subject: [PATCH 063/125] Add RankNull from bugbench --- Tests/CMakeLists.txt | 1 + Tests/c/Correct-RankNull.c | 46 +++++++++++++++++++++++++++++ Tests/fort/Correct-RankNull.F90 | 51 +++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 Tests/c/Correct-RankNull.c create mode 100644 Tests/fort/Correct-RankNull.F90 diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index b159a2e..3f21828 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -11,6 +11,7 @@ add_cover_test(Correct-P2P) add_cover_test(Correct-P2PStackBuf) add_cover_test(Correct-WinAlloc) add_cover_test(Correct-P2POffset) +add_cover_test(Correct-RankNull) add_cover_test(PostCall-MissingFinalize) add_cover_test(PreCall-MissingInit) diff --git a/Tests/c/Correct-RankNull.c b/Tests/c/Correct-RankNull.c new file mode 100644 index 0000000..4098562 --- /dev/null +++ b/Tests/c/Correct-RankNull.c @@ -0,0 +1,46 @@ +// Copied from MPI-BugBench: Correct-Rank-001 + +// RUN: %clangContracts %run_common + +#include +#include +#include +#include +#include + +int main(int argc, char **argv) { + int nprocs = -1; + int rank = -1; + MPI_Win mpi_win_0; + int *winbuf; + + MPI_Init(&argc, &argv); + MPI_Comm_size(MPI_COMM_WORLD, &nprocs); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + if (nprocs < 2) + printf( + "MBB ERROR: This test needs at least 2 processes to produce a bug!\n"); + + int *buf = (int *)calloc(10, sizeof(int)); + + MPI_Win_allocate(10 * sizeof(int), sizeof(int), MPI_INFO_NULL, MPI_COMM_WORLD, + &winbuf, &mpi_win_0); + MPI_Win_fence(0, mpi_win_0); + if (rank == 0) { + MPI_Get(buf, 10, MPI_INT, MPI_PROC_NULL, 0, 10, MPI_INT, mpi_win_0); + } + MPI_Win_fence(0, mpi_win_0); + MPI_Win_free(&mpi_win_0); + + MPI_Finalize(); + printf("Rank %d finished normally\n", rank); + return 0; +} + +// CHECK-LABEL: Running Contract Manager on Module +// CHECK-NOT: Contract violation detected! +// CHECK: CoVer: Total Tool Runtime + +// CHECK-LABEL: CoVer-Dynamic: Initializing... +// CHECK-NOT: Contract violation detected! +// CHECK: Analysis finished. diff --git a/Tests/fort/Correct-RankNull.F90 b/Tests/fort/Correct-RankNull.F90 new file mode 100644 index 0000000..dbe18ea --- /dev/null +++ b/Tests/fort/Correct-RankNull.F90 @@ -0,0 +1,51 @@ +! Copied from MPI-BugBench: Correct-Rank-001 + +! RUN: %flangContracts %run_common + +program main + use mpi_f08 + implicit none + + integer :: ierr + integer :: nprocs = -1 + integer :: rank = -1 + integer :: double_size + integer :: integer_size + integer :: logical_size + integer :: i ! Loop index used by some tests + type(MPI_Win) :: mpi_win_0 + type(c_ptr) :: winbuf + integer, pointer :: buf(:) + + call MPI_Init(ierr) + call MPI_Comm_size(MPI_COMM_WORLD, nprocs, ierr) + call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr) + if (nprocs .lt. 2) then + print *, "MBB ERROR: This test needs at least 2 processes to produce a bug!\n" + end if + + call mpi_type_size(MPI_DOUBLE_PRECISION, double_size, ierr) + call mpi_type_size(MPI_INTEGER, integer_size, ierr) + call mpi_type_size(MPI_LOGICAL, logical_size, ierr) + + allocate (buf(0:(10) - 1)) + + call MPI_Win_allocate(int(10*integer_size, mpi_address_kind), integer_size, MPI_INFO_NULL, MPI_COMM_WORLD, winbuf, mpi_win_0, ierr) + call MPI_Win_fence(0, mpi_win_0, ierr) + if (rank == 0) then + call MPI_Get(buf, 10, MPI_INTEGER, MPI_PROC_NULL, int(0, mpi_address_kind), 10, MPI_INTEGER, mpi_win_0, ierr) + end if + call MPI_Win_fence(0, mpi_win_0, ierr) + call MPI_Win_free(mpi_win_0, ierr) + + call MPI_Finalize(ierr) + print *, "Rank ", rank, " finished normally" +end program + +! CHECK-LABEL: Running Contract Manager on Module +! CHECK-NOT: Contract violation detected! +! CHECK: CoVer: Total Tool Runtime + +! CHECK-LABEL: CoVer-Dynamic: Initializing... +! CHECK-NOT: Contract violation detected! +! CHECK: Analysis finished. From 9aa1840b7603fcc65b601eb31dbb4a11386539cb Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 26 Mar 2026 18:55:12 +0100 Subject: [PATCH 064/125] For static paramanalysis prefer constint comparisons --- Passes/ContractVerifierParam.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index 1986563..47069b1 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -150,6 +150,19 @@ Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, Call } } } + // Always prefer constint comparisons. For Fortran, this sometimes requires a lil trickery: + if (Instruction* I = dyn_cast(callVal)) { + MemoryDependenceResults& MDR = MAM->getResult(*I->getModule()).getManager().getResult(*I->getFunction()); + MemoryLocation Loc = MemoryLocation::getForArgument(call, idx, MAM->getResult(*I->getModule()).getManager().getResult(*call->getFunction())); + MemDepResult x = MDR.getPointerDependencyFrom(Loc, true, call->getIterator(), call->getParent()); + if (x.getInst()) { + if (StoreInst* S = dyn_cast(x.getInst())) { + if (isa(S->getValueOperand())) { + callVal = S->getValueOperand(); + } + } + } + } if (callVal->getType()->isPointerTy()) { switch (comp) { case Comparator::NEQ: @@ -166,20 +179,6 @@ Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, Call // Not an exception. Continue analysis, so far no info gained continue; default: - // Check if we can salvage this and get a constant int result still, even if IR says its a pointer at that point - if (Instruction* I = dyn_cast(callVal)) { - MemoryDependenceResults& MDR = MAM->getResult(*I->getModule()).getManager().getResult(*I->getFunction()); - MemoryLocation Loc = MemoryLocation::getForArgument(call, idx, MAM->getResult(*I->getModule()).getManager().getResult(*call->getFunction())); - MemDepResult x = MDR.getPointerDependencyFrom(Loc, true, call->getIterator(), call->getParent()); - if (x.getInst()) { - if (StoreInst* S = dyn_cast(x.getInst())) { - if (isa(S->getValueOperand())) { - callVal = S->getValueOperand(); - break; - } - } - } - } errs() << "Attempt to compare pointers! Not performing parameter analysis\n"; return Fulfillment::UNKNOWN; } From 778db37f410d5e8f1c1dff9b1b08d323c1ff280e Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 26 Mar 2026 18:57:16 +0100 Subject: [PATCH 065/125] Semi-hack: Add heuristic to detect constint for fortran dyn analysis --- Dynamic/Analyses/ParamAnalysis.cpp | 16 ++++++++-------- Dynamic/Analyses/ParamAnalysis.h | 3 ++- Include/DynamicAnalysis.h | 3 ++- Passes/Instrument.cpp | 15 ++++++++++++--- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Dynamic/Analyses/ParamAnalysis.cpp b/Dynamic/Analyses/ParamAnalysis.cpp index ac9d82b..aa99010 100644 --- a/Dynamic/Analyses/ParamAnalysis.cpp +++ b/Dynamic/Analyses/ParamAnalysis.cpp @@ -18,17 +18,17 @@ static constexpr int64_t sign_extend(uintptr_t const ptr, int const size) { Fulfillment ParamAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { if (func != func_supplier) return Fulfillment::UNKNOWN; + void const* act_callp = callsite.params[idx].value; + if (callval_need_deref) { + act_callp = (const void*)(*(void**)act_callp); + } + int64_t const int_callp = sign_extend((uintptr_t)act_callp, callsite.params[idx].size); for (const ParamReq_t* req : param_requirements) { void const* act_req = req->isArg ? callsite.params[(int64_t)req->value].value: req->value; - void const* act_callp = callsite.params[idx].value; - if (req->need_deref) { - act_req = (const void*)DynamicUtils::TruncateBits(*(int64_t*)act_req, callsite.params[idx].size); - act_callp = (const void*)DynamicUtils::TruncateBits(*(int64_t*)act_callp, callsite.params[idx].size); - } else { - act_callp = (const void*)DynamicUtils::TruncateBits((uintptr_t)act_callp, callsite.params[idx].size); + if (req->reqval_need_deref) { + act_req = (const void*)*(void**)act_req; } - int64_t const int_req = (int64_t const)act_req; - int64_t const int_callp = sign_extend((uintptr_t)act_callp, callsite.params[idx].size); + int64_t const int_req = sign_extend((uintptr_t)act_req, callsite.params[idx].size); switch (req->comparator) { case Comparator::EXEQ: // EXEQ is the exception (pun), it overrides other forbidden values. diff --git a/Dynamic/Analyses/ParamAnalysis.h b/Dynamic/Analyses/ParamAnalysis.h index 049b1cc..b0c97ca 100644 --- a/Dynamic/Analyses/ParamAnalysis.h +++ b/Dynamic/Analyses/ParamAnalysis.h @@ -6,7 +6,7 @@ struct ParamAnalysis : BaseAnalysis { public: - ParamAnalysis(void const* _func_supplier, ParamOp_t* paramop) : idx(paramop->idx), func_supplier(_func_supplier) { + ParamAnalysis(void const* _func_supplier, ParamOp_t* paramop) : idx(paramop->idx), func_supplier(_func_supplier), callval_need_deref(paramop->callval_need_deref) { for (int i = 0; i < paramop->num_reqs; i++) { param_requirements.push_back(¶mop->requirements[i]); } @@ -22,5 +22,6 @@ struct ParamAnalysis : BaseAnalysis { // Configuration void const* func_supplier; int const idx; + bool const callval_need_deref; std::vector param_requirements; }; diff --git a/Include/DynamicAnalysis.h b/Include/DynamicAnalysis.h index 0f09c55..de86dcd 100644 --- a/Include/DynamicAnalysis.h +++ b/Include/DynamicAnalysis.h @@ -66,12 +66,13 @@ struct ParamReq_t { const Comparator comparator; const void* value; const bool isArg; - const bool need_deref; + const bool reqval_need_deref; }; struct ParamOp_t { const int32_t idx; const ParamReq_t* requirements; const int32_t num_reqs; + const bool callval_need_deref; }; struct MemOpFunc_t { const void* func; diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index f6930ff..f442177 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -306,6 +306,7 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr pOP = static_pointer_cast(op); std::vector reqCs; + bool hasIntCmp = false; for (ParamRequirement const& req : pOP->reqs) { Constant* var = Basic_Types.Null_Const; try { @@ -313,6 +314,7 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptrContractVariableData.contains(req.value)) { errs() << "Undefined non-constint contract value identifier \"" << req.value << "\"!\n"; @@ -325,13 +327,20 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr(var)) { errs() << "Weird param error in instr pass\n"; } - reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(var->getName().starts_with("_QQ"))})); + reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(!isC && (isa(var) || var->getName().starts_with("_QQ")))})); + if (GlobalVariable const* GV = dyn_cast(var)) { + if (GV->hasInitializer()) { + if (StructType const* T = dyn_cast(GV->getInitializer()->getType())) { + if (T->getNumElements() == 1 && T->getElementType(0)->isIntegerTy()) hasIntCmp = true; + } + } + } } } } Constant* reqsC = ConstantArray::get(ArrayType::get(ParamReq_Type, reqCs.size()), reqCs); reqsC = createConstantGlobalUnique(M, reqsC, "CONTR_PARAM_REQS"); - data = ConstantStruct::get(ParamOp_Type, {Basic_Types.getInt(pOP->idx), reqsC, Basic_Types.getInt(reqCs.size())}); + data = ConstantStruct::get(ParamOp_Type, {Basic_Types.getInt(pOP->idx), reqsC, Basic_Types.getInt(reqCs.size()), Basic_Types.getBool(!isC && hasIntCmp)}); name = "CONTR_PARAMOP"; break; } @@ -419,7 +428,7 @@ void InstrumentPass::createTypes(Module& M) { RWOp_Type->setBody({Basic_Types.Int_Type, Basic_Types.Int_Type, Basic_Types.Bool_Type}); // idx, paramaccess, isWrite ParamOp_Type = StructType::create(M.getContext(), "ParamOp_t"); - ParamOp_Type->setBody({Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // idx, list of reqs, num reqs + ParamOp_Type->setBody({Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Bool_Type}); // idx, list of reqs, num reqs, need deref AllocOp_Type = StructType::create(M.getContext(), "AllocOp_t"); AllocOp_Type->setBody({Basic_Types.Int_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type, Basic_Types.Ptr_Type, Basic_Types.Int_Type}); // idx, accType, list of allocators, num allocs, list of deallocs, num deallocs From 2e2d0803a57724e11175fb9d4975d134ced6961f Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 27 Mar 2026 10:51:08 +0100 Subject: [PATCH 066/125] Allow MPI_COMM_WORLD explicitly for param checks --- Scripts/gen_mpi_contr_h.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 013ec70..1b09551 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -308,7 +308,7 @@ def add_contract(func: str, scope: str, contr: str): ("MPI_Cart_get", 0), ] for func, idx in paramerror_comm: - add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_COMM_NULL) MSG \"Communicator is invalid\"") + add_contract(func, "PRE", f"param!({idx}:^=MPI_COMM_WORLD,!=NULL,!=MPI_COMM_NULL) MSG \"Communicator is invalid\"") # MPI_PROC_NULL uses negative special value in OpenMPI, add exception for it paramerror_rank_send = [ @@ -429,6 +429,10 @@ def get_param_values(lang: str) -> str: "c": "MPI_COMM_NULL", "fort": "MPI_COMM_NULL", }, + "MPI_COMM_WORLD": { + "c": "MPI_COMM_WORLD", + "fort": "MPI_COMM_WORLD", + }, "MPI_STATUS_IGNORE": { "c": "MPI_STATUS_IGNORE", "fort": "MPI_STATUS_IGNORE", From 1bb9dd7df766aa411a2023c18a5e25d8e0f3fe74 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 27 Mar 2026 13:14:48 +0100 Subject: [PATCH 067/125] Improve Fortran param heuristic --- Passes/ContractVerifierParam.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index 47069b1..895c34d 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include using namespace llvm; using namespace ContractTree; @@ -151,6 +153,7 @@ Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, Call } } // Always prefer constint comparisons. For Fortran, this sometimes requires a lil trickery: + // First, try to get at the actual value instead of the weird pointer that is passed as the arg in IR if (Instruction* I = dyn_cast(callVal)) { MemoryDependenceResults& MDR = MAM->getResult(*I->getModule()).getManager().getResult(*I->getFunction()); MemoryLocation Loc = MemoryLocation::getForArgument(call, idx, MAM->getResult(*I->getModule()).getManager().getResult(*call->getFunction())); @@ -163,6 +166,20 @@ Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, Call } } } + // Next, for some global vals its just a struct with one constint member, resolve that as well (for both param val and call val) + std::vector tmps = {&callVal, &var}; + for (Value** tmp : tmps) { + if ((*tmp)->getName().starts_with("_QQ")) { + if (GlobalVariable const* GV = dyn_cast(*tmp)) { + if (GV->hasInitializer()) { + if (StructType const* T = dyn_cast(GV->getInitializer()->getType())) { + if (T->getNumElements() == 1 && T->getElementType(0)->isIntegerTy()) (*tmp) = GV->getInitializer()->getAggregateElement((unsigned int)0); + } + } + } + } + } + if (callVal->getType()->isPointerTy()) { switch (comp) { case Comparator::NEQ: From 565d659fe8a00848e225adbfaeaf28f67e2355a1 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 27 Mar 2026 14:08:02 +0100 Subject: [PATCH 068/125] Improve error message --- Scripts/gen_mpi_contr_h.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 1b09551..e51f794 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -346,7 +346,7 @@ def add_contract(func: str, scope: str, contr: str): ("MPI_Get", 0), ] for func, idx in paramerror_null: - add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_IN_PLACE) MSG \"Parameter is null\"") + add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_IN_PLACE) MSG \"Parameter is null or MPI_IN_PLACE\"") # Allow MPI_IN_PLACE for recv buffer of sendrecv, but dont allow same as send buf add_contract("MPI_Sendrecv", "PRE", f"param!(5:^=MPI_IN_PLACE,!=NULL,!=0 _arg) MSG \"Buffer is null or same as send buffer\"") From c953a2e1702fb3eedf5e4b41d51a6ddfda5f0453 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 27 Mar 2026 14:10:37 +0100 Subject: [PATCH 069/125] Another attempt at improving fort heuristics --- Include/ContractPassUtility.hpp | 5 ++++ Passes/ContractVerifierParam.cpp | 39 +++++++++++++++++++++++++------- Passes/ContractVerifierParam.hpp | 2 ++ Passes/Instrument.cpp | 10 ++------ Utils/ContractPassUtility.cpp | 14 ++++++++++++ 5 files changed, 54 insertions(+), 16 deletions(-) diff --git a/Include/ContractPassUtility.hpp b/Include/ContractPassUtility.hpp index 6baa4e5..c452a65 100644 --- a/Include/ContractPassUtility.hpp +++ b/Include/ContractPassUtility.hpp @@ -74,6 +74,11 @@ namespace ContractPassUtility { * Check if V is definitely allocated */ bool isTrivialAlloc(const Value* V); + + /* + * Fortran Heuristic: Check if global, if so check if constint and return + */ + ConstantInt* fortCheckAndGetGlbInt(Value* V); }; template diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index 895c34d..18482da 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -1,4 +1,5 @@ #include "ContractVerifierParam.hpp" +#include "BasicTypes.hpp" #include "ContractManager.hpp" #include "ContractTree.hpp" #include "ContractPassUtility.hpp" @@ -14,13 +15,11 @@ #include #include #include +#include #include #include #include #include -#include -#include -#include #include #include #include @@ -33,6 +32,8 @@ #include #include #include +#include +#include using namespace llvm; using namespace ContractTree; @@ -41,6 +42,8 @@ PreservedAnalyses ContractVerifierParamPass::run(Module &M, ModuleAnalysisManager &AM) { ContractManagerAnalysis::ContractDatabase DB = AM.getResult(M); MAM = &AM; + Basic_Types = MAM->getResult(M); + for (ContractManagerAnalysis::LinearizedContract const& C : DB.LinearizedContracts) { for (const std::shared_ptr Expr : C.Pre) { if (*Expr->Status != Fulfillment::UNKNOWN) continue; @@ -169,11 +172,31 @@ Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, Call // Next, for some global vals its just a struct with one constint member, resolve that as well (for both param val and call val) std::vector tmps = {&callVal, &var}; for (Value** tmp : tmps) { - if ((*tmp)->getName().starts_with("_QQ")) { - if (GlobalVariable const* GV = dyn_cast(*tmp)) { - if (GV->hasInitializer()) { - if (StructType const* T = dyn_cast(GV->getInitializer()->getType())) { - if (T->getNumElements() == 1 && T->getElementType(0)->isIntegerTy()) (*tmp) = GV->getInitializer()->getAggregateElement((unsigned int)0); + Value* res = ContractPassUtility::fortCheckAndGetGlbInt(*tmp); + *tmp = res ? res : *tmp; + } + + // Check if its a pointer with the weird fortran metadata descriptor. If so, need to get contained value using heuristic + if (AllocaInst const* AI = dyn_cast(callVal)) { + if (StructType const* T = dyn_cast(AI->getAllocatedType())) { + if (T->getElementType(0) == Basic_Types.Ptr_Type && + T->getElementType(1) == Basic_Types.Int64_Type && + T->getElementType(2) == Basic_Types.Int_Type && + T->getNumElements() == 9) { + // Fortran Metadata thing - Find first GEP to callVal + Instruction* cur = call->getPrevNode(); + for (; cur && !isa(cur); cur = cur->getPrevNode()) { + if (GetElementPtrInst const* GEP = dyn_cast(cur)) { + if (GEP->getPointerOperand() != callVal) continue; + if (ExtractValueInst const* EVI = dyn_cast(GEP->getPrevNode())) { + for (Use const& U : EVI->getAggregateOperand()->uses()) { + if (InsertValueInst* IVI = dyn_cast(U.get())) { + if (IVI->getIndices()[0] == 0 && IVI->getNumIndices() == 1) { + callVal = IVI->getInsertedValueOperand(); + } + } + } + } } } } diff --git a/Passes/ContractVerifierParam.hpp b/Passes/ContractVerifierParam.hpp index 25e6425..daeae55 100644 --- a/Passes/ContractVerifierParam.hpp +++ b/Passes/ContractVerifierParam.hpp @@ -1,5 +1,6 @@ #pragma once +#include "BasicTypes.hpp" #include "ContractTree.hpp" #include "llvm/IR/PassManager.h" #include @@ -13,6 +14,7 @@ class ContractVerifierParamPass : public PassInfoMixin vars, CallBase* call, int idx, ContractTree::Comparator const& comp, std::string& ErrInfo); }; diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index f442177..be86bbd 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -327,14 +327,8 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr(var)) { errs() << "Weird param error in instr pass\n"; } - reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(!isC && (isa(var) || var->getName().starts_with("_QQ")))})); - if (GlobalVariable const* GV = dyn_cast(var)) { - if (GV->hasInitializer()) { - if (StructType const* T = dyn_cast(GV->getInitializer()->getType())) { - if (T->getNumElements() == 1 && T->getElementType(0)->isIntegerTy()) hasIntCmp = true; - } - } - } + reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(!isC && (var->getName().starts_with("_QQ")))})); + hasIntCmp = ContractPassUtility::fortCheckAndGetGlbInt(var) ? true : hasIntCmp; } } } diff --git a/Utils/ContractPassUtility.cpp b/Utils/ContractPassUtility.cpp index 709384a..f22987c 100644 --- a/Utils/ContractPassUtility.cpp +++ b/Utils/ContractPassUtility.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -207,6 +208,19 @@ bool isTrivialAlloc(Value const* V) { return false; } +ConstantInt* fortCheckAndGetGlbInt(Value* V) { + if (V->getName().starts_with("_QQ")) { + if (GlobalVariable const* GV = dyn_cast(V)) { + if (GV->hasInitializer()) { + if (StructType const* T = dyn_cast(GV->getInitializer()->getType())) { + if (T->getNumElements() == 1 && T->getElementType(0)->isIntegerTy()) return dyn_cast(GV->getInitializer()->getAggregateElement((unsigned int)0)); + } + } + } + } + return nullptr; +} + bool checkCalledApplies(const CallBase* CB, const StringRef Target, bool isTag, std::map> Tags) { if (!isTag) { if (CB->getCalledOperand()->getName().empty()) { From c85ac5c5965ecda160f9d21d03ce915a63829bf4 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 27 Mar 2026 14:11:01 +0100 Subject: [PATCH 070/125] Filter unneded fort instr --- Passes/Instrument.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index be86bbd..fc1a8c1 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -490,6 +490,11 @@ void InstrumentPass::instrumentRW(Module &M) { } } } + // Filter out new instr from sroa + if (!isC && isa(&I) && dyn_cast(&I)->getPointerOperand()->getName().starts_with(".fca.")) { + continue; + } + insertCBIfNeeded(isa(I) ? callbackRCallee : callbackWCallee, {V}, &I); } } From e219ae8a5ceab9ef1b946b04b26f069f1053710d Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 27 Mar 2026 15:40:06 +0100 Subject: [PATCH 071/125] Fix FP for SendRecv --- Dynamic/Analyses/ParamAnalysis.cpp | 3 +++ Grammars/ContractLexer.g4 | 1 + Grammars/ContractParser.g4 | 2 +- Include/ContractTree.hpp | 2 +- Include/DynamicAnalysis.h | 2 +- LangCode/ContractDataVisitor.cpp | 1 + Passes/ContractVerifierParam.cpp | 4 ++++ Scripts/gen_mpi_contr_h.py | 4 +++- 8 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Dynamic/Analyses/ParamAnalysis.cpp b/Dynamic/Analyses/ParamAnalysis.cpp index aa99010..64b8fd3 100644 --- a/Dynamic/Analyses/ParamAnalysis.cpp +++ b/Dynamic/Analyses/ParamAnalysis.cpp @@ -34,6 +34,9 @@ Fulfillment ParamAnalysis::functionCBImpl(void* const& func, bool const isPre, C // EXEQ is the exception (pun), it overrides other forbidden values. if (act_callp == act_req || int_callp == int_req) return Fulfillment::FULFILLED; continue; + case Comparator::EQ: + if (act_callp == act_req && int_callp == int_req) continue; + references.push_back(callsite.location); return Fulfillment::VIOLATED; case Comparator::NEQ: if (act_callp != act_req && int_callp != int_req) continue; references.push_back(callsite.location); return Fulfillment::VIOLATED; diff --git a/Grammars/ContractLexer.g4 b/Grammars/ContractLexer.g4 index bdc5f51..061c775 100644 --- a/Grammars/ContractLexer.g4 +++ b/Grammars/ContractLexer.g4 @@ -52,3 +52,4 @@ ParamGtEq: '>='; ParamLt: '<'; ParamLtEq: '<='; ParamEqExcept: '^='; +ParamEq: '=='; \ No newline at end of file diff --git a/Grammars/ContractParser.g4 b/Grammars/ContractParser.g4 index 4ab8e74..3faee46 100644 --- a/Grammars/ContractParser.g4 +++ b/Grammars/ContractParser.g4 @@ -26,7 +26,7 @@ rwOp: (OPRead | OPWrite | OPAlloc | OPFree) OPPrefix (Deref | AddrOf)? arg_index varMap: (callP=NatNum | TagParam) MapSep (Deref | AddrOf)? contrP=NatNum; callOp: (OPCall | OPCallTag) OPPrefix Variable (ListSep varMap)* OPPostfix; paramOp: OPParam OPPrefix NatNum MapSep paramReq (ListSep paramReq)* OPPostfix; -paramReq: (ParamEqExcept | ParamForbidEq | ParamGt | ParamGtEq | ParamLt | ParamLtEq) (value=Variable | (value=NatNum MarkArg?)); +paramReq: (ParamEqExcept | ParamEq | ParamForbidEq | ParamGt | ParamGtEq | ParamLt | ParamLtEq) (value=Variable | (value=NatNum MarkArg?)); relForbidden: rwOp | callOp; releaseOp: OPRelease1 OPPrefix forbidden=relForbidden OPPostfix OPRelease2 OPPrefix until=callOp OPPostfix; diff --git a/Include/ContractTree.hpp b/Include/ContractTree.hpp index 4615fbc..7ce3ed8 100644 --- a/Include/ContractTree.hpp +++ b/Include/ContractTree.hpp @@ -51,7 +51,7 @@ namespace ContractTree { virtual const FormulaType type() const override { return FormulaType::FREE; }; }; enum Comparator { - NEQ, GT, GTEQ, LT, LTEQ, EXEQ + NEQ, GT, GTEQ, LT, LTEQ, EXEQ, EQ }; struct ParamRequirement { Comparator comp; diff --git a/Include/DynamicAnalysis.h b/Include/DynamicAnalysis.h index de86dcd..5758bcd 100644 --- a/Include/DynamicAnalysis.h +++ b/Include/DynamicAnalysis.h @@ -29,7 +29,7 @@ struct CallParam_t { int32_t contrP; ParamAccess accType; }; -enum Comparator : int32_t { NEQ, GT, GTEQ, LT, LTEQ, EXEQ }; +enum Comparator : int32_t { NEQ, GT, GTEQ, LT, LTEQ, EXEQ, EQ }; // Number must match those defined in ContractTree.hpp! enum MathType : int32_t { UNARY_VALUE, MULT }; diff --git a/LangCode/ContractDataVisitor.cpp b/LangCode/ContractDataVisitor.cpp index c87b0b5..e10a4d0 100644 --- a/LangCode/ContractDataVisitor.cpp +++ b/LangCode/ContractDataVisitor.cpp @@ -108,6 +108,7 @@ std::any ContractDataVisitor::visitParamOp(ContractParser::ParamOpContext *ctx) if (req->ParamLt()) comp = Comparator::LT; if (req->ParamLtEq()) comp = Comparator::LTEQ; if (req->ParamEqExcept()) comp = Comparator::EXEQ; + if (req->ParamEq()) comp = Comparator::EQ; pOP.reqs.push_back({comp, req->value->getText(), req->MarkArg() != nullptr}); } return std::static_pointer_cast(std::make_shared(pOP)); diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index 18482da..d5711d0 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -112,6 +112,8 @@ std::string createCompErr(const Comparator comp, const ConstantInt* callCI, cons switch (comp) { case Comparator::NEQ: return "Parameter matches forbidden value (" + callCs + ")!"; + case Comparator::EQ: + return "Parameter does not match required value (" + callCs + ")!"; case Comparator::GT: return "Call parameter value (" + callCs + ") not greater than contract value (" + valueCs + ")!"; case Comparator::GTEQ: @@ -129,6 +131,8 @@ Fulfillment compareCI(const ConstantInt* CI, const ConstantInt* CI2, Comparator switch (comp) { case Comparator::NEQ: return CI->getValue().getSExtValue() != CI2->getValue().getSExtValue() ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; + case Comparator::EQ: + return CI->getValue().getSExtValue() == CI2->getValue().getSExtValue() ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; case Comparator::GTEQ: return CI->getValue().sge(CI2->getValue()) ? Fulfillment::UNKNOWN : Fulfillment::BROKEN; case Comparator::GT: diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index e51f794..8863bbc 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -358,13 +358,15 @@ def add_contract(func: str, scope: str, contr: str): ("MPI_Recv", 0), ("MPI_Irecv", 0), ("MPI_Sendrecv", 0), - ("MPI_Sendrecv", 5), ("MPI_Get", 0), ("MPI_Put", 0), ] for func, idx in paramerror_null: add_contract(func, "PRE", f"alloc!({idx}) MSG \"Buffer is not allocated\"") +# Allow MPI_IN_PLACE for recv buffer if its set to MPI_IN_PLACE +add_contract("MPI_Sendrecv", "PRE", f"( param!(5:==MPI_IN_PLACE) | alloc!(5) ) MSG \"SendRecv is not allocated and not MPI_IN_PLACE\"") + allocators = [ ("MPI_Win_allocate", 4), ] From 6d9e85cbde53c4c2c10efe2a86feacdee48bf1a3 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 27 Mar 2026 15:40:36 +0100 Subject: [PATCH 072/125] Stricter buffer nullcheck --- Scripts/gen_mpi_contr_h.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 8863bbc..5ece0d6 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -346,7 +346,7 @@ def add_contract(func: str, scope: str, contr: str): ("MPI_Get", 0), ] for func, idx in paramerror_null: - add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_IN_PLACE) MSG \"Parameter is null or MPI_IN_PLACE\"") + add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_IN_PLACE,!=MPI_BOTTOM) MSG \"Parameter is null, MPI_IN_PLACE, or MPI_BOTTOM\"") # Allow MPI_IN_PLACE for recv buffer of sendrecv, but dont allow same as send buf add_contract("MPI_Sendrecv", "PRE", f"param!(5:^=MPI_IN_PLACE,!=NULL,!=0 _arg) MSG \"Buffer is null or same as send buffer\"") @@ -403,6 +403,10 @@ def get_param_values(lang: str) -> str: "c": "NULL", "fort": "NULL()", }, + "MPI_BOTTOM": { + "c": "MPI_BOTTOM", + "fort": "MPI_BOTTOM", + }, "MPI_PROC_NULL": { "c": "MPI_PROC_NULL", "fort": "MPI_PROC_NULL", From c6e8d7729eacb66737ef625fed7c08c7180fe62d Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 30 Mar 2026 10:08:36 +0200 Subject: [PATCH 073/125] Fix a flaky test failure --- Dynamic/Hooks.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dynamic/Hooks.cpp b/Dynamic/Hooks.cpp index 0f6d170..9a9023f 100644 --- a/Dynamic/Hooks.cpp +++ b/Dynamic/Hooks.cpp @@ -95,9 +95,8 @@ ffi_type* getFFIType(int32_t size) { case 0: return &ffi_type_void; case 16: return &ffi_type_uint16; case 32: return &ffi_type_uint32; - case 64: return &ffi_type_pointer; + default: return &ffi_type_pointer; } - __builtin_unreachable(); } extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(bool isRef, void* function, int32_t ret_size, int32_t num_params, ...) { From dd8958dc1060c8cfc91a00e91811ba2c0e207485 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 31 Mar 2026 15:33:09 +0200 Subject: [PATCH 074/125] Fix output --- Passes/ContractManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index ed8bc33..20afe70 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -181,7 +181,7 @@ void ContractManagerAnalysis::extractFromFunction(Module& M) { // Check for Fortran mangling stuff if (!result) { - errs() << "Could not decipher call to Declare_Value for \n" << CallStr << "!\n"; + errs() << "Could not decipher call to Declare_Value for " << CallStr << "!\n"; } else { if (result->getName().starts_with("_QQ") && isa(result)) { StringRef result_stem = result->getName().split('.').first; From afcd048f22fe6b1deea7b3badc3c5c9021ff4b9b Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 1 Apr 2026 13:38:28 +0200 Subject: [PATCH 075/125] Fix pointer equality comparison --- Passes/ContractVerifierParam.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index d5711d0..a80ef8e 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -215,6 +215,12 @@ Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, Call return Fulfillment::BROKEN; } return Fulfillment::FULFILLED; + case Comparator::EQ: + if (!ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { + ErrInfo = "Parameter does not match required pointer value!"; + return Fulfillment::BROKEN; + } + return Fulfillment::FULFILLED; case Comparator::EXEQ: if (ContractPassUtility::checkParamMatch(callVal, var, ParamAccess::NORMAL, MAM)) { ErrInfo = "Note: Parameter matches or is alias to exception value."; From 86b1fa96e8bf940574baaf8859c2a9f490cc6fd3 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 1 Apr 2026 15:17:53 +0200 Subject: [PATCH 076/125] Fix FP --- Passes/ContractVerifierParam.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index a80ef8e..2a7625c 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -159,7 +159,14 @@ Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, Call } } } - // Always prefer constint comparisons. For Fortran, this sometimes requires a lil trickery: + // Always prefer constint comparisons. + // For C, check if its a constint inttoptr + if (ConstantExpr* CE = dyn_cast(callVal)) { + if (isa(CE->getAsInstruction())) { + callVal = CE->getOperand(0); + } + } + // For Fortran, this sometimes requires a lil trickery: // First, try to get at the actual value instead of the weird pointer that is passed as the arg in IR if (Instruction* I = dyn_cast(callVal)) { MemoryDependenceResults& MDR = MAM->getResult(*I->getModule()).getManager().getResult(*I->getFunction()); From ba999689d4f84b10c4b6a3317489d8563134900c Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 2 Apr 2026 13:56:05 +0200 Subject: [PATCH 077/125] Fix FN, oops --- Scripts/gen_mpi_contr_h.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 5ece0d6..cdbfe4f 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -348,8 +348,8 @@ def add_contract(func: str, scope: str, contr: str): for func, idx in paramerror_null: add_contract(func, "PRE", f"param!({idx}:!=NULL,!=MPI_IN_PLACE,!=MPI_BOTTOM) MSG \"Parameter is null, MPI_IN_PLACE, or MPI_BOTTOM\"") -# Allow MPI_IN_PLACE for recv buffer of sendrecv, but dont allow same as send buf -add_contract("MPI_Sendrecv", "PRE", f"param!(5:^=MPI_IN_PLACE,!=NULL,!=0 _arg) MSG \"Buffer is null or same as send buffer\"") +# For sendrecv also dont allow same as send buf in recv buf +add_contract("MPI_Sendrecv", "PRE", f"param!(5:!=NULL,!=MPI_IN_PLACE,!=MPI_BOTTOM,!=0 _arg) MSG \"Buffer is null or same as send buffer\"") # Comm buffer should be allocated paramerror_null = [ @@ -358,15 +358,13 @@ def add_contract(func: str, scope: str, contr: str): ("MPI_Recv", 0), ("MPI_Irecv", 0), ("MPI_Sendrecv", 0), + ("MPI_Sendrecv", 5), ("MPI_Get", 0), ("MPI_Put", 0), ] for func, idx in paramerror_null: add_contract(func, "PRE", f"alloc!({idx}) MSG \"Buffer is not allocated\"") -# Allow MPI_IN_PLACE for recv buffer if its set to MPI_IN_PLACE -add_contract("MPI_Sendrecv", "PRE", f"( param!(5:==MPI_IN_PLACE) | alloc!(5) ) MSG \"SendRecv is not allocated and not MPI_IN_PLACE\"") - allocators = [ ("MPI_Win_allocate", 4), ] From b52038146d7a394f3e9b29b672c443efce338ec6 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 7 Apr 2026 09:47:35 +0200 Subject: [PATCH 078/125] Add missing alloc size for MPI_Win_allocate --- Scripts/gen_mpi_contr_h.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index cdbfe4f..cbc0d5b 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -369,7 +369,7 @@ def add_contract(func: str, scope: str, contr: str): ("MPI_Win_allocate", 4), ] for func, idx in allocators: - add_contract(func, "POST", f"alloc!(*{idx})") + add_contract(func, "POST", f"alloc!(*{idx}[0])") # Datatype should not be null when doing communication From b442802e0300f18835396a75b685b772ec23f88f Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 7 Apr 2026 10:01:59 +0200 Subject: [PATCH 079/125] Improve contract lang syntax --- Grammars/ContractLexer.g4 | 4 +++- Grammars/ContractParser.g4 | 2 +- LangCode/ContractDataVisitor.cpp | 9 +++++---- Scripts/gen_mpi_contr_h.py | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Grammars/ContractLexer.g4 b/Grammars/ContractLexer.g4 index 061c775..d574e19 100644 --- a/Grammars/ContractLexer.g4 +++ b/Grammars/ContractLexer.g4 @@ -14,7 +14,7 @@ ScopePrefix: '{'; ScopePostfix: '}'; String: '"' ([A-Z] | [a-z] | ' ' | '_' | '-' | '!' | '?' | ',' | [0-9])+ '"'; -Variable: ([A-Z] | [a-z]) ([A-Z] | [a-z] | [0-9] | '_')*; +Variable: ([A-Z] | [a-z]) ([A-Z] | [a-z] | [0-9] | '_')+; NatNum: ('0' | [1-9] [0-9]*); ListSep: ','; @@ -28,6 +28,8 @@ TagParam: '$'; Deref: '*'; AddrOf: '&'; +RetSym: 'R'; + MarkArg: '_arg'; // All ops must end with '!' to differentiate from variables diff --git a/Grammars/ContractParser.g4 b/Grammars/ContractParser.g4 index 3faee46..d38b371 100644 --- a/Grammars/ContractParser.g4 +++ b/Grammars/ContractParser.g4 @@ -22,7 +22,7 @@ multExpr: Deref mathExpr; mathOp: multExpr; mathExpr: natExpr mathOp?; -rwOp: (OPRead | OPWrite | OPAlloc | OPFree) OPPrefix (Deref | AddrOf)? arg_index=NatNum (RWOffsetPrefix alloc_size=mathExpr RWOffsetSuffix)? OPPostfix; +rwOp: (OPRead | OPWrite | OPAlloc | OPFree) OPPrefix (Deref | AddrOf)? arg_index=(NatNum | RetSym) (RWOffsetPrefix alloc_size=mathExpr RWOffsetSuffix)? OPPostfix; varMap: (callP=NatNum | TagParam) MapSep (Deref | AddrOf)? contrP=NatNum; callOp: (OPCall | OPCallTag) OPPrefix Variable (ListSep varMap)* OPPostfix; paramOp: OPParam OPPrefix NatNum MapSep paramReq (ListSep paramReq)* OPPostfix; diff --git a/LangCode/ContractDataVisitor.cpp b/LangCode/ContractDataVisitor.cpp index e10a4d0..907de0b 100644 --- a/LangCode/ContractDataVisitor.cpp +++ b/LangCode/ContractDataVisitor.cpp @@ -87,15 +87,16 @@ std::any ContractDataVisitor::visitRwOp(ContractParser::RwOpContext *ctx) { ParamAccess acc = ParamAccess::NORMAL; if (ctx->Deref()) acc = ParamAccess::DEREF; if (ctx->AddrOf()) acc = ParamAccess::ADDROF; + int idx = ctx->RetSym() ? 99 : std::stoi(ctx->arg_index->getText()); std::shared_ptr op; if (ctx->OPRead()) - op = std::make_shared(std::stoi(ctx->arg_index->getText()), acc); + op = std::make_shared(idx, acc); else if (ctx->OPWrite()) - op = std::make_shared(std::stoi(ctx->arg_index->getText()), acc); + op = std::make_shared(idx, acc); else if (ctx->OPAlloc()) - op = std::make_shared(std::stoi(ctx->arg_index->getText()), acc, ctx->alloc_size ? std::any_cast>(visitMathExpr(ctx->alloc_size)) : std::make_shared(0, false, MathType::UNARY_VALUE)); + op = std::make_shared(idx, acc, ctx->alloc_size ? std::any_cast>(visitMathExpr(ctx->alloc_size)) : std::make_shared(0, false, MathType::UNARY_VALUE)); else if (ctx->OPFree()) - op = std::make_shared(std::stoi(ctx->arg_index->getText()), acc); + op = std::make_shared(idx, acc); return op; } std::any ContractDataVisitor::visitParamOp(ContractParser::ParamOpContext *ctx) { diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index cbc0d5b..c802b21 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -484,8 +484,8 @@ def get_param_values(lang: str) -> str: void __attribute__((weak)) CoVer_RegisterGlobal(void* ptr, int64_t size) CONTRACT( POST {{ alloc!(0[ 1 _arg ]) }}) {{}}; -void* calloc(size_t num, size_t size) CONTRACT( POST {{ alloc!(99[ 0 _arg * 1 _arg]) }}); -void* malloc(size_t size) CONTRACT( POST {{ alloc!(99[0 _arg]) }}); +void* calloc(size_t num, size_t size) CONTRACT( POST {{ alloc!(R[ 0 _arg * 1 _arg]) }}); +void* malloc(size_t size) CONTRACT( POST {{ alloc!(R[0 _arg]) }}); void free(void*) CONTRACT( POST {{ free!(0) }}); From 76eb34835621403c64ccc34ab8af062c0b362576 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 7 Apr 2026 15:45:03 +0200 Subject: [PATCH 080/125] Workaround for older python vers --- Scripts/gen_mpi_contr_h.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index c802b21..d09c1a5 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -527,8 +527,8 @@ def get_param_values(lang: str) -> str: call Declare_Contract(CoVer_RegisterGlobal, \"POST { alloc!(0) }\") """ -header_output_fort = boilerplate_header_fort + f" use mpi\n implicit none\n\n{fortran_lang_intrinsics}\n\n{get_param_values("fort")}\n\n" -header_output_fort_f08 = boilerplate_header_fort + f" use mpi_f08\n implicit none\n\n{fortran_lang_intrinsics}\n\n{get_param_values("fort")}\n\n" +header_output_fort = boilerplate_header_fort + f" use mpi\n implicit none\n\n{fortran_lang_intrinsics}\n\n{get_param_values('fort')}\n\n" +header_output_fort_f08 = boilerplate_header_fort + f" use mpi_f08\n implicit none\n\n{fortran_lang_intrinsics}\n\n{get_param_values('fort')}\n\n" header_output_fort_f08ts = header_output_fort_f08 exclude_fortran = [ From ddb62ca06db9aa76965750196880546011b1adc8 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 8 Apr 2026 14:39:11 +0200 Subject: [PATCH 081/125] Hardcode intrinsics path to lib --- Intrinsics/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Intrinsics/CMakeLists.txt b/Intrinsics/CMakeLists.txt index 3381b3b..1e93814 100644 --- a/Intrinsics/CMakeLists.txt +++ b/Intrinsics/CMakeLists.txt @@ -14,4 +14,4 @@ set_property(TARGET CoVerIntrinsics PROPERTY VISIBILITY_INLINES_HIDDEN ON) target_compile_options(CoVerIntrinsics PUBLIC -fno-rtti -fno-exceptions -fomit-frame-pointer -fno-stack-protector) set_property(TARGET CoVerIntrinsics PROPERTY POSITION_INDEPENDENT_CODE ON) -install(TARGETS CoVerIntrinsics) +install(TARGETS CoVerIntrinsics DESTINATION lib) From a705deb2cc8f6c47205b19ee0249fea067b55540 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 8 Apr 2026 15:20:11 +0200 Subject: [PATCH 082/125] Add "missing" include --- Include/Contracts.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Include/Contracts.h b/Include/Contracts.h index 3a25938..75ccc1b 100644 --- a/Include/Contracts.h +++ b/Include/Contracts.h @@ -2,6 +2,7 @@ #include #include +#include // Convenience Annotation Macros // Example: int f() CONTRACT( ); From 3078313eaebdfccc980051d46fad461ccfcb3ee3 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 9 Apr 2026 15:57:52 +0200 Subject: [PATCH 083/125] Fix instrumentation return value --- Passes/Instrument.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index fc1a8c1..0dde8e5 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -608,7 +608,16 @@ void InstrumentPass::insertCBIfNeeded(FunctionCallee FC, std::vector pa params.insert(params.begin(), Basic_Types.getBool(isRelevant(I))); CallInst* callbackCI = CallInst::Create(FC, params); callbackCI->setDebugLoc(I->getDebugLoc()); - if (isa(I)) ReplaceInstWithInst(I, callbackCI); + if (CallBase* CB = dyn_cast(I)) { + Type* OrigRT = CB->getCalledFunction()->getReturnType(); + if (OrigRT->isIntegerTy()) { + CastInst* CI = CastInst::Create(Instruction::PtrToInt, callbackCI, OrigRT, ""); + callbackCI->insertBefore(I->getIterator()); + ReplaceInstWithInst(I, CI); + } else { + ReplaceInstWithInst(I, callbackCI); + } + } else callbackCI->insertBefore(I->getIterator()); } From 10c3a6c475bd0125c4d3241b986fe575e794daa9 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 9 Apr 2026 16:15:41 +0200 Subject: [PATCH 084/125] Improved error reporting --- Passes/ContractVerifierParam.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index 2a7625c..a730dec 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -236,7 +236,8 @@ Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, Call // Not an exception. Continue analysis, so far no info gained continue; default: - errs() << "Attempt to compare pointers! Not performing parameter analysis\n"; + errs() << "Attempt to compare pointers! Not performing parameter analysis at " + << ContractPassUtility::getInstrLocStr(call) << " for index " << idx << "\n"; return Fulfillment::UNKNOWN; } } From 3c10edc26a5e6916beafb5c1eef44f9fbce3cf3c Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 9 Apr 2026 16:15:57 +0200 Subject: [PATCH 085/125] Fix instrumentation of ret for floats as well --- Passes/Instrument.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 0dde8e5..2a30b4f 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -610,10 +610,16 @@ void InstrumentPass::insertCBIfNeeded(FunctionCallee FC, std::vector pa callbackCI->setDebugLoc(I->getDebugLoc()); if (CallBase* CB = dyn_cast(I)) { Type* OrigRT = CB->getCalledFunction()->getReturnType(); - if (OrigRT->isIntegerTy()) { - CastInst* CI = CastInst::Create(Instruction::PtrToInt, callbackCI, OrigRT, ""); + if (!OrigRT->isPointerTy() && !OrigRT->isVoidTy()) { callbackCI->insertBefore(I->getIterator()); - ReplaceInstWithInst(I, CI); + if (OrigRT->isIntegerTy()) { + CastInst* CI = CastInst::Create(Instruction::PtrToInt, callbackCI, OrigRT); + ReplaceInstWithInst(I, CI); + } else if (OrigRT->isFloatingPointTy()) { + CastInst* CI = CastInst::Create(Instruction::PtrToInt, callbackCI, Basic_Types.Int64_Type, "", I->getIterator()); + CI = CastInst::Create(Instruction::BitCast, CI, OrigRT); + ReplaceInstWithInst(I, CI); + } } else { ReplaceInstWithInst(I, callbackCI); } From 533c1d38c2c8d354de83515d339c86c8d2ed0d8b Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 9 Apr 2026 16:37:04 +0200 Subject: [PATCH 086/125] Reorder opt passes --- Scripts/clangContracts.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Scripts/clangContracts.cpp b/Scripts/clangContracts.cpp index 46b373e..494553e 100644 --- a/Scripts/clangContracts.cpp +++ b/Scripts/clangContracts.cpp @@ -286,15 +286,15 @@ int main(int argc, const char** argv) { // Call LLVM passes std::string passlist = "function(sroa),instrumentIntrinsics,contractVerifierPreCall,contractVerifierPostCall,contractVerifierRelease,contractVerifierParam,contractVerifierAlloc,contractPostProcess"; - if (!opt_level.empty()) { - passlist += ",default<" + opt_level.substr(1) + ">"; // opt_level substr cuts "-" from "-O" - } if (!InstrumentContracts.empty()) { // Need instrumentation, so add instr pass... passlist += ",instrumentContracts"; // ...and link against analyser. Need to hackily link against stdlib as well for C code rem_args.first += " -Wl,--whole-archive @COVER_DYNAMIC_ANALYSER_PATH@ -Wl,-no-whole-archive -lstdc++"; } + if (!opt_level.empty()) { + passlist += ",default<" + opt_level.substr(1) + ">"; // opt_level substr cuts "-" from "-O" + } execSafe("opt --load-pass-plugin=\"@DSA_PLUGIN_PATH@\" --load-pass-plugin \"@CONTR_PLUGIN_PATH@\" -passes='" + passlist + "' " + opt_flags + " " + tmpfile + " -o " + tmpfile + ".opt"); close(fd); From 415fd73fe740ca510f1d80be110bd062567e70c3 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 9 Apr 2026 16:49:41 +0200 Subject: [PATCH 087/125] Fix broken contract --- Scripts/gen_mpi_contr_h.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index d09c1a5..9970631 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -369,7 +369,7 @@ def add_contract(func: str, scope: str, contr: str): ("MPI_Win_allocate", 4), ] for func, idx in allocators: - add_contract(func, "POST", f"alloc!(*{idx}[0])") + add_contract(func, "POST", f"alloc!(*{idx}[0 _arg])") # Datatype should not be null when doing communication From 3c62b6e52be2621069a1c0d383d061c46c863502 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 9 Apr 2026 17:15:38 +0200 Subject: [PATCH 088/125] Remove manual llc step --- Dynamic/CMakeLists.txt | 1 + Scripts/clangContracts.cpp | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dynamic/CMakeLists.txt b/Dynamic/CMakeLists.txt index afbc759..363080b 100644 --- a/Dynamic/CMakeLists.txt +++ b/Dynamic/CMakeLists.txt @@ -14,6 +14,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") else() set_property(TARGET CoVerDynamicAnalyzer PROPERTY UNITY_BUILD ON) endif() +set_property(TARGET CoVerDynamicAnalyzer PROPERTY INTERPROCEDURAL_OPTIMIZATION ON) target_include_directories(CoVerDynamicAnalyzer PUBLIC ../Include/) find_package(PkgConfig REQUIRED) diff --git a/Scripts/clangContracts.cpp b/Scripts/clangContracts.cpp index 494553e..42bf16e 100644 --- a/Scripts/clangContracts.cpp +++ b/Scripts/clangContracts.cpp @@ -299,7 +299,6 @@ int main(int argc, const char** argv) { close(fd); // Finalize executable - execSafe("llc -filetype=obj --relocation-model=pic " + opt_level + " " + tmpfile + ".opt -o " + tmpfile + ".opt.o"); - execSafe(WrapTarget + " -fPIC -lm -ldl -lffi -lpthread -g -I\"@CONTR_INCLUDE_PATH@\"" + rem_args.first + " " + tmpfile + ".opt.o @COVER_INTRINSICS_LIB_PATH@ " + dest_arg); + execSafe(WrapTarget + " -fPIC -lm -ldl -lffi -lpthread -g -I\"@CONTR_INCLUDE_PATH@\"" + rem_args.first + " " + tmpfile + ".opt @COVER_INTRINSICS_LIB_PATH@ " + dest_arg); return 0; } From 31157f2df044f2b403a0827a791108abfa089bcf Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 9 Apr 2026 17:20:04 +0200 Subject: [PATCH 089/125] Try LTO for DynLib only --- Scripts/clangContracts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/clangContracts.cpp b/Scripts/clangContracts.cpp index 42bf16e..c2e50c4 100644 --- a/Scripts/clangContracts.cpp +++ b/Scripts/clangContracts.cpp @@ -299,6 +299,6 @@ int main(int argc, const char** argv) { close(fd); // Finalize executable - execSafe(WrapTarget + " -fPIC -lm -ldl -lffi -lpthread -g -I\"@CONTR_INCLUDE_PATH@\"" + rem_args.first + " " + tmpfile + ".opt @COVER_INTRINSICS_LIB_PATH@ " + dest_arg); + execSafe(WrapTarget + " -fPIC -lm -ldl -lffi -flto -lpthread -g -I\"@CONTR_INCLUDE_PATH@\"" + rem_args.first + " " + tmpfile + ".opt @COVER_INTRINSICS_LIB_PATH@ " + dest_arg); return 0; } From e801d4c2b1f6ea8df12b640d4f231f3f7d05064d Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 10 Apr 2026 09:21:03 +0200 Subject: [PATCH 090/125] Remove lto again --- Dynamic/CMakeLists.txt | 1 - Scripts/clangContracts.cpp | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dynamic/CMakeLists.txt b/Dynamic/CMakeLists.txt index 363080b..afbc759 100644 --- a/Dynamic/CMakeLists.txt +++ b/Dynamic/CMakeLists.txt @@ -14,7 +14,6 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug") else() set_property(TARGET CoVerDynamicAnalyzer PROPERTY UNITY_BUILD ON) endif() -set_property(TARGET CoVerDynamicAnalyzer PROPERTY INTERPROCEDURAL_OPTIMIZATION ON) target_include_directories(CoVerDynamicAnalyzer PUBLIC ../Include/) find_package(PkgConfig REQUIRED) diff --git a/Scripts/clangContracts.cpp b/Scripts/clangContracts.cpp index c2e50c4..494553e 100644 --- a/Scripts/clangContracts.cpp +++ b/Scripts/clangContracts.cpp @@ -299,6 +299,7 @@ int main(int argc, const char** argv) { close(fd); // Finalize executable - execSafe(WrapTarget + " -fPIC -lm -ldl -lffi -flto -lpthread -g -I\"@CONTR_INCLUDE_PATH@\"" + rem_args.first + " " + tmpfile + ".opt @COVER_INTRINSICS_LIB_PATH@ " + dest_arg); + execSafe("llc -filetype=obj --relocation-model=pic " + opt_level + " " + tmpfile + ".opt -o " + tmpfile + ".opt.o"); + execSafe(WrapTarget + " -fPIC -lm -ldl -lffi -lpthread -g -I\"@CONTR_INCLUDE_PATH@\"" + rem_args.first + " " + tmpfile + ".opt.o @COVER_INTRINSICS_LIB_PATH@ " + dest_arg); return 0; } From 7dbff6e2e4b1d7133f60323e4e7480c80cb0772f Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 10 Apr 2026 09:22:55 +0200 Subject: [PATCH 091/125] Cache ffi cifs --- Dynamic/Hooks.cpp | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/Dynamic/Hooks.cpp b/Dynamic/Hooks.cpp index 9a9023f..8b56e98 100644 --- a/Dynamic/Hooks.cpp +++ b/Dynamic/Hooks.cpp @@ -99,6 +99,13 @@ ffi_type* getFFIType(int32_t size) { } } +struct cifCache { + void* func; + std::vector arg_types; + ffi_cif cif; +}; +static std::vector cached_cifs; + extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(bool isRef, void* function, int32_t ret_size, int32_t num_params, ...) { CallsiteInfo callsite = { .location = __builtin_return_address(0) }; callsite.params.reserve(num_params); @@ -106,12 +113,21 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b static std::vector ffi_arg_types; static std::vector ffi_arg_values_ptr; static std::vector ffi_arg_values_store; - ffi_arg_types.reserve(num_params); - ffi_arg_types.clear(); + + cifCache cif_c = {nullptr }; + for (cifCache& entry : cached_cifs) { + if (function == entry.func) { + cif_c = entry; + break; + } + } ffi_arg_values_store.reserve(num_params); ffi_arg_values_store.clear(); ffi_arg_values_ptr.reserve(num_params); ffi_arg_values_ptr.clear(); + if (!cif_c.func) { + cif_c.arg_types.reserve(num_params); + } va_start(list, num_params); for (int i = 0; i < num_params; i++) { @@ -123,7 +139,9 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b } else { callsite.params.push_back({param_val, param_size & 0xFF}); } - ffi_arg_types.push_back(getFFIType((param_size & 0xFF00) >> 8)); + if (!cif_c.func) { + cif_c.arg_types.push_back(getFFIType((param_size & 0xFF00) >> 8)); + } ffi_arg_values_store.push_back(param_val); ffi_arg_values_ptr.push_back(&ffi_arg_values_store[i]); } @@ -133,13 +151,14 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b HANDLE_CALLBACK(analyses_with_funcPreCB, onFunctionCall, function, true, callsite); // Call the intercepted function - ffi_cif cif; - void* res = nullptr; - if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, num_params, getFFIType(ret_size), ffi_arg_types.data()) == FFI_OK) { - ffi_call(&cif, FFI_FN(function), &res, ffi_arg_values_ptr.data()); - } else { - DynamicUtils::out() << "CRITICAL: Failed to prepare libffi CIF!\n"; + if (!cif_c.func) { + cif_c.func = function; + ffi_prep_cif(&cif_c.cif, FFI_DEFAULT_ABI, num_params, getFFIType(ret_size), cif_c.arg_types.data()); + cached_cifs.push_back(cif_c); } + cif_c.cif.arg_types = cif_c.arg_types.data(); + void* res = nullptr; + ffi_call(&cif_c.cif, FFI_FN(function), &res, ffi_arg_values_ptr.data()); // Run event handlers again for the postCBs, including the newly returned value callsite.retval = res; From 1fcc58523b1ed61f4877f695cae6654b434fefc2 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 10 Apr 2026 10:15:04 +0200 Subject: [PATCH 092/125] Fix ret size calc --- Passes/Instrument.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 2a30b4f..918ccbb 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -528,11 +528,7 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { params.push_back(callsite->getCalledOperand()); // First param is funcptr // Get return value size - if (isC && callsite->getType()->isSized()) { - params.push_back(Basic_Types.getInt(callsite->getDataLayout().getTypeStoreSizeInBits(callsite->getType()))); - } else { - params.push_back(Basic_Types.getInt(0)); - } + params.push_back(callsite->getType()->isSized() ? Basic_Types.getInt(callsite->getDataLayout().getTypeStoreSizeInBits(callsite->getType())) : Basic_Types.getInt(0)); params.push_back(Basic_Types.getInt(callsite->arg_size())); for (Use const& U : callsite->args()) { From b860dd61ad211a8b093a81dcbcbe0628dbbabc60 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 10 Apr 2026 10:34:30 +0200 Subject: [PATCH 093/125] Actually fix ret type --- Dynamic/Hooks.cpp | 14 ++++++++------ Passes/Instrument.cpp | 8 +++++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Dynamic/Hooks.cpp b/Dynamic/Hooks.cpp index 8b56e98..88806c0 100644 --- a/Dynamic/Hooks.cpp +++ b/Dynamic/Hooks.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -90,12 +91,12 @@ extern "C" void __attribute__((visibility("default"))) PPDCV_Initialize(int32_t* DynamicUtils::createMessage("Finished Initializing!"); } -ffi_type* getFFIType(int32_t size) { +ffi_type* getFFIType(int32_t size, bool isFloat) { switch (size) { case 0: return &ffi_type_void; case 16: return &ffi_type_uint16; - case 32: return &ffi_type_uint32; - default: return &ffi_type_pointer; + case 32: return isFloat ? &ffi_type_float : &ffi_type_uint32; + default: return isFloat ? &ffi_type_double : &ffi_type_pointer; } } @@ -133,14 +134,15 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b for (int i = 0; i < num_params; i++) { uint32_t param_size = va_arg(list,uint32_t); void* param_val = va_arg(list,void*); - if (param_size >> 16) { + std::bitset<8> flags(param_size >> 16); + if (flags.test(0)) { // Need to deref value first callsite.params.push_back({*(void**)param_val, param_size & 0xFF}); } else { callsite.params.push_back({param_val, param_size & 0xFF}); } if (!cif_c.func) { - cif_c.arg_types.push_back(getFFIType((param_size & 0xFF00) >> 8)); + cif_c.arg_types.push_back(getFFIType(param_size >> 8, flags.test(1))); } ffi_arg_values_store.push_back(param_val); ffi_arg_values_ptr.push_back(&ffi_arg_values_store[i]); @@ -153,7 +155,7 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b // Call the intercepted function if (!cif_c.func) { cif_c.func = function; - ffi_prep_cif(&cif_c.cif, FFI_DEFAULT_ABI, num_params, getFFIType(ret_size), cif_c.arg_types.data()); + ffi_prep_cif(&cif_c.cif, FFI_DEFAULT_ABI, num_params, ret_size == 64 ? &ffi_type_double : getFFIType(ret_size, ret_size >> 16), cif_c.arg_types.data()); cached_cifs.push_back(cif_c); } cif_c.cif.arg_types = cif_c.arg_types.data(); diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 918ccbb..eb7cc44 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -528,7 +528,13 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { params.push_back(callsite->getCalledOperand()); // First param is funcptr // Get return value size - params.push_back(callsite->getType()->isSized() ? Basic_Types.getInt(callsite->getDataLayout().getTypeStoreSizeInBits(callsite->getType())) : Basic_Types.getInt(0)); + if (!callsite->getType()->isSized()) { + params.push_back(Basic_Types.getInt(0)); + } else { + int ret_size = callsite->getDataLayout().getTypeStoreSizeInBits(callsite->getType()); + if (callsite->getType()->isFloatingPointTy()) ret_size |= 2 << 16; // Set bit 2 > float tag + params.push_back(Basic_Types.getInt(ret_size)); + } params.push_back(Basic_Types.getInt(callsite->arg_size())); for (Use const& U : callsite->args()) { From 29d32e9a8391f557f3b16a8309215cf537e0edad Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 10 Apr 2026 16:22:21 +0200 Subject: [PATCH 094/125] Test reinvert instr --- Scripts/clangContracts.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Scripts/clangContracts.cpp b/Scripts/clangContracts.cpp index 494553e..46b373e 100644 --- a/Scripts/clangContracts.cpp +++ b/Scripts/clangContracts.cpp @@ -286,15 +286,15 @@ int main(int argc, const char** argv) { // Call LLVM passes std::string passlist = "function(sroa),instrumentIntrinsics,contractVerifierPreCall,contractVerifierPostCall,contractVerifierRelease,contractVerifierParam,contractVerifierAlloc,contractPostProcess"; + if (!opt_level.empty()) { + passlist += ",default<" + opt_level.substr(1) + ">"; // opt_level substr cuts "-" from "-O" + } if (!InstrumentContracts.empty()) { // Need instrumentation, so add instr pass... passlist += ",instrumentContracts"; // ...and link against analyser. Need to hackily link against stdlib as well for C code rem_args.first += " -Wl,--whole-archive @COVER_DYNAMIC_ANALYSER_PATH@ -Wl,-no-whole-archive -lstdc++"; } - if (!opt_level.empty()) { - passlist += ",default<" + opt_level.substr(1) + ">"; // opt_level substr cuts "-" from "-O" - } execSafe("opt --load-pass-plugin=\"@DSA_PLUGIN_PATH@\" --load-pass-plugin \"@CONTR_PLUGIN_PATH@\" -passes='" + passlist + "' " + opt_flags + " " + tmpfile + " -o " + tmpfile + ".opt"); close(fd); From 9f3dd166429fdf1525fe6baa92c7ea5e2298c250 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Fri, 10 Apr 2026 16:51:49 +0200 Subject: [PATCH 095/125] Move heuristic from param to passutils --- Include/ContractPassUtility.hpp | 5 +++++ Passes/ContractVerifierParam.cpp | 12 +++--------- Utils/ContractPassUtility.cpp | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Include/ContractPassUtility.hpp b/Include/ContractPassUtility.hpp index c452a65..49b156d 100644 --- a/Include/ContractPassUtility.hpp +++ b/Include/ContractPassUtility.hpp @@ -79,6 +79,11 @@ namespace ContractPassUtility { * Fortran Heuristic: Check if global, if so check if constint and return */ ConstantInt* fortCheckAndGetGlbInt(Value* V); + + /* + * Get last storeinst to a call argument, null if it could not be determined + */ + StoreInst* getLastStore(CallBase* CB, int idx, Instruction* loc, FunctionAnalysisManager* FAM); }; template diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index a730dec..d25fe8e 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -169,15 +169,9 @@ Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, Call // For Fortran, this sometimes requires a lil trickery: // First, try to get at the actual value instead of the weird pointer that is passed as the arg in IR if (Instruction* I = dyn_cast(callVal)) { - MemoryDependenceResults& MDR = MAM->getResult(*I->getModule()).getManager().getResult(*I->getFunction()); - MemoryLocation Loc = MemoryLocation::getForArgument(call, idx, MAM->getResult(*I->getModule()).getManager().getResult(*call->getFunction())); - MemDepResult x = MDR.getPointerDependencyFrom(Loc, true, call->getIterator(), call->getParent()); - if (x.getInst()) { - if (StoreInst* S = dyn_cast(x.getInst())) { - if (isa(S->getValueOperand())) { - callVal = S->getValueOperand(); - } - } + StoreInst* SI = ContractPassUtility::getLastStore(call, idx, I, &MAM->getResult(*I->getModule()).getManager()); + if (SI && isa(SI->getValueOperand())) { + callVal = SI->getValueOperand(); } } // Next, for some global vals its just a struct with one constint member, resolve that as well (for both param val and call val) diff --git a/Utils/ContractPassUtility.cpp b/Utils/ContractPassUtility.cpp index f22987c..c6b243a 100644 --- a/Utils/ContractPassUtility.cpp +++ b/Utils/ContractPassUtility.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include @@ -17,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +52,18 @@ const Value* ContractPassUtility::betterGetPointerOperand(const Value* V) { return b; } +StoreInst* ContractPassUtility::getLastStore(CallBase* CB, int idx, Instruction* loc, FunctionAnalysisManager* FAM) { + MemoryDependenceResults& MDR = FAM->getResult(*loc->getFunction()); + MemoryLocation Loc = MemoryLocation::getForArgument(CB, idx, FAM->getResult(*CB->getFunction())); + MemDepResult x = MDR.getPointerDependencyFrom(Loc, true, CB->getIterator(), CB->getParent()); + if (x.getInst()) { + if (StoreInst* S = dyn_cast(x.getInst())) { + return S; + } + } + return nullptr; +} + std::map getFunctionParentInstrCandidates(const Value* Ip) { if (!isa(Ip)) return {}; std::set> candidates = {{dyn_cast(Ip), 0}}; From a4ce342430553fb775154548cace7ea1fcd5c5fb Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 09:58:38 +0200 Subject: [PATCH 096/125] Use own, lighter-weight mem analysis for getLastStore --- Include/ContractPassUtility.hpp | 2 +- Passes/ContractVerifierParam.cpp | 2 +- Passes/Instrument.cpp | 20 ++++++++++++++++---- Passes/Instrument.hpp | 3 ++- Utils/ContractPassUtility.cpp | 14 +++++++------- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Include/ContractPassUtility.hpp b/Include/ContractPassUtility.hpp index 49b156d..231c6c6 100644 --- a/Include/ContractPassUtility.hpp +++ b/Include/ContractPassUtility.hpp @@ -83,7 +83,7 @@ namespace ContractPassUtility { /* * Get last storeinst to a call argument, null if it could not be determined */ - StoreInst* getLastStore(CallBase* CB, int idx, Instruction* loc, FunctionAnalysisManager* FAM); + StoreInst* getLastStore(CallBase* CB, int idx, FunctionAnalysisManager* FAM); }; template diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index d25fe8e..03aa87c 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -169,7 +169,7 @@ Fulfillment ContractVerifierParamPass::checkParamReq(std::set vars, Call // For Fortran, this sometimes requires a lil trickery: // First, try to get at the actual value instead of the weird pointer that is passed as the arg in IR if (Instruction* I = dyn_cast(callVal)) { - StoreInst* SI = ContractPassUtility::getLastStore(call, idx, I, &MAM->getResult(*I->getModule()).getManager()); + StoreInst* SI = ContractPassUtility::getLastStore(call, idx, &MAM->getResult(*I->getModule()).getManager()); if (SI && isa(SI->getValueOperand())) { callVal = SI->getValueOperand(); } diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index eb7cc44..5c0056c 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -50,8 +50,9 @@ static cl::opt ClInstrumentType( PreservedAnalyses InstrumentPass::run(Module &M, ModuleAnalysisManager &AM) { - DB = &AM.getResult(M); - Basic_Types = AM.getResult(M); + MAM = &AM; + DB = &MAM->getResult(M); + Basic_Types = MAM->getResult(M); Function* mainF = M.getFunction("main"); if (!mainF) return PreservedAnalyses::all(); // No point @@ -583,7 +584,7 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { if (cur_argno >= callsite->arg_size() - stringnum) { size_act = size_call = callsite->getParent()->getDataLayout().getTypeAllocSize(actual_param->getType()); } else { - if (checkIsStrParam(U)) stringnum++; + if (checkIsStrParam(callsite, cur_argno)) stringnum++; // All parameters are sent as pointers. Need to check exact size using dbg info DIType const* param_type = Dbg->getType()->getTypeArray()[cur_argno + 1]; // Offset by one, first is ret val size_act = param_type->getSizeInBits() == 0 || isa(actual_param) ? 64 : param_type->getSizeInBits(); @@ -637,7 +638,8 @@ bool InstrumentPass::isRelevant(Instruction const* I) const { return false; } -bool InstrumentPass::checkIsStrParam(Value const* V) { +bool InstrumentPass::checkIsStrParam(CallBase* CB, int idx) { + Value const* V = CB->getArgOperand(idx); // We want to check if I is a string param. If so, instrumentation should omit the string size arg // Lowered FIR does not make this easy. // If its a str var, its just some global, then the str size appended as another (fake) param @@ -658,6 +660,16 @@ bool InstrumentPass::checkIsStrParam(Value const* V) { } } + // If optimization is turned it it will just do load from str -> store. + // So first do this, then check if global + StoreInst* SI = ContractPassUtility::getLastStore(CB, idx, &MAM->getResult(*CB->getModule()).getManager()); + if (SI && isa(SI->getValueOperand())) { + LoadInst* LI = dyn_cast(SI->getValueOperand()); + if (isa(LI->getPointerOperand())) { + V = LI->getPointerOperand(); + } + } + // Now, check if its a global string if (GlobalVariable const* GV = dyn_cast(V)) { Constant const* Init = GV->getInitializer(); diff --git a/Passes/Instrument.hpp b/Passes/Instrument.hpp index d4543a6..478764f 100644 --- a/Passes/Instrument.hpp +++ b/Passes/Instrument.hpp @@ -70,7 +70,7 @@ class InstrumentPass : public PassInfoMixin { StructType* ParamReq_Type; // Helpers - bool checkIsStrParam(Value const* I); + bool checkIsStrParam(CallBase* CB, int idx); // Misc bool isC = true; @@ -79,6 +79,7 @@ class InstrumentPass : public PassInfoMixin { std::unordered_set instrument_ignore; ContractManagerAnalysis::ContractDatabase* DB; + ModuleAnalysisManager* MAM; }; } // namespace llvm diff --git a/Utils/ContractPassUtility.cpp b/Utils/ContractPassUtility.cpp index c6b243a..920e20d 100644 --- a/Utils/ContractPassUtility.cpp +++ b/Utils/ContractPassUtility.cpp @@ -52,14 +52,14 @@ const Value* ContractPassUtility::betterGetPointerOperand(const Value* V) { return b; } -StoreInst* ContractPassUtility::getLastStore(CallBase* CB, int idx, Instruction* loc, FunctionAnalysisManager* FAM) { - MemoryDependenceResults& MDR = FAM->getResult(*loc->getFunction()); - MemoryLocation Loc = MemoryLocation::getForArgument(CB, idx, FAM->getResult(*CB->getFunction())); - MemDepResult x = MDR.getPointerDependencyFrom(Loc, true, CB->getIterator(), CB->getParent()); - if (x.getInst()) { - if (StoreInst* S = dyn_cast(x.getInst())) { - return S; +StoreInst* ContractPassUtility::getLastStore(CallBase* CB, int idx, FunctionAnalysisManager* FAM) { + Instruction* cur = CB->getPrevNode(); + while (cur) { + if (isa(cur) && !dyn_cast(cur)->getCalledOperand()->getName().starts_with("PPDCV")) break; + if (StoreInst* SI = dyn_cast(cur)) { + if (SI->getPointerOperand() == CB->getArgOperand(idx)) return SI; } + cur = cur->getPrevNode(); } return nullptr; } From 1fdc1e04d55d3074237ce4915e40d3d0d21daa0b Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 10:29:41 +0200 Subject: [PATCH 097/125] Restore filtering of unused intrinsics --- Passes/ContractManager.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index 20afe70..c607d42 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -122,22 +122,19 @@ void ContractManagerAnalysis::extractFromFunction(Module& M) { CallBase const* ContrCall = dyn_cast(cur); if (ContrCall->getCalledOperand()->getName() == "declare_contract_") { const Function* ContrSup = (Function*)ContrCall->getArgOperand(0); - if (!ContrSup->getName().starts_with("CoVer_")) { - // Check if this function is actually used in the code apart from the contract definition. - // If not, no need to analyse this contract and can safely skip it. - // Exception: CoVer intrinsics (CoVer_*), such as stack var tracking pseudofunc. - if (ContrSup->hasOneUser()) continue; - bool has_callsite = false; - for (const User* U : ContrSup->users() ) { - if (const CallBase* CB = dyn_cast(U)) { - if (CB->getCalledOperand() == ContrSup) { - has_callsite = true; - break; - } + // Check if this function is actually used in the code apart from the contract definition. + // If not, no need to analyse this contract and can safely skip it. + if (ContrSup->hasOneUser()) continue; + bool has_callsite = false; + for (const User* U : ContrSup->users() ) { + if (const CallBase* CB = dyn_cast(U)) { + if (CB->getCalledOperand() == ContrSup) { + has_callsite = true; + break; } } - if (!has_callsite) continue; } + if (!has_callsite) continue; addContract("CONTRACT { " + CallStr + " }", (Function*)(ContrCall->getArgOperand(0))); } else if (ContrCall->getCalledOperand()->getName() == "declare_value_") { #warning really super duper should find out a less hacky way From 716a32d33ccb70bad289303ba7e89b5c2627d431 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 10:54:50 +0200 Subject: [PATCH 098/125] Add debugger flag --- Scripts/clangContracts.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Scripts/clangContracts.cpp b/Scripts/clangContracts.cpp index 46b373e..56595a8 100644 --- a/Scripts/clangContracts.cpp +++ b/Scripts/clangContracts.cpp @@ -49,6 +49,12 @@ static cl::opt GenerateJSONReport("generate-json-report", cl::value_desc("JSON output path"), cl::cat(WrapperCategory)); +static cl::opt DebuggerCoVerPlugin("debugger-cover-plugin", + cl::desc("Run debugger on opt step"), + cl::ValueOptional, + cl::value_desc("Debugger path"), + cl::cat(WrapperCategory)); + // String option with ValueOptional to handle "full", "funconly", and "filtered=path.json" static cl::opt InstrumentContracts("instrument-contracts", cl::desc("Perform instrumentation for runtime analysis.\n" @@ -90,7 +96,11 @@ std::vector link_time_sources; // For predef fort contracts std::string opt_flags = ""; -std::string exec(std::string const& cmd) { +std::string exec(std::string const& cmd, bool interactive = true) { + if (interactive) { + std::system(cmd.c_str()); + return ""; + } std::array buffer; std::string result; FILE* pipe = popen(cmd.c_str(), "r"); @@ -179,7 +189,7 @@ void sanityCheckCompiler() { // Check for LLVM-based compiler cmd = WrapTarget + " --version | head -n 1"; - compiler_ident = exec(cmd); + compiler_ident = exec(cmd, false); if (compiler_ident.find("clang") == std::string::npos && compiler_ident.find("flang") == std::string::npos) { std::cerr << "Unknown compiler \"" << compiler_ident.substr(0, compiler_ident.size()-1) << "\"!\n"; std::cerr << "Make sure to use an LLVM-based compiler that supports outputting bitcode.\n"; @@ -286,6 +296,8 @@ int main(int argc, const char** argv) { // Call LLVM passes std::string passlist = "function(sroa),instrumentIntrinsics,contractVerifierPreCall,contractVerifierPostCall,contractVerifierRelease,contractVerifierParam,contractVerifierAlloc,contractPostProcess"; + // ALWAYS FIRST OPT THEN INSTR! + // Otherwise significant performance loss! if (!opt_level.empty()) { passlist += ",default<" + opt_level.substr(1) + ">"; // opt_level substr cuts "-" from "-O" } @@ -295,7 +307,7 @@ int main(int argc, const char** argv) { // ...and link against analyser. Need to hackily link against stdlib as well for C code rem_args.first += " -Wl,--whole-archive @COVER_DYNAMIC_ANALYSER_PATH@ -Wl,-no-whole-archive -lstdc++"; } - execSafe("opt --load-pass-plugin=\"@DSA_PLUGIN_PATH@\" --load-pass-plugin \"@CONTR_PLUGIN_PATH@\" -passes='" + passlist + "' " + opt_flags + " " + tmpfile + " -o " + tmpfile + ".opt"); + execSafe(DebuggerCoVerPlugin + " opt --load-pass-plugin=\"@DSA_PLUGIN_PATH@\" --load-pass-plugin \"@CONTR_PLUGIN_PATH@\" -passes='" + passlist + "' " + opt_flags + " " + tmpfile + " -o " + tmpfile + ".opt"); close(fd); // Finalize executable From 86d3d5d3c99d15ff5b0221e941561c2be88df069 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 11:05:37 +0200 Subject: [PATCH 099/125] Propagate error code from child in exec --- Scripts/clangContracts.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Scripts/clangContracts.cpp b/Scripts/clangContracts.cpp index 56595a8..b40a985 100644 --- a/Scripts/clangContracts.cpp +++ b/Scripts/clangContracts.cpp @@ -98,7 +98,8 @@ std::string opt_flags = ""; std::string exec(std::string const& cmd, bool interactive = true) { if (interactive) { - std::system(cmd.c_str()); + int rc = std::system(cmd.c_str()); + if (rc) exit(rc); return ""; } std::array buffer; From af19f9fca380059a8c45e394823227f49c33c7cf Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 12:47:10 +0200 Subject: [PATCH 100/125] Fix comment --- Passes/ContractManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index c607d42..ced3b0f 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -109,8 +109,6 @@ void ContractManagerAnalysis::extractFromFunction(Module& M) { // Only care about this intrinsic #warning TODO probably should figure out a less hacky way. if (CB->getCalledFunction()->getName() != "llvm.memmove.p0.p0.i64" && CB->getCalledFunction()->getName() != "llvm.memcpy.p0.p0.i64") continue; - // Add CONTRACT { ... } brace. Its explicitly needed for C(++) to make sure we are not parsing irrelevant stuff, - // but for fortran its already implicit in declare_contract, making it superfluous std::string CallStr = ((ConstantDataArray*)((GlobalVariable*)CB->getArgOperand(1))->getInitializer())->getAsString().str(); // Call is from memmove -> insertvalue -> extractvalue -> funccall. on -O0, and memcpy -> funccall on -O1 and above Instruction const* cur = CB->getNextNode(); @@ -135,6 +133,8 @@ void ContractManagerAnalysis::extractFromFunction(Module& M) { } } if (!has_callsite) continue; + // Add CONTRACT { ... } brace. Its explicitly needed for C(++) to make sure we are not parsing irrelevant annotations, + // but for Fortran its already implicit in declare_contract, making it superfluous addContract("CONTRACT { " + CallStr + " }", (Function*)(ContrCall->getArgOperand(0))); } else if (ContrCall->getCalledOperand()->getName() == "declare_value_") { #warning really super duper should find out a less hacky way From e2f47da9db066890e1f9a237dc5634845252c469 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 13:14:22 +0200 Subject: [PATCH 101/125] Cleanup --- Passes/ContractManager.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index ced3b0f..965b670 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -238,9 +238,7 @@ void ContractManagerAnalysis::addContract(std::string contract, Function* F) { void ContractManagerAnalysis::addValueDefinition(std::string name, Value* val) { if (IS_DEBUG) { - WithColor(errs(), HighlightColor::Remark) << "[ContractManager] IR Value \""; - val->print(WithColor(errs(), HighlightColor::Remark)), - WithColor(errs(), HighlightColor::Remark) << "\" stored for contract value \"" << name << "\"\n"; + WithColor(errs(), HighlightColor::Remark) << "[ContractManager] IR Value " << *val << "\" stored for contract value \"" << name << "\"\n"; } curDatabase.ContractVariableData[name].insert(val); } From 337807d32e5b5d407dc5522cf36c7e68a69fc202 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 13:57:55 +0200 Subject: [PATCH 102/125] Fix debug str --- Passes/ContractManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index 965b670..f05f243 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -238,7 +238,7 @@ void ContractManagerAnalysis::addContract(std::string contract, Function* F) { void ContractManagerAnalysis::addValueDefinition(std::string name, Value* val) { if (IS_DEBUG) { - WithColor(errs(), HighlightColor::Remark) << "[ContractManager] IR Value " << *val << "\" stored for contract value \"" << name << "\"\n"; + WithColor(errs(), HighlightColor::Remark) << "[ContractManager] IR Value \"" << *val << "\" stored for contract value \"" << name << "\"\n"; } curDatabase.ContractVariableData[name].insert(val); } From 6b5fc2e239fb761929e63c0e8904889a90adc284 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 14:07:44 +0200 Subject: [PATCH 103/125] Stabilize --- Passes/ContractVerifierParam.cpp | 2 +- Passes/Instrument.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Passes/ContractVerifierParam.cpp b/Passes/ContractVerifierParam.cpp index 03aa87c..70f3ff9 100644 --- a/Passes/ContractVerifierParam.cpp +++ b/Passes/ContractVerifierParam.cpp @@ -56,7 +56,7 @@ PreservedAnalyses ContractVerifierParamPass::run(Module &M, // Perform the check on each callsite for (User* U : C.F->users()) { - if (CallBase* CB = dyn_cast(U)) { + if (CallBase* CB = dyn_cast(U); CB && CB->getCalledOperand() == C.F) { for (ParamRequirement const& req : ParamOp->reqs) { // Figure out value(s) to check against std::set vars; diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 5c0056c..896521f 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -519,7 +519,7 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { if (already_instrumented.contains(F)) return; std::vector callsites; for (User* U : F->users()) { - if (CallBase* CB = dyn_cast(U)) { + if (CallBase* CB = dyn_cast(U); CB && CB->getCalledOperand() == F) { callsites.push_back(CB); } } From 0535a9cc9f80689709f657cedeaeef19cc57df2b Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 14:13:13 +0200 Subject: [PATCH 104/125] Move deletion of fortran contr carries after opt passes --- Passes/ContractManager.cpp | 9 --------- Passes/Instrument.cpp | 6 ++++++ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index f05f243..4240b2b 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -95,7 +95,6 @@ void ContractManagerAnalysis::extractFromAnnotations(const Module& M) { } void ContractManagerAnalysis::extractFromFunction(Module& M) { - std::vector to_remove; for (Function& F : M) { F.removeFnAttr(Attribute::NoInline); if (F.getName().starts_with("contract_definitions_fort")) { @@ -194,16 +193,8 @@ void ContractManagerAnalysis::extractFromFunction(Module& M) { } } } - // This function is unreachable, and should definitely not be analysed, so no need to compile. Drop it - to_remove.push_back(&F); } } - // Remove unneeded functions - for (Function* F : to_remove) - F->eraseFromParent(); - if (to_remove.empty()) { - errs() << "Note: No contract declarations found using Declare_Contract calls\n"; - } } void ContractManagerAnalysis::addContract(std::string contract, Function* F) { diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 896521f..517bc5c 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -154,6 +154,12 @@ PreservedAnalyses InstrumentPass::run(Module &M, instrumentRW(M); instrumentFunctions(M); + std::vector to_remove; + for (Function& F : M) { + if (F.getName().starts_with("contract_definitions_fort")) to_remove.push_back(&F); + } + for (Function* F : to_remove) F->eraseFromParent(); + return PreservedAnalyses::none(); } From 03c034060d0479c2e9afaffe544e7ebad019cc26 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 14:39:58 +0200 Subject: [PATCH 105/125] Port improvement from triplefunc --- Passes/Instrument.cpp | 21 ++++++++++++++------- Passes/Instrument.hpp | 1 + 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 517bc5c..cc20c62 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -411,6 +411,19 @@ GlobalVariable* InstrumentPass::createConstantGlobal(Module& M, Constant* C, std return GV; } +Instruction* InstrumentPass::anyValToPtr(Value** V, Instruction* pos) { + if ((*V)->getType()->isVoidTy()) { *V = Basic_Types.Null_Const; return pos; } + if (!(*V)->getType()->isPointerTy()) { + if ((*V)->getType()->isFloatingPointTy()) { + *V = CastInst::Create(Instruction::CastOps::BitCast, *V, Basic_Types.Int64_Type, "", pos->getIterator()); + } + // Now, actual pointer cast + *V = CastInst::Create(Instruction::CastOps::IntToPtr, *V, Basic_Types.Ptr_Type, "", pos->getIterator()); + return dyn_cast(*V)->getNextNode(); + } + return pos; +} + void InstrumentPass::createTypes(Module& M) { // Operations Param_Type = StructType::create(M.getContext(), "CallParam_t"); @@ -553,13 +566,7 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { int size_act = callsite->getDataLayout().getTypeStoreSizeInBits(U->getType()); params.push_back(Basic_Types.getInt((size_act << 8) | (size_act & 0xFF))); // Store actual parameter, making sure to cast if necessary - if (!U->getType()->isPointerTy()) { - if (U->getType()->isFloatingPointTy()) { - actual_param = CastInst::Create(Instruction::CastOps::BitCast, actual_param, Basic_Types.Int_Type, "", callsite->getIterator()); - } - // Now, actual pointer cast - actual_param = CastInst::Create(Instruction::CastOps::IntToPtr, actual_param, Basic_Types.Ptr_Type, "", callsite->getIterator()); - } + anyValToPtr(&actual_param, callsite); } else { if (Function const* F = dyn_cast(callsite->getCalledOperand())) { DISubprogram const* Dbg = F->getSubprogram(); diff --git a/Passes/Instrument.hpp b/Passes/Instrument.hpp index 478764f..a1d5881 100644 --- a/Passes/Instrument.hpp +++ b/Passes/Instrument.hpp @@ -71,6 +71,7 @@ class InstrumentPass : public PassInfoMixin { // Helpers bool checkIsStrParam(CallBase* CB, int idx); + Instruction* anyValToPtr(Value** V, Instruction* pos); // Misc bool isC = true; From f29811435d1bad0de651d3cb649a82e4c2372892 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 15:44:03 +0200 Subject: [PATCH 106/125] Tiny opt --- Passes/Instrument.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index cc20c62..2e2b495 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -314,13 +314,20 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr pOP = static_pointer_cast(op); std::vector reqCs; bool hasIntCmp = false; + bool hasZeroInit = false; for (ParamRequirement const& req : pOP->reqs) { Constant* var = Basic_Types.Null_Const; try { int ivalue = std::stoi(req.value); var = Basic_Types.getInt64(ivalue); var = ConstantExpr::getIntToPtr(var, Basic_Types.Ptr_Type); - reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(false)})); + Constant* CS = ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(false)}); + if (CS->isZeroValue() && !hasZeroInit) hasZeroInit = true; + else if (CS->isZeroValue()) { + if (IS_DEBUG) errs() << "[InstrumentPass] Duplicate paramreq detected and filtered.\n"; + continue; + } + reqCs.push_back(CS); hasIntCmp = req.isArg ? hasIntCmp : true; } catch(std::exception& e) { if (!DB->ContractVariableData.contains(req.value)) { @@ -334,7 +341,12 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr(var)) { errs() << "Weird param error in instr pass\n"; } - reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(!isC && (var->getName().starts_with("_QQ")))})); + Constant* CS = ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(!isC && (var->getName().starts_with("_QQ")))}); + if (CS->isZeroValue() && !hasZeroInit) hasZeroInit = true; + else if (CS->isZeroValue()) { + if (IS_DEBUG) errs() << "[InstrumentPass] Duplicate paramreq detected and filtered.\n"; + continue; + } hasIntCmp = ContractPassUtility::fortCheckAndGetGlbInt(var) ? true : hasIntCmp; } } From 9a923a58451e28eccd94fbc3d8cd0fa0cc7efa3b Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 15:55:19 +0200 Subject: [PATCH 107/125] Improve comments --- Dynamic/Hooks.cpp | 2 +- Dynamic/Hooks.hpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dynamic/Hooks.cpp b/Dynamic/Hooks.cpp index 88806c0..2ea7f04 100644 --- a/Dynamic/Hooks.cpp +++ b/Dynamic/Hooks.cpp @@ -158,7 +158,7 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b ffi_prep_cif(&cif_c.cif, FFI_DEFAULT_ABI, num_params, ret_size == 64 ? &ffi_type_double : getFFIType(ret_size, ret_size >> 16), cif_c.arg_types.data()); cached_cifs.push_back(cif_c); } - cif_c.cif.arg_types = cif_c.arg_types.data(); + cif_c.cif.arg_types = cif_c.arg_types.data(); // Might have been moved, need to keep updated here. void* res = nullptr; ffi_call(&cif_c.cif, FFI_FN(function), &res, ffi_arg_values_ptr.data()); diff --git a/Dynamic/Hooks.hpp b/Dynamic/Hooks.hpp index 390e650..d2c1b23 100644 --- a/Dynamic/Hooks.hpp +++ b/Dynamic/Hooks.hpp @@ -160,10 +160,10 @@ namespace { break; case UNARY_ALLOC: if (isPre) addAnalysis(form, func_supplier, (AllocOp_t*)form->data); - break; // Normal to have alloc in post, but unused. + break; // Normal to have alloc in post, but only used by the precond "actual check". case UNARY_FREE: if (isPre) DynamicUtils::createMessage("Did not expect freeop in precondition!"); - break; // Normal to find free in post, but unused. + break; // Normal to have free in post, but only used by the precond "actual check". default: DynamicUtils::createMessage("Unknown top-level operation!"); break; From 0629305612ad8b45b0cb4cd40cd622c38f8778dd Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 16:06:53 +0200 Subject: [PATCH 108/125] Remove std::bitset --- Dynamic/Hooks.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Dynamic/Hooks.cpp b/Dynamic/Hooks.cpp index 2ea7f04..eb504f5 100644 --- a/Dynamic/Hooks.cpp +++ b/Dynamic/Hooks.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -115,7 +114,7 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b static std::vector ffi_arg_values_ptr; static std::vector ffi_arg_values_store; - cifCache cif_c = {nullptr }; + cifCache cif_c = {nullptr}; for (cifCache& entry : cached_cifs) { if (function == entry.func) { cif_c = entry; @@ -134,15 +133,14 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b for (int i = 0; i < num_params; i++) { uint32_t param_size = va_arg(list,uint32_t); void* param_val = va_arg(list,void*); - std::bitset<8> flags(param_size >> 16); - if (flags.test(0)) { + if ((param_size >> 17) & 0b1) { // Need to deref value first callsite.params.push_back({*(void**)param_val, param_size & 0xFF}); } else { callsite.params.push_back({param_val, param_size & 0xFF}); } if (!cif_c.func) { - cif_c.arg_types.push_back(getFFIType(param_size >> 8, flags.test(1))); + cif_c.arg_types.push_back(getFFIType(param_size >> 8, (param_size >> 16) & 0b10)); } ffi_arg_values_store.push_back(param_val); ffi_arg_values_ptr.push_back(&ffi_arg_values_store[i]); From 9c697813f5d52461680d6810a006f3636d60113d Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 17:18:36 +0200 Subject: [PATCH 109/125] Fix return value (really) (for real this time) --- Dynamic/Hooks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dynamic/Hooks.cpp b/Dynamic/Hooks.cpp index eb504f5..eeb5386 100644 --- a/Dynamic/Hooks.cpp +++ b/Dynamic/Hooks.cpp @@ -153,7 +153,7 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b // Call the intercepted function if (!cif_c.func) { cif_c.func = function; - ffi_prep_cif(&cif_c.cif, FFI_DEFAULT_ABI, num_params, ret_size == 64 ? &ffi_type_double : getFFIType(ret_size, ret_size >> 16), cif_c.arg_types.data()); + ffi_prep_cif(&cif_c.cif, FFI_DEFAULT_ABI, num_params, getFFIType(ret_size, ret_size >> 16), cif_c.arg_types.data()); cached_cifs.push_back(cif_c); } cif_c.cif.arg_types = cif_c.arg_types.data(); // Might have been moved, need to keep updated here. From ec74f7fca835e41bfa6676826a5e22df8f1d2da5 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Mon, 13 Apr 2026 17:19:35 +0200 Subject: [PATCH 110/125] Fix compilation on some code --- Scripts/gen_mpi_contr_h.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 9970631..5d08b49 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -484,10 +484,10 @@ def get_param_values(lang: str) -> str: void __attribute__((weak)) CoVer_RegisterGlobal(void* ptr, int64_t size) CONTRACT( POST {{ alloc!(0[ 1 _arg ]) }}) {{}}; -void* calloc(size_t num, size_t size) CONTRACT( POST {{ alloc!(R[ 0 _arg * 1 _arg]) }}); -void* malloc(size_t size) CONTRACT( POST {{ alloc!(R[0 _arg]) }}); +void* calloc(size_t num, size_t size) CONTRACT( POST {{ alloc!(R[ 0 _arg * 1 _arg]) }}) __THROW; +void* malloc(size_t size) CONTRACT( POST {{ alloc!(R[0 _arg]) }}) __THROW; -void free(void*) CONTRACT( POST {{ free!(0) }}); +void free(void*) CONTRACT( POST {{ free!(0) }}) __THROW; """ From f88cf3fb380d7b271ae5f858b8b973fc00e11362 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 14 Apr 2026 09:04:05 +0200 Subject: [PATCH 111/125] Rem whitespace --- Dynamic/Hooks.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dynamic/Hooks.hpp b/Dynamic/Hooks.hpp index d2c1b23..84f186f 100644 --- a/Dynamic/Hooks.hpp +++ b/Dynamic/Hooks.hpp @@ -36,7 +36,7 @@ namespace { }; std::unordered_set visitedLocs; - + std::filesystem::path const& coverage_prefix = std::getenv("COVER_COVERAGE_FOLDER") ? std::filesystem::path(std::getenv("COVER_COVERAGE_FOLDER")) : std::filesystem::current_path(); std::unordered_map contract_status; From 09aa7e332503e2ae923a29b9722b10a4a1ca82ea Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 14 Apr 2026 09:10:06 +0200 Subject: [PATCH 112/125] Revert "Tiny opt" This reverts commit fe5bef540b192707d249ed28db4a4434a8b06b1e. --- Passes/Instrument.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 2e2b495..cc20c62 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -314,20 +314,13 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr pOP = static_pointer_cast(op); std::vector reqCs; bool hasIntCmp = false; - bool hasZeroInit = false; for (ParamRequirement const& req : pOP->reqs) { Constant* var = Basic_Types.Null_Const; try { int ivalue = std::stoi(req.value); var = Basic_Types.getInt64(ivalue); var = ConstantExpr::getIntToPtr(var, Basic_Types.Ptr_Type); - Constant* CS = ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(false)}); - if (CS->isZeroValue() && !hasZeroInit) hasZeroInit = true; - else if (CS->isZeroValue()) { - if (IS_DEBUG) errs() << "[InstrumentPass] Duplicate paramreq detected and filtered.\n"; - continue; - } - reqCs.push_back(CS); + reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(false)})); hasIntCmp = req.isArg ? hasIntCmp : true; } catch(std::exception& e) { if (!DB->ContractVariableData.contains(req.value)) { @@ -341,12 +334,7 @@ Constant* InstrumentPass::createOperationGlobal(Module& M, std::shared_ptr(var)) { errs() << "Weird param error in instr pass\n"; } - Constant* CS = ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(!isC && (var->getName().starts_with("_QQ")))}); - if (CS->isZeroValue() && !hasZeroInit) hasZeroInit = true; - else if (CS->isZeroValue()) { - if (IS_DEBUG) errs() << "[InstrumentPass] Duplicate paramreq detected and filtered.\n"; - continue; - } + reqCs.push_back(ConstantStruct::get(ParamReq_Type, {Basic_Types.getInt(req.comp), var, Basic_Types.getBool(req.isArg), Basic_Types.getBool(!isC && (var->getName().starts_with("_QQ")))})); hasIntCmp = ContractPassUtility::fortCheckAndGetGlbInt(var) ? true : hasIntCmp; } } From 9dc8b842ec7d3343b3a6f3cb9975438c8697168d Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 14 Apr 2026 09:42:15 +0200 Subject: [PATCH 113/125] Fix bit manips in hooks.cpp --- Dynamic/Hooks.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dynamic/Hooks.cpp b/Dynamic/Hooks.cpp index eeb5386..3f50128 100644 --- a/Dynamic/Hooks.cpp +++ b/Dynamic/Hooks.cpp @@ -133,14 +133,14 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b for (int i = 0; i < num_params; i++) { uint32_t param_size = va_arg(list,uint32_t); void* param_val = va_arg(list,void*); - if ((param_size >> 17) & 0b1) { + if ((param_size >> 16) & 0b1) { // Need to deref value first callsite.params.push_back({*(void**)param_val, param_size & 0xFF}); } else { callsite.params.push_back({param_val, param_size & 0xFF}); } if (!cif_c.func) { - cif_c.arg_types.push_back(getFFIType(param_size >> 8, (param_size >> 16) & 0b10)); + cif_c.arg_types.push_back(getFFIType(param_size >> 8, param_size >> 17)); } ffi_arg_values_store.push_back(param_val); ffi_arg_values_ptr.push_back(&ffi_arg_values_store[i]); @@ -153,7 +153,7 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b // Call the intercepted function if (!cif_c.func) { cif_c.func = function; - ffi_prep_cif(&cif_c.cif, FFI_DEFAULT_ABI, num_params, getFFIType(ret_size, ret_size >> 16), cif_c.arg_types.data()); + ffi_prep_cif(&cif_c.cif, FFI_DEFAULT_ABI, num_params, getFFIType(ret_size, ret_size >> 17), cif_c.arg_types.data()); cached_cifs.push_back(cif_c); } cif_c.cif.arg_types = cif_c.arg_types.data(); // Might have been moved, need to keep updated here. From dc034b58d510202aa5efe1c334bd3eae002600d4 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 14 Apr 2026 09:54:47 +0200 Subject: [PATCH 114/125] Fix __THROW annot location --- Scripts/gen_mpi_contr_h.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 5d08b49..32f3d8e 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -484,10 +484,10 @@ def get_param_values(lang: str) -> str: void __attribute__((weak)) CoVer_RegisterGlobal(void* ptr, int64_t size) CONTRACT( POST {{ alloc!(0[ 1 _arg ]) }}) {{}}; -void* calloc(size_t num, size_t size) CONTRACT( POST {{ alloc!(R[ 0 _arg * 1 _arg]) }}) __THROW; -void* malloc(size_t size) CONTRACT( POST {{ alloc!(R[0 _arg]) }}) __THROW; +void* calloc(size_t num, size_t size) __THROW CONTRACT( POST {{ alloc!(R[ 0 _arg * 1 _arg]) }}); +void* malloc(size_t size) __THROW CONTRACT( POST {{ alloc!(R[0 _arg]) }}); -void free(void*) CONTRACT( POST {{ free!(0) }}) __THROW; +void free(void*) __THROW CONTRACT( POST {{ free!(0) }}); """ From a7c43bdfe89452d3006a48547eeb2fe66c334d65 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 14 Apr 2026 10:37:20 +0200 Subject: [PATCH 115/125] Use extern C for (c/m)alloc, free, and intrinsics --- Scripts/gen_mpi_contr_h.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 32f3d8e..9992cdd 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -478,6 +478,10 @@ def get_param_values(lang: str) -> str: {get_param_values("c")} +#ifdef __cplusplus +extern "C" {{ +#endif + void __attribute__((weak)) CoVer_AllocStack(void* ptr) CONTRACT( POST {{ alloc!(0) }}) {{}}; void __attribute__((weak)) CoVer_FreeStack(void* ptr) CONTRACT( POST {{ free!(0) }}) {{}}; @@ -489,6 +493,10 @@ def get_param_values(lang: str) -> str: void free(void*) __THROW CONTRACT( POST {{ free!(0) }}); +#ifdef __cplusplus +}} +#endif + """ header_output_c = boilerplate_header_c From f87ce5e86a2cf65c62e18fed9c068386379d7283 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 14 Apr 2026 11:01:40 +0200 Subject: [PATCH 116/125] Make ContractValuePair static --- Include/Contracts.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/Contracts.h b/Include/Contracts.h index 75ccc1b..08df689 100644 --- a/Include/Contracts.h +++ b/Include/Contracts.h @@ -25,4 +25,4 @@ struct ContractValuePair { // Define a name for a constant value // Example: CONTRACT_VALUE_PAIR(zero,0) #define CONTRACT_VALUE_PAIR(x,y) \ - ContractValuePair_t MACRO_CONCAT(ContractValueInfo_, __COUNTER__ ) __attribute__((used)) = {#x, (void*)y}; + static ContractValuePair_t MACRO_CONCAT(ContractValueInfo_, __COUNTER__ ) __attribute__((used)) = {#x, (void*)y}; From 3beb541fb7f87788b33e5c3788575c0840fb0dde Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 14 Apr 2026 11:30:52 +0200 Subject: [PATCH 117/125] Add contract for operator new[] --- Scripts/gen_mpi_contr_h.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index 9992cdd..e28f643 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -479,6 +479,9 @@ def get_param_values(lang: str) -> str: {get_param_values("c")} #ifdef __cplusplus + +void* operator new[](size_t size) CONTRACT(POST {{alloc!(R[0 _arg])}}); + extern "C" {{ #endif From 746866ac3e335ed44e71732404be5ff30bd1e0a1 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 14 Apr 2026 11:44:27 +0200 Subject: [PATCH 118/125] Cleanup autocomplete script --- Utils/bash-autocomplete.sh | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Utils/bash-autocomplete.sh b/Utils/bash-autocomplete.sh index 205858d..3f650e7 100644 --- a/Utils/bash-autocomplete.sh +++ b/Utils/bash-autocomplete.sh @@ -1,6 +1,6 @@ # Please add "source /path/to/bash-autocomplete.sh" to your .bashrc to use this. -_@EXECUTABLE_WRAPPER_NAME@_filedir() +_compile_wrapper_filedir() { # _filedir function provided by recent versions of bash-completion package is # better than "compgen -f" because the former honors spaces in pathnames while @@ -8,7 +8,7 @@ _@EXECUTABLE_WRAPPER_NAME@_filedir() _filedir 2> /dev/null || COMPREPLY=( $( compgen -f ) ) } -_@EXECUTABLE_WRAPPER_NAME@() +_compile_wrapper() { local cur prev words cword arg flags w1 w2 # If latest bash-completion is not supported just initialize COMPREPLY and @@ -40,19 +40,13 @@ _@EXECUTABLE_WRAPPER_NAME@() eval local path=${COMP_WORDS[0]} # Use $'\t' so that bash expands the \t for older versions of sed. flags=$( "$path" --autocomplete="$arg" 2>/dev/null | sed -e $'s/\t.*//' ) - # If @EXECUTABLE_WRAPPER_NAME@ is old that it does not support --autocomplete, - # fall back to the filename completion. - if [[ "$?" != 0 ]]; then - _@EXECUTABLE_WRAPPER_NAME@_filedir - return - fi # When @EXECUTABLE_WRAPPER_NAME@ does not emit any possible autocompletion, or user pushed tab after " ", # just autocomplete files. if [[ "$flags" == "$(echo -e '\n')" ]]; then # If -foo= and there was no possible values, autocomplete files. [[ "$cur" == '=' || "$cur" == -*= ]] && cur="" - _@EXECUTABLE_WRAPPER_NAME@_filedir + _compile_wrapper_filedir elif [[ "$cur" == '=' ]]; then COMPREPLY=( $( compgen -W "$flags" -- "") ) else @@ -62,4 +56,4 @@ _@EXECUTABLE_WRAPPER_NAME@() COMPREPLY=( $( compgen -W "$flags" -- "$cur" ) ) fi } -complete -F _@EXECUTABLE_WRAPPER_NAME@ @EXECUTABLE_WRAPPER_NAME@ +complete -F _compile_wrapper @EXECUTABLE_WRAPPER_NAME@ From 6cd15abc56a88206a186086a932699d38607f3b2 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 14 Apr 2026 11:56:00 +0200 Subject: [PATCH 119/125] Fix compilation when throwing functions are instrumented --- Passes/Instrument.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index cc20c62..5e075d3 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -622,25 +622,30 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { void InstrumentPass::insertCBIfNeeded(FunctionCallee FC, std::vector params, Instruction* I) { if (!isRelevant(I) && (isa(I) || isa(I)) && ClInstrumentType.starts_with("filtered")) return; params.insert(params.begin(), Basic_Types.getBool(isRelevant(I))); - CallInst* callbackCI = CallInst::Create(FC, params); - callbackCI->setDebugLoc(I->getDebugLoc()); + CallBase* callbackCB; + if (InvokeInst const* II = dyn_cast(I)) { + callbackCB = InvokeInst::Create(FC, II->getNormalDest(), II->getUnwindDest(), params); + } else { + callbackCB = CallInst::Create(FC, params); + } + callbackCB->setDebugLoc(I->getDebugLoc()); if (CallBase* CB = dyn_cast(I)) { Type* OrigRT = CB->getCalledFunction()->getReturnType(); if (!OrigRT->isPointerTy() && !OrigRT->isVoidTy()) { - callbackCI->insertBefore(I->getIterator()); + callbackCB->insertBefore(I->getIterator()); if (OrigRT->isIntegerTy()) { - CastInst* CI = CastInst::Create(Instruction::PtrToInt, callbackCI, OrigRT); + CastInst* CI = CastInst::Create(Instruction::PtrToInt, callbackCB, OrigRT); ReplaceInstWithInst(I, CI); } else if (OrigRT->isFloatingPointTy()) { - CastInst* CI = CastInst::Create(Instruction::PtrToInt, callbackCI, Basic_Types.Int64_Type, "", I->getIterator()); + CastInst* CI = CastInst::Create(Instruction::PtrToInt, callbackCB, Basic_Types.Int64_Type, "", I->getIterator()); CI = CastInst::Create(Instruction::BitCast, CI, OrigRT); ReplaceInstWithInst(I, CI); } } else { - ReplaceInstWithInst(I, callbackCI); + ReplaceInstWithInst(I, callbackCB); } } - else callbackCI->insertBefore(I->getIterator()); + else callbackCB->insertBefore(I->getIterator()); } bool InstrumentPass::isRelevant(Instruction const* I) const { From e7cdc6f5822db3e88e54f789b06b5cea39d0ee42 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 14 Apr 2026 13:03:09 +0200 Subject: [PATCH 120/125] Split into pre/post cb instead of funcparam in dyn analysis --- Dynamic/Analyses/AllocAnalysis.cpp | 64 +++++++++++++-------------- Dynamic/Analyses/AllocAnalysis.h | 3 +- Dynamic/Analyses/BaseAnalysis.h | 3 +- Dynamic/Analyses/ParamAnalysis.cpp | 2 +- Dynamic/Analyses/ParamAnalysis.h | 3 +- Dynamic/Analyses/PostCallAnalysis.cpp | 2 +- Dynamic/Analyses/PostCallAnalysis.h | 3 +- Dynamic/Analyses/PreCallAnalysis.cpp | 2 +- Dynamic/Analyses/PreCallAnalysis.h | 3 +- Dynamic/Analyses/ReleaseAnalysis.cpp | 2 +- Dynamic/Analyses/ReleaseAnalysis.h | 3 +- Dynamic/Hooks.cpp | 4 +- 12 files changed, 50 insertions(+), 44 deletions(-) diff --git a/Dynamic/Analyses/AllocAnalysis.cpp b/Dynamic/Analyses/AllocAnalysis.cpp index bbec211..5ce32f6 100644 --- a/Dynamic/Analyses/AllocAnalysis.cpp +++ b/Dynamic/Analyses/AllocAnalysis.cpp @@ -8,40 +8,40 @@ #include #include -Fulfillment AllocAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { - if (!isPre) { - if (mem_allocators.contains(func)) { - uintptr_t alloc = (uintptr_t)(mem_allocators[func]->rwOp->idx == 99 ? callsite.retval : callsite.params[mem_allocators[func]->rwOp->idx].value); - if (mem_allocators[func]->rwOp->accType == ParamAccess::DEREF) alloc = (uintptr_t)*(void**)alloc; - MathExpr_t const* cur = mem_allocators[func]->size; - size_t res = cur->isArgValue ? (size_t)callsite.params[cur->value].value : cur->value; - while (cur->other != nullptr) { - switch (cur->type) { - case UNARY_VALUE: - break; - case MULT: - res *= cur->other->isArgValue ? (size_t)callsite.params[cur->other->value].value : cur->other->value; - break; - } - cur = cur->other; - } - allocated[alloc] = res; - } - else if (mem_deallocators.contains(func)) { - if (mem_deallocators[func]->rwOp->idx == 99) allocated.erase((uintptr_t const)callsite.retval); - else allocated.erase((uintptr_t const)callsite.params[mem_deallocators[func]->rwOp->idx].value); +Fulfillment AllocAnalysis::functionPreCBImpl(void* const& func, CallsiteInfo const& callsite) { + if (func == func_supplier) { + uintptr_t ptr = (uintptr_t)callsite.params[idx].value; + for (std::pair alloc_ptr : allocated) { + if ((uintptr_t)alloc_ptr.first == ptr) return Fulfillment::UNKNOWN; + if (ptr >= alloc_ptr.first && ptr < alloc_ptr.first + alloc_ptr.second) return Fulfillment::UNKNOWN; } - return Fulfillment::UNKNOWN; - } else { - if (func == func_supplier) { - uintptr_t ptr = (uintptr_t)callsite.params[idx].value; - for (std::pair alloc_ptr : allocated) { - if ((uintptr_t)alloc_ptr.first == ptr) return Fulfillment::UNKNOWN; - if (ptr >= alloc_ptr.first && ptr < alloc_ptr.first + alloc_ptr.second) return Fulfillment::UNKNOWN; + references.push_back(callsite.location); + return Fulfillment::VIOLATED; + } + return Fulfillment::UNKNOWN; +} + +Fulfillment AllocAnalysis::functionPostCBImpl(void* const& func, CallsiteInfo const& callsite) { + if (mem_allocators.contains(func)) { + uintptr_t alloc = (uintptr_t)(mem_allocators[func]->rwOp->idx == 99 ? callsite.retval : callsite.params[mem_allocators[func]->rwOp->idx].value); + if (mem_allocators[func]->rwOp->accType == ParamAccess::DEREF) alloc = (uintptr_t)*(void**)alloc; + MathExpr_t const* cur = mem_allocators[func]->size; + size_t res = cur->isArgValue ? (size_t)callsite.params[cur->value].value : cur->value; + while (cur->other != nullptr) { + switch (cur->type) { + case UNARY_VALUE: + break; + case MULT: + res *= cur->other->isArgValue ? (size_t)callsite.params[cur->other->value].value : cur->other->value; + break; } - references.push_back(callsite.location); - return Fulfillment::VIOLATED; + cur = cur->other; } - return Fulfillment::UNKNOWN; + allocated[alloc] = res; + } + else if (mem_deallocators.contains(func)) { + if (mem_deallocators[func]->rwOp->idx == 99) allocated.erase((uintptr_t const)callsite.retval); + else allocated.erase((uintptr_t const)callsite.params[mem_deallocators[func]->rwOp->idx].value); } + return Fulfillment::UNKNOWN; } diff --git a/Dynamic/Analyses/AllocAnalysis.h b/Dynamic/Analyses/AllocAnalysis.h index 925c187..a7fcfaf 100644 --- a/Dynamic/Analyses/AllocAnalysis.h +++ b/Dynamic/Analyses/AllocAnalysis.h @@ -17,7 +17,8 @@ struct AllocAnalysis : BaseAnalysis { } } - ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPreCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPostCBImpl(void* const& func, CallsiteInfo const& callsite); ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; // Evidently none of the callsites were erroneous diff --git a/Dynamic/Analyses/BaseAnalysis.h b/Dynamic/Analyses/BaseAnalysis.h index 112dacf..bef5877 100644 --- a/Dynamic/Analyses/BaseAnalysis.h +++ b/Dynamic/Analyses/BaseAnalysis.h @@ -20,7 +20,8 @@ class BaseAnalysis { public: // Event handlers. Return non-unknown if analysis is resolved and no longer needs to be analysed. // onFunctionCall does not forward return address, as it is included in callsiteinfo - inline Fulfillment onFunctionCall(CodePtr const& location, void* const& func, bool const isPre, CallsiteInfo const& callsite) { return static_cast(this)->functionCBImpl(func, isPre, callsite); }; + inline Fulfillment onFunctionCallPre(CodePtr const& location, void* const& func, CallsiteInfo const& callsite) { return static_cast(this)->functionPreCBImpl(func, callsite); }; + inline Fulfillment onFunctionCallPost(CodePtr const& location, void* const& func, CallsiteInfo const& callsite) { return static_cast(this)->functionPostCBImpl(func, callsite); }; inline Fulfillment onMemoryAccess(CodePtr const& location, void const* const& memory, bool const& isWrite) { return static_cast(this)->memoryCBImpl(std::forward(location), memory, isWrite); }; inline Fulfillment onProgramExit(CodePtr const& location) { return static_cast(this)->exitCBImpl(std::forward(location)); }; diff --git a/Dynamic/Analyses/ParamAnalysis.cpp b/Dynamic/Analyses/ParamAnalysis.cpp index 64b8fd3..0bf385f 100644 --- a/Dynamic/Analyses/ParamAnalysis.cpp +++ b/Dynamic/Analyses/ParamAnalysis.cpp @@ -15,7 +15,7 @@ static constexpr int64_t sign_extend(uintptr_t const ptr, int const size) { return ptr; } -Fulfillment ParamAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { +Fulfillment ParamAnalysis::functionPreCBImpl(void* const& func, CallsiteInfo const& callsite) { if (func != func_supplier) return Fulfillment::UNKNOWN; void const* act_callp = callsite.params[idx].value; diff --git a/Dynamic/Analyses/ParamAnalysis.h b/Dynamic/Analyses/ParamAnalysis.h index b0c97ca..7df7811 100644 --- a/Dynamic/Analyses/ParamAnalysis.h +++ b/Dynamic/Analyses/ParamAnalysis.h @@ -12,7 +12,8 @@ struct ParamAnalysis : BaseAnalysis { } } - ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPreCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPostCBImpl(void* const& func, CallsiteInfo const& callsite) { return Fulfillment::UNKNOWN; }; ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; // Evidently none of the callsites were erroneous diff --git a/Dynamic/Analyses/PostCallAnalysis.cpp b/Dynamic/Analyses/PostCallAnalysis.cpp index 3d77258..e906c8c 100644 --- a/Dynamic/Analyses/PostCallAnalysis.cpp +++ b/Dynamic/Analyses/PostCallAnalysis.cpp @@ -22,7 +22,7 @@ PostCallAnalysis::PostCallAnalysis(void const* _func_supplier, CallTagOp_t* call target_funcs = DynamicUtils::getFunctionsForTag(callop->target_tag); } -Fulfillment PostCallAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { +Fulfillment PostCallAnalysis::functionPreCBImpl(void* const& func, CallsiteInfo const& callsite) { for (void const* const& target_func : target_funcs) { if (target_func == func) { // Target function found, maybe analysis success diff --git a/Dynamic/Analyses/PostCallAnalysis.h b/Dynamic/Analyses/PostCallAnalysis.h index 132b3c3..a2e13b7 100644 --- a/Dynamic/Analyses/PostCallAnalysis.h +++ b/Dynamic/Analyses/PostCallAnalysis.h @@ -10,7 +10,8 @@ struct PostCallAnalysis : public BaseAnalysis { PostCallAnalysis(void const* func_supplier, CallOp_t* callop); PostCallAnalysis(void const* func_supplier, CallTagOp_t* callop); - ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPreCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPostCBImpl(void* const& func, CallsiteInfo const& callsite) { return Fulfillment::UNKNOWN; }; ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location); diff --git a/Dynamic/Analyses/PreCallAnalysis.cpp b/Dynamic/Analyses/PreCallAnalysis.cpp index 8f6aa57..315726c 100644 --- a/Dynamic/Analyses/PreCallAnalysis.cpp +++ b/Dynamic/Analyses/PreCallAnalysis.cpp @@ -22,7 +22,7 @@ PreCallAnalysis::PreCallAnalysis(void const* _func_supplier, CallTagOp_t* callop target_funcs = DynamicUtils::getFunctionsForTag(callop->target_tag); } -Fulfillment PreCallAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { +Fulfillment PreCallAnalysis::functionPreCBImpl(void* const& func, CallsiteInfo const& callsite) { for (void const* const& target_func : target_funcs) { if (target_func == func) { // Possible match for precall diff --git a/Dynamic/Analyses/PreCallAnalysis.h b/Dynamic/Analyses/PreCallAnalysis.h index 039d506..ecedf55 100644 --- a/Dynamic/Analyses/PreCallAnalysis.h +++ b/Dynamic/Analyses/PreCallAnalysis.h @@ -12,7 +12,8 @@ struct PreCallAnalysis : BaseAnalysis { PreCallAnalysis(void const* func_supplier, CallOp_t* callop); PreCallAnalysis(void const* func_supplier, CallTagOp_t* callop); - ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPreCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPostCBImpl(void* const& func, CallsiteInfo const& callsite) { return Fulfillment::UNKNOWN; }; ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite) const { return Fulfillment::UNKNOWN; } ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::INACTIVE; }; diff --git a/Dynamic/Analyses/ReleaseAnalysis.cpp b/Dynamic/Analyses/ReleaseAnalysis.cpp index 51339ff..1264b18 100644 --- a/Dynamic/Analyses/ReleaseAnalysis.cpp +++ b/Dynamic/Analyses/ReleaseAnalysis.cpp @@ -53,7 +53,7 @@ CallBacks ReleaseAnalysis::requiredCallbacksImpl() const { return {true, false, !rwOp->isWrite, rwOp->isWrite}; } -Fulfillment ReleaseAnalysis::functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite) { +Fulfillment ReleaseAnalysis::functionPreCBImpl(void* const& func, CallsiteInfo const& callsite) { if (!forbiddenCallsites.empty()) { // First, check if release for (void const* const& rel_func : rel_funcs) { diff --git a/Dynamic/Analyses/ReleaseAnalysis.h b/Dynamic/Analyses/ReleaseAnalysis.h index 28f143c..731e40b 100644 --- a/Dynamic/Analyses/ReleaseAnalysis.h +++ b/Dynamic/Analyses/ReleaseAnalysis.h @@ -8,7 +8,8 @@ struct ReleaseAnalysis : BaseAnalysis { public: ReleaseAnalysis(void const* func_supplier, ReleaseOp_t* rOP); - ANALYSIS_PREAMBLE Fulfillment functionCBImpl(void* const& func, bool const isPre, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPreCBImpl(void* const& func, CallsiteInfo const& callsite); + ANALYSIS_PREAMBLE Fulfillment functionPostCBImpl(void* const& func, CallsiteInfo const& callsite) { return Fulfillment::UNKNOWN; }; ANALYSIS_PREAMBLE Fulfillment memoryCBImpl(CodePtr const& location, void const* const& memory, bool const& isWrite); ANALYSIS_PREAMBLE Fulfillment exitCBImpl(CodePtr const& location) const { return Fulfillment::FULFILLED; }; diff --git a/Dynamic/Hooks.cpp b/Dynamic/Hooks.cpp index 3f50128..18662f7 100644 --- a/Dynamic/Hooks.cpp +++ b/Dynamic/Hooks.cpp @@ -148,7 +148,7 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b va_end(list); // Run event handlers and remove analysis if done - HANDLE_CALLBACK(analyses_with_funcPreCB, onFunctionCall, function, true, callsite); + HANDLE_CALLBACK(analyses_with_funcPreCB, onFunctionCallPre, function, callsite); // Call the intercepted function if (!cif_c.func) { @@ -162,7 +162,7 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b // Run event handlers again for the postCBs, including the newly returned value callsite.retval = res; - HANDLE_CALLBACK(analyses_with_funcPostCB, onFunctionCall, function, false, callsite); + HANDLE_CALLBACK(analyses_with_funcPostCB, onFunctionCallPost, function, callsite); return res; } From 77cbae30587781f8590ddf0d0e4fe14d2a42ccb7 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Tue, 14 Apr 2026 15:44:53 +0200 Subject: [PATCH 121/125] Moar opt flags --- Dynamic/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dynamic/CMakeLists.txt b/Dynamic/CMakeLists.txt index afbc759..d9eef6d 100644 --- a/Dynamic/CMakeLists.txt +++ b/Dynamic/CMakeLists.txt @@ -24,7 +24,7 @@ set_property(TARGET CoVerDynamicAnalyzer PROPERTY CXX_VISIBILITY_PRESET hidden) set_property(TARGET CoVerDynamicAnalyzer PROPERTY C_VISIBILITY_PRESET hidden) set_property(TARGET CoVerDynamicAnalyzer PROPERTY VISIBILITY_INLINES_HIDDEN ON) -target_compile_options(CoVerDynamicAnalyzer PUBLIC -fno-rtti -fno-exceptions -fomit-frame-pointer -fno-stack-protector) +target_compile_options(CoVerDynamicAnalyzer PUBLIC -fno-rtti -fno-exceptions -fomit-frame-pointer -fno-stack-protector -fno-math-errno -fno-trapping-math) target_link_libraries(CoVerDynamicAnalyzer libffi) target_include_directories(CoVerDynamicAnalyzer PRIVATE "${FFI_LIBRARIES}") From 48c338aaa1c278ff2dbe40135f7596d505bdd155 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 15 Apr 2026 13:43:15 +0200 Subject: [PATCH 122/125] Fix param parsing for C++ code --- Passes/ContractManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Passes/ContractManager.cpp b/Passes/ContractManager.cpp index 4240b2b..c48bbf4 100644 --- a/Passes/ContractManager.cpp +++ b/Passes/ContractManager.cpp @@ -56,7 +56,7 @@ ContractManagerAnalysis::ContractDatabase ContractManagerAnalysis::run(Module &M // Annotations done, now add value pairs to database for (GlobalVariable& GV : M.globals()) { - if (GV.getName().starts_with("ContractValueInfo_")) { + if (GV.getName().contains("ContractValueInfo_")) { Constant* data = GV.getInitializer(); StringRef name = dyn_cast(dyn_cast(data->getOperand(0))->getInitializer())->getAsCString(); Value* val = data->getOperand(1); From f31c48fb29dfe581a738f9a601e7f6ad3aff8bb3 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 15 Apr 2026 15:00:05 +0200 Subject: [PATCH 123/125] Collapse all CoVer_AllocStack calls per function --- Dynamic/Hooks.cpp | 9 +++++++-- Intrinsics/Intrinsics.c | 2 +- Passes/Instrument.cpp | 4 +++- Passes/Intrinsics.cpp | 33 ++++++++++++++++++--------------- Scripts/gen_mpi_contr_h.py | 7 ++++--- 5 files changed, 33 insertions(+), 22 deletions(-) diff --git a/Dynamic/Hooks.cpp b/Dynamic/Hooks.cpp index 18662f7..9c6eec3 100644 --- a/Dynamic/Hooks.cpp +++ b/Dynamic/Hooks.cpp @@ -133,6 +133,11 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b for (int i = 0; i < num_params; i++) { uint32_t param_size = va_arg(list,uint32_t); void* param_val = va_arg(list,void*); + if (param_size >> 16 & 0b100) { + // This is an undefined value. The program must be buggy af + // LLVM is sometimes funny and chooses valid ptrs for undef. Need to undermine this. + param_val = (void*)0xDEADBEEF; + } if ((param_size >> 16) & 0b1) { // Need to deref value first callsite.params.push_back({*(void**)param_val, param_size & 0xFF}); @@ -140,7 +145,7 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b callsite.params.push_back({param_val, param_size & 0xFF}); } if (!cif_c.func) { - cif_c.arg_types.push_back(getFFIType(param_size >> 8, param_size >> 17)); + cif_c.arg_types.push_back(getFFIType(param_size >> 8, (param_size >> 16) & 0b10)); } ffi_arg_values_store.push_back(param_val); ffi_arg_values_ptr.push_back(&ffi_arg_values_store[i]); @@ -153,7 +158,7 @@ extern "C" void* __attribute__((visibility("default"))) PPDCV_FunctionCallback(b // Call the intercepted function if (!cif_c.func) { cif_c.func = function; - ffi_prep_cif(&cif_c.cif, FFI_DEFAULT_ABI, num_params, getFFIType(ret_size, ret_size >> 17), cif_c.arg_types.data()); + ffi_prep_cif(&cif_c.cif, FFI_DEFAULT_ABI, num_params, getFFIType(ret_size, (ret_size >> 16) & 0b10), cif_c.arg_types.data()); cached_cifs.push_back(cif_c); } cif_c.cif.arg_types = cif_c.arg_types.data(); // Might have been moved, need to keep updated here. diff --git a/Intrinsics/Intrinsics.c b/Intrinsics/Intrinsics.c index a598cc9..f982c47 100644 --- a/Intrinsics/Intrinsics.c +++ b/Intrinsics/Intrinsics.c @@ -4,7 +4,7 @@ #include // Stack allocation intrinsics (i.e. for IR allocas) -void __attribute__((visibility("default"))) CoVer_AllocStack(void const* ptr) {}; +void __attribute__((visibility("default"))) CoVer_AllocStack(void const* ptr, size_t size) {}; void __attribute__((visibility("default"))) CoVer_FreeStack(void const* ptr) {}; // Global "allocation" intrinsic (i.e. for Global variables or pseudoglobals in fortran) diff --git a/Passes/Instrument.cpp b/Passes/Instrument.cpp index 5e075d3..356416a 100644 --- a/Passes/Instrument.cpp +++ b/Passes/Instrument.cpp @@ -564,7 +564,9 @@ void InstrumentPass::insertFunctionInstrCallback(Function* F) { // Store size of data type if (isC) { int size_act = callsite->getDataLayout().getTypeStoreSizeInBits(U->getType()); - params.push_back(Basic_Types.getInt((size_act << 8) | (size_act & 0xFF))); + int size_meta = (size_act << 8) | size_act; + if (isa(actual_param)) size_meta |= 0b100 << 16; + params.push_back(Basic_Types.getInt(size_meta)); // Store actual parameter, making sure to cast if necessary anyValToPtr(&actual_param, callsite); } else { diff --git a/Passes/Intrinsics.cpp b/Passes/Intrinsics.cpp index 184aa09..0fcd210 100644 --- a/Passes/Intrinsics.cpp +++ b/Passes/Intrinsics.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,7 @@ PreservedAnalyses IntrinsicsPass::run(Module &M, ModuleAnalysisManager &AM) { void IntrinsicsPass::createCallees(Module& M) { // Get callee for alloca instr - FunctionType* FunctionAllocStackType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Ptr_Type}, false); + FunctionType* FunctionAllocStackType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Ptr_Type, Basic_Types.Int64_Type}, false); allocStackCallee = calleeHelper(M, "CoVer_AllocStack", FunctionAllocStackType); FunctionType* FunctionFreeStackType = FunctionType::get(Basic_Types.Void_Type, {Basic_Types.Ptr_Type}, false); @@ -52,23 +53,25 @@ void IntrinsicsPass::createCallees(Module& M) { void IntrinsicsPass::instrumentIntrinsics(Module& M) { // Instrument all allocas + Function *FrameAddrIntrin = Intrinsic::getOrInsertDeclaration(&M, Intrinsic::frameaddress, {Basic_Types.Ptr_Type}); + Function *StackSaveIntrin = Intrinsic::getOrInsertDeclaration(&M, Intrinsic::stacksave, {Basic_Types.Ptr_Type}); for (Function& F : M) { - std::vector stack_vars; - for (BasicBlock& BB : F) { - for (Instruction& I : BB) { - if (AllocaInst* AI = dyn_cast(&I)) { - stack_vars.push_back(AI); - CallInst* intrinsicCI = CallInst::Create(allocStackCallee, {AI}); - intrinsicCI->setDebugLoc(I.getDebugLoc()); - intrinsicCI->insertAfter(AI); - } - } - } + if (F.isDeclaration()) continue; + + IRBuilder<> Builder(&*F.getEntryBlock().getFirstNonPHIOrDbgOrAlloca()); + + CallInst *BasePtr = Builder.CreateCall(FrameAddrIntrin, {Basic_Types.getInt(0)}, "base_ptr"); + CallInst *StackPtr = Builder.CreateCall(StackSaveIntrin, {}, "stack_ptr"); + + Value *BaseInt = Builder.CreatePtrToInt(BasePtr, Basic_Types.Int64_Type, "base_int"); + Value *StackInt = Builder.CreatePtrToInt(StackPtr, Basic_Types.Int64_Type, "stack_int"); + + Value *StackSize = Builder.CreateSub(BaseInt, StackInt, "stack_size"); + Builder.CreateCall(allocStackCallee, {StackPtr, StackSize}); + EscapeEnumerator EE(F, "", false); while (IRBuilder<>* IRB = EE.Next()) { - for (AllocaInst* AI : stack_vars) { - IRB->CreateCall(freeStackCallee, {AI}); - } + IRB->CreateCall(freeStackCallee, {StackPtr}); } } diff --git a/Scripts/gen_mpi_contr_h.py b/Scripts/gen_mpi_contr_h.py index e28f643..d6dfca1 100644 --- a/Scripts/gen_mpi_contr_h.py +++ b/Scripts/gen_mpi_contr_h.py @@ -485,7 +485,7 @@ def get_param_values(lang: str) -> str: extern "C" {{ #endif -void __attribute__((weak)) CoVer_AllocStack(void* ptr) CONTRACT( POST {{ alloc!(0) }}) {{}}; +void __attribute__((weak)) CoVer_AllocStack(void* ptr, size_t size) CONTRACT( POST {{ alloc!(0[1 _arg]) }}) {{}}; void __attribute__((weak)) CoVer_FreeStack(void* ptr) CONTRACT( POST {{ free!(0) }}) {{}}; void __attribute__((weak)) CoVer_RegisterGlobal(void* ptr, int64_t size) CONTRACT( POST {{ alloc!(0[ 1 _arg ]) }}) {{}}; @@ -523,8 +523,9 @@ def get_param_values(lang: str) -> str: end subroutine FortFree ! CoVer intrinsics - subroutine CoVer_AllocStack(ptr) bind(c, name="CoVer_AllocStack") + subroutine CoVer_AllocStack(ptr,size) bind(c, name="CoVer_AllocStack") integer, pointer :: ptr + integer(kind=8) :: size end subroutine CoVer_AllocStack subroutine CoVer_FreeStack() bind(c, name="CoVer_FreeStack") end subroutine CoVer_FreeStack @@ -533,7 +534,7 @@ def get_param_values(lang: str) -> str: end interface call Declare_Contract(FortAlloc, \"POST { alloc!(*0[1 _arg]) }\") call Declare_Contract(FortFree, \"POST { free!(0) }\") - call Declare_Contract(CoVer_AllocStack, \"POST { alloc!(0) }\") + call Declare_Contract(CoVer_AllocStack, \"POST { alloc!(0[1 _arg]) }\") call Declare_Contract(CoVer_FreeStack, \"POST { free!(0) }\") call Declare_Contract(CoVer_RegisterGlobal, \"POST { alloc!(0) }\") """ From d42dbf31a0361feb2ce57bbff69947683b5f919b Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 16 Apr 2026 13:39:40 +0200 Subject: [PATCH 124/125] Fix significant perf regression --- Passes/Intrinsics.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Passes/Intrinsics.cpp b/Passes/Intrinsics.cpp index 0fcd210..a3f0eea 100644 --- a/Passes/Intrinsics.cpp +++ b/Passes/Intrinsics.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,16 @@ void IntrinsicsPass::instrumentIntrinsics(Module& M) { Function *StackSaveIntrin = Intrinsic::getOrInsertDeclaration(&M, Intrinsic::stacksave, {Basic_Types.Ptr_Type}); for (Function& F : M) { if (F.isDeclaration()) continue; + bool hasAlloca = false; + for (BasicBlock const& BB : F) { + for (Instruction const& I : BB) { + if (isa(&I)) { + hasAlloca = true; + break; + } + } + } + if (!hasAlloca) continue; IRBuilder<> Builder(&*F.getEntryBlock().getFirstNonPHIOrDbgOrAlloca()); From 75af6a7a98c4cb6324903f1757e1e2a734bf6a71 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Thu, 16 Apr 2026 14:52:35 +0200 Subject: [PATCH 125/125] Remove unordered_map from allocanalysis --- Dynamic/Analyses/AllocAnalysis.cpp | 30 ++++++++++++++++++++++-------- Dynamic/Analyses/AllocAnalysis.h | 11 +++++------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Dynamic/Analyses/AllocAnalysis.cpp b/Dynamic/Analyses/AllocAnalysis.cpp index 5ce32f6..d55d8c9 100644 --- a/Dynamic/Analyses/AllocAnalysis.cpp +++ b/Dynamic/Analyses/AllocAnalysis.cpp @@ -8,6 +8,13 @@ #include #include +int vecContains(std::vector vec, void* f) { + for (int i = 0; i < vec.size(); i++) { + if (vec[i].func == f) return i; + } + return -1; +} + Fulfillment AllocAnalysis::functionPreCBImpl(void* const& func, CallsiteInfo const& callsite) { if (func == func_supplier) { uintptr_t ptr = (uintptr_t)callsite.params[idx].value; @@ -22,10 +29,11 @@ Fulfillment AllocAnalysis::functionPreCBImpl(void* const& func, CallsiteInfo con } Fulfillment AllocAnalysis::functionPostCBImpl(void* const& func, CallsiteInfo const& callsite) { - if (mem_allocators.contains(func)) { - uintptr_t alloc = (uintptr_t)(mem_allocators[func]->rwOp->idx == 99 ? callsite.retval : callsite.params[mem_allocators[func]->rwOp->idx].value); - if (mem_allocators[func]->rwOp->accType == ParamAccess::DEREF) alloc = (uintptr_t)*(void**)alloc; - MathExpr_t const* cur = mem_allocators[func]->size; + if (int idx = vecContains(mem_allocators, func); idx != -1) { + MemOpFunc_t const& memop = mem_allocators[idx]; + uintptr_t alloc = (uintptr_t)(memop.rwOp->idx == 99 ? callsite.retval : callsite.params[memop.rwOp->idx].value); + if (memop.rwOp->accType == ParamAccess::DEREF) alloc = (uintptr_t)*(void**)alloc; + MathExpr_t const* cur = memop.size; size_t res = cur->isArgValue ? (size_t)callsite.params[cur->value].value : cur->value; while (cur->other != nullptr) { switch (cur->type) { @@ -37,11 +45,17 @@ Fulfillment AllocAnalysis::functionPostCBImpl(void* const& func, CallsiteInfo co } cur = cur->other; } - allocated[alloc] = res; + allocated.push_back({alloc, res}); } - else if (mem_deallocators.contains(func)) { - if (mem_deallocators[func]->rwOp->idx == 99) allocated.erase((uintptr_t const)callsite.retval); - else allocated.erase((uintptr_t const)callsite.params[mem_deallocators[func]->rwOp->idx].value); + else if (int idx = vecContains(mem_deallocators, func); idx != -1) { + MemOpFunc_t const& memop = mem_deallocators[idx]; + uintptr_t const& target = (uintptr_t)(memop.rwOp->idx == 99 ? callsite.retval : callsite.params[memop.rwOp->idx].value); + for (int i = 0; i < allocated.size(); i++) { + if (target == allocated[i].first) { + allocated.erase(allocated.begin() + i); + break; + } + } } return Fulfillment::UNKNOWN; } diff --git a/Dynamic/Analyses/AllocAnalysis.h b/Dynamic/Analyses/AllocAnalysis.h index a7fcfaf..6552c36 100644 --- a/Dynamic/Analyses/AllocAnalysis.h +++ b/Dynamic/Analyses/AllocAnalysis.h @@ -4,16 +4,15 @@ #include "DynamicAnalysis.h" #include #include -#include struct AllocAnalysis : BaseAnalysis { public: AllocAnalysis(void const* _func_supplier, AllocOp_t* allocop) : idx(allocop->idx), acc(allocop->accType), func_supplier(_func_supplier) { for (int i = 0; i < allocop->num_allocators; i++) { - mem_allocators[allocop->allocators[i].func] = &allocop->allocators[i]; + mem_allocators.push_back(allocop->allocators[i]); } for (int i = 0; i < allocop->num_deallocators; i++) { - mem_deallocators[allocop->deallocators[i].func] = &allocop->deallocators[i]; + mem_deallocators.push_back(allocop->deallocators[i]); } } @@ -29,7 +28,7 @@ struct AllocAnalysis : BaseAnalysis { void const* func_supplier; int const idx; ParamAccess const acc; - std::unordered_map allocated; - std::unordered_map mem_allocators; - std::unordered_map mem_deallocators; + std::vector> allocated; + std::vector mem_allocators; + std::vector mem_deallocators; };