Add MSli support to MTA - #1852
Conversation
…cience Unmodified download of the MSli artifact (paper: Multi-Stage On-Demand Program Slicing for Modular Analysis of Multi-Threaded Programs), including the bundled modified SVF 3.2, src/, benchmarks/, and the two source papers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…flow)
A pthread_create is a heap-alloc ext call, so MemSSA used only its ext summary
and never bridged the spawner's memory state into the spawnee -- a spawnee read
missed the spawner's pre-fork writes (unsound).
Per FSAM (Sui'16): the thread-oblivious value flow treats a fork as an ordinary
call, but a fork has no return ('no outgoing edges for a fork site'). So:
- SVF MemRegion::handleCallsiteModRef: for a fork callsite, additionally forward
the spawnee's REF set (ActualIN -> FormalIN), so the spawner's state flows in.
We deliberately do NOT add the spawnee's MOD set (no return edge); the
spawnee's writes reach other threads via the interference edges instead. The
pthread_t heap mod set is preserved.
- MTAFSPTA::initialize: populate the Andersen ThreadCallGraph's fork edges
(updateCallGraph) before building MemSSA so the fork callsite is recognised.
Verified: spawnee now sees spawner's pre-fork write ({Y,Z} not {Y}); the 13
FSAM cases still pass (no return-direction regression).
… Fig.6d) A thread join must make the joined spawnee's exit writes visible to the spawner after the join. Without it, once the MHP is precise enough to cut the join (so the spawnee is no longer parallel with post-join code) the spawnee's writes would be missed -> unsound. Implemented as the dual of fork-as-call -- a 'return without a forward': - SVF MemRegion::modRefAnalysis: for a ThreadJoinEdge (spawner -> spawnee), add the spawnee's MOD set to the join callsite (creates an ActualOUT there). Only MOD, never REF (the spawner's at-join state must not flow into the finished spawnee). - SVF SVFG::connectIndirectSVFGEdges: connect the spawnee's FormalOUT to the ActualOUT at each site that joins it (via ThreadCallGraph::getJoinSites). - SVF ThreadCallGraph::updateJoinEdge: skip joins with no matchable fork site instead of asserting (the assert is why upstream left join edges unbuilt). - MTAFSPTA::initialize: call updateJoinEdge (and updateCallGraph) on the SVFG's Andersen so the fork/join edges exist before MemSSA. No regression (12 PASS + 1 XFAIL). NOTE: the join edges are not yet firing on the test cases because ThreadAPI::isAliasedForkJoin fails to match fork<->join under the analysis points-to -- the same matching failure also stops the MHP from cutting joins. Fixing that matching is the next step and unblocks both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two fixes make the join-related thread-oblivious value flow actually fire:
1. ThreadAPI::isAliasedForkJoin: pthread_t is a scalar (i64), so the join handle
is a loaded value with no points-to and the fork<->join match failed (which
also stopped the MHP from cutting joins). Add a fallback: match when the join
handle was loaded from a pointer aliasing the fork's pthread_t pointer.
2. MRGenerator::modRefAnalysis: thread join edges are kept in a side map, not as
real call-graph edges, so the InEdge walk never saw them. Reach them via
ThreadCallGraph::getJoinSites instead: for the spawnee being joined, add its
MOD set to each join callsite (creating the ActualOUT that the spawnee's
FormalOUT connects to).
Result: the spawnee's exit writes now reach the spawner's post-join reads
(sound), and for a full join the strong update kills the stale pre-fork value
(precise). The whole thread-oblivious def-use family (Pseq + fork-related +
join-related, Sui'16 Fig.6 b/c/d) is now in place.
All 13 FSAM cases PASS (t2_precision_join: {Y,Z} XFAIL -> {Y} PASS).
The pthread_t handle passed to pthread_join is a scalar value that may reach the
join after copies, phis, casts, or by-value parameter passing -- not just a
direct load. Replace the single-load fallback in ThreadAPI::isAliasedForkJoin
with a backward traversal of the top-level value flow (Load/Copy/Phi/Gep/CallPE)
that collects every pthread_t object the handle may have been loaded from, and
matches it against the fork's pthread_t object (pts of &t). Covers the common
shapes soundly; an over-approximate match only adds a sound join-related edge.
Adds tests/cases/j1_join_interproc.c (join via a by-value pthread_t parameter):
pt(c)={Y} -- the cross-call join is now matched and the join-related def-use
fires. Suite: 14/14 PASS.
…Graph}Builder; revert their SVF edits After 2c/2d/point-3 the main FSPTA phase is MTAFSPTA (sparse flow-sensitive), so the flow-insensitive SlicedAndersen / SlicedSteensgaard cluster and their two graph builders are dead (zero instantiations; only stale comments referred to them). Delete all 8 files. That cluster was the *only* reason for three SVF intrusions, now reverted to the fork base (1dbfe7f): - WPA/Andersen.h : collapseFields() un-virtualised - WPA/Andersen.cpp : collapsePWCNode/collapseFields re-inlined - Graphs/ConsG.h : drop the added ConstraintGraph() default ctor Verified nothing live constructs ConstraintGraph() or overrides collapseFields. Net: SVF touched-file count 20 -> 17; the live inheritance surface is now only the MTA headers (MHP.h / TCT.h / LockAnalysis.h) for SlicedMHP/SlicedLockAnalysis/ SlicedTCT. Build (SVF + msli) clean; suite 28 PASS / 11 SLICE-OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Revert Graphs/SVFG.cpp to the fork base and re-add the join-related thread-oblivious value flow (start-routine FormalOUT -> join-site ActualOUT) as a post-pass in MTASVFGBuilder::connectThreadJoinEdges(), run between the stock svfg->buildSVFG() and the interference-edge pass. Uses only the public SVFG API (getFormalOUT/ActualOUTSVFGNodes, hasInterVFGEdge, addSVFGEdge) so the core SVFG class is unmodified -- one of the two Bucket-D core files is now stock. Verified: suite 28 PASS / 11 SLICE-OK; bodytrack 450 sliced races (unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…svf/MTA
Move the reusable library code out of the artifact's src/ into SVF's MTA module
so it builds as part of SvfCore (lib/*.cpp is glob-recursive):
src/mta/{MTASVFGBuilder,MTAFSPTA,SlicedMHP,SlicedLockAnalysis,SlicedTCT}
src/slicer/{SlicerBase,MTASlicer,PTASlicer,SingleSlicer}
src/sliced_view/{SlicedSVFIRView,SlicedICFGView,SlicedPAGView,
SlicedCallGraphView,SlicedThreadCallGraphView}
-> SVF/svf/include/MTA/*.h + SVF/svf/lib/MTA/*.cpp (14 classes)
Our headers are now included as "MTA/<name>.h" (SvfCore PUBLIC include dir).
The msli driver and its RaceDetector client stay in src/ and consume the MTA
module via SVF. src/CMakeLists.txt slimmed accordingly.
Only the driver/tool remains in the artifact; the analysis is now SVF-resident.
Build (SvfCore + msli) clean; suite 28 PASS / 11 SLICE-OK.
Follow-ups: namespace-normalise the moved global-namespace classes into
namespace SVF; relocate the remaining Bucket-D MemRegion.cpp MOD-REF logic.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the last pieces of the artifact's src/ tree into SVF so the whole
MSli pipeline -- library and executable -- is produced by SVF's own build:
- RaceDetector (LLVM-free downstream-task client) ->
SVF/svf/include/MTA/RaceDetector.h + SVF/svf/lib/MTA/RaceDetector.cpp,
compiled into SvfCore via the lib/*.cpp glob (flat under MTA, matching
SVF's one-level module layout -- no nested subdirs).
- msli driver (main.cpp, LLVM-dependent: LLVMModuleSet + SVFIRBuilder) ->
SVF/svf-llvm/tools/MSLi/msli.cpp, registered as an SVF tool beside `mta`
in svf-llvm/tools/CMakeLists.txt. Its existing -enable-slicing /
-slicing-mode / -sliced-max-cxt / -main-ila / -threadvf-sources flags are
the "turn slicing on/off and configure it" knobs on the MTA tool; the
tool then drives the MTA library in svf/lib/MTA.
`msli` now builds to ${SVF_DIR}/Release-build/bin/msli and installs to
${SVF_DIR}/install/bin/msli. The top-level MSli CMake no longer builds a
target of its own (src/ deleted, add_subdirectory(src) removed); it only
hosts SVF/, tests/, benchmarks/, notes/. tests/run.sh defaults MSLI to
$SVFHOME/install/bin/msli.
SVF builds with -Werror -fno-exceptions, which the slird build did not:
dropped an unused `keptNodes` in RaceDetector.cpp and replaced a
std::stoi try/catch in msli.cpp with a non-throwing strtol parse.
Verified: SvfCore + msli build clean, suite 28 PASS / 11 SLICE-OK,
bodytrack 450 races (== whole-program reference).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… comments Remove the unused second SlicedICFGView ctor plus getBridgedEdges()/ hasBridgedPath() and SlicedThreadCallGraphView::setExtendedKeptNodes (no callers). Translate the remaining Chinese comments in the sliced view classes to English and strip migration-scar wording from MTAFSPTA/PTASlicer. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Extract the byte-identical sliced ICFG/CallGraph traversal adapters shared by
the sliced analyses into MTA/SlicedViewAdapter.{h,cpp}.
- Hoist the ~95-line call-graph expansion duplicated by MTASlicer and
SingleSlicer into SlicerBase::_expandCallDependence.
- Delete RaceDetector::_{gatherParallelFunctions,mayHappenInParallel}WithSlicedMHP
(the sliced path queries mayHappenInParallelCache directly — they were dead).
- Drop the dead SlicerBase closure helpers and write-only pthreadCreate2JoinMap.
No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add three protected virtual traversal hooks to base MHP (getFunEntry, getSuccNodes, getInEdgesOfCallGraphNode) with full-graph defaults, and route analyzeInterleaving + handleNonCandidateFun/Fork/Call/Ret/Intra through them. Make updateNonCandidateFunInterleaving virtual (it is called from analyzeInterleaving, so the sliced override must dispatch). SlicedMHP then inherits those handlers and overrides only the hooks, handleJoin (sliced ForkJoinAnalysis) and updateNonCandidateFunInterleaving (sliced CallGraph); ~500 lines of copy-paste and debug instrumentation removed. Base mta tool unaffected; sliced bodytrack still 450 races. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…icedLockAnalysis
Add six protected virtual hooks to base LockAnalysis (getFunEntry, getSuccNodes,
getPredNodes, acceptsNode, getInEdgesOfCallGraphNode, getAnalysisCallGraph) with
full-graph defaults plus a virtual destructor (the class is now polymorphic), and
route collectLockUnlocksites, intra{Forward,Backward}Traverse, collectCxtLock,
handleCallRelation, analyzeLockSpanCxtStmt and handle{Fork,Call,Ret,Intra}
through them.
SlicedLockAnalysis collapses from ~720 to ~65 lines: ctor + the six hook
overrides. The reimplemented analyze/worklist/lock helpers were verbatim copies
needed only to swap traversal, and the sliced pushCxt override was redundant
(TCT::pushCxt is virtual, so base pushCxt already dispatches to SlicedTCT).
Base mta tool unaffected; sliced bodytrack still 450 races; 28/28 tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
P2 view-layer cleanup: - SlicedCallGraphView is never constructed (the MTA pipeline always slices over a ThreadCallGraph), so remove the dead class + its dead getCallGraph() fallback branches in SlicedSVFIRView/SlicedViewAdapter/SlicedMHP/SlicedLockAnalysis; SlicedSVFIRView now asserts a ThreadCallGraph. - Trim SlicedPAGView to build + getKeptStmts + dump (its query API had no callers). - Hoist escapeDotLabel (copy-pasted in 3 view dumps) into SlicedViewAdapter. Remove SlicedForkJoinAnalysis: base ForkJoinAnalysis has no traversal hooks, so the 'sliced' fja overrode nothing and walked the full graph identically to the base fja that MHP's ctor already builds -- a hollow seam plus a redundant fork/join pass per slice. Drop the class and the fja member; handleJoin is now inherited and reuses the base full-graph analysis (fork/join relationships are program-global). No behavior change; sliced bodytrack still 450 races, 28/28 tests + 11 SLICE-OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Move RaceDetector into namespace SVF (it was the lone MTA type in the global namespace, with a public-header 'using namespace SVF' that leaked into every includer) -- an upstream blocker. - Convert std::cout/std::cerr to SVFUtil::outs()/errs() across the sliced views, SingleSlicer and the msli driver (SVFUtil::outs() is std::ostream, so iomanip still works for the timer). - Remove dead view APIs: SlicedICFGView getOutEdgesOf/getInEdgesOf/getKeptEdges and SlicedTCT's classof block (no isa/cast<SlicedTCT> anywhere). - SlicedThreadCallGraphView built its kept-edge set twice (the second build cleared the first); build nodes once, edges once. - Prune SlicerBase.h include bloat (8 -> 3); drop a shadowing local in SlicedICFGView::buildBridgedEdges; rename the mislabeled vulnerableNodes param; fix stale comments (the wrong 'steensgaard' note, the MSLi tagline). No behavior change; sliced bodytrack still 450 races, 28/28 tests + 11 SLICE-OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the standalone `msli` tool; slicing is now an option (`-enable-slicing`)
on SVF's single `mta` tool. The 5-phase multi-stage slicing pipeline and the race
detector move into the SVF library as `SVF::SlicedMTA` (LLVM-free); the one
LLVM-dependent step (`SVFIRBuilder::updateCallGraph`) is injected by the tool as a
`std::function<void(CallGraph*)>` callback.
Replace the hand-rolled argv parsing with proper SVF `Option`s: enable-slicing,
sliced-max-cxt, main-ila-sliced, threadvf-sources, slicing-single,
sliced-dump-dot, observe, observe-sliced.
Consolidate the MTA module to mirror upstream MTA's naming (20 -> 12 file-pairs):
- MTAFSPTA -> FSPTA
- SlicerBase+MTASlicer+PTASlicer+SingleSlicer -> Slicer
- Sliced{SVFIR,ICFG,PAG,ThreadCallGraph}View+Adapter+TCT -> SlicedView
- MTASlicedAnalysis + RaceDetector -> SlicedMTA
Update README, tests/run.sh and CMake accordingly.
Behaviour preserved: 28/28 tests + 11 SLICE-OK; bodytrack 450 races.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The thread fork/join def-use that MSli had injected into core
MRGenerator::handleCallsiteModRef / modRefAnalysis is moved into a thread-aware
MRGenerator that lives in the MTA module, so core MemRegion has no MTA /
ThreadCallGraph knowledge.
Generic seams added to core:
- MRGenerator: two empty virtual hooks handleForkSideEffect/handleJoinSideEffect
(called from the existing mod-ref methods) + a getCallGraph() accessor.
- MemSSA: optional `MRGenerator* injectedMRG` constructor parameter.
- SVFGBuilder: virtual createMRGenerator() (default nullptr).
MTA side: ThreadMRG<Base> mixin (in MTASVFGBuilder.h) layers the fork ref-forward
and join mod-propagate logic on top of any partition strategy; MTASVFGBuilder
overrides createMRGenerator to inject ThreadMRG<Distinct|IntraDisjoint|
InterDisjoint>. Every thread-aware SVFG in the pipeline is built by
MTASVFGBuilder, so non-MTA analyses correctly revert to stock mod-ref.
Behaviour preserved: 28/28 tests + 11 SLICE-OK; bodytrack 450 races.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The slicers (SlicerBase/MTASlicer/PTASlicer/SingleSlicer), the sliced views (SlicedICFGView/PAGView/ThreadCallGraphView/SVFIRView, SlicedTCT, SlicedViewAdapter) and SlicedMHP/SlicedLockAnalysis were in the global namespace, unlike the rest of SVF (and unlike SlicedMTA/FSPTA/MTASVFGBuilder). Wrap them all in `namespace SVF` so the module is namespace-consistent and the forward-declare-in-global dance in SlicedMTA.h goes away. No behaviour change: 28/28 tests + 11 SLICE-OK; bodytrack 450 races. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tct, slicedTCT and mtaFSPTA were raw pointers manually deleted in the destructor, while ~10 sibling members were unique_ptr -- an easy source of leaks on any future early-return. Make all three unique_ptr. The destructor still reset()s them (plus vfgPreBuilder) before SVFIR::releaseSVFIR(), preserving the original release-before-SVFIR ordering; the remaining unique_ptr members auto-destroy after the body as before. No behaviour change: 28/28 tests + 11 SLICE-OK; bodytrack 450 races. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Drop 7 impossible null-checks after make_unique/new in SlicedMTA (cannot return null under -fno-exceptions); keep the genuine factory/empty-set guards. - Move the ThreadMRG template from MTASVFGBuilder.h into MTASVFGBuilder.cpp (anonymous namespace); it is only instantiated in createMRGenerator, so the public header no longer pulls in MemPartition.h / ThreadCallGraph.h. - Extract SlicedMTA::vulnerableStmts() to replace three identical race-pair -> statement-set loops. - Rename SlicerBase's _underscore-prefixed helpers to plain camelCase (SVF style). No behaviour change: 28/28 tests + 11 SLICE-OK; bodytrack 450 races. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Remove the non-default SingleSlicer / -slicing-single path: it was untested, added a branch to phase3/phase4, and only existed for an ablation. Drop the class, the option, the pipeline branches and the docs; phase3/phase4 now run the MTA + PTA slicers unconditionally. - mta tool: replace the prefix-match arg peek with boolFlagEnabled(), so `-enable-slicing=false` no longer trips the -max-cxt=0 injection. - SlicedICFGView: precompute a reverse bridged-edge map (bridgedPreds) so getPredNodes is O(preds) instead of scanning every bridged edge each call. - Rename SlicedMTA's remaining _underscore detector helpers to camelCase. No behaviour change: 28/28 tests + 11 SLICE-OK; bodytrack 450 races. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The only base-class inheritance change SlicedMHP needs is the protected traversal hooks (getFunEntry / getSuccNodes / getInEdgesOfCallGraphNode) plus updateNonCandidateFunInterleaving -- those are genuinely overridden. The earlier work had also made the interleaving handlers (analyzeInterleaving, handleFork / Join / Call / Ret / Intra) virtual, and made all of nested ForkJoinAnalysis polymorphic, but nothing overrides any of them (ForkJoinAnalysis has no subclass at all). De-virtualise those, restore ForkJoinAnalysis's private section + drop its added virtual destructor, and remove a stray comment. The MHP.h delta vs upstream SVF shrinks from ~46 to ~21 lines, all now justified by the actual overrides. TCT / LockAnalysis were already minimal (every virtual there is overridden). No behaviour change: 28/28 tests + 11 SLICE-OK; bodytrack 450 races. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add "Author: Jiawei Yang" to the MSli source files (FSPTA, MTASVFGBuilder, SlicedMTA, SlicedView, Slicer, SlicedMHP, SlicedLockAnalysis) and fix the first-line banner of the library MTA.cpp, which mistakenly read "MTA.h". Comment-only; no behaviour change. 28/28 tests + 11 SLICE-OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FSPTA, MTASVFGBuilder, SlicedMHP and SlicedLockAnalysis were missing the standard SVF AGPL license banner, and their author attribution used an ad-hoc `// Author:` line. Give them the canonical SVF header (license banner + a `/* Filename … * Author: Jiawei Yang … */` block with the description), matching SlicedMTA / SlicedView / Slicer. Comment-only; no behaviour change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aseline
The slice recomputes the may-happen-in-parallel (MHP) interleaving and the
flow-sensitive points-to on a reduced graph, and three recomputation paths
silently dropped real races (false negatives). Found by diffing the sliced
race set against a whole-program FSAM baseline: on bodytrack sliced=450 vs
whole=2158; after these fixes sliced==whole==2158, with the sliced
interleaving+lock phase still 1.16s vs 108.6s whole-program.
Three independent, general fixes (each corrects the abstraction/invariant,
not a specific statement):
1. MHP.cpp -- updateAncestorThreads / updateSiblingThreads seeded interleaving
markers via the raw ICFG (forkInst->getOutEdges(), routine->getEntryBlock()
->front()) instead of the virtual getSuccNodes()/getFunEntry() hooks every
other handler uses. On the slice the marker stranded on a removed node, so an
earlier-forked sibling never learned a later sibling. Routing through the
hooks is behaviour-preserving for the full MHP (the base hooks return exactly
those raw values).
2. SlicedView.cpp -- SlicedViewAdapter::getFunEntry returned the entry block's
first instruction, which is often sliced out and stranded; it now prefers the
kept FunEntryICFGNode (expandCallDependence keeps it for every kept function
and buildBridgedEdges links it to the body). One shared-adapter fix corrects
both SlicedMHP and SlicedLockAnalysis. Previously whole functions -- even
main -- lost all threads from their interleaving.
3. Slicer.cpp -- VFG_pre is a pointer-only SVFG, so a load/store of a NON-pointer
value (a race on an int/float field, the common case) has no statement node;
the data-dependence slice seeded nothing and never kept the dereferenced
address pointer's def chain, so the sliced FSAM computed empty points-to and
dropped the race. sliceDataDependenceOverVFG now also seeds from each
load/store's address-pointer definition (always a pointer, hence always in
the pointer-only SVFG).
Validation: -no-slice runs the whole-program FSAM detection (A/B baseline,
wholeProgramDetection); RACE_DUMP=1 emits one stable KEEP/DROP* line per
candidate for diffing sliced vs whole. tests/race_consistency.sh +
tests/race_cases/mt{1,2,5}.c lock the three bugs (assert sliced==no-slice==
expected race counts); points-to suite 28/28 and SLICE-OK 11/11 unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Close the last raw block-entry seed on the sliced MHP path. handleJoin, for a join site inside a loop, continues the interleaving from each loop-exit block's `eb->front()`. Like the function-entry and fork-successor seeds fixed earlier, that raw node may be sliced out -- stranding the post-loop interleaving (sliced getSuccNodes yields nothing for a removed node) and missing races after the loop. Add a virtual MHP hook projectSeedToKept(node): the full analysis returns the node itself; SlicedMHP returns the first kept node(s) reachable forward (intra-procedurally) over the ICFG -- i.e. where the slice resumes. handleJoin now seeds those instead of the raw block front. This generalises the same projection principle already used for getFunEntry, so every interleaving seed on the sliced path now lands on a kept, propagating node. Defensive: bodytrack and the join-in-loop regression (race_cases/mt2) don't independently trigger a sliced-out loop exit, but this removes the last instance of the stranding anti-pattern. All suites unchanged (race-consistency 5/5, points-to 28/28, SLICE-OK 11/11). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
isProtectedByCommonCxtLock(i1,i2) decides whether two accesses are excluded by a
common context-sensitive lock. For a self-race query (i1==i2) -- two dynamic
instances of one statement, e.g. a thread forked in a loop -- the node has a single
cxt-stmt `cs`, so the only (cts1,cts2) pair the loop produces is (cs,cs). That pair
hit `if(cxtStmt1==cxtStmt2) continue;` and was skipped, so the loop fell through to
the unconditional `return true` ("protected") WITHOUT ever checking whether `cs`
holds a lock. Result: every unlocked multiforked self-race was reported as
lock-protected and dropped -- a real-race false negative.
Found via the mt6 stress case: the whole-program (-no-slice) detection dropped two
unlocked self-races (*pD and F in a no-lock pool thread) that the slice kept. The
slice only escaped the bug because its lock analysis happened not to populate a
cxt-stmt for those nodes (so the earlier `!hasCxtStmtFromInst` early-out fired) --
i.e. the slice was correct by luck and is equally exposed wherever it does populate
one. Fixing the base analysis protects both paths.
Fix: when cxtStmt1==cxtStmt2, check that context's own lock set instead of skipping
-- protected only if it holds a non-empty lock (covers both the empty-set and
no-entry cases); otherwise it is a genuine race. Cross-races (i1!=i2) never produce
an equal CxtStmt pair, so they are unaffected; an unlocked cross-race endpoint is
still caught by the node-level !hasCxtStmtFromInst early-out (verified).
After the fix mt6 no-slice == sliced == 6 (was 4 vs 6); bodytrack still 2158==2158;
race-consistency 6/6, points-to 28/28, SLICE-OK 11/11.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the SVF-3.2-based MSli MTA module + soundness fixes to SVF 3.3 / LLVM 21:
3-way merge the base MTA files + seams onto 3.3, plus API adaptations (CallPE is
now a MultiOpndStmt, StmtVFGNode getPAG{Dst,Src}NodeID rename, getDefSVFGNode takes
ValVar, ThreadAPI::isAliasedForkJoin), the dummy-fork-site guard in
setMultiForkedAttrs, and the fork-as-call-without-return mod/ref fix. Drops 4 dead
private fields for -Wall -Werror CI. Validated: race-consistency 6/6, points-to
28/28, query-preservation SLICE-OK 11/11.
- Rename the flow-sensitive multithreaded pointer analysis class/files FSPTA -> FSMPTA (flow-sensitive multithreaded PTA), matching the historical SVF name for this analysis. - Drop SVF-version references from file-head doxygen and inline comments; comments now describe behaviour only, not porting history across versions. - Replace #pragma once with SVF-style include guards in FSMPTA.h, SlicedMHP.h, SlicedLockAnalysis.h, MTASVFGBuilder.h. - Rename the SlicedMTA pipeline methods to drop the phaseN_ counter prefix (runPreAnalysis / runMTASlicingAndAnalysis / runPTASlicingAndAnalysis / runFinalRaceDetection) and the matching "=== Phase N: ===" log labels. Behaviour-preserving: builds clean on LLVM 21; race-consistency 6/6.
Add the single-pass slicing baseline (MSli paper Sec.3 / Sec.5.4): one unified slice V_Single shared by BOTH the ILA and the FSPTA stages, computed as the transitive closure of the race targets under the combined dependence graph (synchronization + data + call dependence). Hence V_ILA, V_PTA are subsets of V_Single. - SingleSlicer (Slicer.h/.cpp): iterate data dependence (over the thread-aware VFG_pre: direct + indirect + interference) and call dependence to a fixpoint, seeded with the synchronization statements + race targets, then one dual-slicing pass. - -slicing-single option (default off); the differential two-slice path stays the default. SlicedMTA computes V_Single once during MTA slicing and reuses it for PTA slicing, so both stages run on the same slice. Validated: race-consistency 6/6 in both modes (single == differential == expected, sound). Shows the ablation -- V_Single is larger than the differential ILA slice on every case (mt6 54 vs 43, mt5 20 vs 16), i.e. the single-pass slice over-approximates vs the tighter differential slices.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1852 +/- ##
==========================================
+ Coverage 65.78% 67.60% +1.81%
==========================================
Files 251 261 +10
Lines 24699 26174 +1475
Branches 4661 5060 +399
==========================================
+ Hits 16249 17694 +1445
- Misses 8450 8480 +30
🚀 New features to boost your workflow:
|
|
Would also be good to add a few more tests for MTA to increase coverage. |
|
I also added more MTA coverage cases in Test-Suite under src/sliced_mta and wired them into sliced_mta_tests, with a small mta_stat_tests group for the MTA statistic mode. Local selected MTA CTests pass: 69/69. |
Fold Sliced{LockAnalysis,MHP,MTA} into {LockAnalysis,MHP,MTA}, and factor shared sliced-view dot-dump and call-graph helpers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename all MTA-specific command-line options to an explicit -mta-* namespace (keeping the verb): e.g. -enable-slicing -> -mta-enable-slicing, -mt-flow-sensitive -> -mta-flow-sensitive, and the lock/MHP/TCT flags (-print-lock, -lock-analysis, -dump-tct, ...). Code cleanup: - Always rebuild a context-sensitive SVFG from the sliced ILA in the main FSMPTA phase; remove the -main-ila-sliced option and the context-insensitive reuse-VFG_pre path it selected (VFG_pre's interference edges are decided context-insensitively, so reuse could over-approximate the FSAM points-to). - Hardwire [THREAD-VF] slice seeding; remove the -thread-vf-sources ablation. - Remove the -observe inspection mode and the -all-pair-mhp statistic (and the now-dead performMHPPairStat). - Move hasThreadFunctions onto the base MTA detector; reduce the thread- function set to a bool.
|
Could you resolve the conflicts before I could merge it? |
The std::hash specialisations for CxtStmt/CxtThread/CxtThreadStmt/CxtThreadProc/ CxtProc hashed only one field of the key (e.g. just the thread id), collapsing all keys sharing that field into a single bucket. Combine every field that operator== compares, hashing IDs rather than raw pointers.
The sliced analysis and the whole-program FSAM baseline are compared against each other, so both must analyze at the same default context sensitivity and share an identical context-insensitive pre-analysis.
- Generate unique interference candidate pairs directly from inverted object-access bitsets: each store/load pair sharing points-to objects is visited exactly once, replacing the per-object bucket enumeration that revisited a pair once per shared object (hundreds of millions of duplicate occurrences on the larger programs) and the per-pair dedup tables. The redundant alias re-check after bucketing is dropped. - Keep only the lock-span witnesses in the per-edge [THREAD-VF] query map (the endpoints are implicit in the key) and release VFG_pre and the VFG-backed slicers as soon as the slices are fixed, so the pre-graph and the main-phase SVFG never coexist in memory. - Use a constant-time membership view for THREAD-VF source selection.
Drop the joined tids from flows crossing out of a symmetric join loop instead of subtracting them from the exit's accumulated state: node states remain pure unions, so the interleaving fixed point no longer depends on the propagation order and the content-ordered worklists revert to plain FIFO. Forward already-computed callee-exit states to call sites (return-flow rendezvous).
|
It looks to me that some classes of Slicer.h/cpp are quite general not limited to MTA. If so, please fix the ones below (1) |
…ontraction Compute each kept node's removed-only reachable kept nodes by SCC-condensing the removed subgraph and propagating over the condensation, instead of eliminating removed nodes one by one. The old contraction materialised removed->removed cross-products whose cost exploded when the slice was small (a large removed region); on darknet the sliced-view build drops from ~84s to ~0.8s, and cases with tiny slices over large programs from hours to milliseconds. The resulting bridged-edge set is identical (verified exhaustively).
The FSPTA view is consumed only by FSMPTA, which queries it via isKeptNode membership and never walks its control flow, so its bridged ICFG edges are dead. Add a buildBridged flag (default true) and pass false for the FSPTA view; the ILA and whole-program views, whose control flow IS walked, keep building them.
@JoelYYoung could you fix the above before I merge this pr? |
…licerBase
The sliced graph views (SlicedICFGView, SlicedPAGView, SlicedThreadCallGraphView,
SlicedSVFIRView) and their shared dot-dump / traversal adapter (SlicedViewAdapter)
are general program-graph views with no MTA dependency. Move them from
MTA/Slicer.{h,cpp} to Graphs/SlicedGraphView.{h,cpp} so their header no longer
drags MHP/LockAnalysis/TCT into every consumer. SlicedTCT (which inherits SVF::TCT
and is consumed by the sliced MTA analyses) stays in MTA/.
Rename the MTA-coupled base SlicerBase to MTASlicerBase (it holds MHP*,
LockAnalysis*, and TCT helpers); MTASlicer already names the concrete ILA slicer,
so the base takes the MTASlicerBase name. Pure code move + rename, no behaviour
change.
Unify the whole-program and sliced MTA analyses onto one topology abstraction (GenericGraphTraits) and drop the parallel sliced subclasses: - MHP / LockAnalysis: one non-template class each; the compute is templated on the two graphs it traverses (ICFG + CallGraph, whole or sliced views) and calls GenericGraphTraits directly, with no wrapper or virtual-hook layer. Removes SlicedMHP and SlicedLockAnalysis. - FSMPTA<SVFGGraph>: templated only on the SVFG traversal layer; the stock FlowSensitive transfer semantics are unchanged. Adds SlicedSVFGView (non-owning, no bridge edges). - The sliced ICFG / PAG / (Thread)CallGraph / SVFG views get GenericGraphTraits specializations (forward + inverse) so a slice works with SVF's generic graph algorithms and GraphWriter. Loop-exit anchor retention in the slicer removes projectSeedToKept. - Merge PTASlicer into MultiStageSlicer (ILA and FSPTA stages share one memoised data-dependence closure). - Align the new files with SVF conventions (Set/Map, Allman braces, quoted includes, file headers). Preserves the whole-program alarm counts exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| - name: ctest sliced-mta | ||
| working-directory: ${{github.workspace}}/Release-build | ||
| run: | | ||
| ctest -R 'sliced_mta_tests|mta_stat_tests|mta_whole_tests|mta_single_slice_tests' -VV | ||
|
|
There was a problem hiding this comment.
Could we also add the mta ctest back too?
There was a problem hiding this comment.
Yes, I will refine the ctest lay out by seperating dvf test and mta test. There will be new prs to Test-Suite and SVF.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This PR adds MSli support to the MTA flow-sensitive analysis path. It keeps the existing mta entry point and adds the sliced ILA and FSPTA pipeline used by MSli.
The implementation also keeps the thread-aware value-flow construction inside the MTA code path and avoids changing the core SVFG builder for the join-related flow edges.