Add banned functions PRESUBMIT check
Based heavily off Chromium's similar PRESUBMIT check.
Checks against:
1) `using namespace`, as it is banned by the C++ style guide.
2) `v8::Isolate::GetCurrent()`, as it is bug prone and there are better
alternatives.
Bug: pdfium:1737
Change-Id: Ia69d22d11a2383005db77ceaba952e94d21cf6fa
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/86310
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Daniel Hosseinian <dhoss@chromium.org>
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 955839c..55d417c 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -40,6 +40,106 @@
_KNOWN_ROBOTS = set() | set(
'%s@skia-public.iam.gserviceaccount.com' % s for s in ('pdfium-autoroll',))
+_THIRD_PARTY = 'third_party/'
+
+# Format: Sequence of tuples containing:
+# * String pattern or, if starting with a slash, a regular expression.
+# * Sequence of strings to show when the pattern matches.
+# * Error flag. True if a match is a presubmit error, otherwise it's a warning.
+# * Sequence of paths to *not* check (regexps).
+_BANNED_CPP_FUNCTIONS = (
+ (
+ r'/\busing namespace ',
+ (
+ 'Using directives ("using namespace x") are banned by the Google Style',
+ 'Guide ( https://google.github.io/styleguide/cppguide.html#Namespaces ).',
+ 'Explicitly qualify symbols or use using declarations ("using x::foo").',
+ ),
+ True,
+ [_THIRD_PARTY],
+ ),
+ (
+ 'v8::Isolate::GetCurrent()',
+ (
+ 'Avoid uses of v8::Isolate::GetCurrent(). Prefer holding a pointer to',
+ 'the v8::Isolate that was entered.',
+ ),
+ False,
+ (),
+ ),
+)
+
+
+def _CheckNoBannedFunctions(input_api, output_api):
+ """Makes sure that banned functions are not used."""
+ warnings = []
+ errors = []
+
+ def _GetMessageForMatchingType(input_api, affected_file, line_number, line,
+ type_name, message):
+ """Returns an string composed of the name of the file, the line number where
+ the match has been found and the additional text passed as `message` in case
+ the target type name matches the text inside the line passed as parameter.
+ """
+ result = []
+
+ if input_api.re.search(r"^ *//",
+ line): # Ignore comments about banned types.
+ return result
+ if line.endswith(
+ " nocheck"): # A // nocheck comment will bypass this error.
+ return result
+
+ matched = False
+ if type_name[0:1] == '/':
+ regex = type_name[1:]
+ if input_api.re.search(regex, line):
+ matched = True
+ elif type_name in line:
+ matched = True
+
+ if matched:
+ result.append(' %s:%d:' % (affected_file.LocalPath(), line_number))
+ for message_line in message:
+ result.append(' %s' % message_line)
+
+ return result
+
+ def IsExcludedFile(affected_file, excluded_paths):
+ local_path = affected_file.LocalPath()
+ for item in excluded_paths:
+ if input_api.re.match(item, local_path):
+ return True
+ return False
+
+ def CheckForMatch(affected_file, line_num, line, func_name, message, error):
+ problems = _GetMessageForMatchingType(input_api, f, line_num, line,
+ func_name, message)
+ if problems:
+ if error:
+ errors.extend(problems)
+ else:
+ warnings.extend(problems)
+
+ file_filter = lambda f: f.LocalPath().endswith(('.cc', '.cpp', '.h'))
+ for f in input_api.AffectedFiles(file_filter=file_filter):
+ for line_num, line in f.ChangedContents():
+ for func_name, message, error, excluded_paths in _BANNED_CPP_FUNCTIONS:
+ if IsExcludedFile(f, excluded_paths):
+ continue
+ CheckForMatch(f, line_num, line, func_name, message, error)
+
+ result = []
+ if (warnings):
+ result.append(
+ output_api.PresubmitPromptWarning('Banned functions were used.\n' +
+ '\n'.join(warnings)))
+ if (errors):
+ result.append(
+ output_api.PresubmitError('Banned functions were used.\n' +
+ '\n'.join(errors)))
+ return result
+
def _CheckUnwantedDependencies(input_api, output_api):
"""Runs checkdeps on #include statements added in this
@@ -365,6 +465,7 @@
def CheckChangeOnUpload(input_api, output_api):
results = []
+ results.extend(_CheckNoBannedFunctions(input_api, output_api))
results.extend(_CheckUnwantedDependencies(input_api, output_api))
results.extend(
input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
diff --git a/PRESUBMIT_test.py b/PRESUBMIT_test.py
index cbe87d7..72723ed 100755
--- a/PRESUBMIT_test.py
+++ b/PRESUBMIT_test.py
@@ -9,6 +9,37 @@
from PRESUBMIT_test_mocks import MockInputApi, MockOutputApi, MockFile
+class BannedTypeCheckTest(unittest.TestCase):
+
+ def testBannedCppFunctions(self):
+ input_api = MockInputApi()
+ input_api.files = [
+ MockFile('some/cpp/problematic/file.cc', ['using namespace std;']),
+ MockFile('third_party/some/cpp/problematic/file.cc',
+ ['using namespace std;']),
+ MockFile('some/cpp/ok/file.cc', ['using std::string;']),
+ MockFile('some/cpp/nocheck/file.cc',
+ ['using namespace std; // nocheck']),
+ MockFile('some/cpp/comment/file.cc',
+ [' // A comment about `using namespace std;`']),
+ MockFile('some/cpp/v8/file.cc', ['v8::Isolate::GetCurrent()']),
+ ]
+
+ results = PRESUBMIT._CheckNoBannedFunctions(input_api, MockOutputApi())
+
+ # warnings are results[0], errors are results[1]
+ self.assertEqual(2, len(results))
+ self.assertTrue('some/cpp/problematic/file.cc' in results[1].message)
+ self.assertFalse(
+ 'third_party/some/cpp/problematic/file.cc' in results[1].message)
+ self.assertFalse('some/cpp/ok/file.cc' in results[1].message)
+ self.assertFalse('some/cpp/nocheck/file.cc' in results[0].message)
+ self.assertFalse('some/cpp/nocheck/file.cc' in results[1].message)
+ self.assertFalse('some/cpp/comment/file.cc' in results[0].message)
+ self.assertFalse('some/cpp/comment/file.cc' in results[1].message)
+ self.assertTrue('some/cpp/v8/file.cc' in results[0].message)
+
+
class CheckChangeOnUploadTest(unittest.TestCase):
def testCheckPNGFormat(self):
diff --git a/PRESUBMIT_test_mocks.py b/PRESUBMIT_test_mocks.py
index ce9fcb0..e0e484f 100644
--- a/PRESUBMIT_test_mocks.py
+++ b/PRESUBMIT_test_mocks.py
@@ -44,6 +44,12 @@
MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
self.type = 'error'
+ class PresubmitPromptWarning(PresubmitResult):
+
+ def __init__(self, message, items=None, long_text=''):
+ MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
+ self.type = 'warning'
+
class MockFile(object):
"""Mock class for the File class.
@@ -65,5 +71,8 @@
self._action = action
self._old_contents = old_contents
+ def ChangedContents(self):
+ return self._changed_contents
+
def LocalPath(self):
return self._local_path