|
| 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