Phân tích chuyển tiếp (forward)

Một phần của tài liệu Nghiên cứu kỹ thuật phân tích chương trình tĩnh trong việc nâng cao chất lượng phần mềm (Trang 31)

2 Phân tích chương trình tĩnh

2.1.2Phâ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íchbiểu thức có sẵn(Available Expression), phân tíchđịnh nghĩa tới được(Reaching Denitions)

Phâ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ính toán sẵn trước đó trong khi thực thi. Việc xác định các biểu thức đã có sẵn trướ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ới một biến ràng buộc JvK trên Dàn L chứa các tập con của các biểu thức mà nó đượ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ứca+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ập tí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ấu trúc lệnh trong phân tích như sau:

• Với mỗi nút entryta có ràng buộc:

JentryK = {}

• Nếuv chứa một điều kiệnE hoặc lệnh output E, khi đó ràng buộc là:

JvK= ∩

wpred(v)JwK∪exps(E)

• Nếuv chứa một phép gánid = E, khi đó ràng buộc là:

JvK = ( ∩

wpred(v)JwK∪exps(E)) ↓ id

• Đối với tất cả các lệnh khác của các nút, có ràng buộc là:

JvK= ∩

Vớ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à các hàmexpsđược định nghĩa là:

exps(intconst) = ∅

exps(id) = ∅

exps(input) = ∅

exps(E1opE2) = {E1opE2} ∪exps(E1)∪exps(E2)

Vớiop 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útv, 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àng buộc là những hàm đơn điệu. Ví dụ: int func(){ int x,y,a,b; y = a-b; while (y < a+b){ a = a-1; x = a+b; } return x; }

Ta có 4 biểu thức khác nhau, do đó Dàn cho phân tích với chương trình là:

{a+b}

{}

{a-b} {y<a+b} {a-1}

{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}

{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 trên là: y < a+b x=a+b y=a-b a=a-1 int x,y,a,b

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: Các ràng buộc sinh ra như sau:

JentryK = ∅ (adsbygoogle = window.adsbygoogle || []).push({});

Jint x,y,z,a,bK= JentryK

Jy=a-bK = (Jint x,y,z,a,bK∪exps(a-b))↓ y

Jy<a+bK= (Jy=a-bK∩Jx=a+bK)∪exps(y<a+b)

Ja=a-1K = (Jy<a+bK∪exps(a-1)) ↓ a

Jx=a+bK= (Ja=a-1K∪exps(a+b)) ↓ x

JexitK = Jy<a+bK

Sử dụng thuật toán lặpChaotictìm điểm cố định (Phụ lục C), ta thu được nghiệm nhỏ nhất: JentryK = ∅ Jint x,y,z,a,bK= ∅ Jy=a-bK = {a-b} Jy>a+bK= {a+b,y<a+b} Ja=a-1K = ∅ Jx=a+bK= {a+b} JexitK = {a+b,y<a+b}

Trong đó khẳng định giả thiết về biểu thứca+b. Nhận thấy rằng các biểu thức có sẵn tại điểm chương trình kế trước một nútv có thể được tính như ∩

wpred(v)JwK. Từ đó, một trình biên dịch tối ưu hóa hệ thống có thể làm chương trình trở nên hiệu quả/đơn giản hơn:

int func() { int x,y,a,b,t; y = a-b; t = a+b; while (y < t){ a = a-1;+$\\ t = a+b; x = t; } return x; }

Chương trình vẫn đảm bảo bảo toàn ngữ nghĩa và có thể ước tính độ phức tạp trường hợp xấu nhất (worst-case) của việc phân tích: Trước tiên ta thấy rằng nếu

chương trình có n nút trên CFG và k biểu thức không tầm thường, tiếp đó dàn có chiều cao k.n với giới hạn số các lần lặp ta thực hiện. Mỗi phần tử trên Dàn có thể được biểu diễn như là một bitvector chiều dài k. Với mỗi lần lặp, ta phải thực hiện O(n) phép giao, phép hợp, hoặc trong tất cả các phép toán có thời gian O(kn). Như vậy, tổng thời gian của độ phức tạp là O(k2n2).

Phân tích định nghĩa tới được

Trong lĩnh vực kiểm thử và đảm bảo chất lượng phần mềm, việc xác định đồ thị def-use là việc làm quan trọng trong việc hạn chế các mã chết (dead code) và những mã chuyển động (code motion). Vì vậy, mục đích của kỹ phân tích định nghĩa tới được là xác định mối quan hệ của các biến tại có các điểm của chương trình với nhau.

Các định nghĩa tới được (reaching denitions) cho những điểm của chương trình là những phép gán mà có thể được xác định là giá trị hiện tại của các biến. Đối với phân tích này, chúng ta cần một dàn là tập tất cả các phép gán xảy ra trong chương trình. Với mọi nútvtrên CFG biếnJvKlà tập các phép gán mà có thể xác định giá trị của biến tại điểm chương trình. Ta có các ràng buộc cho các lệnh chương trình trong phân tích như sau:

• Với những phép gán có ràng buộc: JvK= ( ∪ wpred(v)JwK) ↓ id∪ {v} • Với tất cả các nút khác, ràng buộc là: JvK= ∪ wpred(v)JwK

Trong đó, hàm↓ id loại bỏ tất cả phép gán chứa biếnid.

ví dụ:

int func(int input){ int x,y,z;

x=input; while (x>1){

y = x/2; if (y>3)

x = x-y; z = x-4; if (z>0) x = x/2; z = z-1; } return x; }

Dàn được xác định như sau:

L= (2{x=input,y=x/2,x=x-y,z=x-4,x=x/2,z=z-1},⊆)

y>3 z>0 x=input x>1 z=x-4 return x int x,y,z y=x/2 x=x-y z=z-1 x=x/2

Hình 2.6:CFG của chương trình phân tích định nghĩa tới được.Các ràng buộc cho chương trình được sinh ra như sau: Các ràng buộc cho chương trình được sinh ra như sau:

JentryK = ∅

Jint x,y,zK= JentryK

Jx=inputK= (Jint x,y,zK) ↓ x∪ {x=input}

Jx>1K = (Jx=inputK∪Jz=z-1K) (adsbygoogle = window.adsbygoogle || []).push({});

Jy=x/2K = (Jx>1K) ↓ yy = x/2

Jy>3K = Jy=x/2K

Jx=x-yK = (Jy>3K) ↓ x∪ {x = xy}

Jz>0K = Jz=x-4K

Jx=x/2K = (Jz>0K) ↓ x∪ {x=x/2}

Jz=z-1K = (Jz>0K∪Jx=x/2K) ↓ z∪ {z=z-1}

Jreturn xK= Jx>1K JexitK = Jreturn xK

Sử dụng thuật toán lặpChaotic (Được giải trong Phụ lục D), ta tìm được nghiệm nhỏ nhất như sau: JentryK = ∅ Jint x,y,zK= ∅ Jx=inputK= {x=input} Jx>1K = {y=x/2,x=x-y,x=input,x=x/2,z=z-1} Jy=x/2K = {x=input,x=x/2,z=z-1,y=x/2} Jy>3K = {x=input,z=z-1,y=x/2} Jx=x-yK = {x=x-y} Jz=x-4K = {x=input,,x=x-y,y=x/2,z=x-4} Jz>0K = {y=x/2,x=x-y,z=x-4} Jx=x/2K = {x=x/2} Jz=z-1K = {y=x/2,x=x-y,x=x/2,z=z-1} Jreturn xK= {y=x/2,x=x-y,x=input,x=x/2,z=z-1} JexitK = {y=x/2,x=x-y,x=input,x=x/2,z=z-1}

y>3 z>0 x=input x>1 z=x-4 return x int x,y,z y=x/2 x=x-y z=z-1 x=x/2

Hình 2.7: Đồ thịdef-use định nghĩa tới được của các biến của chương trình.

Dựa trên đồ thị def-use ta có thể tối ưu hóa chương trình như loại bỏ mã chết (dead code), mã không tới được (làm cho chương trình không dừng).

Một phần của tài liệu Nghiên cứu kỹ thuật phân tích chương trình tĩnh trong việc nâng cao chất lượng phần mềm (Trang 31)