Sự tiến hóa nhanh chóng của các thiết bị phần cứng trong hơn 30 năm qua đã đưa đến hệ quả về sự phát triển theo cấp số nhân của kích cỡ các chương trình phần mềm chạy trên đó. Quy mô của những ứng dụng cực lớn này (khoảng từ 1 tới 40 triệu dòng mã lệnh) vẫn tiếp tục gia tăng trong thời gian tới. Những phần mềm như thế cần phải được thiết kế với chi phí vừa phải trong khi vẫn phải bảo trì, nâng cấp trong toàn bộ vòng đời của chúng, tầm 20 năm. Một thực tế là quy mô và hiệu quả của những nhóm lập trình và bảo trì chúng không thể tăng theo tỉ lệ như vậy. Với hoàn cảnh đó, tỉ lệ giả định 1 lỗi trong 1000 dòng lệnh đối với những phần mềm như vậy là quá lạc quan và sẽ không thể đạt được trong những hệ thống đòi hỏi độ an toàn cực cao. Do đó, vấn đề về độ tin cậy của phần mềm (software reliability) chắc chắn là một mối quan tâm và thách thức đối với xã hội hiện đại ngày càng phụ thuộc vào các dịch vụ do máy tính đem lại. Nhiều kỹ thuật kiểm chứng phần mềm (software verification) và các công cụ hỗ trợ đi kèm đã được phát triển để thực thi hoặc giả lập chương trình trên nhiều môi trường khác nhau. Tuy nhiên, gỡ rối mã dịch hoặc giả lập mô hình của mã nguồn các chương trình không thể mở rộng quy mô và thường chỉ xét được mức độ bao phủ hạn chế các hành vi động của chương trình. Các phương pháp hình thức trong kiểm chứng chương trình (formal methods) cố gắng chứng minh một cách tự động rằng chương trình sẽ thực thi đúng đắn trên mọi môi trường được đặc tả. Mảng nghiên cứu này bao gồm các phương pháp suy dẫn (deductive methods), kiểm chứng mô hình (model checking), định kiểu chương trình (program typing) và phân tích chương trình tĩnh (static program analysis). Ba nhóm đầu tập trung vào việc kiểm chứng phần mềm tại mức mô hình, trong khi nhóm cuối cùng xử lý phần mềm tại mức mã nguồn. Phân tích chương trình tĩnh thu hút sự quan tâm nhất do nền tảng lý thuyết hình thức của nó cũng như mục đích của nó đối với các ứng dụng của nó trong thực tế. Kỹ thuật này phát hiện tính chấthành vi của một chương trình mà không yêu cầu chạy chương trình đó. Ngoài ra, một số lỗi chương trình như việc khởi tạosử dụng biến chương trình, biến con trỏ NULL,... có thể được phát hiện bởi kỹ thuật này. Mục tiêu chính của luận văn là cập nhật được những xu thế trên thế giới trong lĩnh vực phân tích chương trình và cải tiến những kỹ thuật này. Cụ thể, luận văn tập trung vào nghiên cứu kỹ thuật phân tích chương trình dựa trên đồ thị luồng dữ liệu để nâng cao chất lượng phần mềm. Tiến hành thực nghiệm trên công cụ SOOT, một công cụ mã nguồn mở phân tích chương trình viết bằng Java trên môi trường tích hợp phát triển Eclipse. Cấu trúc của luận văn bao gồm: Chương 1 giới thiệu tổng quan về phân tích chương trình tĩnh. Trong chương này, trình bày định nghĩa kỹ thuật phân tích chương trình tĩnh, ứng dụng kỹ thuật phân tích, điểm mạnh và những hạn chế của kỹ thuật phân tích tĩnh. Và phần chính trong Chương 1 là phần kiến thức nền tảng chính sử dụng trong kỹ thuật phân tích chương trình tĩnh. Tiếp theo là Chương 2 trình bày về phân tích luồng dữ liệu. Cụ thể, trình bày phương pháp phân tích luồng dữ liệu trong một hàm không có chứa lời gọi hàm (nội thủ tục) và phân tích luồng dữ liệu đồ thị luồng dữ liệu cho cả chương trình với lời gọi hàm (liên thủ tục). Chương 3 trình bày phần thực nghiệm với SOOT một công cụ nguồn mở để phân tích chương trình viết bằng Java trên môi trường tích hợp phát triển là Eclipse. Cuối cùng là phần kết luận và các tài liệu tham khảo.
Trang 12.1 Phân tích luồng dữ liệu nội thủ tục 2.42.1.1 Phân tích quay lại (backward) 2.52.1.2 Phân tích chuyển tiếp (forward) 3.12.2 Phân tích luồng dữ liệu liên thủ tục 4.02.2.1 Xây dựng đồ thị luồng dữ liệu 4.12.2.2 Tính cảm ngữ cảnh (context sensitivity) 4.32.2.3 Ứng dụng phân tích luồng dữ liệu liên thủ tục 4.5
Trang 23 Thực nghiệm 47
3.1 Tổng quan về SOOT 4.73.1.1 Giới thiệu 4.73.2 Phân tích chương trình cùng với SOOT trong Eclipse 4.8
Trang 3DANH MỤC CÁC KÝ HIỆU VÀ CHỮ VIẾT TẮT
Trang 4Danh sách hình vẽ
1.1 CFG cho các lệnh cơ bản
1.31.2 CFG cho các lệnh tuần tự
1.31.3 CFG cho các lệnh if, if-else
1.31.4 CFG cho các lệnh while, for
1.41.5 CFG của chương trình tính giai thừa
1.51.6 Biểu đồ Hasse biểu diễn Dàn
1.61.7 Các biểu đồ Hasse là Dàn
1.71.8 Các biểu đồ Hasse không phải là Dàn
1.71.9 Phép toán cộng dàn
1.91.10 Phép toán nâng (lift) dàn
1.9
1.11 Phép toán lift của các tập tạo thành dàn 2.0
2.1 Dàn cho chương trình phân tích tính sống của biến
2.62.2 Dàn cho chương trình phân tích biểu thức bận rộn
2.92.3 CFG của chương trình phân tích biểu thức bận rộn
3.02.4 Dàn cho chương trình phân tích biểu thức có sẵn
3.42.5 CFG của chương trình phân tích biểu thức có sẵn
3.42.6 CFG của chương trình phân tích định nghĩa tới được 3.8
2.7 Đồ thị def-use định nghĩa tới được của các biến của chương trình 4.0
2.8 Ví dụ CFG tổng quát cho chương trình có chứa lời gọi hàm 4.22.9 CFG của chương trình phân tích liên thủ tục 4.32.10 CFG đơn biến
4.42.11 CFG đa biến
4.53.1 Tổng quan về luồng làm việc của SOOT [10]
4.83.2 Phương thức copy() và merge() trong SOOT
4.93.3 Kết quả phân tích với SOOT
5.1
Trang 5MỞ ĐẦU
Sự tiến hóa nhanh chóng của các thiết bị phần cứng trong hơn 30 năm qua đãđưa đến hệ quả về sự phát triển theo cấp số nhân của kích cỡ các chương trìnhphần mềm chạy trên đó Quy mô của những ứng dụng cực lớn này (khoảng từ 1tới 40 triệu dòng mã lệnh) vẫn tiếp tục gia tăng trong thời gian tới Những phầnmềm như thế cần phải được thiết kế với chi phí vừa phải trong khi vẫn phải bảotrì, nâng cấp trong toàn bộ vòng đời của chúng, tầm 20 năm Một thực tế là quy
mô và hiệu quả của những nhóm lập trình và bảo trì chúng không thể tăng theo
tỉ lệ như vậy Với hoàn cảnh đó, tỉ lệ giả định 1 lỗi trong 1000 dòng lệnh đối vớinhững phần mềm như vậy là quá lạc quan và sẽ không thể đạt được trong những
hệ thống đòi hỏi độ an toàn cực cao Do đó, vấn đề về độ tin cậy của phần mềm
(software reliability) chắc chắn là một mối quan tâm và thách thức đối với xã
hội hiện đại ngày càng phụ thuộc vào các dịch vụ do máy tính đem lại Nhiều kỹ
thuật kiểm chứng phần mềm (software verification) và các công cụ hỗ trợ đi kèm
đã được phát triển để thực thi hoặc giả lập chương trình trên nhiều môi trườngkhác nhau Tuy nhiên, gỡ rối mã dịch hoặc giả lập mô hình của mã nguồn cácchương trình không thể mở rộng quy mô và thường chỉ xét được mức độ baophủ hạn chế các hành vi động của chương trình Các phương pháp hình thức
trong kiểm chứng chương trình (formal methods) cố gắng chứng minh một cách
tự động rằng chương trình sẽ thực thi đúng đắn trên mọi môi trường được đặc tả
Mảng nghiên cứu này bao gồm các phương pháp suy dẫn (deductive methods), kiểm chứng mô hình (model checking), định kiểu chương trình (program typing)
và phân tích chương trình tĩnh (static program analysis) Ba nhóm đầu tập trung
vào việc kiểm chứng phần mềm tại mức mô hình, trong khi nhóm cuối cùng xử
lý phần mềm tại mức mã nguồn Phân tích chương trình tĩnh thu hút sự quantâm nhất do nền tảng lý thuyết hình thức của nó cũng như mục đích của nó đốivới các ứng dụng của nó trong thực tế Kỹ thuật này phát hiện tính chất/hành vicủa một chương trình mà không yêu cầu chạy chương trình đó Ngoài ra, một sốlỗi chương trình như việc khởi tạo/sử dụng biến chương trình, biến con trỏNULL, có thể được phát hiện bởi kỹ thuật này
Mục tiêu chính của luận văn là cập nhật được những xu thế trên thế giới tronglĩnh vực phân tích chương trình và cải tiến những kỹ thuật này Cụ thể, luận văntập trung vào nghiên cứu kỹ thuật phân tích chương trình dựa trên đồ thị luồng
dữ liệu để nâng cao chất lượng phần mềm Tiến hành thực nghiệm trên công cụ
Trang 6SOOT, một công cụ mã nguồn mở phân tích chương trình viết bằng Java trênmôi trường tích hợp phát triển Eclipse.
Cấu trúc của luận văn bao gồm: Chương 1 giới thiệu tổng quan về phân tíchchương trình tĩnh Trong chương này, trình bày định nghĩa kỹ thuật phân tíchchương trình tĩnh, ứng dụng kỹ thuật phân tích, điểm mạnh và những hạn chếcủa kỹ thuật phân tích tĩnh Và phần chính trong Chương 1 là phần kiến thức nềntảng chính sử dụng trong kỹ thuật phân tích chương trình tĩnh Tiếp theo làChương
2 trình bày về phân tích luồng dữ liệu Cụ thể, trình bày phương pháp phân tíchluồng dữ liệu trong một hàm không có chứa lời gọi hàm (nội thủ tục) và phântích luồng dữ liệu đồ thị luồng dữ liệu cho cả chương trình với lời gọi hàm (liênthủ tục) Chương 3 trình bày phần thực nghiệm với SOOT - một công cụ nguồn
mở để phân tích chương trình viết bằng Java trên môi trường tích hợp pháttriển là Eclipse Cuối cùng là phần kết luận và các tài liệu tham khảo
Trang 7Chương 1
Giới thiệu
1.1 Giới thiệu về phân tích chương trình
Phân tích chương trình tĩnh là kỹ thuật xác định tính chất/hành vi của mộtchương trình mà không cần phải chạy chương trình đó Phân tích tĩnh được xây
dựng dựa trên lý thuyết diễn giải trừu tượng (abstract interpretation) [5, 6] để
chứng minh tính chính xác của các phân tích liên quan đến ngữ nghĩa của mộtngôn ngữ lập trình
Có rất nhiều câu hỏi thú vị mà có thể được hỏi về một chương trình hoặc các
điểm (point) riêng lẻ trong chương trình như:
• Chương trình có dừng hay không?
• Độ lớn có thể của vùng nhớ (heap) trong khi chạy?
• Đầu ra (output) có thể là gì?
• Biến x có luôn luôn cùng giá trị không?
• Giá trị của x sẽ được đọc trong tương lai?
• Con trỏ p null?
• Biến x đã được khởi tạo trước khi đọc không?
• v.v
Theo lý thuyết Rice [14], tất cả các câu hỏi trên về hành vi của chương trình là
không thể quyết định/chứng minh được (undecidable).
Thay vì mức mô hình như nhiều phương pháp hình thức, luận văn hướng tớiviệc phân tích chương trình tĩnh Cụ thể, luận văn trình bày một kỹ thuật để cải
Trang 8tiến mã chương trình và phát hiện các lỗi tiềm năng bằng việc phân tích chươngtrình tĩnh dựa trên phân tích luồng dữ liệu.
1.2 Điểm mạnh và điểm yếu
Phân tích chương trình tĩnh có những ưu điểm sau:
• Chỉ ra lỗi tại vị trí chính xác trong chương trình
• Dễ dàng thực hiện bởi những chuyên gia kiểm định chất lượng phần mềm hiểu rõ về mã nguồn
• Khoảng thời gian ngắn từ lúc phát hiện tới khi sửa lỗi
• Có thể tự động hóa nhanh (thông qua các bộ công cụ hỗ trợ ví dụ: SOOT,Astree, TVLA, )
• Lỗi được phát hiện sớm trong qui trình phát triển phần mềm nên chi phí sửalỗi thấp
Tuy nhiên, điểm yếu của kỹ thuật này xuất hiện khi tại một câu lệnh xuất hiệnnhững tham chiếu, ràng buộc nằm ngoài phạm vi suy luận biểu trưng của chươngtrình Hạn chế này là bản chất của việc phân tích tĩnh - không chạy với dữ liệu cụthể Một số điểm yếu không khắc phục được:
• Mất thời gian nếu phải thực hiện bằng tay
• Việc tự động hóa chỉ hướng vào một ngôn ngữ lập trình (ví dụ: SOOT chỉ kiểm tra mã nguồn chương trình viết bằng ngôn ngữ Java)
• Thiếu nhân lực có thể hiểu và phân tích được chương trình
• Có thể sinh ra nhiều lời cảnh báo lỗi không chính xác
• Không phát hiện được lỗi chỉ xuất hiện khi chạy chương trình (run-time ror)
er-1.3 Các công nghệ phân tích chương trình tĩnh
Những kỹ thuật phân tích chương trình tĩnh đã và đang thu hút nhiều nghiêncứu trên thế giới, hiện có nhiều kỹ thuật nhưng tựu chung có thể phân theo 4nhóm chính như sau:
Trang 9Thứ nhất, kỹ thuật phân tích chương trình tĩnh dựa trên phân tích luồng dữ
liệu (data flow analysis) [11, 15] Phân tích luồng dữ liệu là một quá trình thu
thập thông tin về dữ liệu trong các đoạn mã đó được thực thi trong thực tế trongchương trình mà không cần phải chạy đoạn mã đó Tuy nhiên, phân tích luồng
dữ liệu không sử dụng thao tác dựa trên ngữ nghĩa Phân tích luồng dữ liệu làmột cách rất hiệu quả và khả thi trong việc phát hiện lỗi chương trình và tối ưuhóa trong các trình biên dịch
Thứ hai, nhóm kỹ thuật liên quan tới xấp xỉ ngữ nghĩa được gọi là diễn giải
trừu tượng (abstract interpretation) [5, 6] Kỹ thuật diễn giải trừu tượng dựa trên
nguyên tắc xấp xỉ ngữ nghĩa của chương trình khi kiểm tra đối chiếu sự thỏa mãnđặc tả Kỹ thuật này trích ra từ một ngữ nghĩa chuẩn (standard semantics) đượcmột ngữ nghĩa trừu tượng đã xấp xỉ và tính toán được (approximate andcomputable abstract semantics) Quá trình chuyển này không hoàn toàn tựđộng mà có thể cần sự tương tác với người dùng Trong thực tế, kỹ thuật diễngiải trừu tượng có một thành phần là bộ sinh (generator) ngữ nghĩa trừu tượngđọc mã nguồn chương trình và tạo ra các ràng buộc hoặc hệ các phương trình cầnđược giải bởi máy tính thông qua một thành phần khác là bộ giải (solver) Mộtphương pháp phổ biến là dùng hàm lặp khi giải Việc tìm nghiệm thông qua hàmlặp có hạn chế về mặt thời gian (phương pháp không hội tụ sau vô hạn lần lặp).Các kỹ thuật liên quan tới việc tăng tốc hội tụ cũng được nghiên cứu
Thứ ba, nhóm kỹ thuật liên quan tới mô hình được gọi là kỹ thuật kiểm
chứng mô hình (Model checking) [4] Mô hình (model) của một hệ thống là
một cách biểu hiện ở mức trừu tượng cao hơn của hệ thống bằng cách lược bỏnhững phần quá chi tiết mà vẫn giữ lại những thông tin cần thiết về hệ thốngđang được xem xét Trong lĩnh vực phần mềm, kiểm chứng mô hình là cáchkiểm tra xem liệu mô hình của một hệ thống (phần cứng hay phần mềm) thỏamãn một tính chất nào đó hay không Những đặc tả tính chất đó thường lànhững tính chất an toàn như khả năng không tồn tại những khóa chết (deadlock)hoặc rơi vào những trạng thái nguy hiểm tạo sự cố cho hệ thống Nếu một hệthống không thoả mãn một tính chất thì kiểm chứng mô hình sẽ đưa ra phản ví
dụ với một xâu các trạng thái và sự kiện liên quan bắt đầu từ trạng thái ban đầutới trạng thái lỗi của mô hình
Cuối cùng, kỹ thuật phân tích biểu trưng (symbolic analysis) [18] Kỹ thuật
này là phân tích tĩnh mã nguồn tĩnh, xây dựng các luồng rẽ nhánh trong chương
trình dựa trên các nút Tại các nút tương ứng sẽ là tập hợp các ràng buộc
Trang 10(con-straints) của dữ liệu, biến, tham số Tại nút khởi tạo chương trình, tập hợp cácràng buộc là rỗng Càng đi sâu xuống các nhánh nhỏ, tại các nút con, tập hợpràng buộc sẽ được tạo ra từ tập hợp ràng buộc tại nút ngay phía trên cộng vớiđiều kiện giữa các biến số để có thể rẽ từ nút trên vào nút dưới trong luồng chảychương trình Điểm đặc biệt của kỹ thuật này là các tham số hoàn toàn được thểhiện bằng ký tự biểu trưng, chứ không phải giá trị cụ thể Ý tưởng của phươngpháp này là để kiểm thử một nhánh trong chương trình, điều kiện tiên quyết là
dữ liệu tại đầu vào phải thỏa mãn tập hợp các ràng buộc tại nút bắt đầu nhánh đó.Việc giải các ràng buộc gắn với một nút được thực hiện bởi các bộ công cụ sẵn
có gọi là giải ràng buộc (constraint solver) dựa trên SMT (Satisfiability ModuloTheories) hay SAT (Satisfiability Testing)
Hai nhóm đầu tập trung vào việc nâng cao chất lượng phần mềm tại mức mãnguồn, trong khi hai nhóm sau xử lý phần mềm tại mức trừu tượng cao hơn – môhình Luận văn sẽ tập trung vào xu thế thứ nhất, đó là kiểu phân tích luồng dữliệu dựa trên đồ thị luồng dữ liệu
1.4 Nền tảng
1.4.1 Đồ thị luồng điều khiển
Đồ thị luồng điều khiển (Control Flow Graph-CFG) là một đồ thị có hướng,trong đó các nút (node) tương ứng là các điểm (point) chương trình và các cạnhthể hiện cho luồng điểu khiển Một CFG luôn luôn có một điểm của đầu vào, ký
hiệu là entry, và một điểm của đầu ra, ký hiệu là exit Ngoài ra, nếu v là một nút
trong CFG thì những ký hiệu pred(v) là tập các nút kế trước (predecessor) và
succ(v) là tập các nút kế sau (successor).
CFG cho các lệnh
• Các lệnh cơ bản
Các lệnh đơn giản mà CFG có thể khởi tạo liên quan đến Những CFG cơ bảntrong ngôn ngữ lập trình Java như là các phép gán (id = E;), output(printf(E);), lệnh return (return;), và khai báo biến (ví dụ int f;) được môt
tả trong Hình 1.1 bên dưới:
Trang 11id = E printf(E) return E int id
• Các lệnh cấu trúc điều khiển
Cấu trúc điều khiển được minh họa bởi đồ thị quy nạp: Các lệnh if, if-else
if(E) S; if(E) S1; else S2;
Hình 1.3: CFG cho các lệnh if, if-else.
Trang 12Các lệnh while, for
E1
E2 E
S S
E3
while(E) S; for(E1; E2; E3;) S;
Hình 1.4: CFG cho các lệnh while, for.
Trang 13Hình 1.5: CFG của chương trình tính giai thừa.
1.4.2 Lý thuyết Dàn
Dàn
Trong kỹ thuật phân tích chương trình tĩnh của luận văn, các phân tích sửdụng cấu trúc toán học là Dàn (Lattices) [16], tập các thuộc tính (ví dụ: tập cácbiến, biểu thức, trong chương trình) cần thiết cho mỗi phân tích tĩnh trongchương trình
Định nghĩa Dàn
Một thứ tự bộ phận (partial order) là một cấu trúc toán học: L = (S , ⊑), với S
là một tập và ⊑ là quan hệ hai ngôi trên tập S , thỏa mãn các điều kiện sau:
Trang 14• Phản xạ: ∀x ∈ S : x ⊑ s
• Phản xứng: ∀x, y ∈ S : x ⊑ y ∧ y ⊑ x ⇒ x = y
• Bắc cầu: ∀x, y, z ∈ S : x ⊑ y ∧ y ⊑ z ⇒ x ⊑ z
Biểu diễn Dàn thông qua biểu đồ Hasse
Cho Dàn L = (S , ⊑) là tập có thứ tự bộ phận, biểu đồ Hasse của Dàn L bao
gồm:
• Một tập hợp các điểm trong mặt phẳng tương ứng 1-1 với S, gọi là các đỉnh
• Một tập hợp các cung nối một số cặp đỉnh có quan hệ thứ tự bộ phận
Ví dụ, biểu diễn Dàn (2{x,y,x}, ⊆) (Hình 1.6 (a)) hoặc Dàn đảo ngược (Dànđược sắp bởi thứ tự ngược của các tập con và quan hệ hai ngôi ⊑ được định nghĩa
là ⊇) (2{x,y,x}, ⊇) (Hình 1.6 (b)) bằng biểu đồ Hasse:
Hình 1.6: Biểu đồ Hasse biểu diễn Dàn.
Cận trên, cận dưới
Cho X ⊆ S Ta nói rằng y ∈ S là một cận trên của X, ký hiệu X ⊑ y, nếu
∀x ∈ X : x ⊑ y Tương tự, y ∈ S là một cận dưới của X, ký hiệu y ⊑ X, nếu
∀x ∈ X : y ⊑ x Một cận trên nhỏ nhất, ký hiệu ⊔X, được định nghĩa bởi:
X ⊑ ⊔X ∧ ∀y ∈ X : X ⊑ y ⇒ ⊔X ⊑ y Bên cạnh đó, một cận dưới lớn nhất, ký hiệu ⊓X, được định nghĩa bởi:
Trang 15⊓X ⊑ X ∧ ∀y ∈ X : y ⊑ X ⇒ y ⊑ ⊓X Một Dàn là một thứ tự bộ phận trong đó ⊔X và ⊓X tồn tại cho tất cả X ⊆ S
Phần tử lớn nhất, phân tử nhỏ nhất
Nếu x = ⊔X ∈ X thì x được gọi là phần tử lớn nhất của X, ký hiệu là ⊤ và
định nghĩa là ⊤ = ⊓S Từ tính phản xạ của quan hệ thứ tự ta suy ra rằng ⊤ nếutồn tại
là duy nhất
Tương tự ta có, nếu y = ⊓X ∈ X thì y được gọi là phần tử nhỏ nhất của X, ký
hiệu là ⊥ và định nghĩa là ⊥ = ⊔S Từ tính phản xạ của quan hệ thứ tự ta suy rarằng ⊥ nếu tồn tại là duy nhất
Do tính duy nhất của các phần tử trong ⊤ và ⊥ Ví dụ minh hoạ dưới đây chỉ ra
những thứ tự bộ phận nào là dàn và không phải là Dàn:
Độ cao của một Dàn được tính bằng chiều dài từ phần tử nhỏ nhất ⊥ tới phần
tử lớn nhất ⊤ Dàn L = (S , ⊑) là hữu hạn (chiều cao của Dàn là hữu hạn) nếu tập
S chứa hữu hạn số phần tử.
Trang 16Với mỗi tập S hữu hạn định nghĩa một Dàn L = (2 S , ⊑), với:
• ⊤, ⊥ tồn tại và ⊥ = ∅, ⊤ = S
• Với mỗi cặp phần tử x, y trong S có x ⊔ y = x ∪ y, x ⊓ y = x ∩ y
Trong ví dụ về Dàn trong Hình 1.6 ở trên thì độ cao của Dàn là 3
Tổng quát, độ cao của Dàn hữu hạn L = (2 S , ⊑) có độ cao là |S |
Điểm cố định (Fixed-Point)
Hàm đơn điệu
Ánh xạ f : L → L được gọi là hàm đơn điệu khi ∀x, y ∈ S : x ⊑ y ⇒ f (x) ⊑
f (y) Chú ý rằng thuộc tính này không có nghĩa hàm f là hàm tăng ( ∀x ∈ S : x ⊑
f (x)); Ví dụ, tất cả các hàm hằng là hàm đơn điệu, những hàm ⊔ và ⊓ là nhữngđơn điệu trong cả hai trường hợp Lưu ý rằng những phép hợp của những hàm đơn điệu là hàm đơn điệu
Điểm cố định
Điều mà ta cần chính là việc tìm ra điểm cố định của một hàm Theo lý thuyết
điểm cố định [16], trong một Dàn L với độ cao hữu hạn, mọi hàm đơn điệu f có
duy nhất điểm cố định nhỏ nhất được định nghĩa:
để có f ( f ix( f )) = f ix( f ) Chứng minh của lý thuyết này khá đơn giản Chú
ý rằng ⊥⊑ f (⊥) từ đó ⊥ là phần tử nhỏ nhất Từ đó f là đơn điệu, nó cho phép rằng
f ( ⊥) ⊑ f 2(⊥) và bởi phương pháp quy nạp f i(⊥) ⊑ f i+1(⊥) Do đó, ta có chuỗi
tăng dần:
⊥⊑ f (⊥) ⊑ f 2(⊥) ⊑
Vì L được giả thiết là có chiều cao hữu hạn, ta thêm một số k để có f k (⊥) =
f k+1(⊥) Ta có định nghĩa f ix( f ) = f k (⊥) và vì rằng f ( f ix( f )) = f k+1(⊥) = f
k (⊥
) = f ix( f ), biết rằng f ix( f ) là một điểm cố định Bây giờ, giả sử rằng x là
một điểm cố định khác Khi đó ⊥⊑ x ta có f (⊥) ⊑ f (x) = x, vì f là đơn điệu và
bằng quy nạp ta có f ix( f ) = f k (⊥) ⊑ x Từ đây, f ix( f ) là điểm cố định nhỏnhất Theo
tính chất phản xứng, nó cũng là duy nhất
Trang 17Thuộc tính đóng
Nếu L1, L2, , L n là những Dàn với độ cao hữu hạn, từ đó một phép toán tích
(product):
L1 × L2 × × L n = {(x1, x2, , x n)|xi ∈ L1}với ⊑ được định nghĩa là theo từng điểm (point-wise) Với chú ý rằng ⊔ và ⊓
có thể được tính toán theo từng điểm và như vậy height(L1 × L2 × × L n) =
height(L1) + + height(L n)
Tương tự ta có, một phép toán cộng (sum):
L1 + L2 + + L n = {(i, x i)|xi ∈ L i \ {⊥, ⊤}} ∪ {⊥, ⊤}
với ⊤ và ⊥ được cho trước và (i, x) ⊑ ( j, y) khi và chỉ khi i = j và x ⊑ y Chú
ý rằng height (L1 + + L n ) = max{height(L i)} Phép toán cộng được minh họa
nhưsau:
Trang 18Nếu A là một tập hữu hạn (A không cần thiết phải là một Dàn), khi đó f lat(A)
được minh họa bởi:
Hình 1.11: Phép toán lift của các tập tạo thành dàn.
theo từng điểm thứ tự: f ⊑ g ⇔ ∀a i : f (a i) ⊑ g(ai) Nếu A là hữu hạn và L có độ
cao hữu hạn khi đó height(A 7→ L) = |A|.height(L).
với x i là những biến và F i : L n → L là một tập những hàm đơn điệu Mỗi hệ
chỉ có nghiệm nhỏ nhất duy nhất, nó được gọi là điểm cố định nhỏ nhất của hàm
F : L n → L được định nghĩa bởi:
F(x1, x2, , x n ) = (F1(x1, x2, , x n ), , F n (x1, x2, ,
x n))
Trang 19Tương tự, có thể giải hệ các bất phương trình:
x1 ⊑ F1(x1, , x n)
x2 ⊑ F2(x1, , x n)
x n ⊑ F n (x1, , x n)
mặt khác, ta có quan hệ x ⊑ y tương đương với x = x ⊓ y Vì vậy, những các
nghiệm được duy trì bởi tái thể hiện lại hệ phương trình như sau:
1.4.3 Thuật toán điểm cố định
Giả thiết rằng ta có một đồ thị luồng dữ liệu-CFG (Mục 1.4.1 ở trên) và một
Dàn L có độ cao hữu hạn (Mục 1.4.2 ở trên) Mỗi nút v trong CFG ta gán một biến v thông qua các phần tử của Dàn L Trong mỗi bước xây dựng trong ngôn
J
ngữ lập trình, ta định nghĩa một ràng buộc luồng dữ liệu (data-flow constraint)
và được gọi là hàm chuyển (transfer function), nó liên quan đến giá trị của biến
và tương ứng với nút đến các nút khác (nút lân cận hay nút hàng xóm) trên CFG
Ta có thể tách một cách hệ thống một tập các ràng buộc thông qua các biến trênmột CFG đã cho Nếu tất cả các ràng buộc trong phương trình và bất phươngtrình là các hàm đơn điệu ở vế phải, thì ta có thể sử dụng thuật toán tìm điểm cốđịnh để giải nghiệm nhỏ nhất duy nhất
Các ràng buộc luồng dữ liệu là đúng (sound) nếu tất cả các nghiệm tương ứng
để thông tin đúng về chương trình Việc phân tích dựa trên tìm nghiệm nhỏ nhất
sẽ cho độ chính xác cao nhất
Nếu một CFG có tập những nút V = v1, v2, , v n, khi đó ta làm việc với Dàn
L n Giả thiết rằng nút vi đưa ra phương trình luồng dữ liệu
K
Trang 20xây dựng các hàm F : L n → L n như mô tả trên một cách dễ dàng hơn:
F(x1, , x n ) = (F1(x1, , x n ), , F n (x1, , x n))Sau đây, luận văn giới thiệu một số thuật toán tìm điểm cố định:
Thuật toán naive
Thuật toán naive tìm điểm cố định x như sau:
Thuật toán lặp chaotic
Thuật toán lặp chaotic tìm điểm cố định (x1, x2, , x n) Thuật toán lặp chaoticnhư sau:
Thuật toán work-list
Một thuật toán tìm điểm cố định khác là thuật toán work-list, thuật toán nàytránh lãng phí khi các thông tin của tất cả các nút được tính toán lại trong mỗibước lặp, mặc dù ta có thể biết rằng nó không được thay đổi Trong trường hợp
tổng quát, tất cả các biến v iK đều phụ thuộc vào các biến khác Tuy nhiên, một
yêu cầu thực tế của F i sẽ chỉ đọc giá trị của một số các biến khác Thông tinđược biểu diễn như ánh xạ:
de p : V → 2V
v xuất hiện một cách bất
với mỗi nút v cho ta biết tập con của các nút khác mà J K
thường (nontrivial) trong vế phải của những phương trình Đó là, dep(v) là tập
hợp
J
Trang 21các nút chứa thông tin có thể phụ thuộc vào thông tin của nút v Một thuật toán giải quyết cho vấn đề này được gọi là thuật toán work-list nhằm tính toán điểm
cố định (x1, , x n) Thuật toán work-list như sau:
Tập W thì đây gọi là work-list với các phép toán add và removeNext cho
thêm và loại bỏ một phần tử Độ phức tạp trường hợp xấu nhất không thay đổi,nhưng trong thực tế thuật toán này giúp tiết kiệm nhiều thời gian
Trang 22Chương 2
Phân tích chương trình tĩnh
Trong chương này, luận văn giới thiệu kỹ thuật phân tích chương trình tĩnhdựa trên phân tích luồng dữ liệu trong hàm không có lời gọi hàm hay phân tíchnội thủ tục (intraprocedual) và trong chương trình có chứa lời gọi hàm hay phântích liên thủ tục (interprocedure)
2.1 Phân tích luồng dữ liệu nội thủ tục
Phân tích luồng dữ liệu hay còn gọi là khung đơn điệu (monotoneframework), là kỹ thuật phân tích chương trình tĩnh nhằm thu thập các hành vi
của chương trình và phát hiện lỗi thông qua đồ thị luồng dữ liệu (CFG) và Dàn L
có độ cao hữu hạn
Các bước để phân tích luồng dữ liệu bao gồm:
• Bước 1: Khởi tạo CFG của chương trình: Gọi V = v1, v2, là tập các nút trên
CFG
• Bước 2: Khởi tạo Dàn hữu hạn L thông qua các tập biến, biểu thức trong
chương trình
• Bước 3: Xây dựng các hệ phương trình là các ràng buộc luồng dữ liệu trong
v ∈ L, với mỗi khởi chương trình: với mỗi nút v trên CFG ta gán một biến J K
tạo trong ngôn ngữ lập trình, môt kết hợp ràng buộc luồng dữ liệu (dataflowconstraint) được xác định liên quan đến giá trị biến của các nút tương vớicác nút khác, đặc biệt là các nút láng giếng Hệ phương trình luồng dữ liệuđược xác định thông qua các ràng buộc là hàm đơn điệu:
J v1K v2 , , J )v nK
Trang 23• Bước 4: Giải hệ phương trình các ràng buộc thông qua thuật toán tìm điểm
cố định (Mục 1.4.3):
F(x1, , x n ) = (F1(x1, , x n ), , F n (x1, , x n))
Nghiệm nhỏ nhất thu được sẽ được dùng để kết luận cho phân tích chương trình
2.1.1 Phân tích quay lại (backward)
Với mỗi điểm của chương trình (nút trên CFG), phân tích quay lại (backwardanalysis) là phân tích thông tin về hành vi trong tương lai Do đó, trong vế phảicủa các phương trình chỉ phụ thuộc vào các nút kế sau nó (successor) trên CFG
Phân tích lùi được bắt đầu từ nút exit của CFG và di chuyển quay lại trong CFG Một số phân tích điển hình: Phân tích tính sống của biến (Liveness), phân tích
biểu thức bận rộn (Busy Expression)
Phân tích tính sống của biến
Trong một chương trình việc xác định tính sống của biến tại mỗi điểm củachương trình là rất cần thiết, việc này giúp chương trình xác định và loại bỏ đượccác biến chết giúp tối ưu hóa bộ nhớ/ tối ưu hoá chương trình dịch và làm tăngtốc độ tính toán của chương trình Một biến được gọi là biến sống tại một điểm
của chương trình (liveness) nếu giá trị hiện tại của nó được đọc trong nút hiện tại
hoặc được đọc trong một số nút kế tiếp (không được ghi ở nút hiện tại) Thuộctính này có thể được xấp xỉ bởi phân tích tĩnh dựa trên CFG với một dàn là mộttập các biến trong chương trình
v , khi đó các ràng buộc
Gọi các biến ràng buộc cho mỗi nút v trên CFG là J K
cho tính sống của biến tại các nút với các cấu trúc lệnh trong chương trình đượcxác định như sau:
• Đối với nút exit, có ràng buộc là:
Trang 24• Đối với những phép gán id = E, ràng buộc là:
Dàn ở đây là L = (2{f, uu_f, n}, ⊆), biểu diễn bằng biểu đồ Hasse:
{f,uu_f,n}
{f,uu_f} {f,n} {uu_f,n}
{}
Hình 2.1: Dàn cho chương trình phân tích tính sống của biến.
Các ràng buộc được tạo ra như sau:
J
Trang 25int fKint uu_fK \ {f}
Hệ phương trình trên là để giải thông qua Dàn: L = (2{f, uu_f, n}, ⊆) Hơn
nữa, nó dễ dàng thấy rằng tất cả vế phải ràng buộc của phương trình định nghĩanhững hàm đơn điệu Theo kết quả, lý thuyết của điểm cố định nhỏ nhất có thểđược áp dụng [16] Nghiệm nhỏ nhất cho hệ phương trình (Được giải trong Phụlục A) là duy nhất là:
Từ những thông tin này một bộ dịch có thể suy ra rằng uu_f chưa bao giờ sống,
và giá trị được ghi trong phép gán uu_f = 0 chưa bao giờ được đọc Do đó,chương trình hàm lặp trong Mục 1.4.1 có thể chạy an toàn và tối ưu hóa như sau:int iterative(int n){
int f;// khai báo biến (f có kiểu là int)
Trang 26Phân tích biểu thức bận rộn
Việc tính toán các biểu thức trong chương trình làm tăng bộ nhớ và làm chậmthời gian chạy kết quả của chương trình Do vậy, trong chương trình nếu hạn chếtính toán lại một biểu thức trong tương lai sẽ giúp chương trình chạy nhanh hơn
và giúp bộ nhớ tối ưu hơn
Một biểu thức E là bận rộn (busy) tại điểm p của chương trình nếu tất cả các đường (path) xuất phát từ một điểm chương trình p phải được đánh giá E trước khi một biến bất kỳ trong biểu thức E thay đổi Hoặc, ta cũng có thể hiểu là giá
trị của biểu thức được đánh giá tại thời điểm hiện tại hoặc sẽ được đánh giá trongtất cả các nút trong tương lai trừ khi một phép gán làm thay đổi giá trị của nó Đểxấp xỉ thuộc tính này, ta cần Dàn là tập các biểu thức trong chương trình Đối vớiphân tích này, ta xác định được các ràng buộc luồng dữ liệu cho các cấu trúclệnh như sau:
• Ràng buộc cho lệnh exit:
Trang 27• Với tất cả các nút còn lại có ràng buộc là:
Hình 2.2: Dàn cho chương trình phân tích biểu thức bận rộn.
Đồ thị luồng dữ liệu tương ứng là:
Trang 292.1.2 Phân tích chuyển tiếp (forward)
Với mỗi điểm của chương trình (nút trên CFG), phân tích chuyển tiếp(forward analysis) là phân tích thông tin về hành vi trong quá khứ Do đó,trong vế phải của các phương trình chỉ phụ thuộc vào các nút kế trước nó(predecessor) trên CFG Phân tích tiến được bắt đầu từ nút entry của CFG và
di chuyển chuyển tiếp trong CFG Một số phân tích điển hình: Phân tích biểu
thức có sẵn(Available Expression), phân tích định nghĩa tới được(Reaching
Trang 30Phân tích biểu thức có sẵn
Một biểu thức không bình thường (nontrivial) trong một chương trình là cósẵn (available) tại một điểm trong chương trình nếu giá trị của nó đã được tínhtoán sẵn trước đó trong khi thực thi Việc xác định các biểu thức đã có sẵntrước khi thực thi sẽ giúp cho việc tính toán nhanh và đơn giản hơn Do vậy,trong phân tích này chúng ta sử dụng các thông tin về hành vi trong quá khứ
Và, Dàn cho phân tích này là tập hợp các biểu thức xảy ra cho tất cả các điểm
chương trình và được sắp bởi các tập con đảo ngược (reverse) Đối với mỗi nút
v trên CFG tương ứng
v trên Dàn L chứa các tập con của các biểu thức mà nó
với một biến ràng buộc J K
được đảm bảo luôn luôn có sẵn tại điểm chương trình kế sau nút đó Ví dụ, biểu
thức a + b là có sẵn ở điều kiện trong vòng lặp, nhưng nó không phải là có sẵn
tại các phép gán trong vòng lặp Phân tích đưa ra sẽ bảo toàn kể từ khi thiết lậptính toán có thể là quá nhỏ Từ đó, có các ràng buộc luồng dữ liệu cho các cấutrúc lệnh trong phân tích như sau:
• Với mỗi nút entry ta có ràng buộc:
Trang 31Với hàm ↓ id loại bỏ tất cả các biểu thức có chứa một tham chiếu đến biến id, và
exps(E1o pE2) = {E1o pE2} ∪ ex ps(E1) ∪ ex ps(E2)
Với o p là phép toán nhị phân bất kỳ Ta thấy rằng một biểu thức là có sẵn trong
v nếu nó có sẵn từ tất cả các cạnh hoặc được tính toán trong nút v, trừ khi giá trị
của nó đã được hủy bởi lệnh gán Một lần nữa, phía vế phải của những ràngbuộc là những hàm đơn điệu
Trang 32{a+b}
{a+b,a-b}
{a+b,y<a+b} {a+b,a-1} {a-b,y<a+b} {a-b,a-1} {y<a+b,a-1}
{a+b,a-b,y<a+b} {a+b,a-b,a-1} {a+b,y<a+b,a-1} {a-b,y<a+b,a-1}
{a+b,a-b,y<a+b,a-1}
Hình 2.4: Dàn cho chương trình phân tích biểu thức có sẵn.
Phần tử lớn nhất của Dàn là ∅ và đồ thị luồng dữ liệu tương ứng với chương trình
Hình 2.5: CFG của chương trình phân tích biểu thức có sẵn.
Các ràng buộc sinh ra như sau: