[PATCH] parse: warn on trailing whitespace after line continuation backslash

Jaipaul Cheernam <[email protected]>
Newsgroups org.openembedded.lists.bitbake-devel
Message-ID <[email protected]>
rstrip() is called before checking for backslash, so trailing spaces or
tabs after "\" go unnoticed. For example:

    file://foo.patch \<TAB>
    file://bar.patch \

The first line has a trailing tab after the backslash that is invisible
but ends up in patch context, causing patches to fail to apply on trees
where it was cleaned up.

Emit a warning when this is detected so developers can fix it early.

Tested with bitbake-selftest:
  $ python3 -m unittest lib.bb.tests.parse.ParseTest.test_parse_trailing_whitespace_continuation -v
  $ python3 -m unittest lib.bb.tests.parse.ParseTest.test_parse_clean_continuation_no_warning -v

  2 tests OK

Signed-off-by: Jaipaul Cheernam <[email protected]>
---
Problem:
Ran into this while submitting libssh2 CVE patches to both master and
wrynose. The libssh2_1.11.1.bb on master has a stray tab after the
backslash on the CVE-2025-15661-3.patch line. Patches adding new file://
lines below it applied fine on master but git-am refused them on wrynose
because that branch doesn't have the trailing tab — context mismatch.

Other recipes in oe-core with the same issue:
  autoconf_2.73.bb:2
  perl_5.44.0.bb:313,314
  mc_4.8.33.bb:50,51
  libxml-sax-perl_1.02.bb:4

A separate series will follow to fix the trailing whitespace in the
affected recipes.

 lib/bb/parse/parse_py/BBHandler.py   |  6 +++++-
 lib/bb/parse/parse_py/ConfHandler.py | 10 ++++++++--
 lib/bb/tests/parse.py                | 19 +++++++++++++++++++
 3 files changed, 32 insertions(+), 3 deletions(-)

diff --git a/lib/bb/parse/parse_py/BBHandler.py b/lib/bb/parse/parse_py/BBHandler.py
index 008fec230..710ea04d4 100644
--- a/lib/bb/parse/parse_py/BBHandler.py
+++ b/lib/bb/parse/parse_py/BBHandler.py
@@ -104,7 +104,11 @@ def get_statements(filename, absolute_filename, base_name):
                 lineno = lineno + 1
                 s = f.readline()
                 if not s: break
-                s = s.rstrip()
+                # Warn if trailing whitespace exists after a continuation backslash
+                raw = s.rstrip('\n').rstrip('\r')
+                s = raw.rstrip()
+                if s and s[-1] == '\\' and raw != s:
+                    bb.warn("Trailing whitespace after line continuation backslash in %s, line %s" % (filename, lineno))
                 feeder(lineno, s, filename, base_name, statements)
 
         if __inpython__:
diff --git a/lib/bb/parse/parse_py/ConfHandler.py b/lib/bb/parse/parse_py/ConfHandler.py
index 9ddbae123..07b0c371f 100644
--- a/lib/bb/parse/parse_py/ConfHandler.py
+++ b/lib/bb/parse/parse_py/ConfHandler.py
@@ -134,12 +134,18 @@ def handle(fn, data, include, baseconfig=False):
             # skip empty lines
             if not w:
                 continue
-            s = s.rstrip()
+            raw = s.rstrip('\n').rstrip('\r')
+            s = raw.rstrip()
+            if s and s[-1] == '\\' and raw != s:
+                bb.warn("Trailing whitespace after line continuation backslash in %s, line %s" % (fn, lineno))
             while s[-1] == '\\':
                 line = f.readline()
                 origline += line
-                s2 = line.rstrip()
+                raw2 = line.rstrip('\n').rstrip('\r')
+                s2 = raw2.rstrip()
                 lineno = lineno + 1
+                if s2 and s2[-1] == '\\' and raw2 != s2:
+                    bb.warn("Trailing whitespace after line continuation backslash in %s, line %s" % (fn, lineno))
                 if (not s2 or s2 and s2[0] != "#") and s[0] == "#" :
                     bb.fatal("There is a confusing multiline, partially commented expression starting on line %s of file %s:\n%s\nPlease clarify whether this is all a comment or should be parsed." % (origlineno, fn, origline))
 
diff --git a/lib/bb/tests/parse.py b/lib/bb/tests/parse.py
index 6ac2137e0..b01932350 100644
--- a/lib/bb/tests/parse.py
+++ b/lib/bb/tests/parse.py
@@ -638,3 +638,22 @@ EXTRA_OECONF:append = " foobar"
             output = run_bitbake(["bitbake", "-e", "recipe-file1"], builddir, extraenv).splitlines()
             self.assertIn('BBCLASS_FILE="recipe-file.inc"', output)
             self.assertIn(f'BBCLASS_RECIPE_FILE="recipe-file1.bb"', output)
+
+    trailing_whitespace_continuation = "A = \"1 \\\t  \n2\"\n"
+
+    def test_parse_trailing_whitespace_continuation(self):
+        """Test that trailing whitespace after backslash continuation emits a warning"""
+        with self.parsehelper(self.trailing_whitespace_continuation) as f:
+            with self.assertLogs('BitBake', level='WARNING') as cm:
+                d = bb.parse.handle(f.name, self.d)['']
+        self.assertTrue(any("Trailing whitespace after line continuation backslash" in msg for msg in cm.output))
+        # Verify it still parses correctly despite the warning
+        self.assertEqual(d.getVar("A"), "1 2")
+
+    clean_continuation = "A = \"1 \\\n2\"\n"
+
+    def test_parse_clean_continuation_no_warning(self):
+        """Test that clean backslash continuation does not warn"""
+        with self.parsehelper(self.clean_continuation) as f:
+            d = bb.parse.handle(f.name, self.d)['']
+        self.assertEqual(d.getVar("A"), "1 2")
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.