EDA045F: Program Analysis LECTURE 5 BONUS: BASIC CALLGRAPHS Christoph Reichenbach
The Call Graph void f(char *s) { for (char *p = s; *p; p++) { *p = up(*p); } puts(s); } int main(int argc, char *argv) { char up(char c) { if (argc > 1) { if (c >= ’a’ && c <= ’z’) { f(argv[0]); return c - (’a’ - ’A’); } } g(); return c; return 0; } } void g(void) { puts("Hello, World!"); } 2 / 15
The Call Graph void f(char *s) { for (char *p = s; *p; p++) { *p = up(*p); } puts(s); } int main(int argc, char *argv) { char up(char c) { if (argc > 1) { if (c >= ’a’ && c <= ’z’) { f(argv[0]); return c - (’a’ - ’A’); } } g(); return c; return 0; } } void g(void) { puts("Hello, World!"); } 2 / 15
The Call Graph ◮ G call = � P , E call � ◮ Connects procedures from P via call edges from E call ◮ ‘Which procedure can call which other procedure?’ ◮ Often refined to: ‘Which call site can call which procedure?’ ◮ Used by program analysis to find procedure call targets up f main g 3 / 15
Finding Calls and Targets class Main { public void main(String[] args) { class A { A[] as = { new A(), new B() }; public A for (A a : as) { f() { return new C(); } A a2 = a.f(); print(a.g()); public String print(a2.g()); g() { return "A"; } } } } } class D extends A { class C extends A { class B extends A { @Override @Override @Override public String public String public String g() { return "D"; } g() { return "A"; } g() { return "B"; } } } } 4 / 15
Finding Calls and Targets class Main { public void main(String[] args) { class A { A[] as = { new A(), new B() }; public A for (A a : as) { f() { return new C(); } A a2 = a.f(); print(a.g()); public String print(a2.g()); g() { return "A"; } } } } } class D extends A { class C extends A { class B extends A { @Override @Override @Override public String public String public String g() { return "D"; } g() { return "A"; } g() { return "B"; } } } } 4 / 15
Finding Calls and Targets class Main { public void main(String[] args) { class A { A[] as = { new A(), new B() }; public A for (A a : as) { f() { return new C(); } A a2 = a.f(); print(a.g()); public String print(a2.g()); g() { return "A"; } } } } } class D extends A { class C extends A { class B extends A { @Override @Override @Override public String public String public String g() { return "D"; } g() { return "A"; } g() { return "B"; } } } } 4 / 15
Dynamic Dispatch: Call Graph Challenge: Computing the precise call graph: A.<init>() A.f() Main.main() A.g() B.<init>() a.f B.g() a.g C.<init>() a2.g C.g() direct call D.<init>() virtual call D.g() 5 / 15
Summary ◮ Call Graphs capture which procedure calls which other procedure ◮ For program analysis, further specialised to map: Callsite → Procedure ◮ Direct calls: straightforward ◮ Virtual calls (dynamic dispatch): ◮ Multiple targets possible for call ◮ Not straightforward 6 / 15
Finding Calls and Targets class Main { public void main(String[] args) { class A { A[] as = { new A(), new B() }; public A for (A a : as) { f() { return new C(); } A a2 = a.f(); print(a.g()); public String print(a2.g()); g() { return "A"; } } } } } class D extends A { class C extends A { class B extends A { @Override @Override @Override public String public String public String g() { return "D"; } g() { return "A"; } g() { return "B"; } } } } 7 / 15
Finding Calls and Targets class Main { public void main(String[] args) { class A { A[] as = { new A(), new B() }; public A for ( A a : as) { f() { return new C(); } A a2 = a.f(); print(a.g()); public String print(a2.g()); g() { return "A"; } } } } } class D extends A { class C extends A { class B extends A { @Override @Override @Override public String public String public String g() { return "D"; } g() { return "A"; } g() { return "B"; } } } } 7 / 15
Class Hierarchy Analysis Object Main A main(String[]) f() g() B C D g() g() g() ◮ Use declared type to determine possible targets 8 / 15
Class Hierarchy Analysis Object Main A main(String[]) f() g() B C D g() g() g() ◮ Use declared type to determine possible targets ◮ Must consider all possible subtypes ◮ In our example: assume a.f can call any of: A.f() , B.f() , C.f() , D.f() 8 / 15
Class Hierarchy Analysis: Example A.<init>() A.f() Main.main() A.g() B.<init>() a.f B.g() a.g C.<init>() a2.g C.g() direct call D.<init>() virtual call D.g() CHA prediction 9 / 15
Class Hierarchy Analysis: Example A.<init>() A.f() Main.main() A.g() B.<init>() a.f B.g() a.g C.<init>() a2.g C.g() direct call D.<init>() virtual call D.g() CHA prediction 9 / 15
Summary ◮ Call Hierarchy Analysis resolves virtual calls a . f () by: ◮ Examining static types T of receivers ( a : T ) ◮ Finding all subtypes S < : T ◮ Creating call edges to all S . f , if S . f exists ◮ Sound ◮ Assuming strongly and statically typed language with subtyping ◮ Not very precise 10 / 15
Rapid Type Analysis ◮ Intuition: ◮ Only consider reachable code ◮ Ignore unused classes ◮ Ignore classes instantiated only by unused code 11 / 15
Finding Calls and Targets class Main { public void main(String[] args) { class A { A[] as = { new A(), new B() }; public A for ( A a : as) { f() { return new C(); } A a2 = a.f(); print(a.g()); public String print(a2.g()); g() { return "A"; } } } } } class D extends A { class C extends A { class B extends A { @Override @Override @Override public String public String public String g() { return "D"; } g() { return "A"; } g() { return "B"; } } } } 12 / 15
Finding Calls and Targets class Main { public void main(String[] args) { class A { A[] as = { new A(), new B() }; public A for (A a : as) { f() { return new C(); } A a2 = a.f(); print(a.g()); public String print(a2.g()); g() { return "A"; } } } } } class D extends A { class C extends A { class B extends A { @Override @Override @Override public String public String public String g() { return "D"; } g() { return "A"; } g() { return "B"; } } } } 12 / 15
Finding Calls and Targets class Main { public void main(String[] args) { class A { A[] as = { new A(), new B() }; public A for (A a : as) { f() { return new C(); } A a2 = a.f(); print(a.g()); public String print(a2.g()); g() { return "A"; } } } } } class D extends A { class C extends A { class B extends A { @Override @Override @Override public String public String public String g() { return "D"; } g() { return "A"; } g() { return "B"; } } } } 12 / 15
Finding Calls and Targets class Main { public void main(String[] args) { class A { A[] as = { new A(), new B() }; public A for (A a : as) { f() { return new C(); } A a2 = a.f(); print(a.g()); public String print(a2.g()); g() { return "A"; } } } } } class D extends A { class C extends A { class B extends A { @Override @Override @Override public String public String public String g() { return "D"; } g() { return "A"; } g() { return "B"; } } } } 12 / 15
Finding Calls and Targets class Main { public void main(String[] args) { class A { A[] as = { new A(), new B() }; public A for (A a : as) { f() { return new C(); } A a2 = a.f(); print(a.g()); public String print(a2.g()); g() { return "A"; } } } } } class D extends A { class C extends A { class B extends A { @Override @Override @Override public String public String public String g() { return "D"; } g() { return "A"; } g() { return "B"; } } } } 12 / 15
Finding Calls and Targets class Main { public void main(String[] args) { class A { A[] as = { new A(), new B() }; public A for (A a : as) { f() { return new C(); } A a2 = a.f(); print(a.g()); public String print(a2.g()); g() { return "A"; } } } } } class D extends A { class C extends A { class B extends A { @Override @Override @Override public String public String public String g() { return "D"; } g() { return "A"; } g() { return "B"; } } } } 12 / 15
Finding Calls and Targets class Main { public void main(String[] args) { class A { A[] as = { new A(), new B() }; public A for (A a : as) { f() { return new C(); } A a2 = a.f(); print(a.g()); public String print(a2.g()); g() { return "A"; } } } } } class D extends A { class C extends A { class B extends A { @Override @Override @Override public String public String public String g() { return "D"; } g() { return "A"; } g() { return "B"; } } } } 12 / 15
Rapid Type Analysis: Example A.<init>() A.f() Main.main() A.g() B.<init>() a.f B.g() a.g C.<init>() a2.g C.g() direct call D.<init>() virtual call D.g() RTA prediction 13 / 15
Rapid Type Analysis: Example A.<init>() A.f() Main.main() A.g() B.<init>() a.f B.g() a.g C.<init>() a2.g C.g() direct call D.<init>() virtual call D.g() RTA prediction 13 / 15
Recommend
More recommend