Search code examples
perlmemory-leaksreferencegarbage-collectionxs

leak while blessing an empty reference inside the XS code


I am trying to do a XS equivalent of this:

package RefTestPP;
use strict;
use warnings;

sub new {
    my ($class, $self) = (@_, {});
    return bless $self, $class;
}

1;

This kind of constructor is supposed to "autovivify" it's base when called as RefTestPP->new(), or use the given reference as a base, like RefTestPP->new({ stuff => 123 });.

However, I am experiencing unexplainable leaks. Here's my RefTest.xs file:

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

MODULE = RefTest PACKAGE = RefTest

PROTOTYPES: ENABLE

void
new(sclass="RefTest", base=sv_2mortal(newRV_noinc((SV *) newHV())))
    const char *sclass
    SV *base
    PREINIT:
        HV *stash;
    PPCODE:
        stash = gv_stashpv(sclass, 0);
        ST(0) = sv_bless(base, stash);

        XSRETURN(1);

void
new_leaky(sclass="RefTest", base=newRV_noinc(sv_2mortal((SV *) newHV())))
    const char *sclass
    SV *base
    PREINIT:
        HV *stash;
    PPCODE:
        stash = gv_stashpv(sclass, 0);
        ST(0) = sv_bless(base, stash);

        XSRETURN(1);

And the RefTest.t file which detects the leak:

use strict;
use warnings;

use Devel::Leak;
use Test::More;
BEGIN { use_ok('RefTest') };

sub test_leak (&$;$) {
    my ($code, $descr, $maxleak) = (@_, 0);
    my $n1 = Devel::Leak::NoteSV(my $handle);
    $code->() for 1 .. 1000;
    my $n2 = Devel::Leak::CheckSV($handle);
    cmp_ok($n1 + $maxleak, '>=', $n2, $descr);
}

# OK
test_leak { my $ref = RefTest->new() or die }
    'first sv_2mortal(); then newRV_noinc()', 2;

# also OK
test_leak { my $ref = RefTest->new_leaky({}) or die }
    'first sv_2mortal(); then newRV_noinc(); pre-init base', 2;

# leaks!
test_leak { my $ref = RefTest->new_leaky() or die }
    'first newRV_noinc(); then sv_2mortal()', 2;

done_testing 4;

(the remaining files required for proper compilation are the defaults generated by h2xs -A -n RefTest)

The point is:

  1. base reference passed as a parameter to the constructor will never leak;
  2. base created inside XS code with sv_2mortal(newRV_noinc((SV *) newHV())) doesn't leak, also;
  3. base created with newRV_noinc(sv_2mortal((SV *) newHV())) will leak (and eventually cause the infamous Attempt to free unreferenced scalar: SV 0xdeadbeef message during global destruction).

Is there any reason for sv_2mortal(newRV_noinc(...)) to be different from newRV_noinc(sv_2mortal(...))? Am I simply doing it wrong?


Solution

  • sv_2mortal is a delayed refcount decrement. (It happens after the caller has a chance to get reference it or copy it.) In this post, I shall give the ref counts as if sv_2mortal decrements immediately, but will use a star ("*") to indicate this.


    The following code mortalises the reference:

    sv_2mortal(newRV_noinc((SV*)newHV()))
    

    So,

    1. The hash has REFCNT=1. This is good because the reference you create hold a reference to it.
    2. The reference has REFCNT=0*. This is good because nothing references it.

    The following code mortalises the hash:

    newRV_noinc(sv_2mortal((SV*)newHV()))
    

    So,

    1. The hash has REFCNT=0*. This is bad because the reference you create hold a reference to it. It will lead to a premature deallocation (which is why you get "Attempt to free unreferenced scalar" when the reference is freed).
    2. The reference has REFCNT=1. This is bad because nothing references it. This is a leak.