From d3f2624da0028690cf4fc29e960644c487710ae8 Mon Sep 17 00:00:00 2001 From: bjjwwang Date: Wed, 3 Jun 2026 23:31:23 +1000 Subject: [PATCH] Assignment-3: 2026 AE redesign skeleton + minimal test set Base: SVF-tools/Software-Security-Analysis main. - Replace the Assignment-3 C++/Python solution with the blank student starter (Assignment_3.cpp / Assignment_3.h / Assignment_3.py): empty bodies and only the framework-facing interface in the header; students implement the rest. - Replace Assignment-3/Tests with the three redesign cases only (buf_overflow, null_deref, stmt; each .c + .ll); the previous ae/buf tests are removed. --- Assignment-3/CPP/Assignment_3.cpp | 291 +----- Assignment-3/CPP/Assignment_3.h | 106 ++- Assignment-3/CPP/Assignment_3_Helper.cpp | 981 ++++++++------------- Assignment-3/CPP/Assignment_3_Helper.h | 88 +- Assignment-3/CPP/CMakeLists.txt | 14 + Assignment-3/CPP/test-ae.cpp | 80 +- Assignment-3/Python/Assignment_3.py | 124 +-- Assignment-3/Python/Assignment_3_Helper.py | 440 +++------ Assignment-3/Python/CMakeLists.txt | 14 + Assignment-3/Python/test-ae.py | 2 +- Assignment-3/Tests/ae/test1.c | 15 - Assignment-3/Tests/ae/test1.ll | 74 -- Assignment-3/Tests/ae/test2.c | 9 - Assignment-3/Tests/ae/test2.ll | 56 -- Assignment-3/Tests/ae/test3.c | 31 - Assignment-3/Tests/ae/test3.ll | 106 --- Assignment-3/Tests/ae/test4.c | 12 - Assignment-3/Tests/ae/test4.ll | 71 -- Assignment-3/Tests/buf/test1.c | 6 - Assignment-3/Tests/buf/test1.ll | 55 -- Assignment-3/Tests/buf/test2.c | 22 - Assignment-3/Tests/buf/test2.ll | 126 --- Assignment-3/Tests/buf/test3.c | 28 - Assignment-3/Tests/buf/test3.ll | 94 -- Assignment-3/Tests/buf/test4.c | 7 - Assignment-3/Tests/buf/test4.ll | 70 -- Assignment-3/Tests/buf/test5.c | 7 - Assignment-3/Tests/buf/test5.ll | 70 -- Assignment-3/Tests/buf/test6.c | 13 - Assignment-3/Tests/buf/test6.ll | 98 -- Assignment-3/Tests/buf_overflow.c | 5 + Assignment-3/Tests/buf_overflow.ll | 51 ++ Assignment-3/Tests/null_deref.c | 7 + Assignment-3/Tests/null_deref.ll | 48 + Assignment-3/Tests/stmt.c | 10 + Assignment-3/Tests/stmt.ll | 67 ++ 36 files changed, 948 insertions(+), 2350 deletions(-) delete mode 100644 Assignment-3/Tests/ae/test1.c delete mode 100644 Assignment-3/Tests/ae/test1.ll delete mode 100644 Assignment-3/Tests/ae/test2.c delete mode 100644 Assignment-3/Tests/ae/test2.ll delete mode 100644 Assignment-3/Tests/ae/test3.c delete mode 100644 Assignment-3/Tests/ae/test3.ll delete mode 100644 Assignment-3/Tests/ae/test4.c delete mode 100644 Assignment-3/Tests/ae/test4.ll delete mode 100644 Assignment-3/Tests/buf/test1.c delete mode 100644 Assignment-3/Tests/buf/test1.ll delete mode 100644 Assignment-3/Tests/buf/test2.c delete mode 100644 Assignment-3/Tests/buf/test2.ll delete mode 100644 Assignment-3/Tests/buf/test3.c delete mode 100644 Assignment-3/Tests/buf/test3.ll delete mode 100644 Assignment-3/Tests/buf/test4.c delete mode 100644 Assignment-3/Tests/buf/test4.ll delete mode 100644 Assignment-3/Tests/buf/test5.c delete mode 100644 Assignment-3/Tests/buf/test5.ll delete mode 100644 Assignment-3/Tests/buf/test6.c delete mode 100644 Assignment-3/Tests/buf/test6.ll create mode 100644 Assignment-3/Tests/buf_overflow.c create mode 100644 Assignment-3/Tests/buf_overflow.ll create mode 100644 Assignment-3/Tests/null_deref.c create mode 100644 Assignment-3/Tests/null_deref.ll create mode 100644 Assignment-3/Tests/stmt.c create mode 100644 Assignment-3/Tests/stmt.ll diff --git a/Assignment-3/CPP/Assignment_3.cpp b/Assignment-3/CPP/Assignment_3.cpp index fe8ccd1..910509a 100644 --- a/Assignment-3/CPP/Assignment_3.cpp +++ b/Assignment-3/CPP/Assignment_3.cpp @@ -26,292 +26,39 @@ */ #include "Assignment_3.h" -#include "Util/Options.h" -#include "Util/WorkList.h" using namespace SVF; using namespace SVFUtil; -/// TODO : Implement the state updates for Copy, Binary, Store, Load, Gep, Phi -void AbstractExecution::updateStateOnCopy(const CopyStmt* copy) { - /// TODO: your code starts from here +// =========================================================================== +// Student TODOs +// =========================================================================== +// Implement abstract interpretation for the assignment cases. The harness +// (Assignment_3_Helper.cpp) drives the analysis and only calls into the six +// entry points below; design and add any internal helpers you need. +// =========================================================================== +void AbstractExecution::updateAbsState(const SVFStmt* stmt) { + // TODO: dispatch on the statement subtype and update the abstract state. } -/// Find the comparison predicates in "class BinaryOPStmt:OpCode" under SVF/svf/include/SVFIR/SVFStatements.h -/// You are required to handle predicates (The program is assumed to have signed ints and also interger-overflow-free), -/// including Add, FAdd, Sub, FSub, Mul, FMul, SDiv, FDiv, UDiv, SRem, FRem, URem, Xor, And, Or, AShr, Shl, LShr -void AbstractExecution::updateStateOnBinary(const BinaryOPStmt* binary) { - /// TODO: your code starts from here - -} - -void AbstractExecution::updateStateOnStore(const StoreStmt* store) { - /// TODO: your code starts from here - -} - -void AbstractExecution::updateStateOnLoad(const LoadStmt* load) { - /// TODO: your code starts from here - -} - -void AbstractExecution::updateStateOnGep(const GepStmt* gep) { - /// TODO: your code starts from here - -} - -void AbstractExecution::updateStateOnPhi(const PhiStmt* phi) { - /// TODO: your code starts from here - -} - -/// TODO: handle GepStmt `lhs = rhs + off` and detect buffer overflow -// Step 1: For each `obj \in pts(rhs)`, get the size of allocated baseobj (entire mem object) via `obj_size = svfir->getBaseObj(objId)->getByteSizeOfObj();` -// There is a buffer overflow if `accessOffset.ub() >= obj_size`, where accessOffset is obtained via `getAccessOffset` -// Step 2: invoke `reportBufOverflow` with the current ICFGNode if an overflow is detected -void AbstractExecution::bufOverflowDetection(const SVF::SVFStmt* stmt) { - if (!SVFUtil::isa(stmt->getICFGNode())) { - if (const GepStmt* gep = SVFUtil::dyn_cast(stmt)) { - AbstractState& as = getAbsStateFromTrace(gep->getICFGNode()); - NodeID lhs = gep->getLHSVarID(); - NodeID rhs = gep->getRHSVarID(); - updateGepObjOffsetFromBase(as, as[lhs].getAddrs(), as[rhs].getAddrs(), svfStateMgr->getGepByteOffset(gep)); - - /// TODO: your code starts from here - - } - } +bool AbstractExecution::mergeStatesFromPredecessors(const ICFGNode* block, AbstractState& as) { + // TODO: join predecessor post-states (with branch refinement) into `as`. + return false; } -/** - * @brief Handle ICFG nodes in a cycle using widening and narrowing operators - * - * This function implements abstract interpretation for cycles in the ICFG using widening and narrowing - * operators to ensure termination. It processes all ICFG nodes within a cycle and implements - * widening-narrowing iteration to reach fixed points twice: once for widening (to ensure termination) - * and once for narrowing (to improve precision). - * - * @param cycle The WTO cycle containing ICFG nodes to be processed - * @return void - */ void AbstractExecution::handleICFGCycle(const ICFGCycleWTO* cycle) { - const ICFGNode* head = cycle->head()->getICFGNode(); - bool increasing = true; - u32_t iteration = 0; - u32_t widen_delay = Options::WidenDelay(); // or use a class member if you have one - - while (true) { - /// TODO: your code starts from here - } - -} - - -/// Abstract state updates on an AddrStmt -void AbstractExecution::updateStateOnAddr(const AddrStmt* addr) { - const ICFGNode* node = addr->getICFGNode(); - AbstractState& as = getAbsStateFromTrace(node); - as.initObjVar(SVFUtil::cast(addr->getRHSVar())); - as[addr->getLHSVarID()] = as[addr->getRHSVarID()]; -} - -/// Abstract state updates on an CmpStmt -void AbstractExecution::updateStateOnCmp(const CmpStmt* cmp) { - const ICFGNode* node = cmp->getICFGNode(); - AbstractState& as = getAbsStateFromTrace(node); - u32_t op0 = cmp->getOpVarID(0); - u32_t op1 = cmp->getOpVarID(1); - u32_t res = cmp->getResID(); - if (as.inVarToValTable(op0) && as.inVarToValTable(op1)) { - IntervalValue resVal; - IntervalValue &lhs = as[op0].getInterval(), &rhs = as[op1].getInterval(); - // AbstractValue - auto predicate = cmp->getPredicate(); - switch (predicate) { - case CmpStmt::ICMP_EQ: - case CmpStmt::FCMP_OEQ: - case CmpStmt::FCMP_UEQ: resVal = (lhs == rhs); break; - case CmpStmt::ICMP_NE: - case CmpStmt::FCMP_ONE: - case CmpStmt::FCMP_UNE: resVal = (lhs != rhs); break; - case CmpStmt::ICMP_UGT: - case CmpStmt::ICMP_SGT: - case CmpStmt::FCMP_OGT: - case CmpStmt::FCMP_UGT: resVal = (lhs > rhs); break; - case CmpStmt::ICMP_UGE: - case CmpStmt::ICMP_SGE: - case CmpStmt::FCMP_OGE: - case CmpStmt::FCMP_UGE: resVal = (lhs >= rhs); break; - case CmpStmt::ICMP_ULT: - case CmpStmt::ICMP_SLT: - case CmpStmt::FCMP_OLT: - case CmpStmt::FCMP_ULT: resVal = (lhs < rhs); break; - case CmpStmt::ICMP_ULE: - case CmpStmt::ICMP_SLE: - case CmpStmt::FCMP_OLE: - case CmpStmt::FCMP_ULE: resVal = (lhs <= rhs); break; - case CmpStmt::FCMP_FALSE: resVal = IntervalValue(0, 0); break; - case CmpStmt::FCMP_TRUE: resVal = IntervalValue(1, 1); break; - default: { - assert(false && "undefined compare: "); - } - } - as[res] = resVal; - } - else if (as.inVarToAddrsTable(op0) && as.inVarToAddrsTable(op1)) { - IntervalValue resVal; - AbstractValue &lhs = as[op0], &rhs = as[op1]; - auto predicate = cmp->getPredicate(); - switch (predicate) { - case CmpStmt::ICMP_EQ: - case CmpStmt::FCMP_OEQ: - case CmpStmt::FCMP_UEQ: { - if (lhs.getAddrs().size() == 1 && rhs.getAddrs().size() == 1) { - resVal = IntervalValue(lhs.equals(rhs)); - } - else { - if (lhs.getAddrs().hasIntersect(rhs.getAddrs())) { - resVal = IntervalValue::top(); - } - else { - resVal = IntervalValue(0); - } - } - break; - } - case CmpStmt::ICMP_NE: - case CmpStmt::FCMP_ONE: - case CmpStmt::FCMP_UNE: { - if (lhs.getAddrs().size() == 1 && rhs.getAddrs().size() == 1) { - resVal = IntervalValue(!lhs.equals(rhs)); - } - else { - if (lhs.getAddrs().hasIntersect(rhs.getAddrs())) { - resVal = IntervalValue::top(); - } - else { - resVal = IntervalValue(1); - } - } - break; - } - case CmpStmt::ICMP_UGT: - case CmpStmt::ICMP_SGT: - case CmpStmt::FCMP_OGT: - case CmpStmt::FCMP_UGT: { - if (lhs.getAddrs().size() == 1 && rhs.getAddrs().size() == 1) { - resVal = IntervalValue(*lhs.getAddrs().begin() > *rhs.getAddrs().begin()); - } - else { - resVal = IntervalValue::top(); - } - break; - } - case CmpStmt::ICMP_UGE: - case CmpStmt::ICMP_SGE: - case CmpStmt::FCMP_OGE: - case CmpStmt::FCMP_UGE: { - if (lhs.getAddrs().size() == 1 && rhs.getAddrs().size() == 1) { - resVal = IntervalValue(*lhs.getAddrs().begin() >= *rhs.getAddrs().begin()); - } - else { - resVal = IntervalValue::top(); - } - break; - } - case CmpStmt::ICMP_ULT: - case CmpStmt::ICMP_SLT: - case CmpStmt::FCMP_OLT: - case CmpStmt::FCMP_ULT: { - if (lhs.getAddrs().size() == 1 && rhs.getAddrs().size() == 1) { - resVal = IntervalValue(*lhs.getAddrs().begin() < *rhs.getAddrs().begin()); - } - else { - resVal = IntervalValue::top(); - } - break; - } - case CmpStmt::ICMP_ULE: - case CmpStmt::ICMP_SLE: - case CmpStmt::FCMP_OLE: - case CmpStmt::FCMP_ULE: { - if (lhs.getAddrs().size() == 1 && rhs.getAddrs().size() == 1) { - resVal = IntervalValue(*lhs.getAddrs().begin() <= *rhs.getAddrs().begin()); - } - else { - resVal = IntervalValue::top(); - } - break; - } - case CmpStmt::FCMP_FALSE: resVal = IntervalValue(0, 0); break; - case CmpStmt::FCMP_TRUE: resVal = IntervalValue(1, 1); break; - default: { - assert(false && "undefined compare: "); - } - } - as[res] = resVal; - } + // TODO: iterate the cycle body to a fixpoint (widening optional). } -/// Abstract state updates on an CallPE (phi-like: formal = join(actual1@cs1, actual2@cs2, ...)) -void AbstractExecution::updateStateOnCall(const CallPE* callPE) { - const ICFGNode* node = callPE->getICFGNode(); - AbstractState& as = getAbsStateFromTrace(node); - NodeID res = callPE->getResID(); - AbstractValue rhs; - for (u32_t i = 0; i < callPE->getOpVarNum(); i++) - { - NodeID curId = callPE->getOpVarID(i); - const ICFGNode* opICFGNode = callPE->getOpCallICFGNode(i); - if (postAbsTrace().count(opICFGNode)) - { - AbstractState& opAs = postAbsTrace()[opICFGNode]; - rhs.join_with(opAs[curId]); - } - } - as[res] = rhs; +void AbstractExecution::bufOverflowDetection(const ICFGNode* node) { + // TODO: detect out-of-bounds memory accesses at `node`. } -/// Abstract state updates on an RetPE -void AbstractExecution::updateStateOnRet(const RetPE* retPE) { - const ICFGNode* node = retPE->getICFGNode(); - AbstractState& as = getAbsStateFromTrace(node); - NodeID lhs = retPE->getLHSVarID(); - NodeID rhs = retPE->getRHSVarID(); - as[lhs] = as[rhs]; +void AbstractExecution::nullptrDerefDetection(const ICFGNode* node) { + // TODO: detect nullptr dereferences at `node`. } -/// Abstract state updates on an SelectStmt -void AbstractExecution::updateStateOnSelect(const SelectStmt* select) { - const ICFGNode* node = select->getICFGNode(); - AbstractState& as = getAbsStateFromTrace(node); - u32_t res = select->getResID(); - u32_t tval = select->getTrueValue()->getId(); - u32_t fval = select->getFalseValue()->getId(); - u32_t cond = select->getCondition()->getId(); - if (as[cond].getInterval().is_numeral()) { - as[res] = as[cond].getInterval().is_zero() ? as[fval] : as[tval]; - } - else { - as[res].join_with(as[tval]); - as[res].join_with(as[fval]); - } +void AbstractExecution::updateStateOnExtCall(const SVF::CallICFGNode* call) { + // TODO: model memory/string library calls and assignment-specific stubs. } - -void AbstractExecution::updateStateOnExtCall(const SVF::CallICFGNode* extCallNode) { - std::string funcName = extCallNode->getCalledFunction()->getName(); - //TODO: handle external calls - // void mem_insert(void *buffer, const void *data, size_t data_size, size_t position); - if (funcName == "mem_insert") { - // check sizeof(buffer) > position + data_size - /// TODO: your code starts from here - - } - // void str_insert(void *buffer, const void *data, size_t position); - else if (funcName == "str_insert") { - // check sizeof(buffer) > position + strlen(data) - /// TODO: your code starts from here - - } -} \ No newline at end of file diff --git a/Assignment-3/CPP/Assignment_3.h b/Assignment-3/CPP/Assignment_3.h index ccfde23..a4e0231 100644 --- a/Assignment-3/CPP/Assignment_3.h +++ b/Assignment-3/CPP/Assignment_3.h @@ -25,16 +25,17 @@ * Created on: Feb 19, 2024 */ #include "Assignment_3_Helper.h" -#include "AE/Svfexe/AbsExtAPI.h" -#include "AE/Svfexe/AbstractInterpretation.h" #include "SVFIR/SVFIR.h" +#include namespace SVF { + class AndersenWaveDiff; /// Abstract Execution class class AbstractExecution { public: /// Constructor - AbstractExecution() { + explicit AbstractExecution(const AssignmentCaseConfig& config = AssignmentCaseConfig()) + : caseConfig(config) { } virtual void runOnModule(ICFG* icfg); @@ -55,71 +56,58 @@ namespace SVF { virtual void updateAbsState(const SVFStmt* stmt); /// Fuction used to implement buffer overflow detection - virtual void bufOverflowDetection(const SVFStmt* stmt); + virtual void bufOverflowDetection(const ICFGNode* node); + /// Function used to implement nullptr dereference detection + virtual void nullptrDerefDetection(const ICFGNode* node); /// Report a buffer overflow for a given ICFG node void reportBufOverflow(const ICFGNode* node); + /// Report a nullptr dereference for a given ICFG node + void reportNullDeref(const ICFGNode* node); - // handle SVF Statements - ///@{ - void updateStateOnAddr(const AddrStmt* addr); - void updateStateOnGep(const GepStmt* gep); - void updateStateOnStore(const StoreStmt* store); - void updateStateOnLoad(const LoadStmt* load); - void updateStateOnCmp(const CmpStmt* cmp); - void updateStateOnCall(const CallPE* call); - void updateStateOnRet(const RetPE* retPE); - void updateStateOnCopy(const CopyStmt* copy); - void updateStateOnPhi(const PhiStmt* phi); - void updateStateOnBinary(const BinaryOPStmt* binary); - void updateStateOnSelect(const SelectStmt* select); + /// External-API value summaries (student TODO). void updateStateOnExtCall(const SVF::CallICFGNode* extCallNode); - ///@} /// Handle stub functions for verifying abstract interpretation results void handleStubFunctions(const CallICFGNode* call); - /// Mark recursive functions in the call graph + /// Build the (interprocedural) WTO for each call-graph SCC entry. void initWTO(); - /// Path feasiblity handling - ///@{ + /// Merge predecessor states into the current node's pre-state. bool mergeStatesFromPredecessors(const ICFGNode* curNode, AbstractState& as); - bool isCmpBranchFeasible(const CmpStmt* cmpStmt, s64_t succ, AbstractState& as); - bool isSwitchBranchFeasible(const SVFVar* var, s64_t succ, AbstractState& as); - bool isBranchFeasible(const IntraCFGEdge* intraEdge, AbstractState& as); - ///@} - /// Handle a call site in the control flow graph void handleCallSite(const CallICFGNode* callnode); + /// Validate the SAFE/UNSAFE checkpoint stub functions + void handleCheckpointStubs(const CallICFGNode* callnode); bool isExternalCallForAssignment(const SVF::FunObjVar* func); - /// Handle a function in the ICFG + /// Handle a function in the ICFG void handleFunction(const ICFGNode* funEntry); - /// Get the next nodes of a node - std::vector getNextNodes(const ICFGNode* node) const; - /// Get the next nodes of a cycle - std::vector getNextNodesOfCycle(const ICFGCycleWTO* cycle) const; - bool handleICFGNode(const ICFGNode* node); void handleICFGCycle(const ICFGCycleWTO* cycle); - /// Return its abstract state given an ICFGNode - AbstractState& getAbsStateFromTrace(const ICFGNode* node) { - return (*svfStateMgr)[node]; - } - - /// Update the offset of a GEP (GetElementPtr) object from its base address - void updateGepObjOffsetFromBase(AbstractState& as, AddressValue gepAddrs, AddressValue objAddrs, IntervalValue offset); - - /// Return the accessing offset of an object at a GepStmt - IntervalValue getAccessOffset(NodeID objId, const GepStmt* gep); + /// Return its abstract state given an ICFGNode (defined in the helper). + AbstractState& getAbsStateFromTrace(const ICFGNode* node); void ensureAllAssertsValidated(); + /// Case-based grading/reporting helpers. These are intentionally + /// end-to-end: the grader should score TP/FP/time/coverage per case, + /// while module tags are only diagnosis hints. + void writeJsonSummary(std::ostream& os, double wallSeconds, int exitCode, + bool assertsValidated) const; + u32_t getAnalyzedNodeCount() const; + u32_t getTotalNodeCount() const; + double getICFGCoverage() const; + bool hasTargetReport() const; + const AssignmentCaseConfig& getCaseConfig() const { + return caseConfig; + } + /// Destructor virtual ~AbstractExecution() { // svfStateMgr is the AbstractInterpretation singleton; SVF owns its lifetime. @@ -129,31 +117,37 @@ namespace SVF { /// SVFIR and ICFG SVFIR* svfir; ICFG* icfg; - /// Owns the abstract trace immediately after an ICFGNode (post trace). - /// AbsExtAPI and the GEP/load/store helpers (getGepByteOffset etc.) - /// read and write through this manager; we don't keep a separate - /// postAbsTrace map any more. - AbstractInterpretation* svfStateMgr = nullptr; - + /// Narrow state-manager facade used by all student code: it forwards only + /// the whitelisted state read/write and GEP primitives and exposes no + /// path to the SVF external-API modeller (see Ass3StateManager). + Ass3StateManager* svfStateMgr = nullptr; + + /// Andersen pointer analysis (owns the call graph + SCC used to drive + /// the interprocedural WTO); created in initWTO(). + AndersenWaveDiff* ander = nullptr; /// Map a function to its corresponding WTO Map funcToWTO; - /// A set of functions which are involved in recursions - Set recursiveFuns; + /// Functions whose WTO is currently being iterated; re-entry returns + /// early so the outer cycle drives the recursion to a fixpoint. + Set _funcsInFlight; /// Abstract trace immediately before an ICFGNode. Map preAbsTrace; - /// Convenience alias: the "post" trace lives inside svfStateMgr. - Map& postAbsTrace() { - return svfStateMgr->getTrace(); - } + /// The "post" trace lives inside the manager (defined in the helper). + Map& postAbsTrace(); private: - AbstractExecutionHelper bufOverflowHelper; + AssignmentCaseConfig caseConfig; + + AbstractExecutionHelper bugReporter; Set assert_points; + Set analyzedNodes; Map cycleHeadToCycle; - AbsExtAPI* utils; + /// harness-only raw handle to the underlying state manager; never used by + /// student code (which only sees the svfStateMgr facade above). + AbstractInterpretation* ai = nullptr; }; } // namespace SVF diff --git a/Assignment-3/CPP/Assignment_3_Helper.cpp b/Assignment-3/CPP/Assignment_3_Helper.cpp index e8174af..2bcb60d 100644 --- a/Assignment-3/CPP/Assignment_3_Helper.cpp +++ b/Assignment-3/CPP/Assignment_3_Helper.cpp @@ -26,86 +26,102 @@ */ #include "Assignment_3.h" +// harness-only: the facade implementation and the post-trace accessor need the +// full AbstractInterpretation definition. Student code (Assignment_3.cpp) never +// includes this header, so it cannot reach AbsExtAPI / getUtils. +#include "AE/Svfexe/AbstractInterpretation.h" #include "WPA/Andersen.h" +#include +#include using namespace SVF; -// according to varieties of cmp insts, -// maybe var X var, var X const, const X var, const X const -// we accept 'var X const' 'var X var' 'const X const' -// if 'const X var', we need to reverse op0 op1 and its predicate 'var X' const' -// X' is reverse predicate of X -// == -> !=, != -> ==, > -> <=, >= -> <, < -> >=, <= -> > -Map _reverse_predicate = { - {CmpStmt::Predicate::FCMP_OEQ, CmpStmt::Predicate::FCMP_ONE}, // == -> != - {CmpStmt::Predicate::FCMP_UEQ, CmpStmt::Predicate::FCMP_UNE}, // == -> != - {CmpStmt::Predicate::FCMP_OGT, CmpStmt::Predicate::FCMP_OLE}, // > -> <= - {CmpStmt::Predicate::FCMP_OGE, CmpStmt::Predicate::FCMP_OLT}, // >= -> < - {CmpStmt::Predicate::FCMP_OLT, CmpStmt::Predicate::FCMP_OGE}, // < -> >= - {CmpStmt::Predicate::FCMP_OLE, CmpStmt::Predicate::FCMP_OGT}, // <= -> > - {CmpStmt::Predicate::FCMP_ONE, CmpStmt::Predicate::FCMP_OEQ}, // != -> == - {CmpStmt::Predicate::FCMP_UNE, CmpStmt::Predicate::FCMP_UEQ}, // != -> == - {CmpStmt::Predicate::ICMP_EQ, CmpStmt::Predicate::ICMP_NE}, // == -> != - {CmpStmt::Predicate::ICMP_NE, CmpStmt::Predicate::ICMP_EQ}, // != -> == - {CmpStmt::Predicate::ICMP_UGT, CmpStmt::Predicate::ICMP_ULE}, // > -> <= - {CmpStmt::Predicate::ICMP_ULT, CmpStmt::Predicate::ICMP_UGE}, // < -> >= - {CmpStmt::Predicate::ICMP_UGE, CmpStmt::Predicate::ICMP_ULT}, // >= -> < - {CmpStmt::Predicate::ICMP_SGT, CmpStmt::Predicate::ICMP_SLE}, // > -> <= - {CmpStmt::Predicate::ICMP_SLT, CmpStmt::Predicate::ICMP_SGE}, // < -> >= - {CmpStmt::Predicate::ICMP_SGE, CmpStmt::Predicate::ICMP_SLT}, // >= -> < -}; - -Map _switch_lhsrhs_predicate = { - {CmpStmt::Predicate::FCMP_OEQ, CmpStmt::Predicate::FCMP_OEQ}, // == -> == - {CmpStmt::Predicate::FCMP_UEQ, CmpStmt::Predicate::FCMP_UEQ}, // == -> == - {CmpStmt::Predicate::FCMP_OGT, CmpStmt::Predicate::FCMP_OLT}, // > -> < - {CmpStmt::Predicate::FCMP_OGE, CmpStmt::Predicate::FCMP_OLE}, // >= -> <= - {CmpStmt::Predicate::FCMP_OLT, CmpStmt::Predicate::FCMP_OGT}, // < -> > - {CmpStmt::Predicate::FCMP_OLE, CmpStmt::Predicate::FCMP_OGE}, // <= -> >= - {CmpStmt::Predicate::FCMP_ONE, CmpStmt::Predicate::FCMP_ONE}, // != -> != - {CmpStmt::Predicate::FCMP_UNE, CmpStmt::Predicate::FCMP_UNE}, // != -> != - {CmpStmt::Predicate::ICMP_EQ, CmpStmt::Predicate::ICMP_EQ}, // == -> == - {CmpStmt::Predicate::ICMP_NE, CmpStmt::Predicate::ICMP_NE}, // != -> != - {CmpStmt::Predicate::ICMP_UGT, CmpStmt::Predicate::ICMP_ULT}, // > -> < - {CmpStmt::Predicate::ICMP_ULT, CmpStmt::Predicate::ICMP_UGT}, // < -> > - {CmpStmt::Predicate::ICMP_UGE, CmpStmt::Predicate::ICMP_ULE}, // >= -> <= - {CmpStmt::Predicate::ICMP_SGT, CmpStmt::Predicate::ICMP_SLT}, // > -> < - {CmpStmt::Predicate::ICMP_SLT, CmpStmt::Predicate::ICMP_SGT}, // < -> > - {CmpStmt::Predicate::ICMP_SGE, CmpStmt::Predicate::ICMP_SLE}, // >= -> <= -}; - - -IntervalValue AbstractExecution::getAccessOffset(NodeID objId, const GepStmt* gep) { - auto obj = svfir->getGNode(objId); - AbstractState& as = getAbsStateFromTrace(gep->getICFGNode()); - // Field-insensitive base object - if (SVFUtil::isa(obj)) { - // get base size - IntervalValue accessOffset = svfStateMgr->getGepByteOffset(gep); - return accessOffset; - } - // A sub object of an aggregate object - else if (SVFUtil::isa(obj)) { - IntervalValue accessOffset = - bufOverflowHelper.getGepObjOffsetFromBase(SVFUtil::cast(obj)) + svfStateMgr->getGepByteOffset(gep); - return accessOffset; - } - else{ - assert(SVFUtil::isa(obj) && "What other types of object?"); - return IntervalValue::top(); +std::string SVF::ass3JsonEscape(const std::string& input) { + std::ostringstream os; + for (char ch : input) { + switch (ch) { + case '"': os << "\\\""; break; + case '\\': os << "\\\\"; break; + case '\b': os << "\\b"; break; + case '\f': os << "\\f"; break; + case '\n': os << "\\n"; break; + case '\r': os << "\\r"; break; + case '\t': os << "\\t"; break; + default: + if (static_cast(ch) < 0x20) { + os << "\\u" << std::hex << std::setw(4) << std::setfill('0') + << static_cast(static_cast(ch)) + << std::dec << std::setfill(' '); + } + else { + os << ch; + } + } } + return os.str(); +} + +static std::string ass3BaseName(const std::string& path) { + size_t slash = path.find_last_of("/\\"); + if (slash == std::string::npos) + return path; + return path.substr(slash + 1); } +static bool ass3ReportMatchesTarget(const AssignmentBugReport& report, + const std::string& target) { + if (target.empty()) + return false; + if (report.location.find(target) != std::string::npos || + report.message.find(target) != std::string::npos) + return true; + + size_t colon = target.rfind(':'); + if (colon == std::string::npos) + return false; + std::string file = ass3BaseName(target.substr(0, colon)); + std::string line = target.substr(colon + 1); + if (file.empty() || line.empty()) + return false; + + bool fileSeen = report.location.find(file) != std::string::npos || + report.message.find(file) != std::string::npos; + bool lineSeen = report.location.find("\"ln\": " + line) != std::string::npos || + report.location.find("\"ln\":" + line) != std::string::npos || + report.message.find("\"ln\": " + line) != std::string::npos || + report.message.find("\"ln\":" + line) != std::string::npos || + report.location.find(":" + line) != std::string::npos || + report.message.find(":" + line) != std::string::npos; + return fileSeen && lineSeen; +} + +// Branch refinement, statement transfer functions (updateAbsState + +// updateStateOn*), the GEP-offset tracking, getAccessOffset, the memory-safety +// helpers (canSafelyAccessMemory / canSafelyDerefPtr) and the buffer/null +// checkers are all student TODOs this year and live in Assignment_3.cpp. The +// stub validators in this file deliberately do not call them — they compute +// ground truth from SVF primitives only, then query the BugReporter for the +// student's verdict. + /// Report a buffer overflow for a given ICFG node void AbstractExecution::reportBufOverflow(const ICFGNode* node) { // Create an exception with the node's string representation AEException bug(node->toString()); // Add the bug to the reporter using the helper - bufOverflowHelper.addBugToReporter(bug, node); + bugReporter.addBugToReporter("buffer-overflow", bug, node); +} + +/// Report a nullptr dereference for a given ICFG node +void AbstractExecution::reportNullDeref(const ICFGNode* node) { + AEException bug(node->toString()); + bugReporter.addBugToReporter("nullptr-deref", bug, node); } bool AbstractExecution::isExternalCallForAssignment(const SVF::FunObjVar* func) { - Set extFuncs = {"mem_insert", "str_insert"}; + Set extFuncs = { + "mem_insert", "str_insert", + "UNSAFE_BUFACCESS", "SAFE_BUFACCESS", + "UNSAFE_PTRDEREF", "SAFE_PTRDEREF"}; if (extFuncs.find(func->getName()) != extFuncs.end()) { return true; } else { @@ -117,383 +133,149 @@ void AbstractExecution::runOnModule(SVF::ICFG* _icfg) { svfir = PAG::getPAG(); icfg = _icfg; analyse(); - bufOverflowHelper.printReport(); + if (!caseConfig.emitJson) + bugReporter.printReport(); } -/** - * @brief Mark recursive functions in the call graph - * - * This function identifies and marks recursive functions in the call graph. - * It does this by detecting cycles in the call graph's strongly connected components (SCC). - * Any function found to be part of a cycle is marked as recursive. - */ -void AbstractExecution::initWTO() { - AndersenWaveDiff* ander = AndersenWaveDiff::createAndersenWaveDiff(svfir); - // Detect if the call graph has cycles by finding its strongly connected components (SCC) - Andersen::CallGraphSCC* callGraphScc = ander->getCallGraphSCC(); - callGraphScc->find(); - auto callGraph = ander->getCallGraph(); +u32_t AbstractExecution::getAnalyzedNodeCount() const { + return static_cast(analyzedNodes.size()); +} - // Iterate through the call graph - for (auto it = callGraph->begin(); it != callGraph->end(); it++) { - // Check if the current function is part of a cycle - if (callGraphScc->isInCycle(it->second->getId())) - recursiveFuns.insert(it->second->getFunction()); // Mark the function as recursive - } +u32_t AbstractExecution::getTotalNodeCount() const { + if (!icfg) + return 0; + u32_t total = 0; + for (auto it = icfg->begin(); it != icfg->end(); ++it) + total++; + return total; +} - // Initialize WTO for each function in the module - for (const auto& item : *callGraph) { - const FunObjVar* fun = item.second->getFunction(); - if(fun->isDeclaration()) - continue; - auto* wto = new ICFGWTO(icfg->getFunEntryICFGNode(fun)); - wto->init(); - funcToWTO[fun] = wto; - } - for (auto fun: funcToWTO) { - for (const ICFGWTOComp* comp : fun.second->getWTOComponents()) { - if (const ICFGCycleWTO* cycle = SVFUtil::dyn_cast(comp)) { - cycleHeadToCycle[cycle->head()->getICFGNode()] = cycle; - } - } - } +double AbstractExecution::getICFGCoverage() const { + u32_t total = getTotalNodeCount(); + if (total == 0) + return 0.0; + return 100.0 * static_cast(getAnalyzedNodeCount()) / static_cast(total); } -/** - * @brief Update the offset of a GEP (GetElementPtr) object from its base address - * - * This function updates the offset of a GEP object from its base address in the abstract state. - * It handles both field-insensitive base objects and sub-objects of aggregate objects. - * The function calculates the new offset based on the provided GEP addresses and the offset interval. - * - * @param as The abstract state in this context - * @param gepAddrs The set of addresses for the GEP object - * @param objAddrs The set of addresses for the object - * @param offset The interval value representing the offset - */ -void AbstractExecution::updateGepObjOffsetFromBase(AbstractState& as, SVF::AddressValue gepAddrs, SVF::AddressValue objAddrs, - SVF::IntervalValue offset) -{ - for (const auto& objAddr : objAddrs) { - NodeID objId = as.getIDFromAddr(objAddr); - auto obj = svfir->getGNode(objId); - if (SVFUtil::isa(obj)) { - for (const auto& gepAddr : gepAddrs) { - NodeID gepObj = as.getIDFromAddr(gepAddr); - const GepObjVar* gepObjVar = SVFUtil::cast(svfir->getGNode(gepObj)); - bufOverflowHelper.addToGepObjOffsetFromBase(gepObjVar, offset); - } - } - else if (SVFUtil::isa(obj)) { - const GepObjVar* objVar = SVFUtil::cast(obj); - for (const auto& gepAddr : gepAddrs) { - NodeID gepObj = as.getIDFromAddr(gepAddr); - const GepObjVar* gepObjVar = SVFUtil::cast(svfir->getGNode(gepObj)); - if (bufOverflowHelper.hasGepObjOffsetFromBase(objVar)) { - IntervalValue objOffsetFromBase = bufOverflowHelper.getGepObjOffsetFromBase(objVar); - /// make sure gepObjVar has not been written before - if (!bufOverflowHelper.hasGepObjOffsetFromBase(gepObjVar)) - bufOverflowHelper.addToGepObjOffsetFromBase(gepObjVar, objOffsetFromBase + offset); - } - else { - assert(false && "gepRhsObjVar has no gepObjOffsetFromBase"); - } - } - } +bool AbstractExecution::hasTargetReport() const { + if (caseConfig.targetLoc.empty()) + return false; + for (const AssignmentBugReport& report : bugReporter.getReports()) { + if (ass3ReportMatchesTarget(report, caseConfig.targetLoc)) + return true; } + return false; +} + +void AbstractExecution::writeJsonSummary(std::ostream& os, double wallSeconds, + int exitCode, bool assertsValidated) const { + const auto& reports = bugReporter.getReports(); + const bool targetHit = hasTargetReport(); + const u32_t tp = caseConfig.targetLoc.empty() ? 0 : (targetHit ? 1 : 0); + const u32_t fp = reports.size() > tp ? static_cast(reports.size() - tp) : 0; + + os << "{\n"; + os << " \"case_id\": \"" << ass3JsonEscape(caseConfig.caseId) << "\",\n"; + os << " \"target\": \"" << ass3JsonEscape(caseConfig.targetLoc) << "\",\n"; + os << " \"tags\": \"" << ass3JsonEscape(caseConfig.tags) << "\",\n"; + os << " \"exit_code\": " << exitCode << ",\n"; + os << " \"asserts_validated\": " << (assertsValidated ? "true" : "false") << ",\n"; + os << " \"tp\": " << tp << ",\n"; + os << " \"fp\": " << fp << ",\n"; + os << " \"reports\": " << reports.size() << ",\n"; + os << " \"wall_sec\": " << std::fixed << std::setprecision(3) << wallSeconds << ",\n"; + os << " \"icfg_nodes\": " << getTotalNodeCount() << ",\n"; + os << " \"analyzed_icfg_nodes\": " << getAnalyzedNodeCount() << ",\n"; + os << " \"icfg_coverage\": " << std::fixed << std::setprecision(2) << getICFGCoverage() << ",\n"; + os << " \"report_list\": ["; + for (size_t i = 0; i < reports.size(); ++i) { + const AssignmentBugReport& report = reports[i]; + os << (i == 0 ? "\n" : ",\n"); + os << " {\"kind\": \"" << ass3JsonEscape(report.kind) + << "\", \"node\": " << report.nodeId + << ", \"location\": \"" << ass3JsonEscape(report.location) + << "\", \"message\": \"" << ass3JsonEscape(report.message) << "\"}"; + } + if (!reports.empty()) + os << "\n "; + os << "]\n"; + os << "}\n"; } /** - * @brief Propagate the states from predecessors to the current node and return true if the control-flow is feasible - * - * This function attempts to propagate the execution state to a given block by merging the states - * of its predecessor blocks. It handles two scenarios: intra-block edges and call edges. - * Scenario 1: preblock -----(intraEdge)----> block, join the preES of inEdges - * Scenario 2: preblock -----(callEdge)----> block - * If the propagation is feasible, it updates the execution state and returns true. Otherwise, it returns false. + * @brief Build the interprocedural WTO per call-graph SCC entry. * - * @param block The ICFG node (block) for which the state propagation is attempted - * @return bool True if the state propagation is feasible and successful, false otherwise + * Each (mutually) recursive function's entry node becomes a WTO cycle head + * because intra-SCC call edges are turned into back-edges. The same + * widening/narrowing machinery used for loops then drives recursion to a + * fixpoint via handleICFGCycle; there is no separate "is recursive?" check. */ -bool AbstractExecution::mergeStatesFromPredecessors(const ICFGNode* block, AbstractState& as) { - u32_t inEdgeNum = 0; // Initialize the number of incoming edges with feasible states - as = AbstractState(); - // Iterate over all incoming edges of the given block - for (auto& edge : block->getInEdges()) { - // Check if the source node of the edge has a post-execution state recorded - if (postAbsTrace().find(edge->getSrcNode()) != postAbsTrace().end()) { - const IntraCFGEdge* intraCfgEdge = SVFUtil::dyn_cast(edge); - - // If the edge is an intra-block edge and has a condition - if (intraCfgEdge && intraCfgEdge->getCondition()) { - AbstractState tmpEs = postAbsTrace()[edge->getSrcNode()]; - // Check if the branch condition is feasible - if (isBranchFeasible(intraCfgEdge, tmpEs)) { - as.joinWith(tmpEs); // Merge the state with the current state - inEdgeNum++; - } - // If branch is not feasible, do nothing - } - else { - // For non-conditional edges, directly merge the state - as.joinWith(postAbsTrace()[edge->getSrcNode()]); - inEdgeNum++; - } - } - // If no post-execution state is recorded for the source node, do nothing - } - - // If no incoming edges have feasible states, return false - if (inEdgeNum == 0) { - return false; - } - else { - return true; - } - assert(false && "implement this part"); // This part should not be reached -} +void AbstractExecution::initWTO() { + ander = AndersenWaveDiff::createAndersenWaveDiff(svfir); + // Find the strongly connected components of the call graph so we can hand + // each SCC's member set to ICFGWTO below. + Andersen::CallGraphSCC* callGraphScc = ander->getCallGraphSCC(); + callGraphScc->find(); + auto callGraph = ander->getCallGraph(); -bool AbstractExecution::isCmpBranchFeasible(const CmpStmt* cmpStmt, s64_t succ, AbstractState& as) { - AbstractState new_es = as; - // get cmp stmt's op0, op1, and predicate - NodeID op0 = cmpStmt->getOpVarID(0); - NodeID op1 = cmpStmt->getOpVarID(1); - NodeID res_id = cmpStmt->getResID(); - s32_t predicate = cmpStmt->getPredicate(); + // Build one interprocedural WTO per call-graph-SCC entry function. The + // SCC's function set is passed to ICFGWTO so that call edges *into* a + // callee in the same SCC become back-edges: a (mutually) recursive + // function's entry node then shows up as a WTO cycle head, and the same + // widening/narrowing machinery used for loops drives it to a fixpoint. + for (auto it = callGraph->begin(); it != callGraph->end(); ++it) { + const FunObjVar* fun = it->second->getFunction(); + if (fun->isDeclaration()) + continue; - // if op0 or op1 is undefined, return; - // skip address compare - if (new_es.inVarToAddrsTable(op0) || new_es.inVarToAddrsTable(op1)) { - as = new_es; - return true; - } - // get '%1 = load i32 s', and load inst may not exist - auto getLoadOp = [](SVFVar* opVar) -> const LoadStmt* { - if (!opVar->getInEdges().empty()) { - SVFStmt* loadVar0InStmt = *opVar->getInEdges().begin(); - if (const LoadStmt* loadStmt = SVFUtil::dyn_cast(loadVar0InStmt)) { - return loadStmt; - } - else if (const CopyStmt* copyStmt = SVFUtil::dyn_cast(loadVar0InStmt)) { - if (!copyStmt->getRHSVar()->getInEdges().empty()) { - SVFStmt* loadVar0InStmt2 = *opVar->getInEdges().begin(); - if (const LoadStmt* loadStmt = SVFUtil::dyn_cast(loadVar0InStmt2)) { - return loadStmt; - } - } - } - } - return nullptr; - }; - const LoadStmt* load_op0 = getLoadOp(svfir->getGNode(op0)); - const LoadStmt* load_op1 = getLoadOp(svfir->getGNode(op1)); - - // for const X const, we may get concrete resVal instantly - // for var X const, we may get [0,1] if the intersection of var and const is not empty set - IntervalValue resVal = new_es[res_id].getInterval(); - resVal.meet_with(IntervalValue((s64_t)succ, succ)); - // If Var X const generates bottom value, it means this branch path is not feasible. - if (resVal.isBottom()) { - return false; - } + NodeID repNodeId = callGraphScc->repNode(it->second->getId()); + const NodeBS& cgSCCNodes = callGraphScc->subNodes(repNodeId); + + // Only SCC-entry functions (with a caller outside the SCC, or no + // caller at all) own a WTO; intra-SCC members are reached via the + // entry's interprocedural WTO. + bool isEntry = it->second->getInEdges().empty(); + for (auto inEdge : it->second->getInEdges()) + if (!cgSCCNodes.test(inEdge->getSrcID())) + isEntry = true; + if (!isEntry) + continue; - bool b0 = new_es[op0].getInterval().is_numeral(); - bool b1 = new_es[op1].getInterval().is_numeral(); + Set funcScc; + for (const auto& node : cgSCCNodes) + funcScc.insert(callGraph->getGNode(node)->getFunction()); - // if const X var, we should reverse op0 and op1. - if (b0 && !b1) { - std::swap(op0, op1); - std::swap(load_op0, load_op1); - predicate = _switch_lhsrhs_predicate[predicate]; - } - else { - // if var X var, we cannot preset the branch condition to infer the intervals of var0,var1 - if (!b0 && !b1) { - as = new_es; - return true; - } - // if const X const, we can instantly get the resVal - else if (b0 && b1) { - as = new_es; - return true; - } - } - // if cmp is 'var X const == false', we should reverse predicate 'var X' const == true' - // X' is reverse predicate of X - if (succ == 0) { - predicate = _reverse_predicate[predicate]; - } - else { - } - // change interval range according to the compare predicate - AddressValue addrs; - if (load_op0 && new_es.inVarToAddrsTable(load_op0->getRHSVarID())) - addrs = new_es[load_op0->getRHSVarID()].getAddrs(); - - IntervalValue &lhs = new_es[op0].getInterval(), &rhs = new_es[op1].getInterval(); - switch (predicate) { - case CmpStmt::Predicate::ICMP_EQ: - case CmpStmt::Predicate::FCMP_OEQ: - case CmpStmt::Predicate::FCMP_UEQ: { - // Var == Const, so [var.lb, var.ub].meet_with(const) - lhs.meet_with(rhs); - break; - } - case CmpStmt::Predicate::ICMP_NE: - case CmpStmt::Predicate::FCMP_ONE: - case CmpStmt::Predicate::FCMP_UNE: - // Compliment set - break; - case CmpStmt::Predicate::ICMP_UGT: - case CmpStmt::Predicate::ICMP_SGT: - case CmpStmt::Predicate::FCMP_OGT: - case CmpStmt::Predicate::FCMP_UGT: - // Var > Const, so [var.lb, var.ub].meet_with([Const+1, +INF]) - lhs.meet_with(IntervalValue(rhs.lb() + 1, IntervalValue::plus_infinity())); - break; - case CmpStmt::Predicate::ICMP_UGE: - case CmpStmt::Predicate::ICMP_SGE: - case CmpStmt::Predicate::FCMP_OGE: - case CmpStmt::Predicate::FCMP_UGE: { - // Var >= Const, so [var.lb, var.ub].meet_with([Const, +INF]) - lhs.meet_with(IntervalValue(rhs.lb(), IntervalValue::plus_infinity())); - break; - } - case CmpStmt::Predicate::ICMP_ULT: - case CmpStmt::Predicate::ICMP_SLT: - case CmpStmt::Predicate::FCMP_OLT: - case CmpStmt::Predicate::FCMP_ULT: { - // Var < Const, so [var.lb, var.ub].meet_with([-INF, const.ub-1]) - lhs.meet_with(IntervalValue(IntervalValue::minus_infinity(), rhs.ub() - 1)); - break; - } - case CmpStmt::Predicate::ICMP_ULE: - case CmpStmt::Predicate::ICMP_SLE: - case CmpStmt::Predicate::FCMP_OLE: - case CmpStmt::Predicate::FCMP_ULE: { - // Var <= Const, so [var.lb, var.ub].meet_with([-INF, const.ub]) - lhs.meet_with(IntervalValue(IntervalValue::minus_infinity(), rhs.ub())); - break; - } - case CmpStmt::Predicate::FCMP_FALSE: break; - case CmpStmt::Predicate::FCMP_TRUE: break; - default: assert(false && "implement this part"); abort(); + auto* wto = new ICFGWTO(icfg->getFunEntryICFGNode(fun), funcScc); + wto->init(); + funcToWTO[fun] = wto; } - for (const auto& addr : addrs) { - NodeID objId = as.getIDFromAddr(addr); - if (new_es.inAddrToValTable(objId)) { - switch (predicate) { - case CmpStmt::Predicate::ICMP_EQ: - case CmpStmt::Predicate::FCMP_OEQ: - case CmpStmt::Predicate::FCMP_UEQ: { - new_es.load(addr).meet_with(rhs); - break; - } - case CmpStmt::Predicate::ICMP_NE: - case CmpStmt::Predicate::FCMP_ONE: - case CmpStmt::Predicate::FCMP_UNE: - // Compliment set - break; - case CmpStmt::Predicate::ICMP_UGT: - case CmpStmt::Predicate::ICMP_SGT: - case CmpStmt::Predicate::FCMP_OGT: - case CmpStmt::Predicate::FCMP_UGT: - // Var > Const, so [var.lb, var.ub].meet_with([Const+1, +INF]) - new_es.load(addr).meet_with(IntervalValue(rhs.lb() + 1, IntervalValue::plus_infinity())); - break; - case CmpStmt::Predicate::ICMP_UGE: - case CmpStmt::Predicate::ICMP_SGE: - case CmpStmt::Predicate::FCMP_OGE: - case CmpStmt::Predicate::FCMP_UGE: { - // Var >= Const, so [var.lb, var.ub].meet_with([Const, +INF]) - new_es.load(addr).meet_with(IntervalValue(rhs.lb(), IntervalValue::plus_infinity())); - break; - } - case CmpStmt::Predicate::ICMP_ULT: - case CmpStmt::Predicate::ICMP_SLT: - case CmpStmt::Predicate::FCMP_OLT: - case CmpStmt::Predicate::FCMP_ULT: { - // Var < Const, so [var.lb, var.ub].meet_with([-INF, const.ub-1]) - new_es.load(addr).meet_with(IntervalValue(IntervalValue::minus_infinity(), rhs.ub() - 1)); - break; - } - case CmpStmt::Predicate::ICMP_ULE: - case CmpStmt::Predicate::ICMP_SLE: - case CmpStmt::Predicate::FCMP_OLE: - case CmpStmt::Predicate::FCMP_ULE: { - // Var <= Const, so [var.lb, var.ub].meet_with([-INF, const.ub]) - new_es.load(addr).meet_with(IntervalValue(IntervalValue::minus_infinity(), rhs.ub())); - break; - } - case CmpStmt::Predicate::FCMP_FALSE: break; - case CmpStmt::Predicate::FCMP_TRUE: break; - default: assert(false && "implement this part"); abort(); + // Record every cycle head (loop heads and recursive-function entries) so + // handleFunction can dispatch them to handleICFGCycle. + for (auto fun : funcToWTO) { + for (const ICFGWTOComp* comp : fun.second->getWTOComponents()) { + if (const ICFGCycleWTO* cycle = SVFUtil::dyn_cast(comp)) { + cycleHeadToCycle[cycle->head()->getICFGNode()] = cycle; } } } - - as = new_es; - return true; } -bool AbstractExecution::isSwitchBranchFeasible(const SVFVar* var, s64_t succ, AbstractState& as) { - AbstractState new_es = as; - IntervalValue& switch_cond = new_es[var->getId()].getInterval(); - s64_t value = succ; - FIFOWorkList workList; - for (SVFStmt* cmpVarInStmt : var->getInEdges()) { - workList.push(cmpVarInStmt); - } - switch_cond.meet_with(IntervalValue(value, value)); - if (switch_cond.isBottom()) { - return false; - } - while (!workList.empty()) { - const SVFStmt* stmt = workList.pop(); - if (SVFUtil::isa(stmt)) { - IntervalValue& copy_cond = new_es[var->getId()].getInterval(); - copy_cond.meet_with(IntervalValue(value, value)); - } - else if (const LoadStmt* load = SVFUtil::dyn_cast(stmt)) { - if (new_es.inVarToAddrsTable(load->getRHSVarID())) { - AddressValue& addrs = new_es[load->getRHSVarID()].getAddrs(); - for (const auto& addr : addrs) { - NodeID objId = as.getIDFromAddr(addr); - if (new_es.inAddrToValTable(objId)) { - new_es.load(addr).meet_with(switch_cond); - } - } - } - } - } - as = new_es; - return true; -} +// updateGepObjOffsetFromBase / hasGepObjOffsetFromBase / getGepObjOffsetFromBase +// / getAccessOffset / updateAbsState / mergeStatesFromPredecessors / branch +// refinement (isCmpBranchFeasible / isSwitchBranchFeasible / isBranchFeasible) +// are all student TODOs this year and live in Assignment_3.cpp. -bool AbstractExecution::isBranchFeasible(const IntraCFGEdge* intraEdge, AbstractState& as) { - const SVFVar* cmpVar = intraEdge->getCondition(); - if (cmpVar->getInEdges().empty()) { - return isSwitchBranchFeasible(cmpVar, intraEdge->getSuccessorCondValue(), as); - } - else { - assert(!cmpVar->getInEdges().empty() && "no in edges?"); - SVFStmt* cmpVarInStmt = *cmpVar->getInEdges().begin(); - if (const CmpStmt* cmpStmt = SVFUtil::dyn_cast(cmpVarInStmt)) { - return isCmpBranchFeasible(cmpStmt, intraEdge->getSuccessorCondValue(), as); - } - else { - return isSwitchBranchFeasible(cmpVar, intraEdge->getSuccessorCondValue(), as); - } - } -} /// handle global node void AbstractExecution::handleGlobalNode() { AbstractState as; const ICFGNode* node = icfg->getGlobalICFGNode(); + analyzedNodes.insert(node); postAbsTrace()[node] = preAbsTrace[node]; - postAbsTrace()[node][0] = AddressValue(); + // The null pointer carries the dedicated null memory address so that + // pointer-vs-null comparisons and null dereferences can be detected. + postAbsTrace()[node][IRGraph::NullPtr] = AddressValue(NullMemAddr); // Global Node, we just need to handle addr, load, store, copy and gep for (const SVFStmt* stmt : node->getSVFStmts()) { updateAbsState(stmt); @@ -528,7 +310,7 @@ void AbstractExecution::ensureAllAssertsValidated() { } } - assert(overflow_assert_to_be_verified <= bufOverflowHelper.getBugReporter().getBugSet().size() && + assert(overflow_assert_to_be_verified <= bugReporter.getBugReporter().getBugSet().size() && "The number of stub asserts (ground truth) should <= the number of overflow reported"); } @@ -547,8 +329,8 @@ void AbstractExecution::analyse() { // header AE/Svfexe/AbstractStateManager.h was removed. Use the singleton // AbstractInterpretation; it pulls SVFIR from PAG::getPAG() internally and // does not need an explicit Andersen analysis to be passed in. - svfStateMgr = &AbstractInterpretation::getAEInstance(); - utils = new AbsExtAPI(svfStateMgr); + ai = &AbstractInterpretation::getAEInstance(); + svfStateMgr = new Ass3StateManager(ai); // Handle the global node handleGlobalNode(); @@ -586,17 +368,24 @@ bool AbstractExecution::handleICFGNode(const ICFGNode* node) { SVFUtil::errs() << "Infeasible for node " << node->getId() << "\n"; return false; } + analyzedNodes.insert(node); preAbsTrace[node] = tmpEs; // Store the last abstract state, used to check if the abstract state has reached a fixpoint AbstractState last_as = postAbsTrace()[node]; postAbsTrace()[node] = preAbsTrace[node]; for (const SVFStmt* stmt : node->getSVFStmts()) { updateAbsState(stmt); - bufOverflowDetection(stmt); } if (const CallICFGNode* callNode = SVFUtil::dyn_cast(node)) { - handleCallSite(callNode); + // Bug checking for external API calls happens inside handleCallSite, + // after the API value summary is applied. + handleCallSite(callNode); + } + else { + // Implicit dereference / GEP overflow checks on ordinary statements. + nullptrDerefDetection(node); + bufOverflowDetection(node); } // If the abstract state is the same as the last abstract state, return false because we have reached fixpoint if (postAbsTrace()[node] == last_as) { @@ -604,69 +393,55 @@ bool AbstractExecution::handleICFGNode(const ICFGNode* node) { } return true; } -/** - * @brief Handle state updates for each type of SVF statement - * - * This function updates the abstract state based on the type of SVF statement provided. - * It dispatches the handling of each specific statement type to the corresponding update function. - * - * @param stmt The SVF statement for which the state needs to be updated - */ -void AbstractExecution::updateAbsState(const SVFStmt* stmt) { - // Handle address statements - if (const AddrStmt* addr = SVFUtil::dyn_cast(stmt)) { - updateStateOnAddr(addr); - } - // Handle binary operation statements - else if (const BinaryOPStmt* binary = SVFUtil::dyn_cast(stmt)) { - updateStateOnBinary(binary); - } - // Handle comparison statements - else if (const CmpStmt* cmp = SVFUtil::dyn_cast(stmt)) { - updateStateOnCmp(cmp); - } - // Handle load statements - else if (const LoadStmt* load = SVFUtil::dyn_cast(stmt)) { - updateStateOnLoad(load); - } - // Handle store statements - else if (const StoreStmt* store = SVFUtil::dyn_cast(stmt)) { - updateStateOnStore(store); - } - // Handle copy statements - else if (const CopyStmt* copy = SVFUtil::dyn_cast(stmt)) { - updateStateOnCopy(copy); - } - // Handle GEP (GetElementPtr) statements - else if (const GepStmt* gep = SVFUtil::dyn_cast(stmt)) { - updateStateOnGep(gep); - } - // Handle phi statements - else if (const PhiStmt* phi = SVFUtil::dyn_cast(stmt)) { - updateStateOnPhi(phi); - } - // Handle call procedure entries - else if (const CallPE* callPE = SVFUtil::dyn_cast(stmt)) { - updateStateOnCall(callPE); - } - // Handle return procedure entries - else if (const RetPE* retPE = SVFUtil::dyn_cast(stmt)) { - updateStateOnRet(retPE); - } - // Handle select statements - else if (const SelectStmt* select = SVFUtil::dyn_cast(stmt)) { - updateStateOnSelect(select); - } - // Handle unary operations and branch statements (no action needed) - else if (SVFUtil::isa(stmt) || SVFUtil::isa(stmt)) { - // Nothing needs to be done here as BranchStmt is handled in hasBranchES - } - // Assert false for unsupported statement types - else { - assert(false && "implement this part"); +// updateAbsState now lives in Assignment_3.cpp (student TODO). + +namespace { +/// Harness-only ground-truth check for buffer access safety. Computed from +/// SVF primitives (base object size + GepObjVar::getConstantFieldIdx) so it +/// never depends on the student's gepObjOffsetFromBase map or +/// canSafelyAccessMemory implementation. +bool harnessSafeAccess(AbstractState& as, SVFIR* svfir, const ValVar* value, + const IntervalValue& len) { + AbstractValue ptrVal = as[value->getId()]; + if (!ptrVal.isAddr()) + return true; + for (const auto& addr : ptrVal.getAddrs()) { + if (AbstractState::isBlackHoleObjAddr(addr) || AbstractState::isNullMem(addr)) + continue; + NodeID objId = as.getIDFromAddr(addr); + const BaseObjVar* baseObj = svfir->getBaseObject(objId); + if (!baseObj || baseObj->isBlackHoleObj() || !baseObj->isConstantByteSize()) + continue; + u32_t size = baseObj->getByteSizeOfObj(); + IntervalValue baseOffset(0); + const SVFVar* svfVar = svfir->getGNode(objId); + if (auto* gepObj = SVFUtil::dyn_cast(svfVar)) + baseOffset = IntervalValue((s64_t)gepObj->getConstantFieldIdx()); + IntervalValue offset = baseOffset + len; + if (offset.ub().getIntNumeral() >= (s64_t)size) + return false; } + return true; } +/// Harness-only ground-truth check for pointer-dereference safety. +bool harnessSafeDeref(AbstractState& as, const ValVar* value) { + if (!value || value->getId() == IRGraph::NullPtr) + return false; + const AbstractValue& absVal = as[value->getId()]; + if (!absVal.isAddr()) + return true; + for (const auto& addr : absVal.getAddrs()) { + if (AbstractState::isBlackHoleObjAddr(addr)) + continue; + if (AbstractState::isNullMem(addr)) + return false; + if (as.isFreedMem(addr)) + return false; + } + return true; +} +} // namespace /** * @brief Handle a call site in the control flow graph @@ -679,133 +454,99 @@ void AbstractExecution::updateAbsState(const SVFStmt* stmt) { void AbstractExecution::handleCallSite(const CallICFGNode* callNode) { // Get the callee function associated with the call site const FunObjVar* callee = callNode->getCalledFunction(); + if (!callee) + return; std::string fun_name = callee->getName(); if (fun_name == "OVERFLOW" || fun_name == "svf_assert" || fun_name == "svf_assert_eq") { handleStubFunctions(callNode); - } + } + else if (fun_name == "SAFE_BUFACCESS" || fun_name == "UNSAFE_BUFACCESS" || + fun_name == "SAFE_PTRDEREF" || fun_name == "UNSAFE_PTRDEREF") { + // Ground-truth checkpoints for the buffer/nullptr checkers. + handleCheckpointStubs(callNode); + } else if (fun_name == "nd" || fun_name == "rand") { NodeID lhsId = callNode->getRetICFGNode()->getActualRet()->getId(); postAbsTrace()[callNode][lhsId] = AbstractValue(IntervalValue::top()); - } - else if (isExternalCallForAssignment(callee)) { - // implement external calls for the assignment - updateStateOnExtCall(callNode); } else if (SVFUtil::isExtCall(callee)) { - // handle external API calls — AbsExtAPI reads/writes through the - // same svfStateMgr that backs postAbsTrace(), so no sync needed. - utils->handleExtAPI(callNode); - } - else if (recursiveFuns.find(callee) != recursiveFuns.end()) { - // skip recursive functions - return; + // External API value summaries. The student implements the memory and + // string families (memcpy/memset/strcpy/strcat/...) plus the + // assignment-specific mem_insert/str_insert stubs in updateStateOnExtCall; + // unmodelled functions fall back to SVF inside that dispatcher. After + // propagating values we run the bug checkers on the API's + // pointer/length arguments. + updateStateOnExtCall(callNode); + nullptrDerefDetection(callNode); + bufOverflowDetection(callNode); } else { - // Handle the callee function + // Inline the callee body unconditionally. handleFunction guards + // against re-entering a WTO that is already on the stack, so + // recursive callsites just fall back to the outer WTO cycle. handleFunction(svfir->getICFG()->getFunEntryICFGNode(callee)); + const RetICFGNode* retNode = callNode->getRetICFGNode(); + if (postAbsTrace().count(callNode)) + postAbsTrace()[retNode] = postAbsTrace()[callNode]; } } /** - * @brief Get the next nodes of a node - * - * Returns the next nodes of a node that are inside the same function. - * And if CallICFGNode, shortcut to the RetICFGNode . - * - * @param node The node to get the next nodes of - * @return The next nodes of the node + * @brief Validate the SAFE/UNSAFE checkpoint stub functions. + * + * These stubs encode the ground truth for the bug checkers. Validation uses + * the harness-only `harnessSafeAccess` / `harnessSafeDeref` helpers above, + * NOT the student's `canSafelyAccessMemory` / `canSafelyDerefPtr` — so the + * stub verdict cannot be biased by student bugs. */ -std::vector AbstractExecution::getNextNodes(const ICFGNode* node) const { - std::vector outEdges; - for (const ICFGEdge* edge : node->getOutEdges()) { - const ICFGNode* dst = edge->getDstNode(); - // Only nodes inside the same function are included - if (dst->getFun() == node->getFun()) { - outEdges.push_back(dst); - } - } - if (const CallICFGNode* callNode = SVFUtil::dyn_cast(node)) { - // Shortcut to the RetICFGNode - const ICFGNode* retNode = callNode->getRetICFGNode(); - outEdges.push_back(retNode); +void AbstractExecution::handleCheckpointStubs(const CallICFGNode* callNode) { + const std::string fun_name = callNode->getCalledFunction()->getName(); + if (fun_name == "SAFE_BUFACCESS" || fun_name == "UNSAFE_BUFACCESS") { + if (callNode->arg_size() < 2) + return; + AbstractState& as = getAbsStateFromTrace(callNode); + IntervalValue len = as[callNode->getArgument(1)->getId()].getInterval(); + if (len.isBottom()) + len = IntervalValue(0); + const ValVar* ptr = callNode->getArgument(0); + if (!harnessSafeAccess(as, svfir, ptr, len - IntervalValue(1))) + reportBufOverflow(callNode); + } + else if (fun_name == "SAFE_PTRDEREF" || fun_name == "UNSAFE_PTRDEREF") { + if (callNode->arg_size() < 1) + return; + AbstractState& as = getAbsStateFromTrace(callNode); + const ValVar* ptr = callNode->getArgument(0); + if (!harnessSafeDeref(as, ptr)) + reportNullDeref(callNode); } - return outEdges; } -/** - * @brief Get the next nodes of a cycle - * - * Returns the next nodes of cycle components that are outside the cycle. - * Inner cycles are skipped since their next nodes cannot be outside the outer cycle. - * And Inner cycles are handled in the outer cycle. - * Only nodes that point outside the cycle are included in cycleNext. - * - * @param cycle The cycle to get the next nodes of - * @return The next nodes of the cycle - */ -std::vector AbstractExecution::getNextNodesOfCycle(const ICFGCycleWTO* cycle) const { - Set cycleNodes; - // Insert the head of the cycle and the heads of the inner cycles - cycleNodes.insert(cycle->head()->getICFGNode()); - for (const ICFGWTOComp* comp : cycle->getWTOComponents()) { - if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) { - cycleNodes.insert(singleton->getICFGNode()); - } else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) { - cycleNodes.insert(subCycle->head()->getICFGNode()); - } - } - std::vector outEdges; - std::vector nextNodes = getNextNodes(cycle->head()->getICFGNode()); - for (const ICFGNode* nextNode : nextNodes) { - // Only nodes that point outside the cycle are included - if (cycleNodes.find(nextNode) == cycleNodes.end()) { - outEdges.push_back(nextNode); - } - } - for (const ICFGWTOComp* comp : cycle->getWTOComponents()) { +void AbstractExecution::handleFunction(const ICFGNode* funEntry) { + // Iterate the function's interprocedural WTO components in WTO order. + // Singletons are handled directly; cycles (loop heads and recursive + // function entries) are driven to a fixpoint by handleICFGCycle. + // + // `_funcsInFlight` guards re-entry: if this WTO is already on the call + // stack (i.e. a recursive callsite tried to inline back into us), return + // immediately and let the outer cycle's widen/narrow iteration drive the + // recursion to a fixpoint. This is the only mechanism for handling + // recursion — there is no separate "is recursive callsite?" check. + const FunObjVar* fun = funEntry->getFun(); + auto it = funcToWTO.find(fun); + if (it == funcToWTO.end()) + return; + if (!_funcsInFlight.insert(fun).second) + return; + for (const ICFGWTOComp* comp : it->second->getWTOComponents()) { if (const ICFGSingletonWTO* singleton = SVFUtil::dyn_cast(comp)) { - std::vector nextNodes = getNextNodes(singleton->getICFGNode()); - // Only nodes that point outside the cycle are included - for (const ICFGNode* nextNode : nextNodes) { - if (cycleNodes.find(nextNode) == cycleNodes.end()) { - outEdges.push_back(nextNode); - } - } - } else if (const ICFGCycleWTO* subCycle = SVFUtil::dyn_cast(comp)) { - // skip inner cycle inside the outer cycle, because 1) it will be handled in the outer cycle. - //2) its next nodes won't be outside the outer cycle. - continue; + handleICFGNode(singleton->getICFGNode()); } - } - return outEdges; -} - -void AbstractExecution::handleFunction(const ICFGNode* funEntry) - { - FIFOWorkList worklist; - worklist.push(funEntry); - while (!worklist.empty()) { - const ICFGNode* node = worklist.pop(); - if (cycleHeadToCycle.find(node) != cycleHeadToCycle.end()) { - const ICFGCycleWTO* cycle = cycleHeadToCycle[node]; + else if (const ICFGCycleWTO* cycle = SVFUtil::dyn_cast(comp)) { handleICFGCycle(cycle); - std::vector cycleNextNodes = getNextNodesOfCycle(cycle); - for (const ICFGNode* nextNode : cycleNextNodes) { - worklist.push(nextNode); - } - } - else { - if (!handleICFGNode(node)) { - SVFUtil::errs() << "Fixpoint reached or infeasible for node " << node->getId() << "\n"; - continue; - } - std::vector nextNodes = getNextNodes(node); - for (const ICFGNode* nextNode : nextNodes) { - worklist.push(nextNode); - } } } - return; + _funcsInFlight.erase(fun); } /** @@ -863,9 +604,11 @@ void AbstractExecution::handleStubFunctions(const SVF::CallICFGNode* callNode) { } return; } - // Handle the 'OVERFLOW' stub function + // Handle the 'OVERFLOW' stub function. Ground truth is computed from SVF + // primitives only — `GepObjVar::getConstantFieldIdx()` gives the accumulated + // offset of the sub-object from its base — so the verdict does not depend + // on the student's gepObjOffsetFromBase map. else if (callNode->getCalledFunction()->getName() == "OVERFLOW") { - // If the condition is false, the program is infeasible assert_points.insert(callNode); u32_t arg0 = callNode->getArgument(0)->getId(); u32_t arg1 = callNode->getArgument(1)->getId(); @@ -873,23 +616,23 @@ void AbstractExecution::handleStubFunctions(const SVF::CallICFGNode* callNode) { AbstractState& as = getAbsStateFromTrace(callNode); AbstractValue gepRhsVal = as[arg0]; - // Check if the RHS value is an address if (gepRhsVal.isAddr()) { bool overflow = false; + s64_t access_offset = as[arg1].getInterval().ub().getIntNumeral(); for (const auto& addr : gepRhsVal.getAddrs()) { - u64_t access_offset = as[arg1].getInterval().getIntNumeral(); NodeID objId = as.getIDFromAddr(addr); - const GepObjVar* gepLhsObjVar = SVFUtil::cast(svfir->getGNode(objId)); - auto size = svfir->getBaseObject(objId)->getByteSizeOfObj(); - if (bufOverflowHelper.hasGepObjOffsetFromBase(gepLhsObjVar)) { - overflow = (bufOverflowHelper.getGepObjOffsetFromBase(gepLhsObjVar).ub().getIntNumeral() + access_offset - >= size); - } - else { - assert(false && "pointer not found in gepObjOffsetFromBase"); - } + const BaseObjVar* baseObj = svfir->getBaseObject(objId); + if (!baseObj || !baseObj->isConstantByteSize()) + continue; + s64_t size = (s64_t)baseObj->getByteSizeOfObj(); + s64_t baseOffset = 0; + if (auto* gepObj = SVFUtil::dyn_cast(svfir->getGNode(objId))) + baseOffset = (s64_t)gepObj->getConstantFieldIdx(); + if (baseOffset + access_offset >= size) + overflow = true; } if (overflow) { + reportBufOverflow(callNode); std::cerr << "Your implementation successfully detected the buffer overflow\n"; } else { @@ -906,4 +649,56 @@ void AbstractExecution::handleStubFunctions(const SVF::CallICFGNode* callNode) { } } +// =========================================================================== +// Ass3StateManager — narrow facade forwarding only the whitelisted state and +// GEP primitives to the underlying AbstractInterpretation. Defined here (not +// in the header) so student code never sees AbstractInterpretation/AbsExtAPI. +// =========================================================================== +namespace SVF { + +const AbstractValue& Ass3StateManager::getAbsValue(const ValVar* var, const ICFGNode* node) { + return ai->getAbsValue(var, node); +} +const AbstractValue& Ass3StateManager::getAbsValue(const ObjVar* var, const ICFGNode* node) { + return ai->getAbsValue(var, node); +} +const AbstractValue& Ass3StateManager::getAbsValue(const SVFVar* var, const ICFGNode* node) { + return ai->getAbsValue(var, node); +} +void Ass3StateManager::updateAbsValue(const ValVar* var, const AbstractValue& val, const ICFGNode* node) { + ai->updateAbsValue(var, val, node); +} +void Ass3StateManager::updateAbsValue(const ObjVar* var, const AbstractValue& val, const ICFGNode* node) { + ai->updateAbsValue(var, val, node); +} +void Ass3StateManager::updateAbsValue(const SVFVar* var, const AbstractValue& val, const ICFGNode* node) { + ai->updateAbsValue(var, val, node); +} +AbstractValue Ass3StateManager::loadValue(const ValVar* pointer, const ICFGNode* node) { + return ai->loadValue(pointer, node); +} +void Ass3StateManager::storeValue(const ValVar* pointer, const AbstractValue& val, const ICFGNode* node) { + ai->storeValue(pointer, val, node); +} +AddressValue Ass3StateManager::getGepObjAddrs(const ValVar* pointer, IntervalValue offset) { + return ai->getGepObjAddrs(pointer, offset); +} +IntervalValue Ass3StateManager::getGepElementIndex(const GepStmt* gep) { + return ai->getGepElementIndex(gep); +} +IntervalValue Ass3StateManager::getGepByteOffset(const GepStmt* gep) { + return ai->getGepByteOffset(gep); +} +u32_t Ass3StateManager::getAllocaInstByteSize(const AddrStmt* addr) { + return ai->getAllocaInstByteSize(addr); +} + +// harness-only post-trace accessors (need full AbstractInterpretation type). +AbstractState& AbstractExecution::getAbsStateFromTrace(const ICFGNode* node) { + return (*ai)[node]; +} +Map& AbstractExecution::postAbsTrace() { + return ai->getTrace(); +} +} // namespace SVF diff --git a/Assignment-3/CPP/Assignment_3_Helper.h b/Assignment-3/CPP/Assignment_3_Helper.h index dac3469..9665e97 100644 --- a/Assignment-3/CPP/Assignment_3_Helper.h +++ b/Assignment-3/CPP/Assignment_3_Helper.h @@ -31,13 +31,74 @@ #include "SVFIR/SVFStatements.h" #include "Util/Options.h" #include "Util/SVFBugReport.h" +#include +#include +#include namespace SVF { + class AbstractInterpretation; + + /// Narrow facade over the SVF abstract-interpretation state manager. + /// + /// Students interact with the abstract state *only* through this object + /// (the `svfStateMgr` member of AbstractExecution). It forwards exactly the + /// state read/write and GEP primitives the assignment is allowed to use, and + /// deliberately exposes no path to the SVF external-API modeller + /// (AbsExtAPI / handleExtAPI / getRangeLimitFromType / getUtils), so the + /// memory/string library summaries and the cast-range logic must be written + /// by hand. Method bodies live in Assignment_3_Helper.cpp. + class Ass3StateManager { + public: + explicit Ass3StateManager(AbstractInterpretation* ai = nullptr) : ai(ai) {} + + const AbstractValue& getAbsValue(const ValVar* var, const ICFGNode* node); + const AbstractValue& getAbsValue(const ObjVar* var, const ICFGNode* node); + const AbstractValue& getAbsValue(const SVFVar* var, const ICFGNode* node); + + void updateAbsValue(const ValVar* var, const AbstractValue& val, const ICFGNode* node); + void updateAbsValue(const ObjVar* var, const AbstractValue& val, const ICFGNode* node); + void updateAbsValue(const SVFVar* var, const AbstractValue& val, const ICFGNode* node); + + AbstractValue loadValue(const ValVar* pointer, const ICFGNode* node); + void storeValue(const ValVar* pointer, const AbstractValue& val, const ICFGNode* node); + + AddressValue getGepObjAddrs(const ValVar* pointer, IntervalValue offset); + IntervalValue getGepElementIndex(const GepStmt* gep); + IntervalValue getGepByteOffset(const GepStmt* gep); + u32_t getAllocaInstByteSize(const AddrStmt* addr); + + private: + // harness-only: AbstractExecution reaches the underlying manager for the + // post-trace; never exposed to student code. + friend class AbstractExecution; + AbstractInterpretation* ai; + }; + + struct AssignmentCaseConfig { + std::string caseId; + std::string targetLoc; + std::string tags; + bool emitJson = false; + }; + + struct AssignmentBugReport { + std::string kind; + std::string location; + std::string message; + NodeID nodeId = 0; + }; + + std::string ass3JsonEscape(const std::string& input); + class AbstractExecutionHelper { public: /// Add a detected bug to the bug reporter and print the report ///@{ void addBugToReporter(const AEException& e, const ICFGNode* node) { + addBugToReporter("buffer-overflow", e, node); + } + + void addBugToReporter(const std::string& kind, const AEException& e, const ICFGNode* node) { GenericBug::EventStack eventStack; SVFBugEvent sourceInstEvent(SVFBugEvent::EventType::SourceInst, node); @@ -50,16 +111,18 @@ namespace SVF { std::string loc = eventStack.back().getEventLoc(); // Get the location of the last event in the stack // Check if the bug at this location has already been reported - if (_bugLoc.find(loc) != _bugLoc.end()) { + const std::string dedupKey = kind + ":" + loc; + if (_bugLoc.find(dedupKey) != _bugLoc.end()) { return; // If the bug location is already reported, return early } else { - _bugLoc.insert(loc); // Otherwise, mark this location as reported + _bugLoc.insert(dedupKey); // Otherwise, mark this location as reported } // Add the bug to the recorder with details from the event stack _recoder.addAbsExecBug(GenericBug::FULLBUFOVERFLOW, eventStack, 0, 0, 0, 0); _nodeToBugInfo[node] = e.what(); // Record the exception information for the node + _reports.push_back({kind, loc, e.what(), node ? node->getId() : 0}); } void printReport() { @@ -74,34 +137,23 @@ namespace SVF { } ///@} - void addToGepObjOffsetFromBase(const GepObjVar* obj, const IntervalValue& offset) { - gepObjOffsetFromBase[obj] = offset; + const std::vector& getReports() const { + return _reports; } - bool hasGepObjOffsetFromBase(const GepObjVar* obj) const { - return gepObjOffsetFromBase.find(obj) != gepObjOffsetFromBase.end(); + u32_t getReportCount() const { + return static_cast(_reports.size()); } - IntervalValue getGepObjOffsetFromBase(const GepObjVar* obj) const { - if (hasGepObjOffsetFromBase(obj)) - return gepObjOffsetFromBase.at(obj); - else { - assert(false && "GepObjVar not found in gepObjOffsetFromBase"); - abort(); - } - } SVFBugReport& getBugReporter() { return _recoder; } private: - /// Map a GEP objVar to its offset from the base address - /// e.g. alloca [i32*10] x; lhs = gep x, 3 - /// gepObjOffsetFromBase[lhs] = [12, 12] - Map gepObjOffsetFromBase; /// Bug reporter Set _bugLoc; SVFBugReport _recoder; Map _nodeToBugInfo; + std::vector _reports; }; } diff --git a/Assignment-3/CPP/CMakeLists.txt b/Assignment-3/CPP/CMakeLists.txt index 7c42229..2e346b2 100644 --- a/Assignment-3/CPP/CMakeLists.txt +++ b/Assignment-3/CPP/CMakeLists.txt @@ -35,6 +35,20 @@ foreach(filename ${buf_files}) WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) endforeach() + +# Categorised abstract-execution corpus (Tests/cases). Each case self-validates +# via svf_assert / UNSAFE_* checkpoints, so a clean exit means pass. Only the +# passing corpus lives under Tests/cases; known-failing cases are kept under +# Tests/cases-unpass and are intentionally NOT registered. +file(GLOB_RECURSE case_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/../Tests "${CMAKE_CURRENT_SOURCE_DIR}/../Tests/cases/*.ll") +foreach(filename ${case_files}) + add_test( + NAME ass3-cases-cpp/${filename} + COMMAND ass3 ${CMAKE_CURRENT_SOURCE_DIR}/../Tests/${filename} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/bin + ) + set_tests_properties(ass3-cases-cpp/${filename} PROPERTIES TIMEOUT 120) +endforeach() include(CTest) diff --git a/Assignment-3/CPP/test-ae.cpp b/Assignment-3/CPP/test-ae.cpp index 1f8e99e..e4d12fe 100644 --- a/Assignment-3/CPP/test-ae.cpp +++ b/Assignment-3/CPP/test-ae.cpp @@ -4,29 +4,68 @@ #include "Util/Options.h" #include "WPA/Andersen.h" #include "WPA/WPAPass.h" +#include +#include +#include +#include using namespace SVF; using namespace SVFUtil; +static bool consumeAss3Option(const char* arg, AssignmentCaseConfig& config) { + std::string opt(arg); + auto valueOf = [&](const std::string& prefix) -> std::string { + return opt.substr(prefix.size()); + }; + + if (opt == "--emit-json") { + config.emitJson = true; + return true; + } + if (opt.rfind("--case-id=", 0) == 0) { + config.caseId = valueOf("--case-id="); + return true; + } + if (opt.rfind("--target=", 0) == 0) { + config.targetLoc = valueOf("--target="); + return true; + } + if (opt.rfind("--tags=", 0) == 0) { + config.tags = valueOf("--tags="); + return true; + } + return false; +} + int main(int argc, char** argv) { - int arg_num = 0; - int extraArgc = 5; - char** arg_value = new char*[argc + extraArgc]; - for (; arg_num < argc; ++arg_num) { - arg_value[arg_num] = argv[arg_num]; + auto started = std::chrono::steady_clock::now(); + AssignmentCaseConfig config; + + std::vector argStorage; + argStorage.emplace_back(argv[0]); + for (int i = 1; i < argc; ++i) { + if (!consumeAss3Option(argv[i], config)) + argStorage.emplace_back(argv[i]); } - // add extra options - arg_value[arg_num++] = (char*)"-model-consts=true"; - arg_value[arg_num++] = (char*)"-model-arrays=true"; - arg_value[arg_num++] = (char*)"-pre-field-sensitive=false"; - arg_value[arg_num++] = (char*)"-field-limit=10000"; - arg_value[arg_num++] = (char*)"-stat=false"; - assert(arg_num == (argc + extraArgc) && "more extra arguments? Change the value of extraArgc"); + + // Assignment runner defaults. These are deliberately applied outside the + // student implementation so grading cases are comparable. + argStorage.emplace_back("-model-consts=true"); + argStorage.emplace_back("-model-arrays=true"); + argStorage.emplace_back("-pre-field-sensitive=false"); + argStorage.emplace_back("-field-limit=10000"); + argStorage.emplace_back("-stat=false"); + + std::vector argValue; + argValue.reserve(argStorage.size()); + for (std::string& arg : argStorage) + argValue.push_back(const_cast(arg.c_str())); std::vector moduleNameVec; moduleNameVec = - OptionBase::parseOptions(arg_num, arg_value, "Static Symbolic Execution", "[options] "); - delete[] arg_value; + OptionBase::parseOptions(static_cast(argValue.size()), argValue.data(), + "Assignment 3 Abstract Execution", + "[assignment-options] [svf-options] "); LLVMModuleSet::getLLVMModuleSet()->buildSVFModule(moduleNameVec); SVFIRBuilder builder; @@ -35,9 +74,16 @@ int main(int argc, char** argv) { CallGraph* callgraph = ander->getCallGraph(); builder.updateCallGraph(callgraph); pag->getICFG()->updateCallGraph(callgraph); - AbstractExecution* ae = new AbstractExecution(); - ae->runOnModule(pag->getICFG()); - ae->ensureAllAssertsValidated(); + AbstractExecution ae(config); + ae.runOnModule(pag->getICFG()); + ae.ensureAllAssertsValidated(); + auto finished = std::chrono::steady_clock::now(); + double wallSeconds = std::chrono::duration(finished - started).count(); + if (config.emitJson) { + std::cout << "ASS3_JSON_BEGIN\n"; + ae.writeJsonSummary(std::cout, wallSeconds, 0, true); + std::cout << "ASS3_JSON_END\n"; + } LLVMModuleSet::releaseLLVMModuleSet(); } diff --git a/Assignment-3/Python/Assignment_3.py b/Assignment-3/Python/Assignment_3.py index 7fcacc8..9178d52 100644 --- a/Assignment-3/Python/Assignment_3.py +++ b/Assignment-3/Python/Assignment_3.py @@ -1,118 +1,40 @@ from Assignment_3_Helper import * import pysvf + class Assignment3(AbstractExecution): def __init__(self, pag: pysvf.SVFIR) -> None: super().__init__(pag) - - """ - Handle ICFG nodes in a cycle using widening and narrowing operators. - - This function implements abstract interpretation for cycles in the ICFG using widening and narrowing - operators to ensure termination. It processes all ICFG nodes within a cycle and implements - widening-narrowing iteration to reach fixed points twice: once for widening (to ensure termination) - and once for narrowing (to improve precision). - - :param cycle: The WTO cycle containing ICFG nodes to be processed - :type cycle: ICFGWTOCycle - """ - def handleICFGCycle(self, cycle: ICFGWTOCycle): - head = cycle.head.node - increasing = True - iteration = 0 - widen_delay = self.widen_delay # Use class member for widen delay - - while True: - # TODO: your code starts from here - pass - - #TODO : Implement the state updates for Copy, Binary, Store, Load, Gep, Phi - # TODO: your code starts from here - def updateStateOnGep(self, gep: pysvf.GepStmt): + # Dispatch a single SVF statement to the matching transfer function. + def updateAbsState(self, stmt: pysvf.SVFStmt): + # TODO: dispatch on statement subtype and update the abstract state. pass - #TODO: your code starts from here - def updateStateOnStore(self, store: pysvf.StoreStmt): - pass + # Join predecessor post-states (with branch refinement) into the + # current node's pre-state. + def mergeStatesFromPredecessors(self, block: pysvf.ICFGNode, + abstract_state: pysvf.AbstractState) -> bool: + # TODO + return False - #TODO: your code starts from here - # Find the comparison predicates in "class BinaryOPStmt:OpCode" under SVF/svf/include/SVFIR/SVFStatements.h - # You are required to handle predicates (The program is assumed to have signed ints and also interger-overflow-free), - # including Add, FAdd, Sub, FSub, Mul, FMul, SDiv, FDiv, UDiv, SRem, FRem, URem, Xor, And, Or, AShr, Shl, LShr - def updateStateOnBinary(self, binary: pysvf.BinaryOPStmt): + # Iterate the cycle body to a fixpoint (widening optional). + def handleICFGCycle(self, cycle): + # TODO pass - - #TODO: your code starts from here - def updateStateOnLoad(self, load: pysvf.LoadStmt): + # Detect out-of-bounds memory accesses at `node`. + def bufOverflowDetection(self, node: pysvf.ICFGNode): + # TODO pass - #TODO: your code starts from here - def updateStateOnCopy(self, copy: pysvf.CopyStmt): + # Model external library calls (memory/string families and + # assignment-specific stubs). + def updateStateOnExtCall(self, call: pysvf.CallICFGNode): + # TODO pass - # TODO: your code starts from here - def updateStateOnPhi(self, phi: pysvf.PhiStmt): + # Handle a call site in the control-flow graph. + def handleCallSite(self, node: pysvf.CallICFGNode): + # TODO pass - - """ - Detect buffer overflows in the given statement. - - TODO: handle GepStmt `lhs = rhs + off` and detect buffer overflow - Step 1: For each `obj \in pts(rhs)`, get the size of allocated baseobj (entire mem object) via `obj_size = svfir->getBaseObj(objId)->getByteSizeOfObj();` - There is a buffer overflow if `accessOffset.ub() >= obj_size`, where accessOffset is obtained via `getAccessOffset` - Step 2: invoke `reportBufOverflow` with the current ICFGNode if an overflow is detected - - :param stmt: The statement to analyze for buffer overflows. - :type stmt: pysvf.SVFStmt - """ - def bufOverflowDetection(self, stmt: pysvf.SVFStmt): - if not isinstance(stmt.getICFGNode(), pysvf.CallICFGNode): - if isinstance(stmt, pysvf.GepStmt): - abstract_state = self.post_abs_trace[stmt.getICFGNode()] - lhs = stmt.getLHSVarID() - rhs = stmt.getRHSVarID() - - # Update GEP object offset from base - self.buf_overflow_helper.updateGepObjOffsetFromBase(abstract_state, - abstract_state[lhs].getAddrs(), abstract_state[rhs].getAddrs(), - self.buf_overflow_helper.getByteOffset(abstract_state, stmt) - ) - - # TODO: your code starts from here - # Check for buffer overflow - pass - - """ - Handle external function calls and update the abstract state. - - This function processes specific external function calls, such as `mem_insert` and `str_insert`, - to ensure that buffer overflows are detected and prevented. It checks the constraints on the - buffer size and access offsets based on the function arguments. - - TODO: Steps: - 1. For `mem_insert`: - - Validate that the buffer size is greater than or equal to the sum of the position and data size. - 2. For `str_insert`: - - Validate that the buffer size is greater than or equal to the sum of the position and the length of the string. - - :param ext_call_node: The call node representing the external function call. - :type ext_call_node: pysvf.CallICFGNode - """ - def updateStateOnExtCall(self, extCallNode: pysvf.CallICFGNode): - func_name = extCallNode.getCalledFunction().getName() - - # Handle external calls - # TODO: handle external calls - # void mem_insert(void *buffer, const void *data, size_t data_size, size_t position); - if func_name == "mem_insert": - # void mem_insert(void *buffer, const void *data, size_t data_size, size_t position); - # Check sizeof(buffer) >= position + data_size - pass - # TODO: handle external calls - # void str_insert(void *buffer, const void *data, size_t position); - elif func_name == "str_insert": - # void str_insert(void *buffer, const void *data, size_t position); - # Check sizeof(buffer) >= position + strlen(data) - pass \ No newline at end of file diff --git a/Assignment-3/Python/Assignment_3_Helper.py b/Assignment-3/Python/Assignment_3_Helper.py index 87dce44..9941695 100644 --- a/Assignment-3/Python/Assignment_3_Helper.py +++ b/Assignment-3/Python/Assignment_3_Helper.py @@ -113,9 +113,18 @@ def visitNode(self, node: ICFGWTONode): self.node_to_wto_cycle_depth[node.getICFGNode()] = self.wto_cycle_depth - def __init__(self, graph: ICFG, entry: ICFGNode): + def __init__(self, graph: ICFG, entry: ICFGNode, scc=None): self.graph = graph self.entry = entry + # Interprocedural WTO: `scc` is the set of FunObjVar *ids* in this + # call-graph SCC. Call edges into a callee in the same SCC are then + # followed (becoming back-edges), so a (mutually) recursive function's + # entry shows up as a WTO cycle head -- exactly like the C++ ICFGWTO. + # If no SCC is given, the SCC is the entry's own function. + if scc: + self.scc_fun_ids = set(scc) + else: + self.scc_fun_ids = {entry.getFun().getId()} self.components: List[ICFGWTOComp] = [] self.all_components : Set[ICFGWTOComp] = set() self.head_ref_to_cycle: Dict[ICFGNode, ICFGWTOCycle] = {} @@ -175,15 +184,24 @@ def visit(self, node: ICFGNode, components: List[ICFGWTOComp]): def get_successors(self, node: ICFGNode) -> List[ICFGNode]: + # Interprocedural successor relation, mirroring C++ ICFGWTO::getSuccessors. successors = [] if isinstance(node, pysvf.CallICFGNode): - return [node.getRetICFGNode()] - else: for e in node.getOutEdges(): - if not e.isIntraCFGEdge() or node.getFun() != e.getDstNode().getFun(): - continue + callee_entry = e.getDstNode() + if callee_entry.getFun().getId() in self.scc_fun_ids: + # caller & callee in the same SCC -> follow the call edge + successors.append(callee_entry) else: - successors.append(e.getDstNode()) + # different SCC -> shortcut to the local return node + successors.append(node.getRetICFGNode()) + else: + for e in node.getOutEdges(): + succ = e.getDstNode() + # Only stay within the SCC (intra edges, and return edges back + # into an SCC function). + if succ.getFun().getId() in self.scc_fun_ids: + successors.append(succ) return successors @@ -208,11 +226,6 @@ def __init__(self, svfir: pysvf.SVFIR, svf_state_mgr: 'pysvf.AbstractInterpretat """ Initialize member variables. """ - # Map a GEP objVar to its offset from the base address - # Example: alloca [i32*10] x; lhs = gep x, 3 - # gep_obj_offset_from_base[lhs] = [12, 12] - self.gep_obj_offset_from_base = {} - # Map to store exception information for each ICFGNode self.node_to_bug_info = {} self.svfir = svfir @@ -274,35 +287,8 @@ def printReport(self): print(f"{node}: {msg}\n---------------------------------------------") - def updateGepObjOffsetFromBase(self, abstractState: pysvf.AbstractState, gepAddrs: pysvf.AddressValue, objAddrs: pysvf.AddressValue, offset: pysvf.IntervalValue): - """ - Update the GEP object offset from the base address. - - :param gepAddrs: List of GEP address values. - :param objAddrs: List of object address values. - :param offset: IntervalValue representing the offset. - """ - for obj_addr in objAddrs: - obj_id = abstractState.getIDFromAddr(obj_addr) - obj = self.svfir.getGNode(obj_id) - if isinstance(obj, pysvf.BaseObjVar): - for gep_addr in gepAddrs: - gep_obj = abstractState.getIDFromAddr(gep_addr) - gep_obj_var = self.svfir.getGNode(gep_obj) - self.addToGepObjOffsetFromBase(gep_obj_var, offset) - elif isinstance(obj, pysvf.GepObjVar): - obj_var = obj - for gep_addr in gepAddrs: - gep_obj = abstractState.getIDFromAddr(gep_addr) - gep_obj_var = self.svfir.getGNode(gep_obj) - if self.hasGepObjOffsetFromBase(obj_var): - obj_offset_from_base = self.getGepObjOffsetFromBase(obj_var) - # Ensure gep_obj_var has not been written before - if not self.hasGepObjOffsetFromBase(gep_obj_var): - self.addToGepObjOffsetFromBase(gep_obj_var, obj_offset_from_base + offset) - else: - raise AssertionError("gepRhsObjVar has no gepObjOffsetFromBase") - + # GEP-offset tracking (updateGepObjOffsetFromBase / has / get) was removed + # from the helper this year — students implement it in Assignment_3.py. def handleMemcpy(self, abstractState: pysvf.AbstractState, dst: pysvf.SVFVar, src: pysvf.SVFVar, len: pysvf.IntervalValue, start_idx: int): """ @@ -403,36 +389,9 @@ def getStrlen(self, abstractState, strValue): else: return pysvf.IntervalValue(length * elem_size) - def addToGepObjOffsetFromBase(self, obj, offset): - """ - Add a GEP object variable and its offset from the base address. - - :param obj: The GEP object variable. - :param offset: The offset as an IntervalValue. - """ - self.gep_obj_offset_from_base[obj] = offset - - def hasGepObjOffsetFromBase(self, obj): - """ - Check if a GEP object variable has an offset from the base address. - - :param obj: The GEP object variable. - :return: True if the offset exists, False otherwise. - """ - return obj in self.gep_obj_offset_from_base - - def getGepObjOffsetFromBase(self, obj): - """ - Get the offset of a GEP object variable from the base address. - - :param obj: The GEP object variable. - :return: The offset as an IntervalValue. - :raises AssertionError: If the object is not found. - """ - if obj not in self.gep_obj_offset_from_base: - raise AssertionError(f"Object {obj} not found in gep_obj_offset_from_base") - else: - return self.gep_obj_offset_from_base[obj] + # addToGepObjOffsetFromBase / hasGepObjOffsetFromBase / getGepObjOffsetFromBase + # are no longer part of the helper API; students manage their own + # GEP-offset-from-base state in Assignment_3.py. class AbstractExecution: @@ -441,7 +400,11 @@ def __init__(self, pag: pysvf.SVFIR): self.icfg = pag.getICFG() self.call_site_stack = [] self.func_to_wto = {} - self.recursive_funs = set() + # Functions whose WTO is currently being iterated; re-entry returns + # early so the outer cycle drives the recursion to a fixpoint. This + # is the only mechanism for handling recursion — there is no separate + # "is recursive callsite?" check. + self._funcs_in_flight = set() self.pre_abs_trace = {} # Owns the post-trace and is the backing store for AbsExtAPI as well # as the GEP/load/store helpers (getGepByteOffset etc.). Replaces @@ -463,24 +426,55 @@ def __init__(self, pag: pysvf.SVFIR): """ - Initialize the WTO (Weak topological order) for each function. + Initialize the interprocedural WTO per call-graph SCC entry. + + Each (mutually) recursive function's entry node becomes a WTO cycle head + because intra-SCC call edges are turned into back-edges. The same + widening/narrowing machinery used for loops then drives recursion to a + fixpoint via handleICFGCycle; there is no separate "is recursive?" check. """ def initWto(self): callgraphScc = pysvf.getCallGraphSCC() - for node in self.svfir.getCallGraph().getNodes(): - if callgraphScc.isInCycle(node.getId()): - self.recursive_funs.add(node.getFunction()) + callgraph = self.svfir.getCallGraph() + + # SCC membership comes from pysvf: CallGraphSCC.subNodes(rep) returns + # the call-graph node IDs in the SCC represented by 'rep'. We only + # need it to feed ICFGWTO so intra-SCC call edges become back-edges. + cgid_to_fun = {} + for node in callgraph.getNodes(): + cgid_to_fun[node.getId()] = node.getFunction() + + # Build one interprocedural WTO per call-graph-SCC entry function. An + # SCC entry is a member with a caller outside the SCC (or no caller). + # Intra-SCC members are reached via the entry's interprocedural WTO. + self.func_to_wto = {} + for node in callgraph.getNodes(): fun = node.getFunction() - assert isinstance(fun, pysvf.FunObjVar) if fun.isDeclaration(): continue - wto = ICFGWTO(self.icfg, self.icfg.getFunEntryICFGNode(fun)) + cgid = node.getId() + rep = callgraphScc.repNode(cgid) + scc_cgids = set(callgraphScc.subNodes(rep)) + + in_edges = list(node.getInEdges()) + is_entry = (len(in_edges) == 0) + for e in in_edges: + if e.getSrcID() not in scc_cgids: # caller outside the SCC + is_entry = True + if not is_entry: + continue + + func_scc_ids = {cgid_to_fun[c].getId() for c in scc_cgids} + wto = ICFGWTO(self.icfg, self.icfg.getFunEntryICFGNode(fun), func_scc_ids) wto.init() - self.func_to_wto[fun] = wto - + # Key by function id: pybind FunObjVar wrappers are not guaranteed to + # hash consistently across calls, so don't use the object as a key. + self.func_to_wto[fun.getId()] = wto + # Build mapping from cycle head nodes to their corresponding cycles + # (loop heads AND recursive-function entries). self.cycle_head_to_cycle = {} - for fun, wto in self.func_to_wto.items(): + for wto in self.func_to_wto.values(): for comp in wto.components: if isinstance(comp, ICFGWTOCycle): self.cycle_head_to_cycle[comp.head.node] = comp @@ -510,112 +504,33 @@ def handleGlobalNode(self): """ - Handle a function in the ICFG using WTO components and worklist algorithm. - - This function processes a function by: - 1. Building a set of WTO components for the function - 2. Using a worklist algorithm to process nodes in topological order - 3. Handling cycles and singleton nodes appropriately - 4. Managing the flow between different components - """ - def handleFunction(self, funEntry: pysvf.ICFGNode): - # Use worklist algorithm to process nodes - worklist = [funEntry] # FIFO worklist using list - - while worklist: - node = worklist.pop(0) # FIFO: pop from front - - # Check if current node is a cycle head - if node in self.cycle_head_to_cycle: - cycle = self.cycle_head_to_cycle[node] - self.handleICFGCycle(cycle) - # Get next nodes of the cycle and add to worklist - cycle_next_nodes = self.getNextNodesOfCycle(cycle) - for next_node in cycle_next_nodes: - worklist.append(next_node) - else: - # Handle singleton node - if not self.handleICFGNode(node): - print(f"Fixpoint reached or infeasible for node {node.getId()}") - continue - - # Get next nodes and add to worklist - next_nodes = self.getNextNodes(node) - for next_node in next_nodes: - worklist.append(next_node) + Iterate a function's interprocedural WTO components. - """ - Get the next nodes of a given ICFG node. - - This function returns the successor nodes of a given ICFG node by examining - its outgoing edges. It handles both intra-procedural edges and call-return edges. - - :param node: The ICFG node whose successors are to be found - :type node: pysvf.ICFGNode - :return: List of successor nodes - :rtype: List[pysvf.ICFGNode] - """ - def getNextNodes(self, node: pysvf.ICFGNode) -> List[pysvf.ICFGNode]: - out_edges = [] - for edge in node.getOutEdges(): - dst = edge.getDstNode() - if dst.getFun() == node.getFun(): - out_edges.append(dst) - - # Handle call-return edges - if isinstance(node, pysvf.CallICFGNode): - ret_node = node.getRetICFGNode() - out_edges.append(ret_node) - - return out_edges + Singletons are handled directly; cycles (loop heads AND recursive-function + entries) are driven to a fixpoint by handleICFGCycle. + `_funcs_in_flight` guards re-entry: if this WTO is already on the call + stack (i.e. a recursive callsite tried to inline back into us), return + immediately and let the outer cycle's widen/narrow iteration drive the + recursion to a fixpoint. This is the only mechanism for handling + recursion — there is no separate "is recursive callsite?" check. """ - Get the next nodes of a cycle. - - This function returns the next nodes of a cycle by iterating through the cycle's components. - The next nodes of a cycle are the next nodes of the cycle nodes (including cycle head and cycle components) - that are located outside the cycle. - - Inner cycles are skipped because their next nodes cannot be outside the outer cycle. - Only the next nodes of cycle nodes that point to nodes outside the cycle are included in cycleNext. - - :param cycle: The WTO cycle whose next nodes are to be found - :type cycle: ICFGWTOCycle - :return: List of next nodes that are outside the cycle - :rtype: List[pysvf.ICFGNode] - """ - def getNextNodesOfCycle(self, cycle: ICFGWTOCycle) -> List[pysvf.ICFGNode]: - cycle_nodes = set() - cycle_nodes.add(cycle.head.node) - - for comp in cycle.components: - if isinstance(comp, ICFGWTONode): - cycle_nodes.add(comp.node) - elif isinstance(comp, ICFGWTOCycle): - cycle_nodes.add(comp.head.node) - - out_edges = [] - - # Get next nodes of cycle head - next_nodes = self.getNextNodes(cycle.head.node) - for next_node in next_nodes: - if next_node not in cycle_nodes: - out_edges.append(next_node) - - # Get next nodes of cycle components - for comp in cycle.components: - if isinstance(comp, ICFGWTONode): - next_nodes = self.getNextNodes(comp.node) - for next_node in next_nodes: - if next_node not in cycle_nodes: - out_edges.append(next_node) - elif isinstance(comp, ICFGWTOCycle): - # Skip inner cycles inside the outer cycle - # because their next nodes won't be outside the outer cycle - continue - - return out_edges - + def handleFunction(self, funEntry: pysvf.ICFGNode): + fun = funEntry.getFun() + wto = self.func_to_wto.get(fun.getId()) + if wto is None: + return + if fun.getId() in self._funcs_in_flight: + return + self._funcs_in_flight.add(fun.getId()) + try: + for comp in wto.components: + if isinstance(comp, ICFGWTOCycle): + self.handleICFGCycle(comp) + elif isinstance(comp, ICFGWTONode): + self.handleICFGNode(comp.getICFGNode()) + finally: + self._funcs_in_flight.discard(fun.getId()) """ Handle a singleton WTO @@ -668,9 +583,10 @@ def handleCallSite(self, node: pysvf.CallICFGNode): self.updateStateOnExtCall(node) elif pysvf.isExtCall(node.getCalledFunction()): pass - elif node.getCalledFunction() in self.recursive_funs: - return else: + # Inline unconditionally; handleFunction's `_funcs_in_flight` + # guard short-circuits recursive re-entry, and the outer WTO + # cycle drives the recursion to a fixpoint. self.handleFunction(self.svfir.getICFG().getFunEntryICFGNode(node.getCalledFunction())) @@ -717,7 +633,10 @@ def handleStubFunction(self, callNode: pysvf.CallICFGNode): assert False elif callNode.getCalledFunction().getName() == "OVERFLOW": - # If the condition is false, the program is infeasible + # Harness-only ground truth: read the GepObjVar's accumulated + # offset from base via SVF's native getConstantFieldIdx, so the + # stub verdict does not depend on the student's gep_obj_offset + # tracking. self.assert_points.add(callNode) arg0 = callNode.getArgument(0).getId() arg1 = callNode.getArgument(1).getId() @@ -725,25 +644,19 @@ def handleStubFunction(self, callNode: pysvf.CallICFGNode): abstract_state = self.post_abs_trace[callNode] gep_rhs_val = abstract_state[arg0] - # Check if the RHS value is an address if gep_rhs_val.isAddr(): overflow = False + access_offset = int(abstract_state[arg1].getInterval().ub()) for addr in gep_rhs_val.getAddrs(): - access_offset = abstract_state[arg1].getInterval().getIntNumeral() obj_id = abstract_state.getIDFromAddr(addr) - gep_lhs_obj_var = self.svfir.getGNode(obj_id).asGepObjVar() - size = self.svfir.getBaseObject(obj_id).getByteSizeOfObj() - - if self.buf_overflow_helper.hasGepObjOffsetFromBase(gep_lhs_obj_var): - overflow = ( - int(self.buf_overflow_helper.getGepObjOffsetFromBase(gep_lhs_obj_var).ub()) - + access_offset >= size - ) - if overflow: - print("obj len: {}, you want to access: {}.".format(size, access_offset)) - else: - raise AssertionError("Pointer not found in gepObjOffsetFromBase") - + base_obj = self.svfir.getBaseObject(obj_id) + if base_obj is None or not base_obj.isConstantByteSize(): + continue + size = base_obj.getByteSizeOfObj() + gnode = self.svfir.getGNode(obj_id) + base_offset = gnode.getConstantFieldIdx() if isinstance(gnode, pysvf.GepObjVar) else 0 + if base_offset + access_offset >= size: + overflow = True if overflow: print("Your implementation successfully detected the buffer overflow") else: @@ -754,56 +667,8 @@ def handleStubFunction(self, callNode: pysvf.CallICFGNode): assert False - """ - Merge abstract states from the predecessors of a given ICFG node. - - This function collects and combines the abstract states from all incoming edges - of the specified ICFG node. It ensures that the abstract state of the current node - is consistent with the states of its predecessors. The merging process involves - joining the abstract states from all valid incoming edges. - - Steps: - 1. Initialize an empty abstract state and a counter for valid incoming edges. - 2. Iterate through all incoming edges of the given block: - - If the source node of the edge has a post-abstract state, process the edge. - - For intra-procedural edges with conditions, check branch feasibility. - - If the branch is feasible, join the source's abstract state with the current state. - - For inter-procedural or unconditional edges, directly join the source's state. - 3. If no valid incoming edges are found, print an error and return failure. - 4. Return a tuple indicating whether the merge was successful and the merged abstract state. - - :param block: The ICFG node whose predecessors' states are to be merged. - :type block: pysvf.ICFGNode - :return: A tuple (is_feasible, merged_state), where: - - is_feasible (bool): True if at least one predecessor exists, False otherwise. - - merged_state (AbstractState): The resulting merged abstract state. - """ - def mergeStatesFromPredecessors(self, block: pysvf.ICFGNode): - in_edge_num = 0 - abstract_state = pysvf.AbstractState() - for edge in block.getInEdges(): - if edge.getSrcNode() in self.post_abs_trace: - if isinstance(edge, pysvf.IntraCFGEdge): - if edge.getCondition(): - tmp_es = self.post_abs_trace[edge.getSrcNode()].clone() - if self.isBranchFeasible(edge, tmp_es): - abstract_state.joinWith(tmp_es) - in_edge_num += 1 - else: - pass - else: - abstract_state.joinWith(self.post_abs_trace[edge.getSrcNode()]) - in_edge_num += 1 - else: - abstract_state.joinWith(self.post_abs_trace[edge.getSrcNode()]) - in_edge_num += 1 - else: - pass - if in_edge_num == 0: - print(f"Error: No predecessors for block {block.getId()}") - return (False, None) - return (True, abstract_state) - + # mergeStatesFromPredecessors is a student TODO this year and lives in + # Assignment_3.py. def isBranchFeasible(self, intraEdge: pysvf.IntraCFGEdge, abstractState: pysvf.AbstractState) -> bool : cmp_var = intraEdge.getCondition() @@ -882,41 +747,7 @@ def analyse(self): self.buf_overflow_helper.printReport() - """ - Update the abstract state based on the given statement. - This function updates the abstract state based on the given statement. - """ - def updateAbsState(self, stmt:pysvf.SVFStmt): - if isinstance(stmt, pysvf.AddrStmt): - self.updateStateOnAddr(stmt) - elif isinstance(stmt, pysvf.BinaryOPStmt): - self.updateStateOnBinary(stmt) - elif isinstance(stmt, pysvf.CmpStmt): - self.updateStateOnCmp(stmt) - elif isinstance(stmt, pysvf.LoadStmt): - self.updateStateOnLoad(stmt) - elif isinstance(stmt, pysvf.StoreStmt): - self.updateStateOnStore(stmt) - elif isinstance(stmt, pysvf.CopyStmt): - self.updateStateOnCopy(stmt) - elif isinstance(stmt, pysvf.GepStmt): - self.updateStateOnGep(stmt) - #phi - elif isinstance(stmt, pysvf.PhiStmt): - self.updateStateOnPhi(stmt) - # callpe - elif isinstance(stmt, pysvf.CallPE): - self.updateStateOnCall(stmt) - # retpe - elif isinstance(stmt, pysvf.RetPE): - self.updateStateOnRet(stmt) - #select - elif isinstance(stmt, pysvf.SelectStmt): - self.updateStateOnSelect(stmt) - elif isinstance(stmt, pysvf.UnaryOPStmt) or isinstance(stmt, pysvf.BranchStmt): - pass - else: - assert False , "Unhandled statement type" + # updateAbsState is a student TODO this year and lives in Assignment_3.py. """ Initialize an object variable in the abstract state. @@ -1103,39 +934,4 @@ def updateStateOnSelect(self, select: pysvf.SelectStmt): - """ - Calculate the access offset for a given object ID and GEP statement. - - This function determines the offset of a memory access relative to the base address - of an object. It handles different types of objects, including base objects, sub-objects - of aggregate objects, and dummy objects. The offset is calculated using the abstract - state and helper functions. - - :param obj_id: The ID of the object being accessed. - :type obj_id: int - :param gep: The GEP (GetElementPtr) statement representing the memory access. - :type gep: pysvf.GepStmt - :return: The calculated access offset as an IntervalValue. - :rtype: pysvf.IntervalValue - """ - def getAccessOffset(self, objId: int, gep: pysvf.GepStmt) -> pysvf.IntervalValue: - obj = self.svfir.getGNode(objId) - abstract_state = self.post_abs_trace[gep.getICFGNode()] - - # Field-insensitive base object - if isinstance(obj, pysvf.BaseObjVar): - # Get base size - access_offset = self.buf_overflow_helper.getByteOffset(abstract_state, gep) - return access_offset - - # A sub-object of an aggregate object - elif isinstance(obj, pysvf.GepObjVar): - access_offset = ( - self.buf_overflow_helper.getGepObjOffsetFromBase(obj) - + self.buf_overflow_helper.getByteOffset(abstract_state, gep) - ) - return access_offset - - else: - assert isinstance(obj, pysvf.DummyObjVar), "What other types of object?" - return pysvf.IntervalValue.top() + # getAccessOffset is a student TODO this year and lives in Assignment_3.py. diff --git a/Assignment-3/Python/CMakeLists.txt b/Assignment-3/Python/CMakeLists.txt index 13679af..6a814ca 100755 --- a/Assignment-3/Python/CMakeLists.txt +++ b/Assignment-3/Python/CMakeLists.txt @@ -16,6 +16,20 @@ foreach(filename ${buf_files}) WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) endforeach() + +# Categorised abstract-execution corpus (Tests/cases). Each case self-validates +# via svf_assert / UNSAFE_* checkpoints, so a clean exit means pass. Only the +# passing corpus lives under Tests/cases; known-failing cases are kept under +# Tests/cases-unpass and are intentionally NOT registered. +file(GLOB_RECURSE case_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/../Tests "${CMAKE_CURRENT_SOURCE_DIR}/../Tests/cases/*.ll") +foreach(filename ${case_files}) + add_test( + NAME ass3-cases-py/${filename} + COMMAND python3 test-ae.py ${CMAKE_CURRENT_SOURCE_DIR}/../Tests/${filename} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + set_tests_properties(ass3-cases-py/${filename} PROPERTIES TIMEOUT 180) +endforeach() include(CTest) diff --git a/Assignment-3/Python/test-ae.py b/Assignment-3/Python/test-ae.py index d6943a3..90e0bfe 100644 --- a/Assignment-3/Python/test-ae.py +++ b/Assignment-3/Python/test-ae.py @@ -5,7 +5,7 @@ if len(sys.argv) < 2: print("Usage: python3 test-ae.py ") sys.exit(1) - pysvf.buildSVFModule(sys.argv[1:]) + pysvf.buildSVFModule(sys.argv[1:]) # Build Program Assignment Graph (SVFIR) pag = pysvf.getPAG() ass3 = Assignment3(pag) ass3.analyse() diff --git a/Assignment-3/Tests/ae/test1.c b/Assignment-3/Tests/ae/test1.c deleted file mode 100644 index c45a8c3..0000000 --- a/Assignment-3/Tests/ae/test1.c +++ /dev/null @@ -1,15 +0,0 @@ - // -// Created by Jiawei Wang on 1/17/22. -// - -#include "stdbool.h" -extern void svf_assert(bool); -#define LEN 10 - -int main() { - int a[LEN] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - int *p = a + 9; - *p = 10; - svf_assert(a[LEN-1] == 10); - return 0; -} diff --git a/Assignment-3/Tests/ae/test1.ll b/Assignment-3/Tests/ae/test1.ll deleted file mode 100644 index 802e86e..0000000 --- a/Assignment-3/Tests/ae/test1.ll +++ /dev/null @@ -1,74 +0,0 @@ -; ModuleID = './test1.ll' -source_filename = "./test1.c" -target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" -target triple = "arm64-apple-macosx14.0.0" - -@__const.main.a = private unnamed_addr constant [10 x i32] [i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9], align 4 - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define i32 @main() #0 !dbg !9 { -entry: - %a = alloca [10 x i32], align 4 - call void @llvm.dbg.declare(metadata ptr %a, metadata !15, metadata !DIExpression()), !dbg !19 - call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 @__const.main.a, i64 40, i1 false), !dbg !19 - %arraydecay = getelementptr inbounds [10 x i32], ptr %a, i64 0, i64 0, !dbg !20 - %add.ptr = getelementptr inbounds i32, ptr %arraydecay, i64 9, !dbg !21 - call void @llvm.dbg.value(metadata ptr %add.ptr, metadata !22, metadata !DIExpression()), !dbg !24 - store i32 10, ptr %add.ptr, align 4, !dbg !25 - %arrayidx = getelementptr inbounds [10 x i32], ptr %a, i64 0, i64 9, !dbg !26 - %0 = load i32, ptr %arrayidx, align 4, !dbg !26 - %cmp = icmp eq i32 %0, 10, !dbg !27 - call void @svf_assert(i1 noundef zeroext %cmp), !dbg !28 - ret i32 0, !dbg !29 -} - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 - -; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) -declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #2 - -declare void @svf_assert(i1 noundef zeroext) #3 - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.value(metadata, metadata, metadata) #1 - -attributes #0 = { noinline nounwind ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } -attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } -attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } -attributes #3 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } - -!llvm.dbg.cu = !{!0} -!llvm.module.flags = !{!2, !3, !4, !5, !6, !7} -!llvm.ident = !{!8} - -!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "Homebrew clang version 16.0.6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None, sysroot: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk", sdk: "MacOSX14.sdk") -!1 = !DIFile(filename: "test1.c", directory: "/Users/z5489735/2023/0513/Software-Security-Analysis/Assignment-3/Tests/ae") -!2 = !{i32 7, !"Dwarf Version", i32 4} -!3 = !{i32 2, !"Debug Info Version", i32 3} -!4 = !{i32 1, !"wchar_size", i32 4} -!5 = !{i32 8, !"PIC Level", i32 2} -!6 = !{i32 7, !"uwtable", i32 1} -!7 = !{i32 7, !"frame-pointer", i32 1} -!8 = !{!"Homebrew clang version 16.0.6"} -!9 = distinct !DISubprogram(name: "main", scope: !10, file: !10, line: 9, type: !11, scopeLine: 9, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !14) -!10 = !DIFile(filename: "./test1.c", directory: "/Users/z5489735/2023/0513/Software-Security-Analysis/Assignment-3/Tests/ae") -!11 = !DISubroutineType(types: !12) -!12 = !{!13} -!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) -!14 = !{} -!15 = !DILocalVariable(name: "a", scope: !9, file: !10, line: 10, type: !16) -!16 = !DICompositeType(tag: DW_TAG_array_type, baseType: !13, size: 320, elements: !17) -!17 = !{!18} -!18 = !DISubrange(count: 10) -!19 = !DILocation(line: 10, column: 9, scope: !9) -!20 = !DILocation(line: 11, column: 15, scope: !9) -!21 = !DILocation(line: 11, column: 17, scope: !9) -!22 = !DILocalVariable(name: "p", scope: !9, file: !10, line: 11, type: !23) -!23 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !13, size: 64) -!24 = !DILocation(line: 0, scope: !9) -!25 = !DILocation(line: 12, column: 8, scope: !9) -!26 = !DILocation(line: 13, column: 16, scope: !9) -!27 = !DILocation(line: 13, column: 25, scope: !9) -!28 = !DILocation(line: 13, column: 5, scope: !9) -!29 = !DILocation(line: 14, column: 5, scope: !9) diff --git a/Assignment-3/Tests/ae/test2.c b/Assignment-3/Tests/ae/test2.c deleted file mode 100644 index de3da20..0000000 --- a/Assignment-3/Tests/ae/test2.c +++ /dev/null @@ -1,9 +0,0 @@ -#include "stdbool.h" -extern void svf_assert(bool); -int main() { - int a = 10; - int b = 5; - int c = a / b; - svf_assert(c == 2); - return 0; -} \ No newline at end of file diff --git a/Assignment-3/Tests/ae/test2.ll b/Assignment-3/Tests/ae/test2.ll deleted file mode 100644 index c084295..0000000 --- a/Assignment-3/Tests/ae/test2.ll +++ /dev/null @@ -1,56 +0,0 @@ -; ModuleID = './test2.ll' -source_filename = "./test2.c" -target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" -target triple = "arm64-apple-macosx14.0.0" - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define i32 @main() #0 !dbg !9 { -entry: - call void @llvm.dbg.value(metadata i32 10, metadata !15, metadata !DIExpression()), !dbg !16 - call void @llvm.dbg.value(metadata i32 5, metadata !17, metadata !DIExpression()), !dbg !16 - %div = sdiv i32 10, 5, !dbg !18 - call void @llvm.dbg.value(metadata i32 %div, metadata !19, metadata !DIExpression()), !dbg !16 - %cmp = icmp eq i32 %div, 2, !dbg !20 - call void @svf_assert(i1 noundef zeroext %cmp), !dbg !21 - ret i32 0, !dbg !22 -} - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 - -declare void @svf_assert(i1 noundef zeroext) #2 - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.value(metadata, metadata, metadata) #1 - -attributes #0 = { noinline nounwind ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } -attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } -attributes #2 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } - -!llvm.dbg.cu = !{!0} -!llvm.module.flags = !{!2, !3, !4, !5, !6, !7} -!llvm.ident = !{!8} - -!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "Homebrew clang version 16.0.6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None, sysroot: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk", sdk: "MacOSX14.sdk") -!1 = !DIFile(filename: "test2.c", directory: "/Users/z5489735/2023/0513/Software-Security-Analysis/Assignment-3/Tests/ae") -!2 = !{i32 7, !"Dwarf Version", i32 4} -!3 = !{i32 2, !"Debug Info Version", i32 3} -!4 = !{i32 1, !"wchar_size", i32 4} -!5 = !{i32 8, !"PIC Level", i32 2} -!6 = !{i32 7, !"uwtable", i32 1} -!7 = !{i32 7, !"frame-pointer", i32 1} -!8 = !{!"Homebrew clang version 16.0.6"} -!9 = distinct !DISubprogram(name: "main", scope: !10, file: !10, line: 3, type: !11, scopeLine: 3, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !14) -!10 = !DIFile(filename: "./test2.c", directory: "/Users/z5489735/2023/0513/Software-Security-Analysis/Assignment-3/Tests/ae") -!11 = !DISubroutineType(types: !12) -!12 = !{!13} -!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) -!14 = !{} -!15 = !DILocalVariable(name: "a", scope: !9, file: !10, line: 4, type: !13) -!16 = !DILocation(line: 0, scope: !9) -!17 = !DILocalVariable(name: "b", scope: !9, file: !10, line: 5, type: !13) -!18 = !DILocation(line: 6, column: 12, scope: !9) -!19 = !DILocalVariable(name: "c", scope: !9, file: !10, line: 6, type: !13) -!20 = !DILocation(line: 7, column: 15, scope: !9) -!21 = !DILocation(line: 7, column: 2, scope: !9) -!22 = !DILocation(line: 8, column: 2, scope: !9) diff --git a/Assignment-3/Tests/ae/test3.c b/Assignment-3/Tests/ae/test3.c deleted file mode 100644 index 5b02866..0000000 --- a/Assignment-3/Tests/ae/test3.c +++ /dev/null @@ -1,31 +0,0 @@ -#include "stdbool.h" -extern void svf_assert(bool); - -int foo(void) -{ - return 1; -} - -int main() { - int x, y; - x = 1; - y = 0; - switch (foo()) - { - case 0: - x += 1; - break; - case 1: - x += y; - break; - case 2: - x -= y; - break; - default: - x++; - y++; - break; - } - svf_assert(x >= y); - return 0; -} \ No newline at end of file diff --git a/Assignment-3/Tests/ae/test3.ll b/Assignment-3/Tests/ae/test3.ll deleted file mode 100644 index 9ef5d83..0000000 --- a/Assignment-3/Tests/ae/test3.ll +++ /dev/null @@ -1,106 +0,0 @@ -; ModuleID = './test3.ll' -source_filename = "test3.c" -target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" -target triple = "arm64-apple-macosx14.0.0" - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define i32 @foo() #0 !dbg !9 { -entry: - ret i32 1, !dbg !14 -} - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define i32 @main() #0 !dbg !15 { -entry: - call void @llvm.dbg.value(metadata i32 1, metadata !16, metadata !DIExpression()), !dbg !17 - call void @llvm.dbg.value(metadata i32 0, metadata !18, metadata !DIExpression()), !dbg !17 - %call = call i32 @foo(), !dbg !19 - switch i32 %call, label %sw.default [ - i32 0, label %sw.bb - i32 1, label %sw.bb1 - i32 2, label %sw.bb3 - ], !dbg !20 - -sw.bb: ; preds = %entry - %add = add nsw i32 1, 1, !dbg !21 - call void @llvm.dbg.value(metadata i32 %add, metadata !16, metadata !DIExpression()), !dbg !17 - br label %sw.epilog, !dbg !23 - -sw.bb1: ; preds = %entry - %add2 = add nsw i32 1, 0, !dbg !24 - call void @llvm.dbg.value(metadata i32 %add2, metadata !16, metadata !DIExpression()), !dbg !17 - br label %sw.epilog, !dbg !25 - -sw.bb3: ; preds = %entry - %sub = sub nsw i32 1, 0, !dbg !26 - call void @llvm.dbg.value(metadata i32 %sub, metadata !16, metadata !DIExpression()), !dbg !17 - br label %sw.epilog, !dbg !27 - -sw.default: ; preds = %entry - %inc = add nsw i32 1, 1, !dbg !28 - call void @llvm.dbg.value(metadata i32 %inc, metadata !16, metadata !DIExpression()), !dbg !17 - %inc4 = add nsw i32 0, 1, !dbg !29 - call void @llvm.dbg.value(metadata i32 %inc4, metadata !18, metadata !DIExpression()), !dbg !17 - br label %sw.epilog, !dbg !30 - -sw.epilog: ; preds = %sw.default, %sw.bb3, %sw.bb1, %sw.bb - %x.0 = phi i32 [ %inc, %sw.default ], [ %sub, %sw.bb3 ], [ %add2, %sw.bb1 ], [ %add, %sw.bb ], !dbg !31 - %y.0 = phi i32 [ %inc4, %sw.default ], [ 0, %sw.bb3 ], [ 0, %sw.bb1 ], [ 0, %sw.bb ], !dbg !17 - call void @llvm.dbg.value(metadata i32 %y.0, metadata !18, metadata !DIExpression()), !dbg !17 - call void @llvm.dbg.value(metadata i32 %x.0, metadata !16, metadata !DIExpression()), !dbg !17 - %cmp = icmp sge i32 %x.0, %y.0, !dbg !32 - call void @svf_assert(i1 noundef zeroext %cmp), !dbg !33 - ret i32 0, !dbg !34 -} - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 - -declare void @svf_assert(i1 noundef zeroext) #2 - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.value(metadata, metadata, metadata) #1 - -attributes #0 = { noinline nounwind ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } -attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } -attributes #2 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } - -!llvm.dbg.cu = !{!0} -!llvm.module.flags = !{!2, !3, !4, !5, !6, !7} -!llvm.ident = !{!8} - -!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "Homebrew clang version 16.0.6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None, sysroot: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk", sdk: "MacOSX14.sdk") -!1 = !DIFile(filename: "test3.c", directory: "/Users/z5489735/2023/0718/Software-Security-Analysis/teaching/Assignment-3/Tests/ae") -!2 = !{i32 7, !"Dwarf Version", i32 4} -!3 = !{i32 2, !"Debug Info Version", i32 3} -!4 = !{i32 1, !"wchar_size", i32 4} -!5 = !{i32 8, !"PIC Level", i32 2} -!6 = !{i32 7, !"uwtable", i32 1} -!7 = !{i32 7, !"frame-pointer", i32 1} -!8 = !{!"Homebrew clang version 16.0.6"} -!9 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 4, type: !10, scopeLine: 5, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !13) -!10 = !DISubroutineType(types: !11) -!11 = !{!12} -!12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) -!13 = !{} -!14 = !DILocation(line: 6, column: 5, scope: !9) -!15 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 9, type: !10, scopeLine: 9, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !13) -!16 = !DILocalVariable(name: "x", scope: !15, file: !1, line: 10, type: !12) -!17 = !DILocation(line: 0, scope: !15) -!18 = !DILocalVariable(name: "y", scope: !15, file: !1, line: 10, type: !12) -!19 = !DILocation(line: 13, column: 11, scope: !15) -!20 = !DILocation(line: 13, column: 3, scope: !15) -!21 = !DILocation(line: 16, column: 11, scope: !22) -!22 = distinct !DILexicalBlock(scope: !15, file: !1, line: 14, column: 3) -!23 = !DILocation(line: 17, column: 9, scope: !22) -!24 = !DILocation(line: 19, column: 11, scope: !22) -!25 = !DILocation(line: 20, column: 9, scope: !22) -!26 = !DILocation(line: 22, column: 11, scope: !22) -!27 = !DILocation(line: 23, column: 9, scope: !22) -!28 = !DILocation(line: 25, column: 10, scope: !22) -!29 = !DILocation(line: 26, column: 10, scope: !22) -!30 = !DILocation(line: 27, column: 9, scope: !22) -!31 = !DILocation(line: 0, scope: !22) -!32 = !DILocation(line: 29, column: 18, scope: !15) -!33 = !DILocation(line: 29, column: 5, scope: !15) -!34 = !DILocation(line: 30, column: 5, scope: !15) diff --git a/Assignment-3/Tests/ae/test4.c b/Assignment-3/Tests/ae/test4.c deleted file mode 100644 index 321160e..0000000 --- a/Assignment-3/Tests/ae/test4.c +++ /dev/null @@ -1,12 +0,0 @@ -#include "stdbool.h" -extern void svf_assert(bool); - -int main() { - int x; - x=1; - while(x<10000) { - x++; - } - svf_assert(x == 10000); - return 0; -} \ No newline at end of file diff --git a/Assignment-3/Tests/ae/test4.ll b/Assignment-3/Tests/ae/test4.ll deleted file mode 100644 index 4fb8f09..0000000 --- a/Assignment-3/Tests/ae/test4.ll +++ /dev/null @@ -1,71 +0,0 @@ -; ModuleID = './test4.ll' -source_filename = "./test4.c" -target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" -target triple = "arm64-apple-macosx14.0.0" - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define i32 @main() #0 !dbg !9 { -entry: - call void @llvm.dbg.value(metadata i32 1, metadata !15, metadata !DIExpression()), !dbg !16 - br label %while.cond, !dbg !17 - -while.cond: ; preds = %while.body, %entry - %x.0 = phi i32 [ 1, %entry ], [ %inc, %while.body ], !dbg !16 - call void @llvm.dbg.value(metadata i32 %x.0, metadata !15, metadata !DIExpression()), !dbg !16 - %cmp = icmp slt i32 %x.0, 10000, !dbg !18 - br i1 %cmp, label %while.body, label %while.end, !dbg !17 - -while.body: ; preds = %while.cond - %inc = add nsw i32 %x.0, 1, !dbg !19 - call void @llvm.dbg.value(metadata i32 %inc, metadata !15, metadata !DIExpression()), !dbg !16 - br label %while.cond, !dbg !17, !llvm.loop !21 - -while.end: ; preds = %while.cond - %cmp1 = icmp eq i32 %x.0, 10000, !dbg !24 - call void @svf_assert(i1 noundef zeroext %cmp1), !dbg !25 - ret i32 0, !dbg !26 -} - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 - -declare void @svf_assert(i1 noundef zeroext) #2 - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.value(metadata, metadata, metadata) #1 - -attributes #0 = { noinline nounwind ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } -attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } -attributes #2 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } - -!llvm.dbg.cu = !{!0} -!llvm.module.flags = !{!2, !3, !4, !5, !6, !7} -!llvm.ident = !{!8} - -!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "Homebrew clang version 16.0.6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None, sysroot: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk", sdk: "MacOSX14.sdk") -!1 = !DIFile(filename: "test4.c", directory: "/Users/z5489735/2023/0513/Software-Security-Analysis/Assignment-3/Tests/ae") -!2 = !{i32 7, !"Dwarf Version", i32 4} -!3 = !{i32 2, !"Debug Info Version", i32 3} -!4 = !{i32 1, !"wchar_size", i32 4} -!5 = !{i32 8, !"PIC Level", i32 2} -!6 = !{i32 7, !"uwtable", i32 1} -!7 = !{i32 7, !"frame-pointer", i32 1} -!8 = !{!"Homebrew clang version 16.0.6"} -!9 = distinct !DISubprogram(name: "main", scope: !10, file: !10, line: 4, type: !11, scopeLine: 4, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !14) -!10 = !DIFile(filename: "./test4.c", directory: "/Users/z5489735/2023/0513/Software-Security-Analysis/Assignment-3/Tests/ae") -!11 = !DISubroutineType(types: !12) -!12 = !{!13} -!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) -!14 = !{} -!15 = !DILocalVariable(name: "x", scope: !9, file: !10, line: 5, type: !13) -!16 = !DILocation(line: 0, scope: !9) -!17 = !DILocation(line: 7, column: 5, scope: !9) -!18 = !DILocation(line: 7, column: 12, scope: !9) -!19 = !DILocation(line: 8, column: 10, scope: !20) -!20 = distinct !DILexicalBlock(scope: !9, file: !10, line: 7, column: 20) -!21 = distinct !{!21, !17, !22, !23} -!22 = !DILocation(line: 9, column: 5, scope: !9) -!23 = !{!"llvm.loop.mustprogress"} -!24 = !DILocation(line: 10, column: 18, scope: !9) -!25 = !DILocation(line: 10, column: 5, scope: !9) -!26 = !DILocation(line: 11, column: 5, scope: !9) diff --git a/Assignment-3/Tests/buf/test1.c b/Assignment-3/Tests/buf/test1.c deleted file mode 100644 index f6c301b..0000000 --- a/Assignment-3/Tests/buf/test1.c +++ /dev/null @@ -1,6 +0,0 @@ -extern void OVERFLOW(void* data, int size); -int main() { - int arr[5]; - OVERFLOW(arr, 5*sizeof(int)); - arr[5] = 10; -} \ No newline at end of file diff --git a/Assignment-3/Tests/buf/test1.ll b/Assignment-3/Tests/buf/test1.ll deleted file mode 100644 index 7a4f24f..0000000 --- a/Assignment-3/Tests/buf/test1.ll +++ /dev/null @@ -1,55 +0,0 @@ -; ModuleID = './test1.ll' -source_filename = "./test1.c" -target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" -target triple = "arm64-apple-macosx14.0.0" - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define i32 @main() #0 !dbg !9 { -entry: - %arr = alloca [5 x i32], align 4 - call void @llvm.dbg.declare(metadata ptr %arr, metadata !15, metadata !DIExpression()), !dbg !19 - %arraydecay = getelementptr inbounds [5 x i32], ptr %arr, i64 0, i64 0, !dbg !20 - call void @OVERFLOW(ptr noundef %arraydecay, i32 noundef 20), !dbg !21 - %arrayidx = getelementptr inbounds [5 x i32], ptr %arr, i64 0, i64 5, !dbg !22 - store i32 10, ptr %arrayidx, align 4, !dbg !23 - ret i32 0, !dbg !24 -} - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 - -declare void @OVERFLOW(ptr noundef, i32 noundef) #2 - -attributes #0 = { noinline nounwind ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } -attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } -attributes #2 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } - -!llvm.dbg.cu = !{!0} -!llvm.module.flags = !{!2, !3, !4, !5, !6, !7} -!llvm.ident = !{!8} - -!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "Homebrew clang version 16.0.6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None, sysroot: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk", sdk: "MacOSX14.sdk") -!1 = !DIFile(filename: "test1.c", directory: "/Users/z5489735/2023/0513/Software-Security-Analysis/Assignment-3/Tests/buf") -!2 = !{i32 7, !"Dwarf Version", i32 4} -!3 = !{i32 2, !"Debug Info Version", i32 3} -!4 = !{i32 1, !"wchar_size", i32 4} -!5 = !{i32 8, !"PIC Level", i32 2} -!6 = !{i32 7, !"uwtable", i32 1} -!7 = !{i32 7, !"frame-pointer", i32 1} -!8 = !{!"Homebrew clang version 16.0.6"} -!9 = distinct !DISubprogram(name: "main", scope: !10, file: !10, line: 2, type: !11, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !14) -!10 = !DIFile(filename: "./test1.c", directory: "/Users/z5489735/2023/0513/Software-Security-Analysis/Assignment-3/Tests/buf") -!11 = !DISubroutineType(types: !12) -!12 = !{!13} -!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) -!14 = !{} -!15 = !DILocalVariable(name: "arr", scope: !9, file: !10, line: 3, type: !16) -!16 = !DICompositeType(tag: DW_TAG_array_type, baseType: !13, size: 160, elements: !17) -!17 = !{!18} -!18 = !DISubrange(count: 5) -!19 = !DILocation(line: 3, column: 9, scope: !9) -!20 = !DILocation(line: 4, column: 14, scope: !9) -!21 = !DILocation(line: 4, column: 5, scope: !9) -!22 = !DILocation(line: 5, column: 5, scope: !9) -!23 = !DILocation(line: 5, column: 12, scope: !9) -!24 = !DILocation(line: 6, column: 1, scope: !9) diff --git a/Assignment-3/Tests/buf/test2.c b/Assignment-3/Tests/buf/test2.c deleted file mode 100644 index 952760b..0000000 --- a/Assignment-3/Tests/buf/test2.c +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include // For int64_t -#include // For exit and EXIT_FAILURE -extern void OVERFLOW(void* data, int size); - -void CWE121_Stack_Based_Buffer_Overflow__CWE805_int64_t_declare_loop_01_bad() { - int64_t dataBadBuffer[50]; - - int64_t source[100] = {0}; // Fill with 0's - size_t i; - - // POTENTIAL FLAW: Possible buffer overflow if data < 100 - OVERFLOW(dataBadBuffer, 100 * sizeof(int64_t)); - for (i = 0; i < 100; i++) { - dataBadBuffer[i] = source[i]; // Unsafe memory access - } -} - -int main() { - CWE121_Stack_Based_Buffer_Overflow__CWE805_int64_t_declare_loop_01_bad(); - return 0; -} \ No newline at end of file diff --git a/Assignment-3/Tests/buf/test2.ll b/Assignment-3/Tests/buf/test2.ll deleted file mode 100644 index 6470a25..0000000 --- a/Assignment-3/Tests/buf/test2.ll +++ /dev/null @@ -1,126 +0,0 @@ -; ModuleID = './test2.ll' -source_filename = "./test2.c" -target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" -target triple = "arm64-apple-macosx14.0.0" - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define void @CWE121_Stack_Based_Buffer_Overflow__CWE805_int64_t_declare_loop_01_bad() #0 !dbg !9 { -entry: - %dataBadBuffer = alloca [50 x i64], align 8 - %source = alloca [100 x i64], align 8 - call void @llvm.dbg.declare(metadata ptr %dataBadBuffer, metadata !14, metadata !DIExpression()), !dbg !21 - call void @llvm.dbg.declare(metadata ptr %source, metadata !22, metadata !DIExpression()), !dbg !26 - call void @llvm.memset.p0.i64(ptr align 8 %source, i8 0, i64 800, i1 false), !dbg !26 - %arraydecay = getelementptr inbounds [50 x i64], ptr %dataBadBuffer, i64 0, i64 0, !dbg !27 - call void @OVERFLOW(ptr noundef %arraydecay, i32 noundef 800), !dbg !28 - call void @llvm.dbg.value(metadata i64 0, metadata !29, metadata !DIExpression()), !dbg !35 - br label %for.cond, !dbg !36 - -for.cond: ; preds = %for.inc, %entry - %i.0 = phi i64 [ 0, %entry ], [ %inc, %for.inc ], !dbg !38 - call void @llvm.dbg.value(metadata i64 %i.0, metadata !29, metadata !DIExpression()), !dbg !35 - %cmp = icmp ult i64 %i.0, 100, !dbg !39 - br i1 %cmp, label %for.body, label %for.end, !dbg !41 - -for.body: ; preds = %for.cond - %arrayidx = getelementptr inbounds [100 x i64], ptr %source, i64 0, i64 %i.0, !dbg !42 - %0 = load i64, ptr %arrayidx, align 8, !dbg !42 - %arrayidx1 = getelementptr inbounds [50 x i64], ptr %dataBadBuffer, i64 0, i64 %i.0, !dbg !44 - store i64 %0, ptr %arrayidx1, align 8, !dbg !45 - br label %for.inc, !dbg !46 - -for.inc: ; preds = %for.body - %inc = add i64 %i.0, 1, !dbg !47 - call void @llvm.dbg.value(metadata i64 %inc, metadata !29, metadata !DIExpression()), !dbg !35 - br label %for.cond, !dbg !48, !llvm.loop !49 - -for.end: ; preds = %for.cond - ret void, !dbg !52 -} - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 - -; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) -declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) #2 - -declare void @OVERFLOW(ptr noundef, i32 noundef) #3 - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define i32 @main() #0 !dbg !53 { -entry: - call void @CWE121_Stack_Based_Buffer_Overflow__CWE805_int64_t_declare_loop_01_bad(), !dbg !57 - ret i32 0, !dbg !58 -} - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.value(metadata, metadata, metadata) #1 - -attributes #0 = { noinline nounwind ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } -attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } -attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: write) } -attributes #3 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } - -!llvm.dbg.cu = !{!0} -!llvm.module.flags = !{!2, !3, !4, !5, !6, !7} -!llvm.ident = !{!8} - -!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "Homebrew clang version 16.0.6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None, sysroot: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk", sdk: "MacOSX14.sdk") -!1 = !DIFile(filename: "test2.c", directory: "/Users/z5489735/2023/0513/Software-Security-Analysis/Assignment-3/Tests/buf") -!2 = !{i32 7, !"Dwarf Version", i32 4} -!3 = !{i32 2, !"Debug Info Version", i32 3} -!4 = !{i32 1, !"wchar_size", i32 4} -!5 = !{i32 8, !"PIC Level", i32 2} -!6 = !{i32 7, !"uwtable", i32 1} -!7 = !{i32 7, !"frame-pointer", i32 1} -!8 = !{!"Homebrew clang version 16.0.6"} -!9 = distinct !DISubprogram(name: "CWE121_Stack_Based_Buffer_Overflow__CWE805_int64_t_declare_loop_01_bad", scope: !10, file: !10, line: 6, type: !11, scopeLine: 6, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !13) -!10 = !DIFile(filename: "./test2.c", directory: "/Users/z5489735/2023/0513/Software-Security-Analysis/Assignment-3/Tests/buf") -!11 = !DISubroutineType(types: !12) -!12 = !{null} -!13 = !{} -!14 = !DILocalVariable(name: "dataBadBuffer", scope: !9, file: !10, line: 7, type: !15) -!15 = !DICompositeType(tag: DW_TAG_array_type, baseType: !16, size: 3200, elements: !19) -!16 = !DIDerivedType(tag: DW_TAG_typedef, name: "int64_t", file: !17, line: 30, baseType: !18) -!17 = !DIFile(filename: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk/usr/include/sys/_types/_int64_t.h", directory: "") -!18 = !DIBasicType(name: "long long", size: 64, encoding: DW_ATE_signed) -!19 = !{!20} -!20 = !DISubrange(count: 50) -!21 = !DILocation(line: 7, column: 13, scope: !9) -!22 = !DILocalVariable(name: "source", scope: !9, file: !10, line: 9, type: !23) -!23 = !DICompositeType(tag: DW_TAG_array_type, baseType: !16, size: 6400, elements: !24) -!24 = !{!25} -!25 = !DISubrange(count: 100) -!26 = !DILocation(line: 9, column: 13, scope: !9) -!27 = !DILocation(line: 13, column: 14, scope: !9) -!28 = !DILocation(line: 13, column: 5, scope: !9) -!29 = !DILocalVariable(name: "i", scope: !9, file: !10, line: 10, type: !30) -!30 = !DIDerivedType(tag: DW_TAG_typedef, name: "size_t", file: !31, line: 31, baseType: !32) -!31 = !DIFile(filename: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk/usr/include/sys/_types/_size_t.h", directory: "") -!32 = !DIDerivedType(tag: DW_TAG_typedef, name: "__darwin_size_t", file: !33, line: 70, baseType: !34) -!33 = !DIFile(filename: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk/usr/include/arm/_types.h", directory: "") -!34 = !DIBasicType(name: "unsigned long", size: 64, encoding: DW_ATE_unsigned) -!35 = !DILocation(line: 0, scope: !9) -!36 = !DILocation(line: 14, column: 10, scope: !37) -!37 = distinct !DILexicalBlock(scope: !9, file: !10, line: 14, column: 5) -!38 = !DILocation(line: 14, scope: !37) -!39 = !DILocation(line: 14, column: 19, scope: !40) -!40 = distinct !DILexicalBlock(scope: !37, file: !10, line: 14, column: 5) -!41 = !DILocation(line: 14, column: 5, scope: !37) -!42 = !DILocation(line: 15, column: 28, scope: !43) -!43 = distinct !DILexicalBlock(scope: !40, file: !10, line: 14, column: 31) -!44 = !DILocation(line: 15, column: 9, scope: !43) -!45 = !DILocation(line: 15, column: 26, scope: !43) -!46 = !DILocation(line: 16, column: 5, scope: !43) -!47 = !DILocation(line: 14, column: 27, scope: !40) -!48 = !DILocation(line: 14, column: 5, scope: !40) -!49 = distinct !{!49, !41, !50, !51} -!50 = !DILocation(line: 16, column: 5, scope: !37) -!51 = !{!"llvm.loop.mustprogress"} -!52 = !DILocation(line: 17, column: 1, scope: !9) -!53 = distinct !DISubprogram(name: "main", scope: !10, file: !10, line: 19, type: !54, scopeLine: 19, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !13) -!54 = !DISubroutineType(types: !55) -!55 = !{!56} -!56 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) -!57 = !DILocation(line: 20, column: 5, scope: !53) -!58 = !DILocation(line: 21, column: 5, scope: !53) diff --git a/Assignment-3/Tests/buf/test3.c b/Assignment-3/Tests/buf/test3.c deleted file mode 100644 index c8793c3..0000000 --- a/Assignment-3/Tests/buf/test3.c +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include // For rand() and srand() -#include // For time() -extern void OVERFLOW(void* data, int size); - - -void CWE121_Stack_Based_Buffer_Overflow__CWE129_rand_01_bad() { - int data; - /* Initialize data */ - data = -1; - /* POTENTIAL FLAW: Set data to the remainder when 9999 is divided by 100 */ - data = 9999 % 100; - - int i; - int buffer[10] = { 0 }; - /* POTENTIAL FLAW: Attempt to write to an index of the array that is above the upper bound - * This code does check to see if the array index is negative */ - if (data >= 0) - { - OVERFLOW(buffer, 99 * sizeof(int)); - buffer[data] = 1; - } -} - -int main() { - CWE121_Stack_Based_Buffer_Overflow__CWE129_rand_01_bad(); - return 0; -} diff --git a/Assignment-3/Tests/buf/test3.ll b/Assignment-3/Tests/buf/test3.ll deleted file mode 100644 index 22db37b..0000000 --- a/Assignment-3/Tests/buf/test3.ll +++ /dev/null @@ -1,94 +0,0 @@ -; ModuleID = './test3.ll' -source_filename = "test3.c" -target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" -target triple = "arm64-apple-macosx14.0.0" - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define void @CWE121_Stack_Based_Buffer_Overflow__CWE129_rand_01_bad() #0 !dbg !9 { -entry: - %buffer = alloca [10 x i32], align 4 - call void @llvm.dbg.value(metadata i32 -1, metadata !13, metadata !DIExpression()), !dbg !15 - call void @llvm.dbg.value(metadata i32 99, metadata !13, metadata !DIExpression()), !dbg !15 - call void @llvm.dbg.declare(metadata ptr undef, metadata !16, metadata !DIExpression()), !dbg !17 - call void @llvm.dbg.declare(metadata ptr %buffer, metadata !18, metadata !DIExpression()), !dbg !22 - call void @llvm.memset.p0.i64(ptr align 4 %buffer, i8 0, i64 40, i1 false), !dbg !22 - %cmp = icmp sge i32 99, 0, !dbg !23 - br i1 %cmp, label %if.then, label %if.end, !dbg !25 - -if.then: ; preds = %entry - %arraydecay = getelementptr inbounds [10 x i32], ptr %buffer, i64 0, i64 0, !dbg !26 - call void @OVERFLOW(ptr noundef %arraydecay, i32 noundef 396), !dbg !28 - %idxprom = sext i32 99 to i64, !dbg !29 - %arrayidx = getelementptr inbounds [10 x i32], ptr %buffer, i64 0, i64 %idxprom, !dbg !29 - store i32 1, ptr %arrayidx, align 4, !dbg !30 - br label %if.end, !dbg !31 - -if.end: ; preds = %if.then, %entry - ret void, !dbg !32 -} - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 - -; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) -declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) #2 - -declare void @OVERFLOW(ptr noundef, i32 noundef) #3 - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define i32 @main() #0 !dbg !33 { -entry: - call void @CWE121_Stack_Based_Buffer_Overflow__CWE129_rand_01_bad(), !dbg !36 - ret i32 0, !dbg !37 -} - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.value(metadata, metadata, metadata) #1 - -attributes #0 = { noinline nounwind ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } -attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } -attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: write) } -attributes #3 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } - -!llvm.dbg.cu = !{!0} -!llvm.module.flags = !{!2, !3, !4, !5, !6, !7} -!llvm.ident = !{!8} - -!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "Homebrew clang version 16.0.6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None, sysroot: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk", sdk: "MacOSX14.sdk") -!1 = !DIFile(filename: "test3.c", directory: "/Users/z5489735/2023/0617/solution/Software-Security-Analysis/Assignment-3/Tests/buf") -!2 = !{i32 7, !"Dwarf Version", i32 4} -!3 = !{i32 2, !"Debug Info Version", i32 3} -!4 = !{i32 1, !"wchar_size", i32 4} -!5 = !{i32 8, !"PIC Level", i32 2} -!6 = !{i32 7, !"uwtable", i32 1} -!7 = !{i32 7, !"frame-pointer", i32 1} -!8 = !{!"Homebrew clang version 16.0.6"} -!9 = distinct !DISubprogram(name: "CWE121_Stack_Based_Buffer_Overflow__CWE129_rand_01_bad", scope: !1, file: !1, line: 7, type: !10, scopeLine: 7, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !12) -!10 = !DISubroutineType(types: !11) -!11 = !{null} -!12 = !{} -!13 = !DILocalVariable(name: "data", scope: !9, file: !1, line: 8, type: !14) -!14 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) -!15 = !DILocation(line: 0, scope: !9) -!16 = !DILocalVariable(name: "i", scope: !9, file: !1, line: 14, type: !14) -!17 = !DILocation(line: 14, column: 9, scope: !9) -!18 = !DILocalVariable(name: "buffer", scope: !9, file: !1, line: 15, type: !19) -!19 = !DICompositeType(tag: DW_TAG_array_type, baseType: !14, size: 320, elements: !20) -!20 = !{!21} -!21 = !DISubrange(count: 10) -!22 = !DILocation(line: 15, column: 9, scope: !9) -!23 = !DILocation(line: 18, column: 14, scope: !24) -!24 = distinct !DILexicalBlock(scope: !9, file: !1, line: 18, column: 9) -!25 = !DILocation(line: 18, column: 9, scope: !9) -!26 = !DILocation(line: 20, column: 18, scope: !27) -!27 = distinct !DILexicalBlock(scope: !24, file: !1, line: 19, column: 5) -!28 = !DILocation(line: 20, column: 9, scope: !27) -!29 = !DILocation(line: 21, column: 9, scope: !27) -!30 = !DILocation(line: 21, column: 22, scope: !27) -!31 = !DILocation(line: 22, column: 5, scope: !27) -!32 = !DILocation(line: 23, column: 1, scope: !9) -!33 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 25, type: !34, scopeLine: 25, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !12) -!34 = !DISubroutineType(types: !35) -!35 = !{!14} -!36 = !DILocation(line: 26, column: 5, scope: !33) -!37 = !DILocation(line: 27, column: 5, scope: !33) diff --git a/Assignment-3/Tests/buf/test4.c b/Assignment-3/Tests/buf/test4.c deleted file mode 100644 index 2afc484..0000000 --- a/Assignment-3/Tests/buf/test4.c +++ /dev/null @@ -1,7 +0,0 @@ -void mem_insert(void *buffer, const void *data, int data_size, int position); -extern void OVERFLOW(void* data, int size); -int main() { - char buffer[10] = {0}; - mem_insert(buffer, "abcdef", 6, 5); - OVERFLOW(buffer, 11); -} \ No newline at end of file diff --git a/Assignment-3/Tests/buf/test4.ll b/Assignment-3/Tests/buf/test4.ll deleted file mode 100644 index 5c002a8..0000000 --- a/Assignment-3/Tests/buf/test4.ll +++ /dev/null @@ -1,70 +0,0 @@ -; ModuleID = './test4.ll' -source_filename = "test4.c" -target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" -target triple = "arm64-apple-macosx14.0.0" - -@.str = private unnamed_addr constant [7 x i8] c"abcdef\00", align 1, !dbg !0 - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define i32 @main() #0 !dbg !16 { -entry: - %buffer = alloca [10 x i8], align 1 - call void @llvm.dbg.declare(metadata ptr %buffer, metadata !21, metadata !DIExpression()), !dbg !25 - call void @llvm.memset.p0.i64(ptr align 1 %buffer, i8 0, i64 10, i1 false), !dbg !25 - %arraydecay = getelementptr inbounds [10 x i8], ptr %buffer, i64 0, i64 0, !dbg !26 - call void @mem_insert(ptr noundef %arraydecay, ptr noundef @.str, i32 noundef 6, i32 noundef 5), !dbg !27 - %arraydecay1 = getelementptr inbounds [10 x i8], ptr %buffer, i64 0, i64 0, !dbg !28 - call void @OVERFLOW(ptr noundef %arraydecay1, i32 noundef 11), !dbg !29 - ret i32 0, !dbg !30 -} - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 - -; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) -declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) #2 - -declare void @mem_insert(ptr noundef, ptr noundef, i32 noundef, i32 noundef) #3 - -declare void @OVERFLOW(ptr noundef, i32 noundef) #3 - -attributes #0 = { noinline nounwind ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } -attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } -attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: write) } -attributes #3 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } - -!llvm.dbg.cu = !{!7} -!llvm.module.flags = !{!9, !10, !11, !12, !13, !14} -!llvm.ident = !{!15} - -!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) -!1 = distinct !DIGlobalVariable(scope: null, file: !2, line: 5, type: !3, isLocal: true, isDefinition: true) -!2 = !DIFile(filename: "test4.c", directory: "/Users/z5489735/2023/0617/Software-Security-Analysis/Assignment-3/Tests/buf") -!3 = !DICompositeType(tag: DW_TAG_array_type, baseType: !4, size: 56, elements: !5) -!4 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char) -!5 = !{!6} -!6 = !DISubrange(count: 7) -!7 = distinct !DICompileUnit(language: DW_LANG_C11, file: !2, producer: "Homebrew clang version 16.0.6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !8, splitDebugInlining: false, nameTableKind: None, sysroot: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk", sdk: "MacOSX14.sdk") -!8 = !{!0} -!9 = !{i32 7, !"Dwarf Version", i32 4} -!10 = !{i32 2, !"Debug Info Version", i32 3} -!11 = !{i32 1, !"wchar_size", i32 4} -!12 = !{i32 8, !"PIC Level", i32 2} -!13 = !{i32 7, !"uwtable", i32 1} -!14 = !{i32 7, !"frame-pointer", i32 1} -!15 = !{!"Homebrew clang version 16.0.6"} -!16 = distinct !DISubprogram(name: "main", scope: !2, file: !2, line: 3, type: !17, scopeLine: 3, spFlags: DISPFlagDefinition, unit: !7, retainedNodes: !20) -!17 = !DISubroutineType(types: !18) -!18 = !{!19} -!19 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) -!20 = !{} -!21 = !DILocalVariable(name: "buffer", scope: !16, file: !2, line: 4, type: !22) -!22 = !DICompositeType(tag: DW_TAG_array_type, baseType: !4, size: 80, elements: !23) -!23 = !{!24} -!24 = !DISubrange(count: 10) -!25 = !DILocation(line: 4, column: 7, scope: !16) -!26 = !DILocation(line: 5, column: 23, scope: !16) -!27 = !DILocation(line: 5, column: 2, scope: !16) -!28 = !DILocation(line: 6, column: 11, scope: !16) -!29 = !DILocation(line: 6, column: 2, scope: !16) -!30 = !DILocation(line: 7, column: 1, scope: !16) diff --git a/Assignment-3/Tests/buf/test5.c b/Assignment-3/Tests/buf/test5.c deleted file mode 100644 index c0ed835..0000000 --- a/Assignment-3/Tests/buf/test5.c +++ /dev/null @@ -1,7 +0,0 @@ -void str_insert(void *buffer, const void *data, int position); -extern void OVERFLOW(void* data, int size); -int main() { - char buffer[10] = {0}; - str_insert(buffer, "abcdef", 5); - OVERFLOW(buffer, 11); -} \ No newline at end of file diff --git a/Assignment-3/Tests/buf/test5.ll b/Assignment-3/Tests/buf/test5.ll deleted file mode 100644 index 6e18bf9..0000000 --- a/Assignment-3/Tests/buf/test5.ll +++ /dev/null @@ -1,70 +0,0 @@ -; ModuleID = './test5.ll' -source_filename = "test5.c" -target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" -target triple = "arm64-apple-macosx14.0.0" - -@.str = private unnamed_addr constant [7 x i8] c"abcdef\00", align 1, !dbg !0 - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define i32 @main() #0 !dbg !16 { -entry: - %buffer = alloca [10 x i8], align 1 - call void @llvm.dbg.declare(metadata ptr %buffer, metadata !21, metadata !DIExpression()), !dbg !25 - call void @llvm.memset.p0.i64(ptr align 1 %buffer, i8 0, i64 10, i1 false), !dbg !25 - %arraydecay = getelementptr inbounds [10 x i8], ptr %buffer, i64 0, i64 0, !dbg !26 - call void @str_insert(ptr noundef %arraydecay, ptr noundef @.str, i32 noundef 5), !dbg !27 - %arraydecay1 = getelementptr inbounds [10 x i8], ptr %buffer, i64 0, i64 0, !dbg !28 - call void @OVERFLOW(ptr noundef %arraydecay1, i32 noundef 11), !dbg !29 - ret i32 0, !dbg !30 -} - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 - -; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) -declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) #2 - -declare void @str_insert(ptr noundef, ptr noundef, i32 noundef) #3 - -declare void @OVERFLOW(ptr noundef, i32 noundef) #3 - -attributes #0 = { noinline nounwind ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } -attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } -attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: write) } -attributes #3 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } - -!llvm.dbg.cu = !{!7} -!llvm.module.flags = !{!9, !10, !11, !12, !13, !14} -!llvm.ident = !{!15} - -!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) -!1 = distinct !DIGlobalVariable(scope: null, file: !2, line: 5, type: !3, isLocal: true, isDefinition: true) -!2 = !DIFile(filename: "test5.c", directory: "/Users/z5489735/2023/0617/Software-Security-Analysis/Assignment-3/Tests/buf") -!3 = !DICompositeType(tag: DW_TAG_array_type, baseType: !4, size: 56, elements: !5) -!4 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char) -!5 = !{!6} -!6 = !DISubrange(count: 7) -!7 = distinct !DICompileUnit(language: DW_LANG_C11, file: !2, producer: "Homebrew clang version 16.0.6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !8, splitDebugInlining: false, nameTableKind: None, sysroot: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk", sdk: "MacOSX14.sdk") -!8 = !{!0} -!9 = !{i32 7, !"Dwarf Version", i32 4} -!10 = !{i32 2, !"Debug Info Version", i32 3} -!11 = !{i32 1, !"wchar_size", i32 4} -!12 = !{i32 8, !"PIC Level", i32 2} -!13 = !{i32 7, !"uwtable", i32 1} -!14 = !{i32 7, !"frame-pointer", i32 1} -!15 = !{!"Homebrew clang version 16.0.6"} -!16 = distinct !DISubprogram(name: "main", scope: !2, file: !2, line: 3, type: !17, scopeLine: 3, spFlags: DISPFlagDefinition, unit: !7, retainedNodes: !20) -!17 = !DISubroutineType(types: !18) -!18 = !{!19} -!19 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) -!20 = !{} -!21 = !DILocalVariable(name: "buffer", scope: !16, file: !2, line: 4, type: !22) -!22 = !DICompositeType(tag: DW_TAG_array_type, baseType: !4, size: 80, elements: !23) -!23 = !{!24} -!24 = !DISubrange(count: 10) -!25 = !DILocation(line: 4, column: 7, scope: !16) -!26 = !DILocation(line: 5, column: 23, scope: !16) -!27 = !DILocation(line: 5, column: 2, scope: !16) -!28 = !DILocation(line: 6, column: 11, scope: !16) -!29 = !DILocation(line: 6, column: 2, scope: !16) -!30 = !DILocation(line: 7, column: 1, scope: !16) diff --git a/Assignment-3/Tests/buf/test6.c b/Assignment-3/Tests/buf/test6.c deleted file mode 100644 index e8c7461..0000000 --- a/Assignment-3/Tests/buf/test6.c +++ /dev/null @@ -1,13 +0,0 @@ -#include -void mem_insert(void *buffer, const void *data, int data_size, int position); -extern void OVERFLOW(void* data, int size); -extern void svf_assert(bool condition); -int main() { - char buffer[10] = {0}; - mem_insert(buffer, "abcdef", 3, 5); - svf_assert(buffer[5] == 'a'); - svf_assert(buffer[6] == 'b'); - svf_assert(buffer[7] == 'c'); - svf_assert(buffer[8] != 'd'); - return 0; -} \ No newline at end of file diff --git a/Assignment-3/Tests/buf/test6.ll b/Assignment-3/Tests/buf/test6.ll deleted file mode 100644 index 3c1b905..0000000 --- a/Assignment-3/Tests/buf/test6.ll +++ /dev/null @@ -1,98 +0,0 @@ -; ModuleID = './test6.ll' -source_filename = "test6.c" -target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" -target triple = "arm64-apple-macosx14.0.0" - -@.str = private unnamed_addr constant [7 x i8] c"abcdef\00", align 1, !dbg !0 - -; Function Attrs: noinline nounwind ssp uwtable(sync) -define i32 @main() #0 !dbg !16 { -entry: - %buffer = alloca [10 x i8], align 1 - call void @llvm.dbg.declare(metadata ptr %buffer, metadata !21, metadata !DIExpression()), !dbg !25 - call void @llvm.memset.p0.i64(ptr align 1 %buffer, i8 0, i64 10, i1 false), !dbg !25 - %arraydecay = getelementptr inbounds [10 x i8], ptr %buffer, i64 0, i64 0, !dbg !26 - call void @mem_insert(ptr noundef %arraydecay, ptr noundef @.str, i32 noundef 3, i32 noundef 5), !dbg !27 - %arrayidx = getelementptr inbounds [10 x i8], ptr %buffer, i64 0, i64 5, !dbg !28 - %0 = load i8, ptr %arrayidx, align 1, !dbg !28 - %conv = sext i8 %0 to i32, !dbg !28 - %cmp = icmp eq i32 %conv, 97, !dbg !29 - call void @svf_assert(i1 noundef zeroext %cmp), !dbg !30 - %arrayidx2 = getelementptr inbounds [10 x i8], ptr %buffer, i64 0, i64 6, !dbg !31 - %1 = load i8, ptr %arrayidx2, align 1, !dbg !31 - %conv3 = sext i8 %1 to i32, !dbg !31 - %cmp4 = icmp eq i32 %conv3, 98, !dbg !32 - call void @svf_assert(i1 noundef zeroext %cmp4), !dbg !33 - %arrayidx6 = getelementptr inbounds [10 x i8], ptr %buffer, i64 0, i64 7, !dbg !34 - %2 = load i8, ptr %arrayidx6, align 1, !dbg !34 - %conv7 = sext i8 %2 to i32, !dbg !34 - %cmp8 = icmp eq i32 %conv7, 99, !dbg !35 - call void @svf_assert(i1 noundef zeroext %cmp8), !dbg !36 - %arrayidx10 = getelementptr inbounds [10 x i8], ptr %buffer, i64 0, i64 8, !dbg !37 - %3 = load i8, ptr %arrayidx10, align 1, !dbg !37 - %conv11 = sext i8 %3 to i32, !dbg !37 - %cmp12 = icmp ne i32 %conv11, 100, !dbg !38 - call void @svf_assert(i1 noundef zeroext %cmp12), !dbg !39 - ret i32 0, !dbg !40 -} - -; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) -declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 - -; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) -declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) #2 - -declare void @mem_insert(ptr noundef, ptr noundef, i32 noundef, i32 noundef) #3 - -declare void @svf_assert(i1 noundef zeroext) #3 - -attributes #0 = { noinline nounwind ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } -attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } -attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: write) } -attributes #3 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-m1" "target-features"="+aes,+crc,+crypto,+dotprod,+fp-armv8,+fp16fml,+fullfp16,+lse,+neon,+ras,+rcpc,+rdm,+sha2,+sha3,+sm4,+v8.1a,+v8.2a,+v8.3a,+v8.4a,+v8.5a,+v8a,+zcm,+zcz" } - -!llvm.dbg.cu = !{!7} -!llvm.module.flags = !{!9, !10, !11, !12, !13, !14} -!llvm.ident = !{!15} - -!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) -!1 = distinct !DIGlobalVariable(scope: null, file: !2, line: 7, type: !3, isLocal: true, isDefinition: true) -!2 = !DIFile(filename: "test6.c", directory: "/Users/z5489735/2023/0718/Software-Security-Analysis/teaching/Assignment-3/Tests/buf") -!3 = !DICompositeType(tag: DW_TAG_array_type, baseType: !4, size: 56, elements: !5) -!4 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char) -!5 = !{!6} -!6 = !DISubrange(count: 7) -!7 = distinct !DICompileUnit(language: DW_LANG_C11, file: !2, producer: "Homebrew clang version 16.0.6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !8, splitDebugInlining: false, nameTableKind: None, sysroot: "/Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk", sdk: "MacOSX14.sdk") -!8 = !{!0} -!9 = !{i32 7, !"Dwarf Version", i32 4} -!10 = !{i32 2, !"Debug Info Version", i32 3} -!11 = !{i32 1, !"wchar_size", i32 4} -!12 = !{i32 8, !"PIC Level", i32 2} -!13 = !{i32 7, !"uwtable", i32 1} -!14 = !{i32 7, !"frame-pointer", i32 1} -!15 = !{!"Homebrew clang version 16.0.6"} -!16 = distinct !DISubprogram(name: "main", scope: !2, file: !2, line: 5, type: !17, scopeLine: 5, spFlags: DISPFlagDefinition, unit: !7, retainedNodes: !20) -!17 = !DISubroutineType(types: !18) -!18 = !{!19} -!19 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) -!20 = !{} -!21 = !DILocalVariable(name: "buffer", scope: !16, file: !2, line: 6, type: !22) -!22 = !DICompositeType(tag: DW_TAG_array_type, baseType: !4, size: 80, elements: !23) -!23 = !{!24} -!24 = !DISubrange(count: 10) -!25 = !DILocation(line: 6, column: 7, scope: !16) -!26 = !DILocation(line: 7, column: 13, scope: !16) -!27 = !DILocation(line: 7, column: 2, scope: !16) -!28 = !DILocation(line: 8, column: 16, scope: !16) -!29 = !DILocation(line: 8, column: 26, scope: !16) -!30 = !DILocation(line: 8, column: 5, scope: !16) -!31 = !DILocation(line: 9, column: 16, scope: !16) -!32 = !DILocation(line: 9, column: 26, scope: !16) -!33 = !DILocation(line: 9, column: 5, scope: !16) -!34 = !DILocation(line: 10, column: 16, scope: !16) -!35 = !DILocation(line: 10, column: 26, scope: !16) -!36 = !DILocation(line: 10, column: 5, scope: !16) -!37 = !DILocation(line: 11, column: 16, scope: !16) -!38 = !DILocation(line: 11, column: 26, scope: !16) -!39 = !DILocation(line: 11, column: 5, scope: !16) -!40 = !DILocation(line: 12, column: 5, scope: !16) diff --git a/Assignment-3/Tests/buf_overflow.c b/Assignment-3/Tests/buf_overflow.c new file mode 100644 index 0000000..b38ed5d --- /dev/null +++ b/Assignment-3/Tests/buf_overflow.c @@ -0,0 +1,5 @@ +int main(void) { + char buf[4] = {0}; + buf[4] = 'x'; + return 0; +} diff --git a/Assignment-3/Tests/buf_overflow.ll b/Assignment-3/Tests/buf_overflow.ll new file mode 100644 index 0000000..74ba647 --- /dev/null +++ b/Assignment-3/Tests/buf_overflow.ll @@ -0,0 +1,51 @@ +; ModuleID = 'buf_overflow.c' +source_filename = "buf_overflow.c" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; Function Attrs: noinline nounwind uwtable +define dso_local i32 @main() #0 !dbg !10 { + %1 = alloca i32, align 4 + %2 = alloca [4 x i8], align 1 + store i32 0, ptr %1, align 4 + #dbg_declare(ptr %2, !15, !DIExpression(), !20) + call void @llvm.memset.p0.i64(ptr align 1 %2, i8 0, i64 4, i1 false), !dbg !20 + %3 = getelementptr inbounds [4 x i8], ptr %2, i64 0, i64 4, !dbg !21 + store i8 120, ptr %3, align 1, !dbg !22 + ret i32 0, !dbg !23 +} + +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) +declare void @llvm.memset.p0.i64(ptr writeonly captures(none), i8, i64, i1 immarg) #1 + +attributes #0 = { noinline nounwind uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: write) } + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8} +!llvm.ident = !{!9} + +!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.1.0 (https://github.com/bjjwwang/LLVM-compile 4f7056e8ada487923d1c8f9bc38df6472008eda3)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "buf_overflow.c", directory: "/mnt/scratch/PAG/Wjw/vibe/ass3-template-wt/Assignment-3/Tests", checksumkind: CSK_MD5, checksum: "6664fbbfa27f2e3eaf102f9e78ad61d1") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 8, !"PIC Level", i32 2} +!6 = !{i32 7, !"PIE Level", i32 2} +!7 = !{i32 7, !"uwtable", i32 2} +!8 = !{i32 7, !"frame-pointer", i32 2} +!9 = !{!"clang version 21.1.0 (https://github.com/bjjwwang/LLVM-compile 4f7056e8ada487923d1c8f9bc38df6472008eda3)"} +!10 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 1, type: !11, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !14) +!11 = !DISubroutineType(types: !12) +!12 = !{!13} +!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!14 = !{} +!15 = !DILocalVariable(name: "buf", scope: !10, file: !1, line: 2, type: !16) +!16 = !DICompositeType(tag: DW_TAG_array_type, baseType: !17, size: 32, elements: !18) +!17 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char) +!18 = !{!19} +!19 = !DISubrange(count: 4) +!20 = !DILocation(line: 2, column: 10, scope: !10) +!21 = !DILocation(line: 3, column: 5, scope: !10) +!22 = !DILocation(line: 3, column: 12, scope: !10) +!23 = !DILocation(line: 4, column: 5, scope: !10) diff --git a/Assignment-3/Tests/null_deref.c b/Assignment-3/Tests/null_deref.c new file mode 100644 index 0000000..a2f01df --- /dev/null +++ b/Assignment-3/Tests/null_deref.c @@ -0,0 +1,7 @@ +extern void UNSAFE_LOAD(void *); + +int main(void) { + int *p = (int *)0; + UNSAFE_LOAD(p); + return 0; +} diff --git a/Assignment-3/Tests/null_deref.ll b/Assignment-3/Tests/null_deref.ll new file mode 100644 index 0000000..16c5063 --- /dev/null +++ b/Assignment-3/Tests/null_deref.ll @@ -0,0 +1,48 @@ +; ModuleID = 'null_deref.c' +source_filename = "null_deref.c" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; Function Attrs: noinline nounwind uwtable +define dso_local i32 @main() #0 !dbg !13 { + %1 = alloca i32, align 4 + %2 = alloca ptr, align 8 + store i32 0, ptr %1, align 4 + #dbg_declare(ptr %2, !17, !DIExpression(), !18) + store ptr null, ptr %2, align 8, !dbg !18 + %3 = load ptr, ptr %2, align 8, !dbg !19 + call void @UNSAFE_LOAD(ptr noundef %3), !dbg !20 + ret i32 0, !dbg !21 +} + +declare void @UNSAFE_LOAD(ptr noundef) #1 + +attributes #0 = { noinline nounwind uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!5, !6, !7, !8, !9, !10, !11} +!llvm.ident = !{!12} + +!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.1.0 (https://github.com/bjjwwang/LLVM-compile 4f7056e8ada487923d1c8f9bc38df6472008eda3)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, retainedTypes: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "null_deref.c", directory: "/mnt/scratch/PAG/Wjw/vibe/ass3-template-wt/Assignment-3/Tests", checksumkind: CSK_MD5, checksum: "69381433b6fc6047d3e75f8d46b18ae0") +!2 = !{!3} +!3 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !4, size: 64) +!4 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!5 = !{i32 7, !"Dwarf Version", i32 5} +!6 = !{i32 2, !"Debug Info Version", i32 3} +!7 = !{i32 1, !"wchar_size", i32 4} +!8 = !{i32 8, !"PIC Level", i32 2} +!9 = !{i32 7, !"PIE Level", i32 2} +!10 = !{i32 7, !"uwtable", i32 2} +!11 = !{i32 7, !"frame-pointer", i32 2} +!12 = !{!"clang version 21.1.0 (https://github.com/bjjwwang/LLVM-compile 4f7056e8ada487923d1c8f9bc38df6472008eda3)"} +!13 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 3, type: !14, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !16) +!14 = !DISubroutineType(types: !15) +!15 = !{!4} +!16 = !{} +!17 = !DILocalVariable(name: "p", scope: !13, file: !1, line: 4, type: !3) +!18 = !DILocation(line: 4, column: 10, scope: !13) +!19 = !DILocation(line: 5, column: 17, scope: !13) +!20 = !DILocation(line: 5, column: 5, scope: !13) +!21 = !DILocation(line: 6, column: 5, scope: !13) diff --git a/Assignment-3/Tests/stmt.c b/Assignment-3/Tests/stmt.c new file mode 100644 index 0000000..94afed4 --- /dev/null +++ b/Assignment-3/Tests/stmt.c @@ -0,0 +1,10 @@ +#include +extern void svf_assert(bool); + +int main(void) { + unsigned char c = 255; + unsigned int wide = c; + unsigned int result = wide + 1; + svf_assert(result == 256); + return 0; +} diff --git a/Assignment-3/Tests/stmt.ll b/Assignment-3/Tests/stmt.ll new file mode 100644 index 0000000..436f0d8 --- /dev/null +++ b/Assignment-3/Tests/stmt.ll @@ -0,0 +1,67 @@ +; ModuleID = 'stmt.c' +source_filename = "stmt.c" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; Function Attrs: noinline nounwind uwtable +define dso_local i32 @main() #0 !dbg !10 { + %1 = alloca i32, align 4 + %2 = alloca i8, align 1 + %3 = alloca i32, align 4 + %4 = alloca i32, align 4 + store i32 0, ptr %1, align 4 + #dbg_declare(ptr %2, !15, !DIExpression(), !17) + store i8 -1, ptr %2, align 1, !dbg !17 + #dbg_declare(ptr %3, !18, !DIExpression(), !20) + %5 = load i8, ptr %2, align 1, !dbg !21 + %6 = zext i8 %5 to i32, !dbg !21 + store i32 %6, ptr %3, align 4, !dbg !20 + #dbg_declare(ptr %4, !22, !DIExpression(), !23) + %7 = load i32, ptr %3, align 4, !dbg !24 + %8 = add i32 %7, 1, !dbg !25 + store i32 %8, ptr %4, align 4, !dbg !23 + %9 = load i32, ptr %4, align 4, !dbg !26 + %10 = icmp eq i32 %9, 256, !dbg !27 + call void @svf_assert(i1 noundef zeroext %10), !dbg !28 + ret i32 0, !dbg !29 +} + +declare void @svf_assert(i1 noundef zeroext) #1 + +attributes #0 = { noinline nounwind uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8} +!llvm.ident = !{!9} + +!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 21.1.0 (https://github.com/bjjwwang/LLVM-compile 4f7056e8ada487923d1c8f9bc38df6472008eda3)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "stmt.c", directory: "/mnt/scratch/PAG/Wjw/vibe/ass3-template-wt/Assignment-3/Tests", checksumkind: CSK_MD5, checksum: "0e7bce636e687d93d521ebd287aa94db") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 8, !"PIC Level", i32 2} +!6 = !{i32 7, !"PIE Level", i32 2} +!7 = !{i32 7, !"uwtable", i32 2} +!8 = !{i32 7, !"frame-pointer", i32 2} +!9 = !{!"clang version 21.1.0 (https://github.com/bjjwwang/LLVM-compile 4f7056e8ada487923d1c8f9bc38df6472008eda3)"} +!10 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 4, type: !11, scopeLine: 4, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !14) +!11 = !DISubroutineType(types: !12) +!12 = !{!13} +!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!14 = !{} +!15 = !DILocalVariable(name: "c", scope: !10, file: !1, line: 5, type: !16) +!16 = !DIBasicType(name: "unsigned char", size: 8, encoding: DW_ATE_unsigned_char) +!17 = !DILocation(line: 5, column: 19, scope: !10) +!18 = !DILocalVariable(name: "wide", scope: !10, file: !1, line: 6, type: !19) +!19 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned) +!20 = !DILocation(line: 6, column: 18, scope: !10) +!21 = !DILocation(line: 6, column: 25, scope: !10) +!22 = !DILocalVariable(name: "result", scope: !10, file: !1, line: 7, type: !19) +!23 = !DILocation(line: 7, column: 18, scope: !10) +!24 = !DILocation(line: 7, column: 27, scope: !10) +!25 = !DILocation(line: 7, column: 32, scope: !10) +!26 = !DILocation(line: 8, column: 16, scope: !10) +!27 = !DILocation(line: 8, column: 23, scope: !10) +!28 = !DILocation(line: 8, column: 5, scope: !10) +!29 = !DILocation(line: 9, column: 5, scope: !10)