tor-browser

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

commit 9fcc8e7f0b1624433e4228228e4ae911456e5ab0
parent b71bcebac0dfa1a23a2b0c6a032ee4170bf79360
Author: Alex Franchuk <afranchuk@mozilla.com>
Date:   Tue,  4 Nov 2025 16:43:46 +0000

Bug 1989410 - Include Glean information in CrashAnnotations.yaml r=gsvelto,geckoview-reviewers,nalexander

Also cache the interpreted crash annotations so multiple consumers need
not repeat preprocessing.

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

Diffstat:
Mmobile/android/moz.build | 2++
Mtoolkit/crashreporter/CrashAnnotations.yaml | 311++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mtoolkit/crashreporter/annotations/generate.py | 58++++------------------------------------------------------
Atoolkit/crashreporter/annotations/load.py | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/crashreporter/annotations/moz.build | 3+++
5 files changed, 461 insertions(+), 63 deletions(-)

diff --git a/mobile/android/moz.build b/mobile/android/moz.build @@ -60,11 +60,13 @@ GeneratedFile( GeneratedFile( "geckoview/src/main/java/org/mozilla/geckoview/CrashReport.java", script="/toolkit/crashreporter/annotations/generate.py", + inputs=["!/toolkit/crashreporter/annotations/validated.yaml"], ) GeneratedFile( "android-components/components/lib/crash/src/main/java/mozilla/components/lib/crash/service/CrashReport.kt", script="/toolkit/crashreporter/annotations/generate.py", + inputs=["!/toolkit/crashreporter/annotations/validated.yaml"], ) CONFIGURE_SUBST_FILES += ["installer/Makefile"] diff --git a/toolkit/crashreporter/CrashAnnotations.yaml b/toolkit/crashreporter/CrashAnnotations.yaml @@ -9,16 +9,56 @@ # by the crash reporting machinery; these values can't be accessed by the # C++/JS APIs. # -# Additionally a field can have the following optional fields: +# Additionally an entry can have the following optional fields: # - altname: A string that will be used when writing out the annotation to the # .extra file instead of the annotation name # - scope: A string that indicates the scope of the annotation. Valid values # include: # - "client": annotations which are never sent remotely (the default if unspecified), -# - "report": annotations which are sent in reports, and -# - "ping": annotations which are sent in pings and reports. +# - "report": annotations which are sent in reports, +# - "ping": annotations which are sent in pings and reports, and +# - "ping-only": annotations which are sent only in pings. # - skip_if: A string that will cause the annotation not to be included in the # crash report if the contents match it. +# - bugs: An array of bug numbers on bugzilla that relate to the annotation. +# - glean: How the annotation is surfaced as a Glean metric. This may only be +# present if scope is "ping" or "ping-only". +# +# This should be a map with the following (optional) properties: +# - metric: Either the full name of the Glean metric (e.g. crash.foobar) or +# the top-level Glean metric category to use (in which case the metric name +# will be derived as the snake_case version of the annotation name). +# Defaults to `crash` (as a category). +# - type: The Glean metric type. Defaults based on the annotation type: +# - `string` -> `string` +# - `boolean` -> `boolean` +# Other types have no default and will require an entry. +# Valid type descriptions: +# - `boolean` +# - `datetime` +# - requires an adjacent `time_unit` field +# - `timespan` +# - requires an adjacent `time_unit` field +# - `string` +# - `string_list` +# - requires an adjacent `delimiter` field +# - `quantity` +# - requires an adjacent `unit` field +# - `object` +# - requires an adjacent `structure` field (matching the glean schema) +# - custom_convert: If set to `true` or a string, a custom conversion function +# will be called to convert to the metric value. When set to `true`, the +# function will be based on the metric name. When set to a string, the +# function will be based on the provided string. +# +# It is only necessary to set this to override the default conversion +# functions or to share a conversion function -- other conversions will +# always use a custom conversion function. Default conversion functions +# exist for: +# - `boolean` -> `boolean` +# - `string` -> `string` +# - `u32`/`u64`/`usize` -> `quantity` +# - `string` -> `string_list` AbortMessage: description: > @@ -193,7 +233,9 @@ ApplicationBuildID: description: > Product application's build ID. type: string - scope: report + scope: ping + glean: + metric: crash.app_build ApplicationServicesVersion: description: The version of Application Services libraries. @@ -207,6 +249,19 @@ AsyncShutdownTimeout: condition that caused the hang is contained in the annotation. type: string scope: ping + glean: + type: object + structure: + type: object + properties: + phase: + type: string + conditions: + type: string + broken_add_blockers: + type: array + items: + type: string AvailablePageFile: description: > @@ -219,6 +274,10 @@ AvailablePageFile: - Not available on other platforms. type: usize scope: ping + glean: + metric: memory.available_commit + type: quantity + unit: bytes AvailablePhysicalMemory: description: > @@ -230,6 +289,10 @@ AvailablePhysicalMemory: - Not available on other platforms. type: usize scope: ping + glean: + metric: memory.available_physical + type: quantity + unit: bytes AvailableSwapMemory: description: > @@ -240,6 +303,10 @@ AvailableSwapMemory: - Not available on other platforms. type: usize scope: ping + glean: + metric: memory.available_swap + type: quantity + unit: bytes AvailableVirtualMemory: description: > @@ -250,6 +317,10 @@ AvailableVirtualMemory: - For macOS, see AvailableSwapMemory, AvailablePhysicalMemory and PurgeablePhysicalMemory. type: usize scope: ping + glean: + metric: memory.available_virtual + type: quantity + unit: bytes BackgroundTaskMode: description: > @@ -265,9 +336,13 @@ BackgroundTaskName: BlockedDllList: description: > - Comma-separated list of blocked DLLS, Windows-only + Semicolon-separated list of blocked DLLS, Windows-only type: string scope: ping + glean: + metric: dll_blocklist.list + type: string_list + delimiter: ";" BlocklistInitFailed: description: > @@ -275,6 +350,8 @@ BlocklistInitFailed: type: boolean scope: ping skip_if: "0" + glean: + metric: dll_blocklist.init_failed Breadcrumbs: description: > @@ -292,7 +369,7 @@ BuildID: CrashType: description: The type of crash that occurred (Android-only). type: string - scope: report + scope: ping ContentSandboxCapabilities: description: > @@ -347,6 +424,10 @@ CrashTime: Crash time in seconds since the Epoch. type: string scope: ping + glean: + metric: crash.time + type: datetime + time_unit: second CycleCollector: description: > @@ -405,6 +486,9 @@ EventLoopNestingLevel: type: u32 scope: ping skip_if: "0" + glean: + type: quantity + unit: levels FontName: description: > @@ -439,6 +523,10 @@ GPUProcessLaunchCount: Number of times the GPU process was launched. type: u32 scope: ping + glean: + metric: crash.gpu_process_launch + type: quantity + unit: events GPUProcessStatus: description: > @@ -489,6 +577,8 @@ HeadlessMode: True if the app was invoked in headless mode via `--headless ...` or `--backgroundtask ...`, false otherwise. type: boolean scope: ping + glean: + metric: environment PHCKind: description: > @@ -544,7 +634,7 @@ InstallTime: ipc_channel_error: description: > - Set before a content process crashes because of an IPC channel error, holds + Set before a content process crashes because of an IPC channel error. Holds a description of the error. type: string scope: ping @@ -651,7 +741,63 @@ JavaException: JSON structured Java stack trace, only present on Firefox for Android if we encounter an uncaught Java exception. type: string - scope: report + scope: ping + glean: + type: object + # version history (typescript for brevity) + # # 0 + # { + # messages: string[], + # stack: { file: string, line: number, class_name: string, method_name: string, is_native: boolean }[] + # } + # + # # 1 + # { + # throwables: { + # message: string, + # type_name: string, + # stack: { file: string, line: number, class_name: string, method_name: string, is_native: boolean }[] + # }[] + # } + version: 1 + structure: + type: object + properties: + # The Throwable and any cause Throwables. + # They are always in cause order (e.g., [`e`, `e.cause`, `e.cause.cause`, ...]). + throwables: + type: array + items: + type: object + properties: + # The message of the Throwable. + message: + type: string + # The java fully qualified type name of the Throwable. + type_name: + type: string + # The stack trace, from nearest to farthest execution point, of the Throwable. + # This may be omitted on `cause` Throwables. + stack: + type: array + items: + type: object + properties: + # The source file containing this stack trace element. + file: + type: string + # The line number of the source file containing this stack trace element. + line: + type: number + # The name of the class containing this stack trace element. + class_name: + type: string + # The name of the method containing this stack trace element. + method_name: + type: string + # Whether the method is native. + is_native: + type: boolean JavaStackTrace: description: > @@ -680,6 +826,8 @@ JSLargeAllocationFailure: description for the possible values of this annotation. type: string scope: ping + glean: + metric: memory JSModuleLoadError: description: > @@ -697,6 +845,8 @@ JSOutOfMemory: to satisfy the allocation. type: string scope: ping + glean: + metric: memory LastInteractionDuration: @@ -705,6 +855,10 @@ LastInteractionDuration: at crash. The value is not set if the user state was active. type: u64 scope: ping + glean: + type: timespan + time_unit: second + custom_convert: duration_seconds LastStartupWasCrash: description: > @@ -801,6 +955,10 @@ LowPhysicalMemoryEvents: type: u32 scope: ping skip_if: "0" + glean: + metric: memory.low_physical + type: quantity + unit: events MainThreadRunnableName: description: > @@ -808,6 +966,12 @@ MainThreadRunnableName: type: string scope: ping +MinidumpSha256Hash: + description: > + The sha256 hash of the minidump file, if available. + type: string + scope: ping + ModuleSignatureInfo: description: > A string holding a JSON object describing which entity signed the modules @@ -829,6 +993,10 @@ NimbusEnrollments: rollouts, as well as their branches. type: string scope: ping + glean: + metric: environment + type: string_list + delimiter: "," Notes: description: > @@ -842,6 +1010,10 @@ OOMAllocationSize: type: usize scope: ping skip_if: "0" + glean: + metric: memory + type: quantity + unit: bytes PluginFilename: description: > @@ -868,7 +1040,7 @@ ProcessType: Type of the process that crashed, the possible values are defined in GeckoProcessTypes.h. type: string - scope: report + scope: ping ProductName: description: > @@ -901,6 +1073,10 @@ PurgeablePhysicalMemory: vm_statistics64_data_t::purgeable_count * vm_page_size. type: usize scope: ping + glean: + metric: memory.purgeable_physical + type: quantity + unit: bytes QuotaManagerShutdownTimeout: description: > @@ -932,6 +1108,12 @@ QuotaManagerShutdownTimeout: started. These include the time difference and the type of object. type: string scope: ping + glean: + type: object + structure: + type: array + items: + type: string QuotaManagerStorageIsNetworkResource: description: > @@ -951,6 +1133,8 @@ ReleaseChannel: Application release channel (e.g. default, beta, ...) type: string scope: ping + glean: + metric: crash.app_channel RemoteAgent: description: > @@ -975,6 +1159,11 @@ SecondsSinceLastCrash: Time in seconds since the last crash occurred. type: u64 scope: ping + glean: + metric: crash.time_since_last_crash + type: timespan + time_unit: second + custom_convert: duration_seconds ServerURL: description: > @@ -1002,6 +1191,70 @@ StackTraces: sent in pings, however they are extracted and transformed into a different format. The field in the extra file is meant only for client use. type: object + scope: ping-only + glean: + type: object + # All addresses are hex strings rather than numbers. + structure: + type: object + properties: + # An error from the stack trace analysis, omitted if no errors occurred. + error: + type: string + # The type of crash (SIGSEGV, assertion, etc) + crash_type: + type: string + crash_address: + type: string + # Index of the crash thread + crash_thread: + type: number + # Index of the main executable module. + main_module: + type: number + # Modules ordered by base memory address. + modules: + type: array + items: + type: object + properties: + base_address: + type: string + end_address: + type: string + # Unique ID of the module + code_id: + type: string + debug_file: + type: string + debug_id: + type: string + filename: + type: string + version: + type: string + # Stack traces for each thread. + threads: + type: array + items: + type: object + properties: + # Frames in the thread stack trace + frames: + type: array + items: + type: object + properties: + # Index of the module that contains the frame + module_index: + type: number + # The instruction pointer of the frame + ip: + type: string + # Trust of the frame, one of "context", "prewalked", "cfi", + # "frame_pointer", "cfi_scan", "scan", or "none" + trust: + type: string StartupCacheValid: description: > @@ -1015,6 +1268,8 @@ StartupCrash: If set to 1 then this crash occurred during startup. type: boolean scope: ping + glean: + metric: crash.startup StartupTime: description: > @@ -1049,6 +1304,10 @@ SystemMemoryUsePercentage: field. type: u32 scope: ping + glean: + metric: memory.system_use_percentage + type: quantity + unit: percent TelemetryClientId: description: > @@ -1107,6 +1366,10 @@ TextureUsage: type: usize scope: ping skip_if: "0" + glean: + metric: memory.texture + type: quantity + unit: bytes Throttleable: description: > @@ -1127,6 +1390,10 @@ TotalPageFile: - Not available on other systems. type: usize scope: ping + glean: + metric: memory + type: quantity + unit: bytes TotalPhysicalMemory: description: > @@ -1138,6 +1405,10 @@ TotalPhysicalMemory: - Not available on other systems. type: usize scope: ping + glean: + metric: memory.total_physical + type: quantity + unit: bytes TotalVirtualMemory: description: > @@ -1147,6 +1418,10 @@ TotalVirtualMemory: - Not available on other platforms. type: usize scope: ping + glean: + metric: memory.total_virtual + type: quantity + unit: bytes UnknownNetAddrSocketFamily: description: > @@ -1161,6 +1436,10 @@ UptimeTS: because it has a fractional component. type: string # This is a floating-point number but we treat it as a string scope: ping + glean: + metric: environment.uptime + type: timespan + time_unit: millisecond URL: description: > @@ -1180,6 +1459,8 @@ User32BeforeBlocklist: type: boolean scope: ping skip_if: "0" + glean: + metric: dll_blocklist.user32_loaded_before useragent_locale: description: > @@ -1205,6 +1486,9 @@ UtilityActorsName: Comma-separated list of IPC actors name running on this Utility process instance type: string scope: ping + glean: + type: string_list + delimiter: "," Vendor: description: > @@ -1217,6 +1501,8 @@ Version: Product version. type: string scope: ping + glean: + metric: crash.app_display_version VRProcessStatus: description: > @@ -1238,6 +1524,9 @@ WindowsFileDialogErrorCode: for the value. type: u32 # This is an HRESULT which is defined as signed, but we don't want to print it as a signed integer scope: ping + glean: + metric: crash.windows.file_dialog_error_code + type: string WindowsPackageFamilyName: description: > @@ -1248,6 +1537,8 @@ WindowsPackageFamilyName: starts "Mozilla." or "MozillaCorporation.". type: string scope: ping + glean: + metric: windows.package_family_name WindowsErrorReporting: description: > @@ -1255,6 +1546,8 @@ WindowsErrorReporting: runtime exception module. type: boolean scope: ping + glean: + metric: crash.windows.error_reporting Winsock_LSP: description: > diff --git a/toolkit/crashreporter/annotations/generate.py b/toolkit/crashreporter/annotations/generate.py @@ -12,63 +12,12 @@ import yaml # Language-agnostic functionality # ############################################################################### -annotations_filename = path.join(path.dirname(__file__), "..", "CrashAnnotations.yaml") - template_header = ( "/* This file was autogenerated by " "toolkit/crashreporter/annotations/generate.py. DO NOT EDIT */\n\n" ) -def sort_annotations(annotations): - """Return annotations in ascending alphabetical order ignoring case""" - - return sorted(annotations.items(), key=lambda annotation: str.lower(annotation[0])) - - -def validate_annotations(annotations): - """Ensure that the annotations have all the required fields""" - - for name, data in annotations: - if "description" not in data: - sys.exit("Annotation " + name + " does not have a description\n") - if "type" not in data: - sys.exit("Annotation " + name + " does not have a type\n") - - annotation_type = data.get("type") - valid_types = ["string", "boolean", "u32", "u64", "usize", "object"] - if annotation_type not in valid_types: - sys.exit( - "Annotation " + name + " has an unknown type: " + annotation_type + "\n" - ) - - annotation_scope = data.get("scope", "client") - valid_scopes = ["client", "report", "ping", "ping-only"] - if annotation_scope not in valid_scopes: - sys.exit( - "Annotation " - + name - + " has an unknown scope: " - + annotation_scope - + "\n" - ) - - -def read_annotations(): - """Read the annotations from a YAML file. - If an error is encountered quit the program.""" - - try: - with open(annotations_filename) as annotations_file: - annotations = sort_annotations(yaml.safe_load(annotations_file)) - except (OSError, ValueError) as e: - sys.exit("Error parsing " + annotations_filename + ":\n" + str(e) + "\n") - - validate_annotations(annotations) - - return annotations - - def read_template(template_filename): """Read the contents of the template. If an error is encountered quit the program.""" @@ -257,8 +206,9 @@ def emit_java(annotations, output_name): # Main script entrypoint for GeneratedFile -def main(output): - annotations = read_annotations() +def main(output, annotations_path): + with open(annotations_path) as annotations_file: + annotations = yaml.safe_load(annotations_file) suffix = output.name.rpartition(".")[2] generator = generators.get(suffix) @@ -278,4 +228,4 @@ def main(output): except OSError as ex: sys.exit("Error while writing out the generated file:\n" + str(ex) + "\n") - return {annotations_filename, template_path} + return {template_path} diff --git a/toolkit/crashreporter/annotations/load.py b/toolkit/crashreporter/annotations/load.py @@ -0,0 +1,150 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import sys +from os import path + +import yaml + +__all__ = ["annotations_filename", "read_annotations"] + +annotations_filename = path.normpath( + path.join(path.dirname(__file__), "..", "CrashAnnotations.yaml") +) + + +def sort_annotations(annotations): + """Return annotations in ascending alphabetical order ignoring case""" + + return sorted(annotations.items(), key=lambda annotation: str.lower(annotation[0])) + + +# Convert CamelCase to snake_case. Also supports CAPCamelCase. +def camel_to_snake(s): + if s.islower(): + return s + lowers = [c.islower() for c in s] + [False] + words = [] + last = 0 + for i in range(1, len(s)): + if not lowers[i] and (lowers[i - 1] or lowers[i + 1]): + words.append(s[last:i]) + last = i + words.append(s[last:]) + return "_".join(words).lower() + + +class AnnotationValidator: + def __init__(self, name): + self._name = name + self.passed = True + + def validate(self, data): + """ + Ensure that the annotation has all the required fields, and elaborate + default values. + """ + if "description" not in data: + self._error("does not have a description") + annotation_type = data.get("type") + if annotation_type is None: + self._error("does not have a type") + + valid_types = ["string", "boolean", "u32", "u64", "usize", "object"] + if annotation_type and annotation_type not in valid_types: + self._error(f"has an unknown type: {annotation_type}") + annotation_type = None + + annotation_scope = data.setdefault("scope", "client") + valid_scopes = ["client", "report", "ping", "ping-only"] + if annotation_scope not in valid_scopes: + self._error(f"has an unknown scope: {annotation_scope}") + annotation_scope = None + + is_ping = annotation_scope and annotation_scope in ["ping", "ping-only"] + + if annotation_scope and "glean" in data and not is_ping: + self._error("has a glean metric specification but does not have ping scope") + + if annotation_type and is_ping: + self._glean(annotation_type, data.setdefault("glean", {})) + + def _error(self, message): + print( + f"{annotations_filename}: Annotation {self._name} {message}.", + file=sys.stderr, + ) + self.passed = False + + def _glean(self, annotation_type, glean): + if not isinstance(glean, dict): + self._error("has invalid glean metric specification (expected a map)") + return + + glean_metric_name = glean.setdefault("metric", "crash") + # If only a category is given, derive the metric name from the annotation name. + if "." not in glean_metric_name: + glean_metric_name = glean["metric"] = ( + f"{glean_metric_name}.{camel_to_snake(self._name)}" + ) + + glean_default_type = ( + annotation_type if annotation_type in ["string", "boolean"] else None + ) + glean_type = glean.setdefault("type", glean_default_type) + if glean_type is None: + self._error("must have a glean metric type specified") + + glean_types = [ + "boolean", + "datetime", + "timespan", + "string", + "string_list", + "quantity", + "object", + ] + if glean_type and glean_type not in glean_types: + self._error(f"has an invalid glean metric type ({glean_type})") + glean_type = None + + metric_required_fields = { + "datetime": ["time_unit"], + "timespan": ["time_unit"], + "quantity": ["unit"], + "string_list": ["delimiter"], + "object": ["structure"], + } + + required_fields = metric_required_fields.get(glean_type, []) + for field in required_fields: + if field not in glean: + self._error(f"requires a `{field}` field for glean {glean_type} metric") + + +def read_annotations(): + """Read the annotations from the YAML file. + If an error is encountered quit the program.""" + + try: + with open(annotations_filename) as annotations_file: + annotations = sort_annotations(yaml.safe_load(annotations_file)) + except (OSError, ValueError) as e: + sys.exit("Error parsing " + annotations_filename + ":\n" + str(e) + "\n") + + valid = True + for name, data in annotations: + validator = AnnotationValidator(name) + validator.validate(data) + valid &= validator.passed + + if not valid: + sys.exit(1) + + return annotations + + +def main(output): + yaml.safe_dump(read_annotations(), stream=output) + return {annotations_filename} diff --git a/toolkit/crashreporter/annotations/moz.build b/toolkit/crashreporter/annotations/moz.build @@ -14,8 +14,11 @@ UNIFIED_SOURCES = [ FINAL_LIBRARY = "xul" +GeneratedFile("validated.yaml", script="load.py") + # Generate CrashAnnotations.h GeneratedFile( "CrashAnnotations.h", script="generate.py", + inputs=["!validated.yaml"], )