VirtualBox

source: vbox/trunk/src/VBox/Devices/EFI/FirmwareNew/BaseTools/Plugin/CodeQL/CodeQlAnalyzePlugin.py@ 108794

Last change on this file since 108794 was 108794, checked in by vboxsync, 2 weeks ago

Devices/EFI/FirmwareNew: Merge edk2-stable202502 from the vendor branch and make it build for the important platforms, bugref:4643

  • Property svn:eol-style set to native
File size: 9.7 KB
Line 
1# @file CodeQAnalyzePlugin.py
2#
3# A build plugin that analyzes a CodeQL database.
4#
5# Copyright (c) Microsoft Corporation. All rights reserved.
6# Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved.
7# SPDX-License-Identifier: BSD-2-Clause-Patent
8##
9
10import json
11import logging
12import os
13import yaml
14
15from analyze import analyze_filter
16from common import codeql_plugin
17
18from edk2toolext import edk2_logging
19from edk2toolext.environment.plugintypes.uefi_build_plugin import \
20 IUefiBuildPlugin
21from edk2toolext.environment.uefi_build import UefiBuilder
22from edk2toollib.uefi.edk2.path_utilities import Edk2Path
23from edk2toollib.utility_functions import RunCmd
24from pathlib import Path
25
26
27class CodeQlAnalyzePlugin(IUefiBuildPlugin):
28
29 def do_post_build(self, builder: UefiBuilder) -> int:
30 """CodeQL analysis post-build functionality.
31
32 Args:
33 builder (UefiBuilder): A UEFI builder object for this build.
34
35 Returns:
36 int: The number of CodeQL errors found. Zero indicates that
37 AuditOnly mode is enabled or no failures were found.
38 """
39 self.builder = builder
40 self.package = builder.edk2path.GetContainingPackage(
41 builder.edk2path.GetAbsolutePathOnThisSystemFromEdk2RelativePath(
42 builder.env.GetValue("ACTIVE_PLATFORM")
43 )
44 )
45
46 self.package_path = Path(
47 builder.edk2path.GetAbsolutePathOnThisSystemFromEdk2RelativePath(
48 self.package
49 )
50 )
51 self.target = builder.env.GetValue("TARGET")
52
53 self.codeql_db_path = codeql_plugin.get_codeql_db_path(
54 builder.ws, self.package, self.target,
55 new_path=False)
56
57 self.codeql_path = codeql_plugin.get_codeql_cli_path()
58 if not self.codeql_path:
59 logging.critical("CodeQL build enabled but CodeQL CLI application "
60 "not found.")
61 return -1
62
63 codeql_sarif_dir_path = self.codeql_db_path[
64 :self.codeql_db_path.rindex('-')]
65 codeql_sarif_dir_path = codeql_sarif_dir_path.replace(
66 "-db-", "-analysis-")
67 self.codeql_sarif_path = os.path.join(
68 codeql_sarif_dir_path,
69 (os.path.basename(
70 self.codeql_db_path) +
71 ".sarif"))
72
73 edk2_logging.log_progress(f"Analyzing {self.package} ({self.target}) "
74 f"CodeQL database at:\n"
75 f" {self.codeql_db_path}")
76 edk2_logging.log_progress(f"Results will be written to:\n"
77 f" {self.codeql_sarif_path}")
78
79 # Packages are allowed to specify package-specific query specifiers
80 # in the package CI YAML file that override the global query specifier.
81 audit_only = False
82 global_audit_only = builder.env.GetValue("STUART_CODEQL_AUDIT_ONLY")
83 if global_audit_only:
84 if global_audit_only.strip().lower() == "true":
85 audit_only = True
86
87 query_specifiers = None
88 package_config_file = Path(os.path.join(
89 self.package_path, self.package + ".ci.yaml"))
90 plugin_data = None
91 if package_config_file.is_file():
92 with open(package_config_file, 'r') as cf:
93 package_config_file_data = yaml.safe_load(cf)
94 if "CodeQlAnalyze" in package_config_file_data:
95 plugin_data = package_config_file_data["CodeQlAnalyze"]
96 if "AuditOnly" in plugin_data:
97 audit_only = plugin_data["AuditOnly"]
98 if "QuerySpecifiers" in plugin_data:
99 logging.debug(f"Loading CodeQL query specifiers in "
100 f"{str(package_config_file)}")
101 query_specifiers = plugin_data["QuerySpecifiers"]
102
103 if audit_only:
104 logging.info(f"CodeQL Analyze plugin is in audit only mode for "
105 f"{self.package} ({self.target}).")
106
107 # Builds can override the query specifiers defined in this plugin
108 # by setting the value in the STUART_CODEQL_QUERY_SPECIFIERS
109 # environment variable.
110 if not query_specifiers:
111 query_specifiers = builder.env.GetValue(
112 "STUART_CODEQL_QUERY_SPECIFIERS")
113
114 # Use this plugins query set file as the default fallback if it is
115 # not overridden. It is possible the file is not present if modified
116 # locally. In that case, skip the plugin.
117 plugin_query_set = Path(Path(__file__).parent, "CodeQlQueries.qls")
118
119 if not query_specifiers and plugin_query_set.is_file():
120 query_specifiers = str(plugin_query_set.resolve())
121
122 if not query_specifiers:
123 logging.warning("Skipping CodeQL analysis since no CodeQL query "
124 "specifiers were provided.")
125 return 0
126
127 codeql_params = (f'database analyze {self.codeql_db_path} '
128 f'{query_specifiers} --format=sarifv2.1.0 '
129 f'--output={self.codeql_sarif_path} --download '
130 f'--threads=0')
131
132 # CodeQL requires the sarif file parent directory to exist already.
133 Path(self.codeql_sarif_path).parent.mkdir(exist_ok=True, parents=True)
134
135 cmd_ret = RunCmd(self.codeql_path, codeql_params)
136 if cmd_ret != 0:
137 logging.critical(f"CodeQL CLI analysis failed with return code "
138 f"{cmd_ret}.")
139
140 if not os.path.isfile(self.codeql_sarif_path):
141 logging.critical(f"The sarif file {self.codeql_sarif_path} was "
142 f"not created. Analysis cannot continue.")
143 return -1
144
145 filter_pattern_data = []
146 global_filter_file_value = builder.env.GetValue(
147 "STUART_CODEQL_FILTER_FILES")
148 if global_filter_file_value:
149 global_filter_files = global_filter_file_value.strip().split(',')
150 global_filter_files = [Path(f) for f in global_filter_files]
151
152 for global_filter_file in global_filter_files:
153 if global_filter_file.is_file():
154 with open(global_filter_file, 'r') as ff:
155 global_filter_file_data = yaml.safe_load(ff)
156 if "Filters" in global_filter_file_data:
157 current_pattern_data = \
158 global_filter_file_data["Filters"]
159 if type(current_pattern_data) is not list:
160 logging.critical(
161 f"CodeQL pattern data must be a list of "
162 f"strings. Data in "
163 f"{str(global_filter_file.resolve())} is "
164 f"invalid. CodeQL analysis is incomplete.")
165 return -1
166 filter_pattern_data += current_pattern_data
167 else:
168 logging.critical(
169 f"CodeQL global filter file "
170 f"{str(global_filter_file.resolve())} is "
171 f"malformed. Missing Filters section. CodeQL "
172 f"analysis is incomplete.")
173 return -1
174 else:
175 logging.critical(
176 f"CodeQL global filter file "
177 f"{str(global_filter_file.resolve())} was not found. "
178 f"CodeQL analysis is incomplete.")
179 return -1
180
181 if plugin_data and "Filters" in plugin_data:
182 if type(plugin_data["Filters"]) is not list:
183 logging.critical(
184 "CodeQL pattern data must be a list of strings. "
185 "CodeQL analysis is incomplete.")
186 return -1
187 filter_pattern_data.extend(plugin_data["Filters"])
188
189 if filter_pattern_data:
190 logging.info("Applying CodeQL SARIF result filters.")
191 analyze_filter.filter_sarif(
192 self.codeql_sarif_path,
193 self.codeql_sarif_path,
194 filter_pattern_data,
195 split_lines=False)
196
197 with open(self.codeql_sarif_path, 'r') as sf:
198 sarif_file_data = json.load(sf)
199
200 try:
201 # Perform minimal JSON parsing to find the number of errors.
202 total_errors = 0
203 for run in sarif_file_data['runs']:
204 total_errors += len(run['results'])
205 except KeyError:
206 logging.critical("Sarif file does not contain expected data. "
207 "Analysis cannot continue.")
208 return -1
209
210 if total_errors > 0:
211 if audit_only:
212 # Show a warning message so CodeQL analysis is not forgotten.
213 # If the repo owners truly do not want to fix CodeQL issues,
214 # analysis should be disabled entirely.
215 logging.warning(f"{self.package} ({self.target}) CodeQL "
216 f"analysis ignored {total_errors} errors due "
217 f"to audit mode being enabled.")
218 return 0
219 else:
220 logging.error(f"{self.package} ({self.target}) CodeQL "
221 f"analysis failed with {total_errors} errors.")
222
223 return total_errors
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette