Skip to content

Commit 8cc0259

Browse files
authored
[ORC] Clear stale ElemToPendingSN entries in WaitingOnGraph. (#169747)
WaitingOnGraph::processReadyOrFailed was not clearing stale entries from the ElemToPendingSN map. If symbols were removed from the ExecutionSession and then re-added this could lead to dependencies on the stale entries, triggering a use-after-free bug. llvm/llvm-project#169135
1 parent 49516ba commit 8cc0259

File tree

2 files changed

+64
-4
lines changed

2 files changed

+64
-4
lines changed

llvm/include/llvm/ExecutionEngine/Orc/WaitingOnGraph.h

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,9 @@ template <typename ContainerIdT, typename ElementIdT> class WaitingOnGraph {
338338
// incorporate NewSNs.
339339
std::vector<std::unique_ptr<SuperNode>> ReadyNodes, FailedNodes;
340340
processReadyOrFailed(ModifiedPendingSNs, ReadyNodes, FailedNodes,
341-
SuperNodeDeps, ElemToPendingSN, FailedSNs);
341+
SuperNodeDeps, FailedSNs, &ElemToPendingSN);
342342
processReadyOrFailed(NewSNs, ReadyNodes, FailedNodes, SuperNodeDeps,
343-
ElemToNewSN, FailedSNs);
343+
FailedSNs, nullptr);
344344

345345
CoalesceToPendingSNs.coalesce(ModifiedPendingSNs, ElemToPendingSN);
346346
CoalesceToPendingSNs.coalesce(NewSNs, ElemToPendingSN);
@@ -591,8 +591,11 @@ template <typename ContainerIdT, typename ElementIdT> class WaitingOnGraph {
591591
std::vector<std::unique_ptr<SuperNode>> &Ready,
592592
std::vector<std::unique_ptr<SuperNode>> &Failed,
593593
SuperNodeDepsMap &SuperNodeDeps,
594-
ElemToSuperNodeMap &ElemToSNs,
595-
const std::vector<SuperNode *> &FailedSNs) {
594+
const std::vector<SuperNode *> &FailedSNs,
595+
ElemToSuperNodeMap *ElemToSNs) {
596+
597+
SmallVector<SuperNode *> ToRemoveFromElemToSNs;
598+
596599
for (size_t I = 0; I != SNs.size();) {
597600
auto &SN = SNs[I];
598601

@@ -609,13 +612,24 @@ template <typename ContainerIdT, typename ElementIdT> class WaitingOnGraph {
609612
bool SNReady = SN->Deps.empty();
610613

611614
if (SNReady || SNFailed) {
615+
if (ElemToSNs)
616+
ToRemoveFromElemToSNs.push_back(SN.get());
612617
auto &NodeList = SNFailed ? Failed : Ready;
613618
NodeList.push_back(std::move(SN));
614619
std::swap(SN, SNs.back());
615620
SNs.pop_back();
616621
} else
617622
++I;
618623
}
624+
625+
// Update ElemToSNs (if passed) to remove elements pointing at SN.
626+
for (auto *SN : ToRemoveFromElemToSNs) {
627+
for (auto &[Container, Elems] : SN->defs()) {
628+
auto &Row = (*ElemToSNs)[Container];
629+
for (auto &Elem : Elems)
630+
Row.erase(Elem);
631+
}
632+
}
619633
}
620634

621635
std::vector<std::unique_ptr<SuperNode>> PendingSNs;

llvm/unittests/ExecutionEngine/Orc/WaitingOnGraphTest.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,52 @@ TEST_F(WaitingOnGraphTest, Emit_ZigZag) {
532532
EXPECT_TRUE(PendingSNs.empty());
533533
}
534534

535+
TEST_F(WaitingOnGraphTest, Emit_ReEmit) {
536+
// Test for the bug in https://github.com/llvm/llvm-project/issues/169135,
537+
// which was caused by stale entries in the ElemsToPendingSNs map.
538+
//
539+
// To trigger the bug we need to:
540+
// 1. Create a SuperNode with an unmet dependence, causing it to be added to
541+
// ElemsToPendingSNs.
542+
// 2. Cause that SuperNode to become ready (bug left stale entries in map)
543+
// 3. Remove the node from the Ready map (this is equivalent to removal of a
544+
// symbol in an ORC session, and allows new SuperNodes to depend on the
545+
// stale entry).
546+
// 4. Add a new node that references the previously emitted/removed SuperNode
547+
// This triggers access of the stale entry, and should error out in
548+
// sanitizer builds.
549+
550+
SuperNodeBuilder B;
551+
552+
// 1. Create SuperNode with unmet dependence.
553+
ContainerElementsMap Defs0({{0, {0}}});
554+
ContainerElementsMap Deps0({{0, {1}}});
555+
B.add(Defs0, Deps0);
556+
emit(TestGraph::simplify(B.takeSuperNodes()));
557+
558+
EXPECT_TRUE(Ready.empty());
559+
560+
// 2. Cause previous SuperNode to become ready.
561+
ContainerElementsMap Defs1({{0, {1}}});
562+
B.add(Defs1, ContainerElementsMap());
563+
emit(TestGraph::simplify(B.takeSuperNodes()));
564+
565+
// Check that both nodes have become ready.
566+
EXPECT_EQ(Ready, merge(Defs0, Defs1));
567+
568+
// 3. Erase Ready nodes to simulate removal from the graph.
569+
Ready.clear();
570+
571+
// 4. Emit a new dependence on the original def.
572+
ContainerElementsMap Defs2({{0, {2}}});
573+
ContainerElementsMap Deps2({{0, {0}}});
574+
B.add(Defs2, Deps2);
575+
auto ER = emit(TestGraph::simplify(B.takeSuperNodes()));
576+
577+
// We expect the new dependence to remain pending.
578+
EXPECT_TRUE(ER.Ready.empty());
579+
}
580+
535581
TEST_F(WaitingOnGraphTest, Fail_Empty) {
536582
// Check that failing an empty set is a no-op.
537583
auto FR = G.fail(ContainerElementsMap());

0 commit comments

Comments
 (0)