930 likes | 944 Views
This research explores static verification approaches for scripting languages using refinement types to ensure array access bounds and functional consistency. It presents challenges and solutions for thorough verification in dynamic environments.
E N D
Trust, but VerifyTwo-Phase Typing for Dynamic Languages Panagiotis Vekris, Benjamin Cosman, Ranjit Jhala University of California, San Diego
Scripting Languages – Then “Perl is the duct tape of the Internet.” Hassan Schroeder, Sun's first webmaster “Scripting languages are designed for 'gluing' applications; they use typeless approaches to achieve a higher level of programming and more rapid application development than system programming languages.” John K. Ousterhout
Scripting Languages – Now Front-end Server-side Tooling Complex reasoning calls for stronger guarantees
Talk Outline • Goal: Static Verification of Scripting Languages
Program: Compute index of smallest array element Goal: Verify that array accesses within bounds
Program #1: First-order function minIndexFO(a) { }
Program #1: First-order function minIndexFO(a) { var res =0; return res; }
Program #1: First-order function minIndexFO(a) { var res =0; for (var i =0; i < a.length; i++) { } return res; }
Program #1: First-order function minIndexFO(a) { varres=0; for (var i =0; i < a.length; i++) { if (a[i] < a[res]) res= i; } returnres; }
Program #1: First-order function minIndexFO(a) { if (a.length <=0) return-1; varres=0; for (var i =0; i < a.length; i++) { if (a[i] < a[res]) res= i; } returnres; }
Program #1: First-order function minIndexFO(a) { if (a.length <=0) return-1; varres=0; for (var i =0; i < a.length; i++) { if (a[i] < a[res]) res= i; } returnres; } Array bound checks: easy Dataflow Analysis: 0≤res≤i<a.length
Program #2: Higher-Order function $reduce(a, f, x) { var res = x; for (var i =0; i < a.length; i++) { • res = f(res, a[i], i); } returnres; }
Program #2: Higher-Order function $reduce(a, f, x) { var res = x; for (var i =0; i < a.length; i++) { • res = f(res, a[i], i); } returnres; } • function minIndexHO(a) { • if (a.length <=0) return-1; • function step(min, cur, i) { • return (cur < a[min]) ? i : min; • } • return $reduce(a, step, 0); • }
Challenge: Verify array access function $reduce(a, f, x) { var res = x; for (var i =0; i < a.length; i++) { • res = f(res, a[i], i); } returnres; } • function minIndexHO(a) { • if (a.length <=0) return-1; • function step(min, cur, i) { • return (cur < a[min]) ? i : min; • } • return $reduce(a, step, 0); • } Hard to track relational facts among values and closures with DataFlow analyses
Easy to track relational facts among values and closures with Refinement Types
Talk Outline • Goal: Static Verification of Scripting Languages • Approach: Refinement Types
“Set of valid indices for array a” • type idx<a>={v: number|0 <= v && v <a.length} • type idx<a>={v: number|0 <= v && v <a.length}
“Set of valid indices for array a” • type idx<a>={v: number|0 <= v && v <a.length} Base Type
“Set of valid indices for array a” • type idx<a>={v: number|0 <= v && v <a.length} Logical Predicate
“Set of valid indices for array a” • type idx<a>={v: number|0 <= v && v <a.length} Value Variable
“Set of valid indices for array a” • type idx<a>={v: number|0 <= v && v <a.length}
Higher-Order Example • function $reduce(a, f, x) { • varres = x; • for (var i =0; i < a.length; i++) • res = f(res, a[i], i); • returnres; • }
Higher-Order Type Checking • function $reduce<A,B>(a: A[], f: (B,A,number) => B, x: B): B { • varres = x; • for (var i =0; i < a.length; i++) • res = f(res, a[i], i); • returnres; • }
Higher-Order Value Checking • type idx<a>= { v:number|0 <= v&&v<a.length } • function $reduce<A,B>(a: A[], f: (B,A,idx<a>) => B, x: B): B { • varres = x; • for (var i =0; i < a.length; i++) • res = f(res, a[i], i); • returnres; • } + Refinements [Xi’99]
TypeAnalysis Needs Value Analysis
Problem: “Value Based” Overloading https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
Talk Outline • Goal: Static Verification of Scripting Languages • Approach: Refinement Types • Problem: Value Based Overloading
Value Based Overloading function reduce(a, f, x) { • if (arguments.length ===3) • return$reduce(a, f, x); else • return$reduce(a, f, a[0]); }
Value Based Overloading function reduce(a, f, x) { • if (arguments.length ===3) • return$reduce(a, f, x); else • return$reduce(a, f, a[0]); } Type when called with 3 values: • functionreduce<A,B>(a: A[], f: (B,A,idx<a>) => B, x: B): B
Value Based Overloading function reduce(a, f, x) { • if (arguments.length ===3) • return$reduce(a, f, x); else • return$reduce(a, f, a[0]); } Type when called with 3 values: • functionreduce<A,B>(a: A[], f: (B,A,idx<a>) => B, x: B): B Type when called with 2 values: • functionreduce<A>(a: A[]+, f: (A,A,idx<a>) => A): A
TypeAnalysis Needs Value Analysis
TypeAnalysis Needs Needs Value Analysis
TypeAnalysis Needs Circular Dependency Needs Value Analysis
TypeAnalysis Needs Two-Phased Typing Needs Value Analysis
Talk Outline • Goal: Static Verification of Scripting Languages • Approach: Refinement Types • Problem: Overloading • Solution: Two-Phased Typing
Two-Phased Typing – Goal • functionnegate(flag, x) { • if (flag) return0- x; • elsereturn!x; • } (1) If flag≠0 then x: number (2) Ifflag=0 thenx: boolean
Two-Phased Typing – Goal • functionnegate(flag, x) { • if (flag) return0- x; • elsereturn!x; • } (1) If flag≠0 then x: number (2) Ifflag=0 thenx: boolean • varok1= negate(1, 1); • var ok2 = negate(0, true);
Two-Phased Typing – Goal • functionnegate(flag, x) { • if (flag) return0- x; • elsereturn!x; • } (1) If flag≠0 then x: number (2) Ifflag=0 thenx: boolean • varok1= negate(1, 1); • var ok2 = negate(0, true); • var bad1 = negate(0, 1); • var bad2 = negate(1, true);
Two-Phased Typing – Goal • functionnegate(flag, x) { • if (flag) return0- x; • elsereturn!x; • } (1) If flag≠0 then x: number (2) Ifflag=0 thenx: boolean
Specification • type tt = {v: number| v ≠0} // "truthy" numbers • type ff = {v: number| v =0} // "falsy" numbers • functionnegate(flag, x) { • if (flag) return0- x; • elsereturn!x; • } (1) If flag≠0 then x: number (2) Ifflag=0 thenx: boolean
Specification • type tt = {v: number| v ≠0} // "truthy" numbers • type ff = {v: number| v =0} // "falsy" numbers • functionnegate(flag, x) { • if (flag) return0- x; • elsereturn!x; • } (1) Ifflag: tt then x: number (2) Ifflag: ff thenx: boolean
Specification • type tt = {v: number| v ≠0} // "truthy" numbers • type ff = {v: number| v =0} // "falsy" numbers • function negate(flag: tt, x: number):number; • function negate(flag: ff, x: boolean):boolean; • functionnegate(flag, x) { • if (flag) return0- x; • elsereturn!x; • } Incorporate in negate’s type
Verification • type tt = {v: number| v ≠0} // "truthy" numbers • type ff = {v: number| v =0} // "falsy" numbers • function negate(flag: tt, x: number):number; • function negate(flag: ff, x: boolean):boolean; • functionnegate(flag, x) { • if (flag) return0- x; • elsereturn!x; • } • varok1= negate(1, 1); • var ok2 = negate(0, true); • var bad1 = negate(0, 1); • var bad2 = negate(1, true); (A) Statically check negate’s body (B) Statically check call-sites
Safe Two-Phased Typing Unsafe Source Program
Safe Trust Verify • Cons Gen • Check • Clone • Cast • Resolve Unsafe Source Program Elaborated Program
1st Phase (Trust) – Clone function negate(flag: tt, x: number):number; • function negate(flag: ff, x: boolean):boolean; function negate(flag, x) { if (flag) return0- x; elsereturn!x; } Split overloaded functions
1st Phase (Trust) – Clone function negate#1(flag: tt, x: number):number{ • if (flag) return0- x; elsereturn!x; } function negate(flag: tt, x: number):number; • function negate(flag: ff, x: boolean):boolean; function negate(flag, x) { if (flag) return0- x; elsereturn!x; } • function negate#2(flag: ff, x: boolean):boolean{ • if (flag) return0- x; elsereturn!x; } One clone for each conjunct
Safe Trust Verify • Cons Gen • Check • Clone • Cast • Resolve Unsafe Source Program Elaborated Program
1st Phase (Trust) – Cast function negate#1(flag: tt, x: number):number{ • if (flag) return0- x; elsereturn!x; } • function negate#2(flag: ff, x: boolean):boolean{ • if (flag) return0- x; elsereturn!x; } Check each clone separately