Skip to content

Commit aa48004

Browse files
committed
gh-156891: Fix shlex source inclusion at EOF
Fix shlex source inclusion when the source filename is the final token of its parent stream. Newly pushed streams now start with a valid lexical state, and popping a stream restores the parent state.
1 parent f54fd2a commit aa48004

3 files changed

Lines changed: 48 additions & 3 deletions

File tree

Lib/shlex.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,12 @@ def push_source(self, newstream, newfile=None):
7777
"Push an input source onto the lexer's input source stack."
7878
if isinstance(newstream, str):
7979
newstream = StringIO(newstream)
80-
self.filestack.appendleft((self.infile, self.instream, self.lineno))
80+
self.filestack.appendleft((self.infile, self.instream, self.lineno,
81+
self.state))
8182
self.infile = newfile
8283
self.instream = newstream
8384
self.lineno = 1
85+
self.state = ' '
8486
if self.debug:
8587
if newfile is not None:
8688
print('shlex: pushing to file %s' % (self.infile,))
@@ -90,11 +92,11 @@ def push_source(self, newstream, newfile=None):
9092
def pop_source(self):
9193
"Pop the input source stack."
9294
self.instream.close()
93-
(self.infile, self.instream, self.lineno) = self.filestack.popleft()
95+
(self.infile, self.instream, self.lineno,
96+
self.state) = self.filestack.popleft()
9497
if self.debug:
9598
print('shlex: popping to %s, line %d' \
9699
% (self.instream, self.lineno))
97-
self.state = ' '
98100

99101
def get_token(self):
100102
"Get a token from the input stream (or from stack if it's nonempty)"

Lib/test/test_shlex.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,24 @@ def testPushSourceStream(self):
420420
s.push_source(io.StringIO("hello"))
421421
self.assertListEqual(list(s), ["hello", "world"])
422422

423+
def testPushSourceKeepsPushback(self):
424+
s = shlex.shlex("parent")
425+
stream = io.StringIO("child")
426+
s.push_token("pushed")
427+
s.push_source(stream)
428+
self.assertListEqual(list(s), ["pushed", "child", "parent"])
429+
self.assertTrue(stream.closed)
430+
431+
def testPushSourceAfterEOF(self):
432+
for posix in (False, True):
433+
with self.subTest(posix=posix):
434+
s = shlex.shlex("parent", posix=posix)
435+
self.assertEqual(list(s), ["parent"])
436+
stream = io.StringIO("child")
437+
s.push_source(stream)
438+
self.assertEqual(list(s), ["child"])
439+
self.assertTrue(stream.closed)
440+
423441
def testPushSourceStreamDebug(self):
424442
s = shlex.shlex("")
425443
stream = io.StringIO("hello")
@@ -514,6 +532,29 @@ def testSourceInclusion(self):
514532
s.sourcehook = lambda f: (f, io.StringIO("included"))
515533
self.assertEqual(list(s), ["included", "remaining"])
516534

535+
def testSourceInclusionAtEOF(self):
536+
for posix in (False, True):
537+
with self.subTest(posix=posix):
538+
s = shlex.shlex("trigger filename", posix=posix)
539+
s.source = "trigger"
540+
stream = io.StringIO("included")
541+
s.sourcehook = lambda f: (f, stream)
542+
self.assertEqual(list(s), ["included"])
543+
self.assertTrue(stream.closed)
544+
545+
def testNestedSourceInclusionAtEOF(self):
546+
for posix in (False, True):
547+
with self.subTest(posix=posix):
548+
streams = {
549+
"child": io.StringIO("trigger grandchild"),
550+
"grandchild": io.StringIO("included"),
551+
}
552+
s = shlex.shlex("trigger child", posix=posix)
553+
s.source = "trigger"
554+
s.sourcehook = lambda f: (f, streams[f])
555+
self.assertEqual(list(s), ["included"])
556+
self.assertTrue(all(stream.closed for stream in streams.values()))
557+
517558
def testGetTokenPopsPushbackDebug(self):
518559
s = shlex.shlex("")
519560
s.push_token("hello")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :class:`shlex.shlex` source inclusion when the source filename is the
2+
final token in a stream.

0 commit comments

Comments
 (0)