pureshellcheck 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pureshellcheck-0.2.1/src/pureshellcheck.egg-info → pureshellcheck-0.2.2}/PKG-INFO +1 -1
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/pyproject.toml +1 -1
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/__init__.py +1 -1
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/parser.py +73 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2/src/pureshellcheck.egg-info}/PKG-INFO +1 -1
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/LICENSE +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/MANIFEST.in +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/README.md +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/setup.cfg +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/analyzer.py +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/astlib.py +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/checks/__init__.py +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/checks/commands.py +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/checks/misc.py +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/checks/quoting.py +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/checks/variables.py +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/cli.py +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/shast.py +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/varflow.py +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck/varscan.py +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck.egg-info/SOURCES.txt +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck.egg-info/dependency_links.txt +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck.egg-info/entry_points.txt +0 -0
- {pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck.egg-info/top_level.txt +0 -0
|
@@ -22,6 +22,15 @@ END_KEYWORDS = {
|
|
|
22
22
|
# Characters that terminate an unquoted word.
|
|
23
23
|
METACHARS = " \t\n;&|<>()"
|
|
24
24
|
|
|
25
|
+
# Cap on nested constructs - subshells "(", command substitutions "$(",
|
|
26
|
+
# brace/arithmetic groups - which re-enter read_command or read_arith_primary.
|
|
27
|
+
# Arithmetic nesting is the most frame-hungry (its precedence chain adds ~15
|
|
28
|
+
# interpreter frames per level), so 40 trips at ~600 frames: comfortably under
|
|
29
|
+
# CPython's default 1000 limit on every platform (including Windows' ~1 MB
|
|
30
|
+
# stack) even from a deep call site, yet far above any real script. Deeply
|
|
31
|
+
# nested input like "$( $( $(" or "(((((" raises a clean ParseError.
|
|
32
|
+
MAX_NESTING = 40
|
|
33
|
+
|
|
25
34
|
UNQUOTED_RUN = re.compile(r"""[^\\'"$`*?\[\](){}<>|&;\s!#~]+""")
|
|
26
35
|
DQUOTE_RUN = re.compile(r'[^"\\$`]+')
|
|
27
36
|
SQUOTE_END = re.compile(r"'")
|
|
@@ -81,6 +90,17 @@ class ParseError(Exception):
|
|
|
81
90
|
self.code = code
|
|
82
91
|
|
|
83
92
|
|
|
93
|
+
class _NestingLimit(Exception):
|
|
94
|
+
"""Raised when input nests deeper than MAX_NESTING. Deliberately NOT a
|
|
95
|
+
ParseError so the parser's backtracking (which catches ParseError to try
|
|
96
|
+
alternatives) cannot swallow it; parse() turns it into a ParseError."""
|
|
97
|
+
|
|
98
|
+
def __init__(self, message, pos):
|
|
99
|
+
super().__init__(message)
|
|
100
|
+
self.message = message
|
|
101
|
+
self.pos = pos
|
|
102
|
+
|
|
103
|
+
|
|
84
104
|
class Directive:
|
|
85
105
|
"""A `# shellcheck key=value` comment."""
|
|
86
106
|
|
|
@@ -104,6 +124,11 @@ class Parser:
|
|
|
104
124
|
self.pending_heredocs = []
|
|
105
125
|
self.directives = []
|
|
106
126
|
self.line_had_command = False
|
|
127
|
+
# Smallest index from which a glob-class scan reached EOF with no
|
|
128
|
+
# closing ']' or metachar; from there on every '[' is a literal.
|
|
129
|
+
# Caps the otherwise O(n^2) rescan on input like "[1,[1,[1,...".
|
|
130
|
+
self._glob_eof_from = self.n + 1
|
|
131
|
+
self._nesting = 0 # current compound-command nesting depth
|
|
107
132
|
|
|
108
133
|
# ------------------------------------------------------------------
|
|
109
134
|
# Low-level helpers
|
|
@@ -192,6 +217,13 @@ class Parser:
|
|
|
192
217
|
# Script / lists
|
|
193
218
|
|
|
194
219
|
def parse(self):
|
|
220
|
+
try:
|
|
221
|
+
return self._parse()
|
|
222
|
+
except _NestingLimit as e:
|
|
223
|
+
# Surface the internal depth-limit signal as the public error type.
|
|
224
|
+
raise ParseError(e.message, e.pos) from None
|
|
225
|
+
|
|
226
|
+
def _parse(self):
|
|
195
227
|
shebang = None
|
|
196
228
|
if self.src.startswith("#!"):
|
|
197
229
|
end = self.src.find("\n")
|
|
@@ -340,6 +372,20 @@ class Parser:
|
|
|
340
372
|
# Commands
|
|
341
373
|
|
|
342
374
|
def read_command(self):
|
|
375
|
+
# Every nested compound command - subshell, command substitution,
|
|
376
|
+
# brace/arithmetic group - re-enters here, so this bounds parser
|
|
377
|
+
# recursion and keeps adversarial input from overflowing the stack.
|
|
378
|
+
self._nesting += 1
|
|
379
|
+
if self._nesting > MAX_NESTING:
|
|
380
|
+
self._nesting -= 1
|
|
381
|
+
raise _NestingLimit("commands nested too deeply (over %d levels)"
|
|
382
|
+
% MAX_NESTING, self.i)
|
|
383
|
+
try:
|
|
384
|
+
return self._read_command()
|
|
385
|
+
finally:
|
|
386
|
+
self._nesting -= 1
|
|
387
|
+
|
|
388
|
+
def _read_command(self):
|
|
343
389
|
self.skip_inline_ws()
|
|
344
390
|
if self.at_end():
|
|
345
391
|
return None
|
|
@@ -1366,11 +1412,17 @@ class Parser:
|
|
|
1366
1412
|
"""Parse [...] as a glob character class, else literal '['."""
|
|
1367
1413
|
src, n = self.src, self.n
|
|
1368
1414
|
start = self.i
|
|
1415
|
+
# We already proved the rest of the input has no closing ']' (nor a
|
|
1416
|
+
# metachar) from an earlier '['; this one can't open a class either.
|
|
1417
|
+
if start >= self._glob_eof_from:
|
|
1418
|
+
self.i = start + 1
|
|
1419
|
+
return Node("T_Literal", start, self.i, text="[")
|
|
1369
1420
|
j = self.i + 1
|
|
1370
1421
|
if j < n and src[j] in "!^":
|
|
1371
1422
|
j += 1
|
|
1372
1423
|
if j < n and src[j] == "]":
|
|
1373
1424
|
j += 1
|
|
1425
|
+
jumped = False
|
|
1374
1426
|
while j < n:
|
|
1375
1427
|
c = src[j]
|
|
1376
1428
|
if c == "]":
|
|
@@ -1384,8 +1436,15 @@ class Parser:
|
|
|
1384
1436
|
if end == -1:
|
|
1385
1437
|
break
|
|
1386
1438
|
j = end + 2
|
|
1439
|
+
jumped = True
|
|
1387
1440
|
continue
|
|
1388
1441
|
j += 1
|
|
1442
|
+
else:
|
|
1443
|
+
# Reached EOF with no ']' or metachar. Unless a [:class:] jump
|
|
1444
|
+
# skipped over text, no later '[' can find one either - remember
|
|
1445
|
+
# it so the next '[' bails in O(1).
|
|
1446
|
+
if not jumped:
|
|
1447
|
+
self._glob_eof_from = start
|
|
1389
1448
|
self.i = start + 1
|
|
1390
1449
|
return Node("T_Literal", start, self.i, text="[")
|
|
1391
1450
|
|
|
@@ -1842,6 +1901,20 @@ class Parser:
|
|
|
1842
1901
|
return operand
|
|
1843
1902
|
|
|
1844
1903
|
def read_arith_primary(self):
|
|
1904
|
+
# Nested "(...)" in arithmetic re-enters here, a separate recursion
|
|
1905
|
+
# from read_command, so it shares the same nesting guard to keep
|
|
1906
|
+
# "(((((..." / "$((((..." from overflowing the stack.
|
|
1907
|
+
self._nesting += 1
|
|
1908
|
+
if self._nesting > MAX_NESTING:
|
|
1909
|
+
self._nesting -= 1
|
|
1910
|
+
raise _NestingLimit("arithmetic nested too deeply (over %d levels)"
|
|
1911
|
+
% MAX_NESTING, self.i)
|
|
1912
|
+
try:
|
|
1913
|
+
return self._read_arith_primary()
|
|
1914
|
+
finally:
|
|
1915
|
+
self._nesting -= 1
|
|
1916
|
+
|
|
1917
|
+
def _read_arith_primary(self):
|
|
1845
1918
|
self.skip_arith_ws()
|
|
1846
1919
|
start = self.i
|
|
1847
1920
|
src, n = self.src, self.n
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pureshellcheck-0.2.1 → pureshellcheck-0.2.2}/src/pureshellcheck.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|