Skip to content

Commit 03d98b7

Browse files
committed
Merge branch 'Get'
2 parents 30277f5 + d19c51b commit 03d98b7

5 files changed

Lines changed: 1233 additions & 994 deletions

File tree

api/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ class APIConfig(BaseSettings):
6969
docker_builder_image: str = Field(default="pure-codeql-cpp:latest", description="Docker 构建镜像名称")
7070
prefer_local_cpp_build: bool = Field(default=True, description="C/C++ 项目优先尝试本地两步走构建,失败后再用Docker")
7171
local_build_prepare_timeout: int = Field(default=300, description="本地准备阶段(configure/cmake)超时时间(秒)")
72+
73+
# 自动依赖安装配置
74+
auto_install_dependencies: bool = Field(default=True, description="自动检测并安装缺失的C/C++构建依赖")
75+
auto_install_max_retries: int = Field(default=5, description="自动安装依赖后的最大重试次数")
7276

7377
class Config:
7478
env_prefix = "API_"

services/llm_service.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from config import get_chat_config, LLMConfig, get_resilient_llm_config, LLMRole
2323
from utils.logger import get_logger
2424
from services.agent_mcp_config import AgentMCPConfigService
25+
from utils.mcp_schema_fixer import fix_mcp_tools_schemas
2526

2627

2728
class APIErrorClassifier:
@@ -824,7 +825,7 @@ async def initialize(
824825
"""
825826
logger = get_logger(__name__)
826827
self.llm = RetryableChatOpenAI(self.config, self.retry_tracker, event_callback)
827-
828+
828829
# Store language for tools that need it
829830
self.language = language
830831

@@ -846,7 +847,7 @@ async def initialize(
846847
# tree_sitter 和 language-server 都可能因为项目配置问题失败,但不应该阻止整个流程
847848
optional_servers = {"language-server", "tree_sitter"}
848849
failed_servers = []
849-
850+
850851
for server_name, connection in mcp_servers.items():
851852
try:
852853
if server_name == "tree_sitter":
@@ -878,7 +879,7 @@ async def initialize(
878879
self._session_stack = None
879880
self._mcp_sessions = {}
880881
raise
881-
882+
882883
if failed_servers:
883884
logger.info(f"ℹ️ 跳过了 {len(failed_servers)} 个可选 MCP 服务器: {', '.join(failed_servers)}")
884885

@@ -890,6 +891,12 @@ async def initialize(
890891
lsp_lookup_tool = LSPFunctionLookupTool(default_language=self.language or "java")
891892
self.tools.append(lsp_lookup_tool)
892893

894+
# Fix MCP tools schemas for Ubuntu compatibility
895+
# 修复Ubuntu环境下MCP工具schema不完整的问题
896+
# 在某些环境下,langchain-mcp-adapters返回的schema只有$schema字段,
897+
# 缺少type和properties,导致DeepSeek等API拒绝
898+
fix_mcp_tools_schemas(self.tools)
899+
893900
# Wrap all tools with token limiting
894901
logger = get_logger(__name__)
895902
logger.info(f"正在包装 {len(self.tools)} 个工具(包含MCP工具和LSP查询工具)...")

utils/codeql.py

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ def resolve_codeql_database_root(path: str, language: Optional[str] = None) -> s
4343
如果给定路径本身包含 codeql-database.yml,则返回该路径。
4444
如果给定路径不包含,但其子目录(如 python/ 或 cpp/)包含,则返回子目录路径。
4545
如果指定了language,则优先查找名称匹配的子目录。
46+
47+
支持的格式:
48+
- {path}/codeql-database.yml (直接路径)
49+
- {path}/{language}/codeql-database.yml (例如: db/cpp, db/python, db/java)
50+
- {path}/db-{language}/codeql-database.yml (例如: db-java, db-cpp)
51+
- {path}/db/{language}/codeql-database.yml (例如: projects/CVE-xxx/db/cpp)
4652
"""
4753
if not path:
4854
return path
@@ -51,6 +57,7 @@ def resolve_codeql_database_root(path: str, language: Optional[str] = None) -> s
5157
if not db_path.exists():
5258
return path
5359

60+
# 如果路径本身就是数据库根目录
5461
if (db_path / "codeql-database.yml").exists():
5562
return str(db_path)
5663

@@ -68,27 +75,41 @@ def resolve_codeql_database_root(path: str, language: Optional[str] = None) -> s
6875
target_lang = lang_map.get(lang_lower, lang_lower)
6976

7077
# 尝试查找精确匹配的子目录或 db-{lang} 格式
78+
# 优先级:直接子目录 > db-{lang} > db/{lang}
7179
candidates = [
72-
db_path / target_lang,
73-
db_path / f"db-{target_lang}",
74-
db_path / f"db/{target_lang}"
80+
db_path / target_lang, # 例如: db_path/cpp
81+
db_path / f"db-{target_lang}", # 例如: db_path/db-cpp
82+
db_path / "db" / target_lang, # 例如: db_path/db/cpp
7583
]
7684

7785
for candidate in candidates:
7886
if candidate.is_dir() and (candidate / "codeql-database.yml").exists():
7987
return str(candidate)
88+
89+
# 如果没找到,尝试在 db/{lang}/db-{lang} 这样的嵌套结构中查找
90+
nested_candidate = db_path / "db" / target_lang / f"db-{target_lang}"
91+
if nested_candidate.is_dir() and (nested_candidate / "codeql-database.yml").exists():
92+
return str(nested_candidate)
8093

8194
# 如果没指定语言或没找到特定语言目录,则遍历一级子目录
8295
for subdir in db_path.iterdir():
8396
if subdir.is_dir() and (subdir / "codeql-database.yml").exists():
8497
return str(subdir)
85-
86-
# 尝试深入一层 (例如 db/python/codeql-database.yml)
98+
99+
# 尝试深入一层 (例如 db/python/codeql-database.yml 或 db/cpp/codeql-database.yml)
87100
db_subdir = db_path / "db"
88101
if db_subdir.is_dir():
102+
# 先检查 db 下的直接子目录
89103
for subdir in db_subdir.iterdir():
90-
if subdir.is_dir() and (subdir / "codeql-database.yml").exists():
104+
if subdir.is_dir() and (subdir / "codeql-database.yml").exists():
91105
return str(subdir)
106+
107+
# 再检查 db/{lang}/db-{lang} 这样的嵌套结构
108+
for lang_subdir in db_subdir.iterdir():
109+
if lang_subdir.is_dir():
110+
nested_db = lang_subdir / f"db-{lang_subdir.name}"
111+
if nested_db.is_dir() and (nested_db / "codeql-database.yml").exists():
112+
return str(nested_db)
92113

93114
except Exception:
94115
pass
@@ -396,8 +417,9 @@ def run_simple_query(query_content: str, database_path: str, language: Optional[
396417
- results (List): 如果成功则包含解析结果,否则为空列表
397418
- result_file (str): 结果文件的路径
398419
"""
399-
# 在执行前验证数据库
400-
is_valid, validation_error = validate_codeql_database(database_path)
420+
# 在执行前验证数据库并解析真正的数据库路径
421+
resolved_database_path = resolve_codeql_database_root(database_path, language)
422+
is_valid, validation_error = validate_codeql_database(resolved_database_path, language)
401423
if not is_valid:
402424
return {
403425
'success': False,
@@ -432,6 +454,8 @@ def run_simple_query(query_content: str, database_path: str, language: Optional[
432454
with open(log_file, 'a', encoding='utf-8') as f:
433455
f.write(f"\n{'='*80}\n")
434456
f.write(f"[{log_timestamp}] 执行 codeql query run (简单查询)\n")
457+
f.write(f"原始数据库路径: {database_path}\n")
458+
f.write(f"解析后数据库路径: {resolved_database_path}\n")
435459
f.write(f"{'='*80}\n")
436460

437461
start_time = time.time()
@@ -442,7 +466,7 @@ def run_simple_query(query_content: str, database_path: str, language: Optional[
442466
[
443467
'codeql', 'query', 'run',
444468
str(query_file),
445-
'--database', database_path,
469+
'--database', resolved_database_path,
446470
f'--output={str(bqrs_path)}',
447471
],
448472
stdout=subprocess.PIPE,
@@ -662,8 +686,9 @@ def execute_codeql_query(query_content: str, database_path: str, language: Optio
662686
- results (List): 如果成功则包含解析结果,否则为空列表
663687
- sarif_path (str): SARIF输出文件的路径
664688
"""
665-
# 在执行前验证数据库
666-
is_valid, validation_error = validate_codeql_database(database_path)
689+
# 在执行前验证数据库并解析真正的数据库路径
690+
resolved_database_path = resolve_codeql_database_root(database_path, language)
691+
is_valid, validation_error = validate_codeql_database(resolved_database_path, language)
667692
if not is_valid:
668693
return {
669694
'success': False,
@@ -672,9 +697,9 @@ def execute_codeql_query(query_content: str, database_path: str, language: Optio
672697
'sarif_path': None,
673698
}
674699

675-
# 如果指定了alert参数,执行简单查询
700+
# 如果指定了alert参数,执行简单查询(使用解析后的路径)
676701
if alert == 'alert':
677-
return run_simple_query(query_content, database_path, language, query_file)
702+
return run_simple_query(query_content, resolved_database_path, language, query_file)
678703

679704
sarif_path: Optional[Path] = None
680705
try:
@@ -703,6 +728,8 @@ def execute_codeql_query(query_content: str, database_path: str, language: Optio
703728
with open(log_file, 'a', encoding='utf-8') as f:
704729
f.write(f"\n{'='*80}\n")
705730
f.write(f"[{log_timestamp}] 执行 codeql database analyze\n")
731+
f.write(f"原始数据库路径: {database_path}\n")
732+
f.write(f"解析后数据库路径: {resolved_database_path}\n")
706733
f.write(f"{'='*80}\n")
707734

708735
# 使用`codeql database analyze`执行查询,并使用SARIF v2.1.0输出,添加 --verbose 参数
@@ -711,7 +738,7 @@ def execute_codeql_query(query_content: str, database_path: str, language: Optio
711738
[
712739
'codeql', 'database', 'analyze',
713740
'--verbose',
714-
database_path,
741+
resolved_database_path,
715742
str(query_file),
716743
'--rerun',
717744
'--format=sarifv2.1.0',
@@ -861,8 +888,9 @@ def run_query_and_decode_to_text(
861888
- output (str): 查询输出或错误消息
862889
- result_file (str): 结果文件路径
863890
"""
864-
# 在执行前验证数据库
865-
is_valid, validation_error = validate_codeql_database(database_path)
891+
# 在执行前验证数据库并解析真正的数据库路径
892+
resolved_database_path = resolve_codeql_database_root(database_path, language)
893+
is_valid, validation_error = validate_codeql_database(resolved_database_path, language)
866894
if not is_valid:
867895
return {
868896
'success': False,
@@ -885,6 +913,8 @@ def run_query_and_decode_to_text(
885913
with open(log_file, 'a', encoding='utf-8') as f:
886914
f.write(f"\n{'='*80}\n")
887915
f.write(f"[{log_timestamp}] 执行 codeql query run\n")
916+
f.write(f"原始数据库路径: {database_path}\n")
917+
f.write(f"解析后数据库路径: {resolved_database_path}\n")
888918
f.write(f"{'='*80}\n")
889919

890920
# 执行 codeql query run,添加 --verbose 参数
@@ -894,7 +924,7 @@ def run_query_and_decode_to_text(
894924
'codeql', 'query', 'run',
895925
'--verbose',
896926
str(query_file),
897-
'--database', database_path,
927+
'--database', resolved_database_path,
898928
f'--output={str(bqrs_path)}',
899929
],
900930
stdout=subprocess.PIPE,

utils/project_importer.py

Lines changed: 91 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from api.config import get_config
1717
from utils.case import extract_cve_id
18+
from utils.dependency_installer import DependencyInstaller
1819

1920
logger = logging.getLogger(__name__)
2021

@@ -207,7 +208,12 @@ def _infer_case_id(input_dir: Path) -> Optional[str]:
207208
candidates.append(match)
208209

209210
if not candidates:
210-
return None
211+
# 如果无法提取CVE ID,则使用目录名作为Case ID
212+
# 替换非法字符以确保路径安全
213+
import re
214+
safe_name = re.sub(r'[^a-zA-Z0-9\-_]', '_', input_dir.name)
215+
logger.info("未检测到CVE ID,使用目录名作为Case ID: %s", safe_name)
216+
return safe_name
211217

212218
candidates.sort()
213219
return candidates[0]
@@ -461,10 +467,18 @@ def _create_codeql_database(
461467
# 策略1:优先本地两步走构建(如果启用)
462468
if config.prefer_local_cpp_build and not config.use_docker_for_cpp:
463469
logger.info("=" * 60)
464-
logger.info("策略1:尝试本地两步走构建")
470+
logger.info("策略1:尝试本地两步走构建(支持自动依赖安装)")
465471
logger.info("=" * 60)
466472

467-
try:
473+
# 创建依赖安装器(使用配置中的设置)
474+
dep_installer = DependencyInstaller(
475+
auto_install=config.auto_install_dependencies,
476+
max_retries=config.auto_install_max_retries
477+
)
478+
log_path = db_path.parent / "codeql.log"
479+
480+
def build_func():
481+
"""构建函数,供依赖安装器调用"""
468482
# 清理旧数据库
469483
if db_path.exists():
470484
logger.info("清理旧数据库: %s", db_path)
@@ -494,52 +508,101 @@ def _create_codeql_database(
494508
if build_plan.working_dir:
495509
cmd.extend(["--working-dir", str(build_plan.working_dir)])
496510

497-
log_path = db_path.parent / "codeql.log"
498511
_run_process(cmd, cwd=None, log_path=log_path)
499512

500513
# 验证数据库
501514
src_zip = db_path / "src.zip"
502515
if src_zip.exists() and src_zip.stat().st_size > 1024:
516+
return True
517+
else:
518+
logger.warning("本地构建完成,但 src.zip 太小或不存在")
519+
raise RuntimeError("Local build produced invalid database")
520+
521+
try:
522+
# 使用自动依赖安装功能进行构建
523+
success, error = dep_installer.try_build_with_auto_deps(
524+
build_func=build_func,
525+
log_path=log_path,
526+
)
527+
528+
if success:
503529
logger.info("=" * 60)
504530
logger.info("✅ 本地两步走构建成功!")
531+
if dep_installer.installed_packages:
532+
logger.info("📦 自动安装的依赖: %s", ", ".join(dep_installer.installed_packages))
505533
logger.info("=" * 60)
506534
return
507535
else:
508-
logger.warning("本地构建完成,但 src.zip 太小或不存在")
509-
raise RuntimeError("Local build produced invalid database")
536+
raise RuntimeError(f"Local build failed after auto-installing dependencies: {error}")
510537

511538
except Exception as e:
512539
logger.warning("=" * 60)
513540
logger.warning("⚠️ 本地构建失败: %s", e)
514541
logger.warning("=" * 60)
515542

516-
# 如果配置了 Docker,回退到 Docker autobuild
517-
if config.docker_builder_image:
543+
# 策略2:尝试 --build-mode=none(不编译,仅分析源码)
544+
logger.info("=" * 60)
545+
logger.info("策略2:尝试 --build-mode=none(仅分析源码,不编译)")
546+
logger.info("=" * 60)
547+
548+
try:
549+
# 清理失败的数据库
550+
if db_path.exists():
551+
_safe_rmtree(db_path)
552+
553+
# 使用 --build-mode=none 创建数据库
554+
cmd = [
555+
"codeql",
556+
"database",
557+
"create",
558+
str(db_path),
559+
"--language",
560+
language,
561+
"--overwrite",
562+
"--build-mode=none",
563+
"--source-root",
564+
str(source_root),
565+
]
566+
567+
none_log_path = db_path.parent / "codeql_none_mode.log"
568+
_run_process(cmd, cwd=None, log_path=none_log_path)
569+
518570
logger.info("=" * 60)
519-
logger.info("策略2:回退到 Docker autobuild")
571+
logger.info("✅ --build-mode=none 创建数据库成功!")
572+
logger.info("⚠️ 注意:未编译项目,分析结果可能不完整")
520573
logger.info("=" * 60)
574+
return
521575

522-
try:
523-
# 清理失败的数据库
524-
if db_path.exists():
525-
_safe_rmtree(db_path)
526-
527-
_run_docker_build(
528-
source_root=source_root,
529-
db_path=db_path,
530-
image_name=config.docker_builder_image,
531-
build_plan=None # Docker 内部会自动探测
532-
)
576+
except Exception as none_error:
577+
logger.warning("--build-mode=none 也失败了: %s", none_error)
578+
579+
# 策略3:如果配置了 Docker,回退到 Docker autobuild
580+
if config.docker_builder_image:
533581
logger.info("=" * 60)
534-
logger.info(" Docker autobuild 构建成功!")
582+
logger.info("策略3:最后回退到 Docker autobuild")
535583
logger.info("=" * 60)
536-
return
537-
except Exception as docker_error:
538-
logger.error("Docker autobuild 也失败了: %s", docker_error)
539-
raise RuntimeError(f"Both local and Docker builds failed. Local: {e}, Docker: {docker_error}")
540-
else:
541-
# 没有 Docker 配置,直接失败
542-
raise RuntimeError(f"Local build failed and no Docker fallback configured: {e}")
584+
585+
try:
586+
# 清理失败的数据库
587+
if db_path.exists():
588+
_safe_rmtree(db_path)
589+
590+
_run_docker_build(
591+
source_root=source_root,
592+
db_path=db_path,
593+
image_name=config.docker_builder_image,
594+
build_plan=None # Docker 内部会自动探测
595+
)
596+
logger.info("=" * 60)
597+
logger.info("✅ Docker autobuild 构建成功!")
598+
logger.info("=" * 60)
599+
return
600+
except Exception as docker_error:
601+
logger.error("Docker autobuild 也失败了: %s", docker_error)
602+
raise RuntimeError(f"All build strategies failed. Local: {e}, None mode: {none_error}, Docker: {docker_error}")
603+
else:
604+
# 没有 Docker 配置
605+
raise RuntimeError(f"Build failed. Local: {e}, None mode: {none_error}")
543606

544607
# 策略2:直接使用 Docker 构建(如果强制启用)
545608
elif config.use_docker_for_cpp:

0 commit comments

Comments
 (0)