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)

Upload: aquarius

Post on 07-Jan-2016

447 views

Category:

Documents


2 download

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

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