Title: [287789] trunk/Tools
Revision
287789
Author
ddkil...@apple.com
Date
2022-01-07 14:56:53 -0800 (Fri, 07 Jan 2022)

Log Message

check-webkit-style: add checker for unexpected fall through after ASSERT_NOT_REACHED() statements
<https://webkit.org/b/234932>
<rdar://87213520>

Reviewed by Brent Fulgham.

This checker only returns a confidence level of 4 (out of 5)
since there are too many different code patterns to check them
all perfectly using regular expressions.

Run new checker tests like this:
$ test-webkitpy webkitpy.style.checkers.cpp_unittest.CppStyleTest.test_debug_not_reached_assertion

Run this checker against a file or folder like this:
$ check-webkit-style --filter "-,+security/assertion_fallthrough" (file|folder)

* Scripts/webkitpy/style/checkers/cpp.py:
(SingleLineView.__init__):
- Expose `trimmed_lines` as a property on SingleLineView.
(_FunctionState.body_view): Add.
- Add method to return a SingleLineView object containing the
  body of the function.
(check_function_body): Add.
- New function to check for fall through after
  ASSERT_NOT_REACHED() statements.
(process_line):
- Call check_function_body() to implement the new check.
(CppChecker.categories):
- Add 'security/assertion_fallthrough' to the list of known
  checkers.
* Scripts/webkitpy/style/checkers/cpp_unittest.py:
(CppStyleTestBase.perform_function_body_check): Add.
- Add method to call check_function_body() for testing.
(CppStyleTest.test_debug_not_reached_assertion): Add.
- Add tests for the new checker.

Modified Paths

Diff

Modified: trunk/Tools/ChangeLog (287788 => 287789)


--- trunk/Tools/ChangeLog	2022-01-07 22:56:47 UTC (rev 287788)
+++ trunk/Tools/ChangeLog	2022-01-07 22:56:53 UTC (rev 287789)
@@ -1,3 +1,41 @@
+2022-01-07  David Kilzer  <ddkil...@apple.com>
+
+        check-webkit-style: add checker for unexpected fall through after ASSERT_NOT_REACHED() statements
+        <https://webkit.org/b/234932>
+        <rdar://87213520>
+
+        Reviewed by Brent Fulgham.
+
+        This checker only returns a confidence level of 4 (out of 5)
+        since there are too many different code patterns to check them
+        all perfectly using regular expressions.
+
+        Run new checker tests like this:
+        $ test-webkitpy webkitpy.style.checkers.cpp_unittest.CppStyleTest.test_debug_not_reached_assertion
+
+        Run this checker against a file or folder like this:
+        $ check-webkit-style --filter "-,+security/assertion_fallthrough" (file|folder)
+
+        * Scripts/webkitpy/style/checkers/cpp.py:
+        (SingleLineView.__init__):
+        - Expose `trimmed_lines` as a property on SingleLineView.
+        (_FunctionState.body_view): Add.
+        - Add method to return a SingleLineView object containing the
+          body of the function.
+        (check_function_body): Add.
+        - New function to check for fall through after
+          ASSERT_NOT_REACHED() statements.
+        (process_line):
+        - Call check_function_body() to implement the new check.
+        (CppChecker.categories):
+        - Add 'security/assertion_fallthrough' to the list of known
+          checkers.
+        * Scripts/webkitpy/style/checkers/cpp_unittest.py:
+        (CppStyleTestBase.perform_function_body_check): Add.
+        - Add method to call check_function_body() for testing.
+        (CppStyleTest.test_debug_not_reached_assertion): Add.
+        - Add tests for the new checker.
+
 2022-01-07  Wenson Hsieh  <wenson_hs...@apple.com>
 
         Teach modal container observer to make the body element scrollable if necessary

Modified: trunk/Tools/Scripts/webkitpy/style/checkers/cpp.py (287788 => 287789)


--- trunk/Tools/Scripts/webkitpy/style/checkers/cpp.py	2022-01-07 22:56:47 UTC (rev 287788)
+++ trunk/Tools/Scripts/webkitpy/style/checkers/cpp.py	2022-01-07 22:56:53 UTC (rev 287789)
@@ -438,21 +438,21 @@
           end_position: just after where to end (like a slice operation).
         """
         # Get the rows of interest.
-        trimmed_lines = lines[start_position.row:end_position.row + 1]
+        self.trimmed_lines = lines[start_position.row:end_position.row + 1]
 
         # Remove the columns on the last line that aren't included.
-        trimmed_lines[-1] = trimmed_lines[-1][:end_position.column]
+        self.trimmed_lines[-1] = self.trimmed_lines[-1][:end_position.column]
 
         # Remove the columns on the first line that aren't included.
-        trimmed_lines[0] = trimmed_lines[0][start_position.column:]
+        self.trimmed_lines[0] = self.trimmed_lines[0][start_position.column:]
 
         # Create a single line with all of the parameters.
-        self.single_line = ' '.join(trimmed_lines)
+        self.single_line = ' '.join(self.trimmed_lines)
 
         # Keep the row lengths, so we can calculate the original row number
         # given a column in the single line (adding 1 due to the space added
         # during the join).
-        self._row_lengths = [len(line) + 1 for line in trimmed_lines]
+        self._row_lengths = [len(line) + 1 for line in self.trimmed_lines]
         self._starting_row = start_position.row
 
     def convert_column_to_row(self, single_line_column_number):
@@ -600,6 +600,11 @@
         elided = self._clean_lines.elided
         return SingleLineView(elided, self.parameter_end_position, self.body_start_position).single_line.strip()
 
+    def body_view(self):
+        """Returns the function body."""
+        elided = self._clean_lines.elided
+        return SingleLineView(elided, self.body_start_position, self.end_position)
+
     def attributes_after_definition(self, attribute_regex):
         return re.findall(attribute_regex, self.post_modifiers())
 
@@ -2044,6 +2049,44 @@
                   '%s should only appear in directories matching %s.' % (export_macro, path))
 
 
+def check_function_body(filename, file_extension, clean_lines, line_number, class_state, function_state, error):
+    """Check function bodies for style issues.
+
+    Args:
+       filename: Filename of the file that is being processed.
+       file_extension: The current file extension, without the leading dot.
+       clean_lines: A CleansedLines instance containing the file.
+       line_number: The number of the line to check.
+       function_state: Current function name and lines in body so far.
+       error: The function to call with any errors found.
+    """
+    if line_number != function_state.end_position.row:  # last line
+        return
+
+    function_body_view = function_state.body_view()
+    function_line_count = len(function_body_view.trimmed_lines)
+
+    # Check for uncontrolled fall-through after ASSERT_NOT_REACHED() statement.
+    for i in range(0, function_line_count):
+        current_line = function_body_view.trimmed_lines[i]
+        if not re.search(r'[^_]ASSERT_NOT_REACHED\(\);', current_line):
+            continue
+
+        min_index = max(0, i - 1)
+        max_index = min(function_line_count, i + 4)
+        partial_function_body = ' '.join(function_body_view.trimmed_lines[min_index:max_index])
+
+        if search(r'[^_]ASSERT_NOT_REACHED\(\);\s*(continue|return(\s+[^;]+)?);', partial_function_body) \
+                or search(r'[^_]ASSERT_NOT_REACHED\(\);(\s*#endif)?(\s*})+\s*$', partial_function_body) \
+                or search(r'[^_]ASSERT_NOT_REACHED\(\);(\s*completionHandler[^;]+;)?(\s*})+\s*$', partial_function_body) \
+                or search(r'[^_]ASSERT_NOT_REACHED\(\);(\s*[^;]+;)?\s*return(\s+[^;]+)?;', partial_function_body) \
+                or search(r'(default|case\s+.+):\s*[^_]ASSERT_NOT_REACHED\(\);(\s*[^;]+;)*\s*break;', partial_function_body):
+            continue
+
+        error(line_number - (function_line_count - (i + 1)), 'security/assertion_fallthrough', 4,
+              'ASSERT_NOT_REACHED() statement fallthrough may result in unexpected code execution.')
+
+
 def check_for_leaky_patterns(clean_lines, line_number, function_state, error):
     """Check for constructs known to be leak prone.
     Args:
@@ -4496,6 +4539,7 @@
     if asm_state.is_in_asm():  # Ignore further checks because asm blocks formatted differently.
         return
     check_function_definition(filename, file_extension, clean_lines, line, class_state, function_state, error)
+    check_function_body(filename, file_extension, clean_lines, line, class_state, function_state, error)
     check_for_leaky_patterns(clean_lines, line, function_state, error)
     check_for_multiline_comments_and_strings(clean_lines, line, error)
     check_style(clean_lines, line, file_extension, class_state, file_state, enum_state, error)
@@ -4655,6 +4699,7 @@
         'runtime/wtf_move',
         'runtime/wtf_never_destroyed',
         'security/assertion',
+        'security/assertion_fallthrough',
         'security/_javascript_core_wtf_blockptr',
         'security/missing_warn_unused_return',
         'security/printf',

Modified: trunk/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py (287788 => 287789)


--- trunk/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py	2022-01-07 22:56:47 UTC (rev 287788)
+++ trunk/Tools/Scripts/webkitpy/style/checkers/cpp_unittest.py	2022-01-07 22:56:53 UTC (rev 287789)
@@ -338,6 +338,22 @@
                                             error_collector)
         self.assertEqual(error_collector.results(), expected_warning)
 
+    def perform_function_body_check(self, file_name, lines, expected_warning):
+        file_extension = file_name.split('.')[1]
+        clean_lines = cpp_style.CleansedLines(lines.splitlines())
+        function_state = cpp_style._FunctionState(5)
+        error_collector = ErrorCollector(self.assertTrue)
+
+        for i in range(clean_lines.num_lines()):
+            cpp_style.detect_functions(clean_lines, i, function_state, error_collector)
+        self.assertEqual(function_state.in_a_function, True)
+        self.assertEqual(error_collector.results(), '')
+
+        class_state = cpp_style._ClassState()
+        cpp_style.check_function_body(file_name, file_extension, clean_lines, clean_lines.num_lines() - 1, class_state,
+                                      function_state, error_collector)
+        self.assertEqual(error_collector.results(), expected_warning)
+
     # Perform lint and compare the error message with "expected_message".
     def assert_lint(self, code, expected_message, file_name='foo.cpp'):
         self.assertEqual(expected_message, self.perform_single_line_lint(code, file_name))
@@ -1624,6 +1640,220 @@
                          ' for improved thread safety.'
                          '  [runtime/threadsafe_fn] [2]')
 
+    def test_debug_not_reached_assertion(self):
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f()\n'
+            '{\n'
+            '    if (auto createdHandle = SandboxExtension::createHandle(URL.fileSystemPath(), SandboxExtension::Type::ReadWrite))\n'
+            '        handle = WTFMove(*createdHandle);\n'
+            '    else\n'
+            '        ASSERT_NOT_REACHED();\n\n'
+            '    m_dataStore->networkProcess().send(Messages::NetworkProcess::PublishDownloadProgress(m_downloadID, URL, handle), 0);]\n'
+            '}',
+            'ASSERT_NOT_REACHED() statement fallthrough may result in unexpected code execution.'
+            '  [security/assertion_fallthrough] [4]')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f()\n'
+            '{\n'
+            '    if (auto createdHandle = SandboxExtension::createHandle(URL.fileSystemPath(), SandboxExtension::Type::ReadWrite))\n'
+            '        handle = WTFMove(*createdHandle);\n'
+            '    else\n'
+            '        RELEASE_ASSERT_NOT_REACHED();\n\n'
+            '    m_dataStore->networkProcess().send(Messages::NetworkProcess::PublishDownloadProgress(m_downloadID, URL, handle), 0);]\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f()\n'
+            '{\n'
+            '    for (int i = 0; i < 10; ++i) {'
+            '        if (i % 2 == 0) {'
+            '            ASSERT_NOT_REACHED();\n'
+            '            continue;\n'
+            '        }\n'
+            '    }\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f(int i)\n'
+            '{\n'
+            '    if (i % 2 == 0) {'
+            '        ASSERT_NOT_REACHED();\n'
+            '        return;\n'
+            '    }\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f(int i)\n'
+            '{\n'
+            '    if (i % 2 == 0) {'
+            '        ASSERT_NOT_REACHED();\n'
+            '        completionHandler(nullptr);\n'
+            '        return;\n'
+            '    }\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f(int i)\n'
+            '{\n'
+            '    if (i % 2 == 0) {'
+            '        ASSERT_NOT_REACHED();\n'
+            '        failureBlock(nullptr);\n'
+            '        return;\n'
+            '    }\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f()\n'
+            '{\n'
+            '    ASSERT_NOT_REACHED();\n'
+            '    completionHandler(nullString());\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f(int i)\n'
+            '{\n'
+            '    if (i % 2 == 0) {\n'
+            '        ASSERT_NOT_REACHED();\n'
+            '        completionHandler(nullString());\n'
+            '    }\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f(int i)\n'
+            '{\n'
+            '    switch (i) {'
+            '    case 0:'
+            '        ASSERT_NOT_REACHED();\n'
+            '    }\n'
+            '    g();\n'
+            '}',
+            'ASSERT_NOT_REACHED() statement fallthrough may result in unexpected code execution.'
+            '  [security/assertion_fallthrough] [4]')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f(int i)\n'
+            '{\n'
+            '    switch (i) {'
+            '    default:'
+            '        ASSERT_NOT_REACHED();\n'
+            '    }\n'
+            '    g();\n'
+            '}',
+            'ASSERT_NOT_REACHED() statement fallthrough may result in unexpected code execution.'
+            '  [security/assertion_fallthrough] [4]')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f(int i)\n'
+            '{\n'
+            '    if (!i) {'
+            '        g();'
+            '    } else {'
+            '        ASSERT_NOT_REACHED();\n'
+            '    }\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f(int i)\n'
+            '{\n'
+            '    switch (i) {'
+            '    case 0:'
+            '        ASSERT_NOT_REACHED();\n'
+            '        break;\n'
+            '    }\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f(int i)\n'
+            '{\n'
+            '    switch (i) {'
+            '    default:'
+            '        ASSERT_NOT_REACHED();\n'
+            '        break;\n'
+            '    }\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f(int i)\n'
+            '{\n'
+            '    switch (i) {'
+            '    case 0:'
+            '        ASSERT_NOT_REACHED();\n'
+            '        m_completionHandler();\n'
+            '        break;\n'
+            '    }\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f(int i)\n'
+            '{\n'
+            '    switch (i) {'
+            '    default:'
+            '        ASSERT_NOT_REACHED();\n'
+            '        m_completionHandler();\n'
+            '        break;\n'
+            '    }\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'void f()\n'
+            '{\n'
+            '#if PLATFORM(COCOA)\n'
+            '    pageClient().clearTextIndicator(WebCore::TextIndicatorDismissalAnimation::FadeOut);\n'
+            '#else\n'
+            '    ASSERT_NOT_REACHED();\n'
+            '#endif\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.cpp',
+            'int f()\n'
+            '{\n'
+            '    ASSERT_NOT_REACHED();\n'
+            '    *errorCode = U_UNSUPPORTED_ERROR;\n'
+            '    return 0;\n'
+            '}',
+            '')
+
+        self.perform_function_body_check(
+            'foo.h',
+            'void f() { ASSERT_NOT_REACHED(); }',
+            '')
+        self.perform_function_body_check(
+            'foo.h',
+            'void* f() { ASSERT_NOT_REACHED(); return nullptr; }',
+            '')
+
+
     def test_debug_security_assertion(self):
         self.assert_lint(
             'ASSERT_WITH_SECURITY_IMPLICATION(value)',
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to