model-driven software development - static analysis & error checking
TRANSCRIPT
Static Analysis & Error Checking
Course IN4308Master Computer Science
Delft University of Technology
Eelco Visserhttp://eelcovisser.org
Lecture 9
Coming up
Lecture 8: Context-sensitive transformation
★ design 2
★ transformation with dynamic rewrite rules
Lecture 9: Static analysis & error checking
★ name resolution, reference resolution
★ type analysis
Lecture 10: Code generation
★ string templates, code generation by model transformation
★ concrete object syntax
Lecture 11: Code generation strategies
★ customization of generated code
Consistency Checking
Consistency Checking
Syntax definition
★ what are well-formed sentences?
Static analysis
★ not all ‘well-formedness’ properties are context-free
★ consistency of compositions
★ consistency of expressions wrt declarations
Error reporting
★ indicate errors in editor
★ use sensible error message
Consistency Checking: Ingredients
Editor Interface
★ collecting and displaying errors, warnings
Error checking
★ checking static constraints and reporting errors
Type analysis
★ computing types of expressions
Name resolution
★ disambiguation of names
Reference resolving
★ linking identifiers to declarations
Consistency Checking: Generic Approach
Rename
★ make identifiers unique
Map
★ map identifiers to declarations
Project
★ compute properties of declarations, expressions
Check
★ check constraints
Editor Interface
module nwl-Builders
imports nwl-Builders.generated
builders
provider: include/nwl.ctree
observer: editor-analyze
module static-analysis
imports include/nwlimports entitiesimports utils
rules // static analysis
editor-analyze: (ast, path, fullpath) -> (errors, warnings, notes) with ...
editor/nwl-Builders.esvtrans/static-analysis.str
Editor Interface
editor-analyze: (ast, path, fullpath) -> (errors, warnings, notes) with errors := <collect-all(check, conc)> ast; warnings := <collect-all(constraint-warning, conc)> ast; notes := <collect-all(constraint-note, conc)> ast
Error Checking Rules
check : context -> (target, error) where assumption where require(constraint)
require(s) = not(s)
– Context: identifying points in the code to check– Assumptions: only report an error if certain assumptions hold (validating the context and avoiding spurious errors)– Constraints: checking for constraints at the context– Formulating an error message– Attribution of the error to a particular character range in the source text (usually, only part of the context
Error Checking: Binary Operators
check : e@BinOp(e1, op, e2) -> (e, $[operator [op] not defined for [<pp>t1] and [<pp>t2]]) where t1 := <type-of> e1 where t2 := <type-of> e2 where require(<type-of> e)
Pretty-Printing with String Interpolation
pp : Entity(x, prop*) -> $[entity [x] { [<map(pp)>prop*] }] pp : Property(x,t) -> $[[x] : [<pp>t] ] pp : SimpleType(x) -> x pp : SetType(t) -> $[Set<[<pp> t]>] pp : [] -> $[] pp : [t] -> <pp>t pp : [t1,t2|ts] -> $[[<pp>t1],[<pp>[t2|ts]]]
Origin Trackingcheck : e@BinOp(e1, op, e2) -> (e, $[operator [op] not defined for [<pp>t1] and [<pp>t2]]) where ...
Assign( Var("x"), Plus(IntLit("2"), Times(IntLit("3"), StringLit("4"))))
Assign( Var("x"), BinOp( IntLit("2") , "+" , BinOp(IntLit("3"), "+", StringLit("4")) ))
Error Checking: Control-Flow Statements
check : While(e, b) -> (e, $[Expression should have type Bool]) where t := <type-of> e where require(<eq>(t, SimpleType("Bool"))) check : If(e, b1, b2) -> (e, $[Expression should have type Bool]) where t := <type-of> e where require(<eq>(t,SimpleType("Bool")))
check : For(x, t, e, elem*) -> (e, $[[<pp>SetType(t)] expected]) where t2 := <type-of> e where require(<eq>(t2,SetType(t)))
check rules follow the same pattern: type analysis + local consistency check
Type Analysis
type-of : e -> t
compute type of expression
Type Analysis: Literals
type-of : StringLit(x) -> SimpleType("String") type-of : IntLit(x) -> SimpleType("Int")
Type Analysis: Binary Operators
type-of : BinOp(e1, op, e2) -> t where t := <function-type>(op, [<type-of>e1, <type-of>e2])
function-type : ("+", [SimpleType("String"), SimpleType("String")]) -> SimpleType("String") function-type : ("+", [SimpleType("Int"), SimpleType("Int")]) -> SimpleType("Int")
function-type : ("-", [SimpleType("Int"), SimpleType("Int")]) -> SimpleType("Int")
BinOp( IntLit("2"), "+", BinOp( IntLit("3"), "+", IntLit("4") ))
Type Analysis
BinOp( IntLit("2"), "+", BinOp( IntLit("3"), "+", IntLit("4") ))
SimpleType("Int")type-of
SimpleType("Int")type-of
Type Analysis
BinOp( IntLit("2"), "+", BinOp( IntLit("3"), "+", IntLit("4") ))
SimpleType("Int")type-of
SimpleType("Int")type-of
Type Analysis
BinOp( IntLit("2"), "+", BinOp( IntLit("3"), "+", IntLit("4") ))
SimpleType("Int")type-of
Type Analysis
Type Analysis: What is Type of Variable?
define page root(x : Int) { action exptest() { for(y : Int in {1,2,x}) { x := x + y; } } } type-of :
Var(x) -> t where t := ???
Assign( Var("x"), BinOp(Var("x"), "+", Var("y")))
type of variable not part of variable use
Variables: Map
declare-all = alltd(declare)
declare : Param(x, t) -> Param(x, t) with rules( TypeOf : x -> t )
type-of : Var(x) -> t where t := <TypeOf> x
Scope
define page root(x : Int) { action exptest() { for(x : Int in {1,2,x}) { print(x); } }}
multiple occurrences of same identifier corresponding to different declarations
Variables: Map + Renamerename-all = alltd(rename)
rename : Param(x, t) -> Param(y, t) with y := <rename-var>(x, t)
rename-var : (x, t) -> y with y := x{<new>}; rules( TypeOf : y -> t RenameId : x -> y )
rename : Var(x) -> Var(y) where y := <RenameId> x type-of : Var(x) -> t where t := <TypeOf> x
unique annotation
rename occurrences
map variable to type
Term Annotations
t{t1,...,tn}
add additional information to term without affecting signature
Variables: Check
check : e@Var(x) -> (e, $[Variable '[x]' not declared]) where require(<type-of>e)
Variable Binding Constructs
rename : For(x, t, e1, stat1*) -> For(y, t, e2, stat2*) with e2 := <rename-all> e1 with {| RenameId : y := <rename-var>(x, t) ; stat2* := <rename-all> stat1* |}
For defines local variable x in body stat1*’
is-lvalue = ?Var(_) <+ ?PropertyAccess(_, _)
check : Assign(e1, e2) -> (e1, $[Left-hand side of assignment should be variable or property access]) where require(<is-lvalue> e1)
check : Assign(e1, e2) -> (<id>, $[Type of lhs ('[<pp>t1]') does not match type of rhs ('[<pp>t2]')]) where t1 := <type-of>e1 where t2 := <type-of>e2 where require(<eq>(t1, t2))
Assignment
Editor Interface with Analysis
editor-analyze: (ast, path, fullpath) -> (errors, warnings, notes) with ast2 := <analyze> ast; errors := <collect-all(check, conc)> ast2; warnings := <collect-all(constraint-warning, conc)> ast2; notes := <collect-all(constraint-note, conc)> ast2
analyze = rename-all
Rename, Map, Project, Check
Rename
★ make local variables unique
Map
★ variables to their type
Project
★ compute type of expressions
Check
★ check constraints using types
Data Model Consistency
Consistency of Data Model Declarations
entity Blog { url : String (id) name : String (name) posts : Set<Post> author : User }
entity Post { url : String (id) title : String (name) text : WikiText blog : Blog (inverse:posts) author : User blog : Blog version : Int}
Consistency Constraints for Data Models
Unique declarations
★ entity names unique in model
★ property names unique in entity
Valid types
★ type is either primitive types (e.g. String) or declared entity type
Inverse properties
★ should refer to existing entity with existing property
Rename; Map; Project; Check
Rename
★ not needed: top-level declarations have global scope
Map
★ map identifier to AST of declaration
Project
★ lookup information in declaration
Check
★ check consistency using map & project
declare-def: ent@Entity(x, prop*) -> Entity(x, prop*) with rules( EntityDeclaration : x -> ent )
declaration-of : SimpleType(x) -> <EntityDeclaration> x
Entity Declarations: Map & Project
carrier-type = try(?SetType(<id>))
is-entity-type = where(SimpleType(EntityDeclaration))
is-simple-type = is-primitive-type <+ is-entity-type
name-of : Entity(x, prop*) -> x type-of : Entity(x, prop*) -> SimpleType(x)
Map
Project
Entity Declarations: Check
check : ent@Entity(x, prop*) -> (x, $[Entity '[x]' defined more than once]) where require(<EntityDeclaration> x => ent) check : t@SimpleType(x) -> (x, $[Type '[x]' is not defined]) where require(<is-simple-type>t) check : t@SetType(type) -> (t, $[Set should have entity type as argument]) where <is-simple-type> type where require(<is-entity-type> type)
Properties: Project
lookup-property(|x) = lookup-property(?Property(x,_,_)) lookup-property(s) : Entity(x, prop*) -> <fetch-elem(s)> prop* lookup-property(s) : SimpleType(x) -> <declaration-of; lookup-property(s)> type-of : Property(_, type, _) -> type inverse : Property(_, _, anno*) -> <fetch-elem(?Inverse(_))> anno*
Properties: Check
check: ent@Entity(x, prop*) -> errors where errors := <filter(check-property(|ent))> prop* where require(<not(?[])> errors)
check-property(|ent) : Property(name, type, annos) -> (name, $[Property '[name]' defined more than once]) where require(<type-of><lookup-property(|name)>ent => type)
Inverse Property: Check
check-property(|ent) : prop@Property(f, t, annos) -> (g, $[Inverse relation requires entity type]) where Inverse(g) := <inverse>prop where tc := <carrier-type> t where <is-simple-type> tc where require(<is-entity-type> tc)
Inverse Property: Check
check: ent@Entity(x, prop*) -> errors where errors := <filter(check-property(|ent)); not(?[])> prop* check-property(|ent) : Property(f, t, annos) -> (g, $[Inverse relation requires entity type]) where Inverse(g) := <inverse> where tc := <carrier-type> t where <is-simple-type> tc // non-existing type already produces error message where require(<is-entity-type> tc) check-property(|ent1) : Property(f, t, annos) -> (g, $[Entity '[<pp>tc]' has no property '[g]']) where Inverse(g) := <inverse> where tc := <carrier-type> t where <is-entity-type> tc where require(<lookup-property(|g)> tc) check-property(|ent) : Property(f, t, anno*) -> (g, $[Type of '[<pp>t1].[g]' should be [<pp>t3] or [<pp>SetType(t3)]]) where Inverse(g) := <inverse> where t1 := <carrier-type> t where t2 := <lookup-property(|g); type-of; carrier-type> t1 where t3 := <type-of>ent where require(<eq>(t2, t3))
multiple check rules necessary to check different cases
Property References
type-of : PropertyAccess(e, f) -> <type-of; lookup-property(|f); type-of> e
check : e1@PropertyAccess(e2, f) -> (f, $[[<pp>t] has no property '[f]]) where t := <type-of> e2 where require(<type-of>e1)
Template Consistency
Template definitions
★ should be unique
Template references
★ to existing definition
★ consistent with parameter declarations
Template Consistencydefine page editpost(p : Post) { action save() { p.version := p.version + 1; return post(p); } header{output(p.title)} form{ input(p.url) input(p.title) input(p.text) submit save() { "Save" } }}
Template ASTdefine page editpost(p : Post) { action save() { p.version := p.version + 1; return post(p); } header{output(p.title)} form{ input(p.url) input(p.title) input(p.text) submit save() { "Save" } }}
TemplateDef( [Page()], "editpost", [Param("p", SimpleType("Post"))], [ Action( "save" , [] , [ Assign( PropertyAccess(Var("p"), "version") , Plus(PropertyAccess(Var("p"), "version"), IntLit("1")) ) , ReturnPage(PageRef("post", [Var("p")])) ] ) , CallElems( "header" , [CallArgs("output", [PropertyAccess(Var("p"), "title")])] ) , CallElems( "form" , [ CallArgs("input", [PropertyAccess(Var("p"), "url")]) , CallArgs("input", [PropertyAccess(Var("p"), "title")]) , CallArgs("input", [PropertyAccess(Var("p"), "text")]) , Submit("save", [], [String(""Save"")]) ] ) ])
Template Definitions: Map + Rename
declare-def : def@TemplateDef(mod*, x, param*, elem*) -> def with sig := <signature-of> def; rules( Template : x -> def Template : sig -> def )
rename : TemplateDef(mod*, x, param1*, elem1*) -> <declare-def> TemplateDef(mod*, x, param2*, elem3*) with {| RenameId, RenameAction : param2* := <rename-all> param1* ; elem2* := <alltd(rename-action)> elem1* ; elem3* := <rename-all> elem2* |}
rename local variables in template definition
Template Definitions: Projectis-page-def = ?TemplateDef([Page()],_,_,_)
param-types = is-list; map(?Param(_,<id>)) param-types : TemplateDef(mod*, x, param*, elem*) -> <param-types> param* signature-of : TemplateDef(mod*, x, param*, elem*) -> (x, <param-types>param*) declaration-of : TemplateDef(mod*, x, param*, elem*) -> <signature-of; Template>
declaration-of : Navigate(ref, elems) -> <declaration-of> ref declaration-of : PageRef(x, e*) -> <Template> x call-of : PageRef(x, e*) -> (x, e*)
Template Definitions: Check Uniqueness
check : def@TemplateDef(mod*, x, param*, elem*) -> (x, $[Multiple definitions for page '[x]']) where <is-page-def> def where require(<Template> x => def) check : def@TemplateDef(mod*, x, param*, elem*) -> (x, $[Multiple definitions for template with signature [sig]]) where not(is-page-def) where require(<declaration-of> def => def) where sig := <signature-of;pp-sig> def
Checking Template/Page/Function Calls
List of expressions consistent with list of types
★ zip
Multiple possible error causes
★ call of non-existing definition
★ parameter arity mismatch
★ argument type mismatch
Argument checking reusable
Templates: Check Page References
check : PageRef(x, e*) -> (x, $[Navigation to non-existing page]) where require(declaration-of) check : PageRef(x, e*) -> [(x, $[Navigation to template '[x]' (not a page)])] where def := <declaration-of> where require(<is-page-def> def) check : PageRef(x, e*) -> <check-args>
Template Call: Project
signature-of : Call(x, e*, elem*) -> (x, <map(type-of)> e*)
call-of : Call(x, e*, elem*) -> (x, e*)
declaration-of : Call(x, e*, elem*) -> <signature-of; Template>
is-primitive-template = ?"input" <+ ?"output" <+ ?"form"
Templates: Check Template Calls
check : Call(x, e*, elem*) -> (x, $[Template '[x]' is not defined]) where not(<is-primitive-template> x) where require(<Template> x)
check : Call(x, e*, elem*) -> (x, $[No definition for template with signature '[x]([<map(type-of);pp> e*])']) where not(<is-primitive-template> x) where <Template> x where require(declaration-of) constraint-warning : Call(x, e*, elem*) -> [(x, $[Page definition is used as template])] where def := <declaration-of> where require(not(<is-page-def> def)) check : Call(x, e*, elem*) -> <check-args>
Checking Call Arguments
check-args = !(<call-of>, <declaration-of>); (check-arg-types <+ check-args-arity)
check-arg-types : ((f, e*), def) -> errors where errors := <zip; filter(check-arg); not(?[])> (e*, <param-types> def) check-arg : (e, t) -> (e, $[Argument of type '[<pp>t]' expected (not of type '[<pp>t2]')]) where t2 := <type-of> e where require(<eq>(t, t2)) check-args-arity : ((f, e*), def) -> [(f, $['[f]' expects [<int-to-string>l] arguments; [<int-to-string>k] provided])] with k := <length>e* with l := <param-types; length> def where require(<eq>(k, l))
Reference Resolution
Reference Resolution
Aiding program navigation
★ Hover-click on identifier to jump to declaration
Reuse name resolution infrastructure
editor-resolve: (source, position, ast, path, fullpath) -> target where target := <compute-target> source
Reference Resolution
module nwl-References
imports nwl-References.generated
references
reference _ : editor-resolve
editor-resolve: (SimpleType(type), position, ast, path, fullpath) -> target where Entity(target,_) := <EntityDeclaration> type editor-resolve: (ref@PageRef(x,e*), position, ast, path, fullpath) -> target where TemplateDef(_,target,_,_) := <declaration-of> ref
From Use to Declaration
Schedule
Case 3
★ Syntax definition & term rewriting
★ Deadline: May 4
Design 2
★ Make a proposal (can be submitted separately)
★ Deadline: May 5
Lab this week
★ Finish Case 3
★ Syntax for Design 2
Next
★ Lecture 10: code generation