Chương 1 Cơ sở lý thuyết Chương này sẽ trình bày lý thuyết nền tảng trong phương pháp phát hiện mã xấu của luận văn, bao gồm đồ thị thuộc tính mã nguồn và các mạng nơ-ron được sử dụng để phân tích đặc trưng của dữ liệu dạng đồ thị. Sau đó các nghiên cứu hiện đại được phân tích kỹ lưỡng để nhận định điểm mạnh và điểm cần cải thiện, qua đó giúp xác định hướng nghiên cứu của luận văn.1 Đồ thị thuộc tính mã nguồn Phân tích mã nguồn là một quá trình quan trọng để hiểu rõ cấu trúc của một dự án phần mềm. Joern [20] cung cấp một bộ công cụ mạnh mẽ cho phân tích mã nguồn, ứng dụng này có khả năng tạo ra đồ thị thuộc tính mã nguồn (Code Property Graph - CPG) để biểu diễn các thực thể và mối quan hệ giữa chúng. CPG giúp dễ dàng khám phá và tìm kiếm thông tin trong mã nguồn đồng thời cho phép phân tích sâu hơn về cấu trúc và tương tác giữa các thành phần của mã nguồn.
Trong lĩnh vực phân tích chương trình và thiết kế trình biên dịch, nhiều dạng biểu diễn khác nhau của mã nguồn đã được phát triển để suy luận về các thuộc tính của chương trình. CPG có thể kết hợp ba dạng biểu diễn trong cùng một đồ thị bao gồm cây cú pháp trừu tượng (Abstract Syntax Tree - AST), đồ thị luồng điều khiển (Control Flow Graph - CFG) và đồ thị phụ thuộc chương trình (Program Dependence Graph - PDG). Cây cú pháp trừu tượng là một trong những biểu diễn trung gian đầu tiên được tạo ra bởi các bộ phân tích mã của trình biên dịch. AST thể hiện cấu trúc và sự kết hợp của các câu lệnh và biểu thức trong chương trình.
Thay vì biểu diễn chính xác cú pháp của chương trình, AST trừu tượng hóa mã nguồn để biểu diễn những cấu trúc quan trọng và ý nghĩa của các câu lệnh. Ví dụ, trong ngôn ngữ C, một danh sách các khai báo được phân cách bằng dấu phẩy sẽ tạo ra cùng một AST như hai khai báo liên tiếp. AST là một cấu trúc có thứ tự, trong đó các nút bên trong đại diện cho các toán tử (như phép cộng hoặc phép gán), còn các nút lá tương ứng với 6 các toán hạng (như hằng số hoặc tên biến).1 minh họa một cây cú pháp trừu tượng cho Đoạn mã 1. AST là nền tảng cho các bộ phân tích mã nguồn, việc hiểu rõ cấu trúc mã nguồn cũng giúp tối ưu hóa các trình biên dịch, góp phần cải thiện hiệu suất và độ chính xác trong quá trình biên dịch.
1 void foo () 2 { 3 int x = source () ; 4 if ( x < MAX ) 5 { 6 int y = 2 * x ; 7 sink ( y ) : 8 } 9 } Đoạn mã 1.1: Ví dụ mã nguồn C Hình 1.1: Cây cú pháp trừu tượng Đồ thị luồng điều khiển biểu diễn thứ tự thực thi của các lệnh và các điều kiện cần được thỏa mãn theo một trình tự cụ thể. Các lệnh và điều kiện này được biểu diễn bằng các nút, kết nối với nhau bằng các cạnh có hướng để chỉ ra sự chuyển giao của quyền điều khiển. Mỗi cạnh trong CFG được gán một nhãn true, false hoặc ϵ tương ứng với ý nghĩa của nút trước đó. Một nút lệnh thực thi có một cạnh ra được 7 đánh dấu là ϵ, trong khi một nút điều kiện có hai cạnh ra tương ứng với điều kiện true hoặc false.
Đồ thị luồng điều khiển được tạo ra từ cây cú pháp trừu tượng thông qua hai bước biến đổi. Đầu tiên, các lệnh điều khiển có cấu trúc được xử lý để tạo ra một đồ thị luồng điều khiển sơ bộ, mỗi nút trong đồ thị này tương ứng với một câu lệnh được thực thi trong chương trình, còn các cạnh được gán nhãn tương ứng với đầu ra của các nút trước đó. Sau đó, đồ thị này được tinh chỉnh bằng các thêm các vào các lệnh điều khiển không có cấu trúc như goto, break và continue để biểu diễn vòng lặp hoặc các câu lệnh điều kiện.1 khi được biểu diễn thành CFG sẽ có cấu trúc như trong Hình 1. Như vậy khác với AST, đồ thị luồng điều khiển không cung cấp thông tin về luồng dữ liệu mà cho thấy cách chương trình được điều khiển bởi các cấu trúc điều kiện hoặc vòng lặp.2: Đồ thị luồng điều khiển Khái niệm đồ thị phụ thuộc chương trình được giới thiệu bởi Ferrante [21], ban đầu được phát triển để thực hiện kỹ thuật cắt chương trình [22].
PDG xác định tất cả các câu lệnh và điều kiện trong chương trình ảnh hưởng đến giá trị của một biến tại một câu lệnh cụ thể. Đồ thị này biểu diễn các phụ thuộc giữa các câu lệnh và điều kiện thông qua hai loại cạnh: cạnh phụ thuộc dữ liệu, mô tả ảnh hưởng của một biến đến biến khác và cạnh phụ thuộc điều khiển, mô tả ảnh hưởng của các điều kiện đến giá trị của biến. PDG được xây dựng từ đồ thị luồng điều khiển bằng cách xác định tập hợp các biến được định nghĩa và sử dụng bởi mỗi câu lệnh, sau đó xác định mối quan hệ giữa các định nghĩa và vị trí sử dụng của các biến trong chương trình.3 là đồ thị phụ thuộc chương trình của Đoạn 8 mã 1. Các cạnh phụ thuộc điều khiển không đơn thuần là các cạnh luồng điều khiển và không thể xác định thứ tự thực thi của các câu lệnh từ đồ thị này, tuy nhiên các phụ thuộc giữa các câu lệnh và điều kiện vẫn được thể hiện rõ ràng.3: Đồ thị phụ thuộc chương trình Các biểu diễn AST, CFG, PDG có thể được kết hợp với nhau bằng đồ thị thuộc tính.
Một đồ thị thuộc tính G = (V, E, λ, µ) là một đồ thị đa cạnh, có hướng, có nhãn cạnh và được ánh xạ thuộc tính, trong đó V là một tập hợp các nút, E ⊆ (V × V ) là một tập hợp các cạnh có hướng, và λ : E → Σ là hàm gán nhãn cạnh gán một nhãn từ bảng chữ cái Σ cho mỗi cạnh. Các thuộc tính có thể được gán cho các cạnh và nút bằng hàm µ : (V ∪ E) × K → S trong đó K là một tập hợp các khóa thuộc tính và S là tập hợp các giá trị thuộc tính. Để khai thác thông tin trong các đồ thị thuộc tính phục vụ cho các bộ phân tích mã nguồn, các phép duyệt đồ thị được sử dụng. Các phép duyệt này di chuyển dọc theo các cạnh của đồ thị, đọc thông tin các nhãn và thuộc tính.
Một phép duyệt đồ thị được xác định là một hàm T : P (V ) → P (V ) ánh xạ một tập hợp các nút thành một tập hợp các nút khác theo một đồ thị thuộc tính G, trong đó P là tập hợp con của V. Mỗi biểu diễn mã nguồn AST, CFG hay PDG thể hiện một số thuộc tính đơn lẻ của chương trình, kết hợp cả ba biểu diễn này thành một cấu trúc dữ liệu chung sẽ thể hiện đầy đủ hơn các khía cạnh của một chương trình. AST, CFG và PDG sẽ được mô hình hóa dưới dạng các đồ thị thuộc tính, sau đó được hợp nhất thành một đồ thị duy nhất cung cấp tất cả các đặc tính của các biểu diễn đơn lẻ. Phần tiếp theo của luận văn sẽ trình bày cách chuyển đổi từng dạng biểu diễn mà nguồn AST, CFG và PDG thành đồ thị thuộc tính và cách kết hợp chúng để tạo ra CPG.1 Chuyển đổi cây cú pháp trừu tượng Đầu tiên, cây cú pháp trừu tượng được chuẩn bị để tích hợp vào CPG.
AST là biểu diễn duy nhất cung cấp một phân rã chi tiết của mã nguồn thành các cấu trúc ngôn ngữ. Do đó, cần xây dựng một biểu diễn chung bằng cách biểu diễn AST như một đồ thị thuộc tính GA = (V A, EA, λA , µA ), trong đó các nút V A được xác định bởi các nút cây và các cạnh, EA là các cạnh cây tương ứng được đánh dấu là các cạnh AST bởi hàm nhãn λA. Ngoài ra, một thuộc tính code được gán cho mỗi nút bằng cách sử dụng µA , sao cho giá trị thuộc tính tương ứng với toán tử hoặc toán hạng mà nút đại diện. Cuối cùng, mỗi nút được gán một thuộc tính order để phản ánh cấu trúc có thứ tự của cây.
Kết quả là, các khóa thuộc tính của đồ thị là K = {code, order} trong khi tập hợp các giá trị thuộc tính S được xác định bởi tất cả các toán tử và toán hạng cùng với các số tự nhiên.2 Chuyển đổi đồ thị luồng điều khiển CFG tiếp tục được chuẩn bị để tích hợp vào biểu diễn chung. CFG được biểu diễn dưới dạng một đồ thị thuộc tính GC = (V C, EC, λC , ·), trong đó các nút V C tương ứng với các câu lệnh và điều kiện trong cây cú pháp trừu tượng, tức là tất cả các nút V A có giá trị thuộc tính ST M T và P RED cho khóa code. Hàm nhãn cạnh λC gán các nhãn từ tập ΣC = true, f alse, ϵ cho tất cả các cạnh trong đồ thị thuộc tính.3 Chuyển đổi đồ thị phụ thuộc chương trình Đồ thị phụ thuộc chương trình là dạng biểu diễn thứ ba cần được tích hợp vào CPG. PDG đại diện cho các phụ thuộc dữ liệu, cấu trúc điều khiển và điều kiện thực hiện các câu lệnh trong chương trình.
Đồ thị này có các nút giống như các nút của CFG, nhưng các cạnh lại mang ý nghĩa khác biệt so với các cạnh của đồ thị luồng điều khiển. Do đó để chuẩn bị việc kết hợp, PDG được biểu diễn dưới dạng một đồ thị thuộc tính GP = (V C, EP, λP , µP ) bằng cách xác định một tập hợp các cạnh EP và một hàm nhãn cạnh tương ứng λP : Ep → ΣP trong đó ΣP = {C, D} biểu thị các phụ thuộc điều khiển (C) và phụ thuộc dữ liệu (D). Ngoài ra, mỗi phụ thuộc dữ liệu sẽ được gán một nhãn để chỉ ra biến tương ứng và mỗi phụ thuộc điều khiển sẽ có một nhãn cho biết trạng thái của điều kiện điều khiển ban đầu.4 Kết hợp các biểu diễn Ở bước cuối cùng, ba đồ thị thuộc tính kể trên sau khi chuyển đổi sẽ được kết hợp thành một cấu trúc dữ liệu chung, kết quả chúng ta có được đồ thị thuộc tính mã nguồn hoàn chỉnh. Chìa khóa quan trọng để xây dựng đồ thị này là trong mỗi biểu đồ tồn tại một nút cho mỗi câu lệnh và điều kiện trong mã nguồn.