From 3ce3dcd9d07ae1c7dc2ed63c1b826be9ff13a7c7 Mon Sep 17 00:00:00 2001 From: Father Chrysostomos Date: Tue, 6 Nov 2012 10:00:12 -0800 Subject: [PATCH] Stop eval "qq'\$\0foo'" from leaking MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit If the dollar sign in a double-quoted string is followed by a null, then scan_ident gets confused, as it uses the nullness to keep track of whether it has found an identifier. In this case it treats $\0 as a punctuation variable but then loses track of that fact and thinks it has found the end of the string. It’s complicated, but the end result is that the sequence of tokens emitted for eval "qq'$\0foo'" would be the following: stringify ( $ . "foo" ) instead of this: stringify ( $ . "foo" ) But the missing identifier after the dollar sign results in a syn- tax error that prevents yylex from having a chance to emit the "foo", which by that time it has put on the forced token stack. There are cases in which the CV that owns the ops on the forced token stack can be freed while the ops are still on that stack and will still be fed to the parser, so we treat the forced token stack like the savestack, and do not allow ops thereon to be freed when their CV is freed, to avoid feeding freed ops to the parser. In this case, it means that the ops on the stack are never freed, resulting in a memory leak. Whether scan_ident needs to be fixed (or ‘fixed’) I don’t know. I do know that when the parser is freed any remaining forced tokens should also be freed. Even if this leak could be fixed some other way, it would still be a good idea for parser_free to check for forced tokens that need to be cleaned up. --- t/op/svleak.t | 3 ++- toke.c | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/t/op/svleak.t b/t/op/svleak.t index b74967f..96bf092 100644 --- a/t/op/svleak.t +++ b/t/op/svleak.t @@ -15,7 +15,7 @@ BEGIN { use Config; -plan tests => 55; +plan tests => 56; # run some code N times. If the number of SVs at the end of loop N is # greater than (N-1)*delta at the end of loop 1, we've got a leak @@ -205,6 +205,7 @@ eleak(2, 0, 'no warnings; 2 2;BEGIN{}', 'BEGIN block after syntax error'); eleak(2, 0, 'no warnings; 2@!{', 'implicit "use Errno" after syntax error'); } +eleak(2, 0, "\"\$\0\356\"", 'qq containing $ something'); # [perl #114764] Attributes leak scalars leak(2, 0, sub { eval 'my $x : shared' }, 'my $x :shared used to leak'); diff --git a/toke.c b/toke.c index 0d00d9e..f38381c 100644 --- a/toke.c +++ b/toke.c @@ -773,6 +773,12 @@ Perl_lex_start(pTHX_ SV *line, PerlIO *rsfp, U32 flags) void Perl_parser_free(pTHX_ const yy_parser *parser) { +#ifdef PERL_MAD + I32 nexttoke = parser->lasttoke; +#else + I32 nexttoke = parser->nexttoke; +#endif + PERL_ARGS_ASSERT_PARSER_FREE; PL_curcop = parser->saved_curcop; @@ -786,6 +792,16 @@ Perl_parser_free(pTHX_ const yy_parser *parser) SvREFCNT_dec(parser->rsfp_filters); SvREFCNT_dec(parser->lex_stuff); SvREFCNT_dec(parser->sublex_info.repl); + while (nexttoke--) { +#ifdef PERL_MAD + if (S_is_opval_token(parser->nexttoke[nexttoke].next_type + & 0xffff)) + op_free(parser->nexttoke[nexttoke].next_val.opval); +#else + if (S_is_opval_token(parser->nexttype[nexttoke] & 0xffff)) + op_free(parser->nextval[nexttoke].opval); +#endif + } Safefree(parser->lex_brackstack); Safefree(parser->lex_casestack); -- 2.7.4