jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +458 -152
- jarvis/jarvis_agent/agent_manager.py +17 -13
- jarvis/jarvis_agent/builtin_input_handler.py +2 -6
- jarvis/jarvis_agent/config_editor.py +2 -7
- jarvis/jarvis_agent/event_bus.py +82 -12
- jarvis/jarvis_agent/file_context_handler.py +329 -0
- jarvis/jarvis_agent/file_methodology_manager.py +3 -4
- jarvis/jarvis_agent/jarvis.py +628 -55
- jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
- jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
- jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
- jarvis/jarvis_agent/language_support_info.py +486 -0
- jarvis/jarvis_agent/main.py +34 -10
- jarvis/jarvis_agent/memory_manager.py +7 -16
- jarvis/jarvis_agent/methodology_share_manager.py +10 -16
- jarvis/jarvis_agent/prompt_manager.py +1 -1
- jarvis/jarvis_agent/prompts.py +193 -171
- jarvis/jarvis_agent/protocols.py +8 -12
- jarvis/jarvis_agent/run_loop.py +105 -9
- jarvis/jarvis_agent/session_manager.py +2 -3
- jarvis/jarvis_agent/share_manager.py +20 -22
- jarvis/jarvis_agent/shell_input_handler.py +1 -2
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +31 -6
- jarvis/jarvis_agent/task_manager.py +11 -27
- jarvis/jarvis_agent/tool_executor.py +2 -3
- jarvis/jarvis_agent/tool_share_manager.py +12 -24
- jarvis/jarvis_agent/utils.py +5 -1
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +786 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +575 -0
- jarvis/jarvis_c2rust/collector.py +250 -0
- jarvis/jarvis_c2rust/constants.py +26 -0
- jarvis/jarvis_c2rust/library_replacer.py +1254 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
- jarvis/jarvis_c2rust/loaders.py +207 -0
- jarvis/jarvis_c2rust/models.py +28 -0
- jarvis/jarvis_c2rust/optimizer.py +2157 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2983 -0
- jarvis/jarvis_c2rust/utils.py +385 -0
- jarvis/jarvis_code_agent/build_validation_config.py +132 -0
- jarvis/jarvis_code_agent/code_agent.py +1371 -220
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +106 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +110 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +269 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
- jarvis/jarvis_code_agent/lint.py +501 -8
- jarvis/jarvis_code_agent/utils.py +141 -0
- jarvis/jarvis_code_analysis/code_review.py +493 -584
- jarvis/jarvis_data/config_schema.json +128 -12
- jarvis/jarvis_git_squash/main.py +4 -5
- jarvis/jarvis_git_utils/git_commiter.py +82 -75
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
- jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
- jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
- jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
- jarvis/jarvis_methodology/main.py +32 -48
- jarvis/jarvis_multi_agent/__init__.py +287 -55
- jarvis/jarvis_multi_agent/main.py +36 -4
- jarvis/jarvis_platform/base.py +524 -202
- jarvis/jarvis_platform/human.py +7 -8
- jarvis/jarvis_platform/kimi.py +30 -36
- jarvis/jarvis_platform/openai.py +88 -25
- jarvis/jarvis_platform/registry.py +26 -10
- jarvis/jarvis_platform/tongyi.py +24 -25
- jarvis/jarvis_platform/yuanbao.py +32 -43
- jarvis/jarvis_platform_manager/main.py +66 -77
- jarvis/jarvis_platform_manager/service.py +8 -13
- jarvis/jarvis_rag/cli.py +53 -55
- jarvis/jarvis_rag/embedding_manager.py +13 -18
- jarvis/jarvis_rag/llm_interface.py +8 -9
- jarvis/jarvis_rag/query_rewriter.py +10 -21
- jarvis/jarvis_rag/rag_pipeline.py +24 -27
- jarvis/jarvis_rag/reranker.py +4 -5
- jarvis/jarvis_rag/retriever.py +28 -30
- jarvis/jarvis_sec/__init__.py +305 -0
- jarvis/jarvis_sec/agents.py +143 -0
- jarvis/jarvis_sec/analysis.py +276 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +139 -0
- jarvis/jarvis_sec/clustering.py +1439 -0
- jarvis/jarvis_sec/file_manager.py +427 -0
- jarvis/jarvis_sec/parsers.py +73 -0
- jarvis/jarvis_sec/prompts.py +268 -0
- jarvis/jarvis_sec/report.py +336 -0
- jarvis/jarvis_sec/review.py +453 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/utils.py +499 -0
- jarvis/jarvis_sec/verification.py +848 -0
- jarvis/jarvis_sec/workflow.py +226 -0
- jarvis/jarvis_smart_shell/main.py +38 -87
- jarvis/jarvis_stats/cli.py +2 -2
- jarvis/jarvis_stats/stats.py +8 -8
- jarvis/jarvis_stats/storage.py +15 -21
- jarvis/jarvis_stats/visualizer.py +1 -1
- jarvis/jarvis_tools/clear_memory.py +3 -20
- jarvis/jarvis_tools/cli/main.py +21 -23
- jarvis/jarvis_tools/edit_file.py +1019 -132
- jarvis/jarvis_tools/execute_script.py +83 -25
- jarvis/jarvis_tools/file_analyzer.py +6 -9
- jarvis/jarvis_tools/generate_new_tool.py +14 -21
- jarvis/jarvis_tools/lsp_client.py +1552 -0
- jarvis/jarvis_tools/methodology.py +2 -3
- jarvis/jarvis_tools/read_code.py +1736 -35
- jarvis/jarvis_tools/read_symbols.py +140 -0
- jarvis/jarvis_tools/read_webpage.py +12 -13
- jarvis/jarvis_tools/registry.py +427 -200
- jarvis/jarvis_tools/retrieve_memory.py +20 -19
- jarvis/jarvis_tools/rewrite_file.py +72 -158
- jarvis/jarvis_tools/save_memory.py +3 -15
- jarvis/jarvis_tools/search_web.py +18 -18
- jarvis/jarvis_tools/sub_agent.py +36 -43
- jarvis/jarvis_tools/sub_code_agent.py +25 -26
- jarvis/jarvis_tools/virtual_tty.py +55 -33
- jarvis/jarvis_utils/clipboard.py +7 -10
- jarvis/jarvis_utils/config.py +232 -45
- jarvis/jarvis_utils/embedding.py +8 -5
- jarvis/jarvis_utils/fzf.py +8 -8
- jarvis/jarvis_utils/git_utils.py +225 -36
- jarvis/jarvis_utils/globals.py +3 -3
- jarvis/jarvis_utils/http.py +1 -1
- jarvis/jarvis_utils/input.py +99 -48
- jarvis/jarvis_utils/jsonnet_compat.py +465 -0
- jarvis/jarvis_utils/methodology.py +52 -48
- jarvis/jarvis_utils/utils.py +819 -491
- jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
- jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
- jarvis/jarvis_agent/config.py +0 -92
- jarvis/jarvis_agent/edit_file_handler.py +0 -296
- jarvis/jarvis_platform/ai8.py +0 -332
- jarvis/jarvis_tools/ask_user.py +0 -54
- jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
- jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1108 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Jarvis 安全分析套件 —— Rust 启发式安全检查器
|
|
4
|
+
|
|
5
|
+
目标与范围:
|
|
6
|
+
- 聚焦 unsafe 使用、原始指针、错误处理、并发与 FFI 等基础安全问题。
|
|
7
|
+
- 提供可解释的启发式检测与置信度评估,面向 .rs 源文件。
|
|
8
|
+
|
|
9
|
+
输出约定:
|
|
10
|
+
- 返回 jarvis.jarvis_sec.workflow.Issue 列表(结构化,便于聚合评分与报告生成)。
|
|
11
|
+
- 置信度区间 [0,1];严重性(severity)分为 high/medium/low。
|
|
12
|
+
|
|
13
|
+
使用方式:
|
|
14
|
+
- from jarvis.jarvis_sec.checkers.rust_checker import analyze_rust_files
|
|
15
|
+
- issues = analyze_rust_files("./repo", ["src/lib.rs", "src/foo.rs"])
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import List, Sequence, Tuple
|
|
23
|
+
|
|
24
|
+
from ..types import Issue
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------
|
|
28
|
+
# 规则库(正则表达式)
|
|
29
|
+
# ---------------------------
|
|
30
|
+
|
|
31
|
+
RE_UNSAFE = re.compile(r"\bunsafe\b")
|
|
32
|
+
RE_RAW_PTR = re.compile(r"\*(?:mut|const)\s+[A-Za-z_]\w*") # 类型处的原始指针
|
|
33
|
+
RE_FORGET = re.compile(r"\bmem::forget\b")
|
|
34
|
+
RE_TRANSMUTE = re.compile(r"\bmem::transmute\b")
|
|
35
|
+
RE_MAYBE_UNINIT = re.compile(r"\bMaybeUninit\b")
|
|
36
|
+
RE_ASSUME_INIT = re.compile(r"\bassume_init\s*\(")
|
|
37
|
+
|
|
38
|
+
RE_UNWRAP = re.compile(r"\bunwrap\s*\(", re.IGNORECASE)
|
|
39
|
+
RE_EXPECT = re.compile(r"\bexpect\s*\(", re.IGNORECASE)
|
|
40
|
+
RE_EXTERN_C = re.compile(r'extern\s+"C"')
|
|
41
|
+
RE_UNSAFE_IMPL = re.compile(r"\bunsafe\s+impl\s+(?:Send|Sync)\b|\bimpl\s+unsafe\s+(?:Send|Sync)\b", re.IGNORECASE)
|
|
42
|
+
|
|
43
|
+
# 结果忽略/下划线绑定(可能忽略错误)
|
|
44
|
+
RE_LET_UNDERSCORE = re.compile(r"\blet\s+_+\s*=\s*.+;")
|
|
45
|
+
RE_MATCH_IGNORE_ERR = re.compile(r"\.ok\s*\(\s*\)|\.ok\?\s*;|\._?\s*=\s*.+\.err\(\s*\)", re.IGNORECASE) # 粗略
|
|
46
|
+
|
|
47
|
+
# 类型转换相关
|
|
48
|
+
RE_AS_CAST = re.compile(r"\b\w+\s+as\s+[A-Za-z_]\w*", re.IGNORECASE)
|
|
49
|
+
RE_FROM_RAW_PARTS = re.compile(r"\bfrom_raw_parts\s*\(")
|
|
50
|
+
RE_FROM_RAW = re.compile(r"\bfrom_raw\s*\(")
|
|
51
|
+
RE_INTO_RAW = re.compile(r"\binto_raw\s*\(")
|
|
52
|
+
|
|
53
|
+
# 内存操作相关
|
|
54
|
+
RE_GET_UNCHECKED = re.compile(r"\.get_unchecked\s*\(")
|
|
55
|
+
RE_GET_UNCHECKED_MUT = re.compile(r"\.get_unchecked_mut\s*\(")
|
|
56
|
+
RE_OFFSET = re.compile(r"\.offset\s*\(")
|
|
57
|
+
RE_ADD = re.compile(r"\.add\s*\(")
|
|
58
|
+
RE_COPY_NONOVERLAPPING = re.compile(r"\bcopy_nonoverlapping\s*\(")
|
|
59
|
+
RE_COPY = re.compile(r"\bcopy\s*\(")
|
|
60
|
+
RE_WRITE = re.compile(r"\bwrite\s*\(")
|
|
61
|
+
RE_READ = re.compile(r"\bread\s*\(")
|
|
62
|
+
RE_MANUALLY_DROP = re.compile(r"\bManuallyDrop\b")
|
|
63
|
+
|
|
64
|
+
# 并发相关
|
|
65
|
+
RE_ARC = re.compile(r"\bArc\s*<", re.IGNORECASE)
|
|
66
|
+
RE_MUTEX = re.compile(r"\bMutex\s*<", re.IGNORECASE)
|
|
67
|
+
RE_RWLOCK = re.compile(r"\bRwLock\s*<", re.IGNORECASE)
|
|
68
|
+
RE_REFCELL = re.compile(r"\bRefCell\s*<", re.IGNORECASE)
|
|
69
|
+
RE_CELL = re.compile(r"\bCell\s*<", re.IGNORECASE)
|
|
70
|
+
|
|
71
|
+
# 错误处理相关
|
|
72
|
+
RE_PANIC = re.compile(r"\bpanic!\s*\(")
|
|
73
|
+
RE_UNREACHABLE = re.compile(r"\bunreachable!\s*\(")
|
|
74
|
+
|
|
75
|
+
# FFI 相关
|
|
76
|
+
RE_CSTRING = re.compile(r"\bCString\b")
|
|
77
|
+
RE_CSTR = re.compile(r"\bCStr\b")
|
|
78
|
+
RE_FFI_PTR_DEREF = re.compile(r"\*[A-Za-z_]\w*\s*[\[\.]") # 原始指针解引用
|
|
79
|
+
|
|
80
|
+
# 生命周期相关
|
|
81
|
+
RE_LIFETIME_PARAM = re.compile(r"<['][a-z]\w*>") # 生命周期参数
|
|
82
|
+
RE_STATIC_LIFETIME = re.compile(r"&'static\s+")
|
|
83
|
+
|
|
84
|
+
# 其他不安全模式
|
|
85
|
+
RE_UNINIT = re.compile(r"\buninit\s*\(")
|
|
86
|
+
RE_ZEROED = re.compile(r"\bzeroed\s*\(")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ---------------------------
|
|
90
|
+
# 公共工具
|
|
91
|
+
# ---------------------------
|
|
92
|
+
|
|
93
|
+
def _safe_line(lines: Sequence[str], idx: int) -> str:
|
|
94
|
+
if 1 <= idx <= len(lines):
|
|
95
|
+
return lines[idx - 1]
|
|
96
|
+
return ""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _strip_line(s: str, max_len: int = 200) -> str:
|
|
100
|
+
s = s.strip().replace("\t", " ")
|
|
101
|
+
return s if len(s) <= max_len else s[: max_len - 3] + "..."
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _window(lines: Sequence[str], center: int, before: int = 3, after: int = 3) -> List[Tuple[int, str]]:
|
|
105
|
+
start = max(1, center - before)
|
|
106
|
+
end = min(len(lines), center + after)
|
|
107
|
+
return [(i, _safe_line(lines, i)) for i in range(start, end + 1)]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _remove_comments_preserve_strings(text: str) -> str:
|
|
111
|
+
"""
|
|
112
|
+
移除 Rust 源码中的注释(//、///、//!、/* */、/** */、/*! */),保留字符串与字符字面量内容;
|
|
113
|
+
为了保持行号与窗口定位稳定,注释内容会被空格替换并保留换行符。
|
|
114
|
+
说明:本函数为启发式实现,旨在降低"注释中的API命中"造成的误报。
|
|
115
|
+
"""
|
|
116
|
+
res: list[str] = []
|
|
117
|
+
i = 0
|
|
118
|
+
n = len(text)
|
|
119
|
+
in_sl_comment = False # //
|
|
120
|
+
in_bl_comment = False # /* */
|
|
121
|
+
in_string = False # "
|
|
122
|
+
in_char = False # '
|
|
123
|
+
in_raw_string = False # r"..." 或 r#"..."#
|
|
124
|
+
raw_string_hash_count = 0 # 原始字符串的 # 数量
|
|
125
|
+
escape = False
|
|
126
|
+
|
|
127
|
+
while i < n:
|
|
128
|
+
ch = text[i]
|
|
129
|
+
nxt = text[i + 1] if i + 1 < n else ""
|
|
130
|
+
nxt2 = text[i + 2] if i + 2 < n else ""
|
|
131
|
+
|
|
132
|
+
if in_sl_comment:
|
|
133
|
+
# 单行注释直到换行结束
|
|
134
|
+
if ch == "\n":
|
|
135
|
+
in_sl_comment = False
|
|
136
|
+
res.append(ch)
|
|
137
|
+
else:
|
|
138
|
+
# 用空格占位,保持列数
|
|
139
|
+
res.append(" ")
|
|
140
|
+
i += 1
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
if in_bl_comment:
|
|
144
|
+
# 多行注释直到 */
|
|
145
|
+
if ch == "*" and nxt == "/":
|
|
146
|
+
in_bl_comment = False
|
|
147
|
+
res.append(" ")
|
|
148
|
+
res.append(" ")
|
|
149
|
+
i += 2
|
|
150
|
+
else:
|
|
151
|
+
# 注释体内保留换行,其余替换为空格
|
|
152
|
+
res.append("\n" if ch == "\n" else " ")
|
|
153
|
+
i += 1
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
# 处理原始字符串(r"..." 或 r#"..."#)
|
|
157
|
+
if in_raw_string:
|
|
158
|
+
if ch == '"':
|
|
159
|
+
# 检查是否有足够的 # 来结束原始字符串
|
|
160
|
+
hash_count = 0
|
|
161
|
+
j = i - 1
|
|
162
|
+
while j >= 0 and text[j] == '#':
|
|
163
|
+
hash_count += 1
|
|
164
|
+
j -= 1
|
|
165
|
+
if hash_count == raw_string_hash_count:
|
|
166
|
+
in_raw_string = False
|
|
167
|
+
raw_string_hash_count = 0
|
|
168
|
+
res.append(ch)
|
|
169
|
+
i += 1
|
|
170
|
+
else:
|
|
171
|
+
res.append(ch)
|
|
172
|
+
i += 1
|
|
173
|
+
else:
|
|
174
|
+
res.append(ch)
|
|
175
|
+
i += 1
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
# 非注释态下,处理字符串与字符字面量
|
|
179
|
+
if in_string:
|
|
180
|
+
res.append(ch)
|
|
181
|
+
if escape:
|
|
182
|
+
escape = False
|
|
183
|
+
elif ch == "\\":
|
|
184
|
+
escape = True
|
|
185
|
+
elif ch == '"':
|
|
186
|
+
in_string = False
|
|
187
|
+
i += 1
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
if in_char:
|
|
191
|
+
res.append(ch)
|
|
192
|
+
if escape:
|
|
193
|
+
escape = False
|
|
194
|
+
elif ch == "\\":
|
|
195
|
+
escape = True
|
|
196
|
+
elif ch == "'":
|
|
197
|
+
in_char = False
|
|
198
|
+
i += 1
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
# 进入注释判定(需不在字符串/字符字面量中)
|
|
202
|
+
# 单行注释://、///、//!
|
|
203
|
+
if ch == "/" and nxt == "/":
|
|
204
|
+
in_sl_comment = True
|
|
205
|
+
res.append(" ")
|
|
206
|
+
res.append(" ")
|
|
207
|
+
i += 2
|
|
208
|
+
continue
|
|
209
|
+
# 多行注释:/*、/**、/*!
|
|
210
|
+
if ch == "/" and nxt == "*":
|
|
211
|
+
in_bl_comment = True
|
|
212
|
+
res.append(" ")
|
|
213
|
+
res.append(" ")
|
|
214
|
+
i += 2
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
# 进入原始字符串:r"..." 或 r#"..."# 或 br"..." 或 b"..."(字节字符串)
|
|
218
|
+
# 检查是否是 r" 或 br"
|
|
219
|
+
if ch == "r" and nxt == '"':
|
|
220
|
+
# 简单原始字符串:r"
|
|
221
|
+
in_raw_string = True
|
|
222
|
+
raw_string_hash_count = 0
|
|
223
|
+
res.append(ch)
|
|
224
|
+
res.append(nxt)
|
|
225
|
+
i += 2
|
|
226
|
+
continue
|
|
227
|
+
if ch == "b" and nxt == "r" and nxt2 == '"':
|
|
228
|
+
# 字节原始字符串:br"
|
|
229
|
+
in_raw_string = True
|
|
230
|
+
raw_string_hash_count = 0
|
|
231
|
+
res.append(ch)
|
|
232
|
+
res.append(nxt)
|
|
233
|
+
res.append(nxt2)
|
|
234
|
+
i += 3
|
|
235
|
+
continue
|
|
236
|
+
if ch == "r" and nxt == "#":
|
|
237
|
+
# 带 # 的原始字符串:r#"
|
|
238
|
+
# 计算 # 的数量
|
|
239
|
+
raw_string_hash_count = 1
|
|
240
|
+
j = i + 1
|
|
241
|
+
while j < n and text[j] == '#':
|
|
242
|
+
raw_string_hash_count += 1
|
|
243
|
+
j += 1
|
|
244
|
+
if j < n and text[j] == '"':
|
|
245
|
+
in_raw_string = True
|
|
246
|
+
# 输出 r 和所有 # 和 "
|
|
247
|
+
for k in range(i, j + 1):
|
|
248
|
+
res.append(text[k])
|
|
249
|
+
i = j + 1
|
|
250
|
+
continue
|
|
251
|
+
if ch == "b" and nxt == "r" and nxt2 == "#":
|
|
252
|
+
# 字节原始字符串:br#"
|
|
253
|
+
raw_string_hash_count = 1
|
|
254
|
+
j = i + 2
|
|
255
|
+
while j < n and text[j] == '#':
|
|
256
|
+
raw_string_hash_count += 1
|
|
257
|
+
j += 1
|
|
258
|
+
if j < n and text[j] == '"':
|
|
259
|
+
in_raw_string = True
|
|
260
|
+
# 输出 br 和所有 # 和 "
|
|
261
|
+
res.append(ch)
|
|
262
|
+
res.append(nxt)
|
|
263
|
+
for k in range(i + 2, j + 1):
|
|
264
|
+
res.append(text[k])
|
|
265
|
+
i = j + 1
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
# 进入字符串/字符字面量
|
|
269
|
+
# 处理 b"..."(字节字符串,不是原始字符串,需要在原始字符串检测之后)
|
|
270
|
+
if ch == "b" and nxt == '"' and nxt2 != "r":
|
|
271
|
+
in_string = True
|
|
272
|
+
res.append(ch)
|
|
273
|
+
res.append(nxt)
|
|
274
|
+
i += 2
|
|
275
|
+
continue
|
|
276
|
+
if ch == '"':
|
|
277
|
+
in_string = True
|
|
278
|
+
res.append(ch)
|
|
279
|
+
i += 1
|
|
280
|
+
continue
|
|
281
|
+
if ch == "'":
|
|
282
|
+
in_char = True
|
|
283
|
+
res.append(ch)
|
|
284
|
+
i += 1
|
|
285
|
+
continue
|
|
286
|
+
|
|
287
|
+
# 普通字符
|
|
288
|
+
res.append(ch)
|
|
289
|
+
i += 1
|
|
290
|
+
|
|
291
|
+
return "".join(res)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _mask_strings_preserve_len(text: str) -> str:
|
|
295
|
+
"""
|
|
296
|
+
将字符串与字符字面量内部内容替换为空格,保留引号与换行,保持长度与行号不变。
|
|
297
|
+
用于在扫描通用 API 模式时避免误将字符串中的片段(如 "unsafe(")当作代码。
|
|
298
|
+
注意:此函数不移除注释,请在已移除注释的文本上调用。
|
|
299
|
+
"""
|
|
300
|
+
res: list[str] = []
|
|
301
|
+
in_string = False
|
|
302
|
+
in_char = False
|
|
303
|
+
in_raw_string = False
|
|
304
|
+
raw_string_hash_count = 0
|
|
305
|
+
escape = False
|
|
306
|
+
|
|
307
|
+
i = 0
|
|
308
|
+
n = len(text)
|
|
309
|
+
|
|
310
|
+
while i < n:
|
|
311
|
+
ch = text[i]
|
|
312
|
+
nxt = text[i + 1] if i + 1 < n else ""
|
|
313
|
+
nxt2 = text[i + 2] if i + 2 < n else ""
|
|
314
|
+
|
|
315
|
+
if in_raw_string:
|
|
316
|
+
if ch == '"':
|
|
317
|
+
# 检查是否有足够的 # 来结束原始字符串
|
|
318
|
+
hash_count = 0
|
|
319
|
+
j = i - 1
|
|
320
|
+
while j >= 0 and text[j] == '#':
|
|
321
|
+
hash_count += 1
|
|
322
|
+
j -= 1
|
|
323
|
+
if hash_count == raw_string_hash_count:
|
|
324
|
+
in_raw_string = False
|
|
325
|
+
raw_string_hash_count = 0
|
|
326
|
+
res.append('"')
|
|
327
|
+
i += 1
|
|
328
|
+
elif ch == "\n":
|
|
329
|
+
res.append("\n")
|
|
330
|
+
i += 1
|
|
331
|
+
else:
|
|
332
|
+
res.append(" ")
|
|
333
|
+
i += 1
|
|
334
|
+
else:
|
|
335
|
+
if ch == "\n":
|
|
336
|
+
res.append("\n")
|
|
337
|
+
else:
|
|
338
|
+
res.append(" ")
|
|
339
|
+
i += 1
|
|
340
|
+
continue
|
|
341
|
+
|
|
342
|
+
if in_string:
|
|
343
|
+
if escape:
|
|
344
|
+
# 保留转义反斜杠为两字符(反斜杠+空格),以不破坏列对齐过多
|
|
345
|
+
res.append(" ")
|
|
346
|
+
escape = False
|
|
347
|
+
elif ch == "\\":
|
|
348
|
+
res.append("\\")
|
|
349
|
+
escape = True
|
|
350
|
+
elif ch == '"':
|
|
351
|
+
res.append('"')
|
|
352
|
+
in_string = False
|
|
353
|
+
elif ch == "\n":
|
|
354
|
+
res.append("\n")
|
|
355
|
+
else:
|
|
356
|
+
res.append(" ")
|
|
357
|
+
i += 1
|
|
358
|
+
continue
|
|
359
|
+
|
|
360
|
+
if in_char:
|
|
361
|
+
if escape:
|
|
362
|
+
res.append(" ")
|
|
363
|
+
escape = False
|
|
364
|
+
elif ch == "\\":
|
|
365
|
+
res.append("\\")
|
|
366
|
+
escape = True
|
|
367
|
+
elif ch == "'":
|
|
368
|
+
res.append("'")
|
|
369
|
+
in_char = False
|
|
370
|
+
elif ch == "\n":
|
|
371
|
+
res.append("\n")
|
|
372
|
+
else:
|
|
373
|
+
res.append(" ")
|
|
374
|
+
i += 1
|
|
375
|
+
continue
|
|
376
|
+
|
|
377
|
+
# 检测原始字符串开始:r"..." 或 r#"..."# 或 br"..." 或 b"..."(字节字符串)
|
|
378
|
+
if ch == "r" and nxt == '"':
|
|
379
|
+
# 简单原始字符串:r"
|
|
380
|
+
in_raw_string = True
|
|
381
|
+
raw_string_hash_count = 0
|
|
382
|
+
res.append(ch)
|
|
383
|
+
res.append(nxt)
|
|
384
|
+
i += 2
|
|
385
|
+
continue
|
|
386
|
+
if ch == "b" and nxt == "r" and nxt2 == '"':
|
|
387
|
+
# 字节原始字符串:br"
|
|
388
|
+
in_raw_string = True
|
|
389
|
+
raw_string_hash_count = 0
|
|
390
|
+
res.append(ch)
|
|
391
|
+
res.append(nxt)
|
|
392
|
+
res.append(nxt2)
|
|
393
|
+
i += 3
|
|
394
|
+
continue
|
|
395
|
+
if ch == "r" and nxt == "#":
|
|
396
|
+
# 带 # 的原始字符串:r#"
|
|
397
|
+
raw_string_hash_count = 1
|
|
398
|
+
j = i + 1
|
|
399
|
+
while j < n and text[j] == '#':
|
|
400
|
+
raw_string_hash_count += 1
|
|
401
|
+
j += 1
|
|
402
|
+
if j < n and text[j] == '"':
|
|
403
|
+
in_raw_string = True
|
|
404
|
+
# 输出 r 和所有 # 和 "
|
|
405
|
+
for k in range(i, j + 1):
|
|
406
|
+
res.append(text[k])
|
|
407
|
+
i = j + 1
|
|
408
|
+
continue
|
|
409
|
+
if ch == "b" and nxt == "r" and nxt2 == "#":
|
|
410
|
+
# 字节原始字符串:br#"
|
|
411
|
+
raw_string_hash_count = 1
|
|
412
|
+
j = i + 2
|
|
413
|
+
while j < n and text[j] == '#':
|
|
414
|
+
raw_string_hash_count += 1
|
|
415
|
+
j += 1
|
|
416
|
+
if j < n and text[j] == '"':
|
|
417
|
+
in_raw_string = True
|
|
418
|
+
# 输出 br 和所有 # 和 "
|
|
419
|
+
res.append(ch)
|
|
420
|
+
res.append(nxt)
|
|
421
|
+
for k in range(i + 2, j + 1):
|
|
422
|
+
res.append(text[k])
|
|
423
|
+
i = j + 1
|
|
424
|
+
continue
|
|
425
|
+
|
|
426
|
+
# 处理 b"..."(字节字符串,不是原始字符串,需要在原始字符串检测之后)
|
|
427
|
+
if ch == "b" and nxt == '"' and nxt2 != "r":
|
|
428
|
+
in_string = True
|
|
429
|
+
res.append(ch)
|
|
430
|
+
res.append(nxt)
|
|
431
|
+
i += 2
|
|
432
|
+
continue
|
|
433
|
+
if ch == '"':
|
|
434
|
+
in_string = True
|
|
435
|
+
res.append('"')
|
|
436
|
+
i += 1
|
|
437
|
+
continue
|
|
438
|
+
if ch == "'":
|
|
439
|
+
in_char = True
|
|
440
|
+
res.append("'")
|
|
441
|
+
i += 1
|
|
442
|
+
continue
|
|
443
|
+
|
|
444
|
+
res.append(ch)
|
|
445
|
+
i += 1
|
|
446
|
+
|
|
447
|
+
return "".join(res)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _has_safety_comment_around(lines: Sequence[str], line_no: int, radius: int = 5) -> bool:
|
|
451
|
+
"""
|
|
452
|
+
Rust 社区约定在 unsafe 附近写 SAFETY: 注释说明前置条件。
|
|
453
|
+
如存在,适当降低置信度。
|
|
454
|
+
"""
|
|
455
|
+
for _, s in _window(lines, line_no, before=radius, after=radius):
|
|
456
|
+
# 支持英文与中文“SAFETY/安全性”标注,兼容全角冒号
|
|
457
|
+
if (
|
|
458
|
+
"SAFETY:" in s
|
|
459
|
+
or "Safety:" in s
|
|
460
|
+
or "safety:" in s
|
|
461
|
+
or "SAFETY:" in s # 全角冒号
|
|
462
|
+
or "安全性" in s
|
|
463
|
+
or "安全:" in s
|
|
464
|
+
or "安全:" in s
|
|
465
|
+
):
|
|
466
|
+
return True
|
|
467
|
+
return False
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _in_test_context(lines: Sequence[str], line_no: int, radius: int = 20) -> bool:
|
|
471
|
+
"""
|
|
472
|
+
近邻出现 #[test] 或 mod tests { ... } 等,可能处于测试上下文,适度降低严重度。
|
|
473
|
+
"""
|
|
474
|
+
for _, s in _window(lines, line_no, before=radius, after=radius):
|
|
475
|
+
if "#[test]" in s or "cfg(test)" in s or re.search(r"\bmod\s+tests\b", s):
|
|
476
|
+
return True
|
|
477
|
+
return False
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _severity_from_confidence(conf: float) -> str:
|
|
481
|
+
if conf >= 0.8:
|
|
482
|
+
return "high"
|
|
483
|
+
if conf >= 0.6:
|
|
484
|
+
return "medium"
|
|
485
|
+
return "low"
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
# ---------------------------
|
|
489
|
+
# 规则实现
|
|
490
|
+
# ---------------------------
|
|
491
|
+
|
|
492
|
+
def _rule_unsafe(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
493
|
+
issues: List[Issue] = []
|
|
494
|
+
for idx, s in enumerate(lines, start=1):
|
|
495
|
+
# 避免对 unsafe impl 重复上报,由专门规则处理
|
|
496
|
+
if not RE_UNSAFE.search(s) or RE_UNSAFE_IMPL.search(s):
|
|
497
|
+
continue
|
|
498
|
+
conf = 0.8
|
|
499
|
+
if _has_safety_comment_around(lines, idx, radius=5):
|
|
500
|
+
conf -= 0.1
|
|
501
|
+
if _in_test_context(lines, idx):
|
|
502
|
+
conf -= 0.05
|
|
503
|
+
conf = max(0.5, min(0.95, conf))
|
|
504
|
+
issues.append(
|
|
505
|
+
Issue(
|
|
506
|
+
language="rust",
|
|
507
|
+
category="unsafe_usage",
|
|
508
|
+
pattern="unsafe",
|
|
509
|
+
file=relpath,
|
|
510
|
+
line=idx,
|
|
511
|
+
evidence=_strip_line(s),
|
|
512
|
+
description="存在 unsafe 代码块/标识,需证明内存/别名/生命周期安全性。",
|
|
513
|
+
suggestion="将不安全操作封装在最小作用域内,并提供 SAFETY 注释说明前置条件与不变式。",
|
|
514
|
+
confidence=conf,
|
|
515
|
+
severity=_severity_from_confidence(conf),
|
|
516
|
+
)
|
|
517
|
+
)
|
|
518
|
+
return issues
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _rule_raw_pointer(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
522
|
+
issues: List[Issue] = []
|
|
523
|
+
for idx, s in enumerate(lines, start=1):
|
|
524
|
+
if not RE_RAW_PTR.search(s):
|
|
525
|
+
continue
|
|
526
|
+
conf = 0.75
|
|
527
|
+
if _has_safety_comment_around(lines, idx):
|
|
528
|
+
conf -= 0.1
|
|
529
|
+
if _in_test_context(lines, idx):
|
|
530
|
+
conf -= 0.05
|
|
531
|
+
conf = max(0.5, min(0.9, conf))
|
|
532
|
+
issues.append(
|
|
533
|
+
Issue(
|
|
534
|
+
language="rust",
|
|
535
|
+
category="unsafe_usage",
|
|
536
|
+
pattern="raw_pointer",
|
|
537
|
+
file=relpath,
|
|
538
|
+
line=idx,
|
|
539
|
+
evidence=_strip_line(s),
|
|
540
|
+
description="出现原始指针(*mut/*const),可能绕过借用/生命周期检查,带来未定义行为风险。",
|
|
541
|
+
suggestion="优先使用引用/智能指针;必须使用原始指针时,严格证明无别名、对齐与生命周期安全。",
|
|
542
|
+
confidence=conf,
|
|
543
|
+
severity=_severity_from_confidence(conf),
|
|
544
|
+
)
|
|
545
|
+
)
|
|
546
|
+
return issues
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _rule_transmute(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
550
|
+
issues: List[Issue] = []
|
|
551
|
+
for idx, s in enumerate(lines, start=1):
|
|
552
|
+
if not RE_TRANSMUTE.search(s):
|
|
553
|
+
continue
|
|
554
|
+
conf = 0.85
|
|
555
|
+
if _has_safety_comment_around(lines, idx):
|
|
556
|
+
conf -= 0.1
|
|
557
|
+
conf = max(0.6, min(0.95, conf))
|
|
558
|
+
issues.append(
|
|
559
|
+
Issue(
|
|
560
|
+
language="rust",
|
|
561
|
+
category="unsafe_usage",
|
|
562
|
+
pattern="mem::transmute",
|
|
563
|
+
file=relpath,
|
|
564
|
+
line=idx,
|
|
565
|
+
evidence=_strip_line(s),
|
|
566
|
+
description="使用 mem::transmute 进行类型转换,若未严格保证布局/对齐/生命周期,将导致未定义行为。",
|
|
567
|
+
suggestion="避免使用 transmute,优先采用安全转换或 bytemuck 等受审计抽象;必须使用时严格注明不变式。",
|
|
568
|
+
confidence=conf,
|
|
569
|
+
severity=_severity_from_confidence(conf),
|
|
570
|
+
)
|
|
571
|
+
)
|
|
572
|
+
return issues
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _rule_maybe_uninit(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
576
|
+
"""
|
|
577
|
+
MaybeUninit + assume_init 组合常见于优化/FFI,需特别小心初始化与有效性。
|
|
578
|
+
"""
|
|
579
|
+
issues: List[Issue] = []
|
|
580
|
+
for idx, s in enumerate(lines, start=1):
|
|
581
|
+
if not (RE_MAYBE_UNINIT.search(s) or RE_ASSUME_INIT.search(s)):
|
|
582
|
+
continue
|
|
583
|
+
conf = 0.7
|
|
584
|
+
# 若在邻近几行同时出现 MaybeUninit 与 assume_init,风险更高
|
|
585
|
+
win_text = " ".join(t for _, t in _window(lines, idx, before=3, after=3))
|
|
586
|
+
if RE_MAYBE_UNINIT.search(win_text) and RE_ASSUME_INIT.search(win_text):
|
|
587
|
+
conf += 0.1
|
|
588
|
+
if _has_safety_comment_around(lines, idx):
|
|
589
|
+
conf -= 0.05
|
|
590
|
+
conf = max(0.5, min(0.9, conf))
|
|
591
|
+
issues.append(
|
|
592
|
+
Issue(
|
|
593
|
+
language="rust",
|
|
594
|
+
category="unsafe_usage",
|
|
595
|
+
pattern="MaybeUninit/assume_init",
|
|
596
|
+
file=relpath,
|
|
597
|
+
line=idx,
|
|
598
|
+
evidence=_strip_line(s),
|
|
599
|
+
description="使用 MaybeUninit/assume_init 需保证正确初始化与读取顺序,否则可能导致未定义行为。",
|
|
600
|
+
suggestion="确保初始化前不读取;使用更安全的构造函数;在 SAFETY 注释中说明前置条件。",
|
|
601
|
+
confidence=conf,
|
|
602
|
+
severity=_severity_from_confidence(conf),
|
|
603
|
+
)
|
|
604
|
+
)
|
|
605
|
+
return issues
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def _rule_unwrap_expect(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
609
|
+
issues: List[Issue] = []
|
|
610
|
+
for idx, s in enumerate(lines, start=1):
|
|
611
|
+
if not (RE_UNWRAP.search(s) or RE_EXPECT.search(s)):
|
|
612
|
+
continue
|
|
613
|
+
conf = 0.65
|
|
614
|
+
if _in_test_context(lines, idx):
|
|
615
|
+
conf -= 0.1
|
|
616
|
+
conf = max(0.45, min(0.8, conf))
|
|
617
|
+
issues.append(
|
|
618
|
+
Issue(
|
|
619
|
+
language="rust",
|
|
620
|
+
category="error_handling",
|
|
621
|
+
pattern="unwrap/expect",
|
|
622
|
+
file=relpath,
|
|
623
|
+
line=idx,
|
|
624
|
+
evidence=_strip_line(s),
|
|
625
|
+
description="直接 unwrap/expect 可能在错误条件下 panic,缺少健壮的错误处理路径。",
|
|
626
|
+
suggestion="使用 ? 传播错误或 match 显式处理;为关键路径提供错误上下文与恢复策略。",
|
|
627
|
+
confidence=conf,
|
|
628
|
+
severity=_severity_from_confidence(conf),
|
|
629
|
+
)
|
|
630
|
+
)
|
|
631
|
+
return issues
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def _rule_extern_c(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
635
|
+
issues: List[Issue] = []
|
|
636
|
+
for idx, s in enumerate(lines, start=1):
|
|
637
|
+
if not RE_EXTERN_C.search(s):
|
|
638
|
+
continue
|
|
639
|
+
conf = 0.7
|
|
640
|
+
if _has_safety_comment_around(lines, idx):
|
|
641
|
+
conf -= 0.05
|
|
642
|
+
conf = max(0.5, min(0.85, conf))
|
|
643
|
+
issues.append(
|
|
644
|
+
Issue(
|
|
645
|
+
language="rust",
|
|
646
|
+
category="ffi",
|
|
647
|
+
pattern='extern "C"',
|
|
648
|
+
file=relpath,
|
|
649
|
+
line=idx,
|
|
650
|
+
evidence=_strip_line(s),
|
|
651
|
+
description="FFI 边界需要确保指针有效性、长度/对齐、生命周期、线程安全等约束,否则可能产生未定义行为。",
|
|
652
|
+
suggestion="在 FFI 边界进行严格的参数校验与安全封装;在 SAFETY 注释中记录不变式与约束。",
|
|
653
|
+
confidence=conf,
|
|
654
|
+
severity=_severity_from_confidence(conf),
|
|
655
|
+
)
|
|
656
|
+
)
|
|
657
|
+
return issues
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def _rule_unsafe_impl(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
661
|
+
issues: List[Issue] = []
|
|
662
|
+
for idx, s in enumerate(lines, start=1):
|
|
663
|
+
if not RE_UNSAFE_IMPL.search(s):
|
|
664
|
+
continue
|
|
665
|
+
conf = 0.8
|
|
666
|
+
if _has_safety_comment_around(lines, idx):
|
|
667
|
+
conf -= 0.1
|
|
668
|
+
conf = max(0.6, min(0.95, conf))
|
|
669
|
+
issues.append(
|
|
670
|
+
Issue(
|
|
671
|
+
language="rust",
|
|
672
|
+
category="concurrency",
|
|
673
|
+
pattern="unsafe_impl_Send_or_Sync",
|
|
674
|
+
file=relpath,
|
|
675
|
+
line=idx,
|
|
676
|
+
evidence=_strip_line(s),
|
|
677
|
+
description="手写 unsafe impl Send/Sync 可能破坏并发内存模型保证,带来数据竞争风险。",
|
|
678
|
+
suggestion="避免手写 unsafe impl;必要时严格证明线程安全前置条件并最小化不安全区域。",
|
|
679
|
+
confidence=conf,
|
|
680
|
+
severity=_severity_from_confidence(conf),
|
|
681
|
+
)
|
|
682
|
+
)
|
|
683
|
+
return issues
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def _rule_ignore_result(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
687
|
+
"""
|
|
688
|
+
启发式:使用 let _ = xxx; 或 .ok() 等可能忽略错误。
|
|
689
|
+
该规则误报可能较高,因此置信度较低。
|
|
690
|
+
"""
|
|
691
|
+
issues: List[Issue] = []
|
|
692
|
+
for idx, s in enumerate(lines, start=1):
|
|
693
|
+
if not (RE_LET_UNDERSCORE.search(s) or RE_MATCH_IGNORE_ERR.search(s)):
|
|
694
|
+
continue
|
|
695
|
+
conf = 0.55
|
|
696
|
+
if _in_test_context(lines, idx):
|
|
697
|
+
conf -= 0.1
|
|
698
|
+
conf = max(0.4, min(0.7, conf))
|
|
699
|
+
issues.append(
|
|
700
|
+
Issue(
|
|
701
|
+
language="rust",
|
|
702
|
+
category="error_handling",
|
|
703
|
+
pattern="ignored_result",
|
|
704
|
+
file=relpath,
|
|
705
|
+
line=idx,
|
|
706
|
+
evidence=_strip_line(s),
|
|
707
|
+
description="可能忽略了返回的错误结果,导致失败未被处理。",
|
|
708
|
+
suggestion="显式处理 Result(? 传播或 match),确保错误路径涵盖资源回收与日志记录。",
|
|
709
|
+
confidence=conf,
|
|
710
|
+
severity=_severity_from_confidence(conf),
|
|
711
|
+
)
|
|
712
|
+
)
|
|
713
|
+
return issues
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def _rule_forget(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
717
|
+
issues: List[Issue] = []
|
|
718
|
+
for idx, s in enumerate(lines, start=1):
|
|
719
|
+
if not RE_FORGET.search(s):
|
|
720
|
+
continue
|
|
721
|
+
conf = 0.75
|
|
722
|
+
if _has_safety_comment_around(lines, idx):
|
|
723
|
+
conf -= 0.1
|
|
724
|
+
if _in_test_context(lines, idx):
|
|
725
|
+
conf -= 0.05
|
|
726
|
+
conf = max(0.5, min(0.9, conf))
|
|
727
|
+
issues.append(
|
|
728
|
+
Issue(
|
|
729
|
+
language="rust",
|
|
730
|
+
category="resource_management",
|
|
731
|
+
pattern="mem::forget",
|
|
732
|
+
file=relpath,
|
|
733
|
+
line=idx,
|
|
734
|
+
evidence=_strip_line(s),
|
|
735
|
+
description="使用 mem::forget 会跳过 Drop 导致资源泄漏,若错误使用可能破坏不变式或造成泄漏。",
|
|
736
|
+
suggestion="避免无必要的 mem::forget;如需抑制 Drop,优先使用 ManuallyDrop 或设计更安全的所有权转移。",
|
|
737
|
+
confidence=conf,
|
|
738
|
+
severity=_severity_from_confidence(conf),
|
|
739
|
+
)
|
|
740
|
+
)
|
|
741
|
+
return issues
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
def _rule_get_unchecked(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
745
|
+
"""
|
|
746
|
+
检测 get_unchecked/get_unchecked_mut 的使用,这些方法绕过边界检查。
|
|
747
|
+
"""
|
|
748
|
+
issues: List[Issue] = []
|
|
749
|
+
for idx, s in enumerate(lines, start=1):
|
|
750
|
+
if not (RE_GET_UNCHECKED.search(s) or RE_GET_UNCHECKED_MUT.search(s)):
|
|
751
|
+
continue
|
|
752
|
+
conf = 0.8
|
|
753
|
+
if _has_safety_comment_around(lines, idx):
|
|
754
|
+
conf -= 0.1
|
|
755
|
+
if _in_test_context(lines, idx):
|
|
756
|
+
conf -= 0.05
|
|
757
|
+
conf = max(0.6, min(0.95, conf))
|
|
758
|
+
issues.append(
|
|
759
|
+
Issue(
|
|
760
|
+
language="rust",
|
|
761
|
+
category="unsafe_usage",
|
|
762
|
+
pattern="get_unchecked",
|
|
763
|
+
file=relpath,
|
|
764
|
+
line=idx,
|
|
765
|
+
evidence=_strip_line(s),
|
|
766
|
+
description="使用 get_unchecked/get_unchecked_mut 绕过边界检查,若索引无效将导致未定义行为。",
|
|
767
|
+
suggestion="优先使用安全的索引方法([] 或 get);必须使用 get_unchecked 时,在 SAFETY 注释中证明索引有效性。",
|
|
768
|
+
confidence=conf,
|
|
769
|
+
severity=_severity_from_confidence(conf),
|
|
770
|
+
)
|
|
771
|
+
)
|
|
772
|
+
return issues
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def _rule_pointer_arithmetic(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
776
|
+
"""
|
|
777
|
+
检测指针算术操作(offset/add),这些操作可能产生无效指针。
|
|
778
|
+
"""
|
|
779
|
+
issues: List[Issue] = []
|
|
780
|
+
for idx, s in enumerate(lines, start=1):
|
|
781
|
+
if not (RE_OFFSET.search(s) or RE_ADD.search(s)):
|
|
782
|
+
continue
|
|
783
|
+
conf = 0.75
|
|
784
|
+
if _has_safety_comment_around(lines, idx):
|
|
785
|
+
conf -= 0.1
|
|
786
|
+
if _in_test_context(lines, idx):
|
|
787
|
+
conf -= 0.05
|
|
788
|
+
conf = max(0.5, min(0.9, conf))
|
|
789
|
+
issues.append(
|
|
790
|
+
Issue(
|
|
791
|
+
language="rust",
|
|
792
|
+
category="unsafe_usage",
|
|
793
|
+
pattern="pointer_arithmetic",
|
|
794
|
+
file=relpath,
|
|
795
|
+
line=idx,
|
|
796
|
+
evidence=_strip_line(s),
|
|
797
|
+
description="使用 offset/add 进行指针算术,若计算结果超出有效范围将导致未定义行为。",
|
|
798
|
+
suggestion="确保指针算术结果在有效对象边界内;使用 slice 等安全抽象替代原始指针算术。",
|
|
799
|
+
confidence=conf,
|
|
800
|
+
severity=_severity_from_confidence(conf),
|
|
801
|
+
)
|
|
802
|
+
)
|
|
803
|
+
return issues
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def _rule_unsafe_mem_ops(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
807
|
+
"""
|
|
808
|
+
检测不安全的内存操作(copy_nonoverlapping/copy/write/read)。
|
|
809
|
+
"""
|
|
810
|
+
issues: List[Issue] = []
|
|
811
|
+
for idx, s in enumerate(lines, start=1):
|
|
812
|
+
if not (RE_COPY_NONOVERLAPPING.search(s) or RE_COPY.search(s) or RE_WRITE.search(s) or RE_READ.search(s)):
|
|
813
|
+
continue
|
|
814
|
+
# 检查是否在 unsafe 块中
|
|
815
|
+
window_text = " ".join(t for _, t in _window(lines, idx, before=5, after=5))
|
|
816
|
+
if "unsafe" not in window_text.lower():
|
|
817
|
+
continue # 这些函数必须在 unsafe 块中使用
|
|
818
|
+
|
|
819
|
+
conf = 0.8
|
|
820
|
+
if _has_safety_comment_around(lines, idx):
|
|
821
|
+
conf -= 0.1
|
|
822
|
+
if _in_test_context(lines, idx):
|
|
823
|
+
conf -= 0.05
|
|
824
|
+
conf = max(0.6, min(0.95, conf))
|
|
825
|
+
issues.append(
|
|
826
|
+
Issue(
|
|
827
|
+
language="rust",
|
|
828
|
+
category="unsafe_usage",
|
|
829
|
+
pattern="unsafe_mem_ops",
|
|
830
|
+
file=relpath,
|
|
831
|
+
line=idx,
|
|
832
|
+
evidence=_strip_line(s),
|
|
833
|
+
description="使用不安全的内存操作(copy/copy_nonoverlapping/write/read),需确保指针有效性、对齐与重叠检查。",
|
|
834
|
+
suggestion="优先使用安全的复制方法;必须使用时,在 SAFETY 注释中证明指针有效性、对齐与边界条件。",
|
|
835
|
+
confidence=conf,
|
|
836
|
+
severity=_severity_from_confidence(conf),
|
|
837
|
+
)
|
|
838
|
+
)
|
|
839
|
+
return issues
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
def _rule_from_raw_parts(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
843
|
+
"""
|
|
844
|
+
检测 from_raw_parts/from_raw 等不安全构造函数。
|
|
845
|
+
"""
|
|
846
|
+
issues: List[Issue] = []
|
|
847
|
+
for idx, s in enumerate(lines, start=1):
|
|
848
|
+
if not (RE_FROM_RAW_PARTS.search(s) or RE_FROM_RAW.search(s)):
|
|
849
|
+
continue
|
|
850
|
+
conf = 0.85
|
|
851
|
+
if _has_safety_comment_around(lines, idx):
|
|
852
|
+
conf -= 0.1
|
|
853
|
+
if _in_test_context(lines, idx):
|
|
854
|
+
conf -= 0.05
|
|
855
|
+
conf = max(0.6, min(0.95, conf))
|
|
856
|
+
issues.append(
|
|
857
|
+
Issue(
|
|
858
|
+
language="rust",
|
|
859
|
+
category="unsafe_usage",
|
|
860
|
+
pattern="from_raw_parts/from_raw",
|
|
861
|
+
file=relpath,
|
|
862
|
+
line=idx,
|
|
863
|
+
evidence=_strip_line(s),
|
|
864
|
+
description="使用 from_raw_parts/from_raw 从原始指针构造,需确保指针有效性、对齐与生命周期安全。",
|
|
865
|
+
suggestion="优先使用安全的构造函数;必须使用时,在 SAFETY 注释中证明所有前置条件(有效性/对齐/生命周期)。",
|
|
866
|
+
confidence=conf,
|
|
867
|
+
severity=_severity_from_confidence(conf),
|
|
868
|
+
)
|
|
869
|
+
)
|
|
870
|
+
return issues
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
def _rule_manually_drop(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
874
|
+
"""
|
|
875
|
+
检测 ManuallyDrop 的使用,需要手动管理 Drop。
|
|
876
|
+
"""
|
|
877
|
+
issues: List[Issue] = []
|
|
878
|
+
for idx, s in enumerate(lines, start=1):
|
|
879
|
+
if not RE_MANUALLY_DROP.search(s):
|
|
880
|
+
continue
|
|
881
|
+
conf = 0.7
|
|
882
|
+
if _has_safety_comment_around(lines, idx):
|
|
883
|
+
conf -= 0.1
|
|
884
|
+
if _in_test_context(lines, idx):
|
|
885
|
+
conf -= 0.05
|
|
886
|
+
conf = max(0.5, min(0.85, conf))
|
|
887
|
+
issues.append(
|
|
888
|
+
Issue(
|
|
889
|
+
language="rust",
|
|
890
|
+
category="resource_management",
|
|
891
|
+
pattern="ManuallyDrop",
|
|
892
|
+
file=relpath,
|
|
893
|
+
line=idx,
|
|
894
|
+
evidence=_strip_line(s),
|
|
895
|
+
description="使用 ManuallyDrop 需要手动管理 Drop,若使用不当可能导致资源泄漏或双重释放。",
|
|
896
|
+
suggestion="确保 ManuallyDrop 包装的对象在适当时候手动调用 drop;在 SAFETY 注释中说明生命周期管理策略。",
|
|
897
|
+
confidence=conf,
|
|
898
|
+
severity=_severity_from_confidence(conf),
|
|
899
|
+
)
|
|
900
|
+
)
|
|
901
|
+
return issues
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
def _rule_panic_unreachable(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
905
|
+
"""
|
|
906
|
+
检测 panic!/unreachable! 的使用,可能导致程序崩溃。
|
|
907
|
+
"""
|
|
908
|
+
issues: List[Issue] = []
|
|
909
|
+
for idx, s in enumerate(lines, start=1):
|
|
910
|
+
if not (RE_PANIC.search(s) or RE_UNREACHABLE.search(s)):
|
|
911
|
+
continue
|
|
912
|
+
conf = 0.6
|
|
913
|
+
if _in_test_context(lines, idx):
|
|
914
|
+
conf -= 0.15 # 测试中 panic 更常见
|
|
915
|
+
if "assert" in s.lower():
|
|
916
|
+
conf -= 0.1 # assert! 宏中的 panic 通常可接受
|
|
917
|
+
conf = max(0.4, min(0.75, conf))
|
|
918
|
+
issues.append(
|
|
919
|
+
Issue(
|
|
920
|
+
language="rust",
|
|
921
|
+
category="error_handling",
|
|
922
|
+
pattern="panic/unreachable",
|
|
923
|
+
file=relpath,
|
|
924
|
+
line=idx,
|
|
925
|
+
evidence=_strip_line(s),
|
|
926
|
+
description="使用 panic!/unreachable! 可能导致程序崩溃,缺少优雅的错误处理。",
|
|
927
|
+
suggestion="优先使用 Result 类型进行错误处理;仅在确实不可恢复的情况下使用 panic。",
|
|
928
|
+
confidence=conf,
|
|
929
|
+
severity=_severity_from_confidence(conf),
|
|
930
|
+
)
|
|
931
|
+
)
|
|
932
|
+
return issues
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
def _rule_refcell_borrow(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
936
|
+
"""
|
|
937
|
+
检测 RefCell 的使用,运行时借用检查可能 panic。
|
|
938
|
+
"""
|
|
939
|
+
issues: List[Issue] = []
|
|
940
|
+
refcell_vars: set[str] = set()
|
|
941
|
+
|
|
942
|
+
# 收集 RefCell 变量
|
|
943
|
+
for idx, s in enumerate(lines, start=1):
|
|
944
|
+
if RE_REFCELL.search(s):
|
|
945
|
+
# 简单提取变量名
|
|
946
|
+
m = re.search(r"\bRefCell\s*<[^>]+>\s*([A-Za-z_]\w*)", s, re.IGNORECASE)
|
|
947
|
+
if m:
|
|
948
|
+
refcell_vars.add(m.group(1))
|
|
949
|
+
|
|
950
|
+
# 检测 borrow/borrow_mut 的使用
|
|
951
|
+
for idx, s in enumerate(lines, start=1):
|
|
952
|
+
for var in refcell_vars:
|
|
953
|
+
if re.search(rf"\b{re.escape(var)}\s*\.borrow\s*\(", s, re.IGNORECASE):
|
|
954
|
+
conf = 0.55
|
|
955
|
+
# 检查是否有 try_borrow(更安全)
|
|
956
|
+
if "try_borrow" in s.lower():
|
|
957
|
+
continue
|
|
958
|
+
if _in_test_context(lines, idx):
|
|
959
|
+
conf -= 0.1
|
|
960
|
+
conf = max(0.4, min(0.7, conf))
|
|
961
|
+
issues.append(
|
|
962
|
+
Issue(
|
|
963
|
+
language="rust",
|
|
964
|
+
category="error_handling",
|
|
965
|
+
pattern="RefCell_borrow",
|
|
966
|
+
file=relpath,
|
|
967
|
+
line=idx,
|
|
968
|
+
evidence=_strip_line(s),
|
|
969
|
+
description=f"RefCell {var} 使用 borrow/borrow_mut 可能在运行时 panic(借用冲突)。",
|
|
970
|
+
suggestion="考虑使用 try_borrow/try_borrow_mut 返回 Result,或使用 Mutex/RwLock 进行编译时检查。",
|
|
971
|
+
confidence=conf,
|
|
972
|
+
severity=_severity_from_confidence(conf),
|
|
973
|
+
)
|
|
974
|
+
)
|
|
975
|
+
break # 每行只报告一次
|
|
976
|
+
return issues
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
def _rule_ffi_cstring(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
980
|
+
"""
|
|
981
|
+
检测 FFI 中 CString/CStr 的使用,需要确保正确转换与生命周期。
|
|
982
|
+
"""
|
|
983
|
+
issues: List[Issue] = []
|
|
984
|
+
for idx, s in enumerate(lines, start=1):
|
|
985
|
+
if not (RE_CSTRING.search(s) or RE_CSTR.search(s)):
|
|
986
|
+
continue
|
|
987
|
+
# 检查是否在 FFI 上下文中
|
|
988
|
+
window_text = " ".join(t for _, t in _window(lines, idx, before=5, after=5))
|
|
989
|
+
if RE_EXTERN_C.search(window_text) or "ffi" in window_text.lower():
|
|
990
|
+
conf = 0.65
|
|
991
|
+
if _has_safety_comment_around(lines, idx):
|
|
992
|
+
conf -= 0.1
|
|
993
|
+
conf = max(0.5, min(0.8, conf))
|
|
994
|
+
issues.append(
|
|
995
|
+
Issue(
|
|
996
|
+
language="rust",
|
|
997
|
+
category="ffi",
|
|
998
|
+
pattern="CString/CStr",
|
|
999
|
+
file=relpath,
|
|
1000
|
+
line=idx,
|
|
1001
|
+
evidence=_strip_line(s),
|
|
1002
|
+
description="FFI 中使用 CString/CStr 需要确保正确的生命周期管理与空字节处理。",
|
|
1003
|
+
suggestion="确保 CString 生命周期覆盖 FFI 调用期间;注意 CStr 不能包含内部空字节。",
|
|
1004
|
+
confidence=conf,
|
|
1005
|
+
severity=_severity_from_confidence(conf),
|
|
1006
|
+
)
|
|
1007
|
+
)
|
|
1008
|
+
return issues
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
def _rule_uninit_zeroed(lines: Sequence[str], relpath: str) -> List[Issue]:
|
|
1012
|
+
"""
|
|
1013
|
+
检测 uninit/zeroed 的使用,未初始化内存访问风险。
|
|
1014
|
+
"""
|
|
1015
|
+
issues: List[Issue] = []
|
|
1016
|
+
for idx, s in enumerate(lines, start=1):
|
|
1017
|
+
if not (RE_UNINIT.search(s) or RE_ZEROED.search(s)):
|
|
1018
|
+
continue
|
|
1019
|
+
conf = 0.75
|
|
1020
|
+
if _has_safety_comment_around(lines, idx):
|
|
1021
|
+
conf -= 0.1
|
|
1022
|
+
if _in_test_context(lines, idx):
|
|
1023
|
+
conf -= 0.05
|
|
1024
|
+
conf = max(0.5, min(0.9, conf))
|
|
1025
|
+
issues.append(
|
|
1026
|
+
Issue(
|
|
1027
|
+
language="rust",
|
|
1028
|
+
category="unsafe_usage",
|
|
1029
|
+
pattern="uninit/zeroed",
|
|
1030
|
+
file=relpath,
|
|
1031
|
+
line=idx,
|
|
1032
|
+
evidence=_strip_line(s),
|
|
1033
|
+
description="使用 uninit/zeroed 创建未初始化内存,若在初始化前读取将导致未定义行为。",
|
|
1034
|
+
suggestion="确保在使用前完成初始化;优先使用 MaybeUninit 进行更安全的未初始化内存管理。",
|
|
1035
|
+
confidence=conf,
|
|
1036
|
+
severity=_severity_from_confidence(conf),
|
|
1037
|
+
)
|
|
1038
|
+
)
|
|
1039
|
+
return issues
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
# ---------------------------
|
|
1043
|
+
# 对外主入口
|
|
1044
|
+
# ---------------------------
|
|
1045
|
+
|
|
1046
|
+
def analyze_rust_text(relpath: str, text: str) -> List[Issue]:
|
|
1047
|
+
"""
|
|
1048
|
+
基于提供的文本进行 Rust 启发式分析。
|
|
1049
|
+
- 准确性优化:在启发式匹配前移除注释(保留字符串/字符字面量),
|
|
1050
|
+
以避免注释中的API命中导致的误报。
|
|
1051
|
+
- 准确性优化2:对通用 API 扫描使用"字符串内容掩蔽"的副本,避免把字符串里的片段当作代码。
|
|
1052
|
+
"""
|
|
1053
|
+
clean_text = _remove_comments_preserve_strings(text)
|
|
1054
|
+
masked_text = _mask_strings_preserve_len(clean_text)
|
|
1055
|
+
# 原始行:保留字符串内容,供需要解析字面量的规则使用
|
|
1056
|
+
clean_text.splitlines()
|
|
1057
|
+
# 掩蔽行:字符串内容已被空格替换,适合用于通用 API/关键字匹配,减少误报
|
|
1058
|
+
mlines = masked_text.splitlines()
|
|
1059
|
+
|
|
1060
|
+
issues: List[Issue] = []
|
|
1061
|
+
# 通用 API/关键字匹配(使用掩蔽行)
|
|
1062
|
+
issues.extend(_rule_unsafe(mlines, relpath))
|
|
1063
|
+
issues.extend(_rule_raw_pointer(mlines, relpath))
|
|
1064
|
+
issues.extend(_rule_transmute(mlines, relpath))
|
|
1065
|
+
issues.extend(_rule_forget(mlines, relpath))
|
|
1066
|
+
issues.extend(_rule_maybe_uninit(mlines, relpath))
|
|
1067
|
+
# 错误处理
|
|
1068
|
+
issues.extend(_rule_unwrap_expect(mlines, relpath))
|
|
1069
|
+
issues.extend(_rule_ignore_result(mlines, relpath))
|
|
1070
|
+
issues.extend(_rule_panic_unreachable(mlines, relpath))
|
|
1071
|
+
# FFI 相关
|
|
1072
|
+
issues.extend(_rule_extern_c(mlines, relpath))
|
|
1073
|
+
issues.extend(_rule_ffi_cstring(mlines, relpath))
|
|
1074
|
+
# 并发相关
|
|
1075
|
+
issues.extend(_rule_unsafe_impl(mlines, relpath))
|
|
1076
|
+
issues.extend(_rule_refcell_borrow(mlines, relpath))
|
|
1077
|
+
# 内存操作相关
|
|
1078
|
+
issues.extend(_rule_get_unchecked(mlines, relpath))
|
|
1079
|
+
issues.extend(_rule_pointer_arithmetic(mlines, relpath))
|
|
1080
|
+
issues.extend(_rule_unsafe_mem_ops(mlines, relpath))
|
|
1081
|
+
issues.extend(_rule_from_raw_parts(mlines, relpath))
|
|
1082
|
+
issues.extend(_rule_manually_drop(mlines, relpath))
|
|
1083
|
+
issues.extend(_rule_uninit_zeroed(mlines, relpath))
|
|
1084
|
+
return issues
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
def analyze_rust_file(base: Path, relpath: Path) -> List[Issue]:
|
|
1088
|
+
"""
|
|
1089
|
+
从磁盘读取文件进行分析。
|
|
1090
|
+
"""
|
|
1091
|
+
try:
|
|
1092
|
+
text = (base / relpath).read_text(errors="ignore")
|
|
1093
|
+
except Exception:
|
|
1094
|
+
return []
|
|
1095
|
+
return analyze_rust_text(str(relpath), text)
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def analyze_rust_files(base_path: str, relative_paths: List[str]) -> List[Issue]:
|
|
1099
|
+
"""
|
|
1100
|
+
批量分析文件,相对路径相对于 base_path。
|
|
1101
|
+
"""
|
|
1102
|
+
base = Path(base_path).resolve()
|
|
1103
|
+
out: List[Issue] = []
|
|
1104
|
+
for f in relative_paths:
|
|
1105
|
+
p = Path(f)
|
|
1106
|
+
if p.suffix.lower() == ".rs":
|
|
1107
|
+
out.extend(analyze_rust_file(base, p))
|
|
1108
|
+
return out
|