Fix array index access at negative index

Conversion from negative float to unsigned is saturating to 0 in rust
and undefined behavior in C++, we should therefore handle the case
properly

Fixes #8222
This commit is contained in:
Olivier Goffart 2025-04-22 11:28:09 +02:00 committed by GitHub
parent ff6065ace4
commit cd8ab8ce53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 24 additions and 10 deletions

View file

@ -25,9 +25,9 @@ struct ModelChangeListener
using ModelPeer = std::weak_ptr<ModelChangeListener>;
template<typename M>
auto access_array_index(const std::shared_ptr<M> &model, size_t index)
auto access_array_index(const std::shared_ptr<M> &model, std::ptrdiff_t index)
{
if (!model) {
if (!model || index < 0) {
return decltype(*model->row_data_tracked(index)) {};
} else if (const auto v = model->row_data_tracked(index)) {
return *v;

View file

@ -3250,9 +3250,7 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
let base_e = compile_expression(array, ctx);
let index_e = compile_expression(index, ctx);
let value_e = compile_expression(value, ctx);
format!(
"{base_e}->set_row_data({index_e}, {value_e})"
)
format!("[&](auto index, const auto &base) {{ if (index >= 0. && std::size_t(index) < base->row_count()) base->set_row_data(index, {value_e}); }}({index_e}, {base_e})")
}
Expression::BinaryExpression { lhs, rhs, op } => {
let lhs_str = compile_expression(lhs, ctx);

View file

@ -2381,7 +2381,7 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
let base_e = compile_expression(array, ctx);
let index_e = compile_expression(index, ctx);
let value_e = compile_expression(value, ctx);
quote!((#base_e).set_row_data(#index_e as usize, #value_e as _))
quote!((#base_e).set_row_data(#index_e as isize as usize, #value_e as _))
}
Expression::BinaryExpression { lhs, rhs, op } => {
let lhs_ty = lhs.ty(ctx);

View file

@ -173,7 +173,7 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon
let index = eval_expression(index, local_context);
match (array, index) {
(Value::Model(model), Value::Number(index)) => {
model.row_data_tracked(index as usize).unwrap_or_else(|| default_value_for_type(&expression.ty()))
model.row_data_tracked(index as isize as usize).unwrap_or_else(|| default_value_for_type(&expression.ty()))
}
_ => {
Value::Void
@ -1472,8 +1472,8 @@ fn eval_assignment(lhs: &Expression, op: char, rhs: Value, local_context: &mut E
let index = eval_expression(index, local_context);
match (array, index) {
(Value::Model(model), Value::Number(index)) => {
let index = index as usize;
if (index) < model.row_count() {
if index >= 0. && (index as usize) < model.row_count() {
let index = index as usize;
if op == '=' {
model.set_row_data(index, rhs);
} else {

View file

@ -13,9 +13,11 @@ export component TestCase {
out property<bool> test: num_ints == 5 && operations == 10
&& hello_world == (["", "world"])[1] && empty_model.length == 0 && empty_model[45].a.length == 0
&& array_of_array[1][1] == 2 && [].length == 0 && ninth_int == 0;
&& array_of_array[1][1] == 2 && [].length == 0 && ninth_int == 0 && minus_int == 0 && decimal_check;
out property<int> third_int: ints[2];
out property<int> ninth_int: ints[8];
out property<int> minus_int: ints[-1];
out property<bool> decimal_check: ints[-0.5] == 1 && ints[2.9] == 3 && ints[-1.3] == 0;
out property<string> hello_world: [{t: "hello"}, {t: "world"}][1].t;
for xxx in (third_int == 0) ? ints : [] : Rectangle {}
@ -30,6 +32,7 @@ assert_eq(instance.get_num_ints(), 5);
assert_eq(instance.get_n(), 4);
assert_eq(instance.get_third_int(), 3);
assert_eq(instance.get_ninth_int(), 0);
assert_eq(instance.get_minus_int(), 0);
assert_eq(instance.get_test(), true);
auto model = std::make_shared<slint::VectorModel<int>>(std::vector<int>{1, 2, 3, 4, 5, 6, 7});
@ -41,6 +44,7 @@ assert_eq(instance.get_num_ints(), 8);
model->set_row_data(2, 100);
assert_eq(instance.get_third_int(), 100);
assert_eq(instance.get_ninth_int(), 0);
assert_eq(instance.get_minus_int(), 0);
model->push_back(9);
assert_eq(instance.get_ninth_int(), 9);
@ -58,6 +62,7 @@ assert_eq!(instance.get_num_ints(), 5);
assert_eq!(instance.get_n(), 4);
assert_eq!(instance.get_third_int(), 3);
assert_eq!(instance.get_ninth_int(), 0);
assert_eq!(instance.get_minus_int(), 0);
assert_eq!(instance.get_test(), true);
let model: std::rc::Rc<slint::VecModel<i32>> = std::rc::Rc::new(vec![1, 2, 3, 4, 5, 6, 7].into());
@ -69,6 +74,7 @@ assert_eq!(instance.get_num_ints(), 8);
model.set_row_data(2, 100);
assert_eq!(instance.get_third_int(), 100);
assert_eq!(instance.get_ninth_int(), 0);
assert_eq!(instance.get_minus_int(), 0);
model.push(9);
assert_eq!(instance.get_ninth_int(), 9);
@ -84,6 +90,8 @@ assert.equal(instance.num_ints, 5);
assert.equal(instance.n, 4);
assert.equal(instance.third_int, 3);
assert.equal(instance.ninth_int, 0);
assert.equal(instance.minus_int, 0);
assert(instance.test);
let model = new slintlib.ArrayModel([1, 2, 3, 4, 5, 6, 7]);
instance.ints = model;
@ -94,6 +102,7 @@ assert.equal(instance.num_ints, 8);
model.setRowData(2, 100);
assert.equal(instance.third_int, 100);
assert.equal(instance.ninth_int, 0);
assert.equal(instance.minus_int, 0);
model.push(9);
assert.equal(instance.ninth_int, 9);

View file

@ -17,6 +17,13 @@ TestCase := Rectangle {
if (first != 1) { return false; }
bar[0] = 42;
if (first != 42) { return false; }
bar[-1] = 18;
bar[3] = 89;
bar[-30.5] = 78;
bar[1.1] = 7552;
if (first != 42) { debug(first, "!= 42"); return false; }
bar[0.999] = 8;
if (first != 8) { debug(first, "!= 8"); return false; }
return true;
}