Safe TypeScript

40
Safe TypeScript Aseem Rastogi University of Maryland, College Park End of Internship Talk Joint work with: Nikhil Swamy (RiSE, Internship mentor), Cédric Fournet (MSRC), Gavin Bierman (Oracle), Panagiotis Vekris (UCSD)

description

Safe TypeScript. Aseem Rastogi University of Maryland, College Park End of Internship Talk Joint work with: Nikhil Swamy (RiSE, Internship mentor), Cédric Fournet (MSRC), Gavin Bierman (Oracle), Panagiotis Vekris (UCSD). - PowerPoint PPT Presentation

Transcript of Safe TypeScript

Page 1: Safe TypeScript

Safe TypeScriptAseem Rastogi

University of Maryland, College Park

End of Internship TalkJoint work with: Nikhil Swamy (RiSE, Internship mentor),

Cédric Fournet (MSRC), Gavin Bierman (Oracle),

Panagiotis Vekris (UCSD)

Page 2: Safe TypeScript

TypeScriptGradually typed superset of JavaScript (www.typescriptlang.org)

• "Strong tools for large applications"• "Static checking, symbol-based navigation, statement completion,

code refactoring"

• "TypeScript offers classes, modules, and interfaces to help you build robust components"

• "Compiled into simple JavaScript"

Compared to JavaScript, this is a great leap forward!

Page 3: Safe TypeScript

But Typing JavaScript is Hard !For all its dynamic idioms

• TypeScript (like Dart and Closure) gives up soundness, intentionally• Types are uniformly erased when compiling to JavaScript• E.g. casts are unchecked

• Unsound type erasure is beneficial• Lightweight codegen (highly readable JavaScript output)• Performance identical to plain JavaScript• Types don’t get in the way of good programmers

• But it also has its disadvantages• TypeScript components are not robust

Page 4: Safe TypeScript

interface Iterator<A> { next(): A }

var x = { state:[..], index:0, next() { return state[index++]; } };client(x); //client:Iterator<number> => void TypeScript JavaScript

TypeScript compiler

function client(Iterator<number> it) { it["index"] = true;}

Client:

Provider:

var x = { state:[..], index:0, next() { return state[index++]; } };client(x);

function client(it) { it["index"] = true;}

Client:

Provider:

(Un-)Robustness of TypeScript Components

Type safety violation

Page 5: Safe TypeScript

interface Iterator<A> { next(): A }

var x = { state:[..], index:0, next() { return state[index++]; } };client(x); //client:Iterator<number> => void TypeScript JavaScript

TypeScript compiler

function client(Iterator<number> it) { (<any> it).index = -1;}

Client:

Provider:

var x = { state:[..], index:0, next() { return state[index++]; } };client(x);

function client(it) { it.index = -1;}

Client:

Provider:

Abstraction violation

(Un-)Robustness of TypeScript Components

Page 6: Safe TypeScript

Safe TypeScript

• Sound and efficient gradual typing is possible for idiomatic TypeScript

• Sound typing is beneficial• Finds type errors early

• Found and fixed 478 type errors in TypeScript compiler,• 1 functional correctness bug in NavierStokes, a heavily tested Octane benchmark

• Provably robust components

• But it also has its cost• A runtime performance penalty

• Ranging from 6% to 3x on 118,000 lines of code in 8 applications (details follow)

• Need to understand subtle corners of JS semantics and our type system

Page 7: Safe TypeScript

function f(x:any):number { return x.f;}

function f(x):number { return x.f;}

app.ts tsc app.ts

TypeScript type inference

TypeScript parsing

JavaScript emittingfunction f(x) { return x.f;}

app.js

Syntactic errors

Static diagnostic- basic type errors

TypeScript Workflow

Page 8: Safe TypeScript

function f(x:any):number { return x.f;}

function f(x):number { return x.f;}

app.ts tsc --safe app.ts

TypeScript type inference

TypeScript parsing

Safe TypeScript type checking

& instrumentation

function f(x:any):number { return RT.check(RT.Num, RT.read(x, "f"));}

JavaScript emitting

function f(x) { return RT.check(RT.Num, RT.read(x, "f"));}

app.js

- inconsistent subtyping- implicit downcast from any- variables not in scope- unsafe use of this- projecting methods as fields

Static diagnostics

Syntactic errors

Static diagnostic- basic type errors

Safe TypeScript WorkflowFully integrated into TypeScript v0.9.5 as an optional compiler flag

Page 9: Safe TypeScript

Highlights of The Type System• Object-oriented, with a mixture of structural and nominal types

• Nominal types provide a sound model of JavaScript's semantics of classes• In contrast: TypeScript is purely structural

• Types are compiled to provide precise run-time type information (RTTI)• Allows the runtime system to enforce invariants with dynamic checks• In contrast: RTTI in TypeScript is only what is available in JavaScript

• Selective type-erasure for performance and robustness• The type system ensures that erasure does not compromise safety• In contrast: TypeScript uniformly erases all types

By example …

Page 10: Safe TypeScript

Nominal Classes and Structural Interfaces

interface PointI { x:number; y:number}class PointC { constructor(public x:number, public y:number) { }}function f(p:PointC) { assert(p instanceof PointC);}

Page 11: Safe TypeScript

Nominal Classes and Structural Interfaces

interface PointI { x:number; y:number}class PointC { constructor(public x:number, public y:number) { }}

f({x:0, y:0});

TypeScript output: leads to runtime error in f

function f(p:PointC) { assert(p instanceof PointC);}

Safe TypeScript: Static Type Error

{x:number;y:number} is not a subtype of PointC

Page 12: Safe TypeScript

Nominal Classes and Structural Interfaces

interface PointI { x:number; y:number}class PointC { constructor(public x:number, public y:number) { }}

f(new PointC(0, 0));

function f(p:PointC) { assert(p instanceof PointC);}

Safe TypeScript: OK

Page 13: Safe TypeScript

Nominal Classes and Structural Interfaces

f(new PointC(0, 0));

function f(p:PointI) { return p.x + p.y;}

Safe TypeScript: OK

PointC is a subtype of PointI

interface PointI { x:number; y:number}class PointC { constructor(public x:number, public y:number) { }}

Page 14: Safe TypeScript

Highlights of The Type System• Object-oriented, with a mixture of structural and nominal types

• Nominal types provide a sound model of JavaScript's semantics of classes• In contrast: TypeScript is purely structural

• Types are compiled to provide precise run-time type information (RTTI)• Allows the runtime system to enforce invariants with dynamic checks• In contrast: RTTI in TypeScript is only what is available in JavaScript

• Selective type-erasure for performance and robustness• The type system ensures that erasure does not compromise safety• In contrast: TypeScript uniformly erases all types

Page 15: Safe TypeScript

Tag Objects with RTTI to Lock Invariantsfunction f(p:any) { p.x = "boom";}

function g(p:PointI) { f(p); assert(typeof p.x === "number");}

TypeScript output: leads to runtime error in g

Page 16: Safe TypeScript

Tag Objects with RTTI to Lock InvariantsshallowTag for structural objects

function f(p) { … } //coming up !

function g(p) { f(shallowTag(p, PointI)); …}

Safe TypeScript: Adds RTTI to objects to lock their type

shallowTag(x,t) = x.tag := combine(x.tag,t); x

function f(p:any) { p.x = "boom";}

function g(p:PointI) { f(p); assert(typeof p.x === "number");}

Page 17: Safe TypeScript

Instrumentation of any Codefunction f(p) { write(p, "x", "boom");}

Safe TypeScript: Enforces type invariants in any code

write(o,f,v) = let t = o.rtti; o[f] = check(v, t[f]);

function f(p:any) { p.x = "boom";}

function g(p:PointI) { f(p); assert(typeof p.x === "number");}

function g(p) { f(shallowTag(p, PointI)); …}

// fails

Page 18: Safe TypeScript

Tag Objects with RTTI to Lock InvariantsNo tagging for class instances

function g(p) { f(p); // no tagging …}

No tagging for class instances

Class instances have primitive RTTI (prototype chain)

function g(p:PointC) { f(p); assert(typeof p.x === "number");}

function f(p:any) { p.x = "boom";}

Page 19: Safe TypeScript

Runtime Checked Downcastsfunction f(p:PointI) { assert(typeof p.x === "number");}

function g(p:any) { f(<PointI> p);}

g({x:"boom",y:0});

TypeScript output: leads to runtime error in f

Page 20: Safe TypeScript

Runtime Checked DowncastsCheck fields invariants for structural types

function f(p) { … }

function g(p) { f(check(p, PointI));}…

Safe TypeScript: Checks downcasts at runtime

check(o, PointI) = if typeof o.x === “number” && typeof o.y === “number” then o.rtti := PointI; o else die

function f(p:PointI) { assert(typeof p.x === "number");}

function g(p:any) { f(<PointI> p);}

g({x:"boom",y:0});

Page 21: Safe TypeScript

Runtime Checked DowncastsSimple instanceof check for class instances

function f(p) { … }

function g(p) { f(check(p, PointC));}…

check(o, PointC) = if o instanceof PointC then o else die

Fast instanceof check for class instances

function f(p:PointC) { … }

function g(p:any) { f(<PointC> p);}

g({x:"boom",y:0});

Page 22: Safe TypeScript

Highlights of The Type System• Object-oriented, with a mixture of structural and nominal types

• Nominal types provide a sound model of JavaScript's semantics of classes• In contrast: TypeScript is purely structural

• Types are compiled to provide precise run-time type information (RTTI)• Allows the runtime system to enforce invariants with dynamic checks• In contrast: RTTI in TypeScript is only what is available in JavaScript

• Selective type-erasure for performance and robustness• The type system ensures that erasure does not compromise safety• In contrast: TypeScript uniformly erases all types

Page 23: Safe TypeScript

Safe TypeScript adds RTTI Tags On-demandinterface 3dPointI extends PointI { z:number;}

function f(r:any) { ... }

function g(q:PointI) { f(q);}

function h(p:3dPointI) { g(p); }

function main(p:3dPointI) { h(p);}

shallowTag(x, t) = x.rtti := combine(x.rtti, t); x

Safe TypeScript adds minimum RTTI to ensure safety

function main(p) { h(p); } // no tagging

function h(p) { g(shallowTag(p, {z:number}); // diff tagging}

function g(q) { f(shallowTag(q, PointI));}

function f(r) { … }

Page 24: Safe TypeScript

Programmer-controlled Type Erasure

A new operator on types: "Erased t"

A value of type Erased t is known to be a t statically, and at runtime it may not have RTTI

Erased types are erased from the JavaScript output

Page 25: Safe TypeScript

Programmer Controlled Type Erasure

function f(r) { ... }

function g(q) { f(q);}

function h(p) { g(p);}

Cannot pass erased types to any context

static type error

No tagging despite loss in precision

compiles as is

Recall that previously it was:g(shallowTag(p, {z:number}))

Safe TypeScript: Erased types must only be used statically

interface 3dPointI extends PointI { z:number;}

function f(r:any) { ... }

function g(q:PointI) { f(q);}

function h(p:3dPointI) { g(p);}

interface PointI extends Erased { x:number; y:number}

Page 26: Safe TypeScript

interface Iterator<A> { next(): A }

var x = { state:[..], index:0, next() { return state[index++]; } };client(x); //client:Iterator<number> => void

function client(Iterator<number> it) { it["index"] = true;}

Client:

Provider:

Revisiting Robust Components

Full formalization and proofs in technical report: http://research.microsoft.com/apps/pubs/default.aspx?id=224900

//runtime error

Robustness provided by Safe TypeScripttype soundness theorem

Several useful corollaries:

-- RTTI tags are always consistent-- RTTI tags evolve in subtyping hierarchy

Page 27: Safe TypeScript

interface Iterator<A> { next(): A }

var x = { state:[..], index:0, next() { return state[index++]; } };client(x); //client:Iterator<number> => void

function client(Iterator<number> it) { (<any> it).index = -1;}

Client:

Provider:

Revisiting Robust ComponentsProvider’s perspective

Page 28: Safe TypeScript

interface Iterator<A> extends Erased { next(): A }

var x = { state:[..], index:0, next() { return state[index++]; } };client(x); //client:Iterator<number> => void

function client(Iterator<number> it) { (<any> it).index = -1;}

Client:

Provider:

Revisiting Robust ComponentsProvider’s perspective

Stronger abstraction using erased types

Safe TypeScript provides an abstraction theorem for erased types

Full formalization and proofs in technical report: http://research.microsoft.com/apps/pubs/default.aspx?id=224900

//static error

Page 29: Safe TypeScript

Much more …

• Arrays (with mutability controls)• Dictionaries• Inheritance• Overloading• Generics with bounded polymorphism• Optional fields/arguments/variadic functions• Auto-boxing• Primitive prototype hierarchy• Closed vs. open records• Nominal interfaces• Enums• … All these features allow us to handle practical TypeScript developments …

Page 30: Safe TypeScript

Experience with SafeTypeScriptBootstrapping Safe TypeScript compiler (implemented in TypeScript v0.9.5)

• 90,000 lines of code (80,000 lines of TypeScript compiler)• Heavily class based, most of the code is carefully type annotated

• Static errors• 478 in total

• 98 uses of bi-variant array subtyping• 130 uses of covariant method argument subtyping• 128 cases of variable scoping issues• 52 cases of projecting a method (leading to potential unsound use of this

parameter)• …

• Runtime errors• 26 failed downcasts• 5 in our own code !

15% runtime overhead of type safety

Page 31: Safe TypeScript

Experience with SafeTypeScriptCompiling TypeScript v1.1

• 18,000 lines of code• Heavily interface based

• Static errors• 81 in total

• Mainly variable scoping and array subtyping

3x runtime overhead of type safety

High overhead because of more structural types

Have not optimized Safe TypeScript runtime for structural types

Page 32: Safe TypeScript

Experience with SafeTypeScriptOctane benchmarks

• 10,000 lines of code

• Static errors• Found 1 variable scoping bug in heavily tested NavierStokes

• High runtime overhead when no annotations• 2.4x (Splay) to 72x (Crypto), Average: 22x

• Performance recovers once we add type annotations• Average overhead: 6.5%

Page 33: Safe TypeScript

Demo

( Examples on Online Playground: http://research.microsoft.com/en-us/um/people/nswamy/Playground/TSSafe/)

Page 34: Safe TypeScript

Limitations and Work in Progress

• eval and friends• Adversarial typing of unsafe constructs – Swamy et. al. POPL'14

• Implementation limitations:• Does not support external modules• Current implementation is in TypeScript v0.9.5 that has

evolved to v1.1

• Ongoing discussion about integrating Safe TypeScript in TypeScript v1.1

Page 35: Safe TypeScript

Safe TypeScript

Download:http://research.microsoft.com/en-us/downloads/b250c887-2b79-4413-9d7a-5a5a0c38cc57/

Submitted POPL'15 paper:http://www.cs.umd.edu/~aseem/safets.pdf

Technical report (with full formalization and proofs):http://research.microsoft.com/apps/pubs/default.aspx?id=224900

Online playground:http://research.microsoft.com/en-us/um/people/nswamy/Playground/TSSafe/

Sound and efficient gradual type system for TypeScript

Page 36: Safe TypeScript

Structural types distinguish fields from methodsHandling this soundly

class Line { constructor(public p1:Point,

public p2:Point){} public moveUp() {

this.p1.y++; this.p2.y++; }}

function g(l:{moveUp:() => void}) {

var f = l.moveUp; f();

}

function h(p:Point) {g(new Line(p, p));

}

Compiles without warnings in TypeScript• Classes are convertible with their structure

window.p1 is undefined, so p1.y crashes

SafeTypeScript• Line does not contain a field called moveUp• Only a method called moveUp

Line <: {moveUp() :void; //method p1:Point; //field p2:Point} //field

Page 37: Safe TypeScript

class Line { constructor(public p1:Point,

public p2:Point){} public moveUp() {

this.p1.y++; this.p2.y++; }}

function g(l:{moveUp(): void}) {var f = l.moveUp; f();

}

function h(p:Point) {g(new Line(p, p));

}

Compiles without warnings in TypeScript• Classes are convertible with their structure

SafeTypeScript• Cannot project a method

Line <: {moveUp() :void; //method p1:Point; //field p2:Point} //field

Structural types distinguish fields from methodsHandling this soundly

Page 38: Safe TypeScript

class Line { constructor(public p1:Point,

public p2:Point){} public moveUp() {

this.p1.y++; this.p2.y++; }}

function g(l:{moveUp(): void}) {l.moveUp();

}

function h(p:Point) {g(new Line(p, p));g({moveUp() { this.p1.y++;

},p1:p,p2:p});

}

Compiles without warnings in TypeScript• Classes are convertible with their structure

SafeTypeScript: Ok!

Line <: {moveUp() :void; //method p1:Point; //field p2:Point} //field

SafeTypeScript: Object literal with a methodg({moveUp : () => {this.p1.y++;}, p1:p, p2:p})

SafeTypeScript: A function is not a method

Structural types distinguish fields from methodsHandling this soundly

Page 39: Safe TypeScript

Field Addition and DeletionDynamically typed unless it becomes static

function f(p:PointI) { p["z"] = 0; delete p.z;}

function f(p: PointI) { write(p, "z", 0); delete(p, "z"); }

SafeTypeScript: Both write and delete succeed at runtime

Page 40: Safe TypeScript

Field Addition and DeletionDynamically typed unless it becomes static

function g(p:3dPointI) { … }

function f(p:PointI) { p["z"] = 0; g(<3dPointI> p); delete p.z;}

function g(p:3dPointI) { … }

function f(p:PointI) { write(p, "z", 0); g(check(p, 3dPointI)); delete(p, "z");}

SafeTypeScript: write succeeds at runtime

SafeTypeScript: check succeeds at runtime

SafeTypeScript: delete fails at runtime – violates invariant of g