Dependent Types for Javascript
Total Page:16
File Type:pdf, Size:1020Kb
Dependent Types for JavaScript Ravi Chugh David Herman Ranjit Jhala University of California, San Diego Mozilla Research University of California, San Diego [email protected] [email protected] [email protected] Abstract ioms but in a purely functional setting. The main insight in We present Dependent JavaScript (DJS), a statically typed System D is to dependently type all values with formulas dialect of the imperative, object-oriented, dynamic language. drawn from an SMT-decidable refinement logic. We use an DJS supports the particularly challenging features such as SMT solver to reason about the properties it tracks well, run-time type-tests, higher-order functions, extensible ob- namely, control-flow invariants and dictionaries with dy- jects, prototype inheritance, and arrays through a combina- namic keys that bind scalar values. But to describe dynamic tion of nested refinement types, strong updates to the heap, keys that bind rich values like functions, System D encodes and heap unrolling to precisely track prototype hierarchies. function types as logical terms and nests the typing relation With our implementation of DJS, we demonstrate that the as an uninterpreted predicate within the logic. By dividing type system is expressive enough to reason about a variety work between syntactic subtyping and SMT-based validity of tricky idioms found in small examples drawn from several checking, the calculus supports fully automatic checking of sources, including the popular book JavaScript: The Good dynamic features like run-time type tests, value-indexed dic- Parts and the SunSpider benchmark suite. tionaries, higher-order functions, and polymorphism. In this paper, we scale up the System D calculus to Categories and Subject Descriptors D.3.3 [Programming Dependent JavaScript (abbreviated to DJS), an explicitly Languages]: Language Constructs and Features – Inheri- typed dialect of a real-world, imperative, object-oriented, tance, Polymorphism; F.3.1 [Logics and Meanings of Pro- dynamic language. We bridge the vast gap between System D grams]: Specifying and Verifying and Reasoning about Pro- and JavaScript in three steps. grams – Logics of Programs Step 1: Imperative Updates. The types of variables in Keywords Refinement Types, JavaScript, Strong Updates, JavaScript are routinely “changed” either by assignment or Prototype Inheritance, Arrays by incrementally adding or removing fields to objects bound to variables. The presence of mutation makes it challenging 1. Introduction to assign precise types to variables, and the standard method Dynamic languages like JavaScript, Python, and Ruby are of assigning a single “invariant” reference type that overap- widely popular for building both client and server applica- proximates all values held by the variable is useless in the tions, in large part because they provide powerful sets of JavaScript setting. We overcome this challenge by extending features — run-time type tests, mutable variables, extensi- our calculus with flow-sensitive heap types (in the style of ble objects, and higher-order functions. But as applications [2, 12, 15, 31, 32]) which allow the system to precisely track grow, the lack of static typing makes it difficult to achieve the heap location each variable refers to as well as alias- reliability, security, maintainability, and performance. In re- ing relationships, thereby enabling strong updates through sponse, several authors have proposed type systems which mutable variables. Our formulation of flow-sensitive heaps provide static checking for various subsets of dynamic lan- combined with higher-order functions and refinement types guages [5, 16, 22, 23, 30, 36]. is novel, and allows DJS to express precise pre- and post- Recently, we developed System D [9], a core calculus conditions of heaps, as in separation logic [17]. for dynamic languages that supports the above dynamic id- Step 2: Prototype Inheritance. Each JavaScript object maintains an implicit link to the “prototype” object from which it derives. To resolve a key lookup from an object at Permission to make digital or hard copies of all or part of this work for personal or run-time, JavaScript transitively follows its prototype links classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation until either the key is found or the root is reached without on the first page. To copy otherwise, to republish, to post on servers or to redistribute success. Thus, unlike in class-based languages, inheritance to lists, requires prior specific permission and/or a fee. OOPSLA’12, October 19–26, 2012, Tucson, Arizona, USA. relationships are computed at run-time rather than provided Copyright © 2012 ACM 978-1-4503-1561-6/12/10. $10.00 as declarative specifications. The semantics of prototypes is challenging for static typing, because to track the type of a key binding, the system must statically reason about a po- tentially unbounded number of prototype links! In DJS, we program.js basics.dref System !D objects.dref solve this problem with a novel decomposition of the heap Type prelude.dref Checker into a “shallow” part, for which we precisely track a finite DJS Desugarer program.dref number of prototype links, and a “deep” part, for which we do not have precise information, represented abstractly via a logical heap variable. We unroll prototype hierarchies in Figure 1. Architecture of DJS shallow heaps to precisely model the semantics of object op- erations, and we use uninterpreted heap predicates to reason As λJS is a core language with well-understood semantics abstractly about deep parts. In this way, we reduce the rea- and proof techniques, the translation paves a path to a typed soning about unbounded, imperative, prototype hierarchies dialect of JavaScript: define a type system for the core lan- to the underlying decidable, first-order, refinement logic. guage and then type check desugared JavaScript programs. We take this path by developing System !D (pronounced Step 3: Arrays. JavaScript arrays are simply objects whose “D-ref”), a new calculus based on λ . Although the oper- keys are string representations of integers. Arrays are com- JS ational semantics of System !D is straightforward, the dy- monly used both as heterogeneous tuples (that have a fixed namic features of the language ensure that building a type number of elements of different types) as well as homoge- system expressive enough to support desugared JavaScript neous collections (that have an unbounded number of ele- idioms is not. We solve this problem by scaling the purely ments of the same type). The overloaded use of arrays, to- functional technique of nested refinement types up to the im- gether with the fact that arrays are otherwise syntactically perative, object-oriented, setting of real-world JavaScript. indistinguishable and have the same prototype-based seman- Figure 1 depicts the architecture of our approach: we tics as non-array objects, makes it hard to statically reason desugar a Dependent JavaScript (DJS) file program.js to about the very different ways in which they are used. In DJS, the System !D file program.dref, which is analyzed by the we use nested refinements to address the problem neatly by type checker along with a standard prelude comprising three uniformly encoding tuples and collections with refinement files (basics.dref, objects.dref, and prelude.dref) predicates, and by using intersection types that simultane- that model JavaScript semantics. ous encode the semantics of tuples, collections, and objects. Terminology. JavaScript has a long history and an evolv- Expressiveness. We have implemented DJS (available at ing specification. In this paper, we say “JavaScript” to ravichugh.com/djs) and demonstrated its expressiveness roughly mean ECMAScript Edition 3, the standard version by checking a variety of properties found in small but sub- of the language for more than a decade [24]. We say “ES5” tle examples drawn from a variety of sources, including the to refer to Edition 5 of the language, recently released by the popular book JavaScript: The Good Parts [10] and the Sun- JavaScript standards committee [13]; Edition 4 was never Spider benchmark suite [34]. Our experiments show that standardized. We say “ES6” to refer to features proposed for several examples simultaneously require the gamut of fea- the next version of the language, scheduled to be finalized tures in DJS, but that many examples conform to recurring within the next one or two years. DJS includes a large set of patterns that rely on particular aspects of the type system. core features common to all editions. We identify several ways in which future work can handle these patterns more specifically in order to reduce the an- 2.1 Base Types, Operators, and Control Flow notation burden and performance for common cases, while Consider the following function adapted from [9] and anno- falling back to the full expressiveness of DJS in general. tated in DJS. A function type annotation is written just above Thus, we believe that DJS provides a significant step towards the definition inside a JavaScript comment demarcated by an truly retrofitting JavaScript with a practical type system. additional : character. We typeset annotations in math mode for clarity, but the ASCII versions parsed by our type checker 2. Overview are quite similar. Let us begin with an informal overview of the semantics of JavaScript. We will emphasize the aspects that are the /*: x∶Top → {ν S ite Num(x) Num(ν) Bool(ν)} */ most distinctive and challenging from the perspective of type function negate(x) { if(typeofx == "number"){ return0- x; } system design, and describe the key insights in our work that else{ return!x; } } overcome these challenges. JavaScript Semantics by Desugaring. Many corner cases The typeof operator is a facility used pervasively to direct of JavaScript are clarified by λJS [21], a syntax-directed control flow based on the run-time “tag” of a value.