解讀程式碼檢查規則語言CodeNavi的表示式節點和屬性

华为云开发者联盟發表於2024-07-12

本文分享自華為雲社群《CodeNavi 中程式碼表示式的節點和節點屬性》,作者: Uncle_Tom。

根據程式碼檢查中的一些痛點,提出了希望尋找一種適合編寫靜態分析規則的語言。

  • 可以滿足使用者對程式碼檢查不斷增加的各種需求;
  • 使使用者能夠透過增加或減少對檢查約束條件的控制,實現快速調整檢查中出現的誤報和漏報;
  • 這種檢查語言能夠有較低的使用門檻,使使用者更專注於檢查業務,而不需要關注工具是如何實現的。

我們稱這種檢查規則語言為:CodeNavi。本文將繼續介紹 CodeNavi 檢查規則語言如何描述程式碼中的表示式。這些節點主要包括:

  • 3.1. 物件建立表示式(objectCreationExpression)
  • 3.2. 強制型別轉換(castExpression)
  • 3.3. 型別判斷表示式(instanceofExpression)
  • 3.4. 一元表示式(unaryOperation)
  • 3.5. 二元表示式(binaryOperation)
  • 3.6. 條件表示式/三目運算(ternaryOperation)
  • 3.7. 方法引用表示式(methodReferenceExpression)
  • 3.8. lambda表示式(lambdaExpression)
  • 3.9. 匿名內部類表示式(anonymousInnerClassExpression)

2. CodeNavi 中的節點和節點屬性

程式是由空格分隔的字串組成的序列。在程式分析中,這一個個的字串被稱為"token",是原始碼中的最小語法單位,是構成程式語言語法的基本元素。

Token可以分為多種型別,常見的有關鍵字(如if、while)、識別符號(變數名、函式名)、字面量(如數字、字串)、運算子(如+、-、*、/)、分隔符(如逗號,、分號;)等。

我們只需要給程式碼的不同節點給出一個定義,然後透過條件語句來描述對這些節點的要求,使之符合缺陷檢查的模式,就可以完成檢查規則的定義。

2.1. 規則節點和節點屬性圖例

2.1.1. 節點

圖例

節點和子節點都使用個圖例

規則語言中使用節點的 “英文名”,這樣便於規則的編寫。

2.2. 節點集

圖例

節點的集合。

2.3. 屬性

圖例

規則語言中使用屬性的 “英文名”,這樣便於規則的編寫。

3. 表示式

表示式是程式中用於產生一個值或執行一個操作的程式碼片段。表示式可以簡單到只有一個字面量或變數,也可以複雜到包含多個運算子和子表示式。
表示式在程式中的作用非常廣泛,它們是程式邏輯和資料處理的基礎。表示式是構成程式的基本元素之一,它用於表示資料的計算或操作, 幫助開發者構建程式的邏輯和功能。

以下是表示式的一些主要作用:

  • 產生值,表示式的主要作用是產生一個值。例如,5 + 3 產生值 8。

  • 算術運算,表示式可以執行基本的算術運算,如加(+)、減(-)、乘(*)、除(/)、模(%)。

  • 關係運算,表示式可以進行比較,產生布林值(true或false)。例如,5 > 3 產生 true。

  • 邏輯運算,表示式可以進行邏輯運算,如邏輯與(&&)、邏輯或(||)、邏輯非(!)。

  • 條件運算,三元條件運算子(? :)允許基於條件表示式的結果選擇兩個值中的一個。例如,max = (a > b) ? a : b;。

  • 建立陣列,表示式可以用於宣告和初始化陣列。例如,new int[10];。

  • 建立物件,表示式可以用於建立物件例項。例如,new Integer(10)。

  • 型別轉換,表示式可以進行型別轉換,將一個型別的值轉換為另一個型別。例如,(int) 3.14。

  • 建立和使用Lambda表示式,Lambda表示式,允許以簡潔的語法表示匿名函式。例如,() -> System.out.println(“Hello”);。

  • 控制流語句,表示式的結果可以用於控制流語句,如if、while、for等。

  • 異常處理,表示式可以與異常處理結合使用,如在try-catch塊中。

3.1. 物件建立表示式(objectCreationExpression)

表示式用於建立物件例項。

程式碼樣例
new ArrayList();

圖例

名稱描述值型別示例DSL 規則
name 方法名 字串 new MethodRefTypeTest(); objectCreationExpression obc where
obc.name == “MethodRefTypeTest”;
type 返回值型別 objectType節點 new MethodRefTypeTest(); objectCreationExpression obc where
obc.type.name == “com.huawei.secbrella.kirin.test.sourcefile.methodRefType.MethodRefTypeTest”;
function 呼叫的構造方法 functionDeclaration節點 public class MethodRefTypeTest {
MethodRefTypeTest() {}

public static void main(String[] args) {
new MethodRefTypeTest();
}
objectCreationExpression obc where
obc.function.name == “MethodRefTypeTest”;
arguments 入參集合 valueAccess類節點、functionCall節點等的集合 new String(“Hello”); objectCreationExpression obc where
obc.arguments.size() == 1;
arguments[n] 第n個入參 valueAccess類節點、functionCall節點等的集合 new String(“Hello”); objectCreationExpression obc where
obc.arguments[0].value == “Hello”;

3.2. 強制型別轉換(castExpression)

型別強制轉換(Type Casting)是指將一個型別的物件轉換成另一個型別的物件。

程式碼樣例
double d = 10.5;
// 強制轉換,需要顯式指定目標型別
int i = (int) d;

Object obj = new String("Hello");
// 安全的向下轉型
String str = (String) obj; 
圖例

名稱描述值型別示例DSL 規則
castType 目標型別 node ArrayList second = (ArrayList) list; castExpression ce where
ce.castType.name == “java.util.ArrayList”;
operand 操作物件 node ArrayList second = (ArrayList) list; castExpression ce where
ce.operand.name == “list”;

3.3. 型別判斷表示式(instanceofExpression)

instanceof 關鍵字用於檢查一個物件是否是特定類的例項或者是其子類的例項。

程式碼樣例
if (animal instanceof Dog) {
    ((Dog) animal).makeSound();
}

Object[] objects = new Object[10];
if (objects instanceof Object[]) {
    System.out.println("objects is an array of Object");
}
圖例

名稱描述值型別示例DSL 規則
lhs 左值 任意節點 father instanceof Father instanceofExpression ie where
ie.lhs.name == “father”;
rhs 右值,型別值 全類名常量 list instanceof List instanceofExpression ie where
ie.rhs.name == “java.util.List”;

3.4. 一元表示式(unaryOperation)

一元表示式包含單個運算元。

程式碼樣例
// 一元算術表示式
// 遞增:++x(將 x 的值增加 1)
// 遞減:--x(將 x 的值減少 1)
// 前置遞增/遞減:

int a = 5;
int b = ++a; // a 和 b 都是 6
int c = a--; // a 是 6, c 是 5

// 後置遞增/遞減:

int a = 5;
int b = a++; // a 是 6, b 是 5
int c = a--; // a 是 5, c 是 6

// 一元邏輯表示式
// 邏輯非:!x(如果 x 為 true,則結果為 false,反之亦然)
boolean flag = true;
boolean notFlag = !flag; // notFlag 是 false

// 一元位運算表示式
// 按位取反:~x(將 x 的每個位取反)
int num = 5; // 二進位制表示為 101
int bitNotNum = ~num; // 結果是 -6, 二進位制表示為 110...(32 個 1),因為整數溢位
圖例

名稱描述值型別示例DSL 規則
isPrefix 是否運算子前置 布林值 a++; unaryOperation uo where uo.isPrefix == false;
operand 操作物件 任意節點 i++; unaryOperation uo where uo.operand.name == “i”;
operator 運算子 字串 i++; unaryOperation uo where uo.operator == “++”;

3.5. 二元表示式(binaryOperation)

二元表示式包含兩個運算元,並且返回一個單一的值。

程式碼樣例
// 算術二元表示式
// 加法:x + y
// 減法:x - y
// 乘法:x * y
// 除法:x / y
// 取模(求餘數):x % y
int a = 10;
int b = 3;
int sum = a + b; // 13
int difference = a - b; // 7
int product = a * b; // 30
int quotient = a / b; // 3
int remainder = a % b; // 1

// 比較二元表示式
// 等於:x == y
// 不等於:x != y
// 大於:x > y
// 小於:x < y
// 大於等於:x >= y
// 小於等於:x <= y
int x = 5;
int y = 10;
boolean isEqual = (x == y); // false
boolean isNotEqual = (x != y); // true
boolean isLessThan = (x < y); // true
boolean isGreaterThan = (x > y); // false
boolean isLessThanOrEqual = (x <= y); // true
boolean isGreaterThanOrEqual = (x >= y); // false

// 邏輯二元表示式
// 邏輯與:x && y
// 邏輯或:x || y
// 邏輯異或:x ^ y
// 條件與(&):x & y(當 x 為 true 時才判斷 y)
// 條件或(|):x | y(當 x 為 true 時,y 的結果將被忽略)
boolean condition1 = true;
boolean condition2 = false;
boolean andResult = condition1 && condition2; // false
boolean orResult = condition1 || condition2; // true
boolean xorResult = condition1 ^ condition2; // true

int num1 = 5;
int num2 = 3;
boolean bitAndResult = num1 & num2; // true, 因為 5 & 3 的二進位制表示都是 101

// 位運算二元表示式
// 位與:x & y
// 位或:x | y
// 位異或:x ^ y
// 位左移:x << n(將 x 的二進位制表示向左移動 n 位)
// 位右移(算術):x >> n(將 x 的二進位制表示向右移動 n 位,右邊用符號位填充)
// 位右移(邏輯):x >>> n(將 x 的二進位制表示向右移動 n 位,右邊用0填充)
int number = 5; // 在記憶體中的表示是 101
int resultBitwiseAnd = number & 3; // 結果是 1, 因為 101 & 011 = 001
int resultBitwiseOr = number | 3; // 結果是 7, 因為 101 | 011 = 111
int resultBitwiseXor = number ^ 3; // 結果是 2, 因為 101 ^ 011 = 110

int resultLeftShift = number << 1; // 結果是 10, 因為 101 向左移動一位變成 1010
int resultRightShift = number >> 1; // 結果是 2, 因為 101 向右移動一位變成 10
int resultRightShiftLogical = number >>> 1; // 結果是 2, 邏輯右移,忽略符號位

// 賦值二元表示式
// 簡單賦值:x = y
// 加等於:x += y
// 減等於:x -= y
// 乘等於:x *= y
// 除等於:x /= y
// 模等於:x %= y
// 位與等於:x &= y
// 位或等於:x |= y
// 位異或等於:x ^= y
// 位左移等於:x <<= n
// 位右移等於:x >>= n
// 位右移邏輯等於:x >>>= n
int num = 5;
num += 3; // num 現在是 8
num *= 2; // num 現在是 16
num /= 4; // num 現在是 4
num %= 3; // num 現在是 1
圖例

名稱描述值型別示例DSL 規則
lhs 二元表示式的左值 literal類節點、valueAccess類節點、functionCall類節點 int i = 1; binaryOperation bo where bo.lhs.name == “i”;
operator 二元表示式的運算子 字串 while (i == 1) binaryOperation bo where bo.operator == “==”;
rhs 二元表示式的右值 literal類節點、valueAccess類節點、functionCall類節點 a > b; binaryOperation bo where bo.rhs.name == “b”;
operands 二元表達的操作物件(左值和右值) 節點集合 a > b; binaryOperation bo where
bo.operands contain op where
op.name == “a”;

3.6. 條件表示式/三目運算(ternaryOperation)

條件表示式,也稱為三目運算子(Ternary Operator),是一種簡潔的條件語句,格式如下:

result = condition ? value_if_true : value_if_false;

這裡的 condition 是一個布林表示式,value_if_true 是當條件為 true 時的結果,而 value_if_false 是當條件為 false 時的結果。這個表示式的結果 result 將是兩個值中的一個,取決於條件的真假。

樣例程式碼
// 基本使用
int a = 10;
int b = 20;
// max 將被賦值為 20,因為 20 大於 10
int max = (a > b) ? a : b; 

// 巢狀使用
int x = 5;
// result 將是 "x 大於 5"
String result = (x > 10) ? "x 大於 10" : ((x > 5) ? "x 大於 5" : "x 小於等於 5");

// 與賦值結合
int score = 85;
// grade 將被賦值為 "B"
String grade = (score >= 90) ? "A" : ((score >= 80) ? "B" : "C");

// 用於方法呼叫
// outcome 將隨機是 "Heads" 或 "Tails"
String outcome = (Math.random() > 0.5) ? "Heads" : "Tails";

// 與迴圈結合

for (int i = 0; i < 10; i++) {
    // 將列印出 0, -1, 4, -3, 等等
    int value = (i % 2 == 0) ? i * i : -i;
    System.out.println(value);
}
圖例

名稱描述值型別示例DSL 規則
condition 判斷條件 binaryOperation boolean res = num > 1 ? true : false; ternaryOperation tp where
tp.condition.lhs.name == “num”;
thenExpression 真的操作 任意節點 boolean res = num > 1 ? true : false; ternaryOperation tp where
tp.thenExpression contain literal ll where
ll.value == true;
elseExpression 假的操作 任意節點 String str = num > 1 ? “true” : “false”; ternaryOperation tp where
tp.elseExpression contain literal ll where
ll.value == “false”;

3.7. 方法引用表示式(methodReferenceExpression)

方法引用(Method References),它是一種快捷的Lambda表示式,允許你直接引用已有方法或建構函式。方法引用通常用於實現簡單的函式式介面。

樣例程式碼
// 靜態方法引用

List<String> list = Arrays.asList("a", "b", "c");
// System.out::println 是一個方法引用,它引用了 System.out 物件的 println 靜態方法。
list.forEach(System.out::println);

// 例項方法引用
// 當Lambda體需要呼叫一個例項方法時,第一個引數將成為方法的呼叫者:

List<String> list = Arrays.asList("hello", "world");
// String::toLowerCase 是一個方法引用,它引用了 String 類的 toLowerCase 例項方法。
String result = list.stream()
                     .map(String::toLowerCase)
                     .collect(Collectors.joining(" "));
// result 將是 "hello world"

// 建構函式引用
// StringBuilder::new 是一個建構函式引用,它引用了 StringBuilder 類的建構函式。
Function<String, StringBuilder> constructor = StringBuilder::new;
StringBuilder sb = constructor.apply("Hello World");

// 特定類的任意物件的例項方法引用
// 當Lambda的第一個引數是某個類的物件時,可以使用類名加 :: 加方法名的方式引用該類的例項方法:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// upperCaseNames 包含了 "ALICE", "BOB", "CHARLIE"
List<String> upperCaseNames = names.stream()
                                   .map(String::toUpperCase)
                                   .collect(Collectors.toList());

// 陣列的例項方法引用
// 對於陣列,可以使用陣列型別加 ::new 來引用陣列的構造方法:

// String[]::new 是一個方法引用,它引用了 String[] 型別的構造方法。
Function<Integer, String[]> creator = String[]::new;
// 建立了一個長度為3的String陣列
String[] strings = creator.apply(3);
圖例

名稱描述值型別示例DSL 規則
name 方法名 字串 Supplier<Integer> size = list::size; methodReferenceExpression mf where
mf.name == “size”;
function 呼叫的方法 functionDeclaration節點 Supplier<Integer> size = list::size; methodReferenceExpression mf where
mf.function.name == “size”;
type 方法返回值型別 objectType節點 Supplier<Integer> size = list::size; methodReferenceExpression mf where
mf.type.name == “int”;

3.8. lambda表示式(lambdaExpression)

Lambda 表示式是一種簡潔的匿名函式表示式,允許你將行為作為引數傳遞給方法或儲存在變數中。Lambda 表示式提供了一種非常靈活和強大的方式來處理函數語言程式設計,使得程式碼更加簡潔和表達性強。

樣例程式碼
// 基本 Lambda 表示式
// 這個 Lambda 表示式沒有引數,執行的操作是列印 "Hello, World!"。
() -> System.out.println("Hello, World!");

// 帶引數的 Lambda 表示式
// 這個 Lambda 表示式接受兩個引數 x 和 y,執行的操作是將它們相加。
(x, y) -> x + y

// 使用 Lambda 表示式實現 Runnable
// 這裡建立了一個 Runnable 例項,它將在呼叫 run 方法時執行 Lambda 表示式。
Runnable runnable = () -> System.out.println("I'm running on a thread!");
runnable.run();

// 使用 Lambda 表示式排序集合
// 在這個例子中,Lambda 表示式實現了 Comparator 介面,用於按字典順序排序字串。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Collections.sort(names, (name1, name2) -> name1.compareTo(name2));

// 使用 Lambda 表示式過濾集合
// 這裡使用 Lambda 表示式建立了一個流,過濾出長度小於 5 的名字。
List<String> shortNames = names.stream()
                               .filter(name -> name.length() < 5)
                               .collect(Collectors.toList());

// 輸出集合中的單數
list.forEach(integer -> {
    if (integer % 2 == 1) {
        System.out.println("單數");
    }
});

// 使用 Lambda 表示式轉換集合
// 在這個例子中,Lambda 表示式用於將每個名字轉換為其長度。
List<Integer> lengths = names.stream()
                              .map(name -> name.length())
                              .collect(Collectors.toList());

// 使用 Lambda 表示式實現簡單的函式
// Lambda 表示式 name -> name.length() 實現了 Function 介面,用於返回字串的長度。
Function<String, Integer> lengthFunction = name -> name.length();
int length = lengthFunction.apply("Hello");
// 使用 Lambda 表示式實現消費者
// Lambda 表示式 name -> System.out.println(name.toUpperCase()) 實現了 Consumer 介面,用於列印字串的大寫形式。
Consumer<String> consumer = name -> System.out.println(name.toUpperCase());
consumer.accept("Alice");

// 使用 Lambda 表示式實現供應商
// Lambda 表示式 () -> "Hello, Lambda!" 實現了 Supplier 介面,用於提供字串。
Supplier<String> supplier = () -> "Hello, Lambda!";
String message = supplier.get();
圖例

名稱描述值型別示例DSL 規則
parameters 引數 paramDeclaration節點 list.forEach(integer -> {
if (integer == 1) {
System.out.println(“單數”);
}
});
lambdaExpression le where
le.parameters contain p where p.name == “integer”;
body 方法體 block語句塊 list.forEach(integer -> {
if (integer == 1) {
System.out.println(“單數”);
}
});
lambdaExpression le where
le.body contain variableAccess va where va.name == “integer”;
body.statementNum 語句數量 數值 list.forEach(integer -> {
if (integer == 1) {
System.out.println(“單數”);
}
});
lambdaExpression le where
le.body.statementNum == 1;
firstStatement 第一條語句 任意節點 list.forEach(integer -> {
if (integer == 2) {
System.out.println(“單數”);
}
});
lambdaExpression le where
le.firstStatement contain ifBlock;
lastStatement 最後一條語句 任意節點 list.forEach(integer -> {
if (integer == 3) {
System.out.println(“單數”);
}
System.out.print(“雙數”);
});
lambdaExpression le where
le.lastStatement contain functionCall fc where
fc.name == “print”;

3.9. 匿名內部類表示式(anonymousInnerClassExpression)

匿名內部類是當需要建立一個僅用於一次使用的類時使用的一種特殊類。它沒有名稱,並且通常是在宣告的同時例項化。

匿名內部類提供了一種快速實現介面或繼承類的方法,而無需定義一個具體的類名。這在建立一次性使用的類時非常有用,可以減少程式碼的冗餘。

樣例程式碼
// 作為方法引數
// 呼叫方法時使用匿名內部類
// 假設有一個方法需要一個 List 作為引數,我們可以直接在呼叫時建立一個匿名內部類:
// 這裡,我們建立了一個實現了 ArrayList 的匿名內部類,並在構造器中新增了元素。
process(new ArrayList<String>() {
    {
        add("Item 1");
        add("Item 2");
    }
});

// 實現介面
// 在這個例子中,我們建立了一個實現了 Runnable 介面的匿名內部類,並重寫了 run 方法。
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from a thread!");
    }
}).start();
// 重寫介面的多個方法

// 使用 Comparator 進行排序
// 在這個例子中,我們建立了一個實現了 Comparator 介面的匿名內部類,用於按字典順序比較字串。
List<String> names = Arrays.asList("Bob", "Alice", "Eve");
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return o1.compareTo(o2);
    }
});
// 如果需要實現一個介面並重寫多個方法,也可以使用匿名內部類:
new SomeInterface() {
    @Override
    public void method1() {
        // 實現細節
    }

    @Override
    public void method2() {
        // 實現細節
    }
};
// 繼承一個類
// 匿名內部類也可以繼承一個類,並重寫其方法:
// 在這個例子中,我們建立了一個繼承自 SomeClass 的匿名內部類,並重寫了 someMethod 方法。
new SomeClass() {
    @Override
    public void someMethod() {
        // 重寫方法
    }
};

new MemberClass() {  // 告警行
    @Override
    public void f() {
        int i = 0;
        System.out.println("aaaaa");
    }
}.f();
圖例

名稱描述值型別示例DSL 規則
name 名字 字串 public static void main(String[] args) {
new MemberClass() {
@Override
public void f() {
}
}.f();
}
anonymousInnerClassExpression ac where and(
ac.name == “MemberClass”,
ac.enclosingFunctionName == “main”
);
anonymousClassBody 類程式碼塊 任意節點集合 public static void main(String[] args) {
new MemberClass() {
@Override
public void f() {
}
}.f();
}
anonymousInnerClassExpression ac where
ac.anonymousClassBody ab contain functionDeclaration;

4. CodeNavi外掛

在Vscode 的外掛中,查詢:codenavi,並安裝。

使用外掛的連結安裝: https://marketplace.visualstudio.com/items?itemName=HuaweiCloud.codenavi

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章