blob: 9efe4064c97d760cede8ff5a57f939b61db89565 [file] [log] [blame]
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
'''
Checks that each RuntimeFeature gated mojo method used for glic has a
corresponding '// MOJO_RUNTIME_FEATURE_GATED' comment where that method
is excluded from the public API.
Calling RuntimeFeature gated methods when the feature is not enabled
will result in a closed mojo pipe, putting Glic in an unusable state.
This check helps reduce the risk for accidentally calling such methods.
'''
import os
import re
import sys
def _GetDirAbove(dirname: str):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
path = os.path.abspath(__file__)
while True:
path, tail = os.path.split(path)
if not tail:
return None
if tail == dirname:
return path
def _RemoveComments(text: str):
# Remove multi-line comments first, then single-line comments.
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL)
text = re.sub(r'//.*', '', text)
return text
def _FindScopeBoundaries(scope_type: str, scope_name: str, mojom_text: str):
m = re.search(f'\\s*{scope_type}\\s+{scope_name}\\s*\\{{', mojom_text)
if not m:
return None
start_index = m.end()
brace_counter = 1
end_index = -1
for i in range(start_index, len(mojom_text)):
if mojom_text[i] == '{':
brace_counter += 1
elif mojom_text[i] == '}':
brace_counter -= 1
if brace_counter == 0:
end_index = i
break
if end_index == -1:
return None
return start_index, end_index
def _ExcludeTextWithin(scope_type: str, scope_name: str, mojom_text: str):
boundaries = _FindScopeBoundaries(scope_type, scope_name, mojom_text)
if boundaries:
return mojom_text[:boundaries[0]] + mojom_text[boundaries[1]:]
return ''
SRC_ROOT = _GetDirAbove('chrome')
def _Main():
error = False
# Find methods marked as checked in glic_api_client.ts.
api_client_path = ('chrome/browser/resources/glic/glic_api_impl/'
'glic_api_client.ts')
with open(os.path.join(SRC_ROOT, api_client_path), 'r') as f:
client_src = f.read()
checked_methods = set(
m.group(1) for m in re.finditer(
r'//\s*MOJO_RUNTIME_FEATURE_GATED\s+(\S*)', client_src))
# Find methods gated with RuntimeFeature annotations in glic.mojom.
mojo_file_path = 'chrome/browser/glic/host/glic.mojom'
with open(os.path.join(SRC_ROOT, mojo_file_path), 'r') as f:
mojom_text = f.read()
mojom_text = _RemoveComments(mojom_text)
for excluded_interface in ['WebClient']:
new_mojom_text = _ExcludeTextWithin('interface', excluded_interface,
mojom_text)
if new_mojom_text:
mojom_text = new_mojom_text
else:
print('Error: Could not find excluded interface '
f'"{excluded_interface}" in {mojo_file_path}.')
error = True
if error:
sys.exit(1)
gated_methods = set(
m.group(1) for m in re.finditer(
r'^\s*\[[^\]]*RuntimeFeature\s*=[^\]]*]\s*(\S+)\s*\(',
mojom_text,
flags=re.M | re.DOTALL))
# Match checked and gated methods against each other.
unchecked = gated_methods - checked_methods
ungated = checked_methods - gated_methods
for method in unchecked:
decl = f'// MOJO_RUNTIME_FEATURE_GATED {method}'
print('Error: missing feature gating code for feature'
f' gated Mojo method {method} from {mojo_file_path}.'
f' Please update {api_client_path} with the line:\n'
f' {decl}')
error = True
for method in ungated:
decl = f'// MOJO_RUNTIME_FEATURE_GATED {method}'
print(f'Error: found "{decl}" in {api_client_path},'
' but this method was not found or is not gated by'
f' a RuntimeFeature in {mojo_file_path}')
error = True
if error:
sys.exit(1)
if __name__ == '__main__':
_Main()