1 / 40

Safe TypeScript

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).

aquarius
Télécharger la présentation

Safe TypeScript

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 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)

  2. 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!

  3. 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

  4. (Un-)Robustness of TypeScript Components Type safety violation Client: Client: function client(Iterator<number> it) { it["index"] = true; } function client(it) { it["index"] = true; } Provider: Provider: interface Iterator<A> { next(): A } var x = { state:[..], index:0, next() { return state[index++]; } }; client(x); //client:Iterator<number> => void var x = { state:[..], index:0, next() { return state[index++]; } }; client(x); TypeScript JavaScript TypeScript compiler

  5. (Un-)Robustness of TypeScript Components Abstraction violation Client: Client: function client(Iterator<number> it) { (<any> it).index = -1; } function client(it) { it.index = -1; } Provider: Provider: interface Iterator<A> { next(): A } var x = { state:[..], index:0, next() { return state[index++]; } }; client(x); //client:Iterator<number> => void var x = { state:[..], index:0, next() { return state[index++]; } }; client(x); TypeScript JavaScript TypeScript compiler

  6. 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

  7. TypeScript Workflow tscapp.ts app.ts functionf(x):number { returnx.f; } TypeScript parsing Syntactic errors functionf(x:any):number { returnx.f; } TypeScript type inference • Static diagnostic • basic type errors app.js functionf(x) { return x.f; } JavaScript emitting

  8. Safe TypeScript WorkflowFully integrated into TypeScript v0.9.5 as an optional compiler flag tsc --safe app.ts app.ts functionf(x):number { returnx.f; } TypeScript parsing Syntactic errors functionf(x:any):number { returnx.f; } TypeScript type inference • Static diagnostic • basic type errors Static diagnostics • inconsistent subtyping • implicit downcast from any • variables not in scope • unsafe use of this • projecting methods as fields functionf(x:any):number { returnRT.check(RT.Num, RT.read(x, "f")); } Safe TypeScript type checking& instrumentation app.js functionf(x) { returnRT.check(RT.Num, RT.read(x, "f")); } JavaScript emitting

  9. 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 …

  10. Nominal Classes and Structural Interfaces interfacePointI { x:number; y:number } classPointC { constructor(public x:number, public y:number) { } } function f(p:PointC) { assert(p instanceofPointC); }

  11. Nominal Classes and Structural Interfaces interfacePointI { x:number; y:number } classPointC { constructor(public x:number, public y:number) { } } TypeScript output: leads to runtime error in f function f(p:PointC) { assert(p instanceofPointC); } Safe TypeScript: Static Type Error {x:number;y:number} is not a subtype of PointC f({x:0, y:0});

  12. Nominal Classes and Structural Interfaces interfacePointI { x:number; y:number } classPointC { constructor(public x:number, public y:number) { } } function f(p:PointC) { assert(p instanceofPointC); } f(newPointC(0, 0)); Safe TypeScript: OK

  13. Nominal Classes and Structural Interfaces interfacePointI { x:number; y:number } classPointC { constructor(public x:number, public y:number) { } } function f(p:PointI) { returnp.x+ p.y; } Safe TypeScript: OK PointC is a subtype of PointI f(newPointC(0, 0));

  14. 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

  15. Tag Objects with RTTI to Lock Invariants function f(p:any) { p.x = "boom"; } TypeScript output: leads to runtime error in g function g(p:PointI) { f(p); assert(typeofp.x === "number"); }

  16. Tag Objects with RTTI to Lock Invariants shallowTag for structural objects function f(p) { … } //coming up ! function f(p:any) { p.x = "boom"; } function g(p) { f(shallowTag(p, PointI)); … } function g(p:PointI) { f(p); assert(typeofp.x === "number"); } Safe TypeScript: Adds RTTI to objects to lock their type shallowTag(x,t) = x.tag := combine(x.tag,t); x

  17. Instrumentation of any Code function f(p:any) { p.x = "boom"; } function f(p) { write(p, "x", "boom"); } // fails function g(p) { f(shallowTag(p, PointI)); … } function g(p:PointI) { f(p); assert(typeofp.x === "number"); } Safe TypeScript: Enforces type invariants in any code write(o,f,v) = let t = o.rtti; o[f] = check(v, t[f]);

  18. Tag Objects with RTTI to Lock Invariants No tagging for class instances function f(p:any) { p.x = "boom"; } function g(p) { f(p); // no tagging … } function g(p:PointC) { f(p); assert(typeofp.x === "number"); } No tagging for class instances Class instances have primitive RTTI (prototype chain)

  19. Runtime Checked Downcasts function f(p:PointI) { assert(typeofp.x === "number"); } TypeScript output: leads to runtime error in f function g(p:any) { f(<PointI> p); } g({x:"boom",y:0});

  20. Runtime Checked Downcasts Check fields invariants for structural types function f(p:PointI) { assert(typeofp.x === "number"); } function f(p) { … } function g(p) { f(check(p, PointI)); } … function g(p:any) { f(<PointI> p); } g({x:"boom",y:0}); Safe TypeScript: Checks downcasts at runtime check(o, PointI) = if typeofo.x === “number” && typeofo.y === “number” then o.rtti := PointI; o else die

  21. Runtime Checked Downcasts Simple instanceof check for class instances function f(p:PointC) { …} function f(p) { … } function g(p:any) { f(<PointC> p); } g({x:"boom",y:0}); function g(p) { f(check(p, PointC)); } … check(o, PointC) = if o instanceof PointC then o else die Fast instanceof check for class instances

  22. 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

  23. Safe TypeScript adds RTTI Tags On-demand functionf(r) { … } interface 3dPointI extendsPointI { z:number; } function f(r:any) { ... } function g(q:PointI) { f(q); } function h(p:3dPointI) { g(p); } function main(p:3dPointI) { h(p); } function g(q) { f(shallowTag(q, PointI)); } function h(p) { g(shallowTag(p, {z:number}); // diff tagging } function main(p) { h(p); } // no tagging Safe TypeScript adds minimum RTTI to ensure safety shallowTag(x, t) = x.rtti := combine(x.rtti, t); x

  24. Programmer-controlled Type Erasure A new operator on types: "Erased t" A value of type Erasedtis known to be a t statically, and at runtime it may not have RTTI Erased types are erased from the JavaScript output

  25. Programmer Controlled Type Erasure interfacePointIextendsErased{ x:number; y:number } function f(r) { ... } function g(q) { f(q); } function h(p) { g(p); }  static type error Cannot pass erased types to any context interface 3dPointI extendsPointI { z:number; } function f(r:any) { ... } function g(q:PointI) { f(q); } function h(p:3dPointI) { g(p); }  compiles as is No tagging despite loss in precision Recall that previously it was: g(shallowTag(p, {z:number})) Safe TypeScript: Erased types must only be used statically

  26. Revisiting Robust Components Client: Robustness provided by Safe TypeScript type soundness theorem Several useful corollaries: -- RTTI tags are always consistent -- RTTI tags evolve in subtyping hierarchy function client(Iterator<number> it) { it["index"] = true; } //runtime error Provider: interface Iterator<A> { next(): A } var x = { state:[..], index:0, next() { return state[index++]; } }; client(x); //client:Iterator<number> => void Full formalization and proofs in technical report: http://research.microsoft.com/apps/pubs/default.aspx?id=224900

  27. Revisiting Robust ComponentsProvider’s perspective Client: function client(Iterator<number> it) { (<any> it).index = -1; } Provider: interface Iterator<A> { next(): A } var x = { state:[..], index:0, next() { return state[index++]; } }; client(x); //client:Iterator<number> => void

  28. Revisiting Robust ComponentsProvider’s perspective Client: function client(Iterator<number> it) { (<any> it).index = -1; } Stronger abstraction using erased types Safe TypeScript provides an abstraction theorem for erased types //static error Provider: interface Iterator<A> extendsErased { next(): A } var x = { state:[..], index:0, next() { return state[index++]; } }; client(x); //client:Iterator<number> => void Full formalization and proofs in technical report: http://research.microsoft.com/apps/pubs/default.aspx?id=224900

  29. 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 …

  30. 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

  31. 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

  32. 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%

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

  34. 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

  35. Safe TypeScript Sound and efficient gradual type system for 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/

  36. Structural types distinguish fields from methods Handling this soundly class Line { constructor(public p1:Point, public p2:Point){} publicmoveUp() { 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 Line <: {moveUp() :void; //method p1:Point; //field p2:Point}//field window.p1 is undefined, so p1.y crashes • SafeTypeScript • Line does not contain a field called moveUp • Only a method called moveUp

  37. Structural types distinguish fields from methods Handling this soundly class Line { constructor(public p1:Point, public p2:Point){} publicmoveUp() { 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 Line <: {moveUp() :void; //method p1:Point; //field p2:Point}//field • SafeTypeScript • Cannot project a method

  38. Structural types distinguish fields from methods Handling this soundly class Line { constructor(public p1:Point, public p2:Point){} publicmoveUp() { 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 Line <: {moveUp() :void; //method p1:Point; //field p2:Point}//field g({moveUp : () => {this.p1.y++;}, p1:p, p2:p}) SafeTypeScript: Ok! SafeTypeScript: Object literal with a method SafeTypeScript: A function is not a method

  39. Field Addition and DeletionDynamically typed unless it becomes static function f(p: PointI) { write(p, "z", 0); delete(p, "z"); } functionf(p:PointI) { p["z"] = 0; deletep.z; } SafeTypeScript: Both write and delete succeed at runtime

  40. Field Addition and DeletionDynamically typed unless it becomes static function g(p:3dPointI) { … } function f(p:PointI) { write(p, "z", 0); g(check(p, 3dPointI)); delete(p, "z"); } functiong(p:3dPointI) { … } function f(p:PointI) { p["z"] = 0; g(<3dPointI> p); deletep.z; } SafeTypeScript: write succeeds at runtime SafeTypeScript: check succeeds at runtime SafeTypeScript: delete fails at runtime – violates invariant of g

More Related