C# 的 CodeQL 库¶
分析 C# 程序时,可以使用 C# 的 CodeQL 库中大量类。
关于 C# 的 CodeQL 库¶
有一个广泛的核心库用于分析从 C# 项目中提取的 CodeQL 数据库。该库中的类以面向对象的形式呈现数据库中的数据,并提供抽象和谓词来帮助您完成常见的分析任务。该库实现为一组 QL 模块,即扩展名为 .qll
的文件。模块 csharp.qll
导入所有核心 C# 库模块,因此您可以通过在查询开头包含以下内容来包含整个库:
import csharp
由于所有 C# 查询都需要此,因此它在下面的代码片段中被省略了。
核心库包含所有程序元素,包括 文件、类型、方法、变量、语句 和 表达式。这足以满足大多数查询,但是可以导入其他库来实现特定功能,例如控制流和数据流。有关这些其他库的信息,请参见“C# 的 CodeQL。”
类层次结构¶
每节包含一个类层次结构,展示 CodeQL 类之间的继承结构。例如
Expr
Operation
ArithmeticOperation
UnaryArithmeticOperation
UnaryMinusExpr
、UnaryPlusExpr
MutatorOperation
IncrementOperation
PreIncrExpr
、PostIncrExpr
DecrementOperation
PreDecrExpr
、PostDecrExpr
BinaryArithmeticOperation
AddExpr
、SubExpr
、MulExpr
、DivExpr
、RemExpr
这意味着类 AddExpr
扩展了类 BinaryArithmeticOperation
,而 BinaryArithmeticOperation
又扩展了类 ArithmeticOperation
,以此类推。如果您想查询任何算术运算,请使用类 ArithmeticOperation
,但如果您特别想将查询限制为加法运算,请使用类 AddExpr
。
类也可以被认为是集合,而类之间的 extends
关系定义了一个子集。类 AddExpr
的每个成员也都在类 BinaryArithmeticOperation
中。一般来说,类之间会重叠,一个实体可以是多个类的成员。
此概述省略了类层次结构中一些不太重要或中间的类。
每个类都有谓词,即关于该类的逻辑命题。它们还定义了类之间的可导航关系。谓词是继承的,例如 AddExpr
类从 BinaryArithmeticOperation
继承了谓词 getLeftOperand()
和 getRightOperand()
,并从类 Expr
继承了 getType()
。这类似于面向对象编程语言中如何继承方法。
在本概述中,我们介绍了最常见和最有用的谓词。要查看每个类上可用的所有谓词的完整列表,您可以在 CodeQL 源代码中查找,使用编辑器中的自动完成,或者查看 C# 参考。
文件¶
文件由类 File 表示,目录由类 Folder 表示。数据库包含编译期间使用的所有源文件和程序集。
类层次结构¶
File
- 数据库中的任何文件(包括源文件、XML 和程序集)SourceFile
- 包含源代码的文件
Folder
- 目录
谓词¶
getName()
- 获取文件的完整路径(例如,C:\Temp\test.cs
)。getNumberOfLines()
- 获取行数(仅限于源文件)。getShortName()
- 获取不带扩展名的文件名(例如,test
)。getBaseName()
- 获取文件名和扩展名(例如,test.cs
)。getParent()
- 获取父目录。
示例¶
计算源文件的数量
select count(SourceFile f)
计算代码行数,不包括目录 external
select sum(SourceFile f |
not exists(Folder ext | ext.getShortName() = "external" |
ext.getAFolder*().getAFile() = f) |
f.getNumberOfLines())
元素¶
类 Element 是 C# 程序所有部分的基类,并且是元素类层次结构的根。所有程序元素(例如类型、方法、语句和表达式)最终都派生自这个共同的基类。
Element
形成了程序的层次结构,可以使用 getParent()
和 getChild()
谓词进行导航。这很像抽象语法树,也适用于程序集中的元素。
谓词¶
Element
类为所有程序元素提供了通用功能,包括
getLocation()
- 获取源代码中的文本跨度。getFile()
- 获取包含Element
的File
。getParent()
- 获取父Element
(如果有)。getAChild()
- 获取此元素的子Element
(如果有)。
示例¶
列出 Main.cs
中的所有元素,以及它们的 QL 类和位置
from Element e
where e.getFile().getShortName() = "Main"
select e, e.getAQlClass(), e.getLocation()
请注意,getAQlClass()
可用于所有实体,是确定某事物 QL 类的有用方法。通常,同一个元素会有多个类,所有这些类都会被 getAQlClass()
返回。
位置¶
Location 表示源代码或程序集中的文本部分。所有元素都有一个 Location
,可以通过它们的 getLocation()
谓词获取。SourceLocation
表示源代码中的文本跨度,而 Assembly
位置表示引用的程序集。
有时元素会有多个位置,例如如果它们同时出现在源代码和程序集中。在这种情况下,只返回 SourceLocation
。
类层次结构¶
Location
SourceLocation
Assembly
谓词¶
Location
的一些谓词包括
getFile()
- 获取File
。getStartLine()
- 获取文本的第一行。getEndLine()
- 获取文本的最后一行。getStartColumn()
- 获取文本起点的列。getEndColumn()
- 获取文本终点的列。
示例¶
查找所有宽度为一个字符的元素
from Element e, Location l
where l = e.getLocation()
and l.getStartLine() = l.getEndLine()
and l.getStartColumn() = l.getEndColumn()
select e, "This element is a single character."
声明¶
Declaration 是程序中所有定义的实体的公共类,例如类型、方法、变量等。数据库包含源代码和所有引用程序集中的所有声明。
类层次结构¶
Element
Declaration
Callable
UnboundGeneric
ConstructedGeneric
Modifiable
- 可修改声明,可以包含修饰符(例如public
)Member
- 属于类型的声明
Assignable
- 可赋值的元素变量
属性
索引器
事件
谓词¶
对 Declaration
有用的成员谓词包括
getDeclaringType()
- 获取包含声明的类型(如果有)。getName()
/hasName(string)
- 获取声明的实体的名称。isSourceDeclaration()
- 声明是否为源代码,且不是构造类型/方法。getSourceDeclaration()
- 获取原始(未构造)声明。
示例¶
查找包含用户名声明的实体
from Declaration decl
where decl.getName().regexpMatch("[uU]ser([Nn]ame)?")
select decl, "A username."
变量¶
类 Variable 表示 C# 变量,例如字段、参数和局部变量。数据库包含来自源代码的所有变量,以及程序引用的程序集中的所有字段和参数。
类层次结构¶
Element
Declaration
Variable
- 任何类型的变量Field
-class
/struct
中的字段MemberConstant
-const
字段EnumConstant
-enum
中的字段
LocalScopeVariable
- 范围限定为单个Callable
的变量LocalVariable
-Callable
中的局部变量LocalConstant
-Callable
中局部定义的常量
Parameter
-Callable
的参数
谓词¶
对 Variable
有一些常见的谓词
getType()
- 获取该变量的Type
。getAnAccess()
- 获取访问(读取或写入)该变量的表达式(如果有)。getAnAssignedValue()
- 获取分配给该变量的表达式(如果有)。getInitializer()
- 获取用于初始化变量的表达式(如果有)。
示例¶
查找所有未使用的局部变量
from LocalVariable v
where not exists(v.getAnAccess())
select v, "This local variable is unused."
类型¶
类型由 CodeQL 类 Type 表示,包括内置类型、接口、类、结构、枚举和类型参数。数据库包含来自程序和所有引用程序集(包括 mscorlib 和 .NET 框架)的类型。
内置类型(object
、int
、double
等)在 mscorlib 中有相应的类型(System.Object
、System.Int32
等)。
类 ValueOrRefType
表示定义的类型,例如 class
、struct
、interface
或 enum
。
类层次结构¶
Element
Declaration
Modifiable
- 可修改声明,可以包含修饰符(例如public
)Member
- 属于类型的声明Type
- 所有类型ValueOrRefType
- 定义的类型ValueType
- 值类型(见下文了解更详细的层次结构)RefType
- 引用类型(见下文了解更详细的层次结构)NestedType
- 在另一个类型中定义的类型
VoidType
-void
PointerType
- 指针类型
类 ValueType
扩展为
ValueType
- 值类型SimpleType
- 简单内置类型BoolType
-bool
CharType
-char
整数类型
无符号整数类型
ByteType
-byte
UShortType
-unsigned short
/System.UInt16
UIntType
-unsigned int
/System.UInt32
ULongType
-unsigned long
/System.UInt64
有符号整数类型
SByteType
-signed byte
ShortType
-short
/System.Int16
IntType
-int
/System.Int32
LongType
-long
/System.Int64
浮点类型
FloatType
-float
/System.Single
DoubleType
-double
/System.Double
DecimalType
-decimal
/System.Decimal
Enum
-enum
Struct
-struct
可空类型
数组类型
类 RefType
扩展为
RefType
Class
-class
匿名类
ObjectType
-object
/System.Object
StringType
-string
/System.String
Interface
-interface
委托类型
NullType
-null
的类型DynamicType
-dynamic
NestedType
- 在另一个类型中定义的类型
为了简化,这些类层次结构省略了泛型类型。
谓词¶
ValueOrRefType
的有用成员包括
getQualifiedName()/hasQualifiedName(string)
- 获取类型的限定名称(例如,"System.String"
)。getABaseInterface()
- 获取该类型的直接接口(如果有)。getABaseType()
- 获取该类型的直接基类或接口(如果有)。getBaseClass()
- 获取该类型的直接基类(如果有)。getASubType()
- 获取直接继承自该类型的直接子类型(如果有)。getAMember()
- 获取任何成员(字段/方法/属性等)(如果有)。getAMethod()
- 获取方法(如果有)。getAProperty()
- 获取属性(如果有)。getAnIndexer()
- 获取索引器(如果有)。getAnEvent()
- 获取事件(如果有)。getAnOperator()
- 获取运算符(如果有)。getANestedType()
- 获取嵌套类型。getNamespace()
- 获取封闭命名空间。
示例¶
查找 System.Object
的所有成员
from ObjectType object
select object.getAMember()
查找直接实现 System.Collections.IEnumerable
的所有类型
from Interface ienumerable
where ienumerable.hasQualifiedName("System.Collections.IEnumerable")
select ienumerable.getASubType()
列出 System
命名空间中的所有简单类型
select any(SimpleType t | t.getNamespace().hasName("System"))
查找所有类型为 PointerType
的变量
from Variable v
where v.fromSource()
and v.getType() instanceof PointerType
select v
列出源文件中的所有类
from Class c
where c.fromSource()
select c
可调用对象¶
可调用对象由类 Callable 表示,是指可以独立调用的任何东西,例如方法、构造函数、析构函数、运算符、匿名函数、索引器和属性访问器。
数据库包含程序和所有引用程序集中的所有可调用对象。
类层次结构¶
Element
Declaration
Callable
方法
扩展方法
构造函数
静态构造函数
实例构造函数
析构函数
运算符
一元运算符
PlusOperator
,MinusOperator
,NotOperator
,ComplementOperator
,IncrementOperator
,DecrementOperator
,FalseOperator
,TrueOperator
二元运算符
AddOperator
,SubOperator
,MulOperator
,DivOperator
,RemOperator
,AndOperator
,OrOperator
,XorOperator
,LShiftOperator
,RShiftOperator
,EQOperator
,NEOperator
,LTOperator
,GTOperator
,LEOperator
,GEOperator
转换运算符
隐式转换运算符
显式转换运算符
匿名函数表达式
Lambda 表达式
匿名方法表达式
访问器
获取器
设置器
事件访问器
AddEventAccessor
,RemoveEventAccessor
谓词¶
以下是 Callable
类的一些有用谓词。
getParameter(int)
/getAParameter()
- 获取参数。calls(Callable)
- 是否从一个可调用对象到另一个可调用对象存在直接调用。getReturnType()
- 获取返回类型。getBody()
/getExpressionBody()
- 获取可调用对象的正文。
由于 Callable
扩展了 Declaration
,它也具有来自 Declaration
的谓词,例如
getName()
/hasName(string)
getSourceDeclaration()
getName()
getDeclaringType()
方法具有额外的谓词,包括
getAnOverridee()
- 获取被此方法直接覆盖的方法。getAnOverrider()
- 获取直接覆盖此方法的方法。getAnImplementee()
- 获取此方法直接实现的接口方法。getAnImplementor()
- 获取直接实现此接口方法的方法。
示例¶
列出所有覆盖 ToString
的类型。
from Method m
where m.hasName("ToString")
select m
查找类似于 ToString
方法但没有覆盖 Object.ToString
的方法。
from Method toString, Method falseToString
where toString.hasQualifiedName("System.Object.ToString")
and falseToString.getName().toLowerCase() = "tostring"
and not falseToString.overrides*(toString)
and falseToString.getNumberOfParameters() = 0
select falseToString, "This method looks like it overrides Object.ToString but it doesn't."
查找所有接受指针类型的方法。
from Method m
where m.getAParameter().getType() instanceof PointerType
select m, "This method uses pointers."
查找所有具有析构函数但不支持 IDisposable 的类。
from Class c
where c.getAMember() instanceof Destructor
and not c.getABaseType*().hasQualifiedName("System.IDisposable")
select c, "This class has a destructor but is not IDisposable."
查找不是 private
的 Main
方法。
from Method m
where m.hasName("Main")
and not m.isPrivate()
select m, "Main method should be private."
语句¶
语句由类 Stmt 表示,构成方法(和其他可调用对象)的正文。数据库包含源代码中的所有语句,但不包含来自引用程序集的任何语句,因为这些程序集的源代码不可用。
类层次结构¶
Element
ControlFlowElement
Stmt
BlockStmt
-{ ... }
ExprStmt
SelectionStmt
IfStmt
-if
SwitchStmt
-switch
LabeledStmt
ConstCase
DefaultCase
-default
LabelStmt
LoopStmt
WhileStmt
-while(...) { ... }
DoStmt
-do { ... } while(...)
ForStmt
-for
ForEachStmt
-foreach
JumpStmt
BreakStmt
-break
ContinueStmt
-continue
GotoStmt
-goto
GotoLabelStmt
GotoCaseStmt
GotoDefaultStmt
ThrowStmt
-throw
ReturnStmt
-return
YieldStmt
YieldBreakStmt
-yield break
YieldReturnStmt
-yield return
TryStmt
-try
CatchClause
-catch
SpecificCatchClause
GeneralCatchClause
CheckedStmt
-checked
UncheckedStmt
-unchecked
LockStmt
-lock
UsingStmt
-using
LocalVariableDeclStmt
LocalConstantDeclStmt
EmptyStmt
-;
UnsafeStmt
-unsafe
FixedStmt
-fixed
示例¶
查找长方法。
from Method m
where m.getBody().(BlockStmt).getNumberOfStmts() >= 100
select m, "This is a long method!"
查找 for(;;)
。
from ForStmt for
where not exists(for.getAnInitializer())
and not exists(for.getUpdate(_))
and not exists(for.getCondition())
select for, "Infinite loop."
查找 catch(NullDefererenceException)
。
from SpecificCatchClause catch
where catch.getCaughtExceptionType().hasQualifiedName("System.NullReferenceException")
select catch, "Catch NullReferenceException."
查找条件为常量的 if
语句。
from IfStmt ifStmt
where ifStmt.getCondition().hasValue()
select ifStmt, "This 'if' statement is constant."
查找 "then" 块为空的 if
语句。
from IfStmt ifStmt
where ifStmt.getThen().(BlockStmt).isEmpty()
select ifStmt, "If statement with empty 'then' block."
(BlockStmt)
是一个内联强制转换,它将查询限制在 getThen()
的结果具有 QL 类 BlockStmt
的情况,并允许使用 BlockStmt
上的谓词,例如 isEmpty()
。
表达式¶
Expr 类表示程序中的所有 C# 表达式。表达式是指产生值的任何东西,例如 a+b
或 new List<int>()
。数据库包含来自源代码的所有表达式,但不包含来自引用程序集的任何表达式,因为这些程序集的源代码不可用。
Access
类表示对另一个 Declaration
(例如变量、属性、方法或字段)的任何使用或交叉引用。 getTarget()
谓词获取正在访问的声明。
Call
类表示对 Callable
的调用,例如对 Method
或 Accessor
的调用,而 getTarget()
方法获取正在调用的 Callable
。 Operation
类由算术运算、按位运算和逻辑运算组成。
一些表达式使用限定符,它是表达式操作的对象。一个典型的例子是 MethodCall
。在这种情况下, getQualifier()
谓词用于获取 .
左侧的表达式,而 getArgument(int)
用于获取调用的参数。
类层次结构¶
Element
ControlFlowElement
Expr
LocalVariableDeclExpr
LocalConstantDeclExpr
Operation
一元运算符
SizeofExpr
,PointerIndirectionExpr
,AddressOfExpr
二元运算符
比较运算符
相等运算符
EQExpr
,NEExpr
关系运算符
GTExpr
,LTExpr
,GEExpr
,LEExpr
赋值
赋值运算符
AddOrRemoveEventExpr
AddEventExpr
RemoveEventExpr
赋值算术运算符
AssignAddExpr
,AssignSubExpr
,AssignMulExpr
,AssignDivExpr
,AssignRemExpr
赋值按位运算符
AssignAndExpr
,AssignOrExpr
,AssignXorExpr
,AssignLShiftExpr
,AssignRShiftExpr
AssignExpr
成员初始化器
ArithmeticOperation
UnaryArithmeticOperation
UnaryMinusExpr
、UnaryPlusExpr
MutatorOperation
IncrementOperation
PreIncrExpr
、PostIncrExpr
DecrementOperation
PreDecrExpr
、PostDecrExpr
BinaryArithmeticOperation
AddExpr
、SubExpr
、MulExpr
、DivExpr
、RemExpr
按位运算符
一元按位运算符
取反运算符
二元按位运算符
LShiftExpr
,RShiftExpr
,BitwiseAndExpr
,BitwiseOrExpr
,BitwiseXorExpr
逻辑运算符
一元逻辑运算符
逻辑非运算符
二元逻辑运算符
LogicalAndExpr
,LogicalOrExpr
,NullCoalescingExpr
条件表达式
ParenthesisedExpr
,CheckedExpr
,UncheckedExpr
,IsExpr
,AsExpr
,CastExpr
,TypeofExpr
,DefaultValueExpr
,AwaitExpr
,NameofExpr
,InterpolatedStringExpr
访问
ThisAccess
BaseAccess
成员访问
方法访问
虚拟方法访问
FieldAccess
,PropertyAccess
,IndexerAccess
,EventAccess
,MethodAccess
可赋值访问
变量访问
参数访问
局部变量访问
局部作用域变量访问
字段访问
成员常量访问
属性访问
简单属性访问
虚拟属性访问
索引器访问
虚拟索引器访问
事件访问
虚拟事件访问
类型访问
数组访问
调用
属性调用
索引器调用
事件调用
方法调用
虚拟方法调用
元素初始化器
构造函数初始化器
运算符调用
变异运算符调用
委托调用
对象创建
默认值类型对象创建
类型参数对象创建
匿名对象创建
对象或集合初始化器
对象初始化器
集合初始化器
委托创建
ExplicitDelegateCreation
,ImplicitDelegateCreation
数组初始化器
数组创建
匿名函数表达式
Lambda 表达式
匿名方法表达式
字面量
BoolLiteral
,CharLiteral
,IntegerLiteral
,IntLiteral
,LongLiteral
,UIntLiteral
,ULongLiteral
,RealLiteral
,FloatLiteral
,DoubleLiteral
,DecimalLiteral
,StringLiteral
,NullLiteral
谓词¶
对Expr
有用的谓词包括
getType()
- 获取表达式的Type
。getValue()
- 获取编译时常量(如果有)。hasValue()
- 表达式是否具有编译时常量。getEnclosingStmt()
- 获取包含表达式的语句(如果有)。getEnclosingCallable()
- 获取包含表达式的可调用对象(如果有)。stripCasts()
- 删除所有显式或隐式转换。isImplicit()
- 表达式是否为隐式,例如隐式this
限定符(ThisAccess
)。
示例¶
查找仅带一个参数的String.Format
调用
from MethodCall c
where c.getTarget().hasQualifiedName("System.String.Format")
and c.getNumberOfArguments() = 1
select c, "Missing arguments to 'String.Format'."
查找所有浮点数比较
from ComparisonOperation cmp
where (cmp instanceof EQExpr or cmp instanceof NEExpr)
and cmp.getAnOperand().getType() instanceof FloatingPointType
select cmp, "Comparison of floating point values."
查找硬编码密码
from Variable v, string value
where v.getName().regexpMatch("[pP]ass(word|wd|)")
and value = v.getAnAssignedValue().getValue()
select v, "Hard-coded password '" + value + "'."
属性¶
C# 属性由类Attribute 表示。它们可以存在于许多 C# 元素上,例如类、方法、字段和参数。数据库包含来自源代码和所有程序集引用的属性。
任何Element
的属性可以通过getAnAttribute()
获取,而如果你有属性,则可以通过getTarget()
找到它的元素。这两个查询片段是相同的
attribute = element.getAnAttribute()
element = attribute.getTarget()
类层次结构¶
Element
属性
谓词¶
getTarget()
- 获取此属性适用的Element
。getArgument(int)
- 获取属性的给定参数。getType()
- 获取此属性的类型。请注意,类名必须以"Attribute"
结尾。
示例¶
查找所有已过时的元素
from Element e, Attribute attribute
where e = attribute.getTarget()
and attribute.getType().hasName("ObsoleteAttribute")
select e, "This is obsolete because " + attribute.getArgument(0).getValue()
模拟 NUnit 测试夹具
class TestFixture extends Class
{
TestFixture() {
this.getAnAttribute().getType().hasName("TestFixtureAttribute")
}
TestMethod getATest() {
result = this.getAMethod()
}
}
class TestMethod extends Method
{
TestMethod() {
this.getAnAttribute().getType().hasName("TestAttribute")
}
}
from TestFixture f
select f, f.getATest()
答案¶
练习 2¶
from File f
where f.getNumberOfLines() = max(any(File g).getNumberOfLines())
select f
练习 3¶
from StringType s
select s.getAMethod()
练习 4¶
from Interface ienumerable
where ienumerable.hasQualifiedName("System.Collections.IEnumerable")
select ienumerable.getASubType*()
练习 5¶
from Class a
where a.getName().toLowerCase().matches("a%")
select a
练习 6¶
select any(Method m | m.getBody().(BlockStmt).isEmpty())
练习 7¶
from IfStmt ifStmt
where ifStmt.getThen().(BlockStmt).isEmpty() or ifStmt.getThen() instanceof EmptyStmt
select ifStmt, "If statement with empty 'then' block."
练习 8¶
from IfStmt ifStmt
where (ifStmt.getThen().(BlockStmt).isEmpty() or ifStmt.getThen() instanceof EmptyStmt)
and not ifStmt.getElse() instanceof IfStmt
select ifStmt, "If statement with empty 'then' block."
练习 9¶
from Variable v, StringLiteral value
where v.getName().regexpMatch("[pP]ass(word|wd|)")
and value = v.getAnAssignedValue()
and value.getValue() != ""
select v, "Hard-coded password '" + value.getValue() + "'."
练习 10¶
from Method method, Attribute attribute
where method = attribute.getTarget()
and attribute.getType().hasName("ObsoleteAttribute")
select method, "This is obsolete because " + attribute.getArgument(0).getValue()
练习 11¶
from Attribute attribute
where attribute.getType().hasName("ObsoleteAttribute")
and not exists(attribute.getArgument(0))
select attribute, "Missing reason in 'Obsolete' attribute."
练习 12¶
查询不会返回参数丢失的结果。
以下是修复后的版本
from Element e, Attribute attribute, string reason
where e = attribute.getTarget()
and attribute.getType().hasName("ObsoleteAttribute")
and if exists(attribute.getArgument(0))
then reason = attribute.getArgument(0).getValue()
else reason = "(not given)"
select e, "This is obsolete because " + reason