C# 的 CodeQL 库¶
分析 C# 程序时,可以使用 C# 的 CodeQL 库中大量类。
关于 C# 的 CodeQL 库¶
有一个广泛的核心库用于分析从 C# 项目中提取的 CodeQL 数据库。该库中的类以面向对象的形式呈现数据库中的数据,并提供抽象和谓词来帮助您完成常见的分析任务。该库实现为一组 QL 模块,即扩展名为 .qll 的文件。模块 csharp.qll 导入所有核心 C# 库模块,因此您可以通过在查询开头包含以下内容来包含整个库:
import csharp
由于所有 C# 查询都需要此,因此它在下面的代码片段中被省略了。
核心库包含所有程序元素,包括 文件、类型、方法、变量、语句 和 表达式。这足以满足大多数查询,但是可以导入其他库来实现特定功能,例如控制流和数据流。有关这些其他库的信息,请参见“C# 的 CodeQL。”
类层次结构¶
每节包含一个类层次结构,展示 CodeQL 类之间的继承结构。例如
ExprOperationArithmeticOperationUnaryArithmeticOperationUnaryMinusExpr、UnaryPlusExprMutatorOperationIncrementOperationPreIncrExpr、PostIncrExpr
DecrementOperationPreDecrExpr、PostDecrExpr
BinaryArithmeticOperationAddExpr、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。
类层次结构¶
LocationSourceLocationAssembly
谓词¶
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 是程序中所有定义的实体的公共类,例如类型、方法、变量等。数据库包含源代码和所有引用程序集中的所有声明。
类层次结构¶
ElementDeclarationCallableUnboundGenericConstructedGenericModifiable- 可修改声明,可以包含修饰符(例如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# 变量,例如字段、参数和局部变量。数据库包含来自源代码的所有变量,以及程序引用的程序集中的所有字段和参数。
类层次结构¶
ElementDeclarationVariable- 任何类型的变量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。
类层次结构¶
ElementDeclarationModifiable- 可修改声明,可以包含修饰符(例如public)Member- 属于类型的声明Type- 所有类型ValueOrRefType- 定义的类型ValueType- 值类型(见下文了解更详细的层次结构)RefType- 引用类型(见下文了解更详细的层次结构)NestedType- 在另一个类型中定义的类型
VoidType-voidPointerType- 指针类型
类 ValueType 扩展为
ValueType- 值类型SimpleType- 简单内置类型BoolType-boolCharType-char整数类型无符号整数类型ByteType-byteUShortType-unsigned short/System.UInt16UIntType-unsigned int/System.UInt32ULongType-unsigned long/System.UInt64
有符号整数类型SByteType-signed byteShortType-short/System.Int16IntType-int/System.Int32LongType-long/System.Int64
浮点类型FloatType-float/System.SingleDoubleType-double/System.Double
DecimalType-decimal/System.Decimal
Enum-enumStruct-struct可空类型数组类型
类 RefType 扩展为
RefTypeClass-class匿名类ObjectType-object/System.ObjectStringType-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 表示,是指可以独立调用的任何东西,例如方法、构造函数、析构函数、运算符、匿名函数、索引器和属性访问器。
数据库包含程序和所有引用程序集中的所有可调用对象。
类层次结构¶
ElementDeclarationCallable方法扩展方法
构造函数静态构造函数实例构造函数
析构函数运算符一元运算符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 表示,构成方法(和其他可调用对象)的正文。数据库包含源代码中的所有语句,但不包含来自引用程序集的任何语句,因为这些程序集的源代码不可用。
类层次结构¶
ElementControlFlowElementStmtBlockStmt-{ ... }ExprStmtSelectionStmtIfStmt-ifSwitchStmt-switch
LabeledStmtConstCaseDefaultCase-defaultLabelStmt
LoopStmtWhileStmt-while(...) { ... }DoStmt-do { ... } while(...)ForStmt-forForEachStmt-foreach
JumpStmtBreakStmt-breakContinueStmt-continueGotoStmt-gotoGotoLabelStmtGotoCaseStmtGotoDefaultStmt
ThrowStmt-throwReturnStmt-returnYieldStmtYieldBreakStmt-yield breakYieldReturnStmt-yield return
TryStmt-tryCatchClause-catchSpecificCatchClauseGeneralCatchClause
CheckedStmt-checkedUncheckedStmt-uncheckedLockStmt-lockUsingStmt-usingLocalVariableDeclStmtLocalConstantDeclStmt
EmptyStmt-;UnsafeStmt-unsafeFixedStmt-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) 用于获取调用的参数。
类层次结构¶
ElementControlFlowElementExprLocalVariableDeclExprLocalConstantDeclExpr
Operation一元运算符SizeofExpr,PointerIndirectionExpr,AddressOfExpr
二元运算符比较运算符相等运算符EQExpr,NEExpr关系运算符GTExpr,LTExpr,GEExpr,LEExpr
赋值赋值运算符AddOrRemoveEventExprAddEventExprRemoveEventExpr
赋值算术运算符AssignAddExpr,AssignSubExpr,AssignMulExpr,AssignDivExpr,AssignRemExpr
赋值按位运算符AssignAndExpr,AssignOrExpr,AssignXorExpr,AssignLShiftExpr,AssignRShiftExpr
AssignExpr成员初始化器
ArithmeticOperationUnaryArithmeticOperationUnaryMinusExpr、UnaryPlusExprMutatorOperationIncrementOperationPreIncrExpr、PostIncrExpr
DecrementOperationPreDecrExpr、PostDecrExpr
BinaryArithmeticOperationAddExpr、SubExpr、MulExpr、DivExpr、RemExpr
按位运算符一元按位运算符取反运算符
二元按位运算符LShiftExpr,RShiftExpr,BitwiseAndExpr,BitwiseOrExpr,BitwiseXorExpr
逻辑运算符一元逻辑运算符逻辑非运算符
二元逻辑运算符LogicalAndExpr,LogicalOrExpr,NullCoalescingExpr
条件表达式
ParenthesisedExpr,CheckedExpr,UncheckedExpr,IsExpr,AsExpr,CastExpr,TypeofExpr,DefaultValueExpr,AwaitExpr,NameofExpr,InterpolatedStringExpr访问ThisAccessBaseAccess成员访问方法访问虚拟方法访问
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