Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Expat/Expat.xs
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ startElement(void *userData, const char *name, const char **atts)

ENTER;
SAVETMPS;
SAVEFREESV(elname);

PUSHMARK(sp);
EXTEND(sp, attlim - atts + 2);
Expand All @@ -518,7 +519,7 @@ startElement(void *userData, const char *name, const char **atts)

attname = (do_ns ? gen_ns_name(*atts, cbv->nstab, cbv->nslst)
: newUTF8SVpv((char *) *atts, 0));

atts++;
PUSHs(sv_2mortal(attname));
if (*atts)
Expand All @@ -527,6 +528,7 @@ startElement(void *userData, const char *name, const char **atts)
PUTBACK;
call_sv(cbv->start_sv, G_DISCARD|G_VOID);

SvREFCNT_inc_simple_void(elname);
FREETMPS;
LEAVE;
}
Expand Down Expand Up @@ -555,6 +557,7 @@ endElement(void *userData, const char *name)
{
ENTER;
SAVETMPS;
SAVEFREESV(elname);

PUSHMARK(sp);
EXTEND(sp, 2);
Expand All @@ -563,6 +566,7 @@ endElement(void *userData, const char *name)
PUTBACK;
call_sv(cbv->end_sv, G_DISCARD|G_VOID);

SvREFCNT_inc_simple_void(elname);
FREETMPS;
LEAVE;
}
Expand Down
86 changes: 86 additions & 0 deletions t/handler_die.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/perl

# Test that handler exceptions don't leak SVs.
# When a Start or End handler dies, the parser should propagate
# the exception cleanly without leaking the element name SV.

use strict;
use warnings;

use Test::More tests => 6;
use XML::Parser;

my $xml = '<root><child>text</child></root>';

# Test 1-2: Start handler die propagates and parser remains usable
{
my $p = XML::Parser->new(
Handlers => {
Start => sub {
my ($expat, $el) = @_;
die "start handler died on $el" if $el eq 'child';
},
},
);

eval { $p->parse($xml) };
like($@, qr/start handler died on child/, 'Start handler die propagates');

# Parser should be reusable after exception
my $ok_xml = '<simple/>';
eval { $p->parse($ok_xml) };
is($@, '', 'parser reusable after Start handler die');
}

# Test 3-4: End handler die propagates and parser remains usable
{
my $p = XML::Parser->new(
Handlers => {
End => sub {
my ($expat, $el) = @_;
die "end handler died on $el" if $el eq 'child';
},
},
);

eval { $p->parse($xml) };
like($@, qr/end handler died on child/, 'End handler die propagates');

my $ok_xml = '<simple/>';
eval { $p->parse($ok_xml) };
is($@, '', 'parser reusable after End handler die');
}

# Test 5-6: Multiple parse cycles with dying handlers don't accumulate leaks
{
my $die_count = 0;
my $p = XML::Parser->new(
Handlers => {
Start => sub {
my ($expat, $el) = @_;
if ($el eq 'boom') {
$die_count++;
die "boom #$die_count";
}
},
},
);

my $boom_xml = '<root><boom/></root>';
for my $i (1..10) {
eval { $p->parse($boom_xml) };
}
is($die_count, 10, 'all 10 parse attempts triggered the handler');

# If SVs leaked, this would still work but would have accumulated
# leaked SVs. We can't easily check refcounts, but we verify the
# parser still works correctly after many exception cycles.
my $chardata = '';
$p = XML::Parser->new(
Handlers => {
Char => sub { $chardata .= $_[1] },
},
);
$p->parse('<r>ok</r>');
is($chardata, 'ok', 'fresh parser works after exception stress test');
}
Loading