KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_board_item_index.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
21
22#include <chrono>
23#include <memory>
24
25#include <board.h>
26#include <footprint.h>
27#include <pad.h>
28#include <pcb_shape.h>
29#include <pcb_table.h>
30#include <pcb_tablecell.h>
31#include <pcb_track.h>
32
33
34BOOST_AUTO_TEST_SUITE( BoardItemIndex )
35
36
37BOOST_AUTO_TEST_CASE( ResolveItemIdentityCachePurgedOnDestruction )
38{
39 BOARD board;
40
41 FOOTPRINT* footprint = new FOOTPRINT( &board );
42 board.Add( footprint );
43
44 PAD* pad = new PAD( footprint );
45 footprint->Pads().push_back( pad );
46 board.CacheItemById( *pad );
47
48 const KIID padId = pad->m_Uuid;
49
51 BOOST_REQUIRE_EQUAL( board.ResolveItem( padId, true ), static_cast<BOARD_ITEM*>( pad ) );
52
53 // Detach the pad from the footprint without FOOTPRINT::Remove()'s surgical eviction, leaving it
54 // parented to the still-board-attached footprint, then free it directly. This stands in for the
55 // producer paths that free a board-parented item without touching the identity cache; only the
56 // ~BOARD_ITEM safety net can then keep ResolveItem() from returning a freed pointer.
57 std::deque<PAD*>& pads = footprint->Pads();
58 pads.erase( std::find( pads.begin(), pads.end(), pad ) );
59 delete pad;
60
61 BOOST_CHECK( board.ResolveItem( padId, true ) == nullptr );
62}
63
64
65// A never-indexed item parented to a freed board must not touch that board on destruction, the
66// crash from the "KiCad master crashes" report (follow-up to ac12a1c820).
67BOOST_AUTO_TEST_CASE( UncachedItemSurvivesBoardDestruction )
68{
69 BOARD* board = new BOARD();
70 PCB_VIA* dummy = new PCB_VIA( board );
71
72 BOOST_REQUIRE( !dummy->IsIndexedInBoard() );
73
74 delete board;
75
76 BOOST_CHECK_NO_THROW( delete dummy );
77}
78
79
80// The indexed counterpart of the case above. ~BOARD must clear the membership flag as it drops
81// the index, or a survivor still believes it is indexed and walks its parent chain into the
82// freed board.
83BOOST_AUTO_TEST_CASE( IndexedItemSurvivesBoardDestruction )
84{
85 BOARD* board = new BOARD();
86 FOOTPRINT* footprint = new FOOTPRINT( board );
87
88 board->Add( footprint );
89
90 PAD* pad = new PAD( footprint );
91 footprint->Pads().push_back( pad );
92 board->CacheItemById( *pad );
93
94 BOOST_REQUIRE( pad->IsIndexedInBoard() );
95
96 // Detach without FOOTPRINT::Remove() so the board frees the footprint while the pad lives on,
97 // still parented to it. This is the ownership hand-off the safety net in ~BOARD_ITEM covers.
98 std::deque<PAD*>& pads = footprint->Pads();
99 pads.erase( std::find( pads.begin(), pads.end(), pad ) );
100
101 delete board;
102
103 BOOST_CHECK( !pad->IsIndexedInBoard() );
104 BOOST_CHECK_NO_THROW( delete pad );
105}
106
107
108BOOST_AUTO_TEST_CASE( SwapItemDataReindexesFootprintChildren )
109{
110 BOARD* board = new BOARD();
111
112 auto footprint = std::make_unique<FOOTPRINT>( board );
113 auto pad = new PAD( footprint.get() );
114 pad->SetNumber( "1" );
115
116 PAD* originalPad = pad;
117 KIID originalPadId = originalPad->m_Uuid;
118
119 footprint->Add( pad );
120
121 FOOTPRINT* liveFootprint = footprint.get();
122 board->Add( footprint.release() );
123
124 auto image = std::unique_ptr<FOOTPRINT>( static_cast<FOOTPRINT*>( liveFootprint->Clone() ) );
125
126 liveFootprint->SwapItemData( image.get() );
127 image.reset();
128
129 BOARD_ITEM* resolved = board->ResolveItem( originalPadId, true );
130
131 BOOST_REQUIRE( resolved );
132 BOOST_CHECK_EQUAL( resolved->Type(), PCB_PAD_T );
133 BOOST_CHECK( resolved != originalPad );
134
135 delete board;
136}
137
138
139BOOST_AUTO_TEST_CASE( AttachedItemUuidRewriteDropsOldAlias )
140{
141 BOARD* board = new BOARD();
142
143 auto shape = std::make_unique<PCB_SHAPE>( board );
144 PCB_SHAPE* liveShape = shape.get();
145 const KIID oldId = liveShape->m_Uuid;
146 const KIID newId;
147
148 board->Add( shape.release() );
149
150 const_cast<KIID&>( liveShape->m_Uuid ) = newId;
151
152 BOOST_CHECK( board->ResolveItem( oldId, true ) == nullptr );
153 BOOST_CHECK_EQUAL( board->ResolveItem( newId, true ), liveShape );
154
155 delete board;
156}
157
158
159BOOST_AUTO_TEST_CASE( ResolvingNewUuidPurgesStaleOldAlias )
160{
161 BOARD* board = new BOARD();
162
163 auto shape = std::make_unique<PCB_SHAPE>( board );
164 PCB_SHAPE* liveShape = shape.get();
165 const KIID oldId = liveShape->m_Uuid;
166 const KIID newId;
167
168 board->Add( shape.release() );
169 const_cast<KIID&>( liveShape->m_Uuid ) = newId;
170
171 BOOST_CHECK_EQUAL( board->ResolveItem( newId, true ), liveShape );
172 BOOST_CHECK( board->IsItemIndexedById( newId ) );
173 BOOST_CHECK( !board->IsItemIndexedById( oldId ) );
174
175 delete board;
176}
177
178
179BOOST_AUTO_TEST_CASE( RebindItemUuidUpdatesCacheAtomically )
180{
181 BOARD* board = new BOARD();
182
183 auto shape = std::make_unique<PCB_SHAPE>( board );
184 PCB_SHAPE* liveShape = shape.get();
185 const KIID oldId = liveShape->m_Uuid;
186 const KIID newId;
187
188 board->Add( shape.release() );
189 board->RebindItemUuid( *liveShape, newId );
190
191 BOOST_CHECK( liveShape->m_Uuid == newId );
192 BOOST_CHECK( !board->IsItemIndexedById( oldId ) );
193 BOOST_CHECK( board->IsItemIndexedById( newId ) );
194 BOOST_CHECK_EQUAL( board->ResolveItem( newId, true ), liveShape );
195
196 delete board;
197}
198
199
200BOOST_AUTO_TEST_CASE( AttachedChildSetUuidRebindsCache )
201{
202 BOARD* board = new BOARD();
203
204 auto footprint = std::make_unique<FOOTPRINT>( board );
205 auto pad = new PAD( footprint.get() );
206 PAD* livePad = pad;
207 const KIID oldId = livePad->m_Uuid;
208 const KIID newId;
209
210 footprint->Add( pad );
211 board->Add( footprint.release() );
212
213 livePad->SetUuid( newId );
214
215 BOOST_CHECK( livePad->m_Uuid == newId );
216 BOOST_CHECK( !board->IsItemIndexedById( oldId ) );
217 BOOST_CHECK( board->IsItemIndexedById( newId ) );
218 BOOST_CHECK_EQUAL( board->ResolveItem( newId, true ), livePad );
219
220 delete board;
221}
222
223
224BOOST_AUTO_TEST_CASE( CacheItemByIdCanonicalizesRewrittenUuid )
225{
226 BOARD* board = new BOARD();
227
228 auto shape = std::make_unique<PCB_SHAPE>( board );
229 PCB_SHAPE* liveShape = shape.get();
230 const KIID oldId = liveShape->m_Uuid;
231 const KIID newId;
232
233 board->Add( shape.release() );
234
235 const_cast<KIID&>( liveShape->m_Uuid ) = newId;
236 board->CacheItemById( *liveShape );
237
238 BOOST_CHECK( !board->IsItemIndexedById( oldId ) );
239 BOOST_CHECK( board->IsItemIndexedById( newId ) );
240 BOOST_CHECK_EQUAL( board->ResolveItem( newId, true ), liveShape );
241
242 delete board;
243}
244
245
246BOOST_AUTO_TEST_CASE( RepairDuplicateItemUuidsKeepsEarlierTraversalWinner )
247{
248 BOARD* board = new BOARD();
249
250 auto footprint = std::make_unique<FOOTPRINT>( board );
251 FOOTPRINT* liveFootprint = footprint.get();
252 const KIID claimedId = liveFootprint->m_Uuid;
253
254 auto shape = std::make_unique<PCB_SHAPE>( board );
255 PCB_SHAPE* liveShape = shape.get();
256 const KIID originalShapeId = liveShape->m_Uuid;
257
258 board->Add( footprint.release() );
259 board->Add( shape.release() );
260
261 const_cast<KIID&>( liveShape->m_Uuid ) = claimedId;
262
264 BOOST_CHECK( liveFootprint->m_Uuid == claimedId );
265 BOOST_CHECK( liveShape->m_Uuid != claimedId );
266 BOOST_CHECK( liveShape->m_Uuid != originalShapeId );
267 BOOST_CHECK_EQUAL( board->ResolveItem( claimedId, true ), liveFootprint );
268 BOOST_CHECK( board->ResolveItem( originalShapeId, true ) == nullptr );
269 BOOST_CHECK_EQUAL( board->ResolveItem( liveShape->m_Uuid, true ), liveShape );
270
271 delete board;
272}
273
274
275BOOST_AUTO_TEST_CASE( RemovingResolvedChildEvictsCacheOnFootprintHolder )
276{
277 // Regression for the RC_TREE_MODEL::GetValue use-after-free: ResolveItem() caches a
278 // footprint child on demand, but the cache eviction in FOOTPRINT::Remove is gated on the
279 // parent footprint being indexed. A footprint-holder board never indexes anything (
280 // CacheItemById early-returns), yet ResolveItem still caches children, so removing and
281 // freeing a resolved child leaves a dangling id in the cache. A later resolve then hands
282 // back the freed pointer.
283 BOARD* board = new BOARD();
285
286 auto footprint = std::make_unique<FOOTPRINT>( board );
287 auto pad = new PAD( footprint.get() );
288 pad->SetNumber( "1" );
289
290 const KIID padId = pad->m_Uuid;
291 footprint->Add( pad );
292
293 FOOTPRINT* liveFootprint = footprint.get();
294 board->Add( footprint.release() );
295
296 // Resolve the pad the way a results-tree row does. On the buggy code this caches the pad
297 // even though the holder board never indexed its parent footprint.
298 BOOST_REQUIRE_EQUAL( board->ResolveItem( padId, true ), pad );
299
300 // Remove and destroy the pad through the footprint, as a footprint edit does.
301 liveFootprint->Remove( pad );
302 const bool staleEntrySurvived = board->IsItemIndexedById( padId );
303
304 delete pad;
305 delete board;
306
307 BOOST_CHECK( !staleEntrySurvived );
308}
309
310
311BOOST_AUTO_TEST_CASE( TableCellResolvesToTheCellNotItsTable )
312{
313 // A cell carries its own KIID and is an editable item in its own right, so handing back the
314 // parent table forced every caller that can select a cell to special-case it. The cache is
315 // cleared to exercise the linear scan, which is the path that got this wrong.
316 BOARD board;
317 auto* table = new PCB_TABLE( &board, 0 );
318 auto* cell = new PCB_TABLECELL( table );
319
320 table->AddCell( cell );
321 board.Add( table );
322 board.ClearItemByIdCache();
323
324 const KIID tableId = table->m_Uuid;
325 const KIID cellId = cell->m_Uuid;
326
327 BOOST_CHECK_EQUAL( board.ResolveItem( cellId, true ), cell );
328 BOOST_CHECK_EQUAL( board.ResolveItem( tableId, true ), table );
329
330 // Caching the table under the cell's id also made the two lookups evict each other.
331 BOOST_CHECK_EQUAL( board.ResolveItem( cellId, true ), cell );
332 BOOST_CHECK( board.IsItemIndexedById( tableId ) );
333 BOOST_CHECK( board.IsItemIndexedById( cellId ) );
334}
335
336
337BOOST_AUTO_TEST_CASE( FootprintTableCellResolvesByItsOwnId )
338{
339 // The footprint branch walked GraphicalItems() without ever descending into a table, so a
340 // cell inside a footprint table could not be resolved at all.
341 BOARD board;
342 auto* footprint = new FOOTPRINT( &board );
343 auto* table = new PCB_TABLE( footprint, 0 );
344 auto* cell = new PCB_TABLECELL( table );
345
346 table->AddCell( cell );
347 footprint->Add( table, ADD_MODE::APPEND );
348 board.Add( footprint );
349 board.ClearItemByIdCache();
350
351 BOOST_CHECK_EQUAL( board.ResolveItem( cell->m_Uuid, true ), cell );
352}
353
354
355// Unbinding an item the index never held must not scan the whole index, or comparing a few footprints
356// against a large board costs as much as the board each time
357BOOST_AUTO_TEST_CASE( UnbindingForeignSubtreesDoesNotScanTheIndex )
358{
359 BOARD board;
360
361 for( int i = 0; i < 20000; i++ )
362 {
363 FOOTPRINT* footprint = new FOOTPRINT( &board );
364
365 for( int j = 0; j < 4; j++ )
366 footprint->Add( new PAD( footprint ), ADD_MODE::APPEND );
367
368 board.Add( footprint );
369 }
370
371 FOOTPRINT foreign( &board );
372
373 for( int j = 0; j < 20; j++ )
374 foreign.Add( new PAD( &foreign ), ADD_MODE::APPEND );
375
376 auto start = std::chrono::steady_clock::now();
377
378 for( int i = 0; i < 300; i++ )
379 board.UncacheItemSubtreeById( foreign );
380
381 std::chrono::duration<double> elapsed = std::chrono::steady_clock::now() - start;
382
383 BOOST_CHECK_LT( elapsed.count(), 5.0 );
384}
385
386
387// Unbinding through one board must leave another board's claim on the item intact, or the item
388// is never evicted from the owner when it is destroyed
389BOOST_AUTO_TEST_CASE( UnbindingThroughAnotherBoardKeepsTheOwner )
390{
391 BOARD owner;
392 BOARD other;
393
394 auto* pad = new PAD( nullptr );
395 KIID id = pad->m_Uuid;
396
397 owner.CacheItemById( *pad );
398 other.UncacheItemSubtreeById( *pad );
399
400 BOOST_CHECK( owner.IsItemIndexedById( id ) );
401
402 delete pad;
403
404 BOOST_CHECK( !owner.IsItemIndexedById( id ) );
405}
406
407
@ FPHOLDER
Definition board.h:403
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
void SwapItemData(BOARD_ITEM *aImage)
Swap data between aItem and aImage.
void SetUuid(const KIID &aUuid)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
void CacheItemById(BOARD_ITEM &aItem) const
Add an item to the item-by-id cache.
Definition board.h:1727
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1520
void SetBoardUse(BOARD_USE aUse)
Set what the board is going to be used for.
Definition board.h:423
void ClearItemByIdCache()
Definition board.h:1739
void RebindItemUuid(BOARD_ITEM &aItem, const KIID &aNewId)
Rebind the UUID of an attached item and keep the item-by-id cache coherent.
Definition board.h:737
void UncacheItemSubtreeById(const BOARD_ITEM &aItem)
Uncache an item and its children.
Definition board.h:1735
int RepairDuplicateItemUuids()
Rebind duplicate attached-item UUIDs so each live board item has a unique ID.
Definition board.cpp:2292
bool IsItemIndexedById(const BOARD_ITEM *aItem) const
Return true if aItem is currently indexed.
Definition board.h:1713
BOARD_ITEM * ResolveItem(const KIID &aID, bool aAllowNullptrReturn=false) const
Definition board.cpp:2142
const KIID m_Uuid
Definition eda_item.h:599
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
EDA_ITEM * Clone() const override
Create a duplicate of this item with linked list members set to NULL.
std::deque< PAD * > & Pads()
Definition footprint.h:355
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
Definition kiid.h:46
Definition pad.h:61
std::vector< FAB_LAYER_COLOR > dummy
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_CASE(ResolveItemIdentityCachePurgedOnDestruction)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79