Bug 1434765 - Properly reject invalid variables in #if{,n}def. r?build draft
authorMike Hommey <mh+mozilla@glandium.org>
Thu, 01 Feb 2018 10:40:59 +0900
changeset 750323 35ba4b4d6f48dad7a1d5d7d9e62d7e06e7a12faf
parent 749909 189c437a125d0be280f47297f2be1c8234022b0c
push id97624
push userbmo:mh+mozilla@glandium.org
push dateThu, 01 Feb 2018 22:37:44 +0000
reviewersbuild
bugs1434765
milestone60.0a1
Bug 1434765 - Properly reject invalid variables in #if{,n}def. r?build The invalid variable test for #if{,n}def was only checking that the first character in the variable was alphanumeric or underscore, not the other characters. More generally, preprocessor instructions were also cut out such that whitespaces before and after arguments were part of the arguments. Subtly, some legitimate strings end with what, in ISO-8859-1, is considered as whitespaces, and because the preprocessor largely works on byte strings (str), and because the regexps are using re.U, those characters (e.g. 0xa0) that can legitimately appear in byte strings of UTF-8 encoding, are treated as whitespaces. So we remove the re.U from the instruction regexp, so that only plain ascii whitespaces only are stripped out. There's one place in layout/tools/reftest/manifest.jsm that was using a broken pattern, making the test never true, which, once fixed, unveils broken tests, so the branch that was never used is removed.
layout/tools/reftest/manifest.jsm
python/mozbuild/mozbuild/preprocessor.py
python/mozbuild/mozbuild/test/test_preprocessor.py
--- a/layout/tools/reftest/manifest.jsm
+++ b/layout/tools/reftest/manifest.jsm
@@ -555,22 +555,17 @@ sandbox.compareRetainedDisplayLists = g.
     }
     sandbox.stylo = styloEnabled && !g.compareStyloToGecko;
     sandbox.styloVsGecko = g.compareStyloToGecko;
 #else
     sandbox.stylo = false;
     sandbox.styloVsGecko = false;
 #endif
 
-// Printing via Skia PDF is only supported on Mac for now.
-#ifdef XP_MACOSX && MOZ_ENABLE_SKIA_PDF
-    sandbox.skiaPdf = true;
-#else
     sandbox.skiaPdf = false;
-#endif
 
 #ifdef RELEASE_OR_BETA
     sandbox.release_or_beta = true;
 #else
     sandbox.release_or_beta = false;
 #endif
 
     var hh = CC[NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX + "http"].
--- a/python/mozbuild/mozbuild/preprocessor.py
+++ b/python/mozbuild/mozbuild/preprocessor.py
@@ -334,19 +334,18 @@ class Preprocessor:
     def setMarker(self, aMarker):
         """
         Set the marker to be used for processing directives.
         Used for handling CSS files, with pp.setMarker('%'), for example.
         The given marker may be None, in which case no markers are processed.
         """
         self.marker = aMarker
         if aMarker:
-            self.instruction = re.compile('{0}(?P<cmd>[a-z]+)(?:\s(?P<args>.*))?$'
-                                          .format(aMarker),
-                                          re.U)
+            self.instruction = re.compile('{0}(?P<cmd>[a-z]+)(?:\s+(?P<args>.*?))?\s*$'
+                                          .format(aMarker))
             self.comment = re.compile(aMarker, re.U)
         else:
             class NoMatch(object):
                 def match(self, *args):
                     return False
             self.instruction = self.comment = NoMatch()
 
     def setSilenceDirectiveWarnings(self, value):
@@ -601,32 +600,32 @@ class Preprocessor:
             self.ifStates[-1] = self.disableLevel
         else:
             self.ifStates.append(self.disableLevel)
         pass
     def do_ifdef(self, args, replace=False):
         if self.disableLevel and not replace:
             self.disableLevel += 1
             return
-        if re.match('\W', args, re.U):
+        if re.search('\W', args, re.U):
             raise Preprocessor.Error(self, 'INVALID_VAR', args)
         if args not in self.context:
             self.disableLevel = 1
         if replace:
             if args in self.context:
                 self.disableLevel = 0
             self.ifStates[-1] = self.disableLevel
         else:
             self.ifStates.append(self.disableLevel)
         pass
     def do_ifndef(self, args, replace=False):
         if self.disableLevel and not replace:
             self.disableLevel += 1
             return
-        if re.match('\W', args, re.U):
+        if re.search('\W', args, re.U):
             raise Preprocessor.Error(self, 'INVALID_VAR', args)
         if args in self.context:
             self.disableLevel = 1
         if replace:
             if args not in self.context:
                 self.disableLevel = 0
             self.ifStates[-1] = self.disableLevel
         else:
--- a/python/mozbuild/mozbuild/test/test_preprocessor.py
+++ b/python/mozbuild/mozbuild/test/test_preprocessor.py
@@ -637,10 +637,29 @@ class TestPreprocessor(unittest.TestCase
             self.pp.do_include('f')
             self.assertEqual(self.pp.out.getvalue(), 'foobarbaz\n')
 
     def test_command_line_literal_at(self):
         with MockedOpen({"@foo@.in": '@foo@\n'}):
             self.pp.handleCommandLine(['-Fsubstitution', '-Dfoo=foobarbaz', '@foo@.in'])
             self.assertEqual(self.pp.out.getvalue(), 'foobarbaz\n')
 
+    def test_invalid_ifdef(self):
+        with MockedOpen({'dummy': '#ifdef FOO == BAR\nPASS\n#endif'}):
+            with self.assertRaises(Preprocessor.Error) as e:
+                self.pp.do_include('dummy')
+            self.assertEqual(e.exception.key, 'INVALID_VAR')
+
+        with MockedOpen({'dummy': '#ifndef FOO == BAR\nPASS\n#endif'}):
+            with self.assertRaises(Preprocessor.Error) as e:
+                self.pp.do_include('dummy')
+            self.assertEqual(e.exception.key, 'INVALID_VAR')
+
+        # Trailing whitespaces, while not nice, shouldn't be an error.
+        self.do_include_pass([
+            '#ifndef  FOO ',
+            'PASS',
+            '#endif',
+        ])
+
+
 if __name__ == '__main__':
     main()