"""Top-level presubmit script for Chromium.
See https://www.chromium.org/developers/how-tos/depottools/presubmit-scripts/
for more details about the presubmit API built into depot_tools.
"""
import dataclasses
from typing import Callable
from typing import Optional
from typing import Sequence
from typing import Tuple
PRESUBMIT_VERSION = '2.0.0'
_EXCLUDED_PATHS = (
(r'chrome/android/webapk/shell_apk/src/org/chromium'
r'/webapk/lib/runtime_library/IWebApkApi.java'),
r'chrome/updater/mac/keystone/ksadmin.mm',
(r'^components/variations/proto/devtools/'
r'client_variations.js'),
r'^media/test/data/.*.ts',
r'^net/tools/spdyshark/.*',
r'^skia/.*',
r'^third_party/blink/.*',
r'^third_party/breakpad/.*',
r'^third_party/sqlite/.*',
r'^v8/.*',
r'.*MakeFile$',
r'.+_autogen\.h$',
r'.+_pb2(_grpc)?\.py$',
r'^gpu/config/.*_list_json\.cc$',
r'tools/md_browser/.*\.css$',
r'tools/perf/page_sets/maps_perf_test.*',
r'tools/perf/page_sets/webrtc_cases.*',
r'tools/polymer/tests/html_to_wrapper/.*.html.ts$',
r'chrome/test/data/webui/chromeos/chai_v4.js$',
)
_EXCLUDED_SET_NO_PARENT_PATHS = (
'third_party/blink/OWNERS', )
_IMPLEMENTATION_EXTENSIONS = r'\.(cc|cpp|cxx|mm)$'
_HEADER_EXTENSIONS = r'\.(h|hpp|hxx)$'
_NON_BASE_DEPENDENT_PATHS = (
r'^chrome/browser/browser_switcher/bho/',
r'^tools/win/',
)
_TEST_CODE_EXCLUDED_PATHS = (
r'.*/(fake_|test_|mock_).+%s' % _IMPLEMENTATION_EXTENSIONS,
r'.+_test_(base|support|util)(_[a-z]+)?%s' % _IMPLEMENTATION_EXTENSIONS,
r'.+_(api|browser|eg|int|perf|pixel|unit|ui)?test(s)?(_[a-z]+)?%s' %
_IMPLEMENTATION_EXTENSIONS,
r'.+_(fuzz|fuzzer)(_[a-z]+)?%s' % _IMPLEMENTATION_EXTENSIONS,
r'.+sync_service_impl_harness%s' % _IMPLEMENTATION_EXTENSIONS,
r'.*/(test|tool(s)?)/.*',
r'content/shell/.*',
r'content/web_test/.*',
r'mojo/examples/.*',
r'testing/iossim/iossim\.mm$',
r'ios/.*_app_interface\.mm$',
r'ui/views/examples/.*',
r'codelabs/*')
_THIRD_PARTY_EXCEPT_BLINK = 'third_party/(?!blink/)'
_TEST_ONLY_WARNING = (
'You might be calling functions intended only for testing from\n'
'production code. If you are doing this from inside another method\n'
'named as *ForTesting(), then consider exposing things to have tests\n'
'make that same call directly.\n'
'If that is not possible, you may put a comment on the same line with\n'
' // IN-TEST \n'
'to tell the PRESUBMIT script that the code is inside a *ForTesting()\n'
'method and can be ignored. Do not do this inside production code.\n'
'The android-binary-size trybot will block if the method exists in the\n'
'release apk.\n'
'Note: this warning might be a false positive (crbug.com/1196548).')
@dataclasses.dataclass
class BanRule:
pattern: str
explanation: Tuple[str, ...]
treat_as_error: bool = False
excluded_paths: Optional[Sequence[str]] = None
surface_as_gerrit_lint: Optional[bool] = None
_BANNED_JAVA_IMPORTS: Sequence[BanRule] = (
BanRule(
'import java.net.URI;',
('Use org.chromium.url.GURL instead of java.net.URI, where possible.',
),
True,
excluded_paths=(
(r'net/android/javatests/src/org/chromium/net/'
r'AndroidProxySelectorTest\.java'),
r'components/cronet/',
r'third_party/robolectric/local/',
),
),
BanRule(
'import android.annotation.TargetApi;',
('Do not use TargetApi, use @androidx.annotation.RequiresApi instead. '
'RequiresApi ensures that any calls are guarded by the appropriate '
'SDK_INT check. See https://crbug.com/1116486.', ), True),
BanRule(
'import androidx.test.rule.ActivityTestRule;',
('Do not use ActivityTestRule, use '
'org.chromium.base.test.BaseActivityTestRule instead.', ),
True,
excluded_paths=('components/cronet/', ),
),
BanRule(
'import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;',
('Do not use VectorDrawableCompat, use getResources().getDrawable() '
'to avoid extra indirections. Please also add trace event as the call '
'might take more than 20 ms to complete.', ),
True,
),
BanRule(
'import java.util.Optional',
('Prefer @Nullable over Optional/OptionalInt/OptionalDouble/etc. See '
'//styleguide/java/java.md', ),
False,
),
BanRule(
'import static org.junit.Assert.assertThat',
('Use com.google.common.truth.Truth.assertThat() instead of '
'org.junit.Assert.assertThat().', ),
True,
excluded_paths=(r'third_party/', ),
),
)
_BANNED_JAVA_FUNCTIONS: Sequence[BanRule] = (
BanRule(
'StrictMode.allowThreadDiskReads()',
('Prefer using StrictModeContext.allowDiskReads() to using StrictMode '
'directly.', ),
False,
),
BanRule(
'StrictMode.allowThreadDiskWrites()',
('Prefer using StrictModeContext.allowDiskWrites() to using StrictMode '
'directly.', ),
False,
),
BanRule(
'.waitForIdleSync()',
('Do not use waitForIdleSync as it masks underlying issues. There is '
'almost always something else you should wait on instead.', ),
False,
),
BanRule(
r'/(?<!\bsuper\.)(?<!\bIntent )\bregisterReceiver\(',
('Do not call android.content.Context.registerReceiver (or an '
'override) directly. Use one of the wrapper methods defined in '
'org.chromium.base.ContextUtils, such as '
'registerProtectedBroadcastReceiver, '
'registerExportedBroadcastReceiver, or '
'registerNonExportedBroadcastReceiver. See their documentation for '
'which one to use.', ),
True,
excluded_paths=(
r'.*Test[^a-z]',
r'third_party/',
'base/android/java/src/org/chromium/base/ContextUtils.java',
'chromecast/browser/android/apk/src/org/chromium/chromecast/shell/BroadcastReceiverScope.java',
),
),
BanRule(
r'/(?:extends|new)\s*(?:android.util.)?Property<[A-Za-z.]+,\s*(?:Integer|Float)>',
('Do not use Property<..., Integer|Float>, but use FloatProperty or '
'IntProperty because it will avoid unnecessary autoboxing of '
'primitives.', ),
),
BanRule(
'requestLayout()',
('Layouts can be expensive. Prefer using ViewUtils.requestLayout(), '
'which emits a trace event with additional information to help with '
'scroll jank investigations. See http://crbug.com/1354176.', ),
False,
excluded_paths=(
'ui/android/java/src/org/chromium/ui/base/ViewUtils.java', ),
),
BanRule(
'ProfileManager.getLastUsedRegularProfile()',
('Prefer passing in the Profile reference instead of relying on the '
'static getLastUsedRegularProfile() call. Only top level entry points '
'(e.g. Activities) should call this method. Otherwise, the Profile '
'should either be passed in explicitly or retrieved from an existing '
'entity with a reference to the Profile (e.g. WebContents).', ),
False,
excluded_paths=(r'.*Test[A-Z]?.*\.java', ),
),
BanRule(
r'/(ResourcesCompat|getResources\(\))\.getDrawable\(\)',
('getDrawable() can be expensive. If you have a lot of calls to '
'GetDrawable() or your code may introduce janks, please put your '
'calls inside a trace().', ),
False,
excluded_paths=(r'.*Test[A-Z]?.*\.java', ),
),
BanRule(
r'/RecordHistogram\.getHistogram(ValueCount|TotalCount|Samples)ForTesting\(',
('Raw histogram counts are easy to misuse; for example they don\'t '
'reset between batched tests. Use HistogramWatcher to check histogram '
'records instead.', ),
False,
excluded_paths=(
'base/android/javatests/src/org/chromium/base/metrics/RecordHistogramTest.java',
'base/test/android/javatests/src/org/chromium/base/test/util/HistogramWatcher.java',
),
),
BanRule(
r'/((announceForAccessibility\()|TYPE_ANNOUNCEMENT)',
('Android 16 deprecates accessibility announcements, characterized by '
'the use of announceForAccessibility or the dispatch of '
'TYPE_ANNOUNCEMENT accessibility events. See '
'https://developer.android.com/about/versions/16/behavior-changes-all#disruptive-a11y'
' for more details and suggested replacements.', ),
False,
),
BanRule(
pattern=
(r'/((DeviceInfo\.isDesktop\()|IS_DESKTOP_ANDROID|PackageManager\.FEATURE_PC)'
),
explanation=
('Usage of IS_DESKTOP_ANDROID build flag or DeviceInfo.isDesktop() '
'is discouraged. Use system affordances to determine feature '
'availablility. Refer to https://chromium.googlesource.com/chromium/src/+/HEAD/docs/ui/android/device_form_factor.md for guidelines. '
'To request an exception, file a bug at '
'https://b.corp.google.com/issues/new?component=1753515&template=2172655'
'Once approved, use centralized util DeviceInfo.isDesktop() '
'instead of direct build flag or PackageManager.FEATURE_PC checks. '
'Allowances may be granted to only the directories below: '
'[build/, chrome/, components/, extensions/, infra/, tools/] '
'Note: in particular we need to avoid components shared with '
'WebView.', ),
treat_as_error=False,
surface_as_gerrit_lint=True,
),
BanRule(
r'/(?:org\.junit\.)?(?<!\w)Assert\.assertThat\(',
('Use com.google.common.truth.Truth.assertThat() instead of '
'org.junit.Assert.assertThat().', ),
True,
excluded_paths=(r'third_party/', ),
),
)
_BANNED_JAVASCRIPT_FUNCTIONS: Sequence[BanRule] = (
BanRule(
r'/\bchrome\.send\b',
(
'The use of chrome.send is disallowed in Chrome (context: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/security/handling-messages-from-web-content.md).',
'Please use mojo instead for new webuis. https://docs.google.com/document/d/1RF-GSUoveYa37eoyZ9EhwMtaIwoW7Z88pIgNZ9YzQi4/edit#heading=h.gkk22wgk6wff',
),
True,
(
r'^(?!ash\/webui).+',
'ash/webui/common/resources/cr.m.js',
'ash/webui/common/resources/multidevice_setup/multidevice_setup_browser_proxy.js',
'ash/webui/common/resources/quick_unlock/lock_screen_constants.ts',
'ash/webui/common/resources/smb_shares/smb_browser_proxy.js',
'ash/webui/connectivity_diagnostics/resources/connectivity_diagnostics.ts',
'ash/webui/diagnostics_ui/resources/diagnostics_browser_proxy.ts',
'ash/webui/multidevice_debug/resources/logs.js',
'ash/webui/multidevice_debug/resources/webui.js',
'ash/webui/projector_app/resources/annotator/trusted/annotator_browser_proxy.js',
'ash/webui/projector_app/resources/app/trusted/projector_browser_proxy.js',
'ash/webui/scanning/resources/scanning_browser_proxy.ts',
),
), )
_BANNED_OBJC_FUNCTIONS: Sequence[BanRule] = (
BanRule(
'addTrackingRect:',
(
'The use of -[NSView addTrackingRect:owner:userData:assumeInside:] is'
'prohibited. Please use CrTrackingArea instead.',
'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
),
False,
),
BanRule(
r'/NSTrackingArea\W',
(
'The use of NSTrackingAreas is prohibited. Please use CrTrackingArea',
'instead.',
'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
),
False,
),
BanRule(
'convertPointFromBase:',
(
'The use of -[NSView convertPointFromBase:] is almost certainly wrong.',
'Please use |convertPoint:(point) fromView:nil| instead.',
'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
),
True,
),
BanRule(
'convertPointToBase:',
(
'The use of -[NSView convertPointToBase:] is almost certainly wrong.',
'Please use |convertPoint:(point) toView:nil| instead.',
'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
),
True,
),
BanRule(
'convertRectFromBase:',
(
'The use of -[NSView convertRectFromBase:] is almost certainly wrong.',
'Please use |convertRect:(point) fromView:nil| instead.',
'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
),
True,
),
BanRule(
'convertRectToBase:',
(
'The use of -[NSView convertRectToBase:] is almost certainly wrong.',
'Please use |convertRect:(point) toView:nil| instead.',
'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
),
True,
),
BanRule(
'convertSizeFromBase:',
(
'The use of -[NSView convertSizeFromBase:] is almost certainly wrong.',
'Please use |convertSize:(point) fromView:nil| instead.',
'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
),
True,
),
BanRule(
'convertSizeToBase:',
(
'The use of -[NSView convertSizeToBase:] is almost certainly wrong.',
'Please use |convertSize:(point) toView:nil| instead.',
'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
),
True,
),
BanRule(
r'/\s+UTF8String\s*]',
(
'The use of -[NSString UTF8String] is dangerous as it can return null',
'even if |canBeConvertedToEncoding:NSUTF8StringEncoding| returns YES.',
'Please use |SysNSStringToUTF8| instead.',
),
True,
excluded_paths=('^third_party/ocmock/OCMock/', ),
),
BanRule(
r'__unsafe_unretained',
(
'The use of __unsafe_unretained is almost certainly wrong, unless',
'when interacting with NSFastEnumeration or NSInvocation.',
'Please use __weak in files build with ARC, nothing otherwise.',
),
False,
),
BanRule(
'freeWhenDone:NO',
(
'The use of "freeWhenDone:NO" with the NoCopy creation of ',
'Foundation types is prohibited.',
),
True,
),
BanRule(
'This file requires ARC support.',
(
'ARC compilation is default in Chromium; do not add boilerplate to ',
'files that require ARC.',
),
True,
),
)
_BANNED_IOS_OBJC_FUNCTIONS = (
BanRule(
r'/\bTEST[(]',
('TEST() macro should not be used in Objective-C++ code as it does not ',
'drain the autorelease pool at the end of the test. Use TEST_F() ',
'macro instead with a fixture inheriting from PlatformTest (or a ',
'typedef).'),
True,
),
BanRule(
r'/\btesting::Test\b',
('testing::Test should not be used in Objective-C++ code as it does ',
'not drain the autorelease pool at the end of the test. Use ',
'PlatformTest instead.'),
True,
),
BanRule(
' systemImageNamed:',
('+[UIImage systemImageNamed:] should not be used to create symbols.',
'Instead use a wrapper defined in:',
'ios/chrome/browser/shared/ui/symbols/symbol_helpers.h'),
True,
excluded_paths=(
'ios/chrome/browser/shared/ui/symbols/symbol_helpers.mm',
'ios/chrome/common',
r'^ios/chrome/\w+_extension/',
),
),
BanRule(
r'public (RefCounted)?BrowserStateKeyedServiceFactory',
('KeyedService factories in //ios/chrome/browser should inherit from',
'(Refcounted)?ProfileKeyedServieFactoryIOS, not directory from',
'(Refcounted)?BrowserStateKeyedServiceFactory.'),
treat_as_error=True,
excluded_paths=(
'ios/components',
'ios/web_view',
),
),
)
_BANNED_IOS_EGTEST_FUNCTIONS: Sequence[BanRule] = (BanRule(
r'/\bEXPECT_OCMOCK_VERIFY\b',
('EXPECT_OCMOCK_VERIFY should not be used in EarlGrey tests because ',
'it is meant for GTests. Use [mock verify] instead.'),
True,
), )
_BANNED_CPP_FUNCTIONS: Sequence[BanRule] = (
BanRule(
'%#0',
(
'Zero-padded values that use "#" to add prefixes don\'t exhibit ',
'consistent behavior, since the prefix is not prepended for zero ',
'values. Use "0x%0..." instead.',
),
False,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\busing namespace ',
(
'Using directives (\'using namespace x\') are banned by the Google Style',
'Guide ( http://google.github.io/styleguide/cppguide.html#Namespaces ).',
'Explicitly qualify symbols or use using declarations (\'using x::foo\').',
),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
'FRIEND_TEST(',
(
'Chromium code should not use gtest\'s FRIEND_TEST() macro. Include',
'base/gtest_prod_util.h and use FRIEND_TEST_ALL_PREFIXES() instead.',
),
False,
excluded_paths=(
'base/gtest_prod_util.h',
'base/allocator/partition_allocator/src/partition_alloc/partition_alloc_base/gtest_prod_util.h',
),
),
BanRule(
r'/HANDLE_EINTR\(.*close',
('HANDLE_EINTR(close) is invalid. If close fails with EINTR, the file',
'descriptor will be closed, and it is incorrect to retry the close.',
'Either call close directly and ignore its return value, or wrap close',
'in IGNORE_EINTR to use its return value. See http://crbug.com/269623'
),
True,
(),
),
BanRule(
r'/IGNORE_EINTR\((?!.*close)',
(
'IGNORE_EINTR is only valid when wrapping close. To wrap other system',
'calls, use HANDLE_EINTR. See http://crbug.com/269623',
),
True,
(
r'^base/posix/eintr_wrapper\.h$',
r'^ppapi/tests/test_broker\.cc$',
),
),
BanRule(
r'/v8::Extension\(',
(
'Do not introduce new v8::Extensions into the code base, use',
'gin::Wrappable instead. See http://crbug.com/334679',
),
True,
(r'extensions/renderer/safe_builtins\.*', ),
),
BanRule(
'#pragma comment(lib,',
('Specify libraries to link with in build files and not in the source.',
),
True,
(
r'^base/third_party/symbolize/.*',
r'^third_party/abseil-cpp/.*',
r'^third_party/grpc/source/.*',
),
),
BanRule(
r'/base::SequenceChecker\b',
('Consider using SEQUENCE_CHECKER macros instead of the class directly.',
),
False,
(),
),
BanRule(
r'/base::ThreadChecker\b',
('Consider using THREAD_CHECKER macros instead of the class directly.',
),
False,
(),
),
BanRule(
r'/\b(?!(Sequenced|SingleThread))\w*TaskRunner::(GetCurrentDefault|CurrentDefaultHandle)',
(
'It is not allowed to call these methods from the subclasses ',
'of Sequenced or SingleThread task runners.',
),
True,
(),
),
BanRule(
r'/(Time(|Delta|Ticks)|ThreadTicks)::FromInternalValue|ToInternalValue',
(
'base::TimeXXX::FromInternalValue() and ToInternalValue() are',
'deprecated (http://crbug.com/634507). Please avoid converting away',
'from the Time types in Chromium code, especially if any math is',
'being done on time values. For interfacing with platform/library',
'APIs, use base::Time::(From,To)DeltaSinceWindowsEpoch() or',
'base::{TimeDelta::In}Microseconds(), or one of the other type',
'converter methods instead. For faking TimeXXX values (for unit',
'testing only), use TimeXXX() + Microseconds(N). For',
'other use cases, please contact base/time/OWNERS.',
),
False,
excluded_paths=(
'base/time/time.h',
'base/allocator/partition_allocator/src/partition_alloc/partition_alloc_base/time/time.h',
),
),
BanRule(
'CallJavascriptFunctionUnsafe',
(
'Don\'t use CallJavascriptFunctionUnsafe() in new code. Instead, use',
'AllowJavascript(), OnJavascriptAllowed()/OnJavascriptDisallowed(),',
'and CallJavascriptFunction(). See https://goo.gl/qivavq.',
),
False,
(
r'^content/browser/webui/web_ui_impl\.(cc|h)$',
r'^content/public/browser/web_ui\.h$',
r'^content/public/test/test_web_ui\.(cc|h)$',
),
),
BanRule(
'leveldb::DB::Open',
(
'Instead of leveldb::DB::Open() use leveldb_env::OpenDB() from',
'third_party/leveldatabase/env_chromium.h. It exposes databases to',
"Chrome's tracing, making their memory usage visible.",
),
True,
(r'^third_party/leveldatabase/.*\.(cc|h)$', ),
),
BanRule(
'leveldb::NewMemEnv',
(
'Instead of leveldb::NewMemEnv() use leveldb_chrome::NewMemEnv() from',
'third_party/leveldatabase/leveldb_chrome.h. It exposes environments',
"to Chrome's tracing, making their memory usage visible.",
),
True,
(r'^third_party/leveldatabase/.*\.(cc|h)$', ),
),
BanRule(
'base::ScopedMockTimeMessageLoopTaskRunner',
(
'ScopedMockTimeMessageLoopTaskRunner is deprecated. Prefer',
'TaskEnvironment::TimeSource::MOCK_TIME. There are still a',
'few cases that may require a ScopedMockTimeMessageLoopTaskRunner',
'(i.e. mocking the main MessageLoopForUI in browser_tests), but check',
'with gab@ first if you think you need it)',
),
False,
(),
),
BanRule(
'\brand\b',
(
'Use base/rand_util.h instead of rand(). The result of rand() is',
'based on a global random seed, which can be fully deterministic,',
'instead of random, if called at a specific time during startup.',
),
True,
(),
),
BanRule(
'\bstd::aligned_(storage|union)\b',
('std::aligned_storage and std::aligned_union are deprecated in',
'C++23. Use an aligned char array instead.'),
True,
(),
),
BanRule(
'std::regex',
(
'Using std::regex adds unnecessary binary size to Chrome. Please use',
're2::RE2 instead (crbug.com/755321)',
),
True,
[
'^third_party/crashpad/crashpad/third_party/'
r'cpp-httplib/cpp-httplib/httplib\.h$',
'third_party/abseil-cpp/.*_benchmark.cc',
],
),
BanRule(
r'/\bstd::sto(i|l|ul|ll|ull)\b',
(
'std::sto{i,l,ul,ll,ull}() use exceptions to communicate results. ',
'Use base::StringTo[U]Int[64]() instead.',
),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bstd::sto(f|d|ld)\b',
(
'std::sto{f,d,ld}() use exceptions to communicate results. ',
'For locale-independent values, e.g. reading numbers from disk',
'profiles, use base::StringToDouble().',
'For user-visible values, parse using ICU.',
),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bstd::to_string\b',
(
'std::to_string() is locale dependent and slower than alternatives.',
'For locale-independent strings, e.g. writing numbers to disk',
'profiles, use base::NumberToString().',
'For user-visible strings, use base::FormatNumber() and',
'the related functions in base/i18n/number_formatting.h.',
),
True,
[
'third_party/blink/renderer/core/css/parser/css_proto_converter.cc',
'third_party/blink/renderer/core/editing/ime/edit_context.cc',
'third_party/blink/renderer/platform/graphics/bitmap_image_test.cc',
_THIRD_PARTY_EXCEPT_BLINK
],
),
BanRule(
r'/#include <(cctype|ctype\.h|cwctype|wctype.h)>',
(
'<cctype>/<ctype.h>/<cwctype>/<wctype.h> are banned. Use',
'"third_party/abseil-cpp/absl/strings/ascii.h" instead.',
),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bstd::shared_ptr\b',
('std::shared_ptr is banned. Use scoped_refptr instead.', ),
True,
[
r'^third_party/blink/renderer/core/typed_arrays/array_buffer/' +
r'array_buffer_contents\.(cc|h)',
r'^third_party/blink/renderer/core/typed_arrays/dom_array_buffer\.cc',
'^third_party/blink/renderer/bindings/core/v8/' +
'v8_wasm_response_extensions.cc',
r'^gin/array_buffer\.(cc|h)',
r'^gin/per_isolate_data\.(cc|h)',
'^chrome/services/sharing/nearby/',
r'^base/profiler/libunwindstack_unwinder_android\.(cc|h)',
r'^base/profiler/native_unwinder_android_memory_regions_map_impl.(cc|h)',
'^third_party/boringssl/',
'^net/cert/',
'^net/tools/cert_verify_tool/',
'^services/cert_verifier/',
'^components/certificate_transparency/',
'^components/media_router/common/providers/cast/certificate/',
'^components/trusted_vault/',
'^chromeos/ash/services/libassistant/grpc/',
'^chromecast/cast_core/grpc',
'^chromecast/cast_core/runtime/browser',
r'^ios/chrome/test/earl_grey/chrome_egtest_plugin_client\.(mm|h)',
r'^base/fuchsia/.*\.(cc|h)',
r'.*fuchsia.*test\.(cc|h)',
'^tools/clang/plugins/',
r'^content/app/android/javaless_child_process_service\.cc',
_THIRD_PARTY_EXCEPT_BLINK
],
),
BanRule(
r'/\bstd::weak_ptr\b',
('std::weak_ptr is banned. Use base::WeakPtr instead.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\blong long\b',
('long long is banned. Use [u]int64_t instead.', ),
False,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\b(absl|std)::any\b',
(
'{absl,std}::any are banned due to incompatibility with the component ',
'build.',
),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bstd::bind\b',
(
'std::bind() is banned because of lifetime risks. Use ',
'base::Bind{Once,Repeating}() instead.',
),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
(r'/\bstd::(?:'
r'linear_congruential_engine|mersenne_twister_engine|'
r'subtract_with_carry_engine|discard_block_engine|'
r'independent_bits_engine|shuffle_order_engine|'
r'minstd_rand0?|mt19937(_64)?|ranlux(24|48)(_base)?|knuth_b|'
r'default_random_engine|'
r'random_device|'
r'seed_seq'
r')\b'),
(
'STL random number engines and generators are banned. Use the ',
'helpers in base/rand_util.h instead, e.g. base::RandBytes() or ',
'base::RandomBitGenerator.'
'',
'Please reach out to cxx@chromium.org if the base APIs are ',
'insufficient for your needs.',
),
True,
[
_THIRD_PARTY_EXCEPT_BLINK,
r'testing/libfuzzer',
r'testing/perf/confidence',
r'tools/android/io_benchmark/',
r'.+_fuzzer\.cc$',
r'.+_fuzzertest\.cc$',
r'ash/ambient/model/ambient_topic_queue\.cc',
r'base/allocator/partition_allocator/src/partition_alloc/partition_alloc_unittest\.cc',
r'base/test/launcher/test_launcher\.cc',
r'cc/metrics/video_playback_roughness_reporter_unittest\.cc',
r'chrome/browser/apps/app_service/metrics/website_metrics\.cc',
r'chrome/browser/ash/power/auto_screen_brightness/monotone_cubic_spline_unittest\.cc',
r'chrome/browser/ash/printing/zeroconf_printer_detector_unittest\.cc',
r'chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager_impl_unittest\.cc',
r'chrome/browser/nearby_sharing/contacts/nearby_share_contacts_sorter_unittest\.cc',
r'chrome/browser/privacy_budget/mesa_distribution_unittest\.cc',
r'chrome/browser/web_applications/test/web_app_test_utils\.cc',
r'chrome/browser/web_applications/test/web_app_test_utils\.cc',
r'chrome/browser/win/conflicts/module_blocklist_cache_util_unittest\.cc',
r'chromeos/ash/components/memory/userspace_swap/swap_storage_unittest\.cc',
r'chromeos/ash/components/memory/userspace_swap/userspace_swap\.cc',
r'components/metrics/metrics_state_manager\.cc',
r'components/omnibox/browser/history_quick_provider_performance_unittest\.cc',
r'components/zucchini/disassembler_elf_unittest\.cc',
r'content/browser/webid/federated_auth_request_impl\.cc',
r'content/browser/webid/federated_auth_request_impl\.cc',
r'media/cast/test/utility/udp_proxy\.h',
r'sql/recover_module/module_unittest\.cc',
r'components/regional_capabilities/regional_capabilities_utils.cc',
],
),
BanRule(
r'/\b(absl,std)::bind_front\b',
('{absl,std}::bind_front() are banned. Use base::Bind{Once,Repeating}() '
'instead.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bABSL_FLAG\b',
('ABSL_FLAG is banned. Use base::CommandLine instead.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\babsl::c_',
('Abseil container utilities are banned. Use std::ranges:: instead.',
),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\babsl::FixedArray\b',
('absl::FixedArray is banned. Use base::FixedArray instead.', ),
True,
[
r'^base/types/fixed_array.h',
_THIRD_PARTY_EXCEPT_BLINK,
],
),
BanRule(
r'/\babsl::FunctionRef\b',
('absl::FunctionRef is banned. Use base::FunctionRef instead.', ),
True,
[
r'^base/functional/bind_internal\.h',
r'^base/functional/function_ref.*\..+',
_THIRD_PARTY_EXCEPT_BLINK,
],
),
BanRule(
r'/\babsl::(Insecure)?BitGen\b',
('absl random number generators are banned. Use the helpers in '
'base/rand_util.h instead, e.g. base::RandBytes() or ',
'base::RandomBitGenerator.'),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
pattern=r'/\babsl::(optional|nullopt|make_optional)\b',
explanation=('absl::optional is banned. Use std::optional instead.', ),
treat_as_error=True,
excluded_paths=[
_THIRD_PARTY_EXCEPT_BLINK,
]),
BanRule(
r'/(\babsl::Span\b|#include <span>|\bstd::span\b)',
('absl::Span and std::span are banned. Use base::span instead.', ),
True,
[
r'base/containers/span.h',
r'base/containers/span_unittest.cc',
r'base/numerics/.*'
r'android_webview/browser/ip_protection/.*',
r'components/ip_protection/.*',
r'net/third_party/quiche/overrides/quiche_platform_impl/quiche_stack_trace_impl\.*',
r'services/network/web_transport\.cc',
_THIRD_PARTY_EXCEPT_BLINK,
],
),
BanRule(
r'/\babsl::StatusOr\b',
('absl::StatusOr is banned. Use base::expected instead.', ),
True,
[
r'components/url_pattern/.*',
r'services/network/shared_dictionary/simple_url_pattern_matcher\.cc',
r'third_party/blink/renderer/core/url_pattern/.*',
r'third_party/blink/renderer/modules/manifest/manifest_parser\.cc',
r'android_webview/browser/ip_protection/.*',
r'components/ip_protection/.*',
r'net/quic/dedicated_web_transport_http3_client\.cc',
r'components/media_effects/.*\.cc',
_THIRD_PARTY_EXCEPT_BLINK
],
),
BanRule(
r'/\babsl::(StrSplit|StrJoin|StrCat|StrAppend|Substitute|StrContains)\b',
('Abseil string utilities are banned. Use base/strings instead.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\babsl::(Mutex|CondVar|Notification|Barrier|BlockingCounter)\b',
(
'Abseil synchronization primitives are banned. Use',
'base/synchronization instead.',
),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\babsl::(Duration|Time|TimeZone|CivilDay)\b',
('Abseil\'s time library is banned. Use base/time instead.', ),
True,
[
r'android_webview/browser/ip_protection/.*',
r'components/ip_protection/.*',
r'chrome/services/sharing/nearby/platform/input_file.cc',
r'chrome/services/sharing/nearby/platform/input_file.h',
r'chrome/services/sharing/nearby/platform/output_file.cc',
r'chrome/services/sharing/nearby/platform/output_file.h',
r'components/cross_device/nearby/system_clock.cc',
_THIRD_PARTY_EXCEPT_BLINK
],
),
BanRule(
r'/absl::(bad_variant_access|get|holds_alternative|monostate|variant|'
r'visit)',
('Abseil\'s variant library is banned, use std.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/absl::(apply|exchange|forward|in_place|index_sequence|'
r'integer_sequence|make_from_tuple|make_index_sequence|'
r'make_integer_sequence|move)',
('Abseil\'s util library is banned, use std.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/#include <chrono>',
('<chrono> is banned. Use base/time instead.', ),
True,
[
_THIRD_PARTY_EXCEPT_BLINK,
'components/openscreen_platform/task_runner.cc',
]),
BanRule(
r'/#include <exception>',
('Exceptions are banned and disabled in Chromium.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bstd::function\b',
('std::function is banned. Use base::{Once,Repeating}Callback instead.',
),
True,
[
r'base/functional/callback_helpers_unittest\.cc',
r'base/tracing/perfetto_task_runner\.cc',
r'base/tracing/perfetto_task_runner\.h',
r'chrome/services/sharing/nearby/nearby_connections_conversions\.cc'
r'chromeos/ash/services/libassistant/callback_utils\.h',
r'fuchsia_web/webengine/browser/context_impl_browsertest\.cc',
r'fuchsia_web/webengine/browser/cookie_manager_impl_unittest\.cc',
r'fuchsia_web/webengine/browser/media_player_impl_unittest\.cc',
r'services/on_device_model/ml/chrome_ml_api\.h',
r'services/on_device_model/ml/on_device_model_executor\.cc',
r'services/on_device_model/ml/on_device_model_executor\.h',
r'components/tracing/common/etw_consumer_win_unittest\.cc',
r'components/tracing/common/etw_system_data_source_win\.cc',
r'components/tracing/common/etw_consumer_win\.h',
r'components/tracing/common/etw_consumer_win\.cc',
r'services/tracing/public/cpp/perfetto/custom_event_recorder\.cc',
r'services/tracing/public/cpp/perfetto/perfetto_traced_process\.cc',
r'services/tracing/public/cpp/perfetto/perfetto_traced_process\.h',
r'services/tracing/public/cpp/perfetto/perfetto_tracing_backend\.cc',
r'services/tracing/public/cpp/perfetto/producer_client\.cc',
r'services/tracing/public/cpp/perfetto/producer_client\.h',
r'services/tracing/public/cpp/perfetto/producer_test_utils\.cc',
r'services/tracing/public/cpp/perfetto/producer_test_utils\.h',
r'third_party/blink/renderer/modules/peerconnection/mock_peer_connection_impl\.cc',
r'third_party/blink/renderer/modules/peerconnection/mock_peer_connection_impl\.h',
r'base/allocator/dispatcher/initializer_unittest\.cc',
r'chrome/browser/ash/accessibility/speech_monitor\.cc',
r'chrome/browser/ash/accessibility/speech_monitor\.h',
r'chrome/browser/ash/login/ash_hud_login_browsertest\.cc',
r'chromecast/base/observer_unittest\.cc',
r'chromecast/browser/cast_web_view\.h',
r'chromecast/public/cast_media_shlib\.h',
r'device/bluetooth/floss/exported_callback_manager\.h',
r'device/bluetooth/floss/floss_dbus_client\.h',
r'device/fido/cable/v2_handshake_unittest\.cc',
r'device/fido/pin\.cc',
r'services/tracing/perfetto/test_utils\.h',
r'chrome/browser/media/webrtc/test_stats_dictionary\.cc',
r'chrome/browser/media/webrtc/test_stats_dictionary\.h',
r'chromeos/ash/services/libassistant/device_settings_controller\.cc',
r'components/browser_ui/client_certificate/android/ssl_client_certificate_request\.cc',
r'components/gwp_asan/client/sampling_malloc_shims_unittest\.cc',
r'content/browser/font_unique_name_lookup/font_unique_name_lookup_unittest\.cc',
r'components/omnibox/browser/autocomplete_result\.cc',
r'device/fido/win/webauthn_api\.cc',
r'media/audio/alsa/alsa_util\.cc',
r'media/remoting/stream_provider\.h',
r'sql/vfs_wrapper\.cc',
r'extensions/renderer/api/automation/automation_internal_custom_bindings\.cc',
r'extensions/renderer/api/automation/automation_internal_custom_bindings\.h',
r'ui/views/controls/focus_ring\.h',
r'tools/binary_size/libsupersize/viewer/caspian/diff\.cc',
r'tools/binary_size/libsupersize/viewer/caspian/model\.cc',
r'tools/binary_size/libsupersize/viewer/caspian/model\.h',
r'tools/binary_size/libsupersize/viewer/caspian/tree_builder\.h',
r'tools/clang/base_bind_rewriters/BaseBindRewriters\.cpp',
_THIRD_PARTY_EXCEPT_BLINK
],
),
BanRule(
r'/#include <X11/',
('Do not use Xlib. Use xproto (from //ui/gfx/x:xproto) instead.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bstd::ratio\b',
('std::ratio is banned by the Google Style Guide.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bstd::aligned_alloc\b',
(
'std::aligned_alloc() is not yet allowed (crbug.com/1412818). Use ',
'base::AlignedAlloc() instead.',
),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/#include <(barrier|latch|semaphore|stop_token)>',
('The thread support library is banned. Use base/synchronization '
'instead.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bstd::execution::(par|seq)\b',
('std::execution::(par|seq) is banned; they do not fit into '
' Chrome\'s threading model, and libc++ doesn\'t have full '
'support.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bstd::bit_cast\b',
('std::bit_cast is banned; use base::bit_cast instead for values and '
'standard C++ casting when pointers are involved.', ),
True,
[
_THIRD_PARTY_EXCEPT_BLINK,
r'base/numerics/.*'
],
),
BanRule(
r'/\bstd::(c8rtomb|mbrtoc8)\b',
('std::c8rtomb() and std::mbrtoc8() are banned.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bchar8_t|std::u8string\b',
(
'char8_t and std::u8string are not yet allowed. Can you use [unsigned]',
' char and std::string instead?',
),
True,
[
r'base/third_party/symbolize/demangle\.cc',
_THIRD_PARTY_EXCEPT_BLINK
],
),
BanRule(
r'/(\b(co_await|co_return|co_yield)\b|#include <coroutine>)',
('Coroutines are not yet allowed (https://crbug.com/1403840).', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/^\s*(export\s|import\s+["<:\w]|module(;|\s+[:\w]))',
('Modules are disallowed for now due to lack of toolchain support.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\[\[(\w*::)?no_unique_address\]\]',
(
'[[no_unique_address]] does not work as expected on Windows ',
'(https://crbug.com/1414621). Use NO_UNIQUE_ADDRESS instead.',
),
True,
[
r'^base/compiler_specific\.h',
r'^base/allocator/partition_allocator/src/partition_alloc/partition_alloc_base/compiler_specific\.h',
_THIRD_PARTY_EXCEPT_BLINK,
],
),
BanRule(
r'/#include <format>',
('<format> is not yet allowed. Use base::StringPrintf() instead.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
pattern='std::views',
explanation=('Use of std::views is banned in Chrome. If you need this '
'functionality, please contact cxx@chromium.org.', ),
treat_as_error=True,
excluded_paths=[
_THIRD_PARTY_EXCEPT_BLINK
],
),
BanRule(
pattern=r'/std::ranges::(?!(?:' + '|'.join((
'begin',
'end',
'cbegin',
'cend',
'rbegin',
'rend',
'crbegin',
'crend',
'size',
'ssize',
'empty',
'data',
'cdata',
'iterator_t',
'const_iterator_t',
'sentinel_t',
'const_sentinel_t',
'range_difference_t',
'range_size_t',
'range_value_t',
'range_reference_t',
'range_const_reference_t',
'range_rvalue_reference_t',
'range_common_reference_t',
'dangling',
'borrowed_iterator_t',
'range',
'borrowed_range',
'sized_range',
'view',
'input_range',
'output_range',
'forward_range',
'bidirectional_range',
'random_access_range',
'contiguous_range',
'common_range',
'viewable_range',
'constant_range',
'subrange',
'subrange_kind',
'enable_borrowed_range',
'enable_view',
'all_of',
'any_of',
'none_of',
'for_each',
'for_each_n',
'count',
'count_if',
'mismatch',
'equal',
'lexicographical_compare',
'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_ranges',
'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',
'is_permutation',
'next_permutation',
'prev_premutation',
'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',
'destroy_n',
'destroy_at',
'construct_at',
'in_fun_result',
'in_in_result',
'in_out_result',
'in_in_out_result',
'in_out_out_result',
'min_max_result',
'in_found_result',
'equal_to',
'not_equal_to',
'greater',
'less',
'greater_equal',
'less_equal',
'advance',
'distance',
'next',
'prev',
)) + r')\b)\w+',
explanation=(
'Use of range views and associated helpers is banned in Chrome. '
'If you need this functionality, please contact cxx@chromium.org.',
),
treat_as_error=True,
excluded_paths=[
_THIRD_PARTY_EXCEPT_BLINK
],
),
BanRule(
r'/#include <regex>',
('<regex> is not allowed. Use third_party/re2 instead.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/#include <source_location>',
('<source_location> is not yet allowed. Use base/location.h instead.',
),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bstd::to_address\b',
(
'std::to_address is banned because it is not guaranteed to be',
'SFINAE-compatible. Use base::to_address from base/types/to_address.h',
'instead.',
),
True,
[
r'base/types/to_address.h',
_THIRD_PARTY_EXCEPT_BLINK
],
),
BanRule(
r'/#include <syncstream>',
('<syncstream> is banned.', ),
True,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
r'/\bRunMessageLoop\b',
('RunMessageLoop is deprecated, use RunLoop instead.', ),
False,
(),
),
BanRule(
'RunAllPendingInMessageLoop()',
(
'Prefer RunLoop over RunAllPendingInMessageLoop, please contact gab@',
"if you're convinced you need this.",
),
False,
(),
),
BanRule(
'RunAllPendingInMessageLoop(BrowserThread',
(
'RunAllPendingInMessageLoop is deprecated. Use RunLoop for',
'BrowserThread::UI, BrowserTaskEnvironment::RunIOThreadUntilIdle',
'for BrowserThread::IO, and prefer RunLoop::QuitClosure to observe',
'async events instead of flushing threads.',
),
False,
(),
),
BanRule(
r'MessageLoopRunner',
('MessageLoopRunner is deprecated, use RunLoop instead.', ),
False,
(),
),
BanRule(
'GetDeferredQuitTaskForRunLoop',
(
"GetDeferredQuitTaskForRunLoop shouldn't be needed, please contact",
'gab@ if you found a use case where this is the only solution.',
),
False,
(),
),
BanRule(
'sqlite3_initialize(',
(
'Instead of calling sqlite3_initialize(), depend on //sql, ',
'#include "sql/initialize.h" and use sql::EnsureSqliteInitialized().',
),
True,
(
r'^sql/initialization\.(cc|h)$',
r'^third_party/sqlite/.*\.(c|cc|h)$',
),
),
BanRule(
'CREATE VIEW',
(
'SQL views are disabled in Chromium feature code',
'https://chromium.googlesource.com/chromium/src/+/HEAD/sql#no-views',
),
True,
(
_THIRD_PARTY_EXCEPT_BLINK,
r'^sql/.*',
r'^infra/.*',
r'^tools/perf.*',
r'.*perfetto.*',
),
),
BanRule(
'CREATE VIRTUAL TABLE',
(
'SQL virtual tables are disabled in Chromium feature code',
'https://chromium.googlesource.com/chromium/src/+/HEAD/sql#no-virtual-tables',
),
True,
(
_THIRD_PARTY_EXCEPT_BLINK,
r'^sql/.*',
r'^tools/perf.*',
r'.*perfetto.*',
),
),
BanRule(
'std::random_shuffle',
('std::random_shuffle is deprecated in C++14, and removed in C++17. Use',
'base::RandomShuffle instead.'),
True,
(),
),
BanRule(
'ios/web/public/test/http_server',
('web::HTTPserver is deprecated use net::EmbeddedTestServer instead.',
),
False,
(),
),
BanRule(
'GetAddressOf',
('Improper use of Microsoft::WRL::ComPtr<T>::GetAddressOf() has been ',
'implicated in a few leaks. ReleaseAndGetAddressOf() is safe but ',
'operator& is generally recommended. So always use operator& instead. ',
'See http://crbug.com/914910 for more conversion guidance.'),
True,
(),
),
BanRule(
'SHFileOperation',
('SHFileOperation was deprecated in Windows Vista, and there are less ',
'complex functions to achieve the same goals. Use IFileOperation for ',
'any esoteric actions instead.'),
True,
(),
),
BanRule(
'StringFromGUID2',
('StringFromGUID2 introduces an unnecessary dependency on ole32.dll.',
'Use base::win::WStringFromGUID instead.'),
True,
(r'/base/win/win_util_unittest.cc', ),
),
BanRule(
'StringFromCLSID',
('StringFromCLSID introduces an unnecessary dependency on ole32.dll.',
'Use base::win::WStringFromGUID instead.'),
True,
(r'/base/win/win_util_unittest.cc', ),
),
BanRule(
'kCFAllocatorNull',
(
'The use of kCFAllocatorNull with the NoCopy creation of ',
'CoreFoundation types is prohibited.',
),
True,
(),
),
BanRule(
'mojo::ConvertTo',
('mojo::ConvertTo and TypeConverter are deprecated. Please consider',
'StructTraits / UnionTraits / EnumTraits / ArrayTraits / MapTraits /',
'StringTraits if you would like to convert between custom types and',
'the wire format of mojom types.'),
False,
(
r'^fuchsia_web/webengine/browser/url_request_rewrite_rules_manager\.cc$',
r'^fuchsia_web/webengine/url_request_rewrite_type_converters\.cc$',
r'^third_party/blink/.*\.(cc|h)$',
r'^content/renderer/.*\.(cc|h)$',
),
),
BanRule(
'GetInterfaceProvider',
('InterfaceProvider is deprecated.',
'Please use ExecutionContext::GetBrowserInterfaceBroker and overrides',
'or Platform::GetBrowserInterfaceBroker.'),
False,
(),
),
BanRule(
'CComPtr',
('New code should use Microsoft::WRL::ComPtr from wrl/client.h as a ',
'replacement for CComPtr from ATL. See http://crbug.com/5027 for more ',
'details.'),
False,
(),
),
BanRule(
r'/\b(IFACE|STD)METHOD_?\(',
('IFACEMETHOD() and STDMETHOD() make code harder to format and read.',
'Instead, always use IFACEMETHODIMP in the declaration.'),
False,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(
'RemoveAllChildViewsWithoutDeleting',
('RemoveAllChildViewsWithoutDeleting is deprecated.',
'This method is deemed dangerous as, unless raw pointers are re-added,',
'calls to this method introduce memory leaks.'),
False,
(),
),
BanRule(
r'/\bTRACE_EVENT(_NESTABLE)?_ASYNC_',
(
'Please use TRACE_EVENT_BEGIN/END/INSTANT macros instead',
'of TRACE_EVENT_ASYNC_.. and TRACE_EVENT_NESTABLE_ASYNC_... (crbug.com/432427382).',
),
False,
(
r'^base/trace_event/.*',
r'^base/tracing/.*',
),
),
BanRule(
'RoInitialize',
('Improper use of [base::win]::RoInitialize() has been implicated in a ',
'few COM initialization leaks. Use base::win::ScopedWinrtInitializer ',
'instead. See http://crbug.com/1197722 for more information.'),
True,
(
r'^base/win/scoped_winrt_initializer\.cc$',
r'^third_party/abseil-cpp/absl/.*',
),
),
BanRule(
r'base::Watchdog',
(
'base::Watchdog is deprecated because it creates its own thread.',
'Instead, manually start a timer on a SequencedTaskRunner.',
),
False,
(),
),
BanRule(
'base::Passed',
('Do not use base::Passed. It is a legacy helper for capturing ',
'move-only types with base::BindRepeating, but invoking the ',
'resulting RepeatingCallback moves the captured value out of ',
'the callback storage, and subsequent invocations may pass the ',
'value in a valid but undefined state. Prefer base::BindOnce().',
'See http://crbug.com/1326449 for context.'),
False,
(
r'^base[\\/]functional[\\/]bind\.h',
r'^base[\\/]functional[\\/]bind_internal\.h',
),
),
BanRule(
r'base::Feature k',
('Please use BASE_DECLARE_FEATURE() or BASE_FEATURE() instead of ',
'directly declaring/defining features.'),
True,
[
r'^base/feature_list\.h',
],
),
BanRule(
r'/\bchartorune\b',
('chartorune is not memory-safe, unless you can guarantee the input ',
'string is always null-terminated. Otherwise, please use charntorune ',
'from libphonenumber instead.'),
True,
[
_THIRD_PARTY_EXCEPT_BLINK,
],
),
BanRule(
r'/\b#include "base/atomicops\.h"\b',
('Do not use base::subtle atomics, but std::atomic, which are simpler '
'to use, have better understood, clearer and richer semantics, and are '
'harder to mis-use. See details in base/atomicops.h.', ),
False,
[_THIRD_PARTY_EXCEPT_BLINK],
),
BanRule(r'CrossThreadPersistent<', (
'Do not use blink::CrossThreadPersistent, but '
'blink::CrossThreadHandle. It is harder to mis-use.', 'More info: '
'https://docs.google.com/document/d/1GIT0ysdQ84sGhIo1r9EscF_fFt93lmNVM_q4vvHj2FQ/edit#heading=h.3e4d6y61tgs',
'Please contact platform-architecture-dev@ before adding new instances.'
), False, []),
BanRule(r'CrossThreadWeakPersistent<', (
'Do not use blink::CrossThreadWeakPersistent, but '
'blink::CrossThreadWeakHandle. It is harder to mis-use.', 'More info: '
'https://docs.google.com/document/d/1GIT0ysdQ84sGhIo1r9EscF_fFt93lmNVM_q4vvHj2FQ/edit#heading=h.3e4d6y61tgs',
'Please contact platform-architecture-dev@ before adding new instances.'
), False, []),
BanRule(r'objc/objc.h', (
'Do not include <objc/objc.h>. It defines away ARC lifetime '
'annotations, and is thus dangerous.',
'Please use the pimpl pattern; search for `ObjCStorage` for examples.',
'For further reading on how to safely mix C++ and Obj-C, see',
'https://chromium.googlesource.com/chromium/src/+/main/docs/mac/mixing_cpp_and_objc.md'
), True, []),
BanRule(
r'/#include <filesystem>',
('libc++ <filesystem> is banned per the Google C++ styleguide.', ),
True,
(r'third_party/centipede'),
),
BanRule(
r'TopDocument()',
('TopDocument() does not work correctly with out-of-process iframes. '
'Please do not introduce new uses.', ),
True,
(
r'^third_party/blink/renderer/core/dom/document\.cc',
r'^third_party/blink/renderer/core/dom/document\.h',
r'^third_party/blink/renderer/core/dom/element\.cc',
r'^third_party/blink/renderer/core/exported/web_disallow_transition_scope_test\.cc',
r'^third_party/blink/renderer/core/exported/web_document_test\.cc',
r'^third_party/blink/renderer/core/html/html_anchor_element\.cc',
r'^third_party/blink/renderer/core/html/html_dialog_element\.cc',
r'^third_party/blink/renderer/core/html/html_element\.cc',
r'^third_party/blink/renderer/core/html/html_frame_owner_element\.cc',
r'^third_party/blink/renderer/core/html/media/video_wake_lock\.cc',
r'^third_party/blink/renderer/core/loader/anchor_element_interaction_tracker\.cc',
r'^third_party/blink/renderer/core/page/scrolling/root_scroller_controller\.cc',
r'^third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller\.cc',
r'^third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller\.h',
r'^third_party/blink/renderer/core/script/classic_pending_script\.cc',
r'^third_party/blink/renderer/core/script/script_loader\.cc',
),
),
BanRule(
pattern=r'base::raw_ptr<',
explanation=('Do not use base::raw_ptr, use raw_ptr.', ),
treat_as_error=True,
excluded_paths=(
'^base/',
'^tools/',
),
),
BanRule(
pattern=r'base:raw_ref<',
explanation=('Do not use base::raw_ref, use raw_ref.', ),
treat_as_error=True,
excluded_paths=(
'^base/',
'^tools/',
),
),
BanRule(
pattern=r'/raw_ptr<[^;}]*\w{};',
explanation=(
'Do not use {} for raw_ptr initialization, use = nullptr instead.',
),
treat_as_error=True,
excluded_paths=(
'^base/',
'^tools/',
),
),
BanRule(
pattern=r'/#include "base/allocator/.*/raw_'
r'(ptr|ptr_cast|ptr_exclusion|ref).h"',
explanation=(
'Please include the corresponding facade headers:',
'- #include "base/memory/raw_ptr.h"',
'- #include "base/memory/raw_ptr_cast.h"',
'- #include "base/memory/raw_ptr_exclusion.h"',
'- #include "base/memory/raw_ref.h"',
),
treat_as_error=True,
excluded_paths=(
'^base/',
'^tools/',
),
),
BanRule(
pattern=r'ContentSettingsType::COOKIES',
explanation=
('Do not use ContentSettingsType::COOKIES to check whether cookies are '
'supported in the provided context. Instead rely on the '
'content_settings::CookieSettings API. If you are using '
'ContentSettingsType::COOKIES to check the user preference setting '
'specifically, disregard this warning.', ),
treat_as_error=False,
excluded_paths=(
'^chrome/browser/ui/content_settings/',
'^components/content_settings/',
'^services/network/cookie_settings.cc',
'.*test.cc',
),
),
BanRule(
pattern=r'ContentSettingsType::TRACKING_PROTECTION',
explanation=
('Do not directly use ContentSettingsType::TRACKING_PROTECTION to check '
'for tracking protection exceptions. Instead rely on the '
'privacy_sandbox::TrackingProtectionSettings API.', ),
treat_as_error=False,
excluded_paths=(
'^chrome/browser/ui/content_settings/',
'^components/content_settings/',
'^components/privacy_sandbox/tracking_protection_settings.cc',
'.*test.cc',
),
),
BanRule(
pattern=r'/\bg_signal_connect',
explanation=('Use ScopedGSignal instead of g_signal_connect*()', ),
treat_as_error=True,
excluded_paths=('^ui/base/glib/scoped_gsignal.h', ),
),
BanRule(
pattern=r'features::kIsolatedWebApps',
explanation=(
'Do not use `features::kIsolatedWebApps` directly to guard Isolated ',
'Web App code. ',
'Use `content::AreIsolatedWebAppsEnabled()` in the browser process '
'or check the `kEnableIsolatedWebAppsInRenderer` command line flag '
'in the renderer process.',
),
treat_as_error=True,
excluded_paths=_TEST_CODE_EXCLUDED_PATHS + (
'^chrome/browser/about_flags.cc',
'^chrome/browser/component_updater/iwa_key_distribution_component_installer.cc',
'^chrome/browser/web_applications/isolated_web_apps/chrome_content_browser_client_isolated_web_apps_part.cc',
'^chrome/browser/ui/startup/bad_flags_prompt.cc',
'^content/shell/browser/shell_content_browser_client.cc',
)),
BanRule(
pattern=r'features::kIsolatedWebAppDevMode',
explanation=(
'Do not use `features::kIsolatedWebAppDevMode` directly to guard code ',
'related to Isolated Web App Developer Mode. ',
'Use `web_app::IsIwaDevModeEnabled()` instead.',
),
treat_as_error=True,
excluded_paths=_TEST_CODE_EXCLUDED_PATHS + (
'^chrome/browser/about_flags.cc',
'^chrome/browser/web_applications/isolated_web_apps/isolated_web_app_features.cc',
'^chrome/browser/ui/startup/bad_flags_prompt.cc',
)),
BanRule(
pattern=r'features::kIsolatedWebAppUnmanagedInstall',
explanation=(
'Do not use `features::kIsolatedWebAppUnmanagedInstall` directly to ',
'guard code related to unmanaged install flow for Isolated Web Apps. ',
'Use `web_app::IsIwaUnmanagedInstallEnabled()` instead.',
),
treat_as_error=True,
excluded_paths=_TEST_CODE_EXCLUDED_PATHS + (
'^chrome/browser/about_flags.cc',
'^chrome/browser/web_applications/isolated_web_apps/isolated_web_app_features.cc',
)),
BanRule(
pattern='/(CUIAutomation|AccessibleObjectFromWindow)',
explanation=
('Direct usage of UIAutomation or IAccessible2 in client code is '
'discouraged in Chromium, as it is not an assistive technology and '
'should not rely on accessibility APIs directly. These APIs can '
'introduce significant performance overhead. However, if you believe '
'your use case warrants an exception, please discuss it with an '
'accessibility owner before proceeding. For more information on the '
'performance implications, see https://docs.google.com/document/d/1jN4itpCe_bDXF0BhFaYwv4xVLsCWkL9eULdzjmLzkuk/edit#heading=h.pwth3nbwdub0.',
),
treat_as_error=False,
),
BanRule(
pattern=r'/WIDGET_OWNS_NATIVE_WIDGET|'
r'NATIVE_WIDGET_OWNS_WIDGET',
explanation=
('WIDGET_OWNS_NATIVE_WIDGET and NATIVE_WIDGET_OWNS_WIDGET are in the '
'process of being deprecated. Consider using the new '
'CLIENT_OWNS_WIDGET ownership model. Eventually, this will be the only '
'available ownership model available and the associated enumeration '
'will be removed.', ),
treat_as_error=False,
),
BanRule(
pattern='ProfileManager::GetLastUsedProfile',
explanation=
('Most code should already be scoped to a Profile. Pass in a Profile* '
'or retrieve from an existing entity with a reference to the Profile '
'(e.g. WebContents).', ),
treat_as_error=False,
),
BanRule(
pattern=(r'/FindBrowserWithUiElementContext|'
r'FindBrowserWithTab|'
r'FindBrowserWithGroup|'
r'FindTabbedBrowser|'
r'FindAnyBrowser|'
r'FindBrowserWithProfile|'
r'FindLastActive|'
r'FindBrowserWithActiveWindow'),
explanation=
('Most code should already be scoped to a Browser. Pass in a Browser* '
'or retrieve from an existing entity with a reference to the Browser.',
),
treat_as_error=False,
),
BanRule(
pattern=r'subspan(0u,',
explanation=(
'Prefer first(n) over subspan(0u, n) as it is shorter, and the '
'compiler may have to emit a branch for the n == dynamic_extent '
'case of subspan().', ),
treat_as_error=False,
),
BanRule(
pattern=r'UNSAFE_TODO(',
explanation=(
'Do not use UNSAFE_TODO() to write new unsafe code. Use only when '
'removing a pre-existing file-wide allow_unsafe_buffers pragma, or '
'when incrementally converting code off of unsafe interfaces', ),
treat_as_error=False,
),
BanRule(
pattern='#pragma allow_unsafe_buffers',
explanation=
('Do not use allow_unsafe_buffers to write new unsafe code. Use only '
'when enabling unsafe buffers checks under a new uncovered path.', ),
treat_as_error=False,
),
BanRule(
pattern=r'UNSAFE_BUFFERS(',
explanation=
('Try to avoid using UNSAFE_BUFFERS() if at all possible. Otherwise, '
'be sure to justify in a // SAFETY comment why other options are not '
'available, and why the code is safe.', ),
treat_as_error=False,
),
BanRule(
pattern='BrowserWithTestWindowTest',
explanation=
('Do not use BrowserWithTestWindowTest. By instantiating an instance '
'of class Browser, the test is no longer a unit test but is instead a '
'browser test. The class BrowserWithTestWindowTest forces production '
'logic to take on test-only conditionals, which is an anti-pattern. '
'Features should be performing dependency injection rather than '
'directly using class Browser. See '
'docs/chrome_browser_design_principles.md for more details.', ),
treat_as_error=False,
),
BanRule(
pattern='TestWithBrowserView',
explanation=
('Do not use TestWithBrowserView. See '
'docs/chrome_browser_design_principles.md for details. If you want '
'to write a test that has both a Browser and a BrowserView, create '
'a browser_test. If you want to write a unit_test, your code must '
'not reference Browser*.', ),
treat_as_error=False,
),
BanRule(
pattern='CreateBrowserWithTestWindow',
explanation=
('Do not use CreateBrowserWithTestWindow. See '
'docs/chrome_browser_design_principles.md for details. If you want '
'to write a test that has a Browser, create a browser_test. If you '
'want to write a unit_test, your code must not reference Browser*.',
),
treat_as_error=False,
),
BanRule(
pattern='CreateBrowserWithTestWindowForParams',
explanation=
('Do not use CreateBrowserWithTestWindowForParams. See '
'docs/chrome_browser_design_principles.md for details. If you want '
'to write a test that has a Browser, create a browser_test and use '
'Browser::Browser. If you want to write a unit_test, your code must '
'not reference Browser*.', ),
treat_as_error=False,
),
BanRule(
pattern='TestBrowserWindow',
explanation=
('Do not use TestBrowserWindow. See '
'docs/chrome_browser_design_principles.md for details. If you want '
'to write a test that has a Browser, create a browser_test. If you '
'want to write a unit_test, your code should not reference Browser '
'or BrowserWindow.', ),
treat_as_error=False,
),
BanRule(
pattern='RunUntilIdle',
explanation=
('Do not RunUntilIdle. If possible, explicitly quit the run loop using '
'run_loop.Quit() or run_loop.QuitClosure() if completion can be '
'observed using a lambda or callback. Otherwise, wait for the '
'condition to be true via base::test::RunUntil().', ),
treat_as_error=False,
),
BanRule(
pattern=r'/\bstd::(literals|string_literals|string_view_literals)\b',
explanation=(
'User-defined literals are banned by the Google C++ style guide. '
'Exceptions are provided in Chrome for string and string_view '
'literals that embed \\0.', ),
treat_as_error=True,
excluded_paths=(
r'^base/strings/string_util_unittest\.cc',
r'^base/strings/utf_string_conversions_unittest\.cc',
r'^chromeos/ash/experiences/arc/session/serial_number_util_unittest\.cc',
r'^components/history/core/browser/visit_annotations_database\.cc',
r'^components/history/core/browser/visit_annotations_database_unittest\.cc',
r'^components/os_crypt/sync/os_crypt_unittest\.cc',
r'^components/password_manager/core/browser/credentials_cleaner_unittest\.cc',
r'^content/browser/file_system_access/file_system_access_file_writer_impl_unittest\.cc',
r'^net/cookies/parsed_cookie_unittest\.cc',
r'^third_party/blink/renderer/modules/webcodecs/test_helpers\.cc',
r'^third_party/blink/renderer/modules/websockets/websocket_channel_impl_test\.cc',
),
),
BanRule(
pattern='BUILDFLAG(GOOGLE_CHROME_BRANDING)',
explanation=
('Code gated by GOOGLE_CHROME_BRANDING is effectively untested. This '
'is typically wrong. Valid use cases are glue for private modules '
'shipped alongside Chrome, and installation-related logic.', ),
treat_as_error=False,
),
BanRule(
pattern='defined(OFFICIAL_BUILD)',
explanation=
('Code gated by OFFICIAL_BUILD is effectively untested. This '
'is typically wrong. One valid use case is low-level code that '
'handles subtleties related to high-levels of optimizations that come '
'with OFFICIAL_BUILD.', ),
treat_as_error=False,
),
BanRule(
pattern='WebContentsDestroyed',
explanation=
('Do not use this method. It is invoked half-way through the '
'destructor of WebContentsImpl and using it often results in crashes '
'or surprising behavior. Conceptually, this is only necessary by '
'objects that depend on, but outlive the WebContents. These objects '
'should instead coordinate with the owner of the WebContents which is '
'responsible for destroying the WebContents.', ),
treat_as_error=False,
),
BanRule(
pattern='IS_CHROMEOS_ASH',
explanation=
('IS_CHROMEOS_ASH is deprecated. Please use the equivalent IS_CHROMEOS '
'instead (Lacros is gone).', ),
treat_as_error=False,
),
BanRule(
pattern=(r'namespace {'),
explanation=
('Anonymous namespaces are disallowed in C++ header files. See '
'https://google.github.io/styleguide/cppguide.html#Internal_Linkage '
' for details.', ),
treat_as_error=False,
excluded_paths=[
_THIRD_PARTY_EXCEPT_BLINK,
r'^(?!.*\.h$).*$',
],
),
BanRule(
pattern=('AddChildViewRaw'),
explanation=(
'Do not use AddChildViewRaw. It is prone to memory leaks and '
'use-after-free bugs. Instead, use AddChildView(std::unique_ptr). '
'See https://crbug.com/40485510 for more details.', ),
treat_as_error=False,
),
BanRule(
pattern=(r'IS_DESKTOP_ANDROID'),
explanation=
('Usage of IS_DESKTOP_ANDROID build flag '
'is discouraged. Use system affordances to determine feature '
'availablility. Refer to https://chromium.googlesource.com/chromium/src/+/HEAD/docs/ui/android/device_form_factor.md for guidelines. '
'To request an exception, file a bug at '
'https://b.corp.google.com/issues/new?component=1753515&template=2172655'
'Once approved, use centralized util DeviceInfo.isDesktop() '
'instead of direct build flag or PackageManager.FEATURE_PC checks. '
'Allowances may be granted to only the directories below: '
'[build/, chrome/, components/, extensions/, infra/, tools/] '
'Note: in particular we need to avoid components shared with '
'WebView.', ),
treat_as_error=False,
surface_as_gerrit_lint=True,
),
BanRule(
pattern='PageActionIconView',
explanation=
('PageActionIconView will soon be removed. Use PageActionView instead. '
'See chrome/browser/ui/views/page_action/README.md for details.'),
treat_as_error=False,
),
BanRule(
pattern=(r'/\bkIgnoreCertificateErrors\b'),
explanation=
('Usage of --ignore-certificate-errors in tests using '
'net::EmbeddedTestServer to serve HTTPS requests is likely '
'incorrect. Instead of blanket ignoring certificate errors using this '
'switch, strongly prefer to configure the EmbeddedTestServer with a '
'valid certificate for the relevant hostname(s). For tips on how to do '
'so, see comment above the kIgnoreCertificateErrors switch definition '
'or see https://www.chromium.org/developers/testing/browser-tests/#networking.',
),
treat_as_error=True,
excluded_paths=[
_THIRD_PARTY_EXCEPT_BLINK,
r'^(?!.*test\.cc$).*$',
r'^chrome/browser/chrome_content_browser_client_unittest\.cc',
r'^chrome/browser/extensions/api/web_request/web_request_apitest\.cc',
r'^chrome/browser/ssl/ssl_browsertest\.cc',
r'^components/network_session_configurator/browser/network_session_configurator_unittest\.cc',
r'^content/browser/fenced_frame/fenced_frame_browsertest\.cc',
],
),
)
_DEPRECATED_SYNC_CONSENT_FUNCTION_WARNING = (
'Used a predicate related to signin::ConsentLevel::kSync which will always '
'return false in the future (crbug.com/40066949). Prefer using a predicate '
'that also supports signin::ConsentLevel::kSignin when appropriate. It is '
'safe to ignore this warning if you are just moving an existing call, or if '
'you want special handling for users in the legacy state. In doubt, reach '
'out to //components/sync/OWNERS.', )
_DEPRECATED_SYNC_CONSENT_CPP_FUNCTIONS: Sequence[BanRule] = (
BanRule(
'HasSyncConsent',
_DEPRECATED_SYNC_CONSENT_FUNCTION_WARNING,
False,
),
BanRule(
'CanSyncFeatureStart',
_DEPRECATED_SYNC_CONSENT_FUNCTION_WARNING,
False,
),
BanRule(
'IsSyncFeatureEnabled',
_DEPRECATED_SYNC_CONSENT_FUNCTION_WARNING,
False,
),
BanRule(
'IsSyncFeatureActive',
_DEPRECATED_SYNC_CONSENT_FUNCTION_WARNING,
False,
),
)
_DEPRECATED_SYNC_CONSENT_JAVA_FUNCTIONS: Sequence[BanRule] = (
BanRule(
'hasSyncConsent',
_DEPRECATED_SYNC_CONSENT_FUNCTION_WARNING,
False,
),
BanRule(
'canSyncFeatureStart',
_DEPRECATED_SYNC_CONSENT_FUNCTION_WARNING,
False,
),
BanRule(
'isSyncFeatureEnabled',
_DEPRECATED_SYNC_CONSENT_FUNCTION_WARNING,
False,
),
BanRule(
'isSyncFeatureActive',
_DEPRECATED_SYNC_CONSENT_FUNCTION_WARNING,
False,
),
)
_BANNED_MOJOM_PATTERNS: Sequence[BanRule] = (
BanRule(
'handle<shared_buffer>',
(
'Please use one of the more specific shared memory types instead:',
' mojo_base.mojom.ReadOnlySharedMemoryRegion',
' mojo_base.mojom.WritableSharedMemoryRegion',
' mojo_base.mojom.UnsafeSharedMemoryRegion',
),
True,
),
BanRule(
'string extension_id',
(
'Please use the extensions::mojom::ExtensionId struct when '
'passing extensions::ExtensionIds as mojom messages in order to ',
'provide message validation.',
),
True,
excluded_paths=(r'^((?!extensions/).)*$', ),
),
)
_IPC_ENUM_TRAITS_DEPRECATED = (
'You are using IPC_ENUM_TRAITS() in your code. It has been deprecated.\n'
'See http://www.chromium.org/Home/chromium-security/education/'
'security-tips-for-ipc')
_LONG_PATH_ERROR = (
'Some files included in this CL have file names that are too long (> 200'
' characters). If committed, these files will cause issues on Windows. See'
' https://crbug.com/612667 for more details.')
_JAVA_MULTIPLE_DEFINITION_EXCLUDED_PATHS = [
r'.*/BuildHooksAndroidImpl\.java',
r'.*/LicenseContentProvider\.java',
r'.*/PlatformServiceBridgeImpl.java',
r'.*chrome/android/feed/dummy/.*\.java',
]
_IMAGE_EXTENSIONS = ['.svg', '.png', '.webp']
_KNOWN_TEST_DATA_AND_INVALID_JSON_FILE_PATTERNS = [
r'test/data/',
r'testing/buildbot/',
r'^components/policy/resources/policy_templates\.json$',
r'^third_party/protobuf/',
r'^third_party/blink/perf_tests/speedometer.*/resources/todomvc/learn\.json',
r'^third_party/blink/renderer/devtools/protocol\.json$',
r'^third_party/blink/web_tests/external/wpt/',
r'^tools/perf/',
r'^tools/traceline/svgui/startup-release.json',
r'^tools/vscode/',
]
_ANDROID_SPECIFIC_PYDEPS_FILES = [
'chrome/android/features/create_stripped_java_factory.pydeps',
]
_GENERIC_PYDEPS_FILES = [
'android_webview/tools/run_cts.pydeps',
'build/android/apk_operations.pydeps',
'build/android/devil_chromium.pydeps',
'build/android/gyp/aar.pydeps',
'build/android/gyp/aidl.pydeps',
'build/android/gyp/apkbuilder.pydeps',
'build/android/gyp/assert_static_initializers.pydeps',
'build/android/gyp/binary_baseline_profile.pydeps',
'build/android/gyp/bytecode_rewriter.pydeps',
'build/android/gyp/check_flag_expectations.pydeps',
'build/android/gyp/check_for_missing_direct_deps.pydeps',
'build/android/gyp/compile_java.pydeps',
'build/android/gyp/compile_kt.pydeps',
'build/android/gyp/compile_resources.pydeps',
'build/android/gyp/copy_ex.pydeps',
'build/android/gyp/create_apk_operations_script.pydeps',
'build/android/gyp/create_app_bundle.pydeps',
'build/android/gyp/create_app_bundle_apks.pydeps',
'build/android/gyp/create_bundle_wrapper_script.pydeps',
'build/android/gyp/create_java_binary_script.pydeps',
'build/android/gyp/create_r_java.pydeps',
'build/android/gyp/create_r_txt.pydeps',
'build/android/gyp/create_size_info_files.pydeps',
'build/android/gyp/create_stub_manifest.pydeps',
'build/android/gyp/create_test_apk_wrapper_script.pydeps',
'build/android/gyp/create_ui_locale_resources.pydeps',
'build/android/gyp/dex.pydeps',
'build/android/gyp/dist_aar.pydeps',
'build/android/gyp/errorprone.pydeps',
'build/android/gyp/filter_zip.pydeps',
'build/android/gyp/flatc_java.pydeps',
'build/android/gyp/gcc_preprocess.pydeps',
'build/android/gyp/generate_linker_version_script.pydeps',
'build/android/gyp/ijar.pydeps',
'build/android/gyp/jacoco_instr.pydeps',
'build/android/gyp/java_cpp_enum.pydeps',
'build/android/gyp/java_cpp_features.pydeps',
'build/android/gyp/java_cpp_strings.pydeps',
'build/android/gyp/java_google_api_keys.pydeps',
'build/android/gyp/jinja_template.pydeps',
'build/android/gyp/lint.pydeps',
'build/android/gyp/merge_manifest.pydeps',
'build/android/gyp/optimize_resources.pydeps',
'build/android/gyp/prepare_resources.pydeps',
'build/android/gyp/process_native_prebuilt.pydeps',
'build/android/gyp/proguard.pydeps',
'build/android/gyp/rename_java_classes.pydeps',
'build/android/gyp/system_image_apks.pydeps',
'build/android/gyp/trace_event_bytecode_rewriter.pydeps',
'build/android/gyp/tracereferences.pydeps',
'build/android/gyp/turbine.pydeps',
'build/android/gyp/unused_resources.pydeps',
'build/android/gyp/validate_static_library_dex_references.pydeps',
'build/android/gyp/write_build_config.pydeps',
'build/android/gyp/write_native_libraries_java.pydeps',
'build/android/gyp/zip.pydeps',
'build/android/incremental_install/generate_android_manifest.pydeps',
'build/android/incremental_install/write_installer_json.pydeps',
'build/android/pylib/results/presentation/test_results_presentation.pydeps',
'build/android/resource_sizes.pydeps',
'build/android/test_runner.pydeps',
'build/android/test_wrapper/logdog_wrapper.pydeps',
'build/fuchsia/test/component_storage_test.pydeps',
'build/protoc_java.pydeps',
'chrome/test/chromedriver/log_replay/client_replay_unittest.pydeps',
'chrome/test/chromedriver/test/run_py_tests.pydeps',
'chrome/test/media/performance/openscreen_cast_performance_test.pydeps',
'chrome/test/media/performance/videostack_performance_test.pydeps',
'chromecast/resource_sizes/chromecast_resource_sizes.pydeps',
'components/cronet/tools/check_combined_proguard_file.pydeps',
'components/cronet/tools/generate_proguard_file.pydeps',
'components/cronet/tools/jar_src.pydeps',
'components/module_installer/android/module_desc_java.pydeps',
'content/public/android/generate_child_service.pydeps',
'fuchsia_web/av_testing/av_sync_tests.pydeps',
'testing/scripts/run_isolated_script_test.pydeps',
'testing/merge_scripts/standard_isolated_script_merge.pydeps',
'testing/merge_scripts/standard_gtest_merge.pydeps',
'testing/merge_scripts/code_coverage/merge_results.pydeps',
'testing/merge_scripts/code_coverage/merge_steps.pydeps',
'third_party/android_platform/development/scripts/stack.pydeps',
'third_party/blink/renderer/bindings/scripts/build_web_idl_database.pydeps',
'third_party/blink/renderer/bindings/scripts/check_generated_file_list.pydeps',
'third_party/blink/renderer/bindings/scripts/collect_idl_files.pydeps',
'third_party/blink/renderer/bindings/scripts/generate_bindings.pydeps',
'third_party/blink/renderer/bindings/scripts/generate_event_interface_names.pydeps',
'third_party/blink/renderer/bindings/scripts/validate_web_idl.pydeps',
'third_party/blink/tools/blinkpy/web_tests/merge_results.pydeps',
'third_party/blink/tools/merge_web_test_results.pydeps',
'tools/binary_size/sizes.pydeps',
'tools/binary_size/supersize.pydeps',
'tools/cygprofile/generate_orderfile.pydeps',
'tools/perf/process_perf_results.pydeps',
'tools/pgo/generate_profile.pydeps',
]
_ALL_PYDEPS_FILES = _ANDROID_SPECIFIC_PYDEPS_FILES + _GENERIC_PYDEPS_FILES
_KNOWN_ROBOTS = set() | set('%s@appspot.gserviceaccount.com' % s for s in (
'findit-for-me', 'luci-bisection', 'predator-for-me-staging',
'predator-for-me')) | set(
'%s@developer.gserviceaccount.com' % s
for s in ('3su6n15k.default', )) | set(
'%s@chops-service-accounts.iam.gserviceaccount.com' % s
for s in ('bling-autoroll-builder', 'v8-ci-autoroll-builder',
'wpt-autoroller', 'skylab-test-cros-roller',
'infra-try-recipes-tester',
'chrome-automated-expectation',
'chromium-automated-expectation', 'chrome-branch-day',
'chrome-cherry-picker', 'chromium-autosharder')
) | set(
'%s@skia-public.iam.gserviceaccount.com' % s
for s in ('chromium-autoroll', 'chromium-release-autoroll')) | set(
'%s@skia-infra-corp.iam.gserviceaccount.com' % s
for s in ('pinpoint-worker', )
) | set(
'%s@skia-corp.google.com.iam.gserviceaccount.com' % s for s in
('chromium-internal-autoroll', )
) | set(
'%s@system.gserviceaccount.com' % s for s in
('chrome-screen-ai-releaser', 'crash-eng', 'crash',
'meet-tools-roll-into-chromium')
) | set(
'%s@owners-cleanup-prod.google.com.iam.gserviceaccount.com' % s
for s in ('swarming-tasks', )) | set(
'%s@fuchsia-infra.iam.gserviceaccount.com' % s
for s in ('global-integration-try-builder',
'global-integration-ci-builder')
) | set('%s@prod.google.com' % s for s in (
'chops-security-borg',
'chops-security-cronjobs-cpesuggest')) | set(
'%s@chromeos-release-bot.iam.gserviceaccount.com' % s
for s in ('chromeos-ci-release', )) | set(
'%s@chromeos-bot.iam.gserviceaccount.com' % s
for s in ('chromeos-ci-prod', ))
_INVALID_GRD_FILE_LINE = [(r'<file lang=.* path=.*',
'Path should come before lang in GRD files.')]
def _IsCPlusPlusFile(input_api, file_path):
"""Returns True if this file contains C++-like code (and not Python,
Go, Java, MarkDown, ...)"""
ext = input_api.os_path.splitext(file_path)[1]
return ext in (
'.h',
'.cc',
'.cpp',
'.m',
'.mm',
)
def _IsCPlusPlusHeaderFile(input_api, file_path):
return input_api.os_path.splitext(file_path)[1] == '.h'
def _IsJavaFile(input_api, file_path):
return input_api.os_path.splitext(file_path)[1] == '.java'
def _IsProtoFile(input_api, file_path):
return input_api.os_path.splitext(file_path)[1] == '.proto'
def _IsXmlOrGrdFile(input_api, file_path):
ext = input_api.os_path.splitext(file_path)[1]
return ext in ('.grd', '.xml')
def _IsMojomFile(input_api, file_path):
return input_api.os_path.splitext(file_path)[1] == '.mojom'
def CheckNoUpstreamDepsOnClank(input_api, output_api):
"""Prevent additions of dependencies from the upstream repo on //clank."""
if input_api.change.RepositoryRoot().endswith('clank'):
return []
build_file_patterns = [
r'(.+/)?BUILD\.gn',
r'.+\.gni',
]
excluded_files = [r'build[/\\]config[/\\]android[/\\]config\.gni']
bad_pattern = input_api.re.compile(r'^[^#]*//clank')
error_message = 'Disallowed import on //clank in an upstream build file:'
def FilterFile(affected_file):
return input_api.FilterSourceFile(affected_file,
files_to_check=build_file_patterns,
files_to_skip=excluded_files)
problems = []
for f in input_api.AffectedSourceFiles(FilterFile):
local_path = f.LocalPath()
for line_number, line in f.ChangedContents():
if (bad_pattern.search(line)):
problems.append('%s:%d\n %s' %
(local_path, line_number, line.strip()))
if problems:
return [output_api.PresubmitPromptOrNotify(error_message, problems)]
else:
return []
def CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api):
"""Attempts to prevent use of functions intended only for testing in
non-testing code. For now this is just a best-effort implementation
that ignores header files and may have some false positives. A
better implementation would probably need a proper C++ parser.
"""
file_inclusion_pattern = [r'.+%s' % _IMPLEMENTATION_EXTENSIONS]
base_function_pattern = r'[ :]test::[^\s]+|ForTest(s|ing)?|for_test(s|ing)?'
inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' %
base_function_pattern)
comment_pattern = input_api.re.compile(r'//.*(%s)' % base_function_pattern)
allowlist_pattern = input_api.re.compile(r'// IN-TEST$')
exclusion_pattern = input_api.re.compile(
r'(::[A-Za-z0-9_]+(%s)|(%s))[^;]+\{' %
(base_function_pattern, base_function_pattern))
method_defn_pattern = input_api.re.compile(r'[A-Za-z0-9_]+::$')
def FilterFile(affected_file):
files_to_skip = (_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
input_api.DEFAULT_FILES_TO_SKIP)
return input_api.FilterSourceFile(
affected_file,
files_to_check=file_inclusion_pattern,
files_to_skip=files_to_skip)
problems = []
for f in input_api.AffectedSourceFiles(FilterFile):
local_path = f.LocalPath()
in_method_defn = False
for line_number, line in f.ChangedContents():
if (inclusion_pattern.search(line)
and not comment_pattern.search(line)
and not exclusion_pattern.search(line)
and not allowlist_pattern.search(line)
and not in_method_defn):
problems.append('%s:%d\n %s' %
(local_path, line_number, line.strip()))
in_method_defn = method_defn_pattern.search(line)
if problems:
return [
output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)
]
else:
return []
def CheckNoProductionCodeUsingTestOnlyFunctionsJava(input_api, output_api):
"""This is a simplified version of
CheckNoProductionCodeUsingTestOnlyFunctions for Java files.
"""
javadoc_start_re = input_api.re.compile(r'^\s*/\*\*')
javadoc_end_re = input_api.re.compile(r'^\s*\*/')
name_pattern = r'ForTest(s|ing)?'
comment_re = input_api.re.compile(r'//.*%s' % name_pattern)
annotation_re = input_api.re.compile(r'@VisibleForTesting\(')
inclusion_re = input_api.re.compile(r'(%s)\s*\(' % name_pattern)
exclusion_re = input_api.re.compile(r'(%s)[^;]+\{' % name_pattern)
allowlist_re = input_api.re.compile(r'// IN-TEST$')
problems = []
sources = lambda x: input_api.FilterSourceFile(
x,
files_to_skip=(
('(?i).*test', r'.*\/junit\/') + input_api.DEFAULT_FILES_TO_SKIP),
files_to_check=[r'.*\.java$'])
for f in input_api.AffectedFiles(include_deletes=False,
file_filter=sources):
local_path = f.LocalPath()
is_inside_javadoc = False
for line_number, line in f.ChangedContents():
if is_inside_javadoc and javadoc_end_re.search(line):
is_inside_javadoc = False
if not is_inside_javadoc and javadoc_start_re.search(line):
is_inside_javadoc = True
if is_inside_javadoc:
continue
if (inclusion_re.search(line) and not comment_re.search(line)
and not annotation_re.search(line)
and not allowlist_re.search(line)
and not exclusion_re.search(line)):
problems.append('%s:%d\n %s' %
(local_path, line_number, line.strip()))
if problems:
return [
output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)
]
else:
return []
def CheckNoIOStreamInHeaders(input_api, output_api):
"""Checks to make sure no .h files include <iostream>."""
files = []
pattern = input_api.re.compile(r'^#include\s*<iostream>',
input_api.re.MULTILINE)
for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
if not f.LocalPath().endswith('.h'):
continue
contents = input_api.ReadFile(f)
if pattern.search(contents):
files.append(f)
if files:
return [
output_api.PresubmitError(
'Do not #include <iostream> in header files, since it inserts static '
'initialization into every file including the header. Instead, '
'#include <ostream>. See http://crbug.com/94794', files)
]
return []
def CheckNoStrCatRedefines(input_api, output_api):
"""Checks no windows headers with StrCat redefined are included directly."""
files = []
files_to_check = (r'.+%s' % _HEADER_EXTENSIONS,
r'.+%s' % _IMPLEMENTATION_EXTENSIONS)
files_to_skip = (input_api.DEFAULT_FILES_TO_SKIP +
_NON_BASE_DEPENDENT_PATHS)
sources_filter = lambda f: input_api.FilterSourceFile(
f, files_to_check=files_to_check, files_to_skip=files_to_skip)
pattern_deny = input_api.re.compile(
r'^#include\s*[<"](shlwapi|atlbase|propvarutil|sphelper).h[">]',
input_api.re.MULTILINE)
pattern_allow = input_api.re.compile(
r'^#include\s"base/win/windows_defines.inc"', input_api.re.MULTILINE)
for f in input_api.AffectedSourceFiles(sources_filter):
contents = input_api.ReadFile(f)
if pattern_deny.search(
contents) and not pattern_allow.search(contents):
files.append(f.LocalPath())
if files:
return [
output_api.PresubmitError(
'Do not #include shlwapi.h, atlbase.h, propvarutil.h or sphelper.h '
'directly since they pollute code with StrCat macro. Instead, '
'include matching header from base/win. See http://crbug.com/856536',
files)
]
return []
def _CheckNoUNIT_TESTInSourceFiles(input_api, f):
problems = []
unit_test_macro = input_api.re.compile(
r'^\s*#.*(?:ifn?def\s+UNIT_TEST|defined\s*\(?\s*UNIT_TEST\s*\)?)(?:$|\s+)'
)
for line_num, line in f.ChangedContents():
if unit_test_macro.match(line):
problems.append(' %s:%d' % (f.LocalPath(), line_num))
return problems
def CheckNoUNIT_TESTInSourceFiles(input_api, output_api):
"""Checks to make sure no source files use UNIT_TEST."""
problems = []
for f in input_api.AffectedFiles():
if (not f.LocalPath().endswith(('.cc', '.mm'))):
continue
problems.extend(_CheckNoUNIT_TESTInSourceFiles(input_api, f))
if not problems:
return []
return [
output_api.PresubmitPromptWarning('UNIT_TEST is only for headers.\n' +
'\n'.join(problems))
]
def CheckNoDISABLETypoInTests(input_api, output_api):
"""Checks to prevent attempts to disable tests with DISABLE_ prefix.
This test warns if somebody tries to disable a test with the DISABLE_ prefix
instead of DISABLED_. To filter false positives, reports are only generated
if a corresponding MAYBE_ line exists.
"""
problems = []
maybe_pattern = input_api.re.compile(r'MAYBE_([a-zA-Z0-9_]+)')
disable_pattern = input_api.re.compile(r'DISABLE_([a-zA-Z0-9_]+)')
full_disable_pattern = input_api.re.compile(
r'^\s*TEST[^(]*\([a-zA-Z0-9_]+,\s*DISABLE_[a-zA-Z0-9_]+\)',
input_api.re.MULTILINE)
for f in input_api.AffectedFiles(include_deletes=False):
if not 'test' in f.LocalPath() or not f.LocalPath().endswith('.cc'):
continue
disable_lines = {}
maybe_lines = {}
for line_num, line in f.ChangedContents():
disable_match = disable_pattern.search(line)
if disable_match:
disable_lines[disable_match.group(1)] = line_num
maybe_match = maybe_pattern.search(line)
if maybe_match:
maybe_lines[maybe_match.group(1)] = line_num
disable_tests = set(disable_lines.keys())
maybe_tests = set(maybe_lines.keys())
for test in disable_tests.intersection(maybe_tests):
problems.append(' %s:%d' % (f.LocalPath(), disable_lines[test]))
contents = input_api.ReadFile(f)
full_disable_match = full_disable_pattern.search(contents)
if full_disable_match:
problems.append(' %s' % f.LocalPath())
if not problems:
return []
return [
output_api.PresubmitPromptWarning(
'Attempt to disable a test with DISABLE_ instead of DISABLED_?\n' +
'\n'.join(problems))
]
def CheckForgettingMAYBEInTests(input_api, output_api):
"""Checks to make sure tests disabled conditionally are not missing a
corresponding MAYBE_ prefix.
"""
define_maybe_pattern = input_api.re.compile(
r'^\#define MAYBE_(?P<test_name>\w*[a-z]\w*)')
test_maybe_pattern = (
r'^\s*\w*TEST[^(]*\(\s*\w+,\s*(\w*\()?MAYBE_{test_name}')
suite_maybe_pattern = r'^\s*\w*TEST[^(]*\(\s*MAYBE_{test_name}'
warnings = []
for f in input_api.AffectedFiles(include_deletes=False):
if not 'test' in f.LocalPath() or not f.LocalPath().endswith('.cc'):
continue
contents = input_api.ReadFile(f)
lines = contents.splitlines(True)
current_position = 0
warning_test_names = set()
for line_num, line in enumerate(lines, start=1):
current_position += len(line)
maybe_match = define_maybe_pattern.search(line)
if maybe_match:
test_name = maybe_match.group('test_name')
if (test_name in warning_test_names):
continue
warning_test_names.add(test_name)
test_match = input_api.re.compile(
test_maybe_pattern.format(test_name=test_name),
input_api.re.MULTILINE).search(contents, current_position)
suite_match = input_api.re.compile(
suite_maybe_pattern.format(test_name=test_name),
input_api.re.MULTILINE).search(contents, current_position)
if not test_match and not suite_match:
warnings.append(
output_api.PresubmitPromptWarning(
'%s:%d found MAYBE_ defined without corresponding test %s'
% (f.LocalPath(), line_num, test_name)))
return warnings
def CheckDCHECK_IS_ONHasBraces(input_api, output_api):
"""Checks to make sure DCHECK_IS_ON() does not skip the parentheses."""
errors = []
pattern = input_api.re.compile(r'\bDCHECK_IS_ON\b(?!\(\))',
input_api.re.MULTILINE)
for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
if (not f.LocalPath().endswith(('.cc', '.mm', '.h'))):
continue
for lnum, line in f.ChangedContents():
if input_api.re.search(pattern, line):
errors.append(
output_api.PresubmitError((
'%s:%d: Use of DCHECK_IS_ON() must be written as "#if '
+ 'DCHECK_IS_ON()", not forgetting the parentheses.') %
(f.LocalPath(), lnum)))
return errors
def CheckFlakyTestUsage(input_api, output_api):
"""Check that FlakyTest annotation is our own instead of the android one"""
pattern = input_api.re.compile(r'import android.test.FlakyTest;')
files = []
for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
if f.LocalPath().endswith('Test.java'):
if pattern.search(input_api.ReadFile(f)):
files.append(f)
if files:
return [
output_api.PresubmitError(
'Use org.chromium.base.test.util.FlakyTest instead of '
'android.test.FlakyTest', files)
]
return []
def CheckNoDEPSGIT(input_api, output_api):
"""Make sure .DEPS.git is never modified manually."""
if any(f.LocalPath().endswith('.DEPS.git')
for f in input_api.AffectedFiles()):
return [
output_api.PresubmitError(
'Never commit changes to .DEPS.git. This file is maintained by an\n'
'automated system based on what\'s in DEPS and your changes will be\n'
'overwritten.\n'
'See https://sites.google.com/a/chromium.org/dev/developers/how-tos/'
'get-the-code#Rolling_DEPS\n'
'for more information')
]
return []
def CheckCrosApiNeedBrowserTest(input_api, output_api):
"""Check new crosapi should add browser test."""
has_new_crosapi = False
has_browser_test = False
for f in input_api.AffectedFiles():
path = f.UnixLocalPath()
if (path.startswith('chromeos/crosapi/mojom')
and _IsMojomFile(input_api, path) and f.Action() == 'A'):
has_new_crosapi = True
if path.endswith('browsertest.cc') or path.endswith('browser_test.cc'):
has_browser_test = True
if has_new_crosapi and not has_browser_test:
return [
output_api.PresubmitPromptWarning(
'You are adding a new crosapi, but there is no file ends with '
'browsertest.cc file being added or modified. It is important '
'to add crosapi browser test coverage to avoid version '
' skew issues.\n'
'Check //docs/lacros/test_instructions.md for more information.'
)
]
return []
def _GetMessageForMatchingType(input_api, affected_file, line_number, line,
ban_rule):
"""Helper method for checking for banned constructs.
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):
return result
if line.endswith(' nocheck'):
return result
matched = False
if ban_rule.pattern[0:1] == '/':
regex = ban_rule.pattern[1:]
if input_api.re.search(regex, line):
matched = True
elif ban_rule.pattern in line:
matched = True
if matched:
result.append(' %s:%d:' % (affected_file.LocalPath(), line_number))
for line in ban_rule.explanation:
result.append(' %s' % line)
return result
def CheckNoBannedPatterns(input_api, output_api):
"""Make sure that banned patterns are not used."""
results = []
def IsExcludedFile(affected_file, excluded_paths):
if not excluded_paths:
return False
local_path = affected_file.UnixLocalPath()
for item in excluded_paths:
if input_api.re.match(item, local_path):
return True
return False
def IsIosObjcFile(affected_file):
local_path = affected_file.LocalPath()
if input_api.os_path.splitext(local_path)[-1] not in ('.mm', '.m',
'.h'):
return False
basename = input_api.os_path.basename(local_path)
if 'ios' in basename.split('_'):
return True
for sep in (input_api.os_path.sep, input_api.os_path.altsep):
if sep and 'ios' in local_path.split(sep):
return True
return False
def CheckForMatch(affected_file, line_num: int, line: str,
ban_rule: BanRule):
if IsExcludedFile(affected_file, ban_rule.excluded_paths):
return
message = _GetMessageForMatchingType(input_api, f, line_num, line,
ban_rule)
if message:
result_loc = []
if ban_rule.surface_as_gerrit_lint:
result_loc.append(
output_api.PresubmitResultLocation(
file_path=affected_file.LocalPath(),
start_line=line_num,
end_line=line_num,
))
if ban_rule.treat_as_error:
results.append(
output_api.PresubmitError('A banned pattern was used.\n' +
'\n'.join(message),
locations=result_loc))
else:
results.append(
output_api.PresubmitPromptWarning(
'A banned pattern was used.\n' + '\n'.join(message),
locations=result_loc))
file_filter = lambda f: f.LocalPath().endswith(('.java'))
for f in input_api.AffectedFiles(file_filter=file_filter):
for line_num, line in f.ChangedContents():
for ban_rule in _BANNED_JAVA_FUNCTIONS + _BANNED_JAVA_IMPORTS:
CheckForMatch(f, line_num, line, ban_rule)
file_filter = lambda f: f.LocalPath().endswith(('.js', '.ts'))
for f in input_api.AffectedFiles(file_filter=file_filter):
for line_num, line in f.ChangedContents():
for ban_rule in _BANNED_JAVASCRIPT_FUNCTIONS:
CheckForMatch(f, line_num, line, ban_rule)
file_filter = lambda f: f.LocalPath().endswith(('.mm', '.m', '.h'))
for f in input_api.AffectedFiles(file_filter=file_filter):
for line_num, line in f.ChangedContents():
for ban_rule in _BANNED_OBJC_FUNCTIONS:
CheckForMatch(f, line_num, line, ban_rule)
for f in input_api.AffectedFiles(file_filter=IsIosObjcFile):
for line_num, line in f.ChangedContents():
for ban_rule in _BANNED_IOS_OBJC_FUNCTIONS:
CheckForMatch(f, line_num, line, ban_rule)
egtest_filter = lambda f: f.LocalPath().endswith(('_egtest.mm'))
for f in input_api.AffectedFiles(file_filter=egtest_filter):
for line_num, line in f.ChangedContents():
for ban_rule in _BANNED_IOS_EGTEST_FUNCTIONS:
CheckForMatch(f, line_num, line, ban_rule)
file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm', '.h'))
for f in input_api.AffectedFiles(file_filter=file_filter):
for line_num, line in f.ChangedContents():
for ban_rule in _BANNED_CPP_FUNCTIONS:
CheckForMatch(f, line_num, line, ban_rule)
ios_pattern = input_api.re.compile(r'(^|[\W_])ios[\W_]')
file_filter = lambda f: (
f.LocalPath().endswith(('.cc', '.mm', '.h')) and
('android' in f.LocalPath() or
ios_pattern.search(f.LocalPath())))
for f in input_api.AffectedFiles(file_filter=file_filter):
for line_num, line in f.ChangedContents():
for ban_rule in _DEPRECATED_SYNC_CONSENT_CPP_FUNCTIONS:
CheckForMatch(f, line_num, line, ban_rule)
file_filter = lambda f: f.LocalPath().endswith(('.java'))
for f in input_api.AffectedFiles(file_filter=file_filter):
for line_num, line in f.ChangedContents():
for ban_rule in _DEPRECATED_SYNC_CONSENT_JAVA_FUNCTIONS:
CheckForMatch(f, line_num, line, ban_rule)
file_filter = lambda f: f.LocalPath().endswith(('.mojom'))
for f in input_api.AffectedFiles(file_filter=file_filter):
for line_num, line in f.ChangedContents():
for ban_rule in _BANNED_MOJOM_PATTERNS:
CheckForMatch(f, line_num, line, ban_rule)
return results
def CheckNoPragmaOnce(input_api, output_api):
"""Make sure #pragma once is not used."""
files = []
pattern = input_api.re.compile(r'^#pragma\s+once', input_api.re.MULTILINE)
for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
if not f.LocalPath().endswith('.h'):
continue
if f.LocalPath().endswith('com_imported_mstscax.h'):
continue
contents = input_api.ReadFile(f)
if pattern.search(contents):
files.append(f)
if files:
return [
output_api.PresubmitError(
'Do not use #pragma once in header files.\n'
'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
files)
]
return []
def CheckNoTrinaryTrueFalse(input_api, output_api):
"""Checks to make sure we don't introduce use of foo ? true : false."""
problems = []
pattern = input_api.re.compile(r'\?\s*(true|false)\s*:\s*(true|false)')
for f in input_api.AffectedFiles():
if not f.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
continue
for line_num, line in f.ChangedContents():
if pattern.match(line):
problems.append(' %s:%d' % (f.LocalPath(), line_num))
if not problems:
return []
return [
output_api.PresubmitPromptWarning(
'Please consider avoiding the "? true : false" pattern if possible.\n'
+ '\n'.join(problems))
]
def CheckUnwantedDependencies(input_api, output_api):
"""Runs checkdeps on #include and import statements added in this
change. Breaking - rules is an error, breaking ! rules is a
warning.
"""
for f in input_api.AffectedFiles():
path = f.LocalPath()
if (_IsCPlusPlusFile(input_api, path) or _IsProtoFile(input_api, path)
or _IsJavaFile(input_api, path)):
break
else:
return []
import sys
original_sys_path = sys.path
try:
sys.path = sys.path + [
input_api.os_path.join(input_api.PresubmitLocalPath(),
'buildtools', 'checkdeps')
]
import checkdeps
from rules import Rule
finally:
sys.path = original_sys_path
added_includes = []
added_imports = []
added_java_imports = []
for f in input_api.AffectedFiles():
if _IsCPlusPlusFile(input_api, f.LocalPath()):
changed_lines = [line for _, line in f.ChangedContents()]
added_includes.append([f.AbsoluteLocalPath(), changed_lines])
elif _IsProtoFile(input_api, f.LocalPath()):
changed_lines = [line for _, line in f.ChangedContents()]
added_imports.append([f.AbsoluteLocalPath(), changed_lines])
elif _IsJavaFile(input_api, f.LocalPath()):
changed_lines = [line for _, line in f.ChangedContents()]
added_java_imports.append([f.AbsoluteLocalPath(), changed_lines])
deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
error_descriptions = []
warning_descriptions = []
error_subjects = set()
warning_subjects = set()
for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
added_includes):
path = input_api.os_path.relpath(path, input_api.PresubmitLocalPath())
description_with_path = '%s\n %s' % (path, rule_description)
if rule_type == Rule.DISALLOW:
error_descriptions.append(description_with_path)
error_subjects.add('#includes')
else:
warning_descriptions.append(description_with_path)
warning_subjects.add('#includes')
for path, rule_type, rule_description in deps_checker.CheckAddedProtoImports(
added_imports):
path = input_api.os_path.relpath(path, input_api.PresubmitLocalPath())
description_with_path = '%s\n %s' % (path, rule_description)
if rule_type == Rule.DISALLOW:
error_descriptions.append(description_with_path)
error_subjects.add('imports')
else:
warning_descriptions.append(description_with_path)
warning_subjects.add('imports')
for path, rule_type, rule_description in deps_checker.CheckAddedJavaImports(
added_java_imports, _JAVA_MULTIPLE_DEFINITION_EXCLUDED_PATHS):
path = input_api.os_path.relpath(path, input_api.PresubmitLocalPath())
description_with_path = '%s\n %s' % (path, rule_description)
if rule_type == Rule.DISALLOW:
error_descriptions.append(description_with_path)
error_subjects.add('imports')
else:
warning_descriptions.append(description_with_path)
warning_subjects.add('imports')
results = []
if error_descriptions:
results.append(
output_api.PresubmitError(
'You added one or more %s that violate checkdeps rules.' %
' and '.join(error_subjects), error_descriptions))
if warning_descriptions:
results.append(
output_api.PresubmitPromptOrNotify(
'You added one or more %s of files that are temporarily\n'
'allowed but being removed. Can you avoid introducing the\n'
'%s? See relevant DEPS file(s) for details and contacts.' %
(' and '.join(warning_subjects), '/'.join(warning_subjects)),
warning_descriptions))
return results
def CheckFilePermissions(input_api, output_api):
"""Check that all files have their permissions properly set."""
if input_api.platform == 'win32':
return []
checkperms_tool = input_api.os_path.join(input_api.PresubmitLocalPath(),
'tools', 'checkperms',
'checkperms.py')
args = [
input_api.python3_executable, checkperms_tool, '--root',
input_api.change.RepositoryRoot()
]
with input_api.CreateTemporaryFile() as file_list:
for f in input_api.AffectedFiles():
file_list.write((f.LocalPath() + '\n').encode('utf8'))
file_list.close()
args += ['--file-list', file_list.name]
try:
input_api.subprocess.check_output(args)
return []
except input_api.subprocess.CalledProcessError as error:
return [
output_api.PresubmitError('checkperms.py failed:',
long_text=error.output.decode(
'utf-8', 'ignore'))
]
def CheckNoAuraWindowPropertyHInHeaders(input_api, output_api):
"""Makes sure we don't include ui/aura/window_property.h
in header files.
"""
pattern = input_api.re.compile(r'^#include\s*"ui/aura/window_property.h"')
errors = []
for f in input_api.AffectedFiles():
if not f.LocalPath().endswith('.h'):
continue
for line_num, line in f.ChangedContents():
if pattern.match(line):
errors.append(' %s:%d' % (f.LocalPath(), line_num))
results = []
if errors:
results.append(
output_api.PresubmitError(
'Header files should not include ui/aura/window_property.h',
errors))
return results
def CheckNoInternalHeapIncludes(input_api, output_api):
"""Makes sure we don't include any headers from
third_party/blink/renderer/platform/heap/impl or
third_party/blink/renderer/platform/heap/v8_wrapper from files outside of
third_party/blink/renderer/platform/heap
"""
impl_pattern = input_api.re.compile(
r'^\s*#include\s*"third_party/blink/renderer/platform/heap/impl/.*"')
v8_wrapper_pattern = input_api.re.compile(
r'^\s*#include\s*"third_party/blink/renderer/platform/heap/v8_wrapper/.*"'
)
file_filter = lambda f: not input_api.re.match(
r'^third_party/blink/renderer/platform/heap/.*', f.UnixLocalPath())
errors = []
for f in input_api.AffectedFiles(file_filter=file_filter):
for line_num, line in f.ChangedContents():
if impl_pattern.match(line) or v8_wrapper_pattern.match(line):
errors.append(' %s:%d' % (f.LocalPath(), line_num))
results = []
if errors:
results.append(
output_api.PresubmitError(
'Do not include files from third_party/blink/renderer/platform/heap/impl'
' or third_party/blink/renderer/platform/heap/v8_wrapper. Use the '
'relevant counterparts from third_party/blink/renderer/platform/heap',
errors))
return results
def _CheckForVersionControlConflictsInFile(input_api, f):
pattern = input_api.re.compile('^(?:<<<<<<<|>>>>>>>) |^=======$')
errors = []
for line_num, line in f.ChangedContents():
if f.LocalPath().endswith(('.md', '.rst', '.txt')):
continue
if pattern.match(line):
errors.append(' %s:%d %s' % (f.LocalPath(), line_num, line))
return errors
def CheckForVersionControlConflicts(input_api, output_api):
"""Usually this is not intentional and will cause a compile failure."""
errors = []
for f in input_api.AffectedFiles():
errors.extend(_CheckForVersionControlConflictsInFile(input_api, f))
results = []
if errors:
results.append(
output_api.PresubmitError(
'Version control conflict markers found, please resolve.',
errors))
return results
def CheckGoogleSupportAnswerUrlOnUpload(input_api, output_api):
pattern = input_api.re.compile(r'support\.google\.com\/chrome.*/answer')
errors = []
for f in input_api.AffectedFiles():
for line_num, line in f.ChangedContents():
if pattern.search(line):
errors.append(' %s:%d %s' % (f.LocalPath(), line_num, line))
results = []
if errors:
results.append(
output_api.PresubmitPromptWarning(
'Found Google support URL addressed by answer number. Please replace '
'with a p= identifier instead. See crbug.com/679462\n',
errors))
return results
def CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api):
def FilterFile(affected_file):
"""Filter function for use with input_api.AffectedSourceFiles,
below. This filters out everything except non-test files from
top-level directories that generally speaking should not hard-code
service URLs (e.g. src/android_webview/, src/content/ and others).
"""
return input_api.FilterSourceFile(
affected_file,
files_to_check=[r'^(android_webview|base|content|net)/.*'],
files_to_skip=(_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
input_api.DEFAULT_FILES_TO_SKIP))
base_pattern = (r'"[^"]*(google|googleapis|googlezip|googledrive|appspot)'
r'\.(com|net)[^"]*"')
comment_pattern = input_api.re.compile('//.*%s' % base_pattern)
pattern = input_api.re.compile(base_pattern)
problems = []
for f in input_api.AffectedSourceFiles(FilterFile):
for line_num, line in f.ChangedContents():
if not comment_pattern.search(line) and pattern.search(line):
problems.append((f.LocalPath(), line_num, line))
if problems:
return [
output_api.PresubmitPromptOrNotify(
'Most layers below src/chrome/ should not hardcode service URLs.\n'
'Are you sure this is correct?', [
' %s:%d: %s' % (problem[0], problem[1], problem[2])
for problem in problems
])
]
else:
return []
def CheckChromeOsSyncedPrefRegistration(input_api, output_api):
"""Warns if Chrome OS C++ files register syncable prefs as browser prefs."""
def FileFilter(affected_file):
"""Includes directories known to be Chrome OS only."""
return input_api.FilterSourceFile(
affected_file,
files_to_check=(
'^ash/',
'^chromeos/',
'.*/chromeos/',
'^components/arc',
'^components/exo'),
files_to_skip=(input_api.DEFAULT_FILES_TO_SKIP))
prefs = []
priority_prefs = []
for f in input_api.AffectedFiles(file_filter=FileFilter):
for line_num, line in f.ChangedContents():
if input_api.re.search('PrefRegistrySyncable::SYNCABLE_PREF',
line):
prefs.append(' %s:%d:' % (f.LocalPath(), line_num))
prefs.append(' %s' % line)
if input_api.re.search(
'PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF', line):
priority_prefs.append(' %s:%d' % (f.LocalPath(), line_num))
priority_prefs.append(' %s' % line)
results = []
if (prefs):
results.append(
output_api.PresubmitPromptWarning(
'Preferences were registered as SYNCABLE_PREF and will be controlled '
'by browser sync settings. If these prefs should be controlled by OS '
'sync settings use SYNCABLE_OS_PREF instead.\n' +
'\n'.join(prefs)))
if (priority_prefs):
results.append(
output_api.PresubmitPromptWarning(
'Preferences were registered as SYNCABLE_PRIORITY_PREF and will be '
'controlled by browser sync settings. If these prefs should be '
'controlled by OS sync settings use SYNCABLE_OS_PRIORITY_PREF '
'instead.\n' + '\n'.join(prefs)))
return results
def CheckNoAbbreviationInPngFileName(input_api, output_api):
"""Makes sure there are no abbreviations in the name of PNG files.
"""
errors = []
files_to_check = [r'.*\.png$']
files_to_skip = [
r'^services/test/',
r'^third_party/blink/web_tests/',
]
file_filter = lambda f: input_api.FilterSourceFile(
f, files_to_check=files_to_check, files_to_skip=files_to_skip)
abbreviation = input_api.re.compile(r'.+_[a-z]\.png|.+_[a-z]_.*\.png')
for f in input_api.AffectedFiles(include_deletes=False,
file_filter=file_filter):
file_name = input_api.os_path.split(f.LocalPath())[1]
if abbreviation.search(file_name):
errors.append(' %s' % f.LocalPath())
results = []
if errors:
results.append(
output_api.PresubmitError(
'The name of PNG files should not have abbreviations. \n'
'Use _hover.png, _center.png, instead of _h.png, _c.png.\n'
'Contact oshima@chromium.org if you have questions.', errors))
return results
def CheckNoProductIconsAddedToPublicRepo(input_api, output_api):
"""Heuristically identifies product icons based on their file name and reminds
contributors not to add them to the Chromium repository.
"""
if input_api.change.RepositoryRoot().endswith('clank'):
return []
errors = []
files_to_check = [r'.*google.*\.png$|.*google.*\.svg$|.*google.*\.icon$']
file_filter = lambda f: input_api.FilterSourceFile(
f, files_to_check=files_to_check)
for f in input_api.AffectedFiles(include_deletes=False,
file_filter=file_filter):
errors.append(' %s' % f.LocalPath())
results = []
if errors:
message_type = (output_api.PresubmitNotifyResult
if input_api.no_diffs else output_api.PresubmitError)
results.append(
message_type(
'Trademarked images should not be added to the public repo. '
'See crbug.com/944754', errors))
return results
def _ExtractAddRulesFromParsedDeps(parsed_deps):
"""Extract the rules that add dependencies from a parsed DEPS file.
Args:
parsed_deps: the locals dictionary from evaluating the DEPS file."""
add_rules = set()
add_rules.update([
rule[1:] for rule in parsed_deps.get('include_rules', [])
if rule.startswith('+') or rule.startswith('!')
])
for _, rules in parsed_deps.get('specific_include_rules', {}).items():
add_rules.update([
rule[1:] for rule in rules
if rule.startswith('+') or rule.startswith('!')
])
return add_rules
def _ParseDeps(contents):
"""Simple helper for parsing DEPS files."""
class _VarImpl:
def __init__(self, local_scope):
self._local_scope = local_scope
def Lookup(self, var_name):
"""Implements the Var syntax."""
try:
return self._local_scope['vars'][var_name]
except KeyError:
raise Exception('Var is not defined: %s' % var_name)
local_scope = {}
global_scope = {
'Var': _VarImpl(local_scope).Lookup,
'Str': str,
}
exec(contents, global_scope, local_scope)
return local_scope
def _FindAllDepsFilesForSubpath(input_api, subpath):
ret = []
while subpath:
cur = input_api.os_path.join(input_api.change.RepositoryRoot(),
subpath, 'DEPS')
if input_api.os_path.isfile(cur):
ret.append(cur)
subpath = input_api.os_path.dirname(subpath)
return ret
def _FindAddedDepsThatRequireReview(input_api, depended_on_paths):
"""Filters to those whose DEPS set new_usages_require_review=True"""
ret = set()
cache = {}
for target_path in depended_on_paths:
for subpath in _FindAllDepsFilesForSubpath(input_api, target_path):
config = cache.get(subpath)
if config is None:
config = _ParseDeps(input_api.ReadFile(subpath))
cache[subpath] = config
if config.get('new_usages_require_review'):
ret.add(target_path)
break
return ret
def _CalculateAddedDeps(os_path, old_contents, new_contents):
"""Helper method for CheckAddedDepsHaveTargetApprovals. Returns
a set of DEPS entries that we should look up.
For a directory (rather than a specific filename) we fake a path to
a specific filename by adding /DEPS. This is chosen as a file that
will seldom or never be subject to per-file include_rules.
"""
AUTO_GENERATED_DIRS = ['grit', 'jni']
old_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(old_contents))
new_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(new_contents))
added_deps = new_deps.difference(old_deps)
results = set()
for added_dep in added_deps:
if added_dep.split('/')[0] in AUTO_GENERATED_DIRS:
continue
if added_dep.endswith('.h'):
results.add(added_dep)
else:
results.add(os_path.join(added_dep, 'DEPS'))
return results
def CheckForNewDEPSDownloadFromGoogleStorageHooks(input_api, output_api):
"""Checks that there are no new download_from_google_storage hooks"""
for f in input_api.AffectedFiles(include_deletes=False):
if f.LocalPath() == 'DEPS':
old_hooks = _ParseDeps('\n'.join(f.OldContents()))['hooks']
new_hooks = _ParseDeps('\n'.join(f.NewContents()))['hooks']
old_name_to_hook = {hook['name']: hook for hook in old_hooks}
new_name_to_hook = {hook['name']: hook for hook in new_hooks}
added_hook_names = set(new_name_to_hook.keys()) - set(
old_name_to_hook.keys())
if not added_hook_names:
return []
new_download_from_google_storage_hooks = []
for new_hook in added_hook_names:
hook = new_name_to_hook[new_hook]
action_cmd = hook['action']
if any('download_from_google_storage' in arg
for arg in action_cmd):
new_download_from_google_storage_hooks.append(new_hook)
if new_download_from_google_storage_hooks:
return [
output_api.PresubmitError(
'Please do not add new download_from_google_storage '
'hooks. Instead, add a `gcs` dep_type entry to `deps`. '
'See https://chromium.googlesource.com/chromium/src.git'
'/+/refs/heads/main/docs/gcs_dependencies.md for more '
'info. Added hooks:',
items=new_download_from_google_storage_hooks)
]
return []
def CheckEachPerfettoTestDataFileHasDepsEntry(input_api, output_api):
test_data_filter = lambda f: input_api.FilterSourceFile(
f, files_to_check=[r'^base/tracing/test/data_sha256/.*\.sha256'])
if not any(input_api.AffectedFiles(file_filter=test_data_filter)):
return []
deps_entry = []
old_deps_entry = []
for f in input_api.AffectedFiles(include_deletes=False):
if f.LocalPath() == 'DEPS':
new_deps = _ParseDeps('\n'.join(f.NewContents()))['deps']
deps_entry = new_deps['src/base/tracing/test/data']
old_deps = _ParseDeps('\n'.join(f.OldContents()))['deps']
old_deps_entry = old_deps['src/base/tracing/test/data']
if not deps_entry:
return [
output_api.PresubmitError(
'You must update the DEPS file when you update a '
'.sha256 file in base/tracing/test/data_sha256')
]
output = []
for f in input_api.AffectedFiles(file_filter=test_data_filter):
objects = deps_entry['objects']
if not f.NewContents():
sha256_from_file = f.OldContents()[0]
object_entry = next(
(item
for item in objects if item['sha256sum'] == sha256_from_file),
None)
old_entry = next((item for item in old_deps_entry['objects']
if item['sha256sum'] == sha256_from_file), None)
if object_entry:
if object_entry['object_name'] != old_entry['object_name']:
continue
output.append(
output_api.PresubmitError(
'You deleted %s so you must also remove the corresponding DEPS entry.'
% f.LocalPath()))
continue
sha256_from_file = f.NewContents()[0]
object_entry = next(
(item
for item in objects if item['sha256sum'] == sha256_from_file),
None)
if not object_entry:
output.append(
output_api.PresubmitError(
'No corresponding DEPS entry found for %s. '
'Run `base/tracing/test/test_data.py get_deps --filepath %s` '
'to generate the DEPS entry.' %
(f.LocalPath(), f.LocalPath())))
if output:
output.append(
output_api.PresubmitError(
'The DEPS entry for `src/base/tracing/test/data` in the DEPS file has not been '
'updated properly. Run `base/tracing/test/test_data.py get_all_deps` to see what '
'the DEPS entry should look like.'))
return output
def CheckAddedDepsHaveTargetApprovals(input_api, output_api):
"""When a dependency prefixed with + is added to a DEPS file, we
want to make sure that the change is reviewed by an OWNER of the
target file or directory, to avoid layering violations from being
introduced. This check verifies that this happens.
"""
if not input_api.gerrit or input_api.no_diffs:
return []
if 'PRESUBMIT_SKIP_NETWORK' in input_api.environ:
return []
try:
if (input_api.change.issue
and input_api.gerrit.IsOwnersOverrideApproved(
input_api.change.issue)):
return []
except Exception as e:
return [
output_api.PresubmitPromptWarning(
'Failed to retrieve owner override status - %s' % str(e))
]
depended_on_paths = set()
file_filter = lambda f: not input_api.re.match(r'^third_party/blink/.*',
f.UnixLocalPath())
for f in input_api.AffectedFiles(include_deletes=False,
file_filter=file_filter):
filename = input_api.os_path.basename(f.LocalPath())
if filename == 'DEPS':
depended_on_paths.update(
_CalculateAddedDeps(input_api.os_path,
'\n'.join(f.OldContents()),
'\n'.join(f.NewContents())))
depended_on_paths = _FindAddedDepsThatRequireReview(
input_api, depended_on_paths)
if not depended_on_paths:
return []
if input_api.is_committing:
if input_api.tbr:
return [
output_api.PresubmitNotifyResult(
'--tbr was specified, skipping OWNERS check for DEPS additions'
)
]
if input_api.dry_run:
return [
output_api.PresubmitNotifyResult(
'This is a dry run, skipping OWNERS check for DEPS additions'
)
]
if not input_api.change.issue:
return [
output_api.PresubmitError(
"DEPS approval by OWNERS check failed: this change has "
"no change number, so we can't check it for approvals.")
]
output = output_api.PresubmitError
else:
output = output_api.PresubmitNotifyResult
owner_email, reviewers = (
input_api.canned_checks.GetCodereviewOwnerAndReviewers(
input_api, None, approval_needed=input_api.is_committing))
owner_email = owner_email or input_api.change.author_email
approval_status = input_api.owners_client.GetFilesApprovalStatus(
depended_on_paths, reviewers.union([owner_email]), [])
missing_files = [
p for p in depended_on_paths
if approval_status[p] != input_api.owners_client.APPROVED
]
def StripDeps(path):
start_deps = path.rfind('/DEPS')
if start_deps != -1:
return path[:start_deps]
else:
return path
submodule_paths = set(input_api.ListSubmodules())
def is_from_submodules(path, submodule_paths):
path = input_api.os_path.normpath(path)
while path:
if path in submodule_paths:
return True
parent = input_api.os_path.dirname(path)
if parent == path:
break
path = parent
return False
unapproved_dependencies = [
"'+%s'," % StripDeps(path) for path in missing_files
if not is_from_submodules(path, submodule_paths)
]
if unapproved_dependencies:
output_list = [
output(
'You need LGTM from owners of depends-on paths in DEPS that were '
'modified in this CL:\n %s' %
'\n '.join(sorted(unapproved_dependencies)))
]
suggested_owners = input_api.owners_client.SuggestOwners(
missing_files, exclude=[owner_email])
output_list.append(
output('Suggested missing target path OWNERS:\n %s' %
'\n '.join(suggested_owners or [])))
return output_list
return []
def CheckSpamLogging(input_api, output_api):
file_inclusion_pattern = [r'.+%s' % _IMPLEMENTATION_EXTENSIONS]
files_to_skip = (
_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
input_api.DEFAULT_FILES_TO_SKIP + (
r'^base/fuchsia/scoped_fx_logger\.cc$',
r'^base/logging\.h$',
r'^base/logging\.cc$',
r'^base/task/thread_pool/task_tracker\.cc$',
r'^chrome/app/chrome_main_delegate\.cc$',
r'^chrome/browser/ash/arc/enterprise/cert_store/arc_cert_installer\.cc$',
r'^chrome/browser/ash/policy/remote_commands/user_command_arc_job\.cc$',
r'^chrome/browser/chrome_browser_main\.cc$',
r'^chrome/browser/ui/startup/startup_browser_creator\.cc$',
r'^chrome/browser/browser_switcher/bho/.*',
r'^chrome/browser/diagnostics/diagnostics_writer\.cc$',
r'^chrome/chrome_elf/dll_hash/dll_hash_main\.cc$',
r'^chrome/installer/setup/.*',
r'^chrome/utility/safe_browsing/mac/crdmg\.cc$',
r'^chromecast/',
r'^components/cast',
r'^components/media_control/renderer/media_playback_options\.cc$',
r'^components/policy/core/common/policy_logger\.cc$',
r'^components/supervised_user/core/browser/android/content_filters_observer_bridge\.cc',
r'^components/viz/service/display/'
r'overlay_strategy_underlay_cast\.cc$',
r'^components/zucchini/.*',
r'^content/browser/notifications/notification_event_dispatcher_impl\.cc$',
r'^content/common/gpu/client/gl_helper_benchmark\.cc$',
r'^courgette/courgette_minimal_tool\.cc$',
r'^courgette/courgette_tool\.cc$',
r'^extensions/renderer/logging_native_handler\.cc$',
r'^fuchsia_web/common/init_logging\.cc$',
r'^fuchsia_web/runners/common/web_component\.cc$',
r'^fuchsia_web/shell/.*\.cc$',
r'^headless/app/headless_shell\.cc$',
r'^ipc/ipc_logging\.cc$',
r'^ios/chrome/app/perf_tests_hook_logging\.mm$',
r'^remoting/base/logging\.h$',
r'^remoting/host/.*',
r'^sandbox/linux/.*',
r'^services/webnn/tflite/graph_impl_tflite\.cc$',
r'^services/webnn/coreml/graph_impl_coreml\.mm$',
r'^storage/browser/file_system/dump_file_system\.cc$',
r'^testing/perf/',
r'^tools/',
r'^ui/base/resource/data_pack\.cc$',
r'^ui/aura/bench/bench_main\.cc$',
r'^ui/ozone/platform/cast/',
r'^ui/base/x/xwmstartupcheck/xwmstartupcheck\.cc$'))
source_file_filter = lambda x: input_api.FilterSourceFile(
x, files_to_check=file_inclusion_pattern, files_to_skip=files_to_skip)
log_info = set([])
printf = set([])
for f in input_api.AffectedSourceFiles(source_file_filter):
for _, line in f.ChangedContents():
if input_api.re.search(r'\bD?LOG\s*\(\s*INFO\s*\)', line):
log_info.add(f.LocalPath())
elif input_api.re.search(r'\bD?LOG_IF\s*\(\s*INFO\s*,', line):
log_info.add(f.LocalPath())
if input_api.re.search(r'\bprintf\(', line):
printf.add(f.LocalPath())
elif input_api.re.search(r'\bfprintf\((stdout|stderr)', line):
printf.add(f.LocalPath())
if log_info:
return [
output_api.PresubmitError(
'These files spam the console log with LOG(INFO):',
items=log_info)
]
if printf:
return [
output_api.PresubmitError(
'These files spam the console log with printf/fprintf:',
items=printf)
]
return []
def CheckUniquePtrOnUpload(input_api, output_api):
def HasMoreThanOneArgOrInvalid(template_str):
nesting = 0
for c in template_str:
if c == '<':
nesting += 1
elif c == '>':
nesting -= 1
elif c == ',' and nesting == 1:
return True
if nesting != 0:
return True
return False
file_inclusion_pattern = [r'.+%s' % _IMPLEMENTATION_EXTENSIONS]
sources = lambda affected_file: input_api.FilterSourceFile(
affected_file,
files_to_skip=(_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS + input_api.
DEFAULT_FILES_TO_SKIP),
files_to_check=file_inclusion_pattern)
template_arg_pattern = (
r'<[^>]*'
r'>([^<]*>)?')
not_inside_template_arg_pattern = r'(^|[^<,\s]\s*)'
null_construct_pattern = input_api.re.compile(
not_inside_template_arg_pattern + r'\bstd::unique_ptr' +
template_arg_pattern + r'\(\)')
template_arg_no_array_pattern = (
r'<[^>]*[^]]'
r'>([^(<]*[^]]>)?')
start_of_expr_pattern = r'(=|\breturn|^)\s*'
nonempty_arg_list_pattern = r'\(([^)]|$)'
return_construct_pattern = input_api.re.compile(
start_of_expr_pattern + r'std::unique_ptr' + '(?P<template_arg>' +
template_arg_no_array_pattern + ')' + nonempty_arg_list_pattern)
problems_constructor = []
problems_nullptr = []
for f in input_api.AffectedSourceFiles(sources):
for line_number, line in f.ChangedContents():
local_path = f.LocalPath()
return_construct_result = return_construct_pattern.search(line)
if return_construct_result and not HasMoreThanOneArgOrInvalid(
return_construct_result.group('template_arg')):
problems_constructor.append(
'%s:%d\n %s' % (local_path, line_number, line.strip()))
if null_construct_pattern.search(line):
problems_nullptr.append(
'%s:%d\n %s' % (local_path, line_number, line.strip()))
errors = []
if problems_nullptr:
errors.append(
output_api.PresubmitPromptWarning(
'The following files use std::unique_ptr<T>(). Use nullptr instead.',
problems_nullptr))
if problems_constructor:
errors.append(
output_api.PresubmitError(
'The following files use explicit std::unique_ptr constructor. '
'Use std::make_unique<T>() instead, or use base::WrapUnique if '
'std::make_unique is not an option.', problems_constructor))
return errors
def CheckUserActionUpdate(input_api, output_api):
"""Checks if any new user action has been added."""
if any('actions.xml' == input_api.os_path.basename(f)
for f in input_api.LocalPaths()):
return []
file_inclusion_pattern = [r'.*\.(cc|mm)$']
files_to_skip = (_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
input_api.DEFAULT_FILES_TO_SKIP)
file_filter = lambda f: input_api.FilterSourceFile(
f, files_to_check=file_inclusion_pattern, files_to_skip=files_to_skip)
action_re = r'[^a-zA-Z]UserMetricsAction\("([^"]*)'
current_actions = None
for f in input_api.AffectedFiles(file_filter=file_filter):
for line_num, line in f.ChangedContents():
match = input_api.re.search(action_re, line)
if match:
if not current_actions:
with open('tools/metrics/actions/actions.xml',
encoding='utf-8') as actions_f:
current_actions = actions_f.read()
for action_name in match.groups():
action = 'name="{0}"'.format(action_name)
if action not in current_actions:
return [
output_api.PresubmitPromptWarning(
'File %s line %d: %s is missing in '
'tools/metrics/actions/actions.xml. Please run '
'tools/metrics/actions/extract_actions.py to update.'
% (f.LocalPath(), line_num, action_name))
]
return []
def _ImportJSONCommentEater(input_api):
import sys
sys.path = sys.path + [
input_api.os_path.join(input_api.PresubmitLocalPath(), 'tools',
'json_comment_eater')
]
import json_comment_eater
return json_comment_eater
def _GetJSONParseError(input_api, filename, eat_comments=True):
try:
contents = input_api.ReadFile(filename)
if eat_comments:
json_comment_eater = _ImportJSONCommentEater(input_api)
contents = json_comment_eater.Nom(contents)
input_api.json.loads(contents)
except ValueError as e:
return e
return None
def _GetIDLParseError(input_api, filename):
try:
contents = input_api.ReadFile(filename)
for i, char in enumerate(contents):
if not char.isascii():
return (
'Non-ascii character "%s" (ord %d) found at offset %d.' %
(char, ord(char), i))
idl_schema = input_api.os_path.join(input_api.PresubmitLocalPath(),
'tools', 'json_schema_compiler',
'idl_schema.py')
process = input_api.subprocess.Popen(
[input_api.python3_executable, idl_schema],
stdin=input_api.subprocess.PIPE,
stdout=input_api.subprocess.PIPE,
stderr=input_api.subprocess.PIPE,
universal_newlines=True)
(_, error) = process.communicate(input=contents)
return error or None
except ValueError as e:
return e
def CheckParseErrors(input_api, output_api):
"""Check that IDL and JSON files do not contain syntax errors."""
actions = {
'.idl': _GetIDLParseError,
'.json': _GetJSONParseError,
}
json_no_comments_patterns = [
r'^testing/',
]
idl_included_patterns = [
r'^chrome/common/extensions/api/',
r'^extensions/common/api/',
]
def get_action(affected_file):
filename = affected_file.LocalPath()
return actions.get(input_api.os_path.splitext(filename)[1])
def FilterFile(affected_file):
action = get_action(affected_file)
if not action:
return False
path = affected_file.UnixLocalPath()
if _MatchesFile(input_api,
_KNOWN_TEST_DATA_AND_INVALID_JSON_FILE_PATTERNS, path):
return False
if (action == _GetIDLParseError
and not _MatchesFile(input_api, idl_included_patterns, path)):
return False
return True
results = []
for affected_file in input_api.AffectedFiles(file_filter=FilterFile,
include_deletes=False):
action = get_action(affected_file)
kwargs = {}
if (action == _GetJSONParseError
and _MatchesFile(input_api, json_no_comments_patterns,
affected_file.UnixLocalPath())):
kwargs['eat_comments'] = False
parse_error = action(input_api, affected_file.AbsoluteLocalPath(),
**kwargs)
if parse_error:
results.append(
output_api.PresubmitError(
'%s could not be parsed: %s' %
(affected_file.LocalPath(), parse_error)))
return results
def CheckJavaStyle(input_api, output_api):
"""Runs checkstyle on changed java files and returns errors if any exist."""
if not any(
_IsJavaFile(input_api, f.LocalPath())
for f in input_api.AffectedFiles()):
return []
import sys
original_sys_path = sys.path
try:
sys.path = sys.path + [
input_api.os_path.join(input_api.PresubmitLocalPath(), 'tools',
'android', 'checkstyle')
]
import checkstyle
finally:
sys.path = original_sys_path
return checkstyle.run_presubmit(input_api,
output_api,
files_to_skip=_EXCLUDED_PATHS +
input_api.DEFAULT_FILES_TO_SKIP)
def CheckPythonDevilInit(input_api, output_api):
"""Checks to make sure devil is initialized correctly in python scripts."""
script_common_initialize_pattern = input_api.re.compile(
r'script_common\.InitializeEnvironment\(')
devil_env_config_initialize = input_api.re.compile(
r'devil_env\.config\.Initialize\(')
errors = []
sources = lambda affected_file: input_api.FilterSourceFile(
affected_file,
files_to_skip=(_EXCLUDED_PATHS + input_api.DEFAULT_FILES_TO_SKIP + (
r'^build/android/devil_chromium\.py',
r'^tools/bisect-builds\.py',
r'^third_party/.*',
)),
files_to_check=[r'.*\.py$'])
for f in input_api.AffectedSourceFiles(sources):
for line_num, line in f.ChangedContents():
if (script_common_initialize_pattern.search(line)
or devil_env_config_initialize.search(line)):
errors.append('%s:%d' % (f.LocalPath(), line_num))
results = []
if errors:
results.append(
output_api.PresubmitError(
'Devil initialization should always be done using '
'devil_chromium.Initialize() in the chromium project, to use better '
'defaults for dependencies (ex. up-to-date version of adb).',
errors))
return results
def _MatchesFile(input_api, patterns, path):
for pattern in patterns:
if input_api.re.search(pattern, path):
return True
return False
def _ChangeHasSecurityReviewer(input_api, owners_file):
"""Returns True iff the CL has a reviewer from SECURITY_OWNERS.
Args:
input_api: The presubmit input API.
owners_file: OWNERS file with required reviewers. Typically, this is
something like ipc/SECURITY_OWNERS.
Note: if the presubmit is running for commit rather than for upload, this
only returns True if a security reviewer has also approved the CL.
"""
if (input_api.change.issue and input_api.gerrit.IsOwnersOverrideApproved(
input_api.change.issue)):
return True
_, reviewers = (input_api.canned_checks.GetCodereviewOwnerAndReviewers(
input_api,
None,
approval_needed=input_api.is_committing and not input_api.dry_run))
security_owners = input_api.owners_client.ListOwners(owners_file)
return any(owner in reviewers for owner in security_owners)
@dataclasses.dataclass
class _SecurityProblemWithItems:
problem: str
items: Sequence[str]
@dataclasses.dataclass
class _MissingSecurityOwnersResult:
owners_file_problems: Sequence[_SecurityProblemWithItems]
has_security_sensitive_files: bool
missing_reviewer_problem: Optional[_SecurityProblemWithItems]
def _FindMissingSecurityOwners(
input_api,
file_patterns: Sequence[str],
excluded_patterns: Sequence[str],
required_owners_file: str,
custom_rule_function: Optional[Callable] = None
) -> _MissingSecurityOwnersResult:
"""Find OWNERS files missing per-file rules for security-sensitive files.
Args:
input_api: the PRESUBMIT input API object.
file_patterns: basename patterns that require a corresponding per-file
security restriction.
excluded_patterns: path patterns that should be exempted from
requiring a security restriction.
required_owners_file: path to the required OWNERS file, e.g.
ipc/SECURITY_OWNERS
cc_alias: If not None, email that will be CCed automatically if the
change contains security-sensitive files, as determined by
`file_patterns` and `excluded_patterns`.
custom_rule_function: If not None, will be called with `input_api` and
the current file under consideration. Returning True will add an
exact match per-file rule check for the current file.
"""
to_check = {}
files_to_review = []
def AddPatternToCheck(file, pattern):
owners_file = input_api.os_path.join(
input_api.os_path.dirname(file.LocalPath()), 'OWNERS')
if owners_file not in to_check:
to_check[owners_file] = {}
if pattern not in to_check[owners_file]:
to_check[owners_file][pattern] = {
'files': [],
'rules': [
f'per-file {pattern}=set noparent',
f'per-file {pattern}=file://{required_owners_file}',
]
}
to_check[owners_file][pattern]['files'].append(file.LocalPath())
files_to_review.append(file.LocalPath())
for file in input_api.AffectedFiles(include_deletes=False):
file_basename = input_api.os_path.basename(file.LocalPath())
if custom_rule_function is not None and custom_rule_function(
input_api, file):
AddPatternToCheck(file, file_basename)
continue
if any(
input_api.fnmatch.fnmatch(file.LocalPath(), pattern)
for pattern in excluded_patterns):
continue
for pattern in file_patterns:
if input_api.fnmatch.fnmatch(file_basename, pattern):
AddPatternToCheck(file, pattern)
break
has_security_sensitive_files = bool(to_check)
newly_covered_files = []
for file in input_api.AffectedFiles(include_deletes=False):
if not file.LocalPath() in to_check:
continue
for _, line in file.ChangedContents():
for _, entry in to_check[file.LocalPath()].items():
if line in entry['rules']:
newly_covered_files.extend(entry['files'])
missing_reviewer_problems = None
if newly_covered_files and not _ChangeHasSecurityReviewer(
input_api, required_owners_file):
missing_reviewer_problems = _SecurityProblemWithItems(
f'Review from an owner in {required_owners_file} is required for '
'the following newly-added files:',
[f'{file}' for file in sorted(set(newly_covered_files))])
for owners_file, patterns in to_check.items():
try:
lines = set(
input_api.ReadFile(
input_api.os_path.join(input_api.change.RepositoryRoot(),
owners_file)).splitlines())
for entry in patterns.values():
entry['rules'] = [
rule for rule in entry['rules'] if rule not in lines
]
except IOError:
continue
owners_file_problems = []
for owners_file, patterns in to_check.items():
missing_lines = []
files = []
for _, entry in patterns.items():
files.extend(entry['files'])
missing_lines.extend(entry['rules'])
if missing_lines:
joined_missing_lines = '\n'.join(line for line in missing_lines)
owners_file_problems.append(
_SecurityProblemWithItems(
'Found missing OWNERS lines for security-sensitive files. '
f'Please add the following lines to {owners_file}:\n'
f'{joined_missing_lines}\n\nTo ensure security review for:',
files))
return _MissingSecurityOwnersResult(owners_file_problems,
has_security_sensitive_files,
missing_reviewer_problems)
def _CheckChangeForIpcSecurityOwners(input_api):
file_patterns = [
'*_messages.cc',
'*_messages*.h',
'*_param_traits*.*',
'*.mojom',
'*_mojom_traits*.*',
'*_type_converter*.*',
'*.aidl',
]
excluded_patterns = [
'third_party/crashpad/*',
'third_party/blink/renderer/platform/bindings/*',
'third_party/protobuf/*',
'third_party/win_build_output/*',
'third_party/blink/public/mojom/use_counter/metrics/*',
]
def IsMojoServiceManifestFile(input_api, file):
manifest_pattern = input_api.re.compile(r'manifests?\.(cc|h)$')
test_manifest_pattern = input_api.re.compile(
r'test_manifests?\.(cc|h)')
if not manifest_pattern.search(file.LocalPath()):
return False
if test_manifest_pattern.search(file.LocalPath()):
return False
return any('service_manager::Manifest' in line
for line in file.NewContents())
return _FindMissingSecurityOwners(
input_api,
file_patterns,
excluded_patterns,
'ipc/SECURITY_OWNERS',
custom_rule_function=IsMojoServiceManifestFile)
def _CheckChangeForFuchsiaSecurityOwners(input_api):
file_patterns = [
'*.cml',
'*.cmx',
'*.fidl',
]
excluded_patterns = [
'third_party/crashpad/*',
]
return _FindMissingSecurityOwners(input_api, file_patterns,
excluded_patterns,
'build/fuchsia/SECURITY_OWNERS')
def CheckSecurityOwners(input_api, output_api):
"""Checks that various security-sensitive files have an IPC OWNERS rule."""
ipc_results = _CheckChangeForIpcSecurityOwners(input_api)
fuchsia_results = _CheckChangeForFuchsiaSecurityOwners(input_api)
if ipc_results.has_security_sensitive_files:
output_api.AppendCC('ipc-security-reviews@chromium.org')
results = []
missing_reviewer_problems = []
if ipc_results.missing_reviewer_problem:
missing_reviewer_problems.append(ipc_results.missing_reviewer_problem)
if fuchsia_results.missing_reviewer_problem:
missing_reviewer_problems.append(
fuchsia_results.missing_reviewer_problem)
if input_api.is_committing and input_api.change.issue:
make_presubmit_message = output_api.PresubmitError
else:
make_presubmit_message = output_api.PresubmitNotifyResult
for problem in missing_reviewer_problems:
results.append(
make_presubmit_message(problem.problem, items=problem.items))
owners_file_problems = []
owners_file_problems.extend(ipc_results.owners_file_problems)
owners_file_problems.extend(fuchsia_results.owners_file_problems)
for problem in owners_file_problems:
results.append(
output_api.PresubmitError(problem.problem, items=problem.items))
return results
def _GetFilesUsingSecurityCriticalFunctions(input_api):
"""Checks affected files for changes to security-critical calls. This
function checks the full change diff, to catch both additions/changes
and removals.
Returns a dict keyed by file name, and the value is a set of detected
functions.
"""
_PATTERNS_TO_CHECK = {
'content::GetServiceSandboxType<>()': 'GetServiceSandboxType\\<'
}
_PATTERNS_TO_CHECK = {
k: input_api.re.compile(v)
for k, v in _PATTERNS_TO_CHECK.items()
}
def presubmit_file_filter(f):
return 'PRESUBMIT.py' != input_api.os_path.split(f.LocalPath())[1]
files_to_functions = {}
for f in input_api.AffectedFiles(file_filter=presubmit_file_filter):
diff = f.GenerateScmDiff()
for line in diff.split('\n'):
if line.startswith('-') or (line.startswith('+')
and not line.startswith('++')):
for name, pattern in _PATTERNS_TO_CHECK.items():
if pattern.search(line):
path = f.LocalPath()
if not path in files_to_functions:
files_to_functions[path] = set()
files_to_functions[path].add(name)
return files_to_functions
def CheckSecurityChanges(input_api, output_api):
"""Checks that changes involving security-critical functions are reviewed
by the security team.
"""
files_to_functions = _GetFilesUsingSecurityCriticalFunctions(input_api)
if not files_to_functions:
return []
owners_file = 'ipc/SECURITY_OWNERS'
if _ChangeHasSecurityReviewer(input_api, owners_file):
return []
msg = ('The following files change calls to security-sensitive functions\n'
'that need to be reviewed by {}.\n'.format(owners_file))
for path, names in files_to_functions.items():
msg += ' {}\n'.format(path)
for name in names:
msg += ' {}\n'.format(name)
msg += '\n'
if input_api.is_committing:
output = output_api.PresubmitError
else:
output = output_api.PresubmitNotifyResult
return [output(msg)]
def CheckSetNoParent(input_api, output_api):
"""Checks that set noparent is only used together with an OWNERS file in
//build/OWNERS.setnoparent (see also
//docs/code_reviews.md#owners-files-details)
"""
if not any(f.LocalPath().endswith('OWNERS')
for f in input_api.AffectedFiles(include_deletes=False)):
return []
errors = []
allowed_owners_files_file = 'build/OWNERS.setnoparent'
allowed_owners_files = set()
with open(allowed_owners_files_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
allowed_owners_files.add(line)
per_file_pattern = input_api.re.compile('per-file (.+)=(.+)')
for f in input_api.AffectedFiles(include_deletes=False):
if not f.LocalPath().endswith('OWNERS'):
continue
found_owners_files = set()
found_set_noparent_lines = dict()
for lineno, line in enumerate(f.NewContents(), 1):
line = line.strip()
if line.startswith('set noparent'):
found_set_noparent_lines[''] = lineno
if line.startswith('file://'):
if line in allowed_owners_files:
found_owners_files.add('')
if line.startswith('per-file'):
match = per_file_pattern.match(line)
if match:
glob = match.group(1).strip()
directive = match.group(2).strip()
if directive == 'set noparent':
found_set_noparent_lines[glob] = lineno
if directive.startswith('file://'):
if directive in allowed_owners_files:
found_owners_files.add(glob)
linux_path = f.UnixLocalPath()
if (linux_path.count('/') != 1
and (not linux_path in _EXCLUDED_SET_NO_PARENT_PATHS)):
for set_noparent_line in found_set_noparent_lines:
if set_noparent_line in found_owners_files:
continue
errors.append(
' %s:%d' %
(linux_path, found_set_noparent_lines[set_noparent_line]))
results = []
if errors:
if input_api.is_committing:
output = output_api.PresubmitError
else:
output = output_api.PresubmitPromptWarning
results.append(
output(
'Found the following "set noparent" restrictions in OWNERS files that '
'do not include owners from build/OWNERS.setnoparent:',
long_text='\n\n'.join(errors)))
return results
def CheckUselessForwardDeclarations(input_api, output_api):
"""Checks that added or removed lines in non third party affected
header files do not lead to new useless class or struct forward
declaration.
"""
results = []
class_pattern = input_api.re.compile(r'^class\s+(\w+);$',
input_api.re.MULTILINE)
struct_pattern = input_api.re.compile(r'^struct\s+(\w+);$',
input_api.re.MULTILINE)
for f in input_api.AffectedFiles(include_deletes=False):
local_path = f.UnixLocalPath()
if (local_path.startswith('third_party')
and not local_path.startswith('third_party/blink')):
continue
if not local_path.endswith('.h'):
continue
contents = input_api.ReadFile(f)
fwd_decls = input_api.re.findall(class_pattern, contents)
fwd_decls.extend(input_api.re.findall(struct_pattern, contents))
useless_fwd_decls = []
for decl in fwd_decls:
count = sum(1 for _ in input_api.re.finditer(
r'\b%s\b' % input_api.re.escape(decl), contents))
if count == 1:
useless_fwd_decls.append(decl)
if not useless_fwd_decls:
continue
for line in f.GenerateScmDiff().splitlines():
if (line.startswith('-') and not line.startswith('--')
or line.startswith('+') and not line.startswith('++')):
for decl in useless_fwd_decls:
if input_api.re.search(r'\b%s\b' % decl, line[1:]):
results.append(
output_api.PresubmitPromptWarning(
'%s: %s forward declaration is no longer needed'
% (f.LocalPath(), decl)))
useless_fwd_decls.remove(decl)
return results
def _CheckAndroidDebuggableBuild(input_api, output_api):
"""Checks that code uses BuildInfo.isDebugAndroid() instead of
Build.TYPE.equals('') or ''.equals(Build.TYPE) to check if
this is a debuggable build of Android.
"""
build_type_check_pattern = input_api.re.compile(
r'\bBuild\.TYPE\.equals\(|\.equals\(\s*\bBuild\.TYPE\)')
errors = []
sources = lambda affected_file: input_api.FilterSourceFile(
affected_file,
files_to_skip=(
_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS + input_api.
DEFAULT_FILES_TO_SKIP + (
r'^android_webview/support_library/boundary_interfaces/',
r'^chrome/android/webapk/.*',
r'^third_party/.*',
r'tools/android/customtabs_benchmark/.*',
r'webview/chromium/License.*',
)),
files_to_check=[r'.*\.java$'])
for f in input_api.AffectedSourceFiles(sources):
for line_num, line in f.ChangedContents():
if build_type_check_pattern.search(line):
errors.append('%s:%d' % (f.LocalPath(), line_num))
results = []
if errors:
results.append(
output_api.PresubmitPromptWarning(
'Build.TYPE.equals or .equals(Build.TYPE) usage is detected.'
' Please use BuildInfo.isDebugAndroid() instead.', errors))
return results
def _CheckAndroidToastUsage(input_api, output_api):
"""Checks that code uses org.chromium.ui.widget.Toast instead of
android.widget.Toast (Chromium Toast doesn't force hardware
acceleration on low-end devices, saving memory).
"""
toast_import_pattern = input_api.re.compile(
r'^import android\.widget\.Toast;$')
errors = []
sources = lambda affected_file: input_api.FilterSourceFile(
affected_file,
files_to_skip=(_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS + input_api.
DEFAULT_FILES_TO_SKIP +
(r'^chromecast/.*', r'^remoting/.*')),
files_to_check=[r'.*\.java$'])
for f in input_api.AffectedSourceFiles(sources):
for line_num, line in f.ChangedContents():
if toast_import_pattern.search(line):
errors.append('%s:%d' % (f.LocalPath(), line_num))
results = []
if errors:
results.append(
output_api.PresubmitError(
'android.widget.Toast usage is detected. Android toasts use hardware'
' acceleration, and can be\ncostly on low-end devices. Please use'
' org.chromium.ui.widget.Toast instead.\n'
'Contact dskiba@chromium.org if you have any questions.',
errors))
return results
def _CheckAndroidCrLogUsage(input_api, output_api):
"""Checks that new logs using org.chromium.base.Log:
- Are using 'TAG' as variable name for the tags (warn)
- Are using a tag that is shorter than 20 characters (error)
"""
cr_log_check_excluded_paths = [
r'^chrome/android/webapk/.*',
r'^android_webview/glue/java/src/com/android/'
r'webview/chromium/License.*',
r'tools/android/customtabs_benchmark/.*',
]
cr_log_import_pattern = input_api.re.compile(
r'^import org\.chromium\.base\.Log;$', input_api.re.MULTILINE)
class_in_base_pattern = input_api.re.compile(
r'^package org\.chromium\.base;$', input_api.re.MULTILINE)
has_some_log_import_pattern = input_api.re.compile(r'^import .*\.Log;$',
input_api.re.MULTILINE)
log_call_pattern = input_api.re.compile(r'\bLog\.\w\((?P<tag>\"?\w+)')
log_decl_pattern = input_api.re.compile(
r'static final String TAG = "(?P<name>(.*))"')
rough_log_decl_pattern = input_api.re.compile(r'\bString TAG\s*=')
REF_MSG = ('See docs/android_logging.md for more info.')
sources = lambda x: input_api.FilterSourceFile(
x,
files_to_check=[r'.*\.java$'],
files_to_skip=cr_log_check_excluded_paths)
tag_decl_errors = []
tag_length_errors = []
tag_errors = []
tag_with_dot_errors = []
util_log_errors = []
for f in input_api.AffectedSourceFiles(sources):
file_content = input_api.ReadFile(f)
has_modified_logs = False
if (cr_log_import_pattern.search(file_content)
or (class_in_base_pattern.search(file_content)
and not has_some_log_import_pattern.search(file_content))):
for line_num, line in f.ChangedContents():
if rough_log_decl_pattern.search(line):
has_modified_logs = True
match = log_call_pattern.search(line)
if match:
has_modified_logs = True
if not match.group('tag') == 'TAG':
tag_errors.append('%s:%d' % (f.LocalPath(), line_num))
else:
for line_num, line in f.ChangedContents():
if (log_call_pattern.search(line)
or has_some_log_import_pattern.search(line)):
util_log_errors.append('%s:%d' % (f.LocalPath(), line_num))
if has_modified_logs:
match = log_decl_pattern.search(file_content)
tag_name = match.group('name') if match else None
if not tag_name:
tag_decl_errors.append(f.LocalPath())
elif len(tag_name) > 20:
tag_length_errors.append(f.LocalPath())
elif '.' in tag_name:
tag_with_dot_errors.append(f.LocalPath())
results = []
if tag_decl_errors:
results.append(
output_api.PresubmitPromptWarning(
'Please define your tags using the suggested format: .\n'
'"private static final String TAG = "<package tag>".\n'
'They will be prepended with "cr_" automatically.\n' + REF_MSG,
tag_decl_errors))
if tag_length_errors:
results.append(
output_api.PresubmitError(
'The tag length is restricted by the system to be at most '
'20 characters.\n' + REF_MSG, tag_length_errors))
if tag_errors:
results.append(
output_api.PresubmitPromptWarning(
'Please use a variable named "TAG" for your log tags.\n' +
REF_MSG, tag_errors))
if util_log_errors:
results.append(
output_api.PresubmitPromptWarning(
'Please use org.chromium.base.Log for new logs.\n' + REF_MSG,
util_log_errors))
if tag_with_dot_errors:
results.append(
output_api.PresubmitPromptWarning(
'Dot in log tags cause them to be elided in crash reports.\n' +
REF_MSG, tag_with_dot_errors))
return results
def _CheckAndroidTestAnnotationUsage(input_api, output_api):
"""Checks that android.test.suitebuilder.annotation.* is no longer used."""
deprecated_annotation_import_pattern = input_api.re.compile(
r'^import android\.test\.suitebuilder\.annotation\..*;',
input_api.re.MULTILINE)
sources = lambda x: input_api.FilterSourceFile(
x, files_to_check=[r'.*\.java$'], files_to_skip=None)
errors = []
for f in input_api.AffectedFiles(file_filter=sources):
for line_num, line in f.ChangedContents():
if deprecated_annotation_import_pattern.search(line):
errors.append('%s:%d' % (f.LocalPath(), line_num))
results = []
if errors:
results.append(
output_api.PresubmitError(
'Annotations in android.test.suitebuilder.annotation have been'
' deprecated since API level 24. Please use androidx.test.filters'
' from //third_party/androidx:androidx_test_runner_java instead.'
' Contact yolandyan@chromium.org if you have any questions.',
errors))
return results
def _CheckAndroidNewMdpiAssetLocation(input_api, output_api):
"""Checks if MDPI assets are placed in a correct directory."""
file_filter = lambda f: (f.UnixLocalPath().endswith('.png') and
('/res/drawable/' in f.UnixLocalPath() or
'/res/drawable-ldrtl/' in f.UnixLocalPath()))
errors = []
for f in input_api.AffectedFiles(include_deletes=False,
file_filter=file_filter):
errors.append(' %s' % f.LocalPath())
results = []
if errors:
results.append(
output_api.PresubmitError(
'MDPI assets should be placed in /res/drawable-mdpi/ or '
'/res/drawable-ldrtl-mdpi/\ninstead of /res/drawable/ and'
'/res/drawable-ldrtl/.\n'
'Contact newt@chromium.org if you have questions.', errors))
return results
def _CheckAndroidWebkitImports(input_api, output_api):
"""Checks that code uses org.chromium.base.Callback instead of
android.webview.ValueCallback except in the WebView glue layer.
"""
valuecallback_import_pattern = input_api.re.compile(
r'^import android\.webkit\.ValueCallback;$')
errors = []
sources = lambda affected_file: input_api.FilterSourceFile(
affected_file,
files_to_skip=(_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS + input_api.
DEFAULT_FILES_TO_SKIP + (
r'^android_webview/glue/.*',
r'^android_webview/support_library/.*',
)),
files_to_check=[r'.*\.java$'])
for f in input_api.AffectedSourceFiles(sources):
for line_num, line in f.ChangedContents():
if valuecallback_import_pattern.search(line):
errors.append('%s:%d' % (f.LocalPath(), line_num))
results = []
if errors:
results.append(
output_api.PresubmitError(
'android.webkit.ValueCallback usage is detected outside of the glue'
' layer. To stay compatible with the support library, android.webkit.*'
' classes should only be used inside the glue layer and'
' org.chromium.base.Callback should be used instead.', errors))
return results
def _CheckAndroidXmlStyle(input_api, output_api, is_check_on_upload):
"""Checks Android XML styles """
if not any(
_IsXmlOrGrdFile(input_api, f.LocalPath())
for f in input_api.AffectedFiles(include_deletes=False)):
return []
import sys
original_sys_path = sys.path
try:
sys.path = sys.path + [
input_api.os_path.join(input_api.PresubmitLocalPath(), 'tools',
'android', 'checkxmlstyle')
]
import checkxmlstyle
finally:
sys.path = original_sys_path
if is_check_on_upload:
return checkxmlstyle.CheckStyleOnUpload(input_api, output_api)
else:
return checkxmlstyle.CheckStyleOnCommit(input_api, output_api)
def _CheckAndroidInfoBarDeprecation(input_api, output_api):
"""Checks Android Infobar Deprecation """
import sys
original_sys_path = sys.path
try:
sys.path = sys.path + [
input_api.os_path.join(input_api.PresubmitLocalPath(), 'tools',
'android', 'infobar_deprecation')
]
import infobar_deprecation
finally:
sys.path = original_sys_path
return infobar_deprecation.CheckDeprecationOnUpload(input_api, output_api)
class _PydepsCheckerResult:
def __init__(self, cmd, pydeps_path, process, old_contents):
self._cmd = cmd
self._pydeps_path = pydeps_path
self._process = process
self._old_contents = old_contents
def GetError(self):
"""Returns an error message, or None."""
import difflib
new_contents = self._process.stdout.read().splitlines()[2:]
if self._process.wait() != 0:
return 'Command failed: ' + self._cmd
if self._old_contents != new_contents:
diff = '\n'.join(
difflib.context_diff(self._old_contents, new_contents))
return ('File is stale: {}\n'
'Diff (apply to fix):\n'
'{}\n'
'To regenerate, run:\n\n'
' {}').format(self._pydeps_path, diff, self._cmd)
return None
class PydepsChecker:
def __init__(self, input_api, pydeps_files):
self._file_cache = {}
self._input_api = input_api
self._pydeps_files = pydeps_files
def _LoadFile(self, path):
"""Returns the list of paths within a .pydeps file relative to //."""
if path not in self._file_cache:
with open(path, encoding='utf-8') as f:
self._file_cache[path] = f.read()
return self._file_cache[path]
def _ComputeNormalizedPydepsEntries(self, pydeps_path):
"""Returns an iterable of paths within the .pydep, relativized to //."""
pydeps_data = self._LoadFile(pydeps_path)
uses_gn_paths = '--gn-paths' in pydeps_data
entries = (l for l in pydeps_data.splitlines()
if not l.startswith('#'))
if uses_gn_paths:
return (e[2:] for e in entries)
else:
os_path = self._input_api.os_path
pydeps_dir = os_path.dirname(pydeps_path)
return (os_path.normpath(os_path.join(pydeps_dir, e))
for e in entries)
def _CreateFilesToPydepsMap(self):
"""Returns a map of local_path -> list_of_pydeps."""
ret = {}
for pydep_local_path in self._pydeps_files:
for path in self._ComputeNormalizedPydepsEntries(pydep_local_path):
ret.setdefault(path, []).append(pydep_local_path)
return ret
def ComputeAffectedPydeps(self):
"""Returns an iterable of .pydeps files that might need regenerating."""
affected_pydeps = set()
file_to_pydeps_map = None
for f in self._input_api.AffectedFiles(include_deletes=True):
local_path = f.LocalPath()
if local_path in ('DEPS', 'PRESUBMIT.py'
) or local_path.endswith('print_python_deps.py'):
return self._pydeps_files
elif local_path.endswith('.pydeps'):
if local_path in self._pydeps_files:
affected_pydeps.add(local_path)
elif local_path.endswith('.py'):
if file_to_pydeps_map is None:
file_to_pydeps_map = self._CreateFilesToPydepsMap()
affected_pydeps.update(file_to_pydeps_map.get(local_path, ()))
return affected_pydeps
def DetermineIfStaleAsync(self, pydeps_path):
"""Runs print_python_deps.py to see if the files is stale."""
import os
old_pydeps_data = self._LoadFile(pydeps_path).splitlines()
if old_pydeps_data:
cmd = old_pydeps_data[1][1:].strip()
if '--output' not in cmd:
cmd += ' --output ' + pydeps_path
old_contents = old_pydeps_data[2:]
else:
cmd = 'build/print_python_deps.py {} --root={} --output={}'.format(
pydeps_path[:-4], os.path.dirname(pydeps_path), pydeps_path)
old_contents = []
env = dict(os.environ)
env['PYTHONDONTWRITEBYTECODE'] = '1'
process = self._input_api.subprocess.Popen(
cmd + ' --output ""',
shell=True,
env=env,
stdout=self._input_api.subprocess.PIPE,
encoding='utf-8')
return _PydepsCheckerResult(cmd, pydeps_path, process, old_contents)
def _ParseGclientArgs():
args = {}
with open('build/config/gclient_args.gni', 'r') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
attribute, value = line.split('=')
args[attribute.strip()] = value.strip()
return args
def CheckPydepsNeedsUpdating(input_api, output_api, checker_for_tests=None):
"""Checks if a .pydeps file needs to be regenerated."""
if not input_api.platform.startswith('linux'):
return []
results = []
for f in input_api.AffectedFiles(include_deletes=True):
if f.LocalPath().endswith('.pydeps'):
exists = input_api.os_path.exists(f.LocalPath())
if f.Action() == 'D' and f.LocalPath() in _ALL_PYDEPS_FILES:
results.append(
output_api.PresubmitError(
'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to '
'remove %s' % f.LocalPath()))
elif (f.Action() != 'D' and exists
and f.LocalPath() not in _ALL_PYDEPS_FILES):
results.append(
output_api.PresubmitError(
'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to '
'include %s' % f.LocalPath()))
if results:
return results
try:
parsed_args = _ParseGclientArgs()
except FileNotFoundError:
message = (
'build/config/gclient_args.gni not found. Please make sure your '
'workspace has been initialized with gclient sync.')
import sys
original_sys_path = sys.path
try:
sys.path = sys.path + [
input_api.os_path.join(input_api.PresubmitLocalPath(),
'third_party', 'depot_tools')
]
import gclient_utils
if gclient_utils.IsEnvCog():
results.append(output_api.PresubmitPromptWarning(message))
else:
results.append(output_api.PresubmitError(message))
return results
finally:
sys.path = original_sys_path
is_android = parsed_args.get('checkout_android', 'false') == 'true'
checker = checker_for_tests or PydepsChecker(input_api, _ALL_PYDEPS_FILES)
affected_pydeps = set(checker.ComputeAffectedPydeps())
affected_android_pydeps = affected_pydeps.intersection(
set(_ANDROID_SPECIFIC_PYDEPS_FILES))
if affected_android_pydeps and not is_android:
results.append(
output_api.PresubmitPromptOrNotify(
'You have changed python files that may affect pydeps for android\n'
'specific scripts. However, the relevant presubmit check cannot be\n'
'run because you are not using an Android checkout. To validate that\n'
'the .pydeps are correct, re-run presubmit in an Android checkout, or\n'
'use the android-internal-presubmit optional trybot.\n'
'Possibly stale pydeps files:\n{}'.format(
'\n'.join(affected_android_pydeps))))
all_pydeps = _ALL_PYDEPS_FILES if is_android else _GENERIC_PYDEPS_FILES
pydeps_to_check = affected_pydeps.intersection(all_pydeps)
pydep_results = [checker.DetermineIfStaleAsync(p) for p in pydeps_to_check]
for result in pydep_results:
error_msg = result.GetError()
if error_msg:
results.append(output_api.PresubmitError(error_msg))
return results
def CheckSingletonInHeaders(input_api, output_api):
"""Checks to make sure no header files have |Singleton<|."""
def FileFilter(affected_file):
files_to_skip = (_EXCLUDED_PATHS + input_api.DEFAULT_FILES_TO_SKIP +
(r'^base/memory/singleton\.h$',
r'^net/quic/platform/impl/quic_singleton_impl\.h$'))
return input_api.FilterSourceFile(affected_file,
files_to_skip=files_to_skip)
pattern = input_api.re.compile(r'(?<!class\sbase::)Singleton\s*<')
files = []
for f in input_api.AffectedSourceFiles(FileFilter):
if (f.LocalPath().endswith('.h') or f.LocalPath().endswith('.hxx')
or f.LocalPath().endswith('.hpp')
or f.LocalPath().endswith('.inl')):
contents = input_api.ReadFile(f)
for line in contents.splitlines(False):
if (not line.lstrip().startswith('//')
and
pattern.search(line)):
files.append(f)
break
if files:
return [
output_api.PresubmitError(
'Found base::Singleton<T> in the following header files.\n' +
'Please move them to an appropriate source file so that the ' +
'template gets instantiated in a single compilation unit.',
files)
]
return []
_DEPRECATED_CSS = [
('-webkit-box', 'flex'),
('-webkit-inline-box', 'inline-flex'),
('-webkit-flex', 'flex'),
('-webkit-inline-flex', 'inline-flex'),
('-webkit-min-content', 'min-content'),
('-webkit-max-content', 'max-content'),
('-webkit-background-clip', 'background-clip'),
('-webkit-background-origin', 'background-origin'),
('-webkit-background-size', 'background-size'),
('-webkit-box-shadow', 'box-shadow'),
('-webkit-user-select', 'user-select'),
('-webkit-gradient', 'gradient'),
('-webkit-repeating-gradient', 'repeating-gradient'),
('-webkit-linear-gradient', 'linear-gradient'),
('-webkit-repeating-linear-gradient', 'repeating-linear-gradient'),
('-webkit-radial-gradient', 'radial-gradient'),
('-webkit-repeating-radial-gradient', 'repeating-radial-gradient'),
]
def CheckNoDeprecatedCss(input_api, output_api):
""" Make sure that we don't use deprecated CSS
properties, functions or values. Our external
documentation and iOS CSS for dom distiller
(reader mode) are ignored by the hooks as it
needs to be consumed by WebKit. """
results = []
file_inclusion_pattern = [r'.+\.css$']
files_to_skip = (
_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
input_api.DEFAULT_FILES_TO_SKIP +
(
r'^chrome/browser/resources/chromeos/arc_support/cr_overlay.css$',
r'^chrome/common/extensions/docs',
r'^chrome/docs',
r'ui/webui/resources/cr_components/most_visited/.*\.css$',
r'ui/webui/resources/cr_components/searchbox/searchbox_match.css$')
)
file_filter = lambda f: input_api.FilterSourceFile(
f, files_to_check=file_inclusion_pattern, files_to_skip=files_to_skip)
for fpath in input_api.AffectedFiles(file_filter=file_filter):
for line_num, line in fpath.ChangedContents():
for (deprecated_value, value) in _DEPRECATED_CSS:
if deprecated_value in line:
results.append(
output_api.PresubmitError(
'%s:%d: Use of deprecated CSS %s, use %s instead' %
(fpath.LocalPath(), line_num, deprecated_value,
value)))
return results
def CheckForRelativeIncludes(input_api, output_api):
bad_files = {}
for f in input_api.AffectedFiles(include_deletes=False):
if (f.UnixLocalPath().startswith('third_party')
and not f.LocalPath().startswith('third_party/blink')):
continue
if not _IsCPlusPlusFile(input_api, f.LocalPath()):
continue
relative_includes = [
line for _, line in f.ChangedContents()
if '#include' in line and '../' in line
]
if not relative_includes:
continue
bad_files[f.LocalPath()] = relative_includes
if not bad_files:
return []
error_descriptions = []
for file_path, bad_lines in bad_files.items():
error_description = file_path
for line in bad_lines:
error_description += '\n ' + line
error_descriptions.append(error_description)
results = []
results.append(
output_api.PresubmitError(
'You added one or more relative #include paths (including "../").\n'
'These shouldn\'t be used because they can be used to include headers\n'
'from code that\'s not correctly specified as a dependency in the\n'
'relevant BUILD.gn file(s).', error_descriptions))
return results
def CheckForCcIncludes(input_api, output_api):
"""Check that nobody tries to include a cc file. It's a relatively
common error which results in duplicate symbols in object
files. This may not always break the build until someone later gets
very confusing linking errors."""
results = []
for f in input_api.AffectedFiles(include_deletes=False):
if (f.UnixLocalPath().startswith('third_party')
and not f.LocalPath().startswith('third_party/blink')):
continue
if not _IsCPlusPlusFile(input_api, f.LocalPath()):
continue
for _, line in f.ChangedContents():
if line.startswith('#include "'):
included_file = line.split('"')[1]
if _IsCPlusPlusFile(input_api, included_file):
if not included_file.endswith(('.h', '-inc.cc')):
results.append(
output_api.PresubmitError(
'Only header files or .inc files should be included in other\n'
'C++ files. Compiling the contents of a cc file more than once\n'
'will cause duplicate information in the build which may later\n'
'result in strange link_errors.\n' +
f.LocalPath() + ':\n ' + line))
return results
def _CheckWatchlistDefinitionsEntrySyntax(key, value, ast):
if not isinstance(key, ast.Str):
return 'Key at line %d must be a string literal' % key.lineno
if not isinstance(value, ast.Dict):
return 'Value at line %d must be a dict' % value.lineno
if len(value.keys) != 1:
return 'Dict at line %d must have single entry' % value.lineno
if not isinstance(value.keys[0], ast.Str) or value.keys[0].s != 'filepath':
return (
'Entry at line %d must have a string literal \'filepath\' as key' %
value.lineno)
return None
def _CheckWatchlistsEntrySyntax(key, value, ast, email_regex):
if not isinstance(key, ast.Str):
return 'Key at line %d must be a string literal' % key.lineno
if not isinstance(value, ast.List):
return 'Value at line %d must be a list' % value.lineno
for element in value.elts:
if not isinstance(element, ast.Str):
return 'Watchlist elements on line %d is not a string' % key.lineno
if not email_regex.match(element.s):
return ('Watchlist element on line %d doesn\'t look like a valid '
+ 'email: %s') % (key.lineno, element.s)
return None
def _CheckWATCHLISTSEntries(wd_dict, w_dict, input_api):
mismatch_template = (
'Mismatch between WATCHLIST_DEFINITIONS entry (%s) and WATCHLISTS '
'entry (%s)')
email_regex = input_api.re.compile(
r'^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+$')
ast = input_api.ast
i = 0
last_key = ''
while True:
if i >= len(wd_dict.keys):
if i >= len(w_dict.keys):
return None
return mismatch_template % ('missing',
'line %d' % w_dict.keys[i].lineno)
elif i >= len(w_dict.keys):
return (mismatch_template %
('line %d' % wd_dict.keys[i].lineno, 'missing'))
wd_key = wd_dict.keys[i]
w_key = w_dict.keys[i]
result = _CheckWatchlistDefinitionsEntrySyntax(wd_key,
wd_dict.values[i], ast)
if result is not None:
return 'Bad entry in WATCHLIST_DEFINITIONS dict: %s' % result
result = _CheckWatchlistsEntrySyntax(w_key, w_dict.values[i], ast,
email_regex)
if result is not None:
return 'Bad entry in WATCHLISTS dict: %s' % result
if wd_key.s != w_key.s:
return mismatch_template % ('%s at line %d' %
(wd_key.s, wd_key.lineno),
'%s at line %d' %
(w_key.s, w_key.lineno))
if wd_key.s < last_key:
return (
'WATCHLISTS dict is not sorted lexicographically at line %d and %d'
% (wd_key.lineno, w_key.lineno))
last_key = wd_key.s
i = i + 1
def _CheckWATCHLISTSSyntax(expression, input_api):
ast = input_api.ast
if not isinstance(expression, ast.Expression):
return 'WATCHLISTS file must contain a valid expression'
dictionary = expression.body
if not isinstance(dictionary, ast.Dict) or len(dictionary.keys) != 2:
return 'WATCHLISTS file must have single dict with exactly two entries'
first_key = dictionary.keys[0]
first_value = dictionary.values[0]
second_key = dictionary.keys[1]
second_value = dictionary.values[1]
if (not isinstance(first_key, ast.Str)
or first_key.s != 'WATCHLIST_DEFINITIONS'
or not isinstance(first_value, ast.Dict)):
return ('The first entry of the dict in WATCHLISTS file must be '
'WATCHLIST_DEFINITIONS dict')
if (not isinstance(second_key, ast.Str) or second_key.s != 'WATCHLISTS'
or not isinstance(second_value, ast.Dict)):
return ('The second entry of the dict in WATCHLISTS file must be '
'WATCHLISTS dict')
return _CheckWATCHLISTSEntries(first_value, second_value, input_api)
def CheckWATCHLISTS(input_api, output_api):
for f in input_api.AffectedFiles(include_deletes=False):
if f.LocalPath() == 'WATCHLISTS':
contents = input_api.ReadFile(f, 'r')
try:
input_api.ast.literal_eval(contents)
expression = input_api.ast.parse(contents,
filename='WATCHLISTS',
mode='eval')
except ValueError as e:
return [
output_api.PresubmitError('Cannot parse WATCHLISTS file',
long_text=repr(e))
]
except SyntaxError as e:
return [
output_api.PresubmitError('Cannot parse WATCHLISTS file',
long_text=repr(e))
]
except TypeError as e:
return [
output_api.PresubmitError('Cannot parse WATCHLISTS file',
long_text=repr(e))
]
result = _CheckWATCHLISTSSyntax(expression, input_api)
if result is not None:
return [output_api.PresubmitError(result)]
break
return []
def CheckGnRebasePath(input_api, output_api):
"""Checks that target_gen_dir is not used with "//" in rebase_path().
Developers should use root_build_dir instead of "//" when using target_gen_dir because
Chromium is sometimes built outside of the source tree.
"""
def gn_files(f):
return input_api.FilterSourceFile(f, files_to_check=(r'.+\.gn', ))
rebase_path_regex = input_api.re.compile(
r'rebase_path\(("\$target_gen_dir"|target_gen_dir), ("/"|"//")\)')
problems = []
for f in input_api.AffectedSourceFiles(gn_files):
for line_num, line in f.ChangedContents():
if rebase_path_regex.search(line):
problems.append('Absolute path in rebase_path() in %s:%d' %
(f.LocalPath(), line_num))
if problems:
return [
output_api.PresubmitPromptWarning(
'Using an absolute path in rebase_path()',
items=sorted(problems),
long_text=(
'rebase_path() should use root_build_dir instead of "/" ',
'since builds can be initiated from outside of the source ',
'root.'))
]
return []
def CheckGnGlobForward(input_api, output_api):
"""Checks that forward_variables_from(invoker, "*") follows best practices.
As documented at //build/docs/writing_gn_templates.md
"""
def gn_files(f):
return input_api.FilterSourceFile(f, files_to_check=(r'.+\.gni', ))
problems = []
for f in input_api.AffectedSourceFiles(gn_files):
for line_num, line in f.ChangedContents():
if 'forward_variables_from(invoker, "*")' in line:
problems.append(
'Bare forward_variables_from(invoker, "*") in %s:%d' %
(f.LocalPath(), line_num))
if problems:
return [
output_api.PresubmitPromptWarning(
'forward_variables_from("*") without exclusions',
items=sorted(problems),
long_text=(
'The variables "visibility" and "test_only" should be '
'explicitly listed in forward_variables_from(). For more '
'details, see:\n'
'https://chromium.googlesource.com/chromium/src/+/HEAD/'
'build/docs/writing_gn_templates.md'
'#Using-forward_variables_from'))
]
return []
def CheckNewHeaderWithoutGnChangeOnUpload(input_api, output_api):
"""Checks that newly added header files have corresponding GN changes.
Note that this is only a heuristic. To be precise, run script:
build/check_gn_headers.py.
"""
def headers(f):
return input_api.FilterSourceFile(
f, files_to_check=(r'.+%s' % _HEADER_EXTENSIONS, ))
new_headers = []
for f in input_api.AffectedSourceFiles(headers):
if f.Action() != 'A':
continue
new_headers.append(f.LocalPath())
def gn_files(f):
return input_api.FilterSourceFile(f, files_to_check=(r'.+\.gn', ))
all_gn_changed_contents = ''
for f in input_api.AffectedSourceFiles(gn_files):
for _, line in f.ChangedContents():
all_gn_changed_contents += line
problems = []
for header in new_headers:
basename = input_api.os_path.basename(header)
if basename not in all_gn_changed_contents:
problems.append(header)
if problems:
return [
output_api.PresubmitPromptWarning(
'Missing GN changes for new header files',
items=sorted(problems),
long_text=
'Please double check whether newly added header files need '
'corresponding changes in gn or gni files.\nThis checking is only a '
'heuristic. Run build/check_gn_headers.py to be precise.\n'
'Read https://crbug.com/661774 for more info.')
]
return []
def CheckCorrectProductNameInMessages(input_api, output_api):
"""Check that Chromium-branded strings don't include "Chrome" or vice versa.
This assumes we won't intentionally reference one product from the other
product.
"""
all_problems = []
test_cases = [{
'filename_postfix': 'google_chrome_strings.grd',
'correct_name': 'Chrome',
'incorrect_name': 'Chromium',
}, {
'filename_postfix': 'google_chrome_strings.grd',
'correct_name': 'Chrome',
'incorrect_name': 'Chrome for Testing',
}, {
'filename_postfix': 'chromium_strings.grd',
'correct_name': 'Chromium',
'incorrect_name': 'Chrome',
}]
for test_case in test_cases:
problems = []
filename_filter = lambda x: x.LocalPath().endswith(test_case[
'filename_postfix'])
for f in input_api.AffectedSourceFiles(filename_filter):
for line_num, line in f.ChangedContents():
if '<message' in line or '<!--' in line or '-->' in line:
continue
if test_case['incorrect_name'] in line:
if (test_case['correct_name'] == 'Chromium'
and line.count('Chrome')
== line.count('Chrome for Testing')):
continue
problems.append('Incorrect product name in %s:%d' %
(f.LocalPath(), line_num))
if problems:
message = (
'Strings in %s-branded string files should reference "%s", not "%s"'
% (test_case['correct_name'], test_case['correct_name'],
test_case['incorrect_name']))
all_problems.append(
output_api.PresubmitPromptWarning(message, items=problems))
return all_problems
def CheckForTooLargeFiles(input_api, output_api):
"""Avoid large files, especially binary files, in the repository since
git doesn't scale well for those. They will be in everyone's repo
clones forever, forever making Chromium slower to clone and work
with."""
TOO_LARGE_FILE_SIZE_LIMIT = 20 * 1024 * 1024
too_large_files = []
for f in input_api.AffectedFiles():
if f.Action() in ('A', 'M'):
size = input_api.os_path.getsize(f.AbsoluteLocalPath())
if size > TOO_LARGE_FILE_SIZE_LIMIT:
too_large_files.append('%s: %d bytes' % (f.LocalPath(), size))
if too_large_files:
message = (
'Do not commit large files to git since git scales badly for those.\n'
+
'Instead put the large files in cloud storage and use DEPS to\n' +
'fetch them.\n' + '\n'.join(too_large_files))
return [
output_api.PresubmitError('Too large files found in commit',
long_text=message + '\n')
]
else:
return []
def CheckFuzzTargetsOnUpload(input_api, output_api):
"""Checks specific for fuzz target sources."""
EXPORTED_SYMBOLS = [
'LLVMFuzzerInitialize',
'LLVMFuzzerCustomMutator',
'LLVMFuzzerCustomCrossOver',
'LLVMFuzzerMutate',
]
REQUIRED_HEADER = '#include "testing/libfuzzer/libfuzzer_exports.h"'
def FilterFile(affected_file):
"""Ignore libFuzzer source code."""
files_to_check = r'.*fuzz.*\.(h|hpp|hcc|cc|cpp|cxx)$'
files_to_skip = r'^third_party/libFuzzer'
return input_api.FilterSourceFile(affected_file,
files_to_check=[files_to_check],
files_to_skip=[files_to_skip])
files_with_missing_header = []
for f in input_api.AffectedSourceFiles(FilterFile):
contents = input_api.ReadFile(f, 'r')
if REQUIRED_HEADER in contents:
continue
if any(symbol in contents for symbol in EXPORTED_SYMBOLS):
files_with_missing_header.append(f.LocalPath())
if not files_with_missing_header:
return []
long_text = (
'If you define any of the libFuzzer optional functions (%s), it is '
'recommended to add \'%s\' directive. Otherwise, the fuzz target may '
'work incorrectly on Mac (crbug.com/687076).\nNote that '
'LLVMFuzzerInitialize should not be used, unless your fuzz target needs '
'to access command line arguments passed to the fuzzer. Instead, prefer '
'static initialization and shared resources as documented in '
'https://chromium.googlesource.com/chromium/src/+/main/testing/'
'libfuzzer/efficient_fuzzing.md#simplifying-initialization_cleanup.\n'
% (', '.join(EXPORTED_SYMBOLS), REQUIRED_HEADER))
return [
output_api.PresubmitPromptWarning(message="Missing '%s' in:" %
REQUIRED_HEADER,
items=files_with_missing_header,
long_text=long_text)
]
def _CheckNewImagesWarning(input_api, output_api):
"""
Warns authors who add images into the repo to make sure their images are
optimized before committing.
"""
images_added = False
image_paths = []
errors = []
filter_lambda = lambda x: input_api.FilterSourceFile(
x,
files_to_skip=(
('(?i).*test', r'.*\/junit\/') + input_api.DEFAULT_FILES_TO_SKIP),
files_to_check=[r'.*\/(drawable|mipmap)'])
for f in input_api.AffectedFiles(include_deletes=False,
file_filter=filter_lambda):
local_path = f.LocalPath().lower()
if any(
local_path.endswith(extension)
for extension in _IMAGE_EXTENSIONS):
images_added = True
image_paths.append(f)
if images_added:
errors.append(
output_api.PresubmitPromptWarning(
'It looks like you are trying to commit some images. If these are '
'non-test-only images, please make sure to read and apply the tips in '
'https://chromium.googlesource.com/chromium/src/+/HEAD/docs/speed/'
'binary_size/optimization_advice.md#optimizing-images\nThis check is '
'FYI only and will not block your CL on the CQ.', image_paths))
return errors
def ChecksAndroidSpecificOnUpload(input_api, output_api):
"""Groups upload checks that target android code."""
results = []
results.extend(_CheckAndroidCrLogUsage(input_api, output_api))
results.extend(_CheckAndroidDebuggableBuild(input_api, output_api))
results.extend(_CheckAndroidNewMdpiAssetLocation(input_api, output_api))
results.extend(_CheckAndroidToastUsage(input_api, output_api))
results.extend(_CheckAndroidTestAnnotationUsage(input_api, output_api))
results.extend(_CheckAndroidWebkitImports(input_api, output_api))
results.extend(_CheckAndroidXmlStyle(input_api, output_api, True))
results.extend(_CheckNewImagesWarning(input_api, output_api))
results.extend(_CheckAndroidInfoBarDeprecation(input_api, output_api))
results.extend(_CheckAndroidNullAwayAnnotatedClasses(
input_api, output_api))
return results
def ChecksAndroidSpecificOnCommit(input_api, output_api):
"""Groups commit checks that target android code."""
results = []
results.extend(_CheckAndroidXmlStyle(input_api, output_api, False))
results.extend(_CheckAndroidNullAwayAnnotatedClasses(
input_api, output_api))
return results
_ACCESSIBILITY_PATHS = (
r'^chrome/browser.*/accessibility/',
r'^chrome/browser/extensions/api/automation.*/',
r'^chrome/renderer/extensions/accessibility_.*',
r'^chrome/tests/data/accessibility/',
r'^content/browser/accessibility/',
r'^content/renderer/accessibility/',
r'^content/tests/data/accessibility/',
r'^extensions/renderer/api/automation/',
r'^services/accessibility/',
r'^services/screen_ai/',
r'^ui/accessibility/',
r'^ui/views/accessibility/',
)
def CheckAccessibilityRelnotesField(input_api, output_api):
"""Checks that commits to accessibility code contain an AX-Relnotes field in
their commit message."""
def FileFilter(affected_file):
paths = _ACCESSIBILITY_PATHS
return input_api.FilterSourceFile(affected_file, files_to_check=paths)
if not any(input_api.AffectedFiles(file_filter=FileFilter)):
return []
ax_regex = input_api.re.compile('ax-relnotes[:=]')
description_has_relnotes = any(
ax_regex.match(line)
for line in input_api.change.DescriptionText().lower().splitlines())
footer_relnotes = input_api.change.GitFootersFromDescription().get(
'AX-Relnotes', [])
if description_has_relnotes or footer_relnotes:
return []
message = (
"Missing 'AX-Relnotes:' field required for accessibility changes"
"\n please add 'AX-Relnotes: [release notes].' to describe any "
"user-facing changes"
"\n otherwise add 'AX-Relnotes: n/a.' if this change has no "
"user-facing effects"
"\n if this is confusing or annoying then please contact members "
"of ui/accessibility/OWNERS.")
return [output_api.PresubmitNotifyResult(message)]
_ACCESSIBILITY_ARIA_METHOD_CANDIDATES_PATTERNS = r'(\-\>|\.)(get|has|FastGet|FastHas)Attribute\('
_ACCESSIBILITY_ARIA_BAD_PARAMS_PATTERNS = (r'\(html_names::kAria(.*)Attr\)',
r'\(html_names::kRoleAttr\)')
_ACCESSIBILITY_ARIA_FILE_CANDIDATES_PATTERNS = (r'.*/accessibility/.*.(cc|h)',
r'.*/ax_.*.(cc|h)')
def CheckAccessibilityAriaElementAttributeGetters(input_api, output_api):
"""Checks that the blink accessibility code follows the defined patterns
for checking aria attributes, so that ElementInternals is not bypassed."""
def FileFilter(affected_file):
paths = _ACCESSIBILITY_ARIA_FILE_CANDIDATES_PATTERNS
return input_api.FilterSourceFile(affected_file, files_to_check=paths)
aria_method_regex = input_api.re.compile(
_ACCESSIBILITY_ARIA_METHOD_CANDIDATES_PATTERNS)
aria_bad_params_regex = input_api.re.compile(
'|'.join(_ACCESSIBILITY_ARIA_BAD_PARAMS_PATTERNS))
problems = []
for f in input_api.AffectedSourceFiles(FileFilter):
for line_num, line in f.ChangedContents():
if aria_method_regex.search(line) and aria_bad_params_regex.search(
line):
problems.append(
f'{f.LocalPath()}:{line_num}\n {line.strip()}')
if problems:
return [
output_api.PresubmitPromptWarning(
'Accessibility code should not use element methods to get or check'
'\nthe presence of aria attributes'
'\nPlease use ARIA-specific attribute access, e.g. HasAriaAttribute(),'
'\nAriaTokenAttribute(), AriaBoolAttribute(), AriaBooleanAttribute(),'
'\nAriaFloatAttribute().',
problems,
)
]
return []
_NON_INCLUSIVE_TERMS = (
(
r'/(?i)\b((black|white)list|master|slave)\b',
(
'Please don\'t use blacklist, whitelist, '
'or slave in your',
'code and make every effort to use other terms. Using "// nocheck"',
'"# nocheck" or "<!-- nocheck -->"',
'at the end of the offending line will bypass this PRESUBMIT error',
'but avoid using this whenever possible. Reach out to',
'community@chromium.org if you have questions'),
True), )
def ChecksCommon(input_api, output_api):
"""Checks common to both upload and commit."""
results = []
results.extend(
input_api.canned_checks.PanProjectChecks(
input_api, output_api, excluded_paths=_EXCLUDED_PATHS))
author = input_api.change.author_email
if author and author not in _KNOWN_ROBOTS:
results.extend(
input_api.canned_checks.CheckAuthorizedAuthor(
input_api, output_api))
results.extend(
input_api.canned_checks.CheckChangeHasNoTabs(
input_api,
output_api,
source_file_filter=lambda x: x.LocalPath().endswith('.grd')))
results.extend(
input_api.RunTests(
input_api.canned_checks.CheckVPythonSpec(input_api, output_api)))
dirmd = 'dirmd.bat' if input_api.is_windows else 'dirmd'
dirmd_bin = input_api.os_path.join(input_api.PresubmitLocalPath(),
'third_party', 'depot_tools', dirmd)
results.extend(
input_api.RunTests(
input_api.canned_checks.CheckDirMetadataFormat(
input_api, output_api, dirmd_bin)))
results.extend(
input_api.canned_checks.CheckOwnersDirMetadataExclusive(
input_api, output_api))
results.extend(
input_api.canned_checks.CheckNoNewMetadataInOwners(
input_api, output_api))
results.extend(
input_api.canned_checks.CheckInclusiveLanguage(
input_api,
output_api,
excluded_directories_relative_path=[
'infra', 'inclusive_language_presubmit_exempt_dirs.txt'
],
non_inclusive_terms=_NON_INCLUSIVE_TERMS))
results.extend(
input_api.canned_checks.CheckNewDEPSHooksHasRequiredReviewers(
input_api, output_api))
results.extend(
input_api.canned_checks.CheckValidHostsInDEPSOnUpload(
input_api, output_api))
presubmit_py_filter = lambda f: input_api.FilterSourceFile(
f, files_to_check=[r'.*PRESUBMIT(?:_test)?\.py$'])
potential_paths = set(
map(
lambda f: input_api.os_path.dirname(f.AbsoluteLocalPath()),
input_api.AffectedFiles(include_deletes=False,
file_filter=presubmit_py_filter)))
for full_path in potential_paths:
test_file = input_api.os_path.join(full_path, 'PRESUBMIT_test.py')
if not input_api.os_path.exists(test_file):
continue
results.extend(
input_api.canned_checks.RunUnitTestsInDirectory(
input_api,
output_api,
full_path,
files_to_check=[r'^PRESUBMIT_test\.py$']))
return results
def CheckPatchFiles(input_api, output_api):
problems = [
f.LocalPath() for f in input_api.AffectedFiles()
if f.LocalPath().endswith(('.orig', '.rej'))
]
problems = [f for f in problems if not f.endswith('Cargo.toml.orig')]
if problems:
return [
output_api.PresubmitError("Don't commit .rej and .orig files.",
problems)
]
else:
return []
def CheckBuildConfigMacrosWithoutInclude(input_api, output_api):
macro_re = input_api.re.compile(
r'^\s*#(el)?if.*\bdefined\(((COMPILER_|ARCH_CPU_|WCHAR_T_IS_)[^)]*)')
include_re = input_api.re.compile(r'^#include\s+"build/build_config.h"',
input_api.re.MULTILINE)
extension_re = input_api.re.compile(r'\.[a-z]+$')
errors = []
config_h_file = input_api.os_path.join('build', 'build_config.h')
for f in input_api.AffectedFiles(include_deletes=False):
if f.LocalPath() == config_h_file:
continue
if not f.LocalPath().endswith(
('.h', '.c', '.cc', '.cpp', '.m', '.mm')):
continue
found_line_number = None
found_macro = None
all_lines = input_api.ReadFile(f, 'r').splitlines()
for line_num, line in enumerate(all_lines):
match = macro_re.search(line)
if match:
found_line_number = line_num
found_macro = match.group(2)
break
if not found_line_number:
continue
found_include_line = -1
for line_num, line in enumerate(all_lines):
if include_re.search(line):
found_include_line = line_num
break
if found_include_line >= 0 and found_include_line < found_line_number:
continue
if not f.LocalPath().endswith('.h'):
primary_header_path = extension_re.sub('.h', f.AbsoluteLocalPath())
try:
content = input_api.ReadFile(primary_header_path, 'r')
if include_re.search(content):
continue
except IOError:
pass
errors.append('%s:%d %s macro is used without first including build/'
'build_config.h.' %
(f.LocalPath(), found_line_number, found_macro))
if errors:
return [output_api.PresubmitPromptWarning('\n'.join(errors))]
return []
def CheckForSuperfluousStlIncludesInHeaders(input_api, output_api):
stl_include_re = input_api.re.compile(r'^#include\s+<('
r'algorithm|'
r'array|'
r'limits|'
r'list|'
r'map|'
r'memory|'
r'queue|'
r'set|'
r'string|'
r'unordered_map|'
r'unordered_set|'
r'utility|'
r'vector)>')
std_namespace_re = input_api.re.compile(r'std::')
errors = []
for f in input_api.AffectedFiles():
if not _IsCPlusPlusHeaderFile(input_api, f.LocalPath()):
continue
uses_std_namespace = False
has_stl_include = False
for line in f.NewContents():
if has_stl_include and uses_std_namespace:
break
if not has_stl_include and stl_include_re.search(line):
has_stl_include = True
continue
if not uses_std_namespace and (std_namespace_re.search(line)
or 'no-std-usage-because-pch-file'
in line):
uses_std_namespace = True
continue
if has_stl_include and not uses_std_namespace:
errors.append(
'%s: Includes STL header(s) but does not reference std::' %
f.LocalPath())
if errors:
return [output_api.PresubmitPromptWarning('\n'.join(errors))]
return []
def _CheckForDeprecatedOSMacrosInFile(input_api, f):
"""Check for sensible looking, totally invalid OS macros."""
preprocessor_statement = input_api.re.compile(r'^\s*#')
os_macro = input_api.re.compile(r'defined\(OS_([^)]+)\)')
results = []
for lnum, line in f.ChangedContents():
if preprocessor_statement.search(line):
for match in os_macro.finditer(line):
results.append(
' %s:%d: %s' %
(f.LocalPath(), lnum, 'defined(OS_' + match.group(1) +
') -> BUILDFLAG(IS_' + match.group(1) + ')'))
return results
def CheckForDeprecatedOSMacros(input_api, output_api):
"""Check all affected files for invalid OS macros."""
bad_macros = []
config_h_file = input_api.os_path.join('build', 'build_config.h')
for f in input_api.AffectedSourceFiles(None):
if (not f.LocalPath().endswith(('.py', '.js', '.html', '.css', '.md'))
and f.LocalPath() != config_h_file):
bad_macros.extend(_CheckForDeprecatedOSMacrosInFile(input_api, f))
if not bad_macros:
return []
return [
output_api.PresubmitError(
'OS macros have been deprecated. Please use BUILDFLAGs instead (still '
'defined in build_config.h):', bad_macros)
]
def _CheckForInvalidIfDefinedMacrosInFile(input_api, f):
"""Check all affected files for invalid "if defined" macros."""
ALWAYS_DEFINED_MACROS = (
'TARGET_CPU_PPC',
'TARGET_CPU_PPC64',
'TARGET_CPU_68K',
'TARGET_CPU_X86',
'TARGET_CPU_ARM',
'TARGET_CPU_MIPS',
'TARGET_CPU_SPARC',
'TARGET_CPU_ALPHA',
'TARGET_IPHONE_SIMULATOR',
'TARGET_OS_EMBEDDED',
'TARGET_OS_IPHONE',
'TARGET_OS_MAC',
'TARGET_OS_UNIX',
'TARGET_OS_WIN32',
)
ifdef_macro = input_api.re.compile(
r'^\s*#.*(?:ifdef\s|defined\()([^\s\)]+)')
results = []
for lnum, line in f.ChangedContents():
for match in ifdef_macro.finditer(line):
if match.group(1) in ALWAYS_DEFINED_MACROS:
always_defined = ' %s is always defined. ' % match.group(1)
did_you_mean = 'Did you mean \'#if %s\'?' % match.group(1)
results.append(
' %s:%d %s\n\t%s' %
(f.LocalPath(), lnum, always_defined, did_you_mean))
return results
def CheckForInvalidIfDefinedMacros(input_api, output_api):
"""Check all affected files for invalid "if defined" macros."""
SKIPPED_PATHS = [
'base/allocator/partition_allocator/src/partition_alloc/build_config.h',
'build/build_config.h',
'third_party/abseil-cpp/',
'third_party/sqlite/',
]
def affected_files_filter(f):
path = f.UnixLocalPath()
for skipped_path in SKIPPED_PATHS:
if path.startswith(skipped_path):
return False
return path.endswith(('.h', '.c', '.cc', '.m', '.mm'))
bad_macros = []
for f in input_api.AffectedSourceFiles(affected_files_filter):
bad_macros.extend(_CheckForInvalidIfDefinedMacrosInFile(input_api, f))
if not bad_macros:
return []
return [
output_api.PresubmitError(
'Found ifdef check on always-defined macro[s]. Please fix your code\n'
'or check the list of ALWAYS_DEFINED_MACROS in src/PRESUBMIT.py.',
bad_macros)
]
def CheckForIPCRules(input_api, output_api):
"""Check for same IPC rules described in
http://www.chromium.org/Home/chromium-security/education/security-tips-for-ipc
"""
base_pattern = r'IPC_ENUM_TRAITS\('
inclusion_pattern = input_api.re.compile(r'(%s)' % base_pattern)
comment_pattern = input_api.re.compile(r'//.*(%s)' % base_pattern)
problems = []
for f in input_api.AffectedSourceFiles(None):
local_path = f.LocalPath()
if not local_path.endswith('.h'):
continue
for line_number, line in f.ChangedContents():
if inclusion_pattern.search(
line) and not comment_pattern.search(line):
problems.append('%s:%d\n %s' %
(local_path, line_number, line.strip()))
if problems:
return [
output_api.PresubmitPromptWarning(_IPC_ENUM_TRAITS_DEPRECATED,
problems)
]
else:
return []
def CheckForLongPathnames(input_api, output_api):
"""Check to make sure no files being submitted have long paths.
This causes issues on Windows.
"""
problems = []
for f in input_api.AffectedTestableFiles():
local_path = f.LocalPath()
if (local_path.startswith('third_party/blink/web_tests/platform/')
and not local_path.startswith(
'third_party/blink/web_tests/platform/win')):
continue
if len(local_path) > 200:
problems.append(local_path)
if problems:
return [output_api.PresubmitError(_LONG_PATH_ERROR, problems)]
else:
return []
def CheckForIncludeGuards(input_api, output_api):
"""Check that header files have proper guards against multiple inclusion.
If a file should not have such guards (and it probably should) then it
should include the string "no-include-guard-because-multiply-included" or
"no-include-guard-because-pch-file".
"""
def is_chromium_header_file(f):
file_with_path = input_api.os_path.normpath(f.LocalPath())
return (file_with_path.endswith('.h')
and not file_with_path.endswith('_message_generator.h')
and not file_with_path.endswith('com_imported_mstscax.h')
and not file_with_path.startswith(
input_api.os_path.join('base', 'allocator',
'partition_allocator'))
and (not file_with_path.startswith('third_party')
or file_with_path.startswith(
input_api.os_path.join('third_party', 'blink'))))
def replace_special_with_underscore(string):
return input_api.re.sub(r'[+\\/.-]', '_', string)
errors = []
for f in input_api.AffectedSourceFiles(is_chromium_header_file):
guard_name = None
guard_line_number = None
seen_guard_end = False
bypass_checks_at_end_of_file = False
file_with_path = input_api.os_path.normpath(f.LocalPath())
base_file_name = input_api.os_path.splitext(
input_api.os_path.basename(file_with_path))[0]
upper_base_file_name = base_file_name.upper()
expected_guard = replace_special_with_underscore(
file_with_path.upper() + '_')
guard_name_pattern_list = [
r'\w+_H__?',
r'\w+_h',
r'\w*' + input_api.re.escape(
replace_special_with_underscore(upper_base_file_name)) +
r'\w*',
]
guard_name_pattern = '|'.join(guard_name_pattern_list)
guard_pattern = input_api.re.compile(r'#ifndef\s+(' +
guard_name_pattern + ')')
for line_number, line in enumerate(f.NewContents()):
if ('no-include-guard-because-multiply-included' in line
or 'no-include-guard-because-pch-file' in line):
bypass_checks_at_end_of_file = True
break
if guard_name is None:
match = guard_pattern.match(line)
if match:
guard_name = match.group(1)
guard_line_number = line_number
if guard_name != expected_guard:
if f.Action() == 'A':
errors.append(
output_api.PresubmitPromptWarning(
'Header using the wrong include guard name %s'
% guard_name, [
'%s:%d' %
(f.LocalPath(), line_number + 1)
], 'Expected: %r\nFound: %r' %
(expected_guard, guard_name)))
else:
if line_number == guard_line_number + 1:
expected_line = '#define %s' % guard_name
if line != expected_line:
errors.append(
output_api.PresubmitPromptWarning(
'Missing "%s" for include guard' %
expected_line,
['%s:%d' % (f.LocalPath(), line_number + 1)],
'Expected: %r\nGot: %r' %
(expected_line, line)))
if not seen_guard_end and line == '#endif // %s' % guard_name:
seen_guard_end = True
elif seen_guard_end:
if line.strip():
errors.append(
output_api.PresubmitPromptWarning(
'Include guard %s not covering the whole file'
% (guard_name), [f.LocalPath()]))
break
if bypass_checks_at_end_of_file:
continue
if guard_name is None:
errors.append(
output_api.PresubmitPromptWarning(
'Missing include guard in %s\n'
'Recommended name: %s\n'
'This check can be disabled by having the string\n'
'"no-include-guard-because-multiply-included" or\n'
'"no-include-guard-because-pch-file" in the header.' %
(f.LocalPath(), expected_guard)))
elif not seen_guard_end:
errors.append(
output_api.PresubmitPromptWarning(
'Incorrect or missing include guard #endif in %s\n'
'Recommended #endif comment: // %s' %
(f.LocalPath(), expected_guard)))
return errors
def CheckForWindowsLineEndings(input_api, output_api):
"""Check source code and known ascii text files for Windows style line
endings.
"""
known_text_files = r'.*\.(txt|html|htm|py|gyp|gypi|gn|isolate|icon)$'
_WEBUI_FILES_EXTENSIONS = r'\.(css|html|js|ts|svg)$'
file_inclusion_pattern = (known_text_files,
r'.+%s' % _IMPLEMENTATION_EXTENSIONS,
r'.+%s' % _HEADER_EXTENSIONS,
r'.+%s' % _WEBUI_FILES_EXTENSIONS)
file_exclusion_pattern = (r'media/test/data/')
problems = []
source_file_filter = lambda f: input_api.FilterSourceFile(
f,
files_to_check=file_inclusion_pattern,
files_to_skip=file_exclusion_pattern)
for f in input_api.AffectedSourceFiles(source_file_filter):
if f.LocalPath().endswith('crlf.txt'):
continue
include_file = False
for line in input_api.ReadFile(f, 'r').splitlines(True):
if line.endswith('\r\n'):
include_file = True
if include_file:
problems.append(f.LocalPath())
if problems:
return [
output_api.PresubmitPromptWarning(
'Are you sure that you want '
'these files to contain Windows style line endings?\n' +
'\n'.join(problems))
]
return []
def CheckIconFilesForLicenseHeaders(input_api, output_api):
"""Check that .icon files (which are fragments of C++) have license headers.
"""
icon_files = (r'.*\.icon$', )
icons = lambda x: input_api.FilterSourceFile(x, files_to_check=icon_files)
return input_api.canned_checks.CheckLicense(input_api,
output_api,
source_file_filter=icons)
def CheckForUseOfChromeAppsDeprecations(input_api, output_api):
"""Check source code for use of Chrome App technologies being
deprecated.
"""
def _CheckForDeprecatedTech(input_api,
detection_list,
files_to_check=None,
files_to_skip=None):
if (files_to_check or files_to_skip):
source_file_filter = lambda f: input_api.FilterSourceFile(
f, files_to_check=files_to_check, files_to_skip=files_to_skip)
else:
source_file_filter = None
problems = []
for f in input_api.AffectedSourceFiles(source_file_filter):
if f.Action() == 'D':
continue
for _, line in f.ChangedContents():
if any(detect in line for detect in detection_list):
problems.append(f.LocalPath())
return problems
problems = []
_NMF_FILES = r'\.(nmf|NMF)$'
problems += _CheckForDeprecatedTech(
input_api,
detection_list=[''],
files_to_check=[r'.+%s' % _NMF_FILES])
_MANIFEST_FILES = r'(manifest\.json)$'
problems += _CheckForDeprecatedTech(
input_api,
detection_list=['"app":'],
files_to_check=[r'.*%s' % _MANIFEST_FILES])
if problems:
return [
output_api.PresubmitPromptWarning(
'You are adding/modifying code'
'related to technologies which will soon be deprecated (Chrome Apps'
'). See this blog post for more details:\n'
'https://blog.chromium.org/2020/08/changes-to-chrome-app-support-timeline.html\n'
'and this documentation for options to replace these technologies:\n'
'https://developer.chrome.com/docs/apps/migration/\n' +
'\n'.join(problems))
]
return []
def CheckSyslogUseWarningOnUpload(input_api, output_api, src_file_filter=None):
"""Checks that all source files use SYSLOG properly."""
syslog_files = []
for f in input_api.AffectedSourceFiles(src_file_filter):
for line_number, line in f.ChangedContents():
if 'SYSLOG' in line:
syslog_files.append(f.LocalPath() + ':' + str(line_number))
if syslog_files:
return [
output_api.PresubmitPromptWarning(
'Please make sure there are no privacy sensitive bits of data in SYSLOG'
' calls.\nFiles to check:\n',
items=syslog_files)
]
return []
def CheckChangeOnUpload(input_api, output_api):
if input_api.version < [2, 0, 0]:
return [
output_api.PresubmitError(
'Your depot_tools is out of date. '
'This PRESUBMIT.py requires at least presubmit_support version 2.0.0, '
'but your version is %d.%d.%d' % tuple(input_api.version))
]
results = []
results.extend(
input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
return results
def CheckChangeOnCommit(input_api, output_api):
if input_api.version < [2, 0, 0]:
return [
output_api.PresubmitError(
'Your depot_tools is out of date. '
'This PRESUBMIT.py requires at least presubmit_support version 2.0.0, '
'but your version is %d.%d.%d' % tuple(input_api.version))
]
results = []
results.extend(
input_api.canned_checks.CheckTreeIsOpen(
input_api,
output_api,
json_url='http://chromium-status.appspot.com/current?format=json'))
results.extend(
input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
results.extend(
input_api.canned_checks.CheckChangeHasBugField(input_api, output_api))
results.extend(
input_api.canned_checks.CheckChangeHasNoUnwantedTags(
input_api, output_api))
return results
def CheckStrings(input_api, output_api):
"""Check string ICU syntax validity and if translation screenshots exist."""
git_footers = input_api.change.GitFootersFromDescription()
skip_screenshot_check_footer = [
footer.lower() for footer in git_footers.get(
u'Skip-Translation-Screenshots-Check', [])
]
run_screenshot_check = u'true' not in skip_screenshot_check_footer
import io
import re
import sys
new_or_added_paths = set(f.LocalPath() for f in input_api.AffectedFiles()
if (f.Action() == 'A' or f.Action() == 'M'))
removed_paths = set(f.LocalPath()
for f in input_api.AffectedFiles(include_deletes=True)
if f.Action() == 'D')
affected_grds = [
f for f in input_api.AffectedFiles()
if f.LocalPath().endswith(('.grd', '.grdp'))
]
affected_grds = [
f for f in affected_grds if not 'testdata' in f.LocalPath()
]
if not affected_grds:
return []
affected_png_paths = [
f.LocalPath() for f in input_api.AffectedFiles()
if f.LocalPath().endswith('.png')
]
unnecessary_screenshots = []
invalid_sha1 = []
missing_sha1 = []
missing_sha1_modified = []
unnecessary_sha1_files = []
icu_syntax_errors = []
sha1_pattern = input_api.re.compile(r'^[a-fA-F0-9]{40}$',
input_api.re.MULTILINE)
def _CheckScreenshotAdded(screenshots_dir, message_id):
sha1_path = input_api.os_path.join(screenshots_dir,
message_id + '.png.sha1')
if sha1_path not in new_or_added_paths:
missing_sha1.append(sha1_path)
elif not _CheckValidSha1(sha1_path):
invalid_sha1.append(sha1_path)
def _CheckScreenshotModified(screenshots_dir, message_id):
sha1_path = input_api.os_path.join(screenshots_dir,
message_id + '.png.sha1')
if sha1_path not in new_or_added_paths:
missing_sha1_modified.append(sha1_path)
elif not _CheckValidSha1(sha1_path):
invalid_sha1.append(sha1_path)
def _CheckValidSha1(sha1_path):
return sha1_pattern.search(
next('\n'.join(f.NewContents()) for f in input_api.AffectedFiles()
if f.LocalPath() == sha1_path))
def _CheckScreenshotRemoved(screenshots_dir, message_id):
sha1_path = input_api.os_path.join(screenshots_dir,
message_id + '.png.sha1')
if input_api.os_path.exists(
sha1_path) and sha1_path not in removed_paths:
unnecessary_sha1_files.append(sha1_path)
def _ValidateIcuSyntax(text, level, signatures):
"""Validates ICU syntax of a text string.
Check if text looks similar to ICU and checks for ICU syntax correctness
in this case. Reports various issues with ICU syntax and values of
variants. Supports checking of nested messages. Accumulate information of
each ICU messages found in the text for further checking.
Args:
text: a string to check.
level: a number of current nesting level.
signatures: an accumulator, a list of tuple of (level, variable,
kind, variants).
Returns:
None if a string is not ICU or no issue detected.
A tuple of (message, start index, end index) if an issue detected.
"""
valid_types = {
'plural': (frozenset([
'=0', '=1', '=2', '=3', 'zero', 'one', 'two', 'few', 'many',
'other'
]), frozenset(['=1', 'other'])),
'selectordinal': (frozenset([
'=0', '=1', '=2', '=3', 'zero', 'one', 'two', 'few', 'many',
'other'
]), frozenset(['one', 'other'])),
'select': (frozenset(), frozenset(['other'])),
}
like = re.match(r'^[^{]*\{[^{]*\b(plural|selectordinal|select)\b',
text)
if not like:
signatures.append((level, None, None, None))
return
m = re.match(
r'^([^{]*\{)([a-zA-Z0-9_]+),\s*'
r'(plural|selectordinal|select),\s*'
r'(?:offset:\d+)?\s*(.*)', text, re.DOTALL)
if not m:
return (('This message looks like an ICU plural, '
'but does not follow ICU syntax.'), like.start(),
like.end())
starting, variable, kind, variant_pairs = m.groups()
variants, depth, last_pos = _ParseIcuVariants(variant_pairs,
m.start(4))
if depth:
return ('Invalid ICU format. Unbalanced opening bracket', last_pos,
len(text))
first = text[0]
ending = text[last_pos:]
if not starting:
return ('Invalid ICU format. No initial opening bracket',
last_pos - 1, last_pos)
if not ending or '}' not in ending:
return ('Invalid ICU format. No final closing bracket',
last_pos - 1, last_pos)
elif first != '{':
return ((
'Invalid ICU format. Extra characters at the start of a complex '
'message (go/icu-message-migration): "%s"') % starting, 0,
len(starting))
elif ending != '}':
return ((
'Invalid ICU format. Extra characters at the end of a complex '
'message (go/icu-message-migration): "%s"') % ending,
last_pos - 1, len(text) - 1)
if kind not in valid_types:
return (('Unknown ICU message type %s. '
'Valid types are: plural, select, selectordinal') % kind,
0, 0)
known, required = valid_types[kind]
defined_variants = set()
for variant, variant_range, value, value_range in variants:
start, end = variant_range
if variant in defined_variants:
return ('Variant "%s" is defined more than once' % variant,
start, end)
elif known and variant not in known:
return ('Variant "%s" is not valid for %s message' %
(variant, kind), start, end)
defined_variants.add(variant)
res = _ValidateIcuSyntax(value[1:-1], level + 1, signatures)
if res:
return (res[0], res[1] + value_range[0] + 1,
res[2] + value_range[0] + 1)
missing = required - defined_variants
if missing:
return ('Required variants missing: %s' % ', '.join(missing), 0,
len(text))
signatures.append((level, variable, kind, defined_variants))
def _ParseIcuVariants(text, offset=0):
"""Parse variants part of ICU complex message.
Builds a tuple of variant names and values, as well as
their offsets in the input string.
Args:
text: a string to parse
offset: additional offset to add to positions in the text to get correct
position in the complete ICU string.
Returns:
List of tuples, each tuple consist of four fields: variant name,
variant name span (tuple of two integers), variant value, value
span (tuple of two integers).
"""
depth, start, end = 0, -1, -1
variants = []
key = None
span = None
for idx, char in enumerate(text):
if char == '{':
if not depth:
start = idx
chunk = text[end + 1:start]
key = chunk.strip()
pos = offset + end + 1 + chunk.find(key)
span = (pos, pos + len(key))
depth += 1
elif char == '}':
if not depth:
return variants, depth, offset + idx
depth -= 1
if not depth:
end = idx
variants.append((key, span, text[start:end + 1],
(offset + start, offset + end + 1)))
return variants, depth, offset + end + 1
old_sys_path = sys.path
try:
sys.path = sys.path + [
input_api.os_path.join(input_api.PresubmitLocalPath(), 'tools',
'translation')
]
from helper import grd_helper
finally:
sys.path = old_sys_path
for f in affected_grds:
file_path = f.LocalPath()
old_id_to_msg_map = {}
new_id_to_msg_map = {}
if file_path.endswith('.grdp'):
if f.OldContents():
old_id_to_msg_map = grd_helper.GetGrdpMessagesFromString(
'\n'.join(f.OldContents()))
if f.NewContents():
new_id_to_msg_map = grd_helper.GetGrdpMessagesFromString(
'\n'.join(f.NewContents()))
else:
file_dir = input_api.os_path.dirname(file_path) or '.'
if f.OldContents():
old_id_to_msg_map = grd_helper.GetGrdMessages(
io.StringIO('\n'.join(f.OldContents())), file_dir)
if f.NewContents():
new_id_to_msg_map = grd_helper.GetGrdMessages(
io.StringIO('\n'.join(f.NewContents())), file_dir)
grd_name, ext = input_api.os_path.splitext(
input_api.os_path.basename(file_path))
screenshots_dir = input_api.os_path.join(
input_api.os_path.dirname(file_path),
grd_name + ext.replace('.', '_'))
old_ids = set(old_id_to_msg_map)
new_ids = set(new_id_to_msg_map)
added_ids = new_ids - old_ids
removed_ids = old_ids - new_ids
modified_ids = set([])
for key in old_ids.intersection(new_ids):
if (old_id_to_msg_map[key].ContentsAsXml('', True)
!= new_id_to_msg_map[key].ContentsAsXml('', True)):
modified_ids.add(key)
elif (old_id_to_msg_map[key].attrs['meaning']
!= new_id_to_msg_map[key].attrs['meaning']):
modified_ids.add(key)
if run_screenshot_check:
for png_path in affected_png_paths:
if png_path.startswith(screenshots_dir):
unnecessary_screenshots.append(png_path)
for added_id in added_ids:
_CheckScreenshotAdded(screenshots_dir, added_id)
for modified_id in modified_ids:
_CheckScreenshotModified(screenshots_dir, modified_id)
for removed_id in removed_ids:
_CheckScreenshotRemoved(screenshots_dir, removed_id)
for key in added_ids.union(modified_ids):
msg = new_id_to_msg_map[key].ContentsAsXml('', True)
err = _ValidateIcuSyntax(msg, 0, [])
if err is not None:
icu_syntax_errors.append(str(key) + ': ' + str(err[0]))
results = []
if run_screenshot_check:
if unnecessary_screenshots:
results.append(
output_api.PresubmitError(
'Do not include actual screenshots in the changelist. Run '
'tools/translation/upload_screenshots.py to upload them instead:',
sorted(unnecessary_screenshots)))
if missing_sha1:
results.append(
output_api.PresubmitError(
'You are adding UI strings.\n'
'To ensure the best translations, take screenshots of the relevant UI '
'(https://g.co/chrome/translation) and add these files to your '
'changelist:', sorted(missing_sha1)))
if invalid_sha1:
results.append(
output_api.PresubmitError(
'The following files do not seem to contain valid sha1 hashes. '
'Make sure they contain hashes created by '
'tools/translation/upload_screenshots.py:',
sorted(invalid_sha1)))
if missing_sha1_modified:
results.append(
output_api.PresubmitError(
'You are modifying UI strings or their meanings.\n'
'To ensure the best translations, take screenshots of the relevant UI '
'(https://g.co/chrome/translation) and add these files to your '
'changelist:', sorted(missing_sha1_modified)))
if unnecessary_sha1_files:
results.append(
output_api.PresubmitError(
'You removed strings associated with these files. Remove:',
sorted(unnecessary_sha1_files)))
else:
results.append(
output_api.PresubmitPromptOrNotify('Skipping translation '
'screenshots check.'))
if icu_syntax_errors:
results.append(
output_api.PresubmitPromptWarning(
'ICU syntax errors were found in the following strings (problems or '
'feedback? Contact rainhard@chromium.org):',
items=icu_syntax_errors))
return results
def CheckTranslationExpectations(input_api,
output_api,
repo_root=None,
translation_expectations_path=None,
grd_files=None):
import sys
affected_grds = [
f for f in input_api.AffectedFiles()
if (f.LocalPath().endswith('.grd') or f.LocalPath().endswith('.grdp'))
]
if not affected_grds:
return []
old_sys_path = sys.path
try:
sys.path = sys.path + [
input_api.os_path.join(input_api.PresubmitLocalPath(), 'tools',
'translation')
]
sys.path = sys.path + [
input_api.os_path.join(input_api.PresubmitLocalPath(),
'third_party', 'depot_tools')
]
from helper import git_helper
from helper import translation_helper
import gclient_utils
finally:
sys.path = old_sys_path
if not repo_root:
repo_root = input_api.PresubmitLocalPath()
if not translation_expectations_path:
translation_expectations_path = input_api.os_path.join(
repo_root, 'tools', 'gritsettings', 'translation_expectations.pyl')
is_cog = gclient_utils.IsEnvCog()
if not grd_files and not is_cog:
grd_files = git_helper.list_grds_in_repository(repo_root)
if not grd_files:
grd_files = []
ignore_path = input_api.os_path.join('ui', 'webui', 'resources', 'tools',
'tests')
grd_files = [p for p in grd_files if ignore_path not in p]
basename_to_src_paths = {}
for grd_path in grd_files:
basename = input_api.os_path.basename(grd_path)
basename_to_src_paths.setdefault(basename, [])
basename_to_src_paths[basename].append(grd_path)
for src_paths in basename_to_src_paths.values():
if len(src_paths) > 1:
return [
output_api.PresubmitNotifyResult(
'Multiple string files have the same basename. This will result in '
'missing translations. Files: %s' % ', '.join(src_paths))
]
try:
translation_helper.get_translatable_grds(
repo_root, grd_files, translation_expectations_path, is_cog)
except Exception as e:
return [
output_api.PresubmitNotifyResult(
'Failed to get a list of translatable grd files. This happens when:\n'
' - One of the modified grd or grdp files cannot be parsed or\n'
' - %s is not updated.\n'
'Stack:\n%s' % (translation_expectations_path, str(e)))
]
return []
def CheckStableMojomChanges(input_api, output_api):
"""Changes to [Stable] mojom types must preserve backward-compatibility."""
no_stable_mojom_checks = input_api.change.GitFootersFromDescription().get(
'No-Stable-Mojom-Checks', None)
expect_stable_mojom_failures = False
if no_stable_mojom_checks:
if no_stable_mojom_checks == ['true']:
expect_stable_mojom_failures = True
else:
return [
output_api.PresubmitError(
f'If present, No-Stable-Mojom-Checks only accepts the value '
f'"true", but got "{no_stable_mojom_checks}" instead.')
]
def CheckMojomsIfNeeded():
changed_mojoms = input_api.AffectedFiles(
include_deletes=True,
file_filter=lambda f: f.LocalPath().endswith(('.mojom')))
if not changed_mojoms or input_api.no_diffs:
return []
delta = []
for mojom in changed_mojoms:
delta.append({
'filename': mojom.LocalPath(),
'old': '\n'.join(mojom.OldContents()) or None,
'new': '\n'.join(mojom.NewContents()) or None,
})
process = input_api.subprocess.Popen([
input_api.python3_executable,
input_api.os_path.join(
input_api.PresubmitLocalPath(), 'mojo', 'public', 'tools',
'mojom', 'check_stable_mojom_compatibility.py'), '--src-root',
input_api.PresubmitLocalPath()
],
stdin=input_api.subprocess.PIPE,
stdout=input_api.subprocess.PIPE,
stderr=input_api.subprocess.PIPE,
universal_newlines=True)
(_, error) = process.communicate(input=input_api.json.dumps(delta))
if process.returncode:
return [
output_api.PresubmitError(
'One or more [Stable] mojom definitions changed in a way '
'that breaks backward compatibility. See '
'https://chromium.googlesource.com/chromium/src/+/HEAD/mojo/public/tools/bindings/README.md#versioning'
' for details.\n\n'
'If you are confident this is a false positive, add '
'`No-Stable-Mojom-Checks: true` to the git footers to suppress '
'this check.',
long_text=error)
]
return []
results = CheckMojomsIfNeeded()
if bool(results) != expect_stable_mojom_failures:
if expect_stable_mojom_failures:
return [
output_api.PresubmitError(
'No [Stable] mojom definitions changed in a way breaks '
'backward compatibility.\n\n'
'Please remove the unnecessary git footer '
'`No-Stable-Mojom-Checks: true`.')
]
else:
return results
return []
def CheckDeprecationOfPreferences(input_api, output_api):
"""Removing a preference should come with a deprecation."""
def FilterFile(affected_file):
"""Accept only .cc files and the like."""
file_inclusion_pattern = [r'.+%s' % _IMPLEMENTATION_EXTENSIONS]
files_to_skip = (_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
input_api.DEFAULT_FILES_TO_SKIP)
return input_api.FilterSourceFile(
affected_file,
files_to_check=file_inclusion_pattern,
files_to_skip=files_to_skip)
def ModifiedLines(affected_file):
"""Returns a list of tuples (line number, line text) of added and removed
lines.
Deleted lines share the same line number as the previous line.
This relies on the scm diff output describing each changed code section
with a line of the form
^@@ <old line num>,<old size> <new line num>,<new size> @@$
"""
line_num = 0
modified_lines = []
for line in affected_file.GenerateScmDiff().splitlines():
m = input_api.re.match(r'^@@ [0-9\,\+\-]+ \+([0-9]+)\,[0-9]+ @@',
line)
if m:
line_num = int(m.groups(1)[0])
continue
if ((line.startswith('+') and not line.startswith('++'))
or (line.startswith('-') and not line.startswith('--'))):
modified_lines.append((line_num, line))
if not line.startswith('-'):
line_num += 1
return modified_lines
def FindLineWith(lines, needle):
"""Returns the line number (i.e. index + 1) in `lines` containing `needle`.
If 0 or >1 lines contain `needle`, -1 is returned.
"""
matching_line_numbers = [
i + 1 for i, line in enumerate(lines) if needle in line
]
return matching_line_numbers[0] if len(
matching_line_numbers) == 1 else -1
def ModifiedPrefMigration(affected_file):
"""Returns whether the MigrateObsolete.*Pref functions were modified."""
new_contents = affected_file.NewContents()
range_1 = (FindLineWith(new_contents,
'BEGIN_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS'),
FindLineWith(new_contents,
'END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS'))
range_2 = (FindLineWith(new_contents,
'BEGIN_MIGRATE_OBSOLETE_PROFILE_PREFS'),
FindLineWith(new_contents,
'END_MIGRATE_OBSOLETE_PROFILE_PREFS'))
if (-1 in range_1 + range_2):
raise Exception(
'Broken .*MIGRATE_OBSOLETE_.*_PREFS markers in browser_prefs.cc.'
)
for line_nr, _ in ModifiedLines(affected_file):
if (range_1[0] <= line_nr <= range_1[1]
or range_2[0] <= line_nr <= range_2[1]):
return True
return False
register_pref_pattern = input_api.re.compile(r'Register.+Pref')
browser_prefs_file_pattern = input_api.re.compile(
r'chrome/browser/prefs/browser_prefs.cc')
changes = input_api.AffectedFiles(include_deletes=True,
file_filter=FilterFile)
potential_problems = []
for f in changes:
for line in f.GenerateScmDiff().splitlines():
if (line.startswith('-') and not line.startswith('--')
and register_pref_pattern.search(line)):
potential_problems.append('%s: %s' % (f.LocalPath(), line))
if browser_prefs_file_pattern.search(f.LocalPath()):
try:
if ModifiedPrefMigration(f):
return []
except Exception as e:
return [output_api.PresubmitError(str(e))]
if potential_problems:
return [
output_api.PresubmitPromptWarning(
'Discovered possible removal of preference registrations.\n\n'
'Please make sure to properly deprecate preferences by clearing their\n'
'value for a couple of milestones before finally removing the code.\n'
'Otherwise data may stay in the preferences files forever. See\n'
'Migrate*Prefs() in chrome/browser/prefs/browser_prefs.cc and\n'
'chrome/browser/prefs/README.md for examples.\n'
'This may be a false positive warning (e.g. if you move preference\n'
'registrations to a different place or if the preference was only\n'
'written to by the policy stack).\n', potential_problems)
]
return []
def CheckConsistentGrdChanges(input_api, output_api):
"""Changes to GRD files must be consistent for tools to read them."""
changed_grds = input_api.AffectedFiles(
include_deletes=False,
file_filter=lambda f: f.LocalPath().endswith(('.grd')))
errors = []
invalid_file_regexes = [(input_api.re.compile(matcher), msg)
for matcher, msg in _INVALID_GRD_FILE_LINE]
for grd in changed_grds:
for i, line in enumerate(grd.NewContents()):
for matcher, msg in invalid_file_regexes:
if matcher.search(line):
errors.append(
output_api.PresubmitError(
'Problem on {grd}:{i} - {msg}'.format(
grd=grd.LocalPath(), i=i + 1, msg=msg)))
return errors
def CheckAssertAshOnlyCode(input_api, output_api):
"""Errors if a BUILD.gn file in an ash/ directory doesn't include
assert(is_chromeos).
For a transition period, assert(is_chromeos_ash) is also accepted.
"""
def FileFilter(affected_file):
"""Includes directories known to be Ash only."""
return input_api.FilterSourceFile(
affected_file,
files_to_check=(
r'^ash/.*BUILD\.gn',
r'.*/ash/.*BUILD\.gn'),
files_to_skip=(input_api.DEFAULT_FILES_TO_SKIP))
errors = []
pattern = input_api.re.compile(r'assert\(is_chromeos(_ash)?\b')
for f in input_api.AffectedFiles(include_deletes=False,
file_filter=FileFilter):
if (not pattern.search(input_api.ReadFile(f))):
errors.append(
output_api.PresubmitError(
'Please add assert(is_chromeos) to %s. If that\'s not '
'possible, please create an issue and add a comment such '
'as:\n # TODO(crbug.com/XXX): add '
'assert(is_chromeos) when ...' % f.LocalPath()))
return errors
def _IsMiraclePtrDisallowed(input_api, affected_file):
path = affected_file.UnixLocalPath()
if not _IsCPlusPlusFile(input_api, path):
return False
if ('third_party/blink/renderer/core/' in path
or 'third_party/blink/renderer/platform/heap/' in path
or 'third_party/blink/renderer/platform/wtf/' in path
or 'third_party/blink/renderer/platform/fonts/' in path):
return True
return False
def CheckRawPtrUsage(input_api, output_api):
"""Rough checks that raw_ptr<T> usage guidelines are followed."""
errors = []
raw_ptr_matcher = input_api.re.compile(r'^((?!//).)*\braw_ptr<')
file_filter = lambda f: _IsMiraclePtrDisallowed(input_api, f)
for f, line_num, line in input_api.RightHandSideLines(file_filter):
if raw_ptr_matcher.search(line):
errors.append(
output_api.PresubmitError(
f'Problem on {f.LocalPath()}:{line_num} - '
'raw_ptr<T> should not be used in this renderer code '
'(as documented in the "Pointers to unprotected memory" '
'section in //base/memory/raw_ptr.md)'))
return errors
def CheckAdvancedMemorySafetyChecksUsage(input_api, output_api):
"""Checks that ADVANCED_MEMORY_SAFETY_CHECKS() macro is neither added nor
removed as it is managed by the memory safety team internally.
Do not add / remove it manually."""
paths = set([])
macro_matcher = input_api.re.compile(
r'^((?!//).)*\bADVANCED_MEMORY_SAFETY_CHECKS\(',
input_api.re.MULTILINE)
for f in input_api.AffectedFiles():
if not _IsCPlusPlusFile(input_api, f.LocalPath()):
continue
if macro_matcher.search(f.GenerateScmDiff()):
paths.add(f.LocalPath())
if not paths:
return []
return [
output_api.PresubmitPromptWarning(
'ADVANCED_MEMORY_SAFETY_CHECKS() macro is managed by '
'the memory safety team (chrome-memory-safety@). '
'Please contact us to add/delete the uses of the macro.', paths)
]
def CheckPythonShebang(input_api, output_api):
"""Checks that python scripts use #!/usr/bin/env instead of hardcoding a
system-wide python.
"""
errors = []
sources = lambda affected_file: input_api.FilterSourceFile(
affected_file,
files_to_skip=((_THIRD_PARTY_EXCEPT_BLINK,
r'third_party/blink/web_tests/external/') + input_api.
DEFAULT_FILES_TO_SKIP),
files_to_check=[r'.*\.py$'])
for f in input_api.AffectedSourceFiles(sources):
for line_num, line in f.ChangedContents():
if line_num == 1 and line.startswith('#!/usr/bin/python'):
errors.append(f.LocalPath())
break
result = []
for file in errors:
result.append(
output_api.PresubmitError(
"Please use '#!/usr/bin/env python/2/3' as the shebang of %s" %
file))
return result
def CheckAndroidTestAnnotations(input_api, output_api):
"""Checks that tests have either @Batch or @DoNotBatch annotation. If this
is not an instrumentation test, disregard."""
batch_annotation = input_api.re.compile(r'^\s*@Batch')
do_not_batch_annotation = input_api.re.compile(r'^\s*@DoNotBatch')
robolectric_test = input_api.re.compile(
r'@RunWith\((.*?)RobolectricTestRunner')
test_class_declaration = input_api.re.compile(r'^\s*public\sclass.*Test')
uiautomator_test = input_api.re.compile(r'[uU]i[aA]utomator')
test_annotation_declaration = input_api.re.compile(
r'^\s*public\s@interface\s.*{')
missing_annotation_errors = []
extra_annotation_errors = []
wrong_robolectric_test_runner_errors = []
def _FilterFile(affected_file):
return input_api.FilterSourceFile(
affected_file,
files_to_skip=input_api.DEFAULT_FILES_TO_SKIP,
files_to_check=[r'.*Test\.java$'])
for f in input_api.AffectedSourceFiles(_FilterFile):
batch_matched = None
do_not_batch_matched = None
is_instrumentation_test = True
test_annotation_declaration_matched = None
has_base_robolectric_rule = False
for line in f.NewContents():
if 'BaseRobolectricTestRule' in line:
has_base_robolectric_rule = True
continue
if m := robolectric_test.search(line):
is_instrumentation_test = False
if not m.group(1) and not has_base_robolectric_rule:
path = str(f.LocalPath())
if 'webapk' not in path and 'build' not in path:
wrong_robolectric_test_runner_errors.append(path)
break
if uiautomator_test.search(line):
is_instrumentation_test = False
break
if not batch_matched:
batch_matched = batch_annotation.search(line)
if not do_not_batch_matched:
do_not_batch_matched = do_not_batch_annotation.search(line)
test_class_declaration_matched = test_class_declaration.search(
line)
test_annotation_declaration_matched = test_annotation_declaration.search(
line)
if test_class_declaration_matched or test_annotation_declaration_matched:
break
if test_annotation_declaration_matched:
continue
if (is_instrumentation_test and not batch_matched
and not do_not_batch_matched):
missing_annotation_errors.append(str(f.LocalPath()))
if (not is_instrumentation_test
and (batch_matched or do_not_batch_matched)):
extra_annotation_errors.append(str(f.LocalPath()))
results = []
if missing_annotation_errors:
results.append(
output_api.PresubmitPromptWarning(
"""
A change was made to an on-device test that has neither been annotated with
@Batch nor @DoNotBatch. If this is a new test, please add the annotation. If
this is an existing test, please consider adding it if you are sufficiently
familiar with the test (but do so as a separate change).
See https://source.chromium.org/chromium/chromium/src/+/main:docs/testing/batching_instrumentation_tests.md
""", missing_annotation_errors))
if extra_annotation_errors:
results.append(
output_api.PresubmitPromptWarning(
"""
Robolectric tests do not need a @Batch or @DoNotBatch annotations.
""", extra_annotation_errors))
if wrong_robolectric_test_runner_errors:
results.append(
output_api.PresubmitPromptWarning(
"""
Robolectric tests should use either @RunWith(BaseRobolectricTestRunner.class) (or
a subclass of it), or use "@Rule BaseRobolectricTestRule".
""", wrong_robolectric_test_runner_errors))
return results
def _CheckAndroidNullAwayAnnotatedClasses(input_api, output_api):
"""Checks that Java classes/interfaces/annotations are null-annotated."""
if input_api.change.RepositoryRoot().endswith('clank'):
return []
nullmarked_annotation = input_api.re.compile(
r'^\s*@(NullMarked|NullUnmarked)')
missing_annotation_errors = []
def _FilterFile(affected_file):
return input_api.FilterSourceFile(
affected_file,
files_to_skip=(_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
input_api.DEFAULT_FILES_TO_SKIP + (
r'.*Test.*\.java',
r'^build/.*',
r'^chromecast/.*',
r'^components/cronet/.*',
r'^tools/.*',
)),
files_to_check=[r'.*\.java$'])
for f in input_api.AffectedSourceFiles(_FilterFile):
if f.Action() != 'A':
continue
for line in f.NewContents():
if nullmarked_annotation.search(line):
break
else:
missing_annotation_errors.append(str(f.LocalPath()))
results = []
if missing_annotation_errors:
results.append(
output_api.PresubmitError(
"""
Please add @NullMarked and fix the NullAway warnings in the following files
(see https://chromium.googlesource.com/chromium/src/+/main/styleguide/java/nullaway.md):
""", missing_annotation_errors))
return results
def CheckNoJsInIos(input_api, output_api):
"""Checks to make sure that JavaScript files are not used on iOS."""
def _FilterFile(affected_file):
return input_api.FilterSourceFile(
affected_file,
files_to_skip=input_api.DEFAULT_FILES_TO_SKIP +
(r'^ios/third_party/*', r'^ios/tools/*', r'^third_party/*',
r'^components/autofill/ios/form_util/resources/*'),
files_to_check=[r'^ios/.*\.js$', r'.*/ios/.*\.js$'])
deleted_files = []
for f in input_api.AffectedFiles(file_filter=_FilterFile):
local_path = f.LocalPath()
if input_api.os_path.splitext(
local_path)[1] == '.js' and f.Action() == 'D':
deleted_files.append(input_api.os_path.basename(local_path))
error_paths = []
moved_paths = []
warning_paths = []
for f in input_api.AffectedSourceFiles(_FilterFile):
local_path = f.LocalPath()
if input_api.os_path.splitext(local_path)[1] == '.js':
if f.Action() == 'A':
if input_api.os_path.basename(local_path) in deleted_files:
moved_paths.append(local_path)
else:
error_paths.append(local_path)
elif f.Action() != 'D':
warning_paths.append(local_path)
results = []
if warning_paths:
results.append(
output_api.PresubmitPromptWarning(
'TypeScript is now fully supported for iOS feature scripts. '
'Consider converting JavaScript files to TypeScript. See '
'//ios/web/public/js_messaging/README.md for more details.',
warning_paths))
if moved_paths:
results.append(
output_api.PresubmitPromptWarning(
'Do not use JavaScript on iOS for new files as TypeScript is '
'fully supported. (If this is a moved file, you may leave the '
'script unconverted.) See //ios/web/public/js_messaging/README.md '
'for help using scripts on iOS.', moved_paths))
if error_paths:
results.append(
output_api.PresubmitError(
'Do not use JavaScript on iOS as TypeScript is fully supported. '
'See //ios/web/public/js_messaging/README.md for help using '
'scripts on iOS.', error_paths))
return results
def CheckLibcxxRevisionsMatch(input_api, output_api):
"""Check to make sure the libc++ version matches across deps files."""
if input_api.PresubmitLocalPath() != input_api.change.RepositoryRoot():
return []
DEPS_FILES = ['DEPS', 'buildtools/deps_revisions.gni']
file_filter = lambda f: f.UnixLocalPath() in DEPS_FILES
changed_deps_files = input_api.AffectedFiles(file_filter=file_filter)
if not changed_deps_files:
return []
def LibcxxRevision(file):
file = input_api.os_path.join(input_api.PresubmitLocalPath(),
*file.split('/'))
return input_api.re.search(r'libcxx_revision.*[:=].*[\'"](\w+)[\'"]',
input_api.ReadFile(file)).group(1)
if len(set([LibcxxRevision(f) for f in DEPS_FILES])) == 1:
return []
return [
output_api.PresubmitError(
'libcxx_revision not equal across %s' % ', '.join(DEPS_FILES),
changed_deps_files)
]
def CheckDanglingUntriaged(input_api, output_api):
"""Warn developers adding DanglingUntriaged raw_ptr."""
if input_api.no_diffs:
return []
def FilterFile(file):
return input_api.FilterSourceFile(
file,
files_to_check=[r'.*\.(h|cc|cpp|cxx|m|mm)$'],
files_to_skip=[r'^base/allocator.*'],
)
count = 0
for f in input_api.AffectedFiles(file_filter=FilterFile):
count -= sum([l.count('DanglingUntriaged') for l in f.OldContents()])
count += sum([l.count('DanglingUntriaged') for l in f.NewContents()])
if count == 0:
return []
if count < 0:
message = f'DanglingUntriaged pointers removed: {-count}\nThank you!'
return [output_api.PresubmitNotifyResult(message)]
notes_regex = input_api.re.compile('DanglingUntriaged-notes[:=]')
if any(
notes_regex.match(line)
for line in input_api.change.DescriptionText().splitlines()):
return []
if input_api.change.GitFootersFromDescription().get(
'DanglingUntriaged-notes', []):
return []
message = ("""Unexpected new occurrences of `DanglingUntriaged` detected.
Please avoid adding new ones.
See documentation:
https://chromium.googlesource.com/chromium/src/+/main/docs/dangling_ptr.md
See also the guide to fix dangling pointers:
https://chromium.googlesource.com/chromium/src/+/main/docs/dangling_ptr_guide.md
To disable this warning, please add in the commit description:
DanglingUntriaged-notes: <rationale for new untriaged dangling pointers>""")
return [output_api.PresubmitPromptWarning(message)]
def CheckInlineConstexprDefinitionsInHeaders(input_api, output_api):
"""Checks that non-static constexpr definitions in headers are inline."""
pattern = input_api.re.compile(r'^constexpr (?!inline )[^\(\)]*[={]')
attribute_pattern = input_api.re.compile(
r'(\[\[[a-zA-Z_:]+\]\]|[A-Z]+[A-Z_]+) ')
problems = []
for f in input_api.AffectedFiles():
if not _IsCPlusPlusHeaderFile(input_api, f.LocalPath()):
continue
for line_number, line in f.ChangedContents():
line = attribute_pattern.sub('', line)
if pattern.search(line):
problems.append(f'{f.LocalPath()}: {line_number}\n {line}')
if problems:
return [
output_api.PresubmitPromptWarning(
'Consider inlining constexpr variable definitions in headers '
'outside of classes to avoid unnecessary copies of the '
'constant. See https://abseil.io/tips/168 for more details.',
problems)
]
else:
return []
def CheckTodoBugReferences(input_api, output_api):
"""Checks that bugs in TODOs use updated issue tracker IDs."""
files_to_skip = [
'PRESUBMIT_test.py', r'^third_party/rust/chromium_crates_io/vendor/.*'
]
def _FilterFile(affected_file):
return input_api.FilterSourceFile(affected_file,
files_to_skip=files_to_skip)
pattern = input_api.re.compile(r'.*\bTODO\([^\)0-9]*([0-9]+)\).*')
problems = []
for f in input_api.AffectedSourceFiles(_FilterFile):
for line_number, line in f.ChangedContents():
match = pattern.match(line)
if match and int(match.group(1)) <= 1524553:
problems.append(f'{f.LocalPath()}: {line_number}\n {line}')
if problems:
return [
output_api.PresubmitPromptWarning(
'TODOs should use the new Chromium Issue Tracker IDs which can '
'be found by navigating to the bug. See '
'https://crbug.com/336778624 for more details.', problems)
]
else:
return []
def CheckNoBrowserStarInUnittests(input_api, output_api):
"""Checks that unit-tests don't contain Browser* variables.
"""
problems = []
def FileFilter(affected_file):
"""Check unit-tests only"""
return input_api.FilterSourceFile(
affected_file,
files_to_check=(r'.*unittest\.cc$', r'.*unittest\.h$'),
files_to_skip=input_api.DEFAULT_FILES_TO_SKIP,
)
browser_star_pattern = input_api.re.compile(r'\bBrowser\s*\*')
for f in input_api.AffectedFiles(include_deletes=False,
file_filter=FileFilter):
for line_num, line in f.ChangedContents():
match = browser_star_pattern.search(line)
if match:
problems.append(' %s:%d:%s' %
(f.LocalPath(), line_num, match.group(0)))
if not problems:
return []
WARNING_MSG = """Do not use "Browser*" type in unittest files (e.g.,
"*unittest.cc" or "*unittest.h"). Unit tests should generally
not depend on the full Browser class or related components. Consider
refactoring to mock dependencies, use test-specific fakes,
or determine if a browser_test is more appropriate.
"""
return [output_api.PresubmitPromptWarning(WARNING_MSG, items=problems)]
def CheckBaseFeatureMacro(input_api, output_api):
"""Checks for correct usage of the BASE_FEATURE macro."""
pattern = input_api.re.compile(
r'\bBASE_FEATURE\s*\(\s*([^,]+)\s*,\s*([^,)]+)')
warnings = []
for f in input_api.AffectedFiles():
if not f.LocalPath().endswith(('.cc', '.mm')):
continue
changed_line_numbers = {
line_num
for line_num, _ in f.ChangedContents()
}
if not changed_line_numbers:
continue
lines = list(f.NewContents())
contents = '\n'.join(lines)
for match in pattern.finditer(contents):
start_line = contents.count('\n', 0, match.start()) + 1
end_line = contents.count('\n', 0, match.end()) + 1
if not changed_line_numbers.intersection(
range(start_line, end_line + 1)):
continue
if lines[start_line - 1].strip().startswith('//'):
continue
param1 = match.group(1).strip()
param2 = match.group(2).strip()
if param2.startswith('"') and param2.endswith('"'):
warnings.append(
' %s:%d: The 3-argument BASE_FEATURE macro with a '
'string literal is discouraged. Use the 2-argument '
'version instead.' % (f.LocalPath(), start_line))
if not input_api.re.match(r'^k[A-Z]', param1):
warnings.append(
' %s:%d: Feature identifier "%s" should start with "k" '
'followed by an uppercase letter.' %
(f.LocalPath(), start_line, param1))
if not warnings:
return []
return [
output_api.PresubmitPromptWarning('BASE_FEATURE() macro naming:',
warnings)
]