In perl.git, the branch blead has been updated <https://perl5.git.perl.org/perl.git/commitdiff/e40cca748f05c81e1929ed625407afbe7c79d4dd?hp=6256cf2c2cee30277e34d38416070f9ec671c989>
- Log ----------------------------------------------------------------- commit e40cca748f05c81e1929ed625407afbe7c79d4dd Author: David Mitchell <da...@iabyn.com> Date: Mon Mar 25 20:13:30 2019 +0000 fix a leak with indented heredocs With something like |print <<~EOF; | some data | EOF it croaks (as it should) with "Indentation ... doesn't match delimiter", but in that case it leaks the recently malloc()d 'indent' buffer. The fix is simple. I've also fixed by code inspection where the code does 'goto interminable', although I didn't try to reproduce the conditions where the goto might occur. commit 3c2fc529eb398c7f2f197abd633720bd6bb627fc Author: David Mitchell <da...@iabyn.com> Date: Mon Mar 25 19:44:57 2019 +0000 reformat S_scan_heredoc() The indentation was inconsistent and confusing. Reindent, add blank lines where appropriate, and change this code comment: "(Closing '}' here to balance" to '<<}', since vim is far too clever for its own good these days in terms of using '%' to bounce between brace pairs. Should be no functional changes. commit d44742f81c5295e9a7ce437cb55c1b56f4c7fc68 Author: David Mitchell <da...@iabyn.com> Date: Mon Mar 25 19:14:43 2019 +0000 S_scan_heredoc(): add cosmetic braces add braces round a single-statement 'if' clause which contains a while loop and spans several lines. Should be functionally equivalent, but less visually confusing. commit 1113f30d91f662c876a07b357666f02f04a30a75 Author: David Mitchell <da...@iabyn.com> Date: Mon Mar 25 17:18:58 2019 +0000 fix leak with local ${^WARNING_BITS} = ... When restoring the old value, need to free the current value first. Can be reproduced with { local ${^WARNING_BITS} = 'swit'; } when run under ASan or similar. An equivalent test already exists in t/op/leaky-magic.t. ----------------------------------------------------------------------- Summary of changes: mg.c | 2 + toke.c | 323 ++++++++++++++++++++++++++++++++++++----------------------------- 2 files changed, 182 insertions(+), 143 deletions(-) diff --git a/mg.c b/mg.c index b022d63442..320e2d39bb 100644 --- a/mg.c +++ b/mg.c @@ -2916,6 +2916,8 @@ Perl_magic_set(pTHX_ SV *sv, MAGIC *mg) else if (strEQ(mg->mg_ptr+1, "ARNING_BITS")) { if ( ! (PL_dowarn & G_WARN_ALL_MASK)) { if (!SvPOK(sv)) { + if (!specialWARN(PL_compiling.cop_warnings)) + PerlMemShared_free(PL_compiling.cop_warnings); PL_compiling.cop_warnings = pWARN_STD; break; } diff --git a/toke.c b/toke.c index 9bed338ecc..8b0c655893 100644 --- a/toke.c +++ b/toke.c @@ -10026,12 +10026,15 @@ S_scan_heredoc(pTHX_ char *s) e = PL_tokenbuf + sizeof PL_tokenbuf - 1; *PL_tokenbuf = '\n'; peek = s; + if (*peek == '~') { indented = TRUE; peek++; s++; } + while (SPACE_OR_TAB(*peek)) peek++; + if (*peek == '`' || *peek == '\'' || *peek =='"') { s = peek; term = *s++; @@ -10047,19 +10050,25 @@ S_scan_heredoc(pTHX_ char *s) s++, term = '\''; else term = '"'; + if (! isWORDCHAR_lazy_if_safe(s, PL_bufend, UTF)) Perl_croak(aTHX_ "Use of bare << to mean <<\"\" is forbidden"); + peek = s; + while (isWORDCHAR_lazy_if_safe(peek, PL_bufend, UTF)) { peek += UTF ? UTF8SKIP(peek) : 1; } + len = (peek - s >= e - d) ? (e - d) : (peek - s); Copy(s, d, len, char); s += len; d += len; } + if (d >= PL_tokenbuf + sizeof PL_tokenbuf - 1) Perl_croak(aTHX_ "Delimiter for here document is too long"); + *d++ = '\n'; *d = '\0'; len = d - PL_tokenbuf; @@ -10102,6 +10111,7 @@ S_scan_heredoc(pTHX_ char *s) PL_multi_start = origline + 1 + PL_parser->herelines; PL_multi_open = PL_multi_close = '<'; + /* inside a string eval or quote-like operator */ if (!infile || PL_lex_inwhat) { SV *linestr; @@ -10112,43 +10122,47 @@ S_scan_heredoc(pTHX_ char *s) entered. But we need them set here. */ shared->ls_bufptr = s; shared->ls_linestr = PL_linestr; - if (PL_lex_inwhat) - /* Look for a newline. If the current buffer does not have one, - peek into the line buffer of the parent lexing scope, going - up as many levels as necessary to find one with a newline - after bufptr. - */ - while (!(s = (char *)memchr( - (void *)shared->ls_bufptr, '\n', - SvEND(shared->ls_linestr)-shared->ls_bufptr - ))) { - shared = shared->ls_prev; - /* shared is only null if we have gone beyond the outermost - lexing scope. In a file, we will have broken out of the - loop in the previous iteration. In an eval, the string buf- - fer ends with "\n;", so the while condition above will have - evaluated to false. So shared can never be null. Or so you - might think. Odd syntax errors like s;@{<<; can gobble up - the implicit semicolon at the end of a flie, causing the - file handle to be closed even when we are not in a string - eval. So shared may be null in that case. - (Closing '}' here to balance the earlier open brace for - editors that look for matched pairs.) */ - if (UNLIKELY(!shared)) - goto interminable; - /* A LEXSHARED struct with a null ls_prev pointer is the outer- - most lexing scope. In a file, shared->ls_linestr at that - level is just one line, so there is no body to steal. */ - if (infile && !shared->ls_prev) { - s = olds; - goto streaming; - } - } + + if (PL_lex_inwhat) { + /* Look for a newline. If the current buffer does not have one, + peek into the line buffer of the parent lexing scope, going + up as many levels as necessary to find one with a newline + after bufptr. + */ + while (!(s = (char *)memchr( + (void *)shared->ls_bufptr, '\n', + SvEND(shared->ls_linestr)-shared->ls_bufptr + ))) + { + shared = shared->ls_prev; + /* shared is only null if we have gone beyond the outermost + lexing scope. In a file, we will have broken out of the + loop in the previous iteration. In an eval, the string buf- + fer ends with "\n;", so the while condition above will have + evaluated to false. So shared can never be null. Or so you + might think. Odd syntax errors like s;@{<<; can gobble up + the implicit semicolon at the end of a flie, causing the + file handle to be closed even when we are not in a string + eval. So shared may be null in that case. + (Closing '>>}' here to balance the earlier open brace for + editors that look for matched pairs.) */ + if (UNLIKELY(!shared)) + goto interminable; + /* A LEXSHARED struct with a null ls_prev pointer is the outer- + most lexing scope. In a file, shared->ls_linestr at that + level is just one line, so there is no body to steal. */ + if (infile && !shared->ls_prev) { + s = olds; + goto streaming; + } + } + } else { /* eval or we've already hit EOF */ s = (char*)memchr((void*)s, '\n', PL_bufend - s); if (!s) goto interminable; } + linestr = shared->ls_linestr; bufend = SvEND(linestr); d = s; @@ -10168,7 +10182,6 @@ S_scan_heredoc(pTHX_ char *s) if (! SPACE_OR_TAB(*backup)) { break; } - indent_len++; } @@ -10183,7 +10196,8 @@ S_scan_heredoc(pTHX_ char *s) } } } - } else { + } + else { while (s < bufend - len + 1 && memNE(s,PL_tokenbuf,len) ) { @@ -10195,6 +10209,7 @@ S_scan_heredoc(pTHX_ char *s) if (s >= bufend - len + 1) { goto interminable; } + sv_setpvn(tmpstr,d+1,s-d); s += len - 1; /* the preceding stmt passes a newline */ @@ -10217,6 +10232,7 @@ S_scan_heredoc(pTHX_ char *s) bufend - shared->re_eval_start); shared->re_eval_start -= s-d; } + if (cxstack_ix >= 0 && CxTYPE(cx) == CXt_EVAL && CxOLD_OP_TYPE(cx) == OP_ENTEREVAL @@ -10225,126 +10241,139 @@ S_scan_heredoc(pTHX_ char *s) cx->blk_eval.cur_text = newSVsv(linestr); cx->blk_u16 |= 0x40; /* indicate cur_text is ref counted */ } + /* Copy everything from s onwards back to d. */ Move(s,d,bufend-s + 1,char); SvCUR_set(linestr, SvCUR(linestr) - (s-d)); /* Setting PL_bufend only applies when we have not dug deeper into other scopes, because sublex_done sets PL_bufend to SvEND(PL_linestr). */ - if (shared == PL_parser->lex_shared) PL_bufend = SvEND(linestr); + if (shared == PL_parser->lex_shared) + PL_bufend = SvEND(linestr); s = olds; } - else - { - SV *linestr_save; - char *oldbufptr_save; - char *oldoldbufptr_save; - streaming: - SvPVCLEAR(tmpstr); /* avoid "uninitialized" warning */ - term = PL_tokenbuf[1]; - len--; - linestr_save = PL_linestr; /* must restore this afterwards */ - d = s; /* and this */ - oldbufptr_save = PL_oldbufptr; - oldoldbufptr_save = PL_oldoldbufptr; - PL_linestr = newSVpvs(""); - PL_bufend = SvPVX(PL_linestr); - while (1) { - PL_bufptr = PL_bufend; - CopLINE_set(PL_curcop, - origline + 1 + PL_parser->herelines); - if (!lex_next_chunk(LEX_NO_TERM) - && (!SvCUR(tmpstr) || SvEND(tmpstr)[-1] != '\n')) { - /* Simply freeing linestr_save might seem simpler here, as it - does not matter what PL_linestr points to, since we are - about to croak; but in a quote-like op, linestr_save - will have been prospectively freed already, via - SAVEFREESV(PL_linestr) in sublex_push, so it’s easier to - restore PL_linestr. */ - SvREFCNT_dec_NN(PL_linestr); - PL_linestr = linestr_save; - PL_oldbufptr = oldbufptr_save; - PL_oldoldbufptr = oldoldbufptr_save; - goto interminable; - } - CopLINE_set(PL_curcop, origline); - if (!SvCUR(PL_linestr) || PL_bufend[-1] != '\n') { - s = lex_grow_linestr(SvLEN(PL_linestr) + 3); - /* ^That should be enough to avoid this needing to grow: */ - sv_catpvs(PL_linestr, "\n\0"); - assert(s == SvPVX(PL_linestr)); - PL_bufend = SvEND(PL_linestr); - } - s = PL_bufptr; - PL_parser->herelines++; - PL_last_lop = PL_last_uni = NULL; + else { + SV *linestr_save; + char *oldbufptr_save; + char *oldoldbufptr_save; + streaming: + SvPVCLEAR(tmpstr); /* avoid "uninitialized" warning */ + term = PL_tokenbuf[1]; + len--; + linestr_save = PL_linestr; /* must restore this afterwards */ + d = s; /* and this */ + oldbufptr_save = PL_oldbufptr; + oldoldbufptr_save = PL_oldoldbufptr; + PL_linestr = newSVpvs(""); + PL_bufend = SvPVX(PL_linestr); + + while (1) { + PL_bufptr = PL_bufend; + CopLINE_set(PL_curcop, + origline + 1 + PL_parser->herelines); + + if ( !lex_next_chunk(LEX_NO_TERM) + && (!SvCUR(tmpstr) || SvEND(tmpstr)[-1] != '\n')) + { + /* Simply freeing linestr_save might seem simpler here, as it + does not matter what PL_linestr points to, since we are + about to croak; but in a quote-like op, linestr_save + will have been prospectively freed already, via + SAVEFREESV(PL_linestr) in sublex_push, so it’s easier to + restore PL_linestr. */ + SvREFCNT_dec_NN(PL_linestr); + PL_linestr = linestr_save; + PL_oldbufptr = oldbufptr_save; + PL_oldoldbufptr = oldoldbufptr_save; + goto interminable; + } + + CopLINE_set(PL_curcop, origline); + + if (!SvCUR(PL_linestr) || PL_bufend[-1] != '\n') { + s = lex_grow_linestr(SvLEN(PL_linestr) + 3); + /* ^That should be enough to avoid this needing to grow: */ + sv_catpvs(PL_linestr, "\n\0"); + assert(s == SvPVX(PL_linestr)); + PL_bufend = SvEND(PL_linestr); + } + + s = PL_bufptr; + PL_parser->herelines++; + PL_last_lop = PL_last_uni = NULL; + #ifndef PERL_STRICT_CR - if (PL_bufend - PL_linestart >= 2) { - if ( (PL_bufend[-2] == '\r' && PL_bufend[-1] == '\n') - || (PL_bufend[-2] == '\n' && PL_bufend[-1] == '\r')) - { - PL_bufend[-2] = '\n'; - PL_bufend--; - SvCUR_set(PL_linestr, PL_bufend - SvPVX_const(PL_linestr)); - } - else if (PL_bufend[-1] == '\r') - PL_bufend[-1] = '\n'; - } - else if (PL_bufend - PL_linestart == 1 && PL_bufend[-1] == '\r') - PL_bufend[-1] = '\n'; + if (PL_bufend - PL_linestart >= 2) { + if ( (PL_bufend[-2] == '\r' && PL_bufend[-1] == '\n') + || (PL_bufend[-2] == '\n' && PL_bufend[-1] == '\r')) + { + PL_bufend[-2] = '\n'; + PL_bufend--; + SvCUR_set(PL_linestr, PL_bufend - SvPVX_const(PL_linestr)); + } + else if (PL_bufend[-1] == '\r') + PL_bufend[-1] = '\n'; + } + else if (PL_bufend - PL_linestart == 1 && PL_bufend[-1] == '\r') + PL_bufend[-1] = '\n'; #endif - if (indented && (PL_bufend-s) >= len) { - char * found = ninstr(s, PL_bufend, (PL_tokenbuf + 1), (PL_tokenbuf +1 + len)); - if (found) { - char *backup = found; - indent_len = 0; + if (indented && (PL_bufend-s) >= len) { + char * found = ninstr(s, PL_bufend, (PL_tokenbuf + 1), (PL_tokenbuf +1 + len)); - /* Only valid if it's preceded by whitespace only */ - while (backup != s && --backup >= s) { - if (! SPACE_OR_TAB(*backup)) { - break; - } - indent_len++; - } + if (found) { + char *backup = found; + indent_len = 0; - /* All whitespace or none! */ - if (backup == found || SPACE_OR_TAB(*backup)) { - Newx(indent, indent_len + 1, char); - memcpy(indent, backup, indent_len); - indent[indent_len] = 0; - SvREFCNT_dec(PL_linestr); - PL_linestr = linestr_save; - PL_linestart = SvPVX(linestr_save); - PL_bufend = SvPVX(PL_linestr) + SvCUR(PL_linestr); - PL_oldbufptr = oldbufptr_save; - PL_oldoldbufptr = oldoldbufptr_save; - s = d; - break; - } - } + /* Only valid if it's preceded by whitespace only */ + while (backup != s && --backup >= s) { + if (! SPACE_OR_TAB(*backup)) { + break; + } + indent_len++; + } - /* Didn't find it */ - sv_catsv(tmpstr,PL_linestr); - } else { - if (*s == term && PL_bufend-s >= len - && memEQ(s,PL_tokenbuf + 1,len)) - { - SvREFCNT_dec(PL_linestr); - PL_linestr = linestr_save; - PL_linestart = SvPVX(linestr_save); - PL_bufend = SvPVX(PL_linestr) + SvCUR(PL_linestr); - PL_oldbufptr = oldbufptr_save; - PL_oldoldbufptr = oldoldbufptr_save; - s = d; - break; - } else { - sv_catsv(tmpstr,PL_linestr); - } - } - } + /* All whitespace or none! */ + if (backup == found || SPACE_OR_TAB(*backup)) { + Newx(indent, indent_len + 1, char); + memcpy(indent, backup, indent_len); + indent[indent_len] = 0; + SvREFCNT_dec(PL_linestr); + PL_linestr = linestr_save; + PL_linestart = SvPVX(linestr_save); + PL_bufend = SvPVX(PL_linestr) + SvCUR(PL_linestr); + PL_oldbufptr = oldbufptr_save; + PL_oldoldbufptr = oldoldbufptr_save; + s = d; + break; + } + } + + /* Didn't find it */ + sv_catsv(tmpstr,PL_linestr); + } + else { + if (*s == term && PL_bufend-s >= len + && memEQ(s,PL_tokenbuf + 1,len)) + { + SvREFCNT_dec(PL_linestr); + PL_linestr = linestr_save; + PL_linestart = SvPVX(linestr_save); + PL_bufend = SvPVX(PL_linestr) + SvCUR(PL_linestr); + PL_oldbufptr = oldbufptr_save; + PL_oldoldbufptr = oldoldbufptr_save; + s = d; + break; + } + else { + sv_catsv(tmpstr,PL_linestr); + } + } + } /* while (1) */ } + PL_multi_end = origline + PL_parser->herelines; + if (indented && indent) { STRLEN linecount = 1; STRLEN herelen = SvCUR(tmpstr); @@ -10362,50 +10391,58 @@ S_scan_heredoc(pTHX_ char *s) linecount++; /* Found our indentation? Strip it */ - } else if (se - ss >= indent_len + } + else if (se - ss >= indent_len && memEQ(ss, indent, indent_len)) { STRLEN le = 0; - ss += indent_len; while ((ss + le) < se && *(ss + le) != '\n') le++; sv_catpvn(newstr, ss, le); - ss += le; /* Line doesn't begin with our indentation? Croak */ - } else { + } + else { + Safefree(indent); Perl_croak(aTHX_ "Indentation on line %d of here-doc doesn't match delimiter", (int)linecount ); } - } + } /* while */ + /* avoid sv_setsv() as we dont wan't to COW here */ sv_setpvn(tmpstr,SvPVX(newstr),SvCUR(newstr)); Safefree(indent); SvREFCNT_dec_NN(newstr); } + if (SvCUR(tmpstr) + 5 < SvLEN(tmpstr)) { SvPV_shrink_to_cur(tmpstr); } + if (!IN_BYTES) { if (UTF && is_utf8_string((U8*)SvPVX_const(tmpstr), SvCUR(tmpstr))) SvUTF8_on(tmpstr); } + PL_lex_stuff = tmpstr; pl_yylval.ival = op_type; return s; interminable: + if (indent) + Safefree(indent); SvREFCNT_dec(tmpstr); CopLINE_set(PL_curcop, origline); missingterm(PL_tokenbuf + 1, sizeof(PL_tokenbuf) - 1); } + /* scan_inputsymbol takes: position of first '<' in input buffer returns: position of first char following the matching '>' in -- Perl5 Master Repository