From c2c229445bc1f62c430fe14dfa47925bca39dbc8 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Thu, 3 Aug 2023 14:24:08 +0200 Subject: [PATCH] Fix panic when code in init callback of conditional/repeater re-triggers repeater traversal Don't call init() while mutably borrowing the repeater's inner. Fixes #3214 --- internal/core/model.rs | 17 +++++++----- tests/cases/models/init_recursion.slint | 36 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 tests/cases/models/init_recursion.slint diff --git a/internal/core/model.rs b/internal/core/model.rs index 1a3508f30e4..37c07c87cf0 100644 --- a/internal/core/model.rs +++ b/internal/core/model.rs @@ -896,27 +896,30 @@ impl Repeater { model: &ModelRc, count: usize, ) -> bool { + let mut indices_to_init = Vec::new(); let mut inner = self.0.inner.borrow_mut(); inner.instances.resize_with(count, || (RepeatedInstanceState::Dirty, None)); let offset = inner.offset; let mut any_items_created = false; for (i, c) in inner.instances.iter_mut().enumerate() { if c.0 == RepeatedInstanceState::Dirty { - let created = if c.1.is_none() { + if c.1.is_none() { any_items_created = true; c.1 = Some(init()); - true - } else { - false + indices_to_init.push(i); }; c.1.as_ref().unwrap().update(i + offset, model.row_data(i + offset).unwrap()); - if created { - c.1.as_ref().unwrap().init(); - } c.0 = RepeatedInstanceState::Clean; } } self.data().is_dirty.set(false); + + drop(inner); + let inner = self.0.inner.borrow(); + for item in indices_to_init.into_iter().filter_map(|index| inner.instances.get(index)) { + item.1.as_ref().unwrap().init(); + } + any_items_created } diff --git a/tests/cases/models/init_recursion.slint b/tests/cases/models/init_recursion.slint new file mode 100644 index 00000000000..7e387e1ad61 --- /dev/null +++ b/tests/cases/models/init_recursion.slint @@ -0,0 +1,36 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial + +TestCase := Rectangle { + property test-height: layout.preferred-height; + property test: self.test-height != 0; + + layout := VerticalLayout { + if true: TextInput { + // Trigger a call into the layout + init => { debug(self.y); } + } + } +} + +/* +```cpp +auto handle = TestCase::create(); +const TestCase &instance = *handle; + +assert(instance.get_test_height() != 0.); +``` + + +```rust +let instance = TestCase::new().unwrap(); + +assert!(instance.get_test_height() != 0.); +``` + +```js +var instance = new slint.TestCase(); + +assert(instance.test_height != 0.); +``` +*/