Skip to content

SV refcount leak in pp_exec_rset() when returned SYS_REFCURSOR is not opened #217

@djzort

Description

@djzort

Description

pp_exec_rset() in dbdimp.c leaks the refcount of the user's bound inout SV when a returned SYS_REFCURSOR was never opened (the OCI_STMT_STATE_INITIALIZED path added for RT 82663).

Root Cause

When bind_param_inout is called for a cursor output parameter, dbd_bind_ph() stores the user's live Perl variable in phs->sv with an incremented refcount:

phs->sv = SvREFCNT_inc(newvalue);   /* point to live var */

In pp_exec_rset() post-execute, when the cursor was never opened (stmt_state == OCI_STMT_STATE_INITIALIZED), the code does:

phs->sv = newSV(0);                 /* undef */

This overwrites the phs->sv pointer without decrementing the refcount on the original SV. The user's variable (e.g., $cursor from bind_param_inout(':cursor', \$cursor, ...)) leaks its refcount and is never freed.

Additionally, replacing the pointer breaks the inout binding — if the same statement is re-executed, the cursor handle would be written into the orphaned newSV(0) instead of the user's original variable.

Impact

  • Memory leak: one SV leaked per execute when the cursor is not opened
  • Repeated re-execution of the same prepared statement with cursor output params compounds the leak
  • The user's bound variable becomes disconnected from the placeholder after the first such execute

Suggested Fix

Instead of replacing the phs->sv pointer, set the existing SV to undef:

if (phs->sv && phs->sv != &PL_sv_undef)
    (void)SvOK_off(phs->sv);

This preserves the inout binding, properly communicates "no cursor returned" to the caller, and avoids the refcount leak.

Reproducer

my $dbh = DBI->connect(...);
my $sth = $dbh->prepare(q{
    BEGIN
        IF :flag = 0 THEN
            :cursor := NULL;  -- cursor never opened
        ELSE
            OPEN :cursor FOR SELECT 1 FROM dual;
        END IF;
    END;
});

my $cursor;
$sth->bind_param(':flag', 0);
$sth->bind_param_inout(':cursor', \$cursor, 0, { ora_type => ORA_RSET });

# Each execute leaks one SV refcount
for (1..10000) {
    $sth->execute;
}

Related

  • RT 82663 (the original fix that introduced this code path)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions