Avoid a segfault: an event may kill the event object itself indirectly.

This commit is contained in:
Matthias Koefferlein 2023-03-26 21:29:06 +02:00
parent 1cd033e168
commit 7d41078e8c
3 changed files with 80 additions and 0 deletions

View File

@ -106,6 +106,7 @@ PYTHONTEST (dbLayoutToNetlist, "dbLayoutToNetlist.py")
PYTHONTEST (dbLayoutVsSchematic, "dbLayoutVsSchematic.py") PYTHONTEST (dbLayoutVsSchematic, "dbLayoutVsSchematic.py")
PYTHONTEST (dbNetlistCrossReference, "dbNetlistCrossReference.py") PYTHONTEST (dbNetlistCrossReference, "dbNetlistCrossReference.py")
PYTHONTEST (layLayers, "layLayers.py") PYTHONTEST (layLayers, "layLayers.py")
PYTHONTEST (layObjects, "layObjects.py")
PYTHONTEST (layPixelBuffer, "layPixelBuffer.py") PYTHONTEST (layPixelBuffer, "layPixelBuffer.py")
PYTHONTEST (tlTest, "tlTest.py") PYTHONTEST (tlTest, "tlTest.py")
#if defined(HAVE_QT) && defined(HAVE_QTBINDINGS) #if defined(HAVE_QT) && defined(HAVE_QTBINDINGS)

View File

@ -175,8 +175,26 @@ public:
typedef typename receivers::iterator receivers_iterator; typedef typename receivers::iterator receivers_iterator;
#endif #endif
event<_TMPLARGLISTP> ()
: mp_destroyed_sentinel (0)
{
// .. nothing yet ..
}
~event<_TMPLARGLISTP> ()
{
if (mp_destroyed_sentinel) {
*mp_destroyed_sentinel = true;
}
mp_destroyed_sentinel = 0;
}
void operator() (_CALLARGLIST) void operator() (_CALLARGLIST)
{ {
bool was_destroyed = false;
bool *org_sentinel = mp_destroyed_sentinel;
mp_destroyed_sentinel = &was_destroyed;
// Issue the events. Because inside the call, other receivers might be added, we make a copy // Issue the events. Because inside the call, other receivers might be added, we make a copy
// first. This way added events won't be called now. // first. This way added events won't be called now.
receivers tmp_receivers = m_receivers; receivers tmp_receivers = m_receivers;
@ -184,6 +202,10 @@ public:
if (r->first.get ()) { if (r->first.get ()) {
try { try {
r->second->call (_JOIN(r->first.get (), _CALLARGS)); r->second->call (_JOIN(r->first.get (), _CALLARGS));
if (was_destroyed) {
// during the call something deleted us. Stop immediately.
return;
}
} catch (tl::Exception &ex) { } catch (tl::Exception &ex) {
handle_event_exception (ex); handle_event_exception (ex);
} catch (std::exception &ex) { } catch (std::exception &ex) {
@ -194,6 +216,8 @@ public:
} }
} }
mp_destroyed_sentinel = org_sentinel;
// Clean up expired entries afterwards (the call may have expired them) // Clean up expired entries afterwards (the call may have expired them)
receivers_iterator w = m_receivers.begin (); receivers_iterator w = m_receivers.begin ();
for (receivers_iterator r = m_receivers.begin (); r != m_receivers.end (); ++r) { for (receivers_iterator r = m_receivers.begin (); r != m_receivers.end (); ++r) {
@ -339,6 +363,7 @@ public:
} }
private: private:
bool *mp_destroyed_sentinel;
receivers m_receivers; receivers m_receivers;
}; };

54
testdata/python/layObjects.py vendored Normal file
View File

@ -0,0 +1,54 @@
# KLayout Layout Viewer
# Copyright (C) 2006-2023 Matthias Koefferlein
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import pya
import unittest
class LAYObjectsTests(unittest.TestCase):
def test_1(self):
class MyBrowserSource(pya.BrowserSource):
def get(self, url):
next_url = "int:" + str(int(url.split(":")[1]) + 1)
return f"This is {url}. <a href='{next_url}'>Goto next ({next_url})</a>"
dialog = pya.BrowserDialog()
dialog.home = "int:0"
dialog.source = MyBrowserSource()
dialog = pya.BrowserDialog()
dialog.home = "int:0"
dialog.source = MyBrowserSource()
self.assertEqual(True, True)
# run unit tests
if __name__ == '__main__':
suite = unittest.TestSuite()
# NOTE: Use this instead of loadTestsfromTestCase to select a specific test:
# suite.addTest(BasicTest("test_26"))
suite = unittest.TestLoader().loadTestsFromTestCase(LAYObjectsTests)
# Only runs with Application available
if "Application" in pya.__all__ and not unittest.TextTestRunner(verbosity = 1).run(suite).wasSuccessful():
sys.exit(1)