Skip to content

Conversation

@msteindorfer
Copy link
Member

@msteindorfer msteindorfer commented Jan 23, 2026

Context

Hash-collision nodes were able to underflow upon removals and store a single remaining (non-colliding) key. This violated the canonical representation assumption and led to erroneously skipping compaction. This PR adds invariant checking and ensures that invariants are honored during removals.

Triaging

With the information provided in #41, I was able to pinpoint the issue after attaching a debugger to the Rascal REPL and execute the provided equality comparison. It turned out that an invariant assertion was missing in the set-multi map (that was now inserted), although it was present in the set and map data types.

Once the invariant check was added, the property-based test suite flagged the invariant violation. Hence the coverage of generated inputs is sufficiently covers scenarios that elicit the behaviour reported in #41:

% ./gradlew test --tests "SetMultimap*"

> Task :test

SetMultimapPropertiesTestSuite$PersistentBidirectionalTrieSetMultimapTest > notContainedAfterInsertRemove FAILED
    java.lang.AssertionError at PropertyFalsified.java:52
        Caused by: java.lang.AssertionError at PersistentTrieSetMultimap.java:2227

SetMultimapPropertiesTestSuite$PersistentBidirectionalTrieSetMultimapTest > sizeAfterTransientInsertKeyValues FAILED
    java.lang.AssertionError at PersistentTrieSetMultimap.java:2227
        Caused by: java.lang.AssertionError at PersistentTrieSetMultimap.java:2227

SetMultimapPropertiesTestSuite$PersistentTrieSetMultimapTest > notContainedAfterInsertRemove FAILED
    java.lang.AssertionError at PropertyFalsified.java:52
        Caused by: java.lang.AssertionError at PersistentTrieSetMultimap.java:2227

SetMultimapPropertiesTestSuite$PersistentTrieSetMultimapTest > sizeAfterTransientInsertKeyValues FAILED
    java.lang.AssertionError at PersistentTrieSetMultimap.java:2227
        Caused by: java.lang.AssertionError at PersistentTrieSetMultimap.java:2227

37 tests completed, 4 failed

> Task :test FAILED

Validating

With the fix present in this PR, the property-based test suite does not show any invariant violations for set-multimap hash-collision nodes any more.

% ./gradlew test --tests "SetMultimap*"                                            

BUILD SUCCESSFUL in 1m 1s

Hash-collision nodes were able to underflow upon removals and store a
single remaining (non-colliding) key. This violated the canonical
representation assumption and led to erroneously skipping compaction.
@jurgenvinju
Copy link
Member

Oh that's quite elegant with the invariants! Writing the test manually proved very difficult for me ;-)

Copy link
Member

@jurgenvinju jurgenvinju left a comment

Choose a reason for hiding this comment

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

  • love the way the invariants can catch the issue from the existing specification tests
  • I knew some compaction was missing somewhere, but I would never have found this elegant solution.
    Thanks @msteindorfer

@jurgenvinju
Copy link
Member

We should probably add the gradle test run to the github actions. I've yet to figure out how to let mvn run all the tests.

@msteindorfer msteindorfer merged commit fb95f8a into usethesource:main Jan 23, 2026
1 check passed
@msteindorfer msteindorfer deleted the restore-set-multimap-canonical-representation-when-deleting-from-hash-collision-node branch January 23, 2026 11:56
jurgenvinju added a commit to usethesource/rascal that referenced this pull request Jan 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants