Skip to content

Commit 3407c5f

Browse files
committed
toke.c: S_intuit_more: Add more commentary
This function is described in its comments as 'terrifying', and by its original author, Larry Wall, as "truly awful". As a result, it has been mostly untouched since its introduction in 1993. That means it has not been updated as new language features have been added. As an example, it does not know about lexical variables, so the code it has for globals just doesn't work on the vast majority of modern day coding practices. Another example is it knows nothing of UTF-8, and as a result simply changing the input encoding from Latin1 to UTF-8 can result in its outcome being the opposite result. And it is buggy. A few years ago, I set out to try to understand it. I added commentary and simplified some overly complicated expressions, but left its behavior unchanged. Now, I set out to make some changes, and found many more issues than I had earlier. This commit adds commentary about those. Hopefully this will lead to some discussion and a consensus on the way forward.
1 parent 765f0a6 commit 3407c5f

File tree

1 file changed

+158
-17
lines changed

1 file changed

+158
-17
lines changed

toke.c

Lines changed: 158 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4549,19 +4549,47 @@ S_intuit_more(pTHX_ char *s, char *e)
45494549
/* Here is '[': maybe we have a character class. Examine the guts */
45504550
s++;
45514551

4552+
/* khw: If the context of this call is $foo[...], we may be able to avoid
4553+
* the heuristics below. The possibilities are:
4554+
* strict @foo $foo
4555+
* vars? exists exists
4556+
* y n n This is an error; return false now
4557+
* y n y must be a a charclass
4558+
* y y n must be a a subscript
4559+
* y y y ambiguous; do heuristics below
4560+
* n n n ambiguous; do heuristics below
4561+
* n n y ambiguous; do heuristics below, but I
4562+
* wonder if the initial bias should be a
4563+
* little towards charclass
4564+
* n y n ambiguous; do heuristics below, but I
4565+
* wonder if the initial bias should be a
4566+
* little towards subscript
4567+
* n y y ambiguous; do heuristics below
4568+
*/
4569+
45524570
/* '^' implies a character class; An empty '[]' isn't legal, but it does
45534571
* mean there isn't more to come */
45544572
if (s[0] == ']' || s[0] == '^')
45554573
return FALSE;
45564574

4557-
/* Find matching ']'. khw: This means any s[1] below is guaranteed to
4558-
* exist */
4575+
/* Find matching ']'. khw: Actually it finds the next ']' and assumes it
4576+
* matches the '['. In order to account for the possibility of the ']'
4577+
* being inside the scope of \Q or preceded by an even number of
4578+
* backslashes, this should be rewritten */
45594579
const char * const send = (char *) memchr(s, ']', e - s);
45604580
if (! send) /* has to be an expression */
45614581
return TRUE;
45624582

4583+
/* Below here, the heuristics start. One idea from alh is, given 'use
4584+
* 5.43.x', that for all digits, that if we have to resort to heuristics,
4585+
* we instead raise an error with an explanation of how to make it
4586+
* unambiguous: ${foo}[123] */
4587+
45634588
/* If the construct consists entirely of one or two digits, call it a
4564-
* subscript. */
4589+
* subscript.
4590+
*
4591+
* khw: No one writes 03 to mean 3. Any string of digits beginning with
4592+
* '0' is likely to be a charclass, including length 2 ones. */
45654593
if (isDIGIT(s[0]) && send - s <= 2 && (send - s == 1 || (isDIGIT(s[1])))) {
45664594
return TRUE;
45674595
}
@@ -4596,6 +4624,13 @@ S_intuit_more(pTHX_ char *s, char *e)
45964624
Zero(seen, 256, char);
45974625

45984626
/* Examine each character in the construct */
4627+
/* That this knows nothing of UTF-8 can lead to opposite results if the
4628+
* text is encoded in UTF-8 or not; another relic of the Unicode Bug.
4629+
* Suppose a string consistis of various un-repeated code points between
4630+
* 0x128 and 0x255. When encoded in UTF-8 their start bytes will all be
4631+
* \xC2 or \xC3. The heuristics below will count those as repeated bytes,
4632+
* and thus lean more towards this being a character class than when not
4633+
* in UTF-8. */
45994634
bool first_time = true;
46004635
for (; s < send; s++, first_time = false) {
46014636
unsigned char prev_un_char = un_char;
@@ -4617,15 +4652,42 @@ S_intuit_more(pTHX_ char *s, char *e)
46174652
char tmpbuf[sizeof PL_tokenbuf * 4];
46184653
scan_ident(s, tmpbuf, sizeof tmpbuf, FALSE);
46194654
len = (int)strlen(tmpbuf);
4655+
4656+
/* khw: This only looks at global variables; lexicals came
4657+
* later, and this hasn't been updated. Ouch!! */
46204658
if ( len > 1
46214659
&& gv_fetchpvn_flags(tmpbuf,
46224660
len,
46234661
UTF ? SVf_UTF8 : 0,
46244662
SVt_PV))
4663+
{
46254664
weight -= 100;
4626-
else /* Not a multi-char identifier already known in the
4627-
program; is somewhat likely to be a subscript */
4665+
4666+
/* khw: Below we keep track of repeated characters; People
4667+
* rarely say qr/[aba]/, as the second a is pointless.
4668+
* (Some do it though as a mnemonic that is meaningful to
4669+
* them.) But generally, repeated characters makes things
4670+
* more likely to be a charclass. But we have here that
4671+
* this an identifier so likely a subscript. Its spelling
4672+
* should be irrelevant to the repeated characters test.
4673+
* So, we should advance past it. Suppose it is a hash
4674+
* element, like $subscripts{$which}. We should advance
4675+
* past the braces and key */
4676+
}
4677+
else {
4678+
/* Not a multi-char identifier already known in the
4679+
* program; is somewhat likely to be a subscript.
4680+
*
4681+
* khw: Our test suite contains several constructs like
4682+
* [$A-Z]. Excluding length 1 identifiers in the
4683+
* conditional above means such are much less likely to be
4684+
* mistaken for subscripts. I would argue that if the next
4685+
* character is a '-' followed by an alpha, that would make
4686+
* it much more likely to be a charclass. It would only
4687+
* make sense to be an expression if that alpha string is a
4688+
* bareword with meaning; something like [$A-ord] */
46284689
weight -= 10;
4690+
}
46294691
}
46304692
else if ( s[0] == '$'
46314693
&& s[1]
@@ -4642,13 +4704,43 @@ S_intuit_more(pTHX_ char *s, char *e)
46424704
}
46434705
break;
46444706

4707+
/* khw: [:blank:] strongly indicates a charclass */
4708+
46454709
case '\\':
46464710
if (s[1]) {
4647-
if (memCHRs("wds]", s[1]))
4711+
if (memCHRs("wds]", s[1])) {
46484712
weight += 100; /* \w \d \s => strongly charclass */
4649-
/* khw: Why not \W \D \S \h \v, etc as well? */
4650-
else if (seen[(U8)'\''] || seen[(U8)'"'])
4651-
weight += 1; /* \' => mildly charclass */
4713+
/* khw: \] can't happen, as any ']' is beyond our search.
4714+
* Why not \W \D \S \h \v, etc as well? Should they have
4715+
* the same weights as \w \d \s or should all or some be
4716+
* in the 'abcfnrtvx' below? */
4717+
} else if (seen[(U8)'\''] || seen[(U8)'"']) {
4718+
weight += 1;
4719+
/* khw: This is problematic. Enough so, that I misread
4720+
* it, and added a wrong comment about what it does in
4721+
* 57ae1f3a8e669082e3d5ec6a8cdffbdc39d87bee. Note that it
4722+
* doesn't look at the current character. What it
4723+
* actually does is: if any quote has been seen in the
4724+
* parse, don't do the rest of the else's below, but for
4725+
* every subsequent backslashed character encountered
4726+
* (except \0 \w \s \d), increment the weight to lean a
4727+
* bit more towards being a charclass. That means that
4728+
* every backslash sequence following the first occurrence
4729+
* of a quote increments the weight regardless of what the
4730+
* sequence is. Again, \0 \w \d and \s are not controlled
4731+
* by this else, so they change the weight by a lot more.
4732+
* But what makes them so special that they aren't subject
4733+
* to this. Any why does having a quote change the
4734+
* behavior from then on. And why only backslashed
4735+
* sequences get this treatment? This code has been
4736+
* unchanged since this function was added in 1993. I
4737+
* don't get it. Instead, it does seem to me that it is
4738+
* especially unlikely to repeat a quote in a charclass,
4739+
* but that having just a single quote is indicative of a
4740+
* charclass, and having pairs of quotes is indicative of
4741+
* a subscript. Similarly for things that could indicate
4742+
* nesting of braces or parens. */
4743+
}
46524744
else if (memCHRs("abcfnrtvx", s[1]))
46534745
weight += 40; /* \n, etc => charclass */
46544746
/* khw: Why not \e etc as well? */
@@ -4657,6 +4749,19 @@ S_intuit_more(pTHX_ char *s, char *e)
46574749
while (s[1] && isDIGIT(s[1]))
46584750
s++;
46594751
}
4752+
4753+
/* khw: There are lots more possible escape sequences. Some,
4754+
* like \A,\z have no special meaning to charclasses, so might
4755+
* indicate a subscript, but I don't know what they would be
4756+
* doing there either. Some have been added to the language
4757+
* after this code was written, but no one thought to, or
4758+
* could wade through this function, to add them. Things like
4759+
* \p{} for properties, \N and \N{}, for example.
4760+
*
4761+
* It's problematic that \a is treated as plain 'a' for
4762+
* purposes of the 'seen' array. Whatever is matched by these
4763+
* backslashed sequences should not be added to 'seen'. That
4764+
* includes the backslash. */
46604765
}
46614766
else /* \ followed by NUL strongly indicates character class */
46624767
weight += 100;
@@ -4702,7 +4807,25 @@ S_intuit_more(pTHX_ char *s, char *e)
47024807
&& isALPHA(s[1]))
47034808
{
47044809
/* Here it's \W (that isn't [$@&] ) followed immediately by two
4705-
* alphas in a row. Accumulate all the consecutive alphas */
4810+
* alphas in a row. Accumulate all the consecutive alphas.
4811+
*
4812+
* khw: The code below was changed in 2015 by
4813+
* 56f81afc0f2d331537f38e6f12b86a850187cb8a to solve a
4814+
* buffer overrun. Prior to that commit, the code copied all
4815+
* the consecutive alphas to a temporary. The problem was
4816+
* that temporary's size could be exceeded, and the temporary
4817+
* wasn't even needed (at least by 2015). The called
4818+
* keyword() function doesn't need a copy. It takes a pointer
4819+
* to the first character and a length, hence it can operate
4820+
* on the original source text. It is intended to catch cases
4821+
* like $a[ord]. If it does match a keyword, we don't want
4822+
* the spelling of that keyword to affect the seen[] array.
4823+
* But if it isn't a keyword we do want to fall back to the
4824+
* normal behavior. And the 2015 commit removed that. It
4825+
* absorbs every bareword regardless, defeating the intent of
4826+
* the algorithm implementing the heuristics. That not many
4827+
* bugs have surfaced since indicates this whole thing doesn't
4828+
* get applied very much */
47064829
char *d = s;
47074830
while (isALPHA(s[0]))
47084831
s++;
@@ -4712,7 +4835,12 @@ S_intuit_more(pTHX_ char *s, char *e)
47124835
if (keyword(d, s - d, 0))
47134836
weight -= 150;
47144837

4715-
/* khw: Should those alphas be marked as seen? */
4838+
/* khw: Barewords could also be subroutine calls, and these
4839+
* would also indicate a subscript. Like [green] where
4840+
* 'green' has been declared, for example, in 'use constant'
4841+
* Or maybe it should just call intuit_method() which checks
4842+
* for keyword, subs, and methods.
4843+
* */
47164844
}
47174845

47184846
/* Consecutive chars like [...12...] and [...ab...] are presumed
@@ -4727,23 +4855,36 @@ S_intuit_more(pTHX_ char *s, char *e)
47274855
/* But repeating a character inside a character class does nothing,
47284856
* like [aba], so less likely that someone makes such a class, more
47294857
* likely that it is a subscript; the more repeats, the less
4730-
* likely. */
4858+
* likely.
4859+
*
4860+
* khw: I think this changes the weight too rapidly. Each time
4861+
* through the loop compounds the previous times. Instead, it
4862+
* would be better to have a separate loop after all the rest that
4863+
* changes the weight once based on how many times each character
4864+
* gets repeated */
47314865
weight -= seen[un_char];
47324866
break;
47334867
} /* End of switch */
47344868

47354869
/* khw: 'seen' is declared as a char. This ++ can cause it to wrap.
47364870
* This gives different results with compilers for which a plain 'char'
4737-
* is actually unsigned, versus those where it is signed. I believe it
4738-
* is undefined behavior to wrap a 'signed'. I think it should be
4739-
* instead declared an unsigned int to make the chances of wrapping
4740-
* essentially zero.
4871+
* is actually unsigned, versus those where it is signed. The C99
4872+
* standard allows a compiler to raise a signal when a 'signed' char
4873+
* is incremented outside its permissible range. I think 'seen'
4874+
* should be instead declared an unsigned, and a conditional added
4875+
* to prevent wrapping.
47414876
*
47424877
* And I believe that extra backslashes are different from other
4743-
* repeated characters. */
4878+
* repeated characters. There may be others, like I have mentioned
4879+
* quotes and paired delimiters */
47444880
seen[un_char]++;
47454881
} /* End of loop through each character of the construct */
47464882

4883+
/* khw: People on #irc have suggested things that I think boil down to:
4884+
* under 'use 5.43.x', output a warning like existing warnings for
4885+
* similar situations "Ambiguous use of [], resolved as ..." Perhaps
4886+
* suppress the message if all (or maybe almost all) the evidence points
4887+
* to the same outcome. This would involve two weight variables */
47474888
if (weight >= 0) /* probably a character class */
47484889
return FALSE;
47494890

0 commit comments

Comments
 (0)