tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 05983f9b8d523636f7c0cfb0bc6634550eea6608
parent 2dd82727f6aa0c3b06a05cb7f3e79dbc6c8ebd5b
Author: serge-sans-paille <sguelton@mozilla.com>
Date:   Tue,  9 Dec 2025 15:32:32 +0000

Bug 1999664 - New Linter - ensure we don't include unnecessary C++ standard headers r=ahal

Differential Revision: https://phabricator.services.mozilla.com/D273179

Diffstat:
Mdocs/code-quality/lint/linters/includes.rst | 21++++++++++++++-------
Mtools/lint/includes.yml | 2++
Mtools/lint/includes/__init__.py | 130++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Atools/lint/includes/std.py | 421+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/lint/test/files/includes/correct_tuple.h | 5+++++
Atools/lint/test/files/includes/incorrect_tuple.h | 6++++++
Mtools/lint/test/test_includes.py | 13++++++++++++-
7 files changed, 545 insertions(+), 53 deletions(-)

diff --git a/docs/code-quality/lint/linters/includes.rst b/docs/code-quality/lint/linters/includes.rst @@ -8,21 +8,27 @@ It should have no false positive, and allows for false negative. Principle --------- -This linter works by looking for mfbt headers included in any C, Objective-C or +This linter has two parts: + +First part works by looking for mfbt headers included in any C, Objective-C or C++ file. For each of this header, it consults an abstract API description file and checks if any symbol (be it macro, function, global variable, type definition, operator) described in the API is actually used in the source. If not it reports the error. -The lookup for symbol is textual, which makes it very fast (no build required), -preprocessor-agnostic (all configuration are honored, no header generation -required), at the expense of including comments etc. +Second part works in a similar manner but for standard C++ headers. + +In both cases, the lookup for symbol is textual, which makes it very fast (no +build required), preprocessor-agnostic (all configuration are honored, no header +generation required), at the expense of including comments etc. API Description --------------- -The description file is manually maintained in :searchfox:`API Description (YAML) -<tools/lint/includes/api.yml>`. +The MFBT description file is manually maintained in :searchfox:`MFBT API Description (YAML) +<mfbt/api.yml>`. The C++ standard headers are described in +:searchfox:`C++ STL Description (Py) <tools/lint/includes/std.py>`. + It is basically a mapping from header name to a set of symbols, classified by type (macro, function etc). @@ -60,5 +66,6 @@ Sources ------- * :searchfox:`Linter Configuration (YAML) <tools/lint/includes.yml>` -* :searchfox:`API Description (YAML) <tools/lint/includes/api.yml>` +* :searchfox:`MFBT API Description (YAML) <mfbt/api.yml>` +* :searchfox:`C++ STL Description (Py) <tools/lint/includes/std.py>` * :searchfox:`Source <tools/lint/includes/__init__.py>` diff --git a/tools/lint/includes.yml b/tools/lint/includes.yml @@ -6,6 +6,8 @@ includes: include: ['.'] exclude: - js/src/tests/style/BadIncludesOrder-inl.h + - config/msvc-stl-wrapper.template.h + - widget/cocoa/nsMenuItemIconX.mm type: external payload: includes:lint support-files: ['tools/lint/includes/**'] diff --git a/tools/lint/includes/__init__.py b/tools/lint/includes/__init__.py @@ -10,6 +10,8 @@ import yaml from mozlint import result from mozlint.pathutils import expand_exclusions +from .std import api as std_api + here = os.path.dirname(__file__) with open(os.path.join(here, "..", "..", "..", "mfbt", "api.yml")) as fd: description = yaml.safe_load(fd) @@ -51,10 +53,90 @@ categories_pattern = { } +def lint_mfbt_headers(results, path, raw_content, config, fix): + supported_keys = "variables", "functions", "macros", "types", "literals" + + for header, categories in description.items(): + assert set(categories.keys()).issubset(supported_keys) + + if path.endswith(f"mfbt/{header}") or path.endswith(f"mfbt/{header[:-1]}.cpp"): + continue + + headerline = rf'#\s*include "mozilla/{header}"' + if not (match := re.search(headerline, raw_content)): + continue + + content = raw_content.replace(f'"mozilla/{header}"', "") + + for category, pattern in categories_pattern.items(): + identifiers = categories.get(category, []) + if any( + re.search(pattern.format(identifier), content) + for identifier in identifiers + ): + break + else: + msg = f"{path} includes {header} but does not reference any of its API" + lineno = 1 + raw_content.count("\n", 0, match.start()) + + if fix: + fix(path, raw_content, lineno) + results["fixed"] += 1 + else: + diff = generate_diff(path, raw_content, lineno) + + results["results"].append( + result.from_config( + config, + path=path, + message=msg, + level="error", + lineno=lineno, + diff=diff, + ) + ) + + +def lint_std_headers(results, path, raw_content, config, fix): + if re.search(r"using\s+namespace\s+std", raw_content): + return + + symbol_pattern = r"\bstd::{}\b" + + for header, symbols in std_api.items(): + headerline = rf"#\s*include <{header}>" + if not (match := re.search(headerline, raw_content)): + continue + if re.search( + "|".join(symbol_pattern.format(symbol) for symbol in symbols), raw_content + ): + continue + + msg = f"{path} includes <{header}> but does not reference any of its API" + lineno = 1 + raw_content.count("\n", 0, match.start()) + + if fix: + fix(path, raw_content, lineno) + results["fixed"] += 1 + else: + diff = generate_diff(path, raw_content, lineno) + + results["results"].append( + result.from_config( + config, + path=path, + message=msg, + level="error", + lineno=lineno, + diff=diff, + ) + ) + + def lint(paths, config, **lintargs): results = {"results": [], "fixed": 0} - paths = list(expand_exclusions(paths, config, lintargs["root"])) + fix = lintargs.get("fix") for path in paths: try: @@ -63,49 +145,7 @@ def lint(paths, config, **lintargs): except UnicodeDecodeError: continue - supported_keys = "variables", "functions", "macros", "types", "literals" + lint_mfbt_headers(results, path, raw_content, config, fix) + lint_std_headers(results, path, raw_content, config, fix) - for header, categories in description.items(): - assert set(categories.keys()).issubset(supported_keys) - - if path.endswith(f"mfbt/{header}") or path.endswith( - f"mfbt/{header[:-1]}.cpp" - ): - continue - - headerline = rf'#\s*include "mozilla/{header}"' - if not re.search(headerline, raw_content): - continue - - content = raw_content.replace(f'"mozilla/{header}"', "") - - for category, pattern in categories_pattern.items(): - identifiers = categories.get(category, []) - if any( - re.search(pattern.format(identifier), content) - for identifier in identifiers - ): - break - else: - msg = f"{path} includes {header} but does not reference any of its API" - for lineno, raw_line in enumerate(raw_content.split("\n"), start=1): - if re.search(headerline, raw_line): - break - - if lintargs.get("fix"): - fix(path, raw_content, lineno) - results["fixed"] += 1 - else: - diff = generate_diff(path, raw_content, lineno) - - results["results"].append( - result.from_config( - config, - path=path, - message=msg, - level="error", - lineno=lineno, - diff=diff, - ) - ) return results diff --git a/tools/lint/includes/std.py b/tools/lint/includes/std.py @@ -0,0 +1,421 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +api = { + "algorithm": [ + "all_of", + "any_of", + "none_of", + "for_each", + "for_each_n", + "count", + "count_if", + "mismatch", + "find", + "find_if", + "find_if_not", + "find_end", + "find_first_of", + "adjacent_find", + "search", + "search_n", + "copy", + "copy_if", + "copy_n", + "copy_backward", + "move", + "move_backward", + "fill", + "fill_n", + "transform", + "generate", + "generate_n", + "remove", + "remove_if", + "remove_copy", + "remove_copy_if", + "replace", + "replace_if", + "replace_copy", + "replace_copy_if", + "swap", + "swap_ranges", + "iter_swap", + "reverse", + "reverse_copy", + "rotate", + "rotate_copy", + "shuffle", + "sample", + "unique", + "unique_copy", + "is_partitioned", + "partition", + "partition_copy", + "stable_partition", + "partition_point", + "is_sorted", + "is_sorted_until", + "sort", + "partial_sort", + "partial_sort_copy", + "stable_sort", + "nth_element", + "lower_bound", + "upper_bound", + "binary_search", + "equal_range", + "merge", + "inplace_merge", + "includes", + "set_difference", + "set_intersection", + "set_symmetric_difference", + "set_union", + "is_heap", + "is_heap_until", + "make_heap", + "push_heap", + "pop_heap", + "sort_heap", + "max", + "max_element", + "min", + "min_element", + "minmax", + "minmax_element", + "clamp", + "equal", + "lexicographical_compare", + "lexicographical_compare_three_way", + "is_permutation", + "next_permutation", + "prev_permutation", + ], + "vector": [ + "vector", + ], + "list": [ + "list", + ], + "array": [ + "array", + "size", + ], + "map": [ + "map", + "multimap", + ], + "set": [ + "set", + "multiset", + ], + "string": [ + "char_traits", + "string", + "u8string", + "u16string", + "u32string", + "wstring", + "pmr::string" "pmr::u8string", + "pmr::u16string", + "pmr::u32string", + "pmr::wstring", + "getline", + "stoi", + "stol", + "stoll", + "stoul", + "stoull", + "stof", + "stod", + "stold", + "hash", + "to_string", + "to_wstring", + ], + "string_view": [ + "basic_string_view", + "string_view", + "u8string_view", + "u16string_view", + "u32string_view", + "wstring_view", + ], + "numeric": [ + "iota", + "accumulate", + "reduce", + "transform_reduce", + "inner_product", + "adjacent_difference", + "partial_sum", + "inclusive_scan", + "exclusive_scan", + "transform_inclusive_scan", + "transform_exclusive_scan", + "gcd", + "midpoint", + ], + "tuple": [ + "tuple", + "tuple_size", + "tuple_element", + "ignore", + "make_tuple", + "tie", + "forward_as_tuple", + "tuple_cat", + "get", + "apply", + "make_from_tuple", + ], + "optional": [ + "optional", + "bad_optional_access", + "nullopt_t", + "nullopt", + "make_optional", + ], + "unordered_map": ["unordered_map", "unordered_multimap"], + "unordered_set": ["unordered_set", "unordered_multiset"], + "memory": [ + "pointer_traits", + "pointer_safety", + "allocator", + "allocator_traits", + "uses_allocator", + "raw_storage_iterator", + "unique_ptr", + "shared_ptr", + "weak_ptr", + "owner_less", + "enable_shared_from_this", + "bad_weak_ptr", + "default_delete", + "allocator_arg", + "allocator_arg_t", + "addressof", + "align", + "uninitialized_copy", + "uninitialized_copy_n", + "uninitialized_fill", + "uninitialized_fill_n", + "uninitialized_move", + "uninitialized_move_n", + "uninitialized_default_construct", + "uninitialized_default_construct_n", + "uninitialized_value_construct", + "uninitialized_value_construct_n", + "destroy_at", + "destroy", + "destroy_n", + "make_unique", + "make_unique_for_overwrite", + "make_shared", + "make_shared_for_overwrite", + "static_pointer_cast", + "dynamic_pointer_cast", + "const_pointer_cast", + "reinterpret_pointer_cast", + "get_deleter", + ], + "utility": [ + "rel_ops", + "swap", + "exchange", + "forward", + "move", + "move_if_no_except", + "as_const", + "declval", + "cmp_equal", + "pair", + "tuple_size", + "get", + "tuple_element", + "integer_sequence", + "ignore", + "piecewise_construct", + "piecewise_construct_t", + "in_place", + "in_place_type", + "in_place_index", + "in_place_t", + "in_place_type_t", + "in_place_index_t", + ], + "type_traits": [ + "integral_constant", + "bool_constant", + "true_type", + "false_type", + "is_void", + "is_null_pointer", + "is_integral", + "is_floating_point", + "is_array", + "is_enum", + "is_union", + "is_class", + "is_function", + "is_pointer", + "is_lvalue_reference", + "is_rvalue_reference", + "is_member_object_pointer", + "is_member_function_pointer", + "is_fundamental", + "is_arithmetic", + "is_scalar", + "is_object", + "is_compound", + "is_reference", + "is_member_pointer", + "is_const", + "is_volatile", + "is_trivial", + "is_trivially_copyable", + "is_standard_layout", + "is_pod", + "is_literal_type", + "has_unique_object_representations", + "is_empty", + "is_polymorphic", + "is_abstract", + "is_final", + "is_aggregate", + "is_implicit_lifetime", + "is_signed", + "is_unsigned", + "is_bounded_array", + "is_unbounded_array", + "is_scoped_enum", + "is_constructible", + "is_trivially_constructible", + "is_nothrow_constructible", + "is_default_constructible", + "is_trivially_default_constructible", + "is_nothrow_default_constructible", + "is_copy_constructible", + "is_trivially_copy_constructible", + "is_nothrow_copy_constructible", + "is_move_constructible", + "is_trivially_move_constructible", + "is_nothrow_move_constructible", + "is_assignable", + "is_trivially_assignable", + "is_nothrow_assignable", + "is_copy_assignable", + "is_trivially_copy_assignable", + "is_nothrow_copy_assignable", + "is_move_assignable", + "is_trivially_move_assignable", + "is_nothrow_move_assignable", + "is_destructible", + "is_trivially_destructible", + "is_nothrow_destructible", + "has_virtual_destructor", + "is_swappable_with", + "is_swappable", + "is_nothrow_swappable_with", + "is_nothrow_swappable", + "reference_converts_from_temporary", + "reference_constructs_from_temporary", + "alignment_of", + "rank", + "extent", + "is_same", + "is_base_of", + "is_virtual_base_of", + "is_convertibleis_nothrow_convertible", + "is_layout_compatible", + "is_pointer_interconvertible_base_of", + "is_invocable", + "is_invocable_r", + "is_nothrow_invocable", + "is_nothrow_invocable_r", + "remove_cv", + "remove_const", + "remove_volatile", + "add_cv", + "add_const", + "add_volatile", + "remove_reference", + "add_lvalue_reference", + "add_rvalue_reference", + "remove_pointer", + "add_pointer", + "make_signed", + "make_unsigned", + "remove_extent", + "remove_all_extents", + "aligned_storage", + "aligned_union", + "decay", + "remove_cvref", + "enable_if", + "conditional", + "common_type", + "common_reference", + "basic_common_reference", + "underlying_type", + "result_ofinvoke_result", + "void_t", + "type_identity", + "unwrap_reference", + "unwrap_ref_decay", + "conjunction", + "disjunction", + "negation", + "is_pointer_interconvertible_with_class", + "is_corresponding_member", + "is_constant_evaluated", + "is_within_lifetime", + ], + "initializer_list": ["initializer_list"], + "limits": [ + "numeric_limits", + "float_round_style", + "float_denorm_style", + "round_indeterminate", + "round_toward_zero", + "round_to_nearest", + "round_toward_infinity", + ], + "iterator": [ + "advance", + "distance", + "next", + "prev", + "begin", + "cbegin", + "end", + "cend", + "make_reverse_iterator", + "make_move_iterator", + "front_inserter", + "back_inserter", + "inserter", + "istream_iterator", + "ostream_iterator", + "istreambug_iterator", + "ostreambuf_iterator", + "insert_iterator", + "front_insert_iterator", + "back_insert_iterator", + "move_iterator", + "reverse_iterator", + "iterator", + "iterator_traits", + "input_iterator_tag", + "output_iterator_tag", + "forward_iterator_tag", + "bidirectional_iterator_tag", + "random_access_iterator_tag", + "contiguous_iterator_tag", + ], +} +api["type_traits"].extend( + [f"{k}_v" for k in api["type_traits"]] + [f"{k}_t" for k in api["type_traits"]] +) diff --git a/tools/lint/test/files/includes/correct_tuple.h b/tools/lint/test/files/includes/correct_tuple.h @@ -0,0 +1,5 @@ +#include <tuple> + +auto foo() { + return std::make_tuple(1, 1., true); +} diff --git a/tools/lint/test/files/includes/incorrect_tuple.h b/tools/lint/test/files/includes/incorrect_tuple.h @@ -0,0 +1,6 @@ +#include <tuple> +#include <array> + +auto foo(std::array<int, 4> a) { + return std::size(a) +} diff --git a/tools/lint/test/test_includes.py b/tools/lint/test/test_includes.py @@ -60,7 +60,7 @@ def test_lint_api_yml(lint): ), f"{symbol} described as a {category} available in {header}, but cannot be found there" -def test_lint_includes(lint, paths): +def test_lint_mfbt_includes(lint, paths): results = lint(paths("correct_assert.h")) assert not results @@ -80,5 +80,16 @@ def test_lint_includes(lint, paths): ) +def test_lint_std_includes(lint, paths): + results = lint(paths("correct_tuple.h")) + assert not results + + results = lint(paths("incorrect_tuple.h")) + assert len(results) == 1 + assert results[0].message.endswith( + "incorrect_tuple.h includes <tuple> but does not reference any of its API" + ) + + if __name__ == "__main__": mozunit.main()