AE CPP APIs - SVF-tools/Software-Security-Analysis GitHub Wiki

Essential API Documentation for Labs and Assignments

Lab-Exercise-3

API Introduction
getNodeID("variable") Retrieves the node ID of the specified variable.
IntervalValue(lower, upper) Creates an interval value.
AbstractValue::getInterval() Retrieves the interval value of the abstract state.
AbstractValue::join_with(value) Merges the current value with another value.
getMemObjAddress("variable") Retrieves the memory object address of the specified variable.
AddressValue(getMemObjAddress("variable")) Creates an address value initialized to the memory object address of the specified variable.
AEState::widening(state) Performs widening on the given state.
AEState::narrowing(state) Performs narrowing on the given state.
AEState::joinWith(state) Merges the current state with another state.
AbstractValue::meet_with(value) Performs an intersection operation between the current value and another value.
getGepObjAddress("variable", offset) Retrieves the GEP (GetElementPtr) object address of the specified variable with the given offset.
AEState::loadValue(varId) Loads the abstract value from the variable ID's address.
AEState::storeValue(varId, val) Stores the abstract value at the variable ID's address.
AEState::printAbstractState() Prints the abstract trace for debugging purposes.

AEState::widening(state)

  • Perform widening on the current state with another state.

    For example,

    void exampleWidening() {
        AEState as1, as2;
        NodeID a = getNodeID("a");
        as1[a] = IntervalValue(1, 5); // as1: a in [1, 5]
        as2[a] = IntervalValue(3, 10); // as2: a in [3, 10]
        AEState widenedState = as1.widening(as2); // widenedState: a in [1, +inf]
    }
    

    Input: state (an AEState)

    Output: Widened state (an AEState)


AEState::loadValue(varId) and AEState::storeValue(varId, val)

  • Load and store values associated with a variable ID.

    For example,

    void exampleLoadValue() {
        AEState as;
        NodeID a = getNodeID("a");
        NodeID p = getNodeID("p");
        NodeID malloc = getNodeID("malloc");
        as[p] = AddressValue(getMemObjAddress("malloc"));
        as.storeValue(p, IntervalValue(42, 42)); // Store 42 at address p
    
        AbstractValue loadedValue = as.loadValue(p);
    
        std::cout << loadedValue.toString() << std::endl;
    }
    

    Input: varId (a NodeID), val (an AbstractValue)

    Output: Loaded value (an AbstractValue, can be Interval Value or Address Value)


AEState::printAbstractState()

  • Print the abstract trace for debugging purposes.

    For example,

    void examplePrintAbstractState() {
        AEState as;
        NodeID a = getNodeID("a");
        NodeID b = getNodeID("b");
    
        as[a] = IntervalValue(1, 5); // a in [1, 5]
        as[b] = IntervalValue(3, 7); // b in [3, 7]
    
        as.printAbstractState(); // Print the abstract trace for debugging
    }
    

    Input: None

    Output: None (prints the abstract trace)

-----------Var and Value-----------
Var2(b)             :  Value: [3, 7]
Var1(a)             :  Value: [1, 5]
-----------------------------------------

Assignment-3

Naming note. SVF master no longer exposes a separate AbstractStateManager / AEState class. The dense per-ICFGNode trace and the GEP / load / store / pointer helpers now live on SVF::AbstractInterpretation and are forwarded through methods on Assignment-3's AbstractExecution (its base class — call them as bare methods, e.g. getGepByteOffset(gep) or loadValue(ptr, node)). The per-node SVF::AbstractState value object is still returned by getAbsStateFromTrace(node) and is what you call widening / narrowing / joinWith / meetWith on.

The tables below list the SVF C++ APIs the Assignment-3 solution actually calls. They are grouped by what they operate on (statements, abstract state, IR queries, harness facade) so you can scan them in the order you will use them while writing Assignment_3.cpp.

SVF statement & ICFG queries

API Introduction
SVFUtil::isa<T>(node) / SVFUtil::dyn_cast<T>(stmt) Type-test / downcast for SVFStmt and ICFGNode subtypes.
stmt->getICFGNode() Containing ICFG node of a statement (use to look up the abstract state with getAbsStateFromTrace).
AssignStmt::getLHSVar() / getRHSVar() LHS / RHS SVFVar* of CopyStmt, LoadStmt, StoreStmt, GepStmt, PhiStmt, SelectStmt.
AssignStmt::getLHSVarID() / getRHSVarID() NodeID for the same operands; compare with IRGraph::NullPtr to detect null-pointer operands.
BinaryOPStmt::getOpcode() Returns the opcode (compare against BinaryOPStmt::Add / Sub / Mul / UDiv / SDiv / URem / SRem / And / Or / Xor / Shl / LShr / AShr / ...).
BinaryOPStmt::getOpVarID(index) / getResID() Operand and result NodeIDs for binary operations.
CmpStmt::getOpVarID(index) / getResID() / getPredicate() Operands, result, and predicate enum (CmpStmt::ICMP_EQ / ICMP_NE / ICMP_SLT / ...).
SelectStmt::getTrueValue() / getFalseValue() / getCondition() Operands of a select instruction.
PhiStmt::getOpVarNum() / getOpVar(index) / getOpICFGNode(index) Number of incoming values, the value at index, and the predecessor ICFG node it came from.
CallPE::getResID() / getOpVarNum() / getOpVarID(index) / getOpCallICFGNode(index) CallPE is phi-like — the result is the formal parameter, each operand pairs an actual parameter with its call site.
RetPE::getLHSVarID() / getRHSVarID() Caller-side LHS and callee-side return-value variables for a return-edge statement.
AddrStmt::getLHSVarID() / getRHSVarID() LHS / RHS of an addr (alloca / global) statement.
GepStmt::isConstantOffset() / accumulateConstantByteOffset() Quickly classify and read constant byte offsets when the GEP is constant.
CallICFGNode::getCalledFunction() / getCaller() / getArgument(i) / arg_size() / getRetICFGNode() Static callee, caller FunObjVar*, ith argument ValVar*, argument count, matching return ICFG node.
RetICFGNode::getActualRet() The actual-return SVFVar* used to write nondeterministic return values to TOP.
IntraCFGEdge::getCondition() / getSuccessorCondValue() Branch condition and successor value used by mergeStatesFromPredecessors to filter infeasible edges.
ICFGNode::getInEdges() / getOutEdges() / getSVFStmts() / getId() / getFun() / toString() ICFG node iteration.
ICFG::getGlobalICFGNode() / getFunEntryICFGNode(fun) Get the global initialisation node, or a function's entry node.
SVFIR::getBaseObject(id) / getGNode(id) / getFunObjVar(name) Resolve NodeID → base object, NodeIDSVFVar*, function name → FunObjVar*.
BaseObjVar::isConstantByteSize() / getByteSizeOfObj() / isBlackHoleObj() Object metadata used by the buffer-overflow ground-truth and the checker.
GepObjVar::getConstantFieldIdx() Accumulated byte offset of a derived GEP object from its base.
IRGraph::NullPtr The dedicated null-pointer node — compare a NodeID against this to detect null operands.

Abstract values, intervals, addresses

API Introduction
IntervalValue(lower, upper) / IntervalValue::top() Construct an interval / get the universal interval.
IntervalValue::lb() / ub() / is_numeral() / is_zero() / is_infinite() / isBottom() / equals(other) Bounds queries used by the transfer functions and the branch refinement.
IntervalValue::getIntNumeral() / BoundedInt::getIntNumeral() Concrete integer when the interval is a single value (or when reading a bound).
AbstractValue::getInterval() / getAddrs() / isInterval() / isAddr() Project an AbstractValue to its interval or address-set view.
AbstractValue::join_with(other) / meet_with(other) / equals(other) Lattice operations on AbstractValue.
AddressValue Set-of-addresses value; iterate with a range-for to walk each addr.
AbstractState::widening(other) / narrowing(other) / joinWith(other) / meetWith(other) Lattice operations on the per-node state used by handleICFGCycle.
AbstractState::isBlackHoleObjAddr(addr) / isNullMem(addr) / isFreedMem(addr) Filter sentinel / null / freed addresses out of AddressValue iteration.
AbstractState::getIDFromAddr(addr) Recover the base-object NodeID from a virtual address (low bits of 0x7f******).
AbstractState::inVarToValTable(id) / inVarToAddrsTable(id) Whether the state already records a value / address-set for id (use before reading as[id] to avoid creating default entries).
as[id] Read / write the AbstractValue for NodeID id in an AbstractState&.
AbstractState::printAbstractState() Debug-print the entire state.
Options::WidenDelay() Read the configurable widening delay (number of iterations before widening kicks in).

AbstractExecution facade (call as bare methods from Assignment_3.cpp)

API Introduction
getAbsStateFromTrace(node) Returns the AbstractState& for the ICFG node (lives inside the underlying AbstractInterpretation's trace).
postAbsTrace() Returns the Map<const ICFGNode*, AbstractState>& keyed by ICFG node — use this when you need to read a previously-computed post-state of a different node (e.g. a CallPE reading from its call site).
getAbsValue(var, node) (overloaded for ValVar*, ObjVar*, SVFVar*) Read the AbstractValue of var at node.
updateAbsValue(var, val, node) Write val for var at node.
loadValue(pointer, node) Load through pointer (ValVar*) — replaces the old AEState::loadValue(varId).
storeValue(pointer, val, node) Store val through pointer — replaces the old AEState::storeValue(varId, val).
getGepObjAddrs(pointer, offset) Resolve a ValVar* + IntervalValue offset into the set of derived GEP-object addresses.
getGepElementIndex(gep) / getGepByteOffset(gep) Element-index / byte-offset of a GepStmt (use the byte version for buffer-bound math).
getAllocaInstByteSize(addr) Byte size of the object created by an AddrStmt (alloca).
reportBufOverflow(node) / reportNullDeref(node) Record a bug at node; the harness deduplicates by location and kind.

Harness driver hooks (entry points the harness calls into)

API Introduction
handleGlobalNode() Replay the SVFModule's global ICFG node so global state is initialised before main is entered.
handleFunction(funEntry) Iterate one function's interprocedural WTO; dispatch singletons to handleICFGNode and cycles to handleICFGCycle.
handleICFGNode(node) Merge predecessor states, run the per-statement transfer functions, dispatch call sites via handleCallSite, run the bug checkers; return whether the post-state changed.
handleICFGCycle(cycle) Drive a WTO cycle (loop head or recursive-function entry) to a fixpoint with widening / narrowing.
handleCallSite(callNode) Dispatch a call ICFG node; the harness expects you to forward stub/checkpoint calls to handleStubFunctions / handleCheckpointStubs, model external APIs via updateStateOnExtCall, and inline non-extern callees with the ander->inSameCallGraphSCC recursion skip.
handleStubFunctions(call) / handleCheckpointStubs(call) Sub-dispatchers for svf_assert / svf_assert_eq and the SAFE_* / UNSAFE_* ground-truth checkpoints (defined in AEHelper.cpp — call them from your handleCallSite).
isExternalCallForAssignment(func) Whether func belongs to the assignment's external-API whitelist (the memory / string family plus mem_insert / str_insert).
ander->inSameCallGraphSCC(caller, callee) Andersen-based SCC check used to skip recursive callsites (the outer WTO cycle drives the recursion).
getReporter() Access the AEReporter instance for advanced bug-reporting needs.

CallPE — phi-like formal-parameter resolution

CallPE joins all of a function's actual parameters at the entry. The result variable is the formal parameter; each operand pairs an actual parameter with its call site.

void AbstractExecution::updateStateOnCall(const CallPE* callPE) {
    AbstractState& as = getAbsStateFromTrace(callPE->getICFGNode());
    NodeID formal = callPE->getResID();
    AbstractValue joined;
    for (u32_t i = 0; i < callPE->getOpVarNum(); ++i) {
        const ICFGNode* callSite = callPE->getOpCallICFGNode(i);
        NodeID actual = callPE->getOpVarID(i);
        if (postAbsTrace().count(callSite))
            joined.join_with(postAbsTrace()[callSite][actual]);
    }
    as[formal] = joined;
}

getAbsStateFromTrace(node) and the post-trace

AbstractState& as = getAbsStateFromTrace(node);            // per-node value object
auto& post = postAbsTrace();                               // Map<ICFGNode*, AbstractState>
if (post.count(prevNode))
    val.join_with(post[prevNode][someVarId]);

AbstractState is a value object (call widening / narrowing / joinWith / meetWith / printAbstractState() on it); postAbsTrace() lets you read another node's post-state by ICFG-node key.


loadValue / storeValue (through a ValVar* pointer)

void AbstractExecution::updateStateOnLoad(const LoadStmt* load) {
    const ICFGNode* node = load->getICFGNode();
    AbstractState& as = getAbsStateFromTrace(node);
    as[load->getLHSVarID()] =
        this->loadValue(SVFUtil::cast<ValVar>(load->getRHSVar()), node);
}

void AbstractExecution::updateStateOnStore(const StoreStmt* store) {
    const ICFGNode* node = store->getICFGNode();
    AbstractState& as = getAbsStateFromTrace(node);
    AbstractValue val = (store->getRHSVarID() == IRGraph::NullPtr)
        ? AbstractValue(AddressValue(AbstractState::nullMem()))
        : as[store->getRHSVarID()];
    this->storeValue(SVFUtil::cast<ValVar>(store->getLHSVar()), val, node);
}

GEP arithmetic (getGepElementIndex, getGepByteOffset, getGepObjAddrs)

void AbstractExecution::updateStateOnGep(const GepStmt* gep) {
    const ICFGNode* node = gep->getICFGNode();
    AbstractState& as = getAbsStateFromTrace(node);
    IntervalValue offset = this->getGepElementIndex(gep);
    AddressValue gepAddrs =
        this->getGepObjAddrs(SVFUtil::cast<ValVar>(gep->getRHSVar()), offset);
    as[gep->getLHSVarID()] = AbstractValue(gepAddrs);
}
  • getGepElementIndex(gep) and getGepByteOffset(gep) return IntervalValues (element-index vs byte-offset views of the same GEP).
  • getGepObjAddrs(pointer, offset) resolves the derived address set; iterate the result and call getIDFromAddr to walk each base object.

Buffer-bound math: getBaseObject / getConstantFieldIdx

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->isConstantByteSize())
        continue;
    u32_t size = baseObj->getByteSizeOfObj();
    IntervalValue baseOffset(0);
    if (auto* gepObj = SVFUtil::dyn_cast<GepObjVar>(svfir->getGNode(objId)))
        baseOffset = IntervalValue((s64_t)gepObj->getConstantFieldIdx());
    // ... compare baseOffset + accessOffset against size
}

Call-site dispatch (the handleCallSite student TODO)

The harness's handleCallSite is the entry point for every CallICFGNode. The supplied dispatch table in Assignment_3.cpp recommends:

  • svf_assert / svf_assert_eqhandleStubFunctions(call)
  • SAFE_BUFACCESS / UNSAFE_BUFACCESS / SAFE_PTRDEREF / UNSAFE_PTRDEREFhandleCheckpointStubs(call)
  • nd / rand → write IntervalValue::top() to the actual-return variable on postAbsTrace()[callNode].
  • SVFUtil::isExtCall(callee)updateStateOnExtCall(callNode) then run the bug checkers on the call's pointer / length arguments.
  • Otherwise → skip if ander->inSameCallGraphSCC(caller, callee), otherwise handleFunction(svfir->getICFG()->getFunEntryICFGNode(callee)) and forward postAbsTrace()[callNode] to callNode->getRetICFGNode().

AbstractState::getIDFromAddr(addr)

Recover the internal object ID from a 0x7f****** virtual address.

AbstractState& as = getAbsStateFromTrace(node);
int internalID = as.getIDFromAddr(addr); // strips the 0x7f000000 mask

AbstractState::printAbstractState()

AbstractState& as = getAbsStateFromTrace(icfgNode);
as.printAbstractState();
-----------Var and Value-----------
Var4                 Value: [5, 5]
Var2                 Value: [5, 5]
Var1                 Value: {0x7f000005}
0x7f000005           Value: [5, 5]
———————————————————————————————————

⚠️ **GitHub.com Fallback** ⚠️