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
63 changes: 62 additions & 1 deletion gitgalaxy/core/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -2988,6 +2988,11 @@ def _slice_by_braces(
# args-pattern search to the signature text, never the body. See
# `_calculate_block_metrics`'s `args_search_text` docstring.
args_sig_end: Optional[int] = None
# #2309: dart's bodyless `this.`/`super.`-forwarding constructor
# branch sets this directly via `_count_top_level_args` instead --
# see that branch below for why the `args` regex itself can't
# safely be taught to accept this shape.
args_count_override: Optional[int] = None

# #789: csharp's func_start regex (unlike every other C-family
# language here) doesn't consume the parameter list or require
Expand Down Expand Up @@ -3432,7 +3437,25 @@ def _dart_scan_terminator(
elif ch == "]":
depth_bracket = max(0, depth_bracket - 1)
elif ch == "<":
depth_angle += 1
# #2308 item 3: a bare `<` is ambiguous between a generic's
# open bracket (`SomeType<int>`, always attached directly to
# the preceding identifier with no space) and a numeric
# less-than comparison (`order < double.infinity`, always
# space-separated in idiomatic/dart-formatted code, and common
# inside a constructor's colon-initializer-list `assert(...)`
# clauses). Treating every `<` as a generic-open regardless of
# context poisons `depth_angle` on a bare comparison (its `>`,
# if any, is usually on a DIFFERENT operand and never closes
# it), permanently blocking the depth-0 check below from ever
# being true again -- silently dropping the real `;`/`{`
# terminator for the rest of the scan (confirmed via
# `OrdinalSortKey`'s `assert(order > ...), assert(order <
# double.infinity);` initializer list, language-crucible/data/
# dart/flutter/semantics.dart:7027). Only count it as a
# generic-open when directly attached to an identifier/`]`/`>`
# (no space), matching how Dart generics are actually written.
if pos > 0 and (safe_code[pos - 1].isalnum() or safe_code[pos - 1] in "_]>"):
depth_angle += 1
elif ch == ">":
depth_angle = max(0, depth_angle - 1)
elif depth_paren == 0 and depth_bracket == 0 and depth_angle == 0:
Expand All @@ -3449,6 +3472,23 @@ def _dart_scan_terminator(

term_idx, term_kind = _dart_scan_terminator(params_end_idx, opener + ";=:,")

# #2309: bounds the `args` regex's search to just the signature
# (through the first real terminator char), the same
# `args_sig_end`/`args_search_text` mechanism objc/c/cpp already use
# (see `_calculate_block_metrics`'s own docstring). Dart never set
# this before, so `args_search_text` stayed None and the `args`
# pattern's `.search()` ran unbounded over the WHOLE block including
# the body -- harmless while its terminator lookahead excluded `;`
# (a body's own call-statement terminator), but once `;` was added
# to support bodyless `this.`/`super.`-forwarding constructors
# (`_DeleteTextAction(this.state, ...);`,
# flutter/editable_text.dart:6353), an unbounded search on any
# PAREN-LESS declaration (a getter like `Rect get bounds { ... }`,
# flutter/editable_text.dart:6241, whose own signature has no `(...)`
# to match at all) fell through into the body and wrongly borrowed
# the first inner call statement's own args instead (`bounds`
# measured 1, borrowed from `box.getTransformTo(null);` deep in its
# body -- confirmed real via direct regex probe, not hypothetical).
if term_kind == "comma":
continue # Bug 4: list-element bare call
if term_kind == "colon":
Expand All @@ -3457,6 +3497,10 @@ def _dart_scan_terminator(
# scanning past the whole list (excluding "," from stop_chars, so
# they're skipped rather than mistaken for Bug 4's terminator) for
# the real terminator: either a bodyless `;` or a body-bearing `{`.
# `:` was already one of `args`'s accepted terminators before
# #2309, so bounding right past it (not the whole initializer
# list) is enough either way.
args_sig_end = term_idx + 1
colon_term_idx, colon_term_kind = _dart_scan_terminator(
term_idx + 1, opener + ";", search_limit=len(safe_code)
)
Expand All @@ -3468,13 +3512,29 @@ def _dart_scan_terminator(
continue
elif term_kind == "semi":
end_idx = term_idx + 1 # Bug 2: bodyless constructor
# #2309: count the real parameter list directly instead of
# relying on the `args` regex here -- that regex must never
# accept a bare `;` terminator (it's `.search()`ed over the
# whole block for dart, so accepting `;` would just as
# readily match a real call statement inside some OTHER
# zero-paren declaration's body; see language_standards.py's
# own comment on this same issue for the confirmed false
# positive). `_count_top_level_args` on the signature text
# already known-good (func_start + `_find_balanced_end`
# already validated real, balanced parens to get here) has
# no such ambiguity.
args_count_override = self._count_top_level_args(
safe_code[start_idx:params_end_idx], treat_as_body=False
)
elif term_kind == "brace":
end_idx = self._find_balanced_end(safe_code, term_idx, opener, closer)
args_sig_end = term_idx + 1
elif term_kind == "arrow":
semi_after_arrow = safe_code.find(";", term_idx, dart_search_limit)
if semi_after_arrow == -1:
continue
end_idx = semi_after_arrow + 1
args_sig_end = min(term_idx + 2, end_idx) # +2: past the full "=>"
else:
continue
# #1629: typescript/javascript idiomatically use brace-less,
Expand Down Expand Up @@ -3842,6 +3902,7 @@ def _dart_scan_terminator(
end_idx,
spatial_map,
args_search_text,
args_count_override,
)
satellites.append(sat)
sum_fxn_impact += mag
Expand Down
82 changes: 74 additions & 8 deletions gitgalaxy/standards/language_standards.py
Original file line number Diff line number Diff line change
Expand Up @@ -9623,6 +9623,25 @@ class PrismConfigSchema(TypedDict):
# every zero/one-arg signature by +1 the same way Python's did
# (#1199). Name group added to the first alternative too, purely
# so existing extraction tests keep passing.
# #2309 (investigated, NOT fixed via this regex -- see detector.py's
# dart branch instead): a bodyless (`;`-terminated) `this.`/`super.`-
# forwarding constructor (`_DeleteTextAction(this.state, ...);`,
# flutter/editable_text.dart:6353) reads 0 args because `func_start`
# accepts a bare `;` terminator for these but this SEPARATE
# `args`-counting regex never did. Adding `;` to the trailing
# lookahead here was tried and reverted: `test_dart_args_invalid`
# (`tests/extraction/languages/test_dart.py`) already asserts this
# regex must NEVER match a bare call statement (`foo(x);`) --
# `func_start`'s own Invocation Shield (#1221) doesn't apply here,
# since this regex is also `.search()`ed over the whole function
# `block` when dart supplies no `args_search_text` (true before
# #2309's own detector.py fix), so a `;`-accepting version matches
# the FIRST bare call statement inside a zero-paren declaration's own
# body (e.g. `Rect get bounds { ... box.getTransformTo(null); ...
# }`, wrongly borrowing 1 arg) just as readily as a real bodyless
# ctor. Fixed instead via `args_count_override` in detector.py,
# which counts the real parameter list directly for this one shape
# without loosening this shared regex's own contract.
"args": re.compile(
r"(?!(?:if|for|while|switch|catch|case|when|return|throw|new)\b)\b([A-Za-z_$][\w$]*)(?:[ \t\n]*<[^>]*>)?[ \t\n]*(\((?:[^()]|\((?:[^()]|\([^()]*\))*\))*\))(?=[ \t\n]*(?:\{|=>|:|async|sync))|(\((?:[^()]|\((?:[^()]|\([^()]*\))*\))*\))[ \t\n]*=>",
re.I | re.M,
Expand Down Expand Up @@ -9672,19 +9691,42 @@ class PrismConfigSchema(TypedDict):
r"^[ \t]*(?!(?:implements|with|extends)\b)(?:@[a-zA-Z_$][\w$]*\b(?:\([^)]*\))?[ \t\n]*){0,5}"
r"(?:"
r"(?:(?:static|external|abstract|covariant|late)[ \t\n]+){1,5}"
r"(?!(?:(?:(?:[\w<>\[\],.?]|\((?:[^()]|\([^()]*\))*\))+[ \t\n]+){0,5}?)(?:class|mixin|enum|extension|typedef|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\())\b)"
r"(?!(?:(?:(?:[\w<>\[\],.?]|\((?:[^()]|\([^()]*\))*\))+[ \t\n]+){0,5}?)(?:class|mixin|enum|extension|typedef|implements|with|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\())\b)"
r"(?:(?:(?:[\w<>\[\],.?]|\((?:[^()]|\([^()]*\))*\))+[ \t\n]+){0,4}?(?:(?:[\w<>\[\],.?]|\((?:[^()]|\([^()]*\))*\))+(?<!,)[ \t\n]+))?"
r"(?!(?:class|mixin|enum|extension|typedef|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\()|Function)\b)"
r"(?!(?:class|mixin|enum|extension|typedef|implements|with|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\()|Function)\b)"
r"(?:(?:(?P<getA>get)|set|factory|const)[ \t\n]+)?((?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*|operator[ \t\n]+[^\s\w]+)"
r"(?=[ \t\n]*(?:<[^>]*>[ \t\n]*)?(?:\(|=>|\{|(?(getA);|(?!))))"
r"|"
r"(?:(?:static|external|abstract|covariant|late)[ \t\n]+){0,5}"
r"(?!(?:(?:(?:[\w<>\[\],.?]|\((?:[^()]|\([^()]*\))*\))+[ \t\n]+){0,5}?)(?:class|mixin|enum|extension|typedef|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\())\b)"
r"(?!(?:(?:(?:[\w<>\[\],.?]|\((?:[^()]|\([^()]*\))*\))+[ \t\n]+){0,5}?)(?:class|mixin|enum|extension|typedef|implements|with|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\())\b)"
r"(?:(?!\?[ \t\n]+(?:get|set|factory|[a-zA-Z_]))(?:(?:(?:[\w<>\[\],.?]|\((?:[^()]|\([^()]*\))*\))+[ \t\n]+){0,4}?(?:(?:[\w<>\[\],.?]|\((?:[^()]|\([^()]*\))*\))+(?<!,)[ \t\n]+)))"
r"(?!(?:class|mixin|enum|extension|typedef|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\()|Function)\b)"
r"(?!(?:class|mixin|enum|extension|typedef|implements|with|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\()|Function)\b)"
r"(?:(?:(?P<getB>get)|set|factory|const)[ \t\n]+)?((?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*|operator[ \t\n]+[^\s\w]+)"
r"(?=[ \t\n]*(?:<[^>]*>[ \t\n]*)?(?:\(|=>|\{|(?(getB);|(?!))))"
r"|"
# #2308 item 1: `implements`/`with` added to every occurrence of this
# keyword-exclusion list (all 8, shared verbatim across all 4
# alternatives). The outermost `(?!(?:implements|with|extends)\b)`
# guard at this regex's very start only rejects a match that STARTS
# on one of these keywords -- it doesn't stop the return-type-prefix
# token loop below from swallowing one of them as an ordinary interior
# token when the match starts on an earlier line instead (e.g. a
# multi-line `class Foo extends Base\n with\n MixinA,\n
# MixinB\n implements SomeInterface {` header, where a match
# starting on `MixinB`'s own line can consume `implements` as its
# final "return-type" token and land on `SomeInterface` as a phantom
# function name). Confirmed via flutter/editable_text.dart:2480's
# `EditableTextState ... implements AutofillClient {` header.
# Deliberately does NOT add `extends` here -- already confirmed unsafe
# in this same investigation (#2072 item 1): `extends` also appears
# inside a generic method's own type-parameter bound
# (`pushNamed<T extends Object?>(...)`), which this token loop cannot
# distinguish from a bare class-header continuation without real
# bracket-depth tracking. A class header that continues via a bare
# `extends` on its own line (rare -- `extends` is almost always
# attached to the same line as `class Foo`) can still slip through;
# left as a known, documented residual limitation rather than risk
# breaking the far more common generic-bound pattern.
# #2071: this zero-prefix branch's lookahead used to accept a bare
# `=>` unconditionally (no `get` required) and validated a preceding
# "parameter list" with a naive, non-balanced-paren `\([^)]*\)` --
Expand All @@ -9701,14 +9743,38 @@ class PrismConfigSchema(TypedDict):
# rejects a parameter list that opens with `:` (only valid in Dart's
# object-destructuring patterns, e.g. `StatefulElement(:final T
# state) => state,` -- never a real parameter list).
r"(?!(?:(?:(?:[\w<>\[\],.?]|\((?:[^()]|\([^()]*\))*\))+[ \t\n]+){0,5}?)(?:class|mixin|enum|extension|typedef|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\())\b)"
r"(?!(?:class|mixin|enum|extension|typedef|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\()|Function)\b)"
r"(?!(?:(?:(?:[\w<>\[\],.?]|\((?:[^()]|\([^()]*\))*\))+[ \t\n]+){0,5}?)(?:class|mixin|enum|extension|typedef|implements|with|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\())\b)"
r"(?!(?:class|mixin|enum|extension|typedef|implements|with|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\()|Function)\b)"
r"(?:(?:(?P<getC>get)|set|factory|const)[ \t\n]+)?((?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*|operator[ \t\n]+[^\s\w]+)"
r"(?=[ \t\n]*(?:<[^>]*>[ \t\n]*)?(?:\((?!\s*:)(?:[^()]|\((?:[^()]|\([^()]*\))*\))*\)[ \t\n]*(?:async\*?|sync\*)?[ \t\n]*(?:=>|\{|:)|(?(getC)=>|(?!))|\{))"
r"|"
r"(?!(?:(?:(?:[\w<>\[\],.?]|\((?:[^()]|\([^()]*\))*\))+[ \t\n]+){0,5}?)(?:class|mixin|enum|extension|typedef|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\())\b)"
r"(?!(?:class|mixin|enum|extension|typedef|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\()|Function)\b)"
r"(?!(?:(?:(?:[\w<>\[\],.?]|\((?:[^()]|\([^()]*\))*\))+[ \t\n]+){0,5}?)(?:class|mixin|enum|extension|typedef|implements|with|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\())\b)"
r"(?!(?:class|mixin|enum|extension|typedef|implements|with|if|for|while|switch|catch|try|finally|case|when|assert|return|throw|new|var|final|const(?![ \t\n]+(?:[a-zA-Z_]\w*\.)?[a-zA-Z_]\w*[ \t\n]*(?:<[^>]*>[ \t\n]*)?\()|Function)\b)"
r"(?:const[ \t\n]+)?(_?[A-Z]\w*(?:\.[a-zA-Z_]\w*)?)"
# #2308 item 2 (investigated, NOT fixed -- confirmed unsafe): this
# alternative requires `this.`/`super.` inside the parens, on the
# theory that dart's zero-prefix valid cases are all constructors
# that always have bodies anyway -- true for constructors that
# forward fields, but a bodyless DEFAULT/named constructor with an
# EMPTY parameter list (`ClassName();`, e.g. flutter/semantics.dart's
# `ChildSemanticsConfigurationsResultBuilder();`) has neither and
# never matches. Widening this lookahead to also accept
# whitespace-only parens (tried in this same investigation) DOES
# recover that shape, but this branch's NAME pattern
# (`_?[A-Z]\w*(?:\.[a-zA-Z_]\w*)?`) is structurally identical for a
# real constructor declaration and a bare STATIC METHOD CALL
# STATEMENT with zero arguments -- `FlutterTimeline.finishSync();`,
# `LiveText.startLiveTextInput();`, `SystemNavigator.
# selectSingleEntryHistory();` (all real, all confirmed
# false-positive-matched by the widened version against
# language-crucible/data/dart). Telling the two apart needs knowing
# whether this line sits at class-body top level (declaration) or
# nested inside a method body (statement) -- real brace-depth
# tracking from the enclosing class's own opening brace, which this
# regex has no mechanism for. Left unfixed rather than trade one
# recall gap for a new, more common precision regression; a real
# fix belongs in detector.py with actual scope tracking, not a
# regex-only change here.
r"(?=[ \t\n]*(?:<[^>]*>[ \t\n]*)?\([^)]*(?:this\.|super\.)[^)]*\)[ \t\n]*;)"
r")",
re.M,
Expand Down
Loading
Loading