該庫被實現為一個QL模塊,是一個后綴為.qll的文件,即java.qll,該模塊導入了所有核心Java庫模塊,因此,我們可以在查詢的開始位置通過以下方式來引入完整的java庫:
import java
庫類總結
標准Java庫中重要的類可以分為5種:
- 表示編程元素的類(例如類和方法)
- 表示AST節點的類(例如語句和表達式)
- 表示元數據的類(例如注解和注釋)
- 用於計算指標的類(例如條件復雜度和耦合度)
- 用於瀏覽程序調用圖的類
我們將依次對每種類型進行討論,簡要描述每種類型中比較重要的類。
編程元素
這些類是用來命名編程元素的:包(Package),編譯單元(CompilationUnit),類型(Type),方法(Method),構造函數(Constructor)和變量(Variable)。
Element是它們的通用超類,提供通用成員謂詞,用於確定編程元素的名稱並檢查兩個元素是否相互嵌套。
Callable類是Method和Constructor的通用超類,可以很方便地引用方法或構造函數的元素
類型
Type類有很多子類,用於表示不同的類型:
PrimitiveType表示基本類型,即boolean,byte,char,double,float,int,long,short等; QL還將void和<nulltype>(null字符串的類型)分類為原始類型。
RefType表示引用(即非原始)類型,它具有幾個子類:
- Class 表示一個Java類。
- Interface 表示一個Java接口。
- EnumType代表Java enum類型。
- Array 表示Java數組類型。
例如,以下查詢查找程序中所有int類型的變量:
import java from Variable v, PrimitiveType pt where pt = v.getType() and pt.hasName("int") select v
引用類型也根據其聲明范圍進行分類:
TopLevelType 表示在編譯單元的頂層聲明的引用類型。
NestedType 是在另一個類型內聲明的類型。
例如,此查詢查找名稱與其編譯單元名稱不同的所有頂級類型:
import java from TopLevelType tl where tl.getName() != tl.getCompilationUnit().getName() select tl
還提供了更多專業類:
TopLevelClass 表示在編譯單元的頂層聲明的類。
NestedClass表示在另一種類型內聲明的類,例如:
LocalClass,在方法或構造函數內部聲明的類。
AnonymousClass,匿名類。
最后,CodeQL java庫也有一些單例類包裝了常用的Java標准庫類,如:TypeObject,TypeCloneable,TypeRuntime,TypeSerializable,TypeString,TypeSystem和TypeClass。每個CodeQL類代表其類名所建議的標准Java類。
例如,我們可以編寫一個查詢來查找所有直接繼承自Object的嵌套類:
import java from NestedClass nc where nc.getASupertype() instanceof TypeObject select nc
泛型
還有一些Type子類用於處理泛型類型。
GenericType是 GenericInterface或GenericClass中的一種。它表示通用類型聲明,例如Java標准庫中的java.util.Map接口:
package java.util.; public interface Map<K, V> { int size(); // ... }
類型參數(例如本示例中的K和V)表示TypeVariable類。
泛型類型的參數化實例提供了一個具體類型來實例化類型參數,如Map<String, File>。這樣的類型由ParameterizedType表示,與GenericType表示泛型實例不同。要從ParameterizedType 轉到其對應的GenericType,我們可以使用getSourceDeclaration謂詞。
例如,我們可以使用以下查詢來查找java.util.Map的所有參數化實例:
import java from GenericInterface map, ParameterizedType pt where map.hasQualifiedName("java.util", "Map") and pt.getSourceDeclaration() = map select pt
通常,通用類型可能會限制某個類型參數可以綁定到哪些類型。例如,從字符串到數字的轉換可以按如下所示聲明:
class StringToNumMap<N extends Number> implements Map<String, N> { // ... }
這意味着StringToNumberMap參數化實例只能實例化Number類型或其子類型之一的類型參數N,而不能實例化File。我們說N是一個有界類型參數,Number是其上限。在QL中,可以使用謂詞getATypeBound來查詢類型變量的類型界限。類型范圍本身由TypeBound類表示,該類通過一個成員謂詞getType來檢索變量所限制的類型。
例如,以下查詢查找所有限制為Number類型的類型變量:
import java from TypeVariable tv, TypeBound tb where tb = tv.getATypeBound() and tb.getType().hasQualifiedName("java.lang", "Number") select tv
為了處理不識別泛型的舊代碼,每個泛型類型都有一個“原始”版本,沒有任何類型參數。在CodeQL庫中,原始類型使用RawType類表示,該類擁有子類RawClass和RawInterface。同樣,有一個謂詞getSourceDeclaration可以獲取相應的泛型類型。例如,我們可以找到(原始)類型Map的變量:
import java from Variable v, RawType rt where rt = v.getType() and rt.getSourceDeclaration().hasQualifiedName("java.util", "Map") select v
例如,在以下代碼段中,此查詢將找到m1,但找不到m2:
Map m1 = new HashMap(); Map<String, String> m2 = new HashMap<String, String>();
最后,變量可以聲明為通配符類型:
Map<? extends Number, ? super Float> m;
通配符? extends Number和? super Float由類WildcardTypeAccess表示。像類型參數一樣,通配符也可能具有類型界限。與類型參數不同,通配符可以有上限(如? extends Number),也可以有下限(如? super Float)。WildcardTypeAccess類提供成員謂詞getUpperBound和getLowerBound分別用於檢索上限和下限。
對於泛型方法,有GenericMethod、ParameterizedMethod和RawMethod類,與表示泛型類型的相似命名類類似。
變量
從Java的角度看Variable類表示一個變量,它可以是類的成員變量(無論是靜態的還是非靜態的),或者是局部變量或參數。因此,有三個子類可以滿足這些特殊情況:
- Field 表示一個Java字段。
- LocalVariableDecl 代表局部變量。
- Parameter 表示方法或構造函數的參數。
抽象語法樹
這個類別中的類表示抽象語法樹(AST)節點,即語句(Stmt類)和表達式(Expr類)。有關標准QL庫中可用的表達式和語句類型的完整列表,請參見用於Java程序的抽象語法樹類。
Expr和Stmt類都提供成員謂詞,用於探究程序的抽象語法樹:
- Expr.getAChildExpr 返回給定表達式的子表達式。
- Stmt.getAChild 返回直接嵌套在給定語句內的語句或表達式。
- Expr.getParent和Stmt.getParent 返回AST節點的父節點。
例如,以下查詢查找父節點為return語句的所有表達式:
import java from Expr e where e.getParent() instanceof ReturnStmt select e
因此,如果程序包含return語句return x + y;,則此查詢將返回x + y
另一個示例,以下查詢查找其父節點為if語句的所有語句:
import java from Stmt s where s.getParent() instanceof IfStmt select s
該查詢將找到程序中所有if語句的then分支和else分支。
最后一個示例,這是一個查找方法主體的查詢:
import java from Stmt s where s.getParent() instanceof Method select s
正如這些示例所示,表達式的父節點並不總是一個表達式:它也可以是一條語句,例如 IfStmt。同樣,一條語句的父節點並不總是一條語句:它也可以是方法或構造函數。為了捕獲這一點,QL Java庫提供了兩個抽象類ExprParent和StmtParent,前者表示可能是表達式的父節點的任何節點,而后者則表示可能是語句的父節點的任何節點。
元數據
Java程序除了程序代碼外,還具有幾種元數據。特別是有注解和Javadoc注釋。由於這些元數據對於增強代碼分析以及本身作為一個分析主題都很有意思,所以QL庫定義了用於訪問它的類。
對於注解,Annotatable類是所有可以注解的程序元素的超類。這包括包,引用類型,字段,方法,構造函數和局部變量聲明。對於每個這樣的元素,其謂詞getAnAnnotation可以檢索任何該元素可能具有的注解。例如,以下查詢用於查找構造函數的所有注解:
import java from Constructor c select c.getAnAnnotation()
這些注解由Annotation類表示。一個注解只是類型為AnnotationType的一個簡單表達式。例如,您可以修改上面的查詢,以使其僅報告Deprecated注解的構造函數:
import java from Constructor c, Annotation ann, AnnotationType anntp where ann = c.getAnAnnotation() and anntp = ann.getType() and anntp.hasQualifiedName("java.lang", "Deprecated") select ann
有關使用注解的更多信息,請參閱關於注解的文章。
對於Javadoc,Element類具有一個成員謂詞getDoc,該成員謂詞返回一個委托Documentable對象,然后可以查詢該委托對象的附加Javadoc注釋。例如,以下查詢在私有字段中查找Javadoc注釋:
import java from Field f, Javadoc jdoc where f.isPrivate() and jdoc = f.getDoc().getJavadoc() select jdoc
Javadoc類將整個Javadoc注釋表示為JavadocElement節點樹,可以使用成員謂詞getAChild和getParent進行遍歷。例如,可以編輯如下查詢在私有字段的Javadoc注釋中找到所有@author標簽:
import java from Field f, Javadoc jdoc, AuthorTag at where f.isPrivate() and jdoc = f.getDoc().getJavadoc() and at.getParent+() = jdoc select at
注意
在第5行,通過使用getParent+捕獲了嵌套在Javadoc注釋內任何深度的標簽。
有關使用Javadoc的更多信息,請參閱Javadoc上的文章。
指標
標准的QL Java庫為Java程序元素上的計算指標提供了廣泛的支持。為了避免用太多與度量計算相關的成員謂詞來使代表那些元素的類負擔過多,請改為在委托類上使用這些謂詞。
總共有六個這樣的類:MetricElement,MetricPackage,MetricRefType,MetricField,MetricCallable和MetricStmt。每個對應的元素類都提供一個成員謂詞getMetrics,該成員謂詞可用於獲取委托類的實例,然后可以在其上執行度量計算。
例如,以下查詢查找循環復雜度大於40的方法:
import java from Method m, MetricCallable mc where mc = m.getMetrics() and mc.getCyclomaticComplexity() > 40 select m
調用圖形
從Java代碼庫生成的CodeQL數據庫包含相關程序的調用流程圖的預先計算的信息,即給定的調用在運行時可以分派給哪些方法或構造函數。
上面介紹的Callable類,包括方法和構造函數。調用表達式被抽象為使用Call類,包含方法調用、new表達式以及使用this或super的顯式構造函數調用。
我們可以使用謂詞Call.getCallee來找出特定調用表達式所指向的方法或構造函數。例如,以下查詢查找println方法的所有調用:
import java from Call c, Method m where m = c.getCallee() and m.hasName("println") select c
相反,Callable.getAReference會返回一個引用它的Call。因此,我們可以通過如下查詢找到從未調用的方法和構造函數:
import java from Callable c where not exists(c.getAReference()) select c