Skip to content

Add MSli support to MTA - #1852

Merged
yuleisui merged 69 commits into
SVF-tools:masterfrom
JoelYYoung:msli-pr
Jul 23, 2026
Merged

Add MSli support to MTA#1852
yuleisui merged 69 commits into
SVF-tools:masterfrom
JoelYYoung:msli-pr

Conversation

@JoelYYoung

@JoelYYoung JoelYYoung commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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.

JoelYYoung and others added 30 commits June 11, 2026 23:31
…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

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.41642% with 368 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.60%. Comparing base (b086369) to head (a8d2bd5).
⚠️ Report is 7 commits behind head on master.

Files with missing lines Patch % Lines
svf/lib/Graphs/SlicedGraphs.cpp 68.21% 89 Missing ⚠️
svf/lib/MTA/MTASVFGBuilder.cpp 58.65% 74 Missing ⚠️
svf/include/Graphs/SlicedGraphs.h 56.00% 66 Missing ⚠️
svf/lib/MTA/MTASlicer.cpp 85.45% 49 Missing ⚠️
svf/lib/MTA/MTA.cpp 92.32% 32 Missing ⚠️
svf/lib/MTA/MHP.cpp 74.46% 24 Missing ⚠️
svf/lib/Util/ThreadAPI.cpp 56.25% 14 Missing ⚠️
svf/lib/MTA/LockAnalysis.cpp 90.27% 7 Missing ⚠️
svf/lib/MSSA/SVFGBuilder.cpp 45.45% 6 Missing ⚠️
svf/include/MTA/MHP.h 0.00% 4 Missing ⚠️
... and 2 more
Additional details and impacted files

Impacted file tree graph

@@            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     
Files with missing lines Coverage Δ
svf-llvm/tools/MTA/mta.cpp 100.00% <100.00%> (ø)
svf/include/Graphs/CallGraph.h 96.29% <100.00%> (-1.75%) ⬇️
svf/include/Graphs/DOTGraphTraits.h 83.33% <ø> (ø)
svf/include/Graphs/GraphPrinter.h 77.77% <ø> (ø)
svf/include/Graphs/GraphWriter.h 74.44% <100.00%> (-2.57%) ⬇️
svf/include/Graphs/ICFG.h 98.41% <100.00%> (+0.37%) ⬆️
svf/include/Graphs/SVFG.h 77.94% <ø> (+0.32%) ⬆️
svf/include/Graphs/ThreadCallGraph.h 79.06% <100.00%> (+8.01%) ⬆️
svf/include/MSSA/MemRegion.h 90.47% <100.00%> (+1.00%) ⬆️
svf/include/MSSA/MemSSA.h 100.00% <ø> (ø)
... and 28 more

... and 16 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@yuleisui

yuleisui commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Would also be good to add a few more tests for MTA to increase coverage.

@JoelYYoung

Copy link
Copy Markdown
Contributor Author

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.

JoelYYoung and others added 2 commits July 3, 2026 00:32
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.
@yuleisui

Copy link
Copy Markdown
Collaborator

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).
@yuleisui

Copy link
Copy Markdown
Collaborator

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) SlicedICFGView, SlicedPAGView, SlicedSVFIRView, SlicedViewAdapter, SlicedThreadCallGraphView can be moved under the folder Graphs (pls also identify other classes if they are also general but not MTA-related)
(2) Rename SlicerBase to MTASlicer as it depends on a number of MTA classes and structures.

…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.
@yuleisui

Copy link
Copy Markdown
Collaborator

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) SlicedICFGView, SlicedPAGView, SlicedSVFIRView, SlicedViewAdapter, SlicedThreadCallGraphView can be moved under the folder Graphs (pls also identify other classes if they are also general but not MTA-related) (2) Rename SlicerBase to MTASlicer as it depends on a number of MTA classes and structures.

@JoelYYoung could you fix the above before I merge this pr?

JoelYYoung and others added 4 commits July 20, 2026 11:31
…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>
JoelYYoung and others added 3 commits July 22, 2026 19:48
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>
Comment thread .github/workflows/github-action.yml Outdated
Comment on lines +69 to +73
- 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also add the mta ctest back too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@yuleisui
yuleisui merged commit c8c45fc into SVF-tools:master Jul 23, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants