Dependent Types for JavaScript Ravi Chugh Ranjit Jhala Dave Herman Pat Rondon Panos Vekris UCSD UCSD Mozilla Google UCSD “Dynamic” Features Facilitate Rapid Innovation Dependent Types for JavaScript 1. Better Development Tools 2. Better Reliability 3. Better Performance var person = { name : { first : “John”, last : “McCarthy” }}; person.nom; produces undefined rather than error… person.nom.first; … but this raises TypeError 3 var person = { name : { first : “John”, last : “McCarthy” }}; if (unlikely()) { person.nom; some errors hard to catch with testing person.nom.first; } 4 Dependent Types for JavaScript Will Never Replace Need for Testing and Dynamic Checking But Want Static Checking When Possible JavaScript scope manipulation implicit global object var lifting ‘,,,’ == new Array(4) 6 scope manipulation JavaScript “The Good Parts” implicit global object arrays objects prototypes type-tests lambdas var lifting eval() ‘,,,’ == new Array(4) 7 JavaScript “The Good Parts” Dependent JavaScript Use Logic, but Avoid Quantifiers! 8 “Usability” Shriram @2:30pm Me @now TypedJS Dependent JavaScript (DJS) Nik @9:00am [POPL ’12, OOPSLA ’12] F* + Dijkstra Expressiveness 9 DJS = Refinement Types + Several New Quantifier-Free Mechanisms Me @now Dependent JavaScript (DJS) [POPL ’12, OOPSLA ’12] 10 typeof true // “boolean” typeof 0.1 // “number” typeof 0 // “number” typeof {} // “object” typeof [] // “object” typeof null // “object” 11 typeof returns run-time “tags” Tags are very coarse-grained types “undefined” “boolean” “string” “number” “object” “function” 12 Refinement Types {x|p} “set of values x s.t. formula p is true” Num NumOrBool { n | tag(n) = “number” } { v | tag(v) = “number” tag(v) = “boolean” } Int { i | tag(i) = “number” integer(i) } Any { x | true } 13 Refinement Types Syntactic Sugar for Common Types Num NumOrBool { n | tag(n) = “number” } { v | tag(v) = “number” tag(v) = “boolean” } Int { i | tag(i) = “number” integer(i) } Any { x | true } 14 Refinement Types 3 :: 3 :: 3 :: 3 :: {n|n=3} {n|n>0} { n | tag(n) = “number” integer(n) } { n | tag(n) = “number” } 15 Refinement Types Subtyping is Implication <: <: <: <: {n|n=3} {n|n>0} { n | tag(n) = “number” integer(n) } { n | tag(n) = “number” } 16 Refinement Types Subtyping is Implication {n|n=3} {n|n>0} { n | tag(n) = “number” integer(n) } { n | tag(n) = “number” } 17 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays 18 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays var negate = function(x) { if (typeof x == “boolean”) return !x; !true // false else return 0 - x; } negate( )true 19 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays var negate = function(x) { if (typeof x == “boolean”) return !x; else return 0 - x; 0 - 2 // -2 } negate( )2 20 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays var negate = function(x) { if (typeof x == “boolean”) return !x; else return 0 - x; 0 - [] // 0 ?!? } negate( ) [] 21 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays var negate = function(x) { if (typeof x == “boolean”) return !x; else return 0 - x; } Use types to prevent implicit coercion (-) :: (Num,Num) Num 22 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays //: negate :: (x:Any) Any var negate = function(x) { if (typeof x == “boolean”) return !x; else return 0 - x; } Function type annotation inside comments 23 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays //: negate :: (x:Any) Any var negate = function(x) { if (typeof x == “boolean”) return !x; else return 0 - x; } x is boolean... so negation is well-typed DJS is Path Sensitive 24 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays ✗ //: negate :: (x:Any) Any var negate = function(x) { if (typeof x == “boolean”) return !x; else return 0 - x; ✗ x is arbitrary non-boolean value… so DJS signals error! } DJS is Path Sensitive 25 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays //: negate :: (x:NumOrBool) Any var negate = function(x) { if (typeof x == “boolean”) return !x; else return 0 - x; } 26 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays //: negate :: (x:NumOrBool) Any var negate = function(x) { if (typeof x == “boolean”) return !x; else return 0 - x; } this time, x is a number… so subtraction is well-typed 27 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays //: negate :: (x:NumOrBool) Any var negate = function(x) { if (typeof x == “boolean”) return !x; else return 0 - x; } but return type is imprecise 28 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays //: negate :: (x:NumOrBool) NumOrBool var negate = function(x) { if (typeof x == “boolean”) return !x; else return 0 - x; } 29 Tag-Tests Duck Typing Mutable Objects /*: negate :: (x:NumOrBool) { y | tag(y) = tag(x) } */ var negate = function(x) { Prototypes Arrays if (typeof x == “boolean”) return !x; else return 0 - x; } output type depends on input value 30 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays What is “Duck Typing”? if (duck.quack) return “Duck says ” + duck.quack(); else return “This duck can’t quack!”; 31 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays What is “Duck Typing”? (+) :: (Num,Num) Num (+) :: (Str,Str) Str if (duck.quack) return “Duck says ” + duck.quack(); else return “This duck can’t quack!”; 32 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays What is “Duck Typing”? Can dynamically test the presence of a method but not its type if (duck.quack) return “Duck says ” + duck.quack(); else return “This duck can’t quack!”; 33 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays { d | tag(d) = “object” ∧ { v | has(d,“quack”) { v | sel(d,“quack”) :: Unit Str } Operators from McCarthy theory of arrays if (duck.quack) return “Duck says ” + duck.quack(); else return “This duck can’t quack!”; 34 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays { d | tag(d) = “object” ∧ { v | has(d,“quack”) { v | sel(d,“quack”) :: Unit Str } Call produces Str, so concat well-typed return “Duck says ” + duck.quack(); if (duck.quack) else return “This duck can’t quack!”; 35 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays DJS is Flow Sensitive var x = {}; x.f = 7; x.f + 2; DJS verifies that x.f is definitely a number x0:Empty x1:{d|d = upd(x0,“f”,7)} McCarthy operator 36 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays DJS is Flow Sensitive var x = {}; x.f = 7; x.f + 2; x0:Empty x1:{d|d = upd(x0,“f”,7)} Strong updates to singleton objects Weak updates to collections of objects 37 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays 38 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays Typical “Dynamic” Features 39 Tag-Tests Duck Typing Typical “Dynamic” Features Mutable Objects Prototypes Arrays JavaScript 40 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays Upon construction, each object links to a prototype object child parent grandpa ... null 41 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays SemanticsvarofkKey Lookup = “first”; child[k]; If child contains k, then Read k from child child Else if parent contains k, then Read k from parent parent Else if grandpa contains k, then Read k from grandpa grandpa ... null Else if … Else Return undefined 42 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays SemanticsvarofkKey Lookup = “first”; child[k]; If child contains { v | if has(child,k) then k, then Read k from child { v | ifv = sel(child,k) child Else if parent contains k, then Read k from parent parent Else if grandpa contains k, then Read k from grandpa grandpa H ... (Rest of Heap) null Else if … Else Return undefined 43 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays SemanticsvarofkKey Lookup = “first”; child[k]; If child contains { v | if has(child,k) then k, then Read k from child { v | ifv = sel(child,k) child { v | elseElse if has(parent,k) thenk, then if parent contains Read k from parent { v | ifv = sel(parent,k) parent Else if grandpa contains k, then Read k from grandpa grandpa H ... (Rest of Heap) null Else if … Else Return undefined 44 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays SemanticsvarofkKey Lookup = “first”; child[k]; If child contains { v | if has(child,k) then k, then Read k from child { v | ifv = sel(child,k) child { v | elseElse if has(parent,k) thenk, then if parent contains Read k from parent { v | ifv = sel(parent,k) parent grandpa H ??? ... (Rest of Heap) null Else if grandpa contains k, then Read k from grandpa Else if … Else Return undefined 45 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays SemanticsvarofkKey Lookup = “first”; child[k]; { v | if has(child,k) then child { v | ifv = sel(child,k) { v | else if has(parent,k) then parent { v | ifv = sel(parent,k) { v | else grandpa H ??? ... (Rest of Heap) null { v | ifv = HeapSel(H,grandpa,k)) } Abstract predicate to summarize the unknown portion of the prototype chain 46 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays var k = “first”; child[k]; child parent { “first” : “John” } { “first” : “Ida” , “last” : “McCarthy” } { v | if has(child,k) then { v | ifv = sel(child,k) { v | else if has(parent,k) then { v | ifv = sel(parent,k) { v | else grandpa H ??? ... { v | ifv = HeapSel(H,grandpa,k)) } <: { v | v = “John” } (Rest of Heap) null 47 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays var k = “last”; child[k]; child parent { “first” : “John” } { “first” : “Ida” , “last” : “McCarthy” } { v | if has(child,k) then { v | ifv = sel(child,k) { v | else if has(parent,k) then { v | ifv = sel(parent,k) { v | else grandpa H ??? ... { v | ifv = HeapSel(H,grandpa,k)) } <: { v | v = “McCarthy” } (Rest of Heap) null 48 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays Prototype Chain Unrolling Key Idea: Reduce prototype semantics to decidable theory of arrays 49 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays A finite tuple… var nums = [0,1,2]; while (…) { nums[nums.length] = 17; } … extended to unbounded collection 50 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays var nums = [0,1,2]; while (…) { nums[nums.length] = 17; } delete nums[1]; A “hole” in the array for (i = 0; i < nums.length; i++) { sum += nums[i]; } Missing element within “length” 51 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays Track types, “packedness,” and length of arrays where possible -1 0 1 2 len(a) { a | a :: Arr(T) … T? T? T? T? … T? T? … { a | packed(a) … X T T T … T X … { a | len(a) = 10 } T? { x | T(x) x = undefined } X { x | x = undefined } 52 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays Encode tuples as arrays var tup = [17, “cacti”]; { a | a :: Arr(Any) { a | packed(a) len(a) = 2 { a | Int(sel(a,0)) { a | Str(sel(a,1)) } 53 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays var tup = [17, “cacti”]; tup[tup.length] = true; { a | a :: Arr(Any) { a | packed(a) len(a) = 3 {a| …} 54 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays DJS handles other array quirks: Special length property Array.prototype Non-integer keys 55 Tag-Tests Duck Typing Mutable Objects Prototypes Arrays 56 What About eval? … Old Types eval(“…”); … Arbitrary code loading 57 What About eval? … Old Types eval(“…”); //: #assume New Types New Types … Can Integrate DJS with “Contract Checking” at Run-time aka “Gradual Typing” 58 “Usability” DJS = Refinement Types + Nested Refinements + Flow Sensitive Types + Prototype Unrolling + Array Encoding } QuantifierFree Mechanisms Dependent JavaScript (DJS) [POPL ’12, OOPSLA ’12] F* + Dijkstra Expressiveness 59 Function Subtyping… { d | sel(d,“f”) :: <: { d | sel(d,“f”) :: } (x:Any) { y|y = x } } (x:Num) Num 60 Function Subtyping… { d | sel(d,“f”) :: { d | sel(d,“f”) :: } (x:Any) { y|y = x } } (x:Num) Num 61 Function Subtyping… { d | sel(d,“ ”f :: { d | sel(d,“”)f :: } (x:Any) { y|y = x } } (x:Num) Num … With Quantifiers ∀x,y. true ∧ y = f(x) y = x ∀x,y. Num(x) ∧ y = f(x) Num(y) Valid, but First-Order Logic is Undecidable 62 Function Subtyping… { d | sel(d,“ ”f :: { d | sel(d,“”)f :: } (x:Any) { y|y = x } } (x:Num) Num … Without Quantifiers! Nested Refinements Treat Function Types as Uninterpreted Implication = SMT Validity + Syntactic Subtyping 63 Heap Updates… var x = {}; x.f = 7; … With Quantifiers ∧ … h0 h1 h2 Encode Heap w/ McCarthy Operators ∧ sel(h1,x) = empty ∧ ∀y. x ≠ y sel(h1,y) = sel(h0,y) ∧ sel(h2,x) = upd(sel(h1,x),“f”,7) ∧ ∀y. x ≠ y sel(h2,y) = sel(h1,y) 64 Heap Updates… var x = {}; x.f = 7; … Without Quantifiers! h0 h1 h2 Flow-Sensitive Types (à la Alias Types) x:T1/H1 T2/H2 input type input heap output heap output type 65 Prototype Inheritance… Array Semantics… … Without Quantifiers! 66 “Usability” DJS = Refinement Types + Nested Refinements + Flow Sensitive Types + Prototype Unrolling + Array Encoding } QuantifierFree Mechanisms Dependent JavaScript (DJS) [POPL ’12, OOPSLA ’12] F* + Dijkstra Expressiveness 67 DJS Program Desugarer Based on Guha et al. [ECOOP ’10] Desugared Program Implementation JavaScript λ-Calculus + References + Prototypes DJS Program Desugarer Based on Guha et al. [ECOOP ’10] Desugared Program Implementation Programmer Chooses Warnings or Errors Local Type Inference Subtyping w/o Z3 If Possible Type Checker Z3 SMT Solver Benchmarks 13 Excerpts from: JavaScript, Good Parts SunSpider Benchmark Suite Google Closure Library LOC before/after 306 a 408 (+33%) Chosen to Stretch the Current Limits of DJS 70 Benchmarks 13 Excerpts from: JavaScript, Good Parts SunSpider Benchmark Suite Google Closure Library 9 Browser Extensions from: [Guha et al. Oakland ’11] 2 Examples from: Google Gadgets TOTALS LOC before/after 306 a 321 a 1,003 a 1,630 408 (+33%) 383 (+19%) 1,027 (+2%) 1,818 (+12%) 71 Already Improved by Simple Type Inference and Syntactic Sugar Plenty of Room for Improvement • Iterative Predicate Abstraction • Bootstrap from Run-Time Traces TOTALS 1,630 1,818 (+12%) 72 Benchmarks 13 Excerpts from: JavaScript, Good Parts SunSpider Benchmark Suite Google Closure Library 9 Browser Extensions from: [Guha et al. Oakland ’11] 2 Examples from: Google Gadgets TOTALS LOC Running before/after Time 306 a 321 a 1,003 a 1,630 408 (+33%) 10 sec 383 (+19%) 3 sec 1,027 (+2%) 19 sec 1,818 (+12%) 32 sec 73 Already Improved by Simple Optimizations • Avoid SMT Solver When Possible • Reduce Precision for Common Patterns Plenty of Room for Improvement TOTALS 1,630 1,818 (+12%) 32 sec 74 Dependent Types for JavaScript 1. Better Development Tools 2. Better Reliability 3. Better Performance “Usability” TypeScript Lightweight (unsound) static checking tools becoming popular Opportunity to improve IDE tools TypedJS Dependent JavaScript (DJS) [POPL ’12, OOPSLA ’12] F* + Dijkstra Expressiveness 76 Reliability / Security • Refinement types for security in presence of untrusted code (e.g. browser extensions) • Combine with static reasoning for JavaScript Performance • JITs use static analysis + profiling to optimize dynamic features (e.g. dictionaries, bignums) • Opportunity to enable more optimizations 77 Thanks! Dependent Types for JavaScript } 1. Better Development Tools 2. Better Reliability 3. Better Performance ravichugh.com/djs DJS is a Step Towards These Goals