Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Lib/test/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ class L(list): pass
with self.assertRaises(TypeError):
(3,) + L([1,2])

@unittest.skip("TODO: RUSTPYTHON; hang")
def test_equal_operator_modifying_operand(self):
# test fix for seg fault reported in bpo-38588 part 2.
class X:
Expand All @@ -254,7 +253,7 @@ def __eq__(self, other):
list4 = [1]
self.assertFalse(list3 == list4)

@unittest.skip("TODO: RUSTPYTHON; hang")
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: TypeError not raised
def test_lt_operator_modifying_operand(self):
# See gh-120298
class evil:
Expand Down
77 changes: 71 additions & 6 deletions crates/vm/src/builtins/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
class::PyClassImpl,
convert::ToPyObject,
function::{ArgSize, FuncArgs, OptionalArg, PyComparisonValue},
iter::PyExactSizeIterator,
protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods},
recursion::ReprGuard,
sequence::{MutObjectSequenceOp, OptionalRangeArgs, SequenceExt, SequenceMutExt},
Expand Down Expand Up @@ -547,11 +546,77 @@
return Ok(res.into());
}
let other = class_or_notimplemented!(Self, other);
let a = &*zelf.borrow_vec();
let b = &*other.borrow_vec();
a.iter()
.richcompare(b.iter(), op, vm)
.map(PyComparisonValue::Implemented)

let mut zlen = zelf.__len__();

Check warning on line 550 in crates/vm/src/builtins/list.rs

View workflow job for this annotation

GitHub Actions / Lint Rust & Python code

Unknown word (zlen)
let mut olen = other.__len__();
if matches!(op, PyComparisonOp::Eq | PyComparisonOp::Ne) && (zlen != olen) {

Check warning on line 552 in crates/vm/src/builtins/list.rs

View workflow job for this annotation

GitHub Actions / Lint Rust & Python code

Unknown word (zlen)
// Shortcut: if the lengths differ, the lists differ
return Ok(PyComparisonValue::Implemented(matches!(
op,
PyComparisonOp::Ne
)));
}

let mut zelements = zelf.borrow_vec().to_vec();

Check warning on line 560 in crates/vm/src/builtins/list.rs

View workflow job for this annotation

GitHub Actions / Lint Rust & Python code

Unknown word (zelements)
let mut oelements = other.borrow_vec().to_vec();

Check warning on line 561 in crates/vm/src/builtins/list.rs

View workflow job for this annotation

GitHub Actions / Lint Rust & Python code

Unknown word (oelements)

// Search for the first index where items are different.
let mut i = 0;
while i < zlen || i < olen {

Check warning on line 565 in crates/vm/src/builtins/list.rs

View workflow job for this annotation

GitHub Actions / Lint Rust & Python code

Unknown word (zlen)
if zelements.len() != zlen {

Check warning on line 566 in crates/vm/src/builtins/list.rs

View workflow job for this annotation

GitHub Actions / Lint Rust & Python code

Unknown word (zlen)

Check warning on line 566 in crates/vm/src/builtins/list.rs

View workflow job for this annotation

GitHub Actions / Lint Rust & Python code

Unknown word (zelements)
// Comparison mutated the list; refetch it.
zelements = zelf.borrow_vec().to_vec();

Check warning on line 568 in crates/vm/src/builtins/list.rs

View workflow job for this annotation

GitHub Actions / Lint Rust & Python code

Unknown word (zelements)
}

if oelements.len() != olen {

Check warning on line 571 in crates/vm/src/builtins/list.rs

View workflow job for this annotation

GitHub Actions / Lint Rust & Python code

Unknown word (oelements)
// Comparison mutated the list; refetch it.
oelements = other.borrow_vec().to_vec();

Check warning on line 573 in crates/vm/src/builtins/list.rs

View workflow job for this annotation

GitHub Actions / Lint Rust & Python code

Unknown word (oelements)
}
Comment on lines +560 to +574
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Length-preserving mutations still compare stale items.

The refresh logic only re-clones when len() changes. If an element comparison mutates list[i + 1] in place, the next iteration still reads the old zelements / oelements snapshots, so equality and ordering can be decided on objects that are no longer in either list. If this path is meant to be mutation-aware, reload the current items each iteration instead of gating refreshes on length changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/builtins/list.rs` around lines 560 - 574, The comparison loop
using zelements and oelements only refreshes snapshots when lengths (zlen/olen)
change, which allows in-place mutations of elements to leave stale values;
update the loop in the equality/comparison routine (the block that uses
zelf.borrow_vec(), other.borrow_vec(), zelements, oelements, zlen, olen and the
index i) so that each iteration reads the current element(s) directly from the
live lists (e.g., call zelf.borrow_vec() and other.borrow_vec() and index into
them for the items being compared) or otherwise refresh the snapshots for the
specific indices per-iteration instead of gating refreshes on length changes,
ensuring comparisons always use the current elements.


let zitem = &zelements[i];
let oitem = &oelements[i];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use && for the prefix scan bounds.

Line 565 currently enters the loop when only one list still has an element, but Lines 576-577 index both snapshots unconditionally. A prefix case like [1] < [1, 2] reaches i == 1 and panics before the size-based fallback at Line 592.

🛠️ Minimal fix
-        while i < zlen || i < olen {
+        while i < zlen && i < olen {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
while i < zlen || i < olen {
if zelements.len() != zlen {
// Comparison mutated the list; refetch it.
zelements = zelf.borrow_vec().to_vec();
}
if oelements.len() != olen {
// Comparison mutated the list; refetch it.
oelements = other.borrow_vec().to_vec();
}
let zitem = &zelements[i];
let oitem = &oelements[i];
while i < zlen && i < olen {
if zelements.len() != zlen {
// Comparison mutated the list; refetch it.
zelements = zelf.borrow_vec().to_vec();
}
if oelements.len() != olen {
// Comparison mutated the list; refetch it.
oelements = other.borrow_vec().to_vec();
}
let zitem = &zelements[i];
let oitem = &oelements[i];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/builtins/list.rs` around lines 565 - 577, The loop condition
uses "while i < zlen || i < olen" which allows indexing both snapshots when only
one has an element; change it to "while i < zlen && i < olen" so the prefix scan
only runs while both zelements and oelements have an index i; adjust no other
logic — keep the existing refetch of zelements/oelements and the subsequent
size-based fallback unchanged (identify zelements, oelements, zlen, olen, and i
in the loop to locate the change).


if !vm.bool_eq(zitem, oitem)? {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use the correct comparator in both phases.

Line 579 should keep the identity fast path via vm.identical_or_equal, otherwise identical self-inequal objects stop matching. And Lines 613-619 ignore op entirely, so ordered comparisons like [1] < [2] devolve to equality and return the wrong result.

🛠️ Minimal fix
-            if !vm.bool_eq(zitem, oitem)? {
+            if !vm.identical_or_equal(zitem, oitem)? {
                 break;
             }
@@
-        let res = vm.identical_or_equal(zitem, oitem)?;
+        let res = zitem.rich_compare_bool(oitem, op, vm)?;
         Ok(PyComparisonValue::Implemented(res))

Also applies to: 613-619

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/builtins/list.rs` at line 579, The code uses vm.bool_eq in the
identity fast-path and later ignores the requested comparator `op`; change the
first-phase check to use vm.identical_or_equal(zitem, oitem) instead of
vm.bool_eq so identical-but-unequal objects match the fast path, and in the
second phase replace the equality-only check with the proper comparator call
(e.g., vm.bool_op(op, zitem, oitem) or the existing VM method that evaluates
`op`) so the ordered comparisons (using `op`) are honored; update the branches
that reference zitem, oitem and op accordingly.

break;
}

// Refetch list sizes as calling the comparison may mutate the list.
zlen = zelf.__len__();
olen = other.__len__();

i += 1;
}

zlen = zelf.__len__();
olen = other.__len__();
if i >= zlen || i >= olen {
// No more items to compare -- compare sizes
let res = match op {
PyComparisonOp::Eq => zlen == olen,
PyComparisonOp::Ne => zlen != olen,
PyComparisonOp::Lt => zlen < olen,
PyComparisonOp::Le => zlen <= olen,
PyComparisonOp::Gt => zlen > olen,
PyComparisonOp::Ge => zlen >= olen,
};

return Ok(PyComparisonValue::Implemented(res));
};

// We have an item that differs -- shortcuts for EQ/NE.
match op {
PyComparisonOp::Eq => return Ok(PyComparisonValue::Implemented(false)),
PyComparisonOp::Ne => return Ok(PyComparisonValue::Implemented(true)),
_ => {}
}

// Compare the final item again using the proper operator.
zelements = zelf.borrow_vec().to_vec();
oelements = other.borrow_vec().to_vec();
let zitem = &zelements[i];
let oitem = &oelements[i];
let res = vm.identical_or_equal(zitem, oitem)?;
Ok(PyComparisonValue::Implemented(res))
}
}

Expand Down
Loading