VirtualBox

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

Last change on this file since 108793 was 105670, checked in by vboxsync, 9 months ago

Devices/EFI/FirmwareNew: Merge edk2-stable-202405 and make it build on aarch64, bugref:4643

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