Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix a potential bug when all elements are individually deleted from a hash #1410

Merged
merged 1 commit into from Dec 27, 2020

Conversation

nwc10
Copy link
Contributor

@nwc10 nwc10 commented Dec 26, 2020

Two optimisations were coming together in an unforeseen way, which could
cause an assertion failure on a debugging build.

There's a space optimisation for empty hashes which only allocates the
control structure, and flags this with both cur_items and max_items
set to 0.

To avoid repeated checks in the insert code for probe distance overflows,
if an insert detects that it has caused any probe distance to hit the limit,
it signals that the next insert will need to reallocate by setting
max_items to 0.

What I'd not realised was that if a hash has a series of inserts that chance
to end with that state of "next insert needs to reallocate", but there are
no more inserts, then max_items remains set to 0 to flag this, and is
never "cleared". This usually doesn't matter, but in the specific unusual
case that the code then systematically deletes all entries in the hash
(without ever making any more inserts) that the count cur_items will
return to 0, and hence the special case "empty hash" optimisation flag
state is set, but with a regular allocation.

This doesn't happen reliably (it depends on hash randomisation), but could
sometimes be hit by t/spec/integration/advent2012-day14.t

To trip the assertion, both assertions and HASH_DEBUG_ITER need to be
enabled. If these aren't hit (and abort the process), I think that the
upshot of hitting this bug would be that part of a larger section of memory
would be returned to the FSA pool for a smaller bin size. Given that the
larger section of memory was still deemed allocated from its bin, and the
FSA never returns blocks of bins to free() (until global shutdown), I don't
think that this could cause any memory corruption.

if (control->max_items == 0
&& control->cur_items < control->max_probe_distance) {
/* Unlike MVMStrHashTable, resetting this here is merely an
* optimisation (to avoid an a doubling), as we don't have
Copy link
Contributor

Choose a reason for hiding this comment

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

"an a" -> "a"?

* The really conservative approach would only be to reset
* if the hash is about to hit zero items.
*
* But I believe that that the earliest we can be *sure*
Copy link
Contributor

Choose a reason for hiding this comment

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

"that that" -> "that"

… hash.

Two optimisations were coming together in an unforeseen way, which could
cause an assertion failure on a debugging build.

There's a space optimisation for empty hashes which only allocates the
control structure, and flags this with both `cur_items` and `max_items`
set to 0.

To avoid repeated checks in the insert code for probe distance overflows,
if an insert detects that it has caused any probe distance to hit the limit,
it signals that the next insert will need to reallocate by setting
`max_items` to 0.

What I'd not realised was that if a hash has a series of inserts that chance
to end with that state of "next insert needs to reallocate", but there are
no more inserts, then `max_items` remains set to 0 to flag this, and is
never "cleared". This usually doesn't matter, but in the specific unusual
case that the code then systematically deletes all entries in the hash
(without ever making any more inserts) that the count `cur_items` will
return to 0, and hence the special case "empty hash" optimisation flag
state is set, but with a regular allocation.

This doesn't happen reliably (it depends on hash randomisation), but could
sometimes be hit by t/spec/integration/advent2012-day14.t

To trip the assertion, both assertions and HASH_DEBUG_ITER need to be
enabled. If these aren't hit (and abort the process), I think that the
upshot of hitting this bug would be that part of a larger section of memory
would be returned to the FSA pool for a smaller bin size. Given that the
larger section of memory was still deemed allocated from its bin, and the
FSA never returns blocks of bins to free() (until global shutdown), I don't
think that this could cause any memory corruption.

(Two errors in the comments spotted by MasterDuke)
@nwc10 nwc10 merged commit b8663f5 into master Dec 27, 2020
@nwc10 nwc10 deleted the hash-delete-corner-case branch December 27, 2020 09:37
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.

None yet

2 participants