|
8 | 8 | #include "db_sta/dbNetwork.hh" |
9 | 9 | #include "gtest/gtest.h" |
10 | 10 | #include "odb/db.h" |
| 11 | +#include "odb/dbTypes.h" |
| 12 | +#include "sta/Graph.hh" |
11 | 13 | #include "sta/NetworkClass.hh" |
| 14 | +#include "sta/Path.hh" |
| 15 | +#include "sta/SdcClass.hh" |
| 16 | +#include "sta/Sta.hh" |
12 | 17 | #include "tst/IntegratedFixture.h" |
13 | 18 |
|
14 | 19 | namespace sta { |
@@ -97,4 +102,87 @@ TEST_F(TestDbSta, TestHierarchyConnectivity) |
97 | 102 | ASSERT_EQ(bterm_clk->getITerm(), nullptr); |
98 | 103 | } |
99 | 104 |
|
| 105 | +// Regression for #10210 (stale Path* dereference in rsz). |
| 106 | +// |
| 107 | +// Topology (TestDbSta_StalePath.v): |
| 108 | +// clk -> b1(BUF) -> inv1(INV) -> nd1(NAND2) -> out1 |
| 109 | +// nd1/A2 <- in2 |
| 110 | +// |
| 111 | +// Flow: |
| 112 | +// 1. Capture drvr_path at nd1/ZN and snapshot prevPath() pointer + pin. |
| 113 | +// 2. Delete upstream b1 + updateTiming -> free b1/inv1 Path[] slots. |
| 114 | +// 3. Add a fresh BUF + clock + updateTiming -> recycle freed slots. |
| 115 | +// 4. Assert the captured Path's prev slot has been recycled: pin() |
| 116 | +// decodes to data that belongs to a different instance than nd1's |
| 117 | +// real input. When the drvr slot itself is preserved, also assert |
| 118 | +// the strict stale-pointer signature (same raw address, different |
| 119 | +// content). |
| 120 | +TEST_F(TestDbSta, StalePrevPathAfterUpdateTiming) |
| 121 | +{ |
| 122 | + readVerilogAndSetup("TestDbSta_StalePath.v"); |
| 123 | + sta_->updateTiming(true); |
| 124 | + |
| 125 | + Network* network = sta_->network(); |
| 126 | + |
| 127 | + Instance* nd1 = db_network_->dbToSta(block_->findInst("nd1")); |
| 128 | + Path* drvr_path = sta_->vertexWorstArrivalPath( |
| 129 | + sta_->ensureGraph()->pinDrvrVertex(network->findPin(nd1, "ZN")), |
| 130 | + MinMax::max()); |
| 131 | + ASSERT_NE(drvr_path, nullptr); |
| 132 | + ASSERT_EQ(network->pathName(drvr_path->pin(sta_.get())), "nd1/ZN"); |
| 133 | + const Path* prev_before = drvr_path->prevPath(); |
| 134 | + ASSERT_NE(prev_before, nullptr); |
| 135 | + const std::string prev_pin_before |
| 136 | + = network->pathName(prev_before->pin(sta_.get())); |
| 137 | + |
| 138 | + // 2. Free upstream Path[] slots. |
| 139 | + sta_->deleteInstance(db_network_->dbToSta(block_->findInst("b1"))); |
| 140 | + sta_->updateTiming(true); |
| 141 | + |
| 142 | + // 3. Recycle freed slots via a single fresh BUF driven by a new clock. |
| 143 | + odb::dbNet* in3_net = odb::dbNet::create(block_, "in3"); |
| 144 | + odb::dbBTerm* new_bt = odb::dbBTerm::create(in3_net, "in3"); |
| 145 | + new_bt->setIoType(odb::dbIoType::INPUT); |
| 146 | + odb::dbNet* nfan_net = odb::dbNet::create(block_, "nfan"); |
| 147 | + odb::dbInst* bnew |
| 148 | + = odb::dbInst::create(block_, db_->findMaster("BUF_X1"), "bnew"); |
| 149 | + bnew->findITerm("A")->connect(in3_net); |
| 150 | + bnew->findITerm("Z")->connect(nfan_net); |
| 151 | + |
| 152 | + PinSet clk2_pins(db_network_); |
| 153 | + clk2_pins.insert(db_network_->dbToSta(new_bt)); |
| 154 | + FloatSeq clk2_waveform = {0.0f, 0.1f}; |
| 155 | + sta_->makeClock( |
| 156 | + "clk2", clk2_pins, false, 0.2f, clk2_waveform, "", sta_->cmdMode()); |
| 157 | + sta_->updateTiming(true); |
| 158 | + |
| 159 | + // 4. Staleness evidence. Allocator behaviour decides which slot lands |
| 160 | + // on the recycled memory: |
| 161 | + // (a) drvr slot preserved + prev slot recycled (glibc in our CI) -- |
| 162 | + // strict stale-pointer signature: same raw prev address, but |
| 163 | + // pin() decodes to content from an unrelated instance. |
| 164 | + // (b) drvr slot itself recycled (other allocators) -- drvr pin decodes |
| 165 | + // to something other than nd1/ZN. |
| 166 | + // Either case proves the captured raw Path* outlived the slot. |
| 167 | + const std::string drvr_pin_after |
| 168 | + = network->pathName(drvr_path->pin(sta_.get())); |
| 169 | + const Path* prev_after = drvr_path->prevPath(); |
| 170 | + const std::string prev_pin_after |
| 171 | + = prev_after ? network->pathName(prev_after->pin(sta_.get())) |
| 172 | + : std::string("<null>"); |
| 173 | + const bool drvr_recycled = drvr_pin_after != "nd1/ZN"; |
| 174 | + const bool prev_recycled = prev_after != nullptr && prev_pin_after != "nd1/A1" |
| 175 | + && prev_pin_after != "nd1/A2"; |
| 176 | + EXPECT_TRUE(drvr_recycled || prev_recycled) |
| 177 | + << "expected slot reuse to be demonstrable; drvr=" << drvr_pin_after |
| 178 | + << " prev=" << prev_pin_after; |
| 179 | + if (!drvr_recycled && prev_after != nullptr) { |
| 180 | + EXPECT_EQ(prev_after, prev_before) |
| 181 | + << "stale-pointer signature: prev_path_ address unchanged"; |
| 182 | + EXPECT_NE(prev_pin_after, prev_pin_before) |
| 183 | + << "but slot content should differ after free+reuse. before=" |
| 184 | + << prev_pin_before << " after=" << prev_pin_after; |
| 185 | + } |
| 186 | +} |
| 187 | + |
100 | 188 | } // namespace sta |
0 commit comments