Skip to content

Commit 3527ae9

Browse files
committed
refactor(codeql): split utils module into package
Decompose the 1149-line utils/codeql.py into a package along a clean dependency DAG: - subprocess_runner: _stream_subprocess (tee + capture) - language: query language detection/normalization - database: locate/validate/diagnose CodeQL databases - qlpack: lock-file generation and temporary query packs - execution: run queries and decode results - results: persist queries, parse/inspect result artifacts Function bodies moved verbatim (byte-identical, verified via AST). Public surface unchanged: all 16 previously-importable names, including the underscore-prefixed _stream_subprocess and _format_db_error used by codeql_compose and base_verification_agent, are re-exported from the same path. 89 passed / 2 skipped.
1 parent 0ec7e5f commit 3527ae9

7 files changed

Lines changed: 698 additions & 616 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""CodeQL utilities (package split from the former module).
2+
3+
Public surface is unchanged: every name previously importable from
4+
``pure_auto_codeql.utils.codeql`` is re-exported here.
5+
"""
6+
7+
from .database import (
8+
_format_db_error,
9+
is_database_error,
10+
resolve_codeql_database_root,
11+
validate_codeql_database,
12+
)
13+
from .execution import (
14+
execute_codeql_query,
15+
run_query_and_decode_to_text,
16+
run_simple_query,
17+
)
18+
from .language import detect_language_from_query, normalize_language
19+
from .qlpack import create_temporary_qlpack, gen_codeql_lock_yml
20+
from .results import (
21+
count_dataflow_paths,
22+
is_empty_result,
23+
parse_codeql_results,
24+
save_query_to_persistent_dir,
25+
)
26+
from .subprocess_runner import _stream_subprocess
27+
28+
__all__ = [
29+
"_format_db_error",
30+
"_stream_subprocess",
31+
"count_dataflow_paths",
32+
"create_temporary_qlpack",
33+
"detect_language_from_query",
34+
"execute_codeql_query",
35+
"gen_codeql_lock_yml",
36+
"is_database_error",
37+
"is_empty_result",
38+
"normalize_language",
39+
"parse_codeql_results",
40+
"resolve_codeql_database_root",
41+
"run_query_and_decode_to_text",
42+
"run_simple_query",
43+
"save_query_to_persistent_dir",
44+
"validate_codeql_database",
45+
]
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
"""Locate, validate, and diagnose CodeQL databases."""
2+
3+
import subprocess
4+
from pathlib import Path
5+
from typing import Optional, Tuple
6+
7+
8+
def _format_db_error(combined_error: str, database_path: str) -> str:
9+
"""构建带排查建议的数据库错误提示(多个执行函数共用)。"""
10+
return (
11+
f"数据库错误:\n"
12+
f"{combined_error}\n\n"
13+
f"建议:\n"
14+
f"1. 检查数据库路径是否正确: {database_path}\n"
15+
f"2. 使用 'codeql database info {database_path}' 验证数据库\n"
16+
f"3. 如果数据库不存在或已损坏,请使用 'codeql database create' 重新创建"
17+
)
18+
19+
20+
def resolve_codeql_database_root(path: str, language: Optional[str] = None) -> str:
21+
"""
22+
解析真正的CodeQL数据库根目录。
23+
如果给定路径本身包含 codeql-database.yml,则返回该路径。
24+
如果给定路径不包含,但其子目录(如 python/ 或 cpp/)包含,则返回子目录路径。
25+
如果指定了language,则优先查找名称匹配的子目录。
26+
27+
支持的格式:
28+
- {path}/codeql-database.yml (直接路径)
29+
- {path}/{language}/codeql-database.yml (例如: db/cpp, db/python, db/java)
30+
- {path}/db-{language}/codeql-database.yml (例如: db-java, db-cpp)
31+
- {path}/db/{language}/codeql-database.yml (例如: projects/CVE-xxx/db/cpp)
32+
"""
33+
if not path:
34+
return path
35+
36+
db_path = Path(path)
37+
if not db_path.exists():
38+
return path
39+
40+
# 如果路径本身就是数据库根目录
41+
if (db_path / "codeql-database.yml").exists():
42+
return str(db_path)
43+
44+
# 检查子目录
45+
try:
46+
# 如果指定了语言,优先检查对应的子目录
47+
if language:
48+
lang_lower = language.lower().strip()
49+
# 处理一些常见的语言名称变体
50+
lang_map = {
51+
'c': 'cpp', 'c++': 'cpp', 'cplusplus': 'cpp',
52+
'c#': 'csharp', 'cs': 'csharp',
53+
'js': 'javascript', 'ts': 'javascript', 'typescript': 'javascript'
54+
}
55+
target_lang = lang_map.get(lang_lower, lang_lower)
56+
57+
# 尝试查找精确匹配的子目录或 db-{lang} 格式
58+
# 优先级:直接子目录 > db-{lang} > db/{lang}
59+
candidates = [
60+
db_path / target_lang, # 例如: db_path/cpp
61+
db_path / f"db-{target_lang}", # 例如: db_path/db-cpp
62+
db_path / "db" / target_lang, # 例如: db_path/db/cpp
63+
]
64+
65+
for candidate in candidates:
66+
if candidate.is_dir() and (candidate / "codeql-database.yml").exists():
67+
return str(candidate)
68+
69+
# 如果没找到,尝试在 db/{lang}/db-{lang} 这样的嵌套结构中查找
70+
nested_candidate = db_path / "db" / target_lang / f"db-{target_lang}"
71+
if nested_candidate.is_dir() and (nested_candidate / "codeql-database.yml").exists():
72+
return str(nested_candidate)
73+
74+
# 如果没指定语言或没找到特定语言目录,则遍历一级子目录
75+
for subdir in db_path.iterdir():
76+
if subdir.is_dir() and (subdir / "codeql-database.yml").exists():
77+
return str(subdir)
78+
79+
# 尝试深入一层 (例如 db/python/codeql-database.yml 或 db/cpp/codeql-database.yml)
80+
db_subdir = db_path / "db"
81+
if db_subdir.is_dir():
82+
# 先检查 db 下的直接子目录
83+
for subdir in db_subdir.iterdir():
84+
if subdir.is_dir() and (subdir / "codeql-database.yml").exists():
85+
return str(subdir)
86+
87+
# 再检查 db/{lang}/db-{lang} 这样的嵌套结构
88+
for lang_subdir in db_subdir.iterdir():
89+
if lang_subdir.is_dir():
90+
nested_db = lang_subdir / f"db-{lang_subdir.name}"
91+
if nested_db.is_dir() and (nested_db / "codeql-database.yml").exists():
92+
return str(nested_db)
93+
94+
except Exception:
95+
pass
96+
97+
return path
98+
99+
100+
def validate_codeql_database(database_path: str, language: Optional[str] = None) -> Tuple[bool, str]:
101+
"""
102+
验证CodeQL数据库是否存在且有效。
103+
104+
Args:
105+
database_path: CodeQL数据库的路径
106+
language: 可选的语言提示,用于辅助定位数据库子目录
107+
108+
Returns:
109+
(is_valid, error_message) 元组:
110+
- is_valid: 数据库是否有效
111+
- error_message: 如果无效,包含详细的错误信息;如果有效,为空字符串
112+
"""
113+
if not database_path:
114+
return False, "数据库路径为空。请提供有效的CodeQL数据库路径。"
115+
116+
# 尝试解析真实的数据库根目录
117+
real_db_path_str = resolve_codeql_database_root(database_path, language)
118+
db_path = Path(real_db_path_str)
119+
120+
# 检查路径是否存在
121+
if not db_path.exists():
122+
return False, (
123+
f"数据库路径不存在: {database_path}\n"
124+
f"请检查路径是否正确,或使用 'codeql database create' 创建数据库。"
125+
)
126+
127+
# 检查是否为目录
128+
if not db_path.is_dir():
129+
return False, (
130+
f"数据库路径不是目录: {database_path}\n"
131+
f"CodeQL数据库必须是一个目录。"
132+
)
133+
134+
# 检查关键文件/目录是否存在(CodeQL数据库的典型结构)
135+
# CodeQL数据库通常包含 codeql-database.yml 或 db-* 目录
136+
has_database_yml = (db_path / "codeql-database.yml").exists()
137+
has_db_subdirs = any(
138+
subdir.name.startswith("db-") or subdir.name == "db"
139+
for subdir in db_path.iterdir()
140+
if subdir.is_dir()
141+
)
142+
143+
if not (has_database_yml or has_db_subdirs):
144+
# 尝试使用 codeql resolve database 命令验证
145+
try:
146+
result = subprocess.run(
147+
['codeql', 'resolve', 'database', str(db_path)],
148+
capture_output=True,
149+
text=True,
150+
timeout=10
151+
)
152+
if result.returncode != 0:
153+
error_msg = result.stderr.strip() or result.stdout.strip()
154+
if "not a recognized CodeQL database" in error_msg:
155+
return False, (
156+
f"无效的CodeQL数据库: {database_path}\n"
157+
f"错误详情: {error_msg}\n"
158+
f"请使用 'codeql database create' 创建有效的数据库,或检查数据库是否已损坏。"
159+
)
160+
return False, (
161+
f"无法验证数据库: {database_path}\n"
162+
f"错误详情: {error_msg}"
163+
)
164+
except FileNotFoundError:
165+
# CodeQL CLI 未找到,但至少路径存在,返回警告而不是错误
166+
return True, "警告: 无法验证数据库(CodeQL CLI 未找到),但路径存在。"
167+
except subprocess.TimeoutExpired:
168+
return False, (
169+
f"数据库验证超时: {database_path}\n"
170+
f"数据库可能已损坏或无法访问。"
171+
)
172+
except Exception as e:
173+
return False, (
174+
f"数据库验证失败: {database_path}\n"
175+
f"错误: {str(e)}"
176+
)
177+
178+
# 数据库看起来有效
179+
return True, ""
180+
181+
182+
def is_database_error(error_output: str) -> bool:
183+
"""
184+
检查错误输出是否与数据库相关。
185+
186+
Args:
187+
error_output: CodeQL命令的错误输出
188+
189+
Returns:
190+
如果是数据库相关错误,返回True;否则返回False
191+
"""
192+
if not error_output:
193+
return False
194+
195+
error_lower = error_output.lower()
196+
database_error_patterns = [
197+
"not a recognized codeql database",
198+
"is not a codeql database",
199+
"database does not exist",
200+
"database path",
201+
"invalid database",
202+
"database not found",
203+
"无法识别",
204+
"不是有效的",
205+
]
206+
207+
return any(pattern in error_lower for pattern in database_error_patterns)

0 commit comments

Comments
 (0)