Branch: refs/heads/blead
  Home:   https://github.com/Perl/perl5
  Commit: 37d796a5a62dfb0753d9d8f0fc91886e05965c8b
      
https://github.com/Perl/perl5/commit/37d796a5a62dfb0753d9d8f0fc91886e05965c8b
  Author: Nicholas Clark <n...@ccl4.org>
  Date:   2021-12-18 (Sat, 18 Dec 2021)

  Changed paths:
    M hv.c

  Log Message:
  -----------
  Document the HV aux struct free/release logic in hv_undef_flags()

struct xpvhv_aux holds 4 pointers which can point to memory which needs
explicit release - document how hv_undef_flags() is directly responsible
for freeing xhv_mro_meta, indirectly responsible for freeing xhv_eiter,
and by implication can't free xhv_backreference or xhv_name_u.


  Commit: d9e6229a98412e8a44e463190ef148867e3e000a
      
https://github.com/Perl/perl5/commit/d9e6229a98412e8a44e463190ef148867e3e000a
  Author: Nicholas Clark <n...@ccl4.org>
  Date:   2021-12-18 (Sat, 18 Dec 2021)

  Changed paths:
    M hv.c

  Log Message:
  -----------
  Don't clear the flag bit SVf_OOK in hv_undef_flags()

Now that HvAUX(hv) is allocated as part of the HV's body, it's imperative
to retain the flag that indicates which size of body has been allocated.

Otherwise, if we clear the flag, sv_clear() believes that the body is the
smaller of the two possible sizes, and so returns it to the correct arena
for that body size. This doesn't result in out-of-bounds memory reads, or
even memory leaks (when full cleanup runs with PERL_DESTRUCT_LEVEL=2),
because all bodies are still correctly tracked. However, without this bug
fixed, code that takes the same hash and repeatedly

1) assigns entries
2) iterates the hash
3) undef %hash

will request one new (larger) hash body for each iteration of the loop,
but each time return it to the arena for the smaller hash bodies, with the
result that the interpreter will steadily request ever more memory for
arenas for larger hash bodies, until memory is exhausted.


  Commit: 7ab3ff6e3556756a0cabd4d4079485903f1aaabe
      
https://github.com/Perl/perl5/commit/7ab3ff6e3556756a0cabd4d4079485903f1aaabe
  Author: Nicholas Clark <n...@ccl4.org>
  Date:   2021-12-18 (Sat, 18 Dec 2021)

  Changed paths:
    M sv.c

  Log Message:
  -----------
  Return the "hv with aux" PVHV bodies to the correct arena

Move the assignments to SvFLAGS(sv) after the check for SvOOK(), and use
arena_index for the array index to PL_body_roots. Without both fixes,
del_body() would always be called with &PL_body_roots[SVt_PVHV], which is
wrong if the body was allocated from &PL_body_roots[HVAUX_ARENA_ROOT_IX].

PVHV body handling was changed recently by commit 94ee6ed79dbca73d:
    Split the XPVHV body into two variants "normal" and "with aux"

    Default to the smaller body, and switch to the larger body if we need to
    allocate a C<struct xpvhv_aux> (eg need an iterator).

    This restores the previous small size optimisation for hashes used as
    objects.

as part of changing where memory for C<struct xpvhv_aux> is allocated.

The new implementation uses two sizes of "bodies" for PVHVs - the default
body is unchanged, but when a hash needs a C<struct xpvhv_aux>, it now
swaps out the default body for a larger body with space for the struct.

(Previously it would reallocate the memory used for HvARRAY() to make space
for the struct there - an approach which requires more tracking of changed
pointers.)

That commit was subtly buggy in that on hash deallocation it didn't *return*
hash bodies to the correct arena. As-was, it would return both sizes of hash
body to the arena for the default (smaller) body. This was due to a
combination of two subtle bugs

1) the explicit reset of all SvFLAGS to SVf_BREAK would implicitly clear
   SVf_OOK. That flag bit set is needed to signal the different body size
2) The code must call del_body() with &PL_body_roots[arena_index], not
   &PL_body_roots[type]
   type is SVt_PVHV for both body sizes. arena_index is SVt_PVHV for the
   smaller (default) bodies, and HVAUX_ARENA_ROOT_IX for the larger bodies.

There were no memory errors or leaks (that would have been detectable by
tools such as ASAN or valgrind), but the bug meant that larger hash bodies
would never get re-used. So for code that was steady-state creating and
destroying hashes with iterators (or similar), instead of those bodies being
recycled via their correct arena, the interpreter would need to continuously
allocate new memory for that arena, whilst accumulating ever more unused
"smaller" bodies in the other arena.

I didn't spot this bug whilst reviewing commit 8461dd8ad90b2bce:
    don't overwrite the faked up type details for hv-with-aux

    CID 340472

which is in the same area of the code, but fixes a related problem.

Curiously when reporting CID 340738:

    Assigning value "SVt_IV" to "arena_index" here, but that stored value is
    overwritten before it can be used

Coverity also didn't spot that the code in question was unconditionally
unreachable, and warn about that, or modify its warning to note that.

(For the code prior to this commit SvOOK(sv) at that point could only ever
be false. This is obscured thanks to macros, but all these macros are
unconditionally defined to the same values, so static analysis should be
able to infer that they are actually constant, and reason accordingly.
To be fair, this is *hard*. But not impossible. Hence I infer that static
analysis tools still have room for improvement, and can make "mistakes".)

Ironically, defining PURIFY would hide this bug, because that disables the
use of arenas, instead allocating all bodies explicitly with malloc() and
releasing them with free(), and free() doesn't need to know the size of the
memory block, hence it wouldn't matter that the code had that size wrong.


  Commit: 5d73aca10c2e46b1f9ea1687f0f3e1a95a489653
      
https://github.com/Perl/perl5/commit/5d73aca10c2e46b1f9ea1687f0f3e1a95a489653
  Author: Nicholas Clark <n...@ccl4.org>
  Date:   2021-12-18 (Sat, 18 Dec 2021)

  Changed paths:
    M hv.c

  Log Message:
  -----------
  No need to Renew() HvARRAY() to the same size in S_hv_auxinit()

Fix a thinko/missed simplification that should have been part of
commit 53a41f9c2c0671d8:
    Inline the xhv_aux struct in the main hash body

    For now, memory for this structure is always allocated, even though it
    isn't always flagged as being present.

Prior to that commit the Renew() in S_hv_auxinit() had been needed because
the memory that HvARRAY() points to needed to be expanded from "just the
array of HE pointers" to "that, plus space for the xhv_aux struct.

With that commit, the xhv_aux struct was no longer stored in the same
memory block, so there was no need to reallocate storage. Hence the
Renew() should be completely removed. What that commit actually did was
neatly change the parameters to the Renew() call such that it reallocated
storage to the exact same size, which isn't wrong, but is makework. Oops.


Compare: https://github.com/Perl/perl5/compare/3d8a032cdae9...5d73aca10c2e

Reply via email to