- 静态分析
- SAST
- 应用安全
- 漏洞扫描
- 代码质量
【免费下载链接】codeql
CodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security
本篇技术指南聚焦 CodeQL 仓库中 C# 分析库Modifiable类的三个可见性谓词——isEffectivelyPrivate、isEffectivelyInternal与isEffectivelyPublic。这一组谓词曾在 2021-06-15 的变更说明(csharp/old-change-notes/2021-06-15-effective-visibility.md)中宣告重做,核心目的是正确处理 C# 的private protected、internal protected组合可见性以及显式接口实现成员的可见性。读完本文,你将理解"有效可见性"(effective visibility)与"声明可见性"的区别、三个谓词的判定规则与实现细节,以及如何通过测试用例验证这些规则,从而在你的自定义查询中准确过滤掉实际上不可从当前程序集外部访问的成员。
变更背景:从"声明修饰符"到"有效可见性"
在 C# 中,一个成员的实际可访问范围并不总是与它声明的访问修饰符一一对应。典型例子包括:
- 嵌套类型中的成员:一个声明为
public的成员,如果它所在的类型本身是internal或private,那么该成员实际上不可能被程序集外部引用; - 受保护成员的可重写性:
protected成员虽然可以被派生类访问,但private protected与protected internal在跨程序集场景下行为不同; - 显式接口实现:
void I.Foo() { }这样的成员没有普通意义上的可见性,它只能通过接口引用访问,因此其有效可见性取决于被实现的接口本身。
为此,CodeQL 的 C# 库在 csharp/ql/lib/semmle/code/csharp/Member.qll 的Modifiable类中提供了三个"有效可见性"谓词,回答的不是"这个成员写了什么修饰符",而是"这个成员在程序语义上到底能被谁引用"。
谓词语义:三个判定规则
有效私有:isEffectivelyPrivate
在 Member.qll 中,isEffectivelyPrivate()的定义为:该声明只能从以下位置引用——
- 声明它的类型及其嵌套类型(与普通
private一致),以及 - 它的封闭类型(enclosing types)。
其实现包含三条判定路径:
predicate isEffectivelyPrivate() { this.isReallyPrivate() or this.getDeclaringType+().(Modifiable).isReallyPrivate() or this.(Virtualizable).getExplicitlyImplementedInterface().isEffectivelyPrivate() }- 自身为私有:
isReallyPrivate()是一个私有辅助谓词,要求isPrivate()且不是isProtected(),同时排除极少数"同名成员跨程序集以不同可见性定义"的情况(Member.qll); - 封闭类型链私有:
this.getDeclaringType+()沿声明类型逐级向上查找(+表示一个或多个步骤),只要任一封闭类型真正私有,成员即视为有效私有; - 显式接口实现:通过
getExplicitlyImplementedInterface()取得显式实现的接口,若接口本身有效私有,则该实现成员同样有效私有。
文档注释特别强调两点:显式接口实现在接口本身有效私有时被视为有效私有;而private protected成员不被视为有效私有,因为它可以在声明程序集内被重写(override)。
有效内部:isEffectivelyInternal
isEffectivelyInternal()的语义是:该声明只能从声明它的程序集内部引用(Member.qll):
predicate isEffectivelyInternal() { this.isReallyInternal() or this.getDeclaringType+().(Modifiable).isReallyInternal() or this.(Virtualizable).getExplicitlyImplementedInterface().isEffectivelyInternal() }其辅助谓词isReallyInternal()判定两条路径(Member.qll):
internal且非protected(即普通internal);private且protected(即private protected——注意它与internal的可达范围相同,都限于本程序集)。
同样需要排除同名成员跨程序集不同可见性的稀有情况。文档注释中的两个关键限定:
- 通过
InternalsVisibleToAttribute声明的友元程序集(friend assemblies)不在考虑范围内——即只要理论上可能被其他程序集看到就归为有效内部之外; internal protected(即protected internal)成员不被视为有效内部,因为它可以在声明程序集之外被派生类重写;- 显式接口实现成员若实现的接口本身有效内部,则该成员同样被视为有效内部。
有效公开:isEffectivelyPublic
isEffectivelyPublic()是一个兜底定义(Member.qll):凡不是有效私有、也不是有效内部的声明,即可从程序集外部引用:
predicate isEffectivelyPublic() { not this.isEffectivelyPrivate() and not this.isEffectivelyInternal() }因此对任意Modifiable,三个谓词构成穷尽且互斥的三分:任何成员必然恰好落在"有效私有""有效内部""有效公开"之一。
测试验证:库测试 Modifiers
仓库在 csharp/ql/test/library-tests/modifiers/ 提供了完整的库测试来锁定这些语义。
被测代码:Modifiers.cs
Modifiers.cs 构造了覆盖各类修饰符组合的样例,其中与本次重做直接相关的成员包括:
internal protected readonly int F3; // 内部保护字段 public int P1 { get; set; } // 公开属性 public int P2 { get; private set; } // 公开属性、私有 setter internal interface I2 { void M1(); } // 内部接口 public class C2 : I2 { void I2.M1() => throw null; // 显式接口实现 protected private void M2() { } // private protected protected internal void M3() { } // internal protected }测试查询:Effectively.ql
Effectively.ql 专门用于本组谓词,它找出三种"声明可见性与有效可见性不一致"的情况并标记实际的有效可见性:
from Modifiable m, string s where m.fromSource() and ( m.isEffectivelyInternal() and not m.isInternal() and s = "internal" or m.isEffectivelyPrivate() and not m.isPrivate() and s = "private" or m.isEffectivelyPublic() and s = "public" ) select m, s即:对每个源码中的声明,若isEffectivelyInternal()成立但声明中并没有internal修饰符,就报告为internal;若isEffectivelyPrivate()成立但没有private修饰符,报告为private;凡有效公开者报告为public。
预期结果:Effectively.expected
对照 Effectively.expected,可以看到重做后的判定结果:
Modifiers.cs:12:14的M1(注释标记的私有方法)→internal:因为其封闭类型C默认是internal(unsafe class C无可见性修饰符,默认为 internal),成员随类型被限制在程序集内;Modifiers.cs:24:22的C1被同时报告为internal与private:sealed class C1声明于internal类内,其构造器C1()是public但随类型降至 internal,而类本身随内部类 C 降至有效私有;Modifiers.cs:52:19的S(public struct)→public;Modifiers.cs:68:14的I2.M1(内部接口的默认实现方法)→internal;Modifiers.cs:73:17的显式接口实现void I2.M1()→internal:这正是本次重做新增的判定——显式实现成员的可见性继承自被实现接口I2的可见性;Modifiers.cs:75:32的protected private void M2()(private protected)→internal:证明private protected被纳入有效内部而非有效私有;Modifiers.cs:76:33的protected internal void M3()→public:因为internal protected可以被程序集外的派生类重写,故不归为有效内部。
这些预期输出同时验证了重做后的三条核心规则:显式接口实现的可见性跟随接口、private protected归入有效内部、internal protected归入有效公开。
谓词在查询中的实际应用
这一组谓词在仓库的多个查询与库模块中被广泛使用,用于过滤"从外部实际不可达"的成员,提升查询精度。
- csharp/ql/lib/semmle/code/csharp/telemetry/ExternalApi.qll:在统计外部 API 使用情况时,需要区分真正暴露给外部的成员与内部实现细节,
isEffectivelyPublic()等谓词正是判定的基础; - csharp/ql/src/Useless code/DefaultToStringQuery.qll、csharp/ql/src/Language Abuse/MissedReadonlyOpportunity.ql、csharp/ql/src/Likely Bugs/Collections/WriteOnlyContainer.ql:这些查询在判断成员是否"可被外部观察"时使用有效可见性,避免把仅程序集内部可见的成员当作公开 API 处理;
- csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll 与 csharp/ql/src/utils/modeleditor/ModelEditor.qll:模型生成工具需要按可见性决定是否为成员生成数据流模型;
- csharp/ql/lib/semmle/code/csharp/dataflow/internal/Steps.qll、csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll:数据流与派发分析中,跨程序集边界的数据流需要以有效可见性为准,
private protected/internal protected的可重写性直接影响跨程序集调用图的构造。
从源码结构看,重做后的谓词通过统一收敛到isReallyPrivate/isReallyInternal两个私有辅助谓词,并把"封闭类型链"与"显式接口实现"两类场景显式建模,使得三个公开谓词在所有Modifiable声明上构成互斥且完备的划分,任何后续查询只需选择其一即可得到精确的可见性分类。
小结
isEffectivelyPrivate、isEffectivelyInternal、isEffectivelyPublic是 CodeQL C# 库中关于成员可见性的核心抽象。2021-06-15 的重做补齐了三个此前处理不准确的场景:private protected(归入有效内部)、internal protected(因其可跨程序集重写而归入有效公开)、以及显式接口实现(可见性继承自被实现接口)。理解这三条规则,不仅有助于读懂ExternalApi、Dispatch等库模块的实现,也能帮助你在编写自定义安全查询时正确判断一个成员是否真正暴露在程序集外部,从而减少误报、提升分析的准确性。
- 静态分析
- SAST
- 应用安全
- 漏洞扫描
- 代码质量
【免费下载链接】codeql
CodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security
相关推荐
CodeQL C++ 溢出分析库 SimpleRangeAnalysis:exprMightOverflow* 谓词如何变得更"可靠"
CodeQL C++ 溢出分析库 SimpleRangeAnalysis:exprMightOverflow 谓词如何变得更"可靠" 本文以 CodeQL C+
静态分析SAST应用安全漏洞扫描代码质量CodeQL C++ 库 0.0.10 新增特性:`Variable::isStructuredBinding` 谓词详解
CodeQL C++ 库 0.0.10 新增特性: Variable::isStructuredBinding 谓词详解 CodeQL 的 C++ 查询库在版本
静态分析SAST应用安全漏洞扫描代码质量CodeQL C/C++ 查询库:通过 Function 新谓词精确分析 virtual、override 与 final 声明
CodeQL C/C++ 查询库:通过 Function 新谓词精确分析 virtual、override 与 final 声明 导读 在编写 C++ 安全查询
静态分析SAST应用安全漏洞扫描代码质量
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考