Types Against Races Atomicity Analysis of Concurrent Software Cormac Flanagan Stephen N. Freund UC Santa Cruz Williams College Shaz Qadeer Microsoft Research Moore ’ s Law Moore ’ s Law is Over • Transistors per chip doubles every 18 months • Sure, we can pack more transistors ... – ... but can ’ t use them effectively to make single- threaded programs faster • Single-threaded performance doubles every 2 years • Multi-core is the future of hardware – faster clocks, deep pipelines, multiple issue – wonderful! • Multi-threading is the future of software Programming With Threads • Decompose program into parallel threads • Advantages – exploit multiple cores/processors – some threads progress, even if others block • Increasingly common (Java, C#, GUIs, servers) Web Server Thread 1 network database Thread 2 1
Security vulnerabilities involving Concurrency is a problem race conditions • Windows 2000 hot fixes • Buffer overruns – Concurrency errors most common defects among “detectable errors” • Phishing attacks – Incorrect synchronization and protocol errors most – “A systematic approach to uncover visual ambiguity common defects among all coding errors vulnerabilities ” , MSR Technical Report MSR-TR- • Windows Server 2003 late cycle defects 2006-48 by Chen et al. – Synchronization errors second in the list, next to buffer overruns Economic Impact Non-Determinism, Heisenbugs • NIST study – On CNN.com - April 27, 2003 • Multithreaded programs are non-deterministic – behavior depends on interleaving of threads • Extremely difficult to test – exponentially many interleavings – during testing, many interleavings behave correctly – post-deployment, other interleavings fail http://www.nist.gov/director/prog-ofc/report02-3.pdf • Complicates code reviews, static analysis, ... 400 horses Mars, July 4, 1997 100 microprocessors Lost contact due to real-time priority inversion bug 2
Bank Account Implementation Bank Account Implementation class Account { class Account { private int bal = 0; private int bal = 0; public void deposit(int n) { public void deposit(int n) { int j = bal; int j = bal; bal = j + n; bal = j + n; } } } } bal = bal = j1 = bal j2 = bal j1 + 10 j2 + 10 bal = 0 bal = 20 bal = bal = j1 = bal j2 = bal j1 + 10 j2 + 10 bal = 0 bal = 10 Bank Account Implementation Race Conditions A race condition occurs if two threads access a class Ref { shared variable at the same time, and at least one of int i; the accesses is a write void add(Ref r) { i = i + r.i; } } bal = bal = j1 = bal j2 = bal j1 + 10 j2 + 10 bal = 0 bal = 20 bal = bal = j1 = bal j2 = bal j1 + 10 j2 + 10 bal = 0 bal = 10 Race Conditions Race Conditions class Ref { class Ref { int i; int i; Race condition on x.i void add(Ref r) { void add(Ref r) { i = i i = i + r.i; + r.i; } } } } Ref x = new Ref(0); Ref x = new Ref(0); Ref y = new Ref(3); Ref y = new Ref(3); parallel { x.add(y); x.add(y); // two calls happen x.add(y); x.add(y); // in parallel } Assertion may fail assert x.i == 6; assert x.i == 6; 3
Lock-Based Synchronization When Locking Goes Bad ... • Hesienbugs (race conditions, etc) are common and class Ref { problematic int i; // guarded by this • Every shared memory void add(Ref r) { – forget to acquire lock, acquire wrong lock, etc location protected by a i = i lock – extremely hard to detect and isolate + r.i; } } • Traditional type systems are great for catching • Lock must be held before Ref x = new Ref(0); any read or write of that certain errors Ref y = new Ref(3); memory location parallel { synchronized (x,y) { x.add(y); } • Type systems for multithreaded software synchronized (x,y) { x.add(y); } – detect race conditions, atomicity violations, ... } assert x.i == 6; Verifying Race Freedom with Types Verifying Race Freedom with Types class Ref { class Ref { int i; int i guarded_by this; void add(Ref r) { void add(Ref r) requires this, r { check: this ∈ { this, r } i = i i = i + r.i; + r.i; } } } } Ref x = new Ref(0); Ref x = new Ref(0); Ref y = new Ref(3); Ref y = new Ref(3); parallel { parallel { synchronized (x,y) { x.add(y); } synchronized (x,y) { x.add(y); } synchronized (x,y) { x.add(y); } synchronized (x,y) { x.add(y); } } } assert x.i == 6; assert x.i == 6; Verifying Race Freedom with Types Verifying Race Freedom with Types class Ref { class Ref { int i guarded_by this; int i guarded_by this; void add(Ref r) requires this, r { void add(Ref r) requires this, r { check: this ∈ { this, r } check: this ∈ { this, r } i = i i = i check: this[this:=r] = r ∈ { this, r } check: this[this:=r] = r ∈ { this, r } + r.i; + r.i; } } } } replace this by r Ref x = new Ref(0); Ref x = new Ref(0); Ref y = new Ref(3); Ref y = new Ref(3); replace formals this,r by actuals x,y parallel { parallel { check: {this,r}[this:=x,r:=y] ∈ { x, y } synchronized (x,y) { x.add(y); } synchronized (x,y) { x.add(y); } synchronized (x,y) { x.add(y); } synchronized (x,y) { x.add(y); } } } assert x.i == 6; assert x.i == 6; 4
Verifying Race Freedom with Types One Problem ... class Ref { Object o; int i guarded_by this; int x guarded_by o; void add(Ref r) requires this, r { check: this ∈ { this, r } i = i check: this[this:=r] = r ∈ { this, r } fork { sync(o) { x++; } } + r.i; } } fork { o = new Object(); sync(o) { x++; } Ref x = new Ref(0); Ref y = new Ref(3); replace formals this,r } by actuals x,y parallel { synchronized (x,y) { x.add(y); } check: {this,r}[this:=x,r:=y] ∈ { x, y } check: {this,r}[this:=x,r:=y] ∈ { x, y } synchronized (x,y) { x.add(y); } • Lock expressions must be constant } assert x.i == 6; Soundness Theorem: Well-typed programs are race-free Lock Equality Lock Equality • Approximate (undecidable) semantic equality by • Type system checks if lock is in lock set syntactic equality – r ∈ { this, r } – two locks expressions are considered equal only if – same as r = this ∨ r = r syntactically identical • Conservative approximation class A { • Semantic equality void f() requires this { ... } – e 1 = e 2 if e 1 and e 2 refer to same object } – need to test whether two program expressions evaluate A p = new A(); to same value q = p; this[this:=p] = p ∈ { q } X synch(q) { p.f(); } – undecidable in general • Not a major source of imprecision RaceFreeJava Typing Rules • Concurrent extension of CLASSICJAVA • Thread creation [Flatt-Krishnamurthi-Felleisen 99] • Judgement for typing expressions • Synchronization lock is constant add to lock set Program Environment Lock set 5
java.util.Vector 0 1 2 a b Field Access 2 class Vector { Object elementData[] /*# guarded_by this */; e has class c int elementCount /*# guarded_by this */; fd is declared in c synchronized int lastIndexOf(Object elem, int n) { lock l is held for (int i = n ; i >= 0 ; i--) if (elem.equals(elementData[i])) return i; return -1; } int lastIndexOf(Object elem) { return lastIndexOf(elem, elementCount - 1); } synchronized void trimToSize() { ... } synchronized boolean remove(int index) { ... } } java.util.Vector 0 a Validation of rccjava 1 class Vector { Object elementData[] /*# guarded_by this */; int elementCount /*# guarded_by this */; Number of Annotation Races synchronized int lastIndexOf(Object elem, int n) { Program Size (lines) annotations time (hrs) Found for (int i = n ; i >= 0 ; i--) Hashtable 434 60 0.5 0 if (elem.equals(elementData[i])) return i; return -1; Vector 440 10 0.5 1 } java.io 16,000 139 16.0 4 Ambit 4,500 38 4.0 4 int lastIndexOf(Object elem) { return lastIndexOf(elem, elementCount - 1); WebL 20,000 358 12.0 5 } synchronized void trimToSize() { ... } synchronized boolean remove(int index) { ... } } Basic Type Inference Basic Type Inference static final Object m =new Object(); Iterative GFP algorithm: class Ref { class Ref { • [Flanagan-Freund, PASTE ’ 01] int i; int i; • Start with maximum set of void add(Ref r) { void add(Ref r) { annotations i = i + r.i; i = i + r.i; } } } } Ref x = new Ref(0); Ref x = new Ref(0); Ref y = new Ref(3); Ref y = new Ref(3); parallel { parallel { synchronized (x,y) { x.add(y); } synchronized (x,y) { x.add(y); } synchronized (x,y) { x.add(y); } synchronized (x,y) { x.add(y); } } } assert x.i == 6; assert x.i == 6; 6
Recommend
More recommend