关于 dotnet-inspect-correctness
这项技能指导使用 dotnet-inspect 检查 .NET 方法的异常行为与不安全操作,并强调依据证据评估,不能把未发现结果当作安全证明。
主要功能
- 01检查异常区域与调用图
- 02定位方法体中的不安全操作
- 03关联不同包版本的安全发现
- 04使用 jq 组合分类证据
- 05记录覆盖范围与未验证事项
适用场景
- 01审查方法的异常处理
- 02调查不安全内存操作
- 03结合不安全代码评估资源所有权
- 04按 .NET 不安全代码指南整理证据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ richlander/ dotnet-inspect --skill dotnet-inspect-correctness安装后在 Claude Code 中输入/dotnet-inspect-correctness确认已加载。
详细介绍
dotnet-inspect:正确性与安全性
使用此技能判断代码是否可靠且可安全调用:它可能引发什么异常、如何处理错误,以及在哪些地方会脱离安全的托管执行。此分析针对单个版本;若要了解这些信号在不同版本之间如何变化,请使用 compatibility 技能。
dnx dotnet-inspect -y -- <command>
它可能引发什么异常?(异常范围)
没有专门的“Exceptions”部分;异常行为通过方法体分析得出。Exception Regions 会显示精确的 catch/filter/finally 布局;图字段和隐藏事实则会概述相关行为:
dnx dotnet-inspect -y -- member Type Method:1 -S "Exception Regions"
dnx dotnet-inspect -y -- member Type Method:1 -S "Call Graph" --fields "Throws,ThrowSites,ExceptionTypes,ConstructedExceptions,Catch,Finally"
dnx dotnet-inspect -y -- member Type Method:1 -S "Call Graph" --fields "Throws,Catch,Finally"
dnx dotnet-inspect -y -- member Type Method:1 -S Facts --tsv
Throws/ThrowSites 统计抛出点;ExceptionTypes/ConstructedExceptions 列出异常类型;Catch/Finally 显示处理方式。Exception Regions 保留 IL 范围和捕获的类型。-S Facts(成员、单个方法)会列出方法体中的隐藏事实,并支持 --tsv。
它在内存方面安全吗?(不安全操作)
dnx dotnet-inspect -y -- member Type Method:1 --library MyLib.dll -S "Unsafe Operations,IL"
-S "Unsafe Operations" 会显示单个方法体中的不安全操作,并提供 IL 证据。若要了解整个库的安全范围(不安全成员、P/Invoke 方法)以及来源/供应链信号,请参阅 signals 技能。
对于某次崩溃或分析器坐标,使用 library coordinate 0x06000001+0x5 --library Foo.dll 获取默认的源位置、成员、指令、异常、调用点和返回地址上下文。安全证据需显式启用:library coordinate 0x06000001+0x5 --library Foo.dll -S "Context: Safety"。
若要确认某个明确的不安全操作是否出现在相邻版本边界,先关联由调用方选择的程序包单元格:
dnx dotnet-inspect -y -- diff --history --package [email protected] \
-t MyType -m Method \
--finding analysis.unsafety --at first --at last
对于稀疏探测,请重复使用 --at;对于有明确边界的密集遍历,请使用 --at all。跨越版本间隔的 Added 行可定位候选边界,但不能证明操作确切在哪个版本引入。请确认相邻版本对:
dnx dotnet-inspect -y -- diff --package [email protected] \
-t MyType -m Method \
--finding analysis.unsafety
PairFinding.Added 是引入操作的证据。Present 和 Removed 用于区分持续存在与消失;此命令仅比较提供的两个端点。
根据 .NET 指南审查不安全代码与所有权
将 .NET 不安全代码最佳实践指南 作为策略依据。其 源文档 目前将审查内容分为 26 个编号主题。
dotnet-inspect 提供编译后的证据,而不是包罗万象的合规性结论。获取可复用的 JSONL 行集,使用 Agent 和 jq 按每个问题筛选并联接这些行,然后仅对影响重大的 Members 进行深入检查。同一批行也能回答其他安全问题,无需新增聚合命令。
获取可复用的行集
将每一类行分别保存,以便清楚呈现其标识、结构和完成状态。记录每条命令的状态和诊断信息:聚焦部分为空,可能是非零结果,并非成功的空清查。
capture()
{
output="$1"
shift
status=0
"$@" > "$output" 2> "$output.stderr" || status=$?
printf '%s\t%s\n' "$output" "$status" >> inspection-status.tsv
}
: > inspection-status.tsv
capture unsafe-members.jsonl \
dnx dotnet-inspect -y -- library MyLib.dll \
-S "Unsafe Members" --jsonl
capture pinvoke-methods.jsonl \
dnx dotnet-inspect -y -- library MyLib.dll \
-S "P/Invoke Methods" --jsonl
capture custom-attributes.jsonl \
dnx dotnet-inspect -y -- library MyLib.dll \
-S "Custom Attributes" --jsonl
capture references.jsonl \
dnx dotnet-inspect -y -- library MyLib.dll \
-S References --jsonl
capture signals.json \
dnx dotnet-inspect -y -- library MyLib.dll \
-S Signals --json
库级 Custom Attributes 涵盖经过筛选的程序集级和模块级特性。它不会发现 Type、字段或方法上的特性,也无法证明 SkipLocalsInit 的覆盖情况。请保留这一范围:只有在其他行或源代码证据选定 Type 或 Member 后,才使用精确目标的特性;并将整个库的特性发现情况报告为 unverified。
所有权与不安全代码相互交织。池化缓冲区可能没有指针操作码,却仍会因返回后使用、缺少清理或所有权转移而出错。请通过同一封装器,在获取不安全证据的同时获取 Resource Triage:
capture resource-triage.jsonl \
dnx dotnet-inspect -y -- library MyLib.dll \
-S "Resource Triage" --jsonl
目前 Resource Triage 没有肯定的完成回执,而且在某些资源根尚未完成时也可能输出行。所有权覆盖情况始终应描述为 unverified;保留任何更具体的诊断信息,绝不要将零行解读为“没有所有权问题”。
切片并合并行
按确切操作汇总不安全调用:
jq -s '
map(select(.reason == "Unsafe call"))
| sort_by(.detail)
| group_by(.detail)
| map({
operation: .[0].detail,
sites: length,
members: (map(.member) | unique)
})
' unsafe-members.jsonl
选择指南中讨论的 API 系列:
jq -s --arg pattern \
'Unsafe\.(AsPointer|As|BitCast|Add|NullRef|SkipInit|ReadUnaligned|WriteUnaligned|CopyBlock)' '
map(select(.detail | test($pattern)))
' unsafe-members.jsonl
选择 stackalloc(localloc)位置:
jq -s '
map(select(.detail == "localloc"))
| group_by(.member)
| map({member: .[0].member, sites: map(.il)})
' unsafe-members.jsonl
组合为一个 Agent 数据包时,保留各证据系列的类型信息:
jq -n \
--slurpfile unsafe unsafe-members.jsonl \
--slurpfile ownership resource-triage.jsonl \
--slurpfile interop pinvoke-methods.jsonl \
'{
unsafe: $unsafe,
ownership: $ownership,
interop: $interop
}'
这些是切片,而非按显示文本联接。保留每行的 Member、IL、 token、Candidate、Finding、Acquire IL、Boundary IL 和边界字段。深入分析前, 先解析确切的 Type 和 Member 选择器;不要根据格式化后的 Member 标签臆造对应关系。
深入分析一个候选项
为聚焦证据使用独立的机器输出,并保留 Markdown IL 供审阅。重复使用
capture 包装器,以便空结果或失败的深入分析也能显现:
capture candidate-unsafe.jsonl \
dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll \
-S "Unsafe Operations" --jsonl
capture candidate-exceptions.jsonl \
dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll \
-S "Exception Regions" --jsonl
capture candidate-attributes.jsonl \
dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll \
-S "Custom Attributes" --jsonl
capture candidate-calls.jsonl \
dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll \
-S "Call Graph" --fields "Unsafe,Throw,Catch,Finally,Loop" --jsonl
capture candidate-il.md \
dnx dotnet-inspect -y -- member MyType Method:1 --library MyLib.dll \
-S IL
仅在重构出的 C# 有助于解释 IL 时才加载 skill decompiler。如果问题取决于词法作用域、手写的保护条件、编译器警告或仓库测试实践,请使用源代码证据。
为 26 个指导问题选择路径
以下路径列出了最精简且有用的证据组合。它们并不表示匹配的证据就能证明违规,也不表示检查后为空的行就能证明安全。
| # | 指引主题 | 证据路径与边界 |
|---|---|---|
| 1 | 未跟踪的托管指针 | 检查 AsPointer 的不安全调用片段;深入追踪指针来源、固定、用途和逃逸。 |
| 2 | 指针从 fixed 中逃逸 | 检查不安全/固定证据,以及 IL 存储、返回和调用;词法 fixed 作用域可能需要源代码。 |
| 3 | 运行时或库内部实现 | 检查不安全调用、特性、引用和非公开调用证据;广泛依赖仍需审查判断。 |
| 4 | 无效托管指针 | 检查 Unsafe.Add 和相关按引用算术片段;深入追踪范围和控制流证据。 |
| 5 | 重新解释型转换 | 检查 Unsafe.As 和 BitCast 片段;在判断兼容性前,先关联精确的源/目标 Type 元数据。 |
| 6 | 写屏障与 GC 引用原子性 | 检查 cpblk、initblk、间接存储和复制 API;深入追踪目标来源和字段类型。 |
| 7 | 对象生命周期 | 检查对 GC.KeepAlive、SafeHandle、终结器、回调和互操作边界的调用;通常无法验证缺失情况。 |
| 8 | 跨线程局部变量 | 检查调用图、异步/状态机、指针/按引用及所有权证据;线程转移通常需要源代码或运行时证据。 |
| 9 | 不安全地移除边界检查 | 检查指针解引用和 Unsafe.Add 片段;检查保护条件和范围,然后要求提供运行时性能证据。 |
| 10 | 内存访问合并 | 检查 Unsafe/MemoryMarshal 调用和存储片段;使用运行时证据比较安全替代方案。 |
| 11 | 非对齐内存访问 | 检查非对齐 API 和间接操作片段;目标对齐和原子性仍需结合平台/上下文判断。 |
| 12 | 二进制结构体序列化 | 检查 MemoryMarshal/Unsafe 读、写、复制操作及精确的 Type 布局;开放泛型仍不完整。 |
| 13 | 空托管指针 | 检查 Unsafe.NullRef 和指针到按引用转换的片段;深入追踪用途和被丢弃的解引用操作。 |
| 14 | stackalloc | 检查 localloc;深入追踪循环归属、长度构造、span 转换和边界。 |
| 15 | 固定大小缓冲区 | 不安全声明可用于筛选候选项,但库特性无法发现字段级别的 FixedBufferAttribute;无法验证整个库的发现结果,因此对于已知候选项,请使用源代码或精确 Type 反编译。 |
| 16 | 指针加长度 API | 指针签名和 P/Invoke 行可用于识别候选项;检查显式长度和终止假设。 |
| 17 | 字符串修改 | 检查指针存储以及字符串来源证据;通常需要源代码或反编译来确定目标。 |
| 18 | 原始 IL 生成 | 引用和调用可用于识别 Reflection.Emit 或 Cecil 的使用;生成的 IL 是否有效需要单独的证据。 |
| 19 | 未初始化的局部变量 | 检查 Unsafe.SkipInit 调用片段。库特性无法确定 SkipLocalsInit;无法验证整个库的普查结果,因此请使用源代码,或检查精确 Member 的特性和 IL 局部变量初始化,然后深入追踪读取、写入和池化缓冲区初始化。 |
| 20 | ArrayPool<T> 和池化 | 关联资源分流、不安全调用、异常和返回;当前所有权覆盖情况始终无法验证。 |
| 21 | bool/整数转换 | 检查涉及 Boolean 和整数 Type 的 Unsafe.As、BitCast 及非对齐读取片段。 |
| 22 | 互操作 | 检查 P/Invoke 行、签名、特性、不安全操作以及所有权/生命周期证据。 |
| 23 | 线程安全 | 调用图和可变资源证据可用于筛选候选项;不涵盖一般性的线程安全结论。 |
| 24 | SIMD/向量化 | 调用和不安全操作可用于识别加载/存储;边界、对齐、原子性和收益需要结合上下文/运行时证据判断。 |
| 25 | 模糊测试 | 程序集证据无法确定仓库的模糊测试覆盖率;请单独检查测试和 CI。 |
| 26 | 编译器警告 | 普通程序集不会保留警告;需要构建诊断信息或 SARIF。 |
报告审查结果
对于每个适用的问题,请报告:
- 指导主题和准确的检查对象;
- 所有者给出的行及准确坐标;
- 生成有界候选集的
jq切片或联接; - Agent 标注的解读;
- 已完成的内容或
unverified边界;以及 - 当证据无法判定问题时,下一步有针对性的探查。
不要因为没有出现匹配行,就报告全部 26 个主题均无问题。省略确实不适用的主题,并将其与证据不可用或不完整的适用问题区分开来。