├── .gitattributes ├── .gitignore ├── .scalafmt.conf ├── README.md ├── build.sbt ├── examples ├── 7x7.tip ├── a1.tip ├── a2.tip ├── a3.tip ├── a4.tip ├── a6.tip ├── a7.tip ├── andersen.tip ├── apply1.tip ├── apply2.tip ├── available.tip ├── block.tip ├── block_decl.tip ├── cfa.tip ├── cfg.tip ├── cmpfunc.tip ├── code.tip ├── comp.tip ├── constants1.tip ├── constants2.tip ├── copyconst.tip ├── div.tip ├── equal.tip ├── err_assignfunc.tip ├── err_cmpfunc.tip ├── err_cmpfunc2.tip ├── err_cmpfunc3.tip ├── err_decl_use.tip ├── err_doubledecl1.tip ├── err_doubledecl2.tip ├── err_funass.tip ├── err_funass2.tip ├── err_func.tip ├── err_locals.tip ├── err_notdecl.tip ├── err_notlocal.tip ├── err_notlocal2.tip ├── err_unify1.tip ├── err_unify2.tip ├── error.tip ├── ex1.tip ├── ex5.tip ├── factorial_iterative.tip ├── factorial_recursive.tip ├── fib.tip ├── foo.tip ├── func.tip ├── if_short_if.tip ├── input1.tip ├── interpreter_test.tip ├── interval0.tip ├── interval1.tip ├── interval2.tip ├── interval3.tip ├── liveness.tip ├── locals.tip ├── loop.tip ├── malloc.tip ├── map.tip ├── mccarthy91.tip ├── mono.tip ├── mono2.tip ├── mpolyrec.tip ├── norm_while.tip ├── normalised.tip ├── nullpointer.tip ├── onelevel.tip ├── parametrized_main.tip ├── parsing.tip ├── pointers.tip ├── poly.tip ├── poly2.tip ├── poly3.tip ├── poly4.tip ├── ptr1.tip ├── ptr2.tip ├── ptr3.tip ├── ptr4.tip ├── ptr5.tip ├── ptr6.tip ├── ptr7.tip ├── reaching.tip ├── rec.tip ├── record1.tip ├── record2.tip ├── record3.tip ├── record4.tip ├── record5.tip ├── record6.tip ├── rectype.tip ├── shape.tip ├── signs.tip ├── signs_fun.tip ├── signs_fun_cfa.tip ├── simple1.tip ├── slicing.tip ├── steensgaard1.tip ├── steensgaard2.tip ├── steensgaard3.tip ├── succ.tip ├── symbolic1.tip ├── symbolic2.tip ├── t1.tip ├── t2.tip ├── testdiv.tip ├── verybusy.tip └── while_short_if.tip ├── ideafiles ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml └── inspectionProfiles │ └── Project_Default.xml ├── project └── plugins.sbt ├── src └── tip │ ├── Tip.scala │ ├── analysis │ ├── Analysis.scala │ ├── AndersenAnalysis.scala │ ├── AvailableExpAnalysis.scala │ ├── CallContext.scala │ ├── ConstantPropagationAnalysis.scala │ ├── ControlFlowAnalysis.scala │ ├── CopyConstantPropagationAnalysis.scala │ ├── DeclarationAnalysis.scala │ ├── Dependencies.scala │ ├── FlowSensitiveAnalysis.scala │ ├── IntervalAnalysis.scala │ ├── LiveVarsAnalysis.scala │ ├── PossiblyUninitializedVarsAnalysis.scala │ ├── SignAnalysis.scala │ ├── SimpleSignAnalysis.scala │ ├── SteensgaardAnalysis.scala │ ├── TaintAnalysis.scala │ ├── TypeAnalysis.scala │ └── ValueAnalysis.scala │ ├── ast │ ├── Ast.scala │ ├── AstNodeData.scala │ ├── AstOps.scala │ ├── AstPrinters.scala │ ├── DepthFirstAstVisitor.scala │ ├── TipNormalizers.scala │ └── TipSublanguages.scala │ ├── cfg │ ├── CfgNode.scala │ ├── CfgOps.scala │ ├── FragmentCfg.scala │ ├── InterproceduralProgramCfg.scala │ └── IntraproceduralProgramCfg.scala │ ├── concolic │ ├── ConcolicEngine.scala │ ├── ExecutionTree.scala │ ├── SMTSolver.scala │ ├── SymbolicInterpreter.scala │ └── SymbolicValues.scala │ ├── interpreter │ ├── ConcreteValues.scala │ ├── Interpreter.scala │ └── ValueSpecification.scala │ ├── lattices │ ├── ConstantPropagationLattice.scala │ ├── EdgeEnvLattice.scala │ ├── EdgeFunctionLattice.scala │ ├── GenericLattices.scala │ ├── IntervalLattice.scala │ ├── LatticeWithOps.scala │ └── SignLattice.scala │ ├── parser │ └── TipParser.scala │ ├── solvers │ ├── FixpointSolvers.scala │ ├── IDEAnalysis.scala │ ├── IDESolver.scala │ ├── SimpleCubicSolver.scala │ ├── SpecialCubicSolver.scala │ ├── SummarySolver.scala │ ├── UnificationTerms.scala │ └── UnionFindSolver.scala │ ├── types │ └── Types.scala │ └── util │ ├── DotTools.scala │ ├── Log.scala │ ├── MapUtils.scala │ ├── Output.scala │ └── TipProgramException.scala ├── tip └── tip.bat /.gitattributes: -------------------------------------------------------------------------------- 1 | *.scala text eol=lf 2 | *.sbt text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | .classpath 3 | .project 4 | .settings 5 | .idea/ 6 | /target 7 | /project 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | style = IntelliJ 2 | maxColumn = 160 3 | binPack.literalArgumentLists = false 4 | align = none 5 | //align.openParenCallSite = true 6 | //align.openParenDefnSite = true 7 | includeCurlyBraceInSelectChains = false 8 | rewrite.rules = [ redundantbraces, sortimports, SortModifiers ] 9 | rewrite.redundantBraces.stringInterpolation = true 10 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "tip" 2 | 3 | scalaVersion := "2.12.20" 4 | 5 | trapExit := false 6 | 7 | scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") 8 | 9 | libraryDependencies += "org.parboiled" %% "parboiled" % "2.1.8" 10 | libraryDependencies += "com.regblanc" % "scala-smtlib_2.12" % "0.2.1" 11 | 12 | Compile / scalaSource := baseDirectory.value / "src" 13 | -------------------------------------------------------------------------------- /examples/a1.tip: -------------------------------------------------------------------------------- 1 | map(l,f,z) { 2 | var r; 3 | if (l==null){ 4 | r=z; 5 | } else { 6 | r=(f)(map(*l,f,z)); 7 | } 8 | return r; 9 | } 10 | 11 | foo(i) { 12 | return i+1; 13 | } 14 | 15 | main() { 16 | var h,t,n; 17 | t = null; 18 | n = 42; 19 | while (n>0) { 20 | n = n-1; 21 | h = alloc null; 22 | *h = t; 23 | t = h; 24 | } 25 | return map(h,foo,0); 26 | } 27 | -------------------------------------------------------------------------------- /examples/a2.tip: -------------------------------------------------------------------------------- 1 | bar(x){ 2 | return 4; 3 | } 4 | 5 | unnormalized(){ 6 | var foo; 7 | var x; 8 | var d; 9 | 10 | d = *x; 11 | *d = (*foo)(&x,bar(alloc null)); 12 | 13 | return 10; 14 | } 15 | 16 | 17 | normalized(){ 18 | var foo; 19 | var x; 20 | var a1,a2; 21 | var m; 22 | var f1,f2; 23 | var d; 24 | var r; 25 | 26 | f1 = bar; 27 | f2 = *foo; 28 | 29 | m = alloc null; 30 | 31 | a1 = &x; 32 | a2 = (f1)(m); 33 | d = *x; 34 | r = (f2)(a1,a2); 35 | *d = r; 36 | return 10; 37 | } 38 | 39 | /* 40 | test(){ 41 | var x,y; 42 | y = (***x)(1,2,3); 43 | return 45; 44 | }*/ -------------------------------------------------------------------------------- /examples/a3.tip: -------------------------------------------------------------------------------- 1 | bar(x) { 2 | return x; 3 | } 4 | 5 | foh(x,y) { 6 | return x+y; 7 | } 8 | 9 | baz(x, y) { 10 | *y = foh(4,3); 11 | *y = ***x * *y; 12 | return *y; 13 | //return 1; 14 | } 15 | 16 | main() { 17 | var foo, x, y, z, w; 18 | w = baz; 19 | foo = &w; 20 | z = 8; 21 | y = &z; 22 | x = &y; 23 | 24 | **x = (*foo)(&x,bar(alloc 0)); 25 | 26 | return **x; 27 | //return 1; 28 | } -------------------------------------------------------------------------------- /examples/a4.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y,p,q; 3 | q = alloc null; 4 | y = &q; 5 | x = *y; 6 | p = null; 7 | y = &p; 8 | *x = p; 9 | return 1; 10 | } -------------------------------------------------------------------------------- /examples/a6.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y,z,a; 3 | x = null; 4 | y = &x; 5 | z = &x; 6 | z = &y; 7 | a = *z; 8 | *a = null; 9 | return 0; 10 | } -------------------------------------------------------------------------------- /examples/a7.tip: -------------------------------------------------------------------------------- 1 | main () { 2 | var x, y, z; 3 | x = 42; 4 | y = 43; 5 | if (x) { 6 | output 0; 7 | } else { 8 | output 1; 9 | } 10 | return 4; 11 | } 12 | -------------------------------------------------------------------------------- /examples/andersen.tip: -------------------------------------------------------------------------------- 1 | f() { 2 | var a,b,d,e,tmp; 3 | a = &d; 4 | b = &e; 5 | a = b; 6 | tmp = alloc null; 7 | *a = tmp; 8 | return 0; 9 | } 10 | 11 | main() { 12 | var a,b,z,w,x,y,tmp; 13 | z = &x; 14 | w = &a; 15 | a = 42; 16 | if (a > b) { 17 | tmp = &a; 18 | *z = tmp; 19 | y = &b; 20 | } else { 21 | x = &b; 22 | y = w; 23 | } 24 | return 0; 25 | } -------------------------------------------------------------------------------- /examples/apply1.tip: -------------------------------------------------------------------------------- 1 | apply(f,a){ 2 | return (f)(a); 3 | } 4 | -------------------------------------------------------------------------------- /examples/apply2.tip: -------------------------------------------------------------------------------- 1 | apply(f,a){ 2 | return f(a); 3 | } 4 | 5 | fib(n){ 6 | var result; 7 | 8 | if( n>1 ){ 9 | result = fib(n-1)+fib(n-2); 10 | } else { 11 | result=1; 12 | } 13 | 14 | return result; 15 | } 16 | 17 | main(){ 18 | return apply(fib,5); 19 | } 20 | -------------------------------------------------------------------------------- /examples/available.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y,z,a,b; 3 | z = a+b; 4 | y = a*b; 5 | while (y > a+b) { 6 | a = a+1; 7 | x = a+b; 8 | } 9 | return a; 10 | } -------------------------------------------------------------------------------- /examples/block.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x; 3 | { 4 | x = input; 5 | } 6 | return x; 7 | } -------------------------------------------------------------------------------- /examples/block_decl.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y; 3 | var z; 4 | var w; 5 | x = 0; 6 | { 7 | z = x; 8 | w = z; 9 | x = w; 10 | } 11 | y = x + 1; 12 | return x; 13 | } -------------------------------------------------------------------------------- /examples/cfa.tip: -------------------------------------------------------------------------------- 1 | inc(i) { return i+1; } 2 | dec(j) { return j-1; } 3 | ide(k) { return k; } 4 | 5 | foo(n,f) { 6 | var r; 7 | if (n==0) { f=ide; } 8 | r = f(n); 9 | return r; 10 | } 11 | 12 | main() { 13 | var x,y; 14 | x = input; 15 | if (x>0) { y = foo(x,inc); } else { y = foo(x,dec); } 16 | return y; 17 | } -------------------------------------------------------------------------------- /examples/cfg.tip: -------------------------------------------------------------------------------- 1 | main(n) { 2 | var f; 3 | f = 1; 4 | if (n>0) { 5 | f = f*n; 6 | n = n-1; 7 | } 8 | return f; 9 | } -------------------------------------------------------------------------------- /examples/cmpfunc.tip: -------------------------------------------------------------------------------- 1 | 2 | f(){ 3 | return 17; 4 | } 5 | 6 | g(){ 7 | return 17; 8 | } 9 | 10 | main(){ 11 | var n; 12 | if( f==g ){ 13 | n=10; 14 | } 15 | return 0; 16 | } -------------------------------------------------------------------------------- /examples/code.tip: -------------------------------------------------------------------------------- 1 | g(a){ 2 | *a = 17; 3 | return 11; 4 | } 5 | 6 | succ(i){ 7 | return i+1; 8 | } 9 | 10 | h(pf,i){ 11 | return (pf)(i); 12 | } 13 | 14 | f(a){ 15 | var b,c,d; 16 | b = 3; 17 | return a+b; 18 | } 19 | 20 | test(i){ 21 | while( i > 0 ){ 22 | output i; 23 | i = i - 1; 24 | } 25 | return 0; 26 | } 27 | 28 | main(){ 29 | /*var a; 30 | a = 17; 31 | if( a>17 ){ 32 | a = 13; 33 | } else { 34 | a = 19; 35 | }*/ 36 | //return h(succ,17); 37 | return test(5); 38 | /* 39 | var a,b,c; 40 | b = alloc null; 41 | a=100; 42 | //c=g(b); 43 | c=g(&a); 44 | //c=h(10); 45 | return f(4)+ a;//*b; 46 | */ 47 | } 48 | -------------------------------------------------------------------------------- /examples/comp.tip: -------------------------------------------------------------------------------- 1 | succ(i){ 2 | return i+1; 3 | } 4 | foo(p) { 5 | return (p)(5); 6 | } 7 | main(){ 8 | return foo(succ); 9 | } -------------------------------------------------------------------------------- /examples/constants1.tip: -------------------------------------------------------------------------------- 1 | /*f(a,b){ 2 | return 5*a + 7*b; 3 | }*/ 4 | 5 | 6 | main(){ 7 | var x,y,z,n; 8 | 9 | x=12; 10 | y=17; 11 | z=8; 12 | 13 | n=4; 14 | z = 3*(x+y-z) + 5 -17; 15 | while(n > 0){ 16 | //x = f(y,z); 17 | y = x * input; 18 | n=n-1; 19 | } 20 | z = 3*(x+y-z) + 5 -16; 21 | return 123; 22 | } 23 | 24 | 25 | 26 | /* 27 | main(){ 28 | var n; 29 | 30 | n=4; 31 | while(n > 0){ 32 | n=n-1; 33 | } 34 | return n; 35 | } 36 | */ -------------------------------------------------------------------------------- /examples/constants2.tip: -------------------------------------------------------------------------------- 1 | main(){ 2 | var x,y,z; 3 | x=27; 4 | y=input; 5 | z=2*x+y; 6 | if( 0>x ){ 7 | y=z-3; 8 | } else { 9 | y=12; 10 | } 11 | return y; 12 | } -------------------------------------------------------------------------------- /examples/copyconst.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y,z,a,b,c,t,u; 3 | output 123; 4 | x = f(87); 5 | y = x; 6 | z = x + 1; 7 | t = f(x); 8 | t = g(t); 9 | u = g(123); 10 | u = u; 11 | output z; 12 | if (z) { 13 | a = x; 14 | b = f(y); 15 | c = z; 16 | } else { 17 | a = g(1); 18 | b = 2; 19 | c = 1; 20 | } 21 | output x; 22 | output y; 23 | output z; 24 | output a; 25 | output b; 26 | output c; 27 | output t; 28 | output u; 29 | return x; 30 | } 31 | 32 | f(x) { return 555; } 33 | 34 | g(p) { return p; } 35 | 36 | h() { return 1234; } 37 | -------------------------------------------------------------------------------- /examples/div.tip: -------------------------------------------------------------------------------- 1 | /* 2 | div(a,b){ 3 | return a/b; 4 | } 5 | 6 | 7 | main(){ 8 | var a,b,c; 9 | 10 | a = 17; 11 | b = 17 + (a / 3); 12 | a = 13; 13 | if( a > 17 ){ 14 | a = 5+2; 15 | } else { 16 | b = a/3; 17 | } 18 | 19 | a = *c; 20 | 21 | while( a > 5 ){ 22 | //a = div(a/2,3); 23 | a=117; 24 | } 25 | 26 | return 127; 27 | }*/ 28 | 29 | 30 | main(){ 31 | var a,b; 32 | 33 | a = 17; 34 | b = 17 + (a / 3); 35 | a = 13; 36 | 37 | if( a > 17 ){ 38 | a = 77*b; 39 | } else { 40 | b = a/3; 41 | } 42 | 43 | b=117; 44 | 45 | return 127; 46 | } -------------------------------------------------------------------------------- /examples/equal.tip: -------------------------------------------------------------------------------- 1 | equal(a,b){ 2 | return a==b; 3 | } 4 | -------------------------------------------------------------------------------- /examples/err_assignfunc.tip: -------------------------------------------------------------------------------- 1 | f(){ 2 | return 5; 3 | } 4 | 5 | g(){ 6 | return 5; 7 | } 8 | 9 | main(){ 10 | f=g; 11 | return 0; 12 | } -------------------------------------------------------------------------------- /examples/err_cmpfunc.tip: -------------------------------------------------------------------------------- 1 | 2 | f(){ 3 | return 17; 4 | } 5 | 6 | g(){ 7 | return 17; 8 | } 9 | 10 | main(){ 11 | var n; 12 | if( f>g ){ 13 | n=10; 14 | } 15 | return 0; 16 | } -------------------------------------------------------------------------------- /examples/err_cmpfunc2.tip: -------------------------------------------------------------------------------- 1 | 2 | f(){ 3 | return 17; 4 | } 5 | 6 | g(i){ 7 | return i+1; 8 | } 9 | 10 | main(){ 11 | var n; 12 | if( f==g ){ 13 | n=10; 14 | } 15 | return 0; 16 | } -------------------------------------------------------------------------------- /examples/err_cmpfunc3.tip: -------------------------------------------------------------------------------- 1 | 2 | f(z){ 3 | return (z)(4); 4 | } 5 | 6 | g(j){ 7 | return j+1; 8 | } 9 | 10 | main(){ 11 | var n; 12 | if( f==g ){ 13 | n=10; 14 | } 15 | return 0; 16 | } -------------------------------------------------------------------------------- /examples/err_decl_use.tip: -------------------------------------------------------------------------------- 1 | f(){ 2 | return g(a); 3 | } 4 | 5 | g(a){ 6 | return a+1; 7 | } 8 | 9 | h(){ 10 | a=3; 11 | return 3; 12 | } -------------------------------------------------------------------------------- /examples/err_doubledecl1.tip: -------------------------------------------------------------------------------- 1 | f(z) { 2 | var v,x,q; 3 | var q; 4 | return 0; 5 | } -------------------------------------------------------------------------------- /examples/err_doubledecl2.tip: -------------------------------------------------------------------------------- 1 | f(x) { 2 | var x, z; 3 | return 0; 4 | } -------------------------------------------------------------------------------- /examples/err_funass.tip: -------------------------------------------------------------------------------- 1 | 2 | // This program is rejected by declaration analysis 3 | 4 | main() { 5 | main = 5; 6 | return 0; 7 | } -------------------------------------------------------------------------------- /examples/err_funass2.tip: -------------------------------------------------------------------------------- 1 | // This program should be rejected by the declaration analysis 2 | main() { 3 | var funalias; 4 | // Circumvent variable assignment restriction 5 | funalias = &main; 6 | *funalias = 5; 7 | return 0; 8 | } -------------------------------------------------------------------------------- /examples/err_func.tip: -------------------------------------------------------------------------------- 1 | f(a,b){ 2 | return g(a,b); 3 | } 4 | 5 | 6 | g(a,b){ 7 | return a+b; 8 | } 9 | 10 | main(){ 11 | return f(4,9); 12 | } -------------------------------------------------------------------------------- /examples/err_locals.tip: -------------------------------------------------------------------------------- 1 | f(){ 2 | var a,b,c; 3 | return 10; 4 | } 5 | 6 | f(){ 7 | return 4; 8 | } 9 | -------------------------------------------------------------------------------- /examples/err_notdecl.tip: -------------------------------------------------------------------------------- 1 | test(){ 2 | a = 5; 3 | return a; 4 | } -------------------------------------------------------------------------------- /examples/err_notlocal.tip: -------------------------------------------------------------------------------- 1 | 2 | f(){ 3 | var n; 4 | return 17; 5 | } 6 | 7 | main(){ 8 | n=10; 9 | return 0; 10 | } -------------------------------------------------------------------------------- /examples/err_notlocal2.tip: -------------------------------------------------------------------------------- 1 | 2 | f(){ 3 | return 17; 4 | } 5 | 6 | main(){ 7 | *f=10; 8 | return 0; 9 | } -------------------------------------------------------------------------------- /examples/err_unify1.tip: -------------------------------------------------------------------------------- 1 | // Not typable : int = [[r]]=[[g]]=&\alpha 2 | 3 | bar(g,x) { 4 | var r; 5 | if (x==0){ 6 | r=g; 7 | } else { 8 | r=bar(2,0); 9 | } 10 | return r+1; 11 | } 12 | 13 | main() { 14 | return bar(null,1); 15 | } -------------------------------------------------------------------------------- /examples/err_unify2.tip: -------------------------------------------------------------------------------- 1 | // Not typable : int = [[r]]=[[g]]=&\alpha 2 | 3 | foo(p) { 4 | return *p; 5 | } 6 | 7 | main() { 8 | var x; 9 | x = 5; 10 | x = foo; 11 | return 4; 12 | } -------------------------------------------------------------------------------- /examples/error.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | error 1+2; 3 | return 0; 4 | } -------------------------------------------------------------------------------- /examples/ex1.tip: -------------------------------------------------------------------------------- 1 | f(x) { return x; } 2 | g(y) { return f(y); } 3 | main() { var t; t = f; t = (f)(t); t = (t)(g); return (t)(42); } -------------------------------------------------------------------------------- /examples/ex5.tip: -------------------------------------------------------------------------------- 1 | f(x) { return x; } 2 | g(y) { return f(y); } 3 | main() { 4 | var t; 5 | t = f; 6 | t = (f)(t); 7 | t = (t)(g); 8 | return (t)(42); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /examples/factorial_iterative.tip: -------------------------------------------------------------------------------- 1 | ite(n){ 2 | var f; 3 | f = 1; 4 | while (n>0) { 5 | f = f*n; 6 | n = n-1; 7 | } 8 | return f; 9 | } 10 | 11 | main() { 12 | var n; 13 | n = input; 14 | return ite(n); 15 | } -------------------------------------------------------------------------------- /examples/factorial_recursive.tip: -------------------------------------------------------------------------------- 1 | rec(n) { 2 | var f; 3 | if (n==0) { f=1; } 4 | else { f=n*rec(n-1); } 5 | return f; 6 | } 7 | 8 | main() { 9 | var n; 10 | n = input; 11 | return rec(n); 12 | } -------------------------------------------------------------------------------- /examples/fib.tip: -------------------------------------------------------------------------------- 1 | fib(n){ 2 | var f1,f2,i; 3 | var temp; 4 | f1=1; 5 | f2=1; 6 | 7 | i=n; 8 | while( i>1 ){ 9 | temp = f1+f2; 10 | f1=f2; 11 | f2=temp; 12 | i=i-1; 13 | } 14 | return f2; 15 | } 16 | 17 | main(){ 18 | var n; 19 | n=input; 20 | return fib(n); 21 | } -------------------------------------------------------------------------------- /examples/foo.tip: -------------------------------------------------------------------------------- 1 | foo(p,x) { 2 | var f,q; 3 | if (*p==0) { f=1; } 4 | else { 5 | q = alloc 0; 6 | *q = (*p)-1; 7 | f=(*p)*((x)(q,x)); 8 | } 9 | return f; 10 | } 11 | 12 | main() { 13 | var n; 14 | n = input; 15 | return foo(&n,foo); 16 | } 17 | -------------------------------------------------------------------------------- /examples/func.tip: -------------------------------------------------------------------------------- 1 | f(a,b){ 2 | return g(a,b); 3 | } 4 | 5 | 6 | g(a,b){ 7 | return a+b; 8 | } 9 | 10 | main(){ 11 | var a,b; 12 | 13 | a = input; 14 | b = input; 15 | 16 | return f(a,b); 17 | } -------------------------------------------------------------------------------- /examples/if_short_if.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y,z; 3 | x = input; 4 | y = input; 5 | z = input; 6 | if (x == 0) 7 | if (y == 0) 8 | output z; 9 | else 10 | output x; 11 | return y; 12 | } 13 | -------------------------------------------------------------------------------- /examples/input1.tip: -------------------------------------------------------------------------------- 1 | test(){ 2 | var a; 3 | a = input; 4 | return a; 5 | } -------------------------------------------------------------------------------- /examples/interpreter_test.tip: -------------------------------------------------------------------------------- 1 | f(x) { 2 | return *x; 3 | } 4 | g(x) { 5 | var y; 6 | y = alloc null; 7 | *y = x; 8 | return y; 9 | } 10 | h(x) { 11 | return x; 12 | } 13 | main() { 14 | var ret; 15 | var x, y, z, u, v, k; 16 | 17 | ret = 0; 18 | 19 | // Pointers fun 20 | 21 | x = &z; 22 | y = &z; 23 | z = 5; 24 | if((z - 5) > 0){ 25 | ret = -1; 26 | } 27 | if((*x - 5) > 0) { 28 | ret = -2; 29 | } 30 | if((*y - 5) > 0) { 31 | ret = -3; 32 | } 33 | 34 | if(x == y) { 35 | ret = ret; 36 | } 37 | else { 38 | ret = -4; 39 | } 40 | 41 | // Call by value 42 | if((f(x) - 5) > 0) { 43 | ret = -5; 44 | } 45 | if((h(z) - 5) > 0) { 46 | ret = -6; 47 | } 48 | 49 | if((*(*(g(x))) - 5) > 0) { 50 | ret = -7; 51 | } 52 | 53 | // Functions fun 54 | u = h; 55 | v = h; 56 | k = (v)(5); 57 | if((k - 5) > 0) { 58 | ret = -8; 59 | } 60 | 61 | if(u == v) { 62 | ret = ret; 63 | } 64 | else { 65 | ret = -9; 66 | } 67 | 68 | 69 | return ret; 70 | } -------------------------------------------------------------------------------- /examples/interval0.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y; 3 | y = 0; 4 | x = 7; 5 | x = x + 1; 6 | while (input) { 7 | x = 7; 8 | x = x+1; 9 | y = y+1; 10 | } 11 | return 0; 12 | } -------------------------------------------------------------------------------- /examples/interval1.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y; 3 | y = 0; 4 | while (input) { 5 | x = 7; 6 | x = x+1; 7 | y = y+1; 8 | } 9 | return 0; 10 | } -------------------------------------------------------------------------------- /examples/interval2.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y,z; 3 | 4 | //z=49; 5 | z=1; 6 | 7 | x = 0; 8 | y = 0; 9 | 10 | if(x>0){ 11 | x=x-8; 12 | } else { 13 | x=x+7; 14 | } 15 | 16 | if(y>0){ 17 | y=y-8; 18 | } else { 19 | y=y-7; 20 | } 21 | 22 | z=x*y; 23 | 24 | /*while(input){ 25 | x = x*(0-1); 26 | }*/ 27 | 28 | return 0; 29 | } -------------------------------------------------------------------------------- /examples/interval3.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y; 3 | x = 0; 4 | y = 0; 5 | while (input) { 6 | x = 7; 7 | x = x+1; 8 | y = y+1; 9 | } 10 | return y; 11 | } -------------------------------------------------------------------------------- /examples/liveness.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y,z; 3 | x = input; 4 | while (x>1) { 5 | y = x/2; 6 | if (y>3) { x = x-y; } 7 | z = x-4; 8 | if (z>0) { x = x/2; } 9 | z = z-1; 10 | } 11 | output x; 12 | return 0; 13 | } -------------------------------------------------------------------------------- /examples/locals.tip: -------------------------------------------------------------------------------- 1 | g(a){ 2 | return *a; 3 | } 4 | 5 | f(){ 6 | var a; 7 | var b; 8 | a=10; 9 | if( a == 10 ){ 10 | b=g(&a); 11 | } 12 | 13 | return a; 14 | } -------------------------------------------------------------------------------- /examples/loop.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var a,b,i; 3 | a = 5; 4 | b = 42; 5 | i = a; 6 | while (b > i) { 7 | // ... 8 | i = i + 1; 9 | } 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /examples/malloc.tip: -------------------------------------------------------------------------------- 1 | test(){ 2 | var p; 3 | p = alloc null; 4 | *p = p; 5 | return 0; 6 | } -------------------------------------------------------------------------------- /examples/map.tip: -------------------------------------------------------------------------------- 1 | /* "Mapping" a function over a list-like sequence of heap-allocated cells. Orginating from examples/a1.tip. */ 2 | 3 | map(l,f,z) { 4 | var r; 5 | if (l==null){ 6 | r=z; 7 | } else { 8 | r=(f)(map(*l,f,z)); 9 | } 10 | return r; 11 | } 12 | 13 | foo(i) { 14 | return i+1; 15 | } 16 | 17 | main() { 18 | var h,t,n; 19 | t = null; 20 | n = 42; 21 | while (n>0) { 22 | n = n-1; 23 | h = alloc null; 24 | *h = t; 25 | t = h; 26 | } 27 | return map(h,foo,0); 28 | } 29 | -------------------------------------------------------------------------------- /examples/mccarthy91.tip: -------------------------------------------------------------------------------- 1 | f(x) { 2 | var r; 3 | if (x > 100) { 4 | r = x + -10; 5 | } else { 6 | r = f(f(x + 11)); 7 | } 8 | return r; 9 | } 10 | 11 | main() { 12 | output f(17); 13 | output f(95); 14 | output f(150); 15 | output f(200); 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /examples/mono.tip: -------------------------------------------------------------------------------- 1 | foo(x,y) { 2 | x = 2*y; 3 | return x+1; 4 | } 5 | 6 | main() { 7 | var a,b; 8 | a = input; 9 | b = foo(a,17); 10 | return b; 11 | } -------------------------------------------------------------------------------- /examples/mono2.tip: -------------------------------------------------------------------------------- 1 | foo(n) { 2 | return n; 3 | } 4 | 5 | main() { 6 | var a,b; 7 | a = 2; 8 | b = foo(a); 9 | return b; 10 | } -------------------------------------------------------------------------------- /examples/mpolyrec.tip: -------------------------------------------------------------------------------- 1 | 2 | foo(p){ 3 | return bar(p); 4 | } 5 | 6 | bar(q){ 7 | return foo(q); 8 | } 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/norm_while.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y; 3 | x = 10; 4 | y = &x; 5 | while (*y > 0) { 6 | *y = *y-1; 7 | } 8 | return x; 9 | } -------------------------------------------------------------------------------- /examples/normalised.tip: -------------------------------------------------------------------------------- 1 | f() { 2 | return 0; 3 | } 4 | 5 | g() { 6 | var x; 7 | x = f(); 8 | return x; 9 | } 10 | 11 | main() { 12 | var u; 13 | u = g(); 14 | return u; 15 | 16 | } -------------------------------------------------------------------------------- /examples/nullpointer.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var p,q,r,n; 3 | p = alloc null; 4 | q = &p; 5 | n = null; 6 | *q = n; 7 | *p = r; 8 | return 0; 9 | } -------------------------------------------------------------------------------- /examples/onelevel.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var s1,s2,s3,p,q; 3 | p = &s1; 4 | p = &s2; 5 | q = &s3; 6 | q = p; 7 | return s1; 8 | } 9 | -------------------------------------------------------------------------------- /examples/parametrized_main.tip: -------------------------------------------------------------------------------- 1 | main(x, y) { 2 | return x; 3 | } -------------------------------------------------------------------------------- /examples/parsing.tip: -------------------------------------------------------------------------------- 1 | main(){ 2 | r=(f)(a); 3 | return 1; 4 | } -------------------------------------------------------------------------------- /examples/pointers.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x, pa, pb; 3 | x = 5; 4 | pa = &x; 5 | pb = pa; 6 | *pb = 2; 7 | return x == 2; 8 | } -------------------------------------------------------------------------------- /examples/poly.tip: -------------------------------------------------------------------------------- 1 | poly(p){ 2 | return *p; 3 | } 4 | 5 | poly2(q){ 6 | var n; 7 | //n=alloc null; 8 | n=poly; 9 | return *q; 10 | } -------------------------------------------------------------------------------- /examples/poly2.tip: -------------------------------------------------------------------------------- 1 | poly(p,q){ 2 | return q; 3 | } 4 | -------------------------------------------------------------------------------- /examples/poly3.tip: -------------------------------------------------------------------------------- 1 | poly(p){ 2 | return *p; 3 | } 4 | 5 | poly2(p){ 6 | return *p; 7 | } -------------------------------------------------------------------------------- /examples/poly4.tip: -------------------------------------------------------------------------------- 1 | foo(a) { 2 | return a; 3 | } 4 | 5 | bar() { 6 | var x; 7 | x = foo(17); 8 | return x; 9 | } 10 | 11 | baz() { 12 | var y; 13 | y = foo(18); 14 | return y; 15 | } 16 | 17 | main() { 18 | var a,b; 19 | a = bar(); 20 | b = baz(); 21 | return a+b; 22 | } -------------------------------------------------------------------------------- /examples/ptr1.tip: -------------------------------------------------------------------------------- 1 | test(p){ 2 | var q; 3 | q=3; 4 | return (*p); 5 | } -------------------------------------------------------------------------------- /examples/ptr2.tip: -------------------------------------------------------------------------------- 1 | doubleDeref(r){ 2 | return **r; 3 | } 4 | 5 | main(){ 6 | var n,p,q; 7 | n=17; 8 | p=&n; 9 | q=&p; 10 | return doubleDeref(q); 11 | } -------------------------------------------------------------------------------- /examples/ptr3.tip: -------------------------------------------------------------------------------- 1 | test(f,a){ 2 | return (*f)(a); 3 | } 4 | 5 | test2(g,b){ 6 | return (*g)(b); 7 | } 8 | -------------------------------------------------------------------------------- /examples/ptr4.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var a, x, z, t, y; 3 | 4 | a = &x; 5 | z = &t; 6 | y = &z; 7 | x = *y; 8 | 9 | return 0; 10 | } -------------------------------------------------------------------------------- /examples/ptr5.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var a,b,c,d; 3 | 4 | c = &d; 5 | a = &b; 6 | *a = c; 7 | 8 | return 0; 9 | } -------------------------------------------------------------------------------- /examples/ptr6.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var p,q,x,y,z,d1,d2,l,s,v,w; 3 | p = alloc null; 4 | l = alloc null; 5 | x = y; 6 | x = z; 7 | *p = z; 8 | p = q; 9 | q = &y; 10 | x = *p; 11 | p = &z; 12 | d1 = l; 13 | d2 = l; 14 | s = &d1; 15 | v = &d2; 16 | w = &d2; 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /examples/ptr7.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | 3 | var a1, a2, b1, b2, c1, c2, d1, d2; 4 | 5 | a1 = &b1; 6 | b1 = &c1; 7 | c1 = &d1; 8 | a2 = &b2; 9 | b2 = &c2; 10 | c2 = &d2; 11 | b1 = &c2; 12 | 13 | return 0; 14 | } -------------------------------------------------------------------------------- /examples/reaching.tip: -------------------------------------------------------------------------------- 1 | main(){ 2 | var x,y,z; 3 | x = input; 4 | while (x>1) { 5 | y = x/2; 6 | if (y>3){ 7 | x = x-y; 8 | } 9 | z = x-4; 10 | if (z>0){ 11 | x = x/2; 12 | } 13 | z = z-1; 14 | } 15 | output x; 16 | 17 | return x; 18 | } -------------------------------------------------------------------------------- /examples/rec.tip: -------------------------------------------------------------------------------- 1 | rec(n) { 2 | var f; 3 | if (n==0) { f=1; } 4 | else { f=n*rec(n-1); } 5 | return f; 6 | } 7 | 8 | main() { 9 | var n; 10 | n = input; 11 | return rec(n); 12 | } -------------------------------------------------------------------------------- /examples/record1.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var n, r1; 3 | n = {p: 5, q: 2}; 4 | r1 = n.p; // output 5 5 | return r1; 6 | } -------------------------------------------------------------------------------- /examples/record2.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var n, r1; 3 | n = alloc {p: 4, q: 2}; 4 | *n = {p:5, q: 6}; 5 | r1 = *n.p; // output 5 6 | return r1; 7 | } -------------------------------------------------------------------------------- /examples/record3.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var n, k, r1; 3 | k = {f: 4}; 4 | n = alloc {p: 4, q: 5}; 5 | *n = {p: 44, q: &k}; 6 | return *(*n.q).f; 7 | } -------------------------------------------------------------------------------- /examples/record4.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var n, k, r1; 3 | k = {a: 1, b: 2}; 4 | n = {c: &k, d: 4}; 5 | r1 = (*(n.c).a); // output 1 6 | return r1; 7 | } -------------------------------------------------------------------------------- /examples/record5.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var n, k; 3 | k = {a: 1, b: 2}; 4 | n = alloc {c: k, d: 4}; 5 | return *n.c.b; 6 | } -------------------------------------------------------------------------------- /examples/record6.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var n, k; 3 | k = {a: 1, b: 2}; 4 | n = alloc {c: k, d: 4}; 5 | return *n.c.bbb; // ERROR 6 | } -------------------------------------------------------------------------------- /examples/rectype.tip: -------------------------------------------------------------------------------- 1 | main(){ 2 | var p; 3 | p = alloc null; 4 | *p=p; 5 | return 0; 6 | } -------------------------------------------------------------------------------- /examples/shape.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y,n,p,q; 3 | x = alloc null; y = alloc null; 4 | *x = null; *y = y; 5 | n = input; 6 | while (n>0) { 7 | p = alloc null; q = alloc null; 8 | *p = x; *q = y; 9 | x = p; y = q; 10 | n = n-1; 11 | } 12 | return 0; 13 | } -------------------------------------------------------------------------------- /examples/signs.tip: -------------------------------------------------------------------------------- 1 | fun(x) { 2 | var y; 3 | var k ; 4 | 5 | k = 8; 6 | y = 7; 7 | 8 | while(k > y) { 9 | k = k - 1; 10 | } 11 | 12 | return 0; 13 | } 14 | 15 | main() { 16 | var pos, neg, top, zero; 17 | var later; 18 | pos = 5 + 5; 19 | pos = 5 * 5; 20 | neg = -5 - 5; 21 | neg = -5 * 5; 22 | neg = 5 * -5; 23 | top = 5 - 5; 24 | top = top * 5; 25 | zero = top * 0; 26 | zero = 5 * zero; 27 | later = 7; 28 | return 0; 29 | } -------------------------------------------------------------------------------- /examples/signs_fun.tip: -------------------------------------------------------------------------------- 1 | idf(a) { 2 | return a; 3 | } 4 | 5 | posf(b) { 6 | return b; 7 | } 8 | 9 | negf(c) { 10 | return c; 11 | } 12 | 13 | main() { 14 | var pos, neg, top, zero; 15 | 16 | zero = 0; 17 | 18 | top = idf(5); 19 | 20 | top = idf(-4); 21 | 22 | pos = posf(5); 23 | 24 | pos = posf(7); 25 | 26 | neg = negf(-4); 27 | 28 | neg = negf(-8); 29 | 30 | return 0; 31 | } -------------------------------------------------------------------------------- /examples/signs_fun_cfa.tip: -------------------------------------------------------------------------------- 1 | posf(b) { 2 | return 5; 3 | } 4 | 5 | negf(c) { 6 | return -5; 7 | } 8 | 9 | main() { 10 | var pos, neg, top, u, f; 11 | 12 | u = 0; 13 | 14 | pos = posf(-4); 15 | 16 | neg = negf(3); 17 | 18 | if(u > 0) { 19 | f = posf; 20 | } 21 | else { 22 | f = negf; 23 | } 24 | 25 | top = (f)(4); 26 | 27 | return 0; 28 | } -------------------------------------------------------------------------------- /examples/simple1.tip: -------------------------------------------------------------------------------- 1 | f(){ 2 | return 1; 3 | } -------------------------------------------------------------------------------- /examples/slicing.tip: -------------------------------------------------------------------------------- 1 | foo(m){ 2 | m = m*2; 3 | return m; 4 | } 5 | 6 | main() { 7 | var x,y,i,j,k; 8 | y = input; 9 | x = foo(y); 10 | i = x; 11 | j = y; 12 | while (i>0) { 13 | y = y*y; 14 | i = i-1; 15 | } 16 | while (j>0) { 17 | x = x*x; 18 | j = j-1; 19 | } 20 | if (x>0) { 21 | x = 0-x; 22 | } else { 23 | x = x+1; 24 | } 25 | return x; 26 | } -------------------------------------------------------------------------------- /examples/steensgaard1.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var a,b,c,p,x,y,z; 3 | a = &x; 4 | b = &y; 5 | if (p) { 6 | y = &z; 7 | } else { 8 | y = &x; 9 | } 10 | c = &y; 11 | return p; 12 | } -------------------------------------------------------------------------------- /examples/steensgaard2.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var a,b,c,d,e,f,g; 3 | *c = f; 4 | b = alloc null; 5 | c = &a; 6 | e = *d; 7 | a = b; 8 | c = &b; 9 | d = &c; 10 | f = &g; 11 | return 0; 12 | } -------------------------------------------------------------------------------- /examples/steensgaard3.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var a, b, x, y, z, c; 3 | a = &x; 4 | //b = &y; 5 | y = &z; 6 | y = &x; 7 | //c = &y; 8 | return 0; 9 | } -------------------------------------------------------------------------------- /examples/succ.tip: -------------------------------------------------------------------------------- 1 | succ(i){ 2 | return i+1; 3 | } -------------------------------------------------------------------------------- /examples/symbolic1.tip: -------------------------------------------------------------------------------- 1 | double(v) { 2 | return 2*v; 3 | } 4 | 5 | testme(x, y) { 6 | var z, res; 7 | res = 0; 8 | z = double(y); 9 | if (z==x) { 10 | if (x > y + 10) { 11 | error 42; 12 | } 13 | error 41; 14 | } 15 | return res; 16 | } 17 | 18 | main() { 19 | var ix, iy; 20 | ix = input; 21 | iy = input; 22 | return testme(ix, iy); 23 | } 24 | -------------------------------------------------------------------------------- /examples/symbolic2.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x, y; 3 | x = input; 4 | x = x + 5; 5 | if (x > 0) { 6 | y = input; 7 | } else { 8 | y = 10; 9 | } 10 | 11 | if (x > 2) 12 | if (y == 2789) 13 | error 1; 14 | return 0; 15 | } -------------------------------------------------------------------------------- /examples/t1.tip: -------------------------------------------------------------------------------- 1 | 2 | foo(p){ 3 | return bar(foo); 4 | } 5 | 6 | bar(q){ 7 | return foo(bar); 8 | } 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/t2.tip: -------------------------------------------------------------------------------- 1 | 2 | foo(p){ 3 | var f; 4 | f=foo; 5 | return bar(f); 6 | } 7 | 8 | bar(q){ 9 | var g; 10 | g=bar; 11 | return foo(g); 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/testdiv.tip: -------------------------------------------------------------------------------- 1 | main(){ 2 | var x,y; 3 | x = input; 4 | y = input; 5 | return x/y; 6 | } -------------------------------------------------------------------------------- /examples/verybusy.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,a,b; 3 | x = input; 4 | a = x-1; 5 | b = x-2; 6 | while (x>0) { 7 | output a*b-x; 8 | x = x-1; 9 | } 10 | return a*b; 11 | } -------------------------------------------------------------------------------- /examples/while_short_if.tip: -------------------------------------------------------------------------------- 1 | main() { 2 | var x,y,z; 3 | x = input; 4 | y = input; 5 | z = input; 6 | if (x == 1) { 7 | while (x > 0) { 8 | if (y == 0) { 9 | output z; 10 | } 11 | else { 12 | x = input; 13 | } 14 | } 15 | } 16 | return y; 17 | } 18 | -------------------------------------------------------------------------------- /ideafiles/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /ideafiles/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /ideafiles/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | 3 | addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1") -------------------------------------------------------------------------------- /src/tip/analysis/Analysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast.{ADeclaration, AstNode} 4 | 5 | /** 6 | * Trait for program analyses. 7 | * 8 | * @tparam R the type of the analysis result 9 | **/ 10 | trait Analysis[+R] { 11 | 12 | /** 13 | * Performs the analysis and returns the result. 14 | */ 15 | def analyze(): R 16 | } 17 | 18 | /** 19 | * Trait for (may-)points-to analyses. 20 | * Can answer may-points-to and may-alias queries. 21 | */ 22 | trait PointsToAnalysis extends Analysis[Unit] { 23 | 24 | /** 25 | * Builds the points-to map. 26 | * For each identifier, the points-to map gives the set of cells the identifier may point to. 27 | */ 28 | def pointsTo(): Map[ADeclaration, Set[AstNode]] 29 | 30 | /** 31 | * Returns a function that tells whether two given identifiers may point to the same cell. 32 | * @return a function that returns true if the identifiers may point to the same cell; false if they definitely do not point to the same cell 33 | */ 34 | def mayAlias(): (ADeclaration, ADeclaration) => Boolean 35 | } 36 | -------------------------------------------------------------------------------- /src/tip/analysis/AndersenAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast.{ADeclaration, DepthFirstAstVisitor, _} 4 | import tip.solvers._ 5 | import tip.util.Log 6 | import tip.ast.AstNodeData.DeclarationData 7 | import scala.language.implicitConversions 8 | 9 | class AndersenAnalysis(program: AProgram)(implicit declData: DeclarationData) extends DepthFirstAstVisitor[Unit] with PointsToAnalysis { 10 | 11 | val log = Log.logger[this.type]() 12 | 13 | sealed trait Cell 14 | case class Alloc(alloc: AAlloc) extends Cell { 15 | override def toString = s"alloc-${alloc.loc}" 16 | } 17 | case class Var(id: ADeclaration) extends Cell { 18 | override def toString = id.toString 19 | } 20 | 21 | val solver = new SimpleCubicSolver[Cell, Cell] 22 | 23 | import AstOps._ 24 | val cells: Set[Cell] = (program.appearingIds.map(Var): Set[Cell]) union program.appearingAllocs.map(Alloc) 25 | 26 | NormalizedForPointsToAnalysis.assertContainsProgram(program) 27 | NoRecords.assertContainsProgram(program) 28 | 29 | /** 30 | * @inheritdoc 31 | */ 32 | def analyze(): Unit = 33 | visit(program, ()) 34 | 35 | /** 36 | * Generates the constraints for the given sub-AST. 37 | * @param node the node for which it generates the constraints 38 | * @param arg unused for this visitor 39 | */ 40 | def visit(node: AstNode, arg: Unit): Unit = { 41 | 42 | implicit def identifierToTarget(id: AIdentifier): Var = Var(id) 43 | implicit def allocToTarget(alloc: AAlloc): Alloc = Alloc(alloc) 44 | 45 | node match { 46 | case AAssignStmt(id: AIdentifier, alloc: AAlloc, _) => ??? //<--- Complete here 47 | case AAssignStmt(id1: AIdentifier, AVarRef(id2: AIdentifier, _), _) => ??? //<--- Complete here 48 | case AAssignStmt(id1: AIdentifier, id2: AIdentifier, _) => ??? //<--- Complete here 49 | case AAssignStmt(id1: AIdentifier, AUnaryOp(DerefOp, id2: AIdentifier, _), _) => ??? //<--- Complete here 50 | case AAssignStmt(ADerefWrite(id1: AIdentifier, _), id2: AIdentifier, _) => ??? //<--- Complete here 51 | case _ => 52 | } 53 | visitChildren(node, ()) 54 | } 55 | 56 | /** 57 | * @inheritdoc 58 | */ 59 | def pointsTo(): Map[ADeclaration, Set[AstNode]] = { 60 | val pointsTo = solver.getSolution.collect { 61 | case (v: Var, ts: Set[Cell]) => 62 | v.id -> ts.map { 63 | case Var(x) => x 64 | case Alloc(m) => m 65 | } 66 | } 67 | log.info(s"Points-to:\n${pointsTo.mapValues(v => s"{${v.mkString(",")}}").mkString("\n")}") 68 | pointsTo 69 | } 70 | 71 | /** 72 | * @inheritdoc 73 | */ 74 | def mayAlias(): (ADeclaration, ADeclaration) => Boolean = { (x: ADeclaration, y: ADeclaration) => 75 | solver.getSolution(Var(x)).intersect(solver.getSolution(Var(y))).nonEmpty 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/tip/analysis/AvailableExpAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast._ 4 | import tip.cfg._ 5 | import tip.lattices.{MapLattice, ReversePowersetLattice} 6 | import tip.solvers.{SimpleMapLatticeFixpointSolver, SimpleWorklistFixpointSolver} 7 | import tip.ast.AstNodeData.DeclarationData 8 | 9 | import scala.collection.immutable.Set 10 | 11 | /** 12 | * Base class for available expressions analysis. 13 | */ 14 | abstract class AvailableExpAnalysis(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) extends FlowSensitiveAnalysis(true) { 15 | 16 | import tip.cfg.CfgOps._ 17 | import tip.ast.AstOps._ 18 | 19 | val allExps: Set[UnlabelledNode[AExpr]] = cfg.nodes.flatMap(_.appearingNonInputExpressions.map(UnlabelledNode[AExpr])) 20 | 21 | val lattice: MapLattice[CfgNode, ReversePowersetLattice[UnlabelledNode[AExpr]]] = new MapLattice(new ReversePowersetLattice(allExps)) 22 | 23 | val domain: Set[CfgNode] = cfg.nodes 24 | 25 | NoPointers.assertContainsProgram(cfg.prog) 26 | NoRecords.assertContainsProgram(cfg.prog) 27 | 28 | def transfer(n: CfgNode, s: lattice.sublattice.Element): lattice.sublattice.Element = 29 | n match { 30 | case _: CfgFunEntryNode => Set() 31 | case r: CfgStmtNode => 32 | r.data match { 33 | case as: AAssignStmt => 34 | as.left match { 35 | case id: AIdentifier => 36 | (s union as.right.appearingNonInputExpressions.map(UnlabelledNode[AExpr])).filter { e => 37 | !(id.appearingIds subsetOf e.n.appearingIds) 38 | } 39 | case _ => ??? 40 | } 41 | case exp: AExpr => 42 | s union exp.appearingNonInputExpressions.map(UnlabelledNode[AExpr]) 43 | case out: AOutputStmt => 44 | s union out.exp.appearingNonInputExpressions.map(UnlabelledNode[AExpr]) 45 | case ret: AReturnStmt => 46 | s union ret.exp.appearingNonInputExpressions.map(UnlabelledNode[AExpr]) 47 | case _ => s 48 | } 49 | case _ => s 50 | } 51 | } 52 | 53 | /** 54 | * Available expressions analysis that uses the simple fipoint solver. 55 | */ 56 | class AvailableExpAnalysisSimpleSolver(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) 57 | extends AvailableExpAnalysis(cfg) 58 | with SimpleMapLatticeFixpointSolver[CfgNode] 59 | with ForwardDependencies 60 | 61 | /** 62 | * Available expressions analysis that uses the worklist solver. 63 | */ 64 | class AvailableExpAnalysisWorklistSolver(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) 65 | extends AvailableExpAnalysis(cfg) 66 | with SimpleWorklistFixpointSolver[CfgNode] 67 | with ForwardDependencies 68 | -------------------------------------------------------------------------------- /src/tip/analysis/CallContext.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.cfg.{CfgCallNode, CfgFunEntryNode} 4 | import tip.lattices.Lattice 5 | 6 | /** 7 | * Base trait for call contexts. 8 | */ 9 | trait CallContext 10 | 11 | /** 12 | * Functions for creating call contexts. 13 | * @tparam C the type for call contexts 14 | */ 15 | trait CallContextFunctions[C <: CallContext] { 16 | 17 | /** 18 | * The type for the abstract state lattice. 19 | */ 20 | type statelatticetype = Lattice 21 | 22 | val statelattice: statelatticetype 23 | 24 | /** 25 | * Initial context, for the main function. 26 | */ 27 | def initialContext: C 28 | 29 | /** 30 | * Makes a context for the callee at a function call site. 31 | * @param c the caller context 32 | * @param n the current call node 33 | * @param x the callee entry abstract state 34 | * @param f the callee function 35 | * @return the context for the callee 36 | */ 37 | def makeCallContext(c: C, n: CfgCallNode, x: statelattice.Element, f: CfgFunEntryNode): C 38 | } 39 | 40 | /** 41 | * Call context for call strings. 42 | */ 43 | case class CallStringContext(cs: List[CfgCallNode]) extends CallContext { 44 | 45 | /** 46 | * Creates string representation using the source locations of the calls in the call string. 47 | */ 48 | override def toString: String = cs.map { _.data.loc } mkString ("[", ",", "]") 49 | } 50 | 51 | /** 52 | * Call context construction for call strings. 53 | */ 54 | trait CallStringFunctions extends CallContextFunctions[CallStringContext] { 55 | 56 | /** 57 | * Default maximum length for call strings: 1. 58 | */ 59 | val maxCallStringLength = 1 60 | 61 | /** 62 | * Creates a context as the empty list. 63 | */ 64 | def initialContext: CallStringContext = CallStringContext(Nil) 65 | 66 | /** 67 | * Creates a context as the singleton list consisting of the call node (and ignoring the other arguments). 68 | */ 69 | def makeCallContext(c: CallStringContext, n: CfgCallNode, x: statelattice.Element, f: CfgFunEntryNode): CallStringContext = 70 | CallStringContext((n :: c.cs).slice(0, maxCallStringLength)) 71 | } 72 | 73 | /** 74 | * Call context for functional approach. 75 | * @param x a lattice element 76 | */ 77 | case class FunctionalContext(x: Any) extends CallContext { // TODO: find some way to make Scala type check that x is indeed of type statelattice.Element 78 | 79 | override def toString: String = x.toString 80 | } 81 | 82 | /** 83 | * Call context construction for functional approach. 84 | */ 85 | trait FunctionalFunctions extends CallContextFunctions[FunctionalContext] { 86 | 87 | /** 88 | * Creates a context as the empty abstract state. 89 | */ 90 | def initialContext = FunctionalContext(statelattice.bottom) 91 | 92 | /** 93 | * Creates a context as the singleton list consisting of the call node (and ignoring the other arguments). 94 | */ 95 | def makeCallContext(c: FunctionalContext, n: CfgCallNode, x: statelattice.Element, f: CfgFunEntryNode): FunctionalContext = 96 | FunctionalContext(x) 97 | } 98 | -------------------------------------------------------------------------------- /src/tip/analysis/ConstantPropagationAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast.AstNodeData.DeclarationData 4 | import tip.cfg.{InterproceduralProgramCfg, IntraproceduralProgramCfg} 5 | import tip.lattices.ConstantPropagationLattice 6 | 7 | object ConstantPropagationAnalysis { 8 | 9 | object Intraprocedural { 10 | 11 | /** 12 | * Intraprocedural analysis that uses the simple fixpoint solver. 13 | */ 14 | class SimpleSolver(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) 15 | extends IntraprocValueAnalysisSimpleSolver(cfg, ConstantPropagationLattice) 16 | 17 | /** 18 | * Intraprocedural analysis that uses the worklist solver. 19 | */ 20 | class WorklistSolver(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) 21 | extends IntraprocValueAnalysisWorklistSolver(cfg, ConstantPropagationLattice) 22 | 23 | /** 24 | * Intraprocedural analysis that uses the worklist solver with reachability. 25 | */ 26 | class WorklistSolverWithReachability(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) 27 | extends IntraprocValueAnalysisWorklistSolverWithReachability(cfg, ConstantPropagationLattice) 28 | 29 | /** 30 | * Intraprocedural analysis that uses the worklist solver with reachability and propagation-style. 31 | */ 32 | class WorklistSolverWithReachabilityAndPropagation(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) 33 | extends IntraprocValueAnalysisWorklistSolverWithReachabilityAndPropagation(cfg, ConstantPropagationLattice) 34 | } 35 | 36 | object Interprocedural { 37 | 38 | /** 39 | * Interprocedural analysis that uses the worklist solver with reachability. 40 | */ 41 | class WorklistSolverWithReachability(cfg: InterproceduralProgramCfg)(implicit declData: DeclarationData) 42 | extends InterprocValueAnalysisWorklistSolverWithReachability(cfg, ConstantPropagationLattice) 43 | 44 | /** 45 | * Interprocedural analysis that uses the worklist solver with reachability and propagation-style. 46 | */ 47 | class WorklistSolverWithReachabilityAndPropagation(cfg: InterproceduralProgramCfg)(implicit declData: DeclarationData) 48 | extends InterprocValueAnalysisWorklistSolverWithReachabilityAndPropagation(cfg, ConstantPropagationLattice) 49 | 50 | /** 51 | * Interprocedural analysis that uses the worklist solver with reachability and propagation-style. 52 | * with call-string context sensitivity. 53 | */ 54 | class CallString(cfg: InterproceduralProgramCfg)(implicit declData: DeclarationData) extends CallStringValueAnalysis(cfg, ConstantPropagationLattice) 55 | 56 | /** 57 | * Interprocedural analysis that uses the worklist solver with reachability and propagation-style. 58 | * with functional-approach context sensitivity. 59 | */ 60 | class Functional(cfg: InterproceduralProgramCfg)(implicit declData: DeclarationData) extends FunctionalValueAnalysis(cfg, ConstantPropagationLattice) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/tip/analysis/ControlFlowAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast.{AAssignStmt, AIdentifier, AProgram, AstNode, DepthFirstAstVisitor, _} 4 | import tip.solvers.SimpleCubicSolver 5 | import tip.util.Log 6 | import tip.ast.AstNodeData.{AstNodeWithDeclaration, DeclarationData} 7 | 8 | import scala.language.implicitConversions 9 | 10 | /** 11 | * Control flow analysis. 12 | */ 13 | class ControlFlowAnalysis(program: AProgram)(implicit declData: DeclarationData) 14 | extends DepthFirstAstVisitor[Unit] 15 | with Analysis[Map[AstNode, Set[AFunDeclaration]]] { 16 | 17 | val log = Log.logger[this.type]() 18 | 19 | case class Decl(fun: AFunDeclaration) { 20 | override def toString = s"${fun.name}:${fun.loc}" 21 | } 22 | 23 | case class AstVariable(n: AstNode) { 24 | override def toString: String = n match { 25 | case fun: AFunDeclaration => s"${fun.name}:${fun.loc}" 26 | case _ => n.toString 27 | } 28 | } 29 | 30 | private val solver = new SimpleCubicSolver[AstVariable, Decl] 31 | 32 | val allFunctions: Set[AFunDeclaration] = program.funs.toSet 33 | 34 | NoPointers.assertContainsProgram(program) 35 | NoRecords.assertContainsProgram(program) 36 | 37 | /** 38 | * @inheritdoc 39 | */ 40 | def analyze(): Map[AstNode, Set[AFunDeclaration]] = { 41 | visit(program, ()) 42 | val sol = solver.getSolution 43 | log.info(s"Solution is:\n${sol.map { case (k, v) => s" \u27E6$k\u27E7 = {${v.mkString(",")}}" }.mkString("\n")}") 44 | sol.map(vardecl => vardecl._1.n -> vardecl._2.map(_.fun)) 45 | } 46 | 47 | /** 48 | * Generates the constraints for the given sub-AST. 49 | * @param node the node for which it generates the constraints 50 | * @param arg unused for this visitor 51 | */ 52 | def visit(node: AstNode, arg: Unit): Unit = { 53 | 54 | /** 55 | * Get the declaration if the supplied AstNode is an identifier, 56 | * which might be a variable declaration or a function declaration. 57 | * It returns the node itself, otherwise. 58 | */ 59 | def decl(n: AstNode): AstNode = n match { 60 | case id: AIdentifier => id.declaration 61 | case _ => n 62 | } 63 | 64 | implicit def toVar(n: AstNode): AstVariable = AstVariable(n) 65 | 66 | node match { 67 | case fun: AFunDeclaration => ??? //<--- Complete here 68 | case AAssignStmt(id: AIdentifier, e, _) => ??? //<--- Complete here 69 | case ACallFuncExpr(targetFun: AIdentifier, args, _) if decl(targetFun).isInstanceOf[AFunDeclaration] => ??? //<--- Complete here (or remove this case) 70 | case ACallFuncExpr(targetFun, args, _) => ??? //<--- Complete here 71 | case _ => 72 | } 73 | visitChildren(node, ()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/tip/analysis/CopyConstantPropagationAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast._ 4 | import tip.ast.AstNodeData.{AstNodeWithDeclaration, DeclarationData} 5 | import tip.cfg._ 6 | import tip.lattices._ 7 | import tip.solvers._ 8 | 9 | import scala.collection.mutable 10 | 11 | /** 12 | * Micro-transfer-functions for copy-constant-propagation analysis. 13 | */ 14 | trait CopyConstantPropagationAnalysisFunctions extends IDEAnalysis[ADeclaration, FlatLattice[Int]] { 15 | 16 | NoPointers.assertContainsProgram(cfg.prog) 17 | NoRecords.assertContainsProgram(cfg.prog) 18 | 19 | implicit val declData: DeclarationData 20 | 21 | val valuelattice = new FlatLattice[Int]() 22 | 23 | val edgelattice: EdgeFunctionLattice[valuelattice.type] = new EdgeFunctionLattice(valuelattice) 24 | 25 | import cfg._ 26 | import edgelattice._ 27 | import edgelattice.valuelattice._ 28 | 29 | def edgesCallToEntry(call: CfgCallNode, entry: CfgFunEntryNode)(d: DL): Map[DL, edgelattice.EdgeFunction] = 30 | entry.data.params.zip(call.invocation.args).foldLeft(Map[DL, edgelattice.EdgeFunction]()) { 31 | case (acc, (id, exp)) => 32 | acc ++ assign(d, id, exp) 33 | } 34 | 35 | def edgesExitToAfterCall(exit: CfgFunExitNode, aftercall: CfgAfterCallNode)(d: DL): Map[DL, edgelattice.EdgeFunction] = 36 | assign(d, aftercall.targetIdentifier.declaration, AstOps.returnId) 37 | 38 | def edgesCallToAfterCall(call: CfgCallNode, aftercall: CfgAfterCallNode)(d: DL): Map[DL, edgelattice.EdgeFunction] = 39 | d match { 40 | case Right(_) => Map(d -> IdEdge()) 41 | case Left(a) => if (a == aftercall.targetIdentifier.declaration) Map() else Map(d -> IdEdge()) 42 | } 43 | 44 | def edgesOther(n: CfgNode)(d: DL): Map[DL, edgelattice.EdgeFunction] = 45 | n match { 46 | case r: CfgStmtNode => 47 | r.data match { 48 | 49 | // var declarations 50 | case varr: AVarStmt => 51 | vars(d, varr.declIds) 52 | 53 | // assignments 54 | case as: AAssignStmt => 55 | as match { 56 | case AAssignStmt(id: AIdentifier, right, _) => 57 | val edges = assign(d, id.declaration, right) 58 | d match { 59 | case Left(a) if id.declaration != a => 60 | edges + (d -> IdEdge()) // not at the variable being written to, so add identity edge 61 | case _ => 62 | edges 63 | } 64 | case AAssignStmt(_, _, _) => NoPointers.LanguageRestrictionViolation(s"$as not allowed", as.loc) 65 | } 66 | 67 | // return statement 68 | case ret: AReturnStmt => assign(d, AstOps.returnId, ret.exp) 69 | 70 | // all other kinds of statements: like no-ops 71 | case _ => Map(d -> IdEdge()) 72 | } 73 | 74 | // the program entry is like a 'var' for the parameters 75 | case funentry: CfgFunEntryNode if programEntry == funentry => 76 | vars(d, funentry.data.params) 77 | 78 | // all other kinds of nodes: like no-ops 79 | case _ => Map(d -> IdEdge()) 80 | } 81 | 82 | /** 83 | * Micro-transfer-functions for assigning an expression to an identifier. 84 | */ 85 | private def assign(d: DL, id: ADeclaration, exp: AExprOrIdentifierDeclaration): Map[DL, edgelattice.EdgeFunction] = { 86 | val edges = mutable.ListBuffer[(DL, EdgeFunction)]() 87 | d match { 88 | case Right(_) => 89 | edges += d -> IdEdge() // identity edge from lambda to lambda 90 | exp match { 91 | case AIdentifier(_, _) | AIdentifierDeclaration(_, _) => // if the expression is a variable, no additional edges from lambda 92 | case num: ANumber => // TODO: could also handle cases like x = 1+2*3 using local constant-only constant folding 93 | edges += Left(id) -> ConstEdge(FlatEl(num.value)) // if the expression is a constant, add constant edge from lambda to the variable being assigned to 94 | case _ => 95 | edges += Left(id) -> ConstEdge(Top) // for other expressions, add top edge from lambda to the variable being assigned to 96 | } 97 | case Left(a) => 98 | exp match { 99 | case aid @ (AIdentifier(_, _) | AIdentifierDeclaration(_, _)) => 100 | val aiddecl = aid match { 101 | case aid: AIdentifier => aid.declaration 102 | case aid: AIdentifierDeclaration => aid 103 | case _ => ??? // unreachable, aid is an AIdentifier or an AIdentifierDeclaration 104 | } 105 | if (aiddecl == a) // at the variable being read from? 106 | edges += Left(id) -> IdEdge() // identity edge to the variable being written to 107 | case _ => // ignore other kinds of expressions 108 | } 109 | } 110 | edges.toMap 111 | } 112 | 113 | /** 114 | * Micro-transfer-functions for variable declarations and parameters of the main function. 115 | */ 116 | private def vars(d: DL, ids: List[AIdentifierDeclaration]): Map[DL, edgelattice.EdgeFunction] = 117 | d match { 118 | case Right(_) => 119 | ids.foldLeft(Map(d -> IdEdge()): Map[DL, EdgeFunction]) { (ps, id) => // identity edge from lambda to lambda 120 | ps + (Left(id) -> ConstEdge(Top)) // top edge from lambda to each variable being declared 121 | } 122 | case Left(a) => 123 | if (ids.contains(a)) 124 | Map() // no edges from the variables being declared 125 | else 126 | Map(d -> IdEdge()) // identity edge from all other variables to themselves 127 | } 128 | } 129 | 130 | /** 131 | * Copy-constant-propagation analysis using IDE solver. 132 | */ 133 | class CopyConstantPropagationIDEAnalysis(cfg: InterproceduralProgramCfg)(implicit val declData: DeclarationData) 134 | extends IDESolver[ADeclaration, FlatLattice[Int]](cfg) 135 | with CopyConstantPropagationAnalysisFunctions 136 | 137 | /** 138 | * Copy-constant-propagation analysis using summary solver. 139 | */ 140 | class CopyConstantPropagationSummaryAnalysis(cfg: InterproceduralProgramCfg)(implicit val declData: DeclarationData) 141 | extends SummarySolver[ADeclaration, FlatLattice[Int]](cfg) 142 | with CopyConstantPropagationAnalysisFunctions 143 | -------------------------------------------------------------------------------- /src/tip/analysis/DeclarationAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast.DepthFirstAstVisitor 4 | import tip.ast._ 5 | import tip.util.TipProgramException 6 | 7 | /** 8 | * Declaration analysis, binds identifiers to their declarations. 9 | * 10 | * @see [[tip.ast.AstNodeData]] 11 | */ 12 | class DeclarationAnalysis(prog: AProgram) extends DepthFirstAstVisitor[Map[String, ADeclaration]] with Analysis[AstNodeData.DeclarationData] { 13 | 14 | private var declResult: AstNodeData.DeclarationData = Map() 15 | 16 | /** 17 | * @inheritdoc 18 | */ 19 | def analyze(): AstNodeData.DeclarationData = { 20 | visit(prog, Map()) 21 | declResult 22 | } 23 | 24 | /** 25 | * Recursively visits the nodes of the AST. 26 | * An environment `env` is provided as argument, mapping each identifier name to the node that declares it. 27 | * Whenever an identifier is visited, `declResult` is extended accordingly. 28 | * 29 | * @param node the node to visit 30 | * @param env the environment associating with each name its declaration in the current scope 31 | */ 32 | def visit(node: AstNode, env: Map[String, ADeclaration]): Unit = 33 | node match { 34 | case block: ABlock => 35 | // Extend the environment with the initial declarations in the block, if present 36 | val ext = block match { 37 | case fblock: AFunBlockStmt => peekDecl(fblock.declarations) 38 | case _: ANestedBlockStmt => Map[String, ADeclaration]() 39 | } 40 | // Extend the env 41 | val extendedEnv = extendEnv(env, ext) 42 | // Visit each statement in the extended environment 43 | block.body.foreach { stmt => 44 | visit(stmt, extendedEnv) 45 | } 46 | case funDec: AFunDeclaration => 47 | // Associate to each parameter itself as definition 48 | val argsMap = funDec.params.foldLeft(Map[String, ADeclaration]()) { (acc, cur: AIdentifierDeclaration) => 49 | extendEnv(acc, cur.name -> cur) 50 | } 51 | // Visit the function body in the extended environment 52 | val extendedEnv = extendEnv(env, argsMap) 53 | visit(funDec.stmts, extendedEnv) 54 | case p: AProgram => 55 | // There can be mutually recursive functions, so pre-bind all the functions to their definitions before visiting each of them 56 | val extended = p.funs.foldLeft(Map[String, ADeclaration]()) { (accEnv, fd: AFunDeclaration) => 57 | extendEnv(accEnv, fd.name -> fd) 58 | } 59 | p.funs.foreach { fd => 60 | visit(fd, extended) 61 | } 62 | case ident @ AIdentifier(name, loc) => 63 | // Associate with each identifier the definition in the environment 64 | try { 65 | declResult += ident -> env(name) 66 | } catch { 67 | case _: Exception => 68 | throw new DeclarationError(s"Identifier ${ident.name} not declared ${loc.toStringLong}") 69 | } 70 | case AAssignStmt(id: AIdentifier, _, loc) => 71 | if (env.contains(id.name)) { 72 | env(id.name) match { 73 | case f: AFunDeclaration => 74 | throw new DeclarationError(s"Function $f cannot appear on the left-hand side of an assignment ${loc.toStringLong}") 75 | case _ => 76 | } 77 | } 78 | visitChildren(node, env) 79 | case AVarRef(id, loc) => 80 | if (env.contains(id.name) && env(id.name).isInstanceOf[AFunDeclaration]) 81 | throw new DeclarationError(s"Cannot take address of function ${env(id.name)} ${loc.toStringLong}") 82 | visitChildren(node, env) 83 | case ARecord(fields, _) => 84 | fields.foldLeft(Set[String]())((s, f) => { 85 | if (s.contains(f.field)) 86 | throw new DeclarationError(s"Duplicate field name ${f.field} ${f.loc.toStringLong}") 87 | s + f.field 88 | }) 89 | visitChildren(node, env) 90 | case _ => 91 | // There is no alteration of the environment, just visit the children in the current environment 92 | visitChildren(node, env) 93 | } 94 | 95 | /** 96 | * Extend the environment `env` with the bindings in `ext`, checking that no re-definitions occur. 97 | * @param env the environment to extend 98 | * @param ext the bindings to add 99 | * @return the extended environment if no conflict occurs, throws a DeclarationError otherwise 100 | */ 101 | def extendEnv(env: Map[String, ADeclaration], ext: Map[String, ADeclaration]): Map[String, ADeclaration] = 102 | ext.foldLeft(env)((e, p) => extendEnv(e, p)) 103 | 104 | /** 105 | * Extend the environment `env` with the binding `pair`, checking that no re-definition occurs. 106 | * @param env the environment to extend 107 | * @param pair the binding to add 108 | * @return the extended environment if no conflict occurs, throws a DeclarationError otherwise 109 | */ 110 | def extendEnv(env: Map[String, ADeclaration], pair: (String, ADeclaration)): Map[String, ADeclaration] = { 111 | if (env.contains(pair._1)) 112 | throw new DeclarationError(s"Redefinition of identifier ${pair._1} ${pair._2.loc.toStringLong}") 113 | env + pair 114 | } 115 | 116 | /** 117 | * Returns a map containing the new declarations contained in the given sequence of variable declaration statements. 118 | * If a variable is re-defined, a DeclarationError is thrown. 119 | * @param decls the sequence of variable declaration statements 120 | * @return a map associating with each name the node that declares it 121 | */ 122 | private def peekDecl(decls: Seq[AVarStmt]): Map[String, ADeclaration] = { 123 | val allDecls = decls.flatMap(v => v.declIds.map(id => id.name -> id)) 124 | allDecls.foldLeft(Map[String, ADeclaration]()) { (map, pair) => 125 | extendEnv(map, pair) 126 | } 127 | } 128 | } 129 | 130 | class DeclarationError(message: String) extends TipProgramException(s"Symbol error: $message") 131 | -------------------------------------------------------------------------------- /src/tip/analysis/Dependencies.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.cfg._ 4 | 5 | import scala.collection.immutable.Set 6 | 7 | /** 8 | * Dependency methods for worklist-based analyses. 9 | */ 10 | trait Dependencies[N] { 11 | 12 | /** 13 | * Outgoing dependencies. Used when propagating dataflow to successors. 14 | * @param n an element from the worklist 15 | * @return the elements that depend on the given element 16 | */ 17 | def outdep(n: N): Set[N] 18 | 19 | /** 20 | * Incoming dependencies. Used when computing the join from predecessors. 21 | * @param n an element from the worklist 22 | * @return the elements that the given element depends on 23 | */ 24 | def indep(n: N): Set[N] 25 | } 26 | 27 | /** 28 | * Dependency methods for forward analyses. 29 | */ 30 | trait ForwardDependencies extends Dependencies[CfgNode] { 31 | 32 | def outdep(n: CfgNode): Set[CfgNode] = n.succ.toSet 33 | 34 | def indep(n: CfgNode): Set[CfgNode] = n.pred.toSet 35 | } 36 | 37 | /** 38 | * Dependency methods for backward analyses. 39 | */ 40 | trait BackwardDependencies extends Dependencies[CfgNode] { 41 | 42 | def outdep(n: CfgNode): Set[CfgNode] = n.pred.toSet 43 | 44 | def indep(n: CfgNode): Set[CfgNode] = n.succ.toSet 45 | } 46 | 47 | /** 48 | * Variant of [[ForwardDependencies]] for interprocedural analysis. 49 | */ 50 | trait InterproceduralForwardDependencies extends Dependencies[CfgNode] { 51 | 52 | val cfg: InterproceduralProgramCfg 53 | 54 | import cfg._ 55 | 56 | /** 57 | * Like [[ForwardDependencies.outdep]] but with call and return edges. 58 | * A call node has an outdep to its after-call node. 59 | */ 60 | override def outdep(n: CfgNode): Set[CfgNode] = { 61 | val interDep = n match { 62 | case call: CfgCallNode => call.callees 63 | case exit: CfgFunExitNode => exit.callersAfterCall 64 | case _ => Set() 65 | } 66 | interDep ++ n.succ.toSet 67 | } 68 | 69 | /** 70 | * Like [[ForwardDependencies.indep]] but returning an empty set for after-call nodes. 71 | */ 72 | override def indep(n: CfgNode): Set[CfgNode] = 73 | n match { 74 | case _: CfgAfterCallNode => Set() 75 | case _ => n.pred.toSet 76 | } 77 | } 78 | 79 | /** 80 | * Variant of [[ForwardDependencies]] for context-sensitive interprocedural analysis. 81 | */ 82 | trait ContextSensitiveForwardDependencies[C <: CallContext] extends Dependencies[(C, CfgNode)] { 83 | 84 | val cfg: InterproceduralProgramCfg 85 | 86 | /** 87 | * Like [[InterproceduralForwardDependencies.outdep]] but returning an empty set for call nodes and function exit nodes, 88 | * and using the same context as the given pair. 89 | */ 90 | override def outdep(n: (C, CfgNode)): Set[(C, CfgNode)] = 91 | (n._2 match { 92 | case _: CfgCallNode => Set() 93 | case _ => n._2.succ.toSet 94 | }).map { d => 95 | (n._1, d) 96 | } 97 | 98 | /** 99 | * (Not implemented as it is not used by any existing analysis.) 100 | */ 101 | override def indep(n: (C, CfgNode)): Set[(C, CfgNode)] = 102 | ??? 103 | } 104 | -------------------------------------------------------------------------------- /src/tip/analysis/IntervalAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.cfg._ 4 | import tip.ast.AstNodeData.DeclarationData 5 | import tip.lattices.IntervalLattice._ 6 | import tip.lattices._ 7 | import tip.solvers._ 8 | 9 | trait IntervalAnalysisWidening extends ValueAnalysisMisc with Dependencies[CfgNode] { 10 | 11 | import tip.cfg.CfgOps._ 12 | 13 | val cfg: ProgramCfg 14 | 15 | val valuelattice: IntervalLattice.type 16 | 17 | val liftedstatelattice: LiftLattice[statelattice.type] 18 | 19 | /** 20 | * Int values occurring in the program, plus -infinity and +infinity. 21 | */ 22 | private val B = cfg.nodes.flatMap { n => 23 | n.appearingConstants.map { x => 24 | IntNum(x.value): Num 25 | } + MInf + PInf 26 | } 27 | 28 | def loophead(n: CfgNode): Boolean = indep(n).exists(cfg.rank(_) > cfg.rank(n)) 29 | 30 | private def minB(b: IntervalLattice.Num) = B.filter(b <= _).min 31 | 32 | private def maxB(a: IntervalLattice.Num) = B.filter(_ <= a).max 33 | 34 | def widenInterval(x: valuelattice.Element, y: valuelattice.Element): valuelattice.Element = 35 | (x, y) match { 36 | case (IntervalLattice.EmptyInterval, _) => y 37 | case (_, IntervalLattice.EmptyInterval) => x 38 | case ((l1, h1), (l2, h2)) => ??? //<--- Complete here 39 | } 40 | 41 | def widen(x: liftedstatelattice.Element, y: liftedstatelattice.Element): liftedstatelattice.Element = 42 | (x, y) match { 43 | case (liftedstatelattice.Bottom, _) => y 44 | case (_, liftedstatelattice.Bottom) => x 45 | case (liftedstatelattice.Lift(xm), liftedstatelattice.Lift(ym)) => 46 | liftedstatelattice.Lift(declaredVars.map { v => 47 | v -> widenInterval(xm(v), ym(v)) 48 | }.toMap) 49 | } 50 | } 51 | 52 | object IntervalAnalysis { 53 | 54 | object Intraprocedural { 55 | 56 | /** 57 | * Interval analysis, using the worklist solver with init and widening. 58 | */ 59 | class WorklistSolverWithWidening(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) 60 | extends IntraprocValueAnalysisWorklistSolverWithReachability(cfg, IntervalLattice) 61 | with WorklistFixpointSolverWithReachabilityAndWidening[CfgNode] 62 | with IntervalAnalysisWidening 63 | 64 | /** 65 | * Interval analysis, using the worklist solver with init, widening, and narrowing. 66 | */ 67 | class WorklistSolverWithWideningAndNarrowing(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) 68 | extends IntraprocValueAnalysisWorklistSolverWithReachability(cfg, IntervalLattice) 69 | with WorklistFixpointSolverWithReachabilityAndWideningAndNarrowing[CfgNode] 70 | with IntervalAnalysisWidening { 71 | 72 | val narrowingSteps = 5 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/tip/analysis/LiveVarsAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast._ 4 | import tip.lattices._ 5 | import tip.ast.AstNodeData.DeclarationData 6 | import tip.solvers._ 7 | import tip.cfg._ 8 | 9 | import scala.collection.immutable.Set 10 | 11 | /** 12 | * Base class for live variables analysis. 13 | */ 14 | abstract class LiveVarsAnalysis(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) extends FlowSensitiveAnalysis(false) { 15 | 16 | val lattice: MapLattice[CfgNode, PowersetLattice[ADeclaration]] = new MapLattice(new PowersetLattice()) 17 | 18 | val domain: Set[CfgNode] = cfg.nodes 19 | 20 | NoPointers.assertContainsProgram(cfg.prog) 21 | NoRecords.assertContainsProgram(cfg.prog) 22 | 23 | def transfer(n: CfgNode, s: lattice.sublattice.Element): lattice.sublattice.Element = 24 | n match { 25 | case _: CfgFunExitNode => lattice.sublattice.bottom 26 | case r: CfgStmtNode => 27 | r.data match { 28 | case cond: AExpr => ??? //<--- Complete here 29 | case as: AAssignStmt => 30 | as.left match { 31 | case id: AIdentifier => ??? //<--- Complete here 32 | case _ => ??? 33 | } 34 | case varr: AVarStmt => ??? //<--- Complete here 35 | case ret: AReturnStmt => ??? //<--- Complete here 36 | case out: AOutputStmt => ??? //<--- Complete here 37 | case _ => s 38 | } 39 | case _ => s 40 | } 41 | } 42 | 43 | /** 44 | * Live variables analysis that uses the simple fixpoint solver. 45 | */ 46 | class LiveVarsAnalysisSimpleSolver(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) 47 | extends LiveVarsAnalysis(cfg) 48 | with SimpleMapLatticeFixpointSolver[CfgNode] 49 | with BackwardDependencies 50 | 51 | /** 52 | * Live variables analysis that uses the worklist solver. 53 | */ 54 | class LiveVarsAnalysisWorklistSolver(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) 55 | extends LiveVarsAnalysis(cfg) 56 | with SimpleWorklistFixpointSolver[CfgNode] 57 | with BackwardDependencies 58 | -------------------------------------------------------------------------------- /src/tip/analysis/PossiblyUninitializedVarsAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast._ 4 | import tip.ast.AstNodeData.{AstNodeWithDeclaration, DeclarationData} 5 | import tip.cfg._ 6 | import tip.lattices._ 7 | import tip.solvers._ 8 | import tip.ast.AstOps._ 9 | 10 | import scala.collection.mutable 11 | 12 | /** 13 | * Micro-transfer-functions for possibly-uninitialized variables analysis. 14 | */ 15 | trait PossiblyUninitializedVarsAnalysisFunctions extends IDEAnalysis[ADeclaration, TwoElementLattice] { 16 | 17 | NoPointers.assertContainsProgram(cfg.prog) 18 | NoRecords.assertContainsProgram(cfg.prog) 19 | 20 | implicit val declData: DeclarationData 21 | 22 | val valuelattice = new TwoElementLattice() 23 | 24 | val edgelattice = new EdgeFunctionLattice(valuelattice) 25 | 26 | import cfg._ 27 | import edgelattice._ 28 | import edgelattice.valuelattice._ 29 | 30 | def edgesCallToEntry(call: CfgCallNode, entry: CfgFunEntryNode)(d: DL): Map[DL, edgelattice.EdgeFunction] = 31 | entry.data.params.zip(call.invocation.args).foldLeft(Map[DL, edgelattice.EdgeFunction]()) { 32 | case (acc, (id, exp)) => 33 | acc ++ assign(d, id, exp) 34 | } 35 | 36 | def edgesExitToAfterCall(exit: CfgFunExitNode, aftercall: CfgAfterCallNode)(d: DL): Map[DL, edgelattice.EdgeFunction] = 37 | assign(d, aftercall.targetIdentifier.declaration, AstOps.returnId) 38 | 39 | def edgesCallToAfterCall(call: CfgCallNode, aftercall: CfgAfterCallNode)(d: DL): Map[DL, edgelattice.EdgeFunction] = 40 | d match { 41 | case Right(_) => Map(d -> IdEdge()) 42 | case Left(a) => if (a == aftercall.targetIdentifier.declaration) Map() else Map(d -> IdEdge()) 43 | } 44 | 45 | def edgesOther(n: CfgNode)(d: DL): Map[DL, edgelattice.EdgeFunction] = 46 | n match { 47 | case r: CfgStmtNode => 48 | r.data match { 49 | 50 | // var declarations 51 | case varr: AVarStmt => 52 | d match { 53 | case Right(_) => 54 | varr.declIds.foldLeft(Map(d -> IdEdge()): Map[DL, EdgeFunction]) { (ps, id) => // identity edge from lambda to lambda 55 | ps + (Left(id) -> ConstEdge(Top)) // top edge from lambda to each variable being declared 56 | } 57 | case Left(a) => 58 | if (varr.declIds.contains(a)) 59 | Map() // no edges from the variables being declared 60 | else 61 | Map(d -> IdEdge()) // identity edge from all other variables to themselves 62 | } 63 | 64 | // assignments 65 | case as: AAssignStmt => 66 | as match { 67 | case AAssignStmt(id: AIdentifier, right, _) => 68 | val edges = assign(d, id.declaration, right) 69 | d match { 70 | case Left(a) if id.declaration != a => 71 | edges + (d -> IdEdge()) // not at the variable being written to, so add identity edge 72 | case _ => 73 | edges 74 | } 75 | case AAssignStmt(_, _, _) => NoPointers.LanguageRestrictionViolation(s"$as not allowed", as.loc) 76 | } 77 | 78 | // return statement 79 | case ret: AReturnStmt => assign(d, AstOps.returnId, ret.exp) 80 | 81 | // all other kinds of statements: like no-ops 82 | case _ => Map(d -> IdEdge()) 83 | } 84 | // all other kinds of nodes: like no-ops 85 | case _ => Map(d -> IdEdge()) 86 | } 87 | 88 | /** 89 | * Micro-transfer-functions for assigning an expression to an identifier. 90 | */ 91 | private def assign(d: DL, id: ADeclaration, exp: AExprOrIdentifierDeclaration): Map[DL, edgelattice.EdgeFunction] = { 92 | val edges = mutable.ListBuffer[(DL, EdgeFunction)]() 93 | d match { 94 | case Right(_) => 95 | edges += d -> IdEdge() // identity edge from lambda to lambda 96 | case Left(a) => 97 | // identity edge from d to the variable being assigned to if d appears in exp 98 | if (exp.appearingIds.contains(a)) 99 | edges += Left(id) -> IdEdge() 100 | } 101 | edges.toMap 102 | } 103 | } 104 | 105 | /** 106 | * Possibly-uninitialized variables analysis using IDE solver. 107 | */ 108 | class PossiblyUninitializedVarsIDEAnalysis(cfg: InterproceduralProgramCfg)(implicit val declData: DeclarationData) 109 | extends IDESolver[ADeclaration, TwoElementLattice](cfg) 110 | with PossiblyUninitializedVarsAnalysisFunctions 111 | 112 | /** 113 | * Possibly-uninitialized variables analysis using summary solver. 114 | */ 115 | class PossiblyUninitializedVarsSummaryAnalysis(cfg: InterproceduralProgramCfg)(implicit val declData: DeclarationData) 116 | extends SummarySolver[ADeclaration, TwoElementLattice](cfg) 117 | with PossiblyUninitializedVarsAnalysisFunctions 118 | -------------------------------------------------------------------------------- /src/tip/analysis/SignAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast.AstNodeData.DeclarationData 4 | import tip.cfg.{InterproceduralProgramCfg, IntraproceduralProgramCfg} 5 | import tip.lattices.SignLattice 6 | 7 | object SignAnalysis { 8 | 9 | object Intraprocedural { 10 | 11 | /** 12 | * Intraprocedural analysis that uses the simple fixpoint solver. 13 | */ 14 | class SimpleSolver(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) extends IntraprocValueAnalysisSimpleSolver(cfg, SignLattice) 15 | 16 | /** 17 | * Intraprocedural analysis that uses the worklist solver. 18 | */ 19 | class WorklistSolver(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) extends IntraprocValueAnalysisWorklistSolver(cfg, SignLattice) 20 | 21 | /** 22 | * Intraprocedural analysis that uses the worklist solver with reachability. 23 | */ 24 | class WorklistSolverWithReachability(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) 25 | extends IntraprocValueAnalysisWorklistSolverWithReachability(cfg, SignLattice) 26 | 27 | /** 28 | * Intraprocedural analysis that uses the worklist solver with reachability and propagation-style. 29 | */ 30 | class WorklistSolverWithReachabilityAndPropagation(cfg: IntraproceduralProgramCfg)(implicit declData: DeclarationData) 31 | extends IntraprocValueAnalysisWorklistSolverWithReachabilityAndPropagation(cfg, SignLattice) 32 | } 33 | 34 | object Interprocedural { 35 | 36 | /** 37 | * Interprocedural analysis that uses the worklist solver with reachability. 38 | */ 39 | class WorklistSolverWithReachability(cfg: InterproceduralProgramCfg)(implicit declData: DeclarationData) 40 | extends InterprocValueAnalysisWorklistSolverWithReachability(cfg, SignLattice) 41 | 42 | /** 43 | * Interprocedural analysis that uses the worklist solver with reachability and propagation-style. 44 | */ 45 | class WorklistSolverWithReachabilityAndPropagation(cfg: InterproceduralProgramCfg)(implicit declData: DeclarationData) 46 | extends InterprocValueAnalysisWorklistSolverWithReachabilityAndPropagation(cfg, SignLattice) 47 | 48 | /** 49 | * Interprocedural analysis that uses the worklist solver with reachability and propagation-style. 50 | * with call-string context sensitivity. 51 | */ 52 | class CallString(cfg: InterproceduralProgramCfg)(implicit declData: DeclarationData) extends CallStringValueAnalysis(cfg, SignLattice) 53 | 54 | /** 55 | * Interprocedural analysis that uses the worklist solver with reachability and propagation-style. 56 | * with functional-approach context sensitivity. 57 | */ 58 | class Functional(cfg: InterproceduralProgramCfg)(implicit declData: DeclarationData) extends FunctionalValueAnalysis(cfg, SignLattice) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/tip/analysis/SimpleSignAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.cfg.CfgOps._ 4 | import tip.cfg.{CfgNode, CfgStmtNode, ProgramCfg} 5 | import tip.lattices.{MapLattice, SignLattice} 6 | import tip.ast.AstNodeData.DeclarationData 7 | import tip.ast._ 8 | import tip.solvers.FixpointSolvers 9 | 10 | import scala.collection.immutable.Set 11 | 12 | /** 13 | * Simple intra-procedural sign analysis. 14 | * 15 | * This is a specialized version of `SignAnalysis.Intraprocedural.SimpleSolver` 16 | * where most of the involved traits, classes, methods, and fields have been inlined. 17 | */ 18 | class SimpleSignAnalysis(cfg: ProgramCfg)(implicit declData: DeclarationData) extends FlowSensitiveAnalysis(true) { 19 | 20 | /** 21 | * The lattice of abstract values. 22 | */ 23 | val valuelattice = SignLattice 24 | 25 | /** 26 | * Set of declared variables, used by `statelattice`. 27 | */ 28 | val declaredVars: Set[ADeclaration] = cfg.nodes.flatMap(_.declaredVarsAndParams) 29 | 30 | /** 31 | * The lattice of abstract states. 32 | */ 33 | val statelattice: MapLattice[ADeclaration, SignLattice.type] = new MapLattice(valuelattice) 34 | 35 | /** 36 | * The program lattice. 37 | */ 38 | val lattice: MapLattice[CfgNode, statelattice.type] = new MapLattice(statelattice) 39 | 40 | /** 41 | * The domain of the program lattice. 42 | */ 43 | val domain: Set[CfgNode] = cfg.nodes 44 | 45 | /** 46 | * Abstract evaluation of expressions. 47 | */ 48 | def eval(exp: AExpr, env: statelattice.Element)(implicit declData: DeclarationData): valuelattice.Element = { 49 | import valuelattice._ 50 | exp match { 51 | case id: AIdentifier => env(id) 52 | case n: ANumber => num(n.value) 53 | case bin: ABinaryOp => 54 | val left = eval(bin.left, env) 55 | val right = eval(bin.right, env) 56 | bin.operator match { 57 | case Eqq => eqq(left, right) 58 | case GreatThan => gt(left, right) 59 | case Divide => div(left, right) 60 | case Minus => minus(left, right) 61 | case Plus => plus(left, right) 62 | case Times => times(left, right) 63 | case _ => ??? 64 | } 65 | case _: AInput => valuelattice.top 66 | case _ => ??? 67 | } 68 | } 69 | 70 | /** 71 | * Incoming dependencies. Used when computing the join from predecessors. 72 | * @param n an element from the worklist 73 | * @return the elements that the given element depends on 74 | */ 75 | def indep(n: CfgNode): Set[CfgNode] = n.pred.toSet 76 | 77 | /** 78 | * Transfer functions for the different kinds of statements. 79 | */ 80 | def localTransfer(n: CfgNode, s: statelattice.Element): statelattice.Element = { 81 | NoPointers.assertContainsNode(n.data) 82 | NoCalls.assertContainsNode(n.data) 83 | NoRecords.assertContainsNode(n.data) 84 | n match { 85 | case r: CfgStmtNode => 86 | r.data match { 87 | // var declarations 88 | case varr: AVarStmt => ??? //<--- Complete here 89 | 90 | // assignments 91 | case AAssignStmt(id: AIdentifier, right, _) => ??? //<--- Complete here 92 | 93 | // all others: like no-ops 94 | case _ => s 95 | } 96 | case _ => s 97 | } 98 | } 99 | 100 | /** 101 | * The constraint function for individual elements in the map domain. 102 | * First computes the join of the incoming elements and then applies the transfer function. 103 | * @param n the current location in the map domain 104 | * @param x the current lattice element for all locations 105 | * @return the output sublattice element 106 | */ 107 | def funsub(n: CfgNode, x: lattice.Element): lattice.sublattice.Element = 108 | localTransfer(n, join(n, x)) 109 | 110 | /** 111 | * Computes the least upper bound of the incoming elements. 112 | */ 113 | def join(n: CfgNode, o: lattice.Element): lattice.sublattice.Element = { 114 | val states = indep(n).map(o(_)) 115 | states.foldLeft(lattice.sublattice.bottom)((acc, pred) => lattice.sublattice.lub(acc, pred)) 116 | } 117 | 118 | /** 119 | * The function for which the least fixpoint is to be computed. 120 | * Applies the sublattice constraint function pointwise to each entry. 121 | * @param x the input lattice element 122 | * @return the output lattice element 123 | */ 124 | def fun(x: lattice.Element): lattice.Element = { 125 | FixpointSolvers.log.verb(s"In state $x") 126 | domain.foldLeft(lattice.bottom)( 127 | (m, a) => 128 | m + (a -> { 129 | FixpointSolvers.log.verb(s"Processing $a") 130 | funsub(a, x) 131 | }) 132 | ) 133 | } 134 | 135 | /** 136 | * The basic Kleene fixpoint solver. 137 | */ 138 | def analyze(): lattice.Element = { 139 | var x = lattice.bottom 140 | var t = x 141 | do { 142 | t = x 143 | x = fun(x) 144 | } while (x != t) 145 | x 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/tip/analysis/SteensgaardAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast.{ADeclaration, DepthFirstAstVisitor, _} 4 | import tip.solvers._ 5 | import tip.util.Log 6 | import tip.ast.AstNodeData.DeclarationData 7 | import scala.language.implicitConversions 8 | 9 | /** 10 | * Steensgaard-style pointer analysis. 11 | * The analysis associates an [[StTerm]] with each variable declaration and expression node in the AST. 12 | * It is implemented using [[tip.solvers.UnionFindSolver]]. 13 | */ 14 | class SteensgaardAnalysis(program: AProgram)(implicit declData: DeclarationData) extends DepthFirstAstVisitor[Unit] with PointsToAnalysis { 15 | 16 | val log = Log.logger[this.type]() 17 | 18 | val solver = new UnionFindSolver[StTerm] 19 | 20 | NormalizedForPointsToAnalysis.assertContainsProgram(program) 21 | NoRecords.assertContainsProgram(program) 22 | 23 | /** 24 | * @inheritdoc 25 | */ 26 | def analyze(): Unit = 27 | // generate the constraints by traversing the AST and solve them on-the-fly 28 | visit(program, ()) 29 | 30 | /** 31 | * Generates the constraints for the given sub-AST. 32 | * @param node the node for which it generates the constraints 33 | * @param arg unused for this visitor 34 | */ 35 | def visit(node: AstNode, arg: Unit): Unit = { 36 | 37 | implicit def identifierToTerm(id: AIdentifier): Term[StTerm] = IdentifierVariable(id) 38 | implicit def allocToTerm(alloc: AAlloc): Term[StTerm] = AllocVariable(alloc) 39 | 40 | log.verb(s"Visiting ${node.getClass.getSimpleName} at ${node.loc}") 41 | node match { 42 | case AAssignStmt(id1: AIdentifier, alloc: AAlloc, _) => ??? //<--- Complete here 43 | case AAssignStmt(id1: AIdentifier, AVarRef(id2: AIdentifier, _), _) => ??? //<--- Complete here 44 | case AAssignStmt(id1: AIdentifier, id2: AIdentifier, _) => ??? //<--- Complete here 45 | case AAssignStmt(id1: AIdentifier, AUnaryOp(DerefOp, id2: AIdentifier, _), _) => ??? //<--- Complete here 46 | case AAssignStmt(ADerefWrite(id1: AIdentifier, _), id2: AIdentifier, _) => ??? //<--- Complete here 47 | case _ => // ignore other kinds of nodes 48 | } 49 | visitChildren(node, ()) 50 | } 51 | 52 | private def unify(t1: Term[StTerm], t2: Term[StTerm]): Unit = { 53 | log.verb(s"Generating constraint $t1 = $t2") 54 | solver.unify(t1, t2) // note that unification cannot fail, because there is only one kind of term constructor and no constants 55 | } 56 | 57 | /** 58 | * @inheritdoc 59 | */ 60 | def pointsTo(): Map[ADeclaration, Set[AstNode]] = { 61 | val solution = solver.solution() 62 | val unifications = solver.unifications() 63 | log.info(s"Solution: \n${solution.mkString(",\n")}") 64 | log.info(s"Sets: \n${unifications.values.map { s => 65 | s"{ ${s.mkString(",")} }" 66 | }.mkString(", ")}") 67 | 68 | val vars = solution.keys.collect { case id: IdentifierVariable => id } 69 | val pointsto = vars.foldLeft(Map[ADeclaration, Set[AstNode]]()) { 70 | case (a, v: IdentifierVariable) => 71 | val pt = unifications(solution(v)) 72 | .collect({ case PointerRef(IdentifierVariable(id)) => id; case PointerRef(AllocVariable(alloc)) => alloc }) 73 | .toSet 74 | a + (v.id -> pt) 75 | } 76 | log.info(s"Points-to:\n${pointsto.map(p => s"${p._1} -> { ${p._2.mkString(",")} }").mkString("\n")}") 77 | pointsto 78 | } 79 | 80 | /** 81 | * @inheritdoc 82 | */ 83 | def mayAlias(): (ADeclaration, ADeclaration) => Boolean = { 84 | val solution = solver.solution() 85 | (id1: ADeclaration, id2: ADeclaration) => 86 | val sol1 = solution(IdentifierVariable(id1)) 87 | val sol2 = solution(IdentifierVariable(id2)) 88 | sol1 == sol2 && sol1.isInstanceOf[PointerRef] // same equivalence class, and it contains a reference 89 | } 90 | } 91 | 92 | /** 93 | * Counter for producing fresh IDs. 94 | */ 95 | object Fresh { 96 | 97 | var n = 0 98 | 99 | def next(): Int = { 100 | n += 1 101 | n 102 | } 103 | } 104 | 105 | /** 106 | * Terms used in unification. 107 | */ 108 | sealed trait StTerm 109 | 110 | /** 111 | * A term variable that represents an alloc in the program. 112 | */ 113 | case class AllocVariable(alloc: AAlloc) extends StTerm with Var[StTerm] { 114 | 115 | override def toString: String = s"\u27E6alloc{${alloc.loc}}\u27E7" 116 | } 117 | 118 | /** 119 | * A term variable that represents an identifier in the program. 120 | */ 121 | case class IdentifierVariable(id: ADeclaration) extends StTerm with Var[StTerm] { 122 | 123 | override def toString: String = s"\u27E6$id\u27E7" 124 | } 125 | 126 | /** 127 | * A fresh term variable. 128 | */ 129 | case class FreshVariable(var id: Int = 0) extends StTerm with Var[StTerm] { 130 | 131 | id = Fresh.next() 132 | 133 | override def toString: String = s"x$id" 134 | } 135 | 136 | /** 137 | * A constructor term that represents a pointer to another term. 138 | */ 139 | case class PointerRef(of: Term[StTerm]) extends StTerm with Cons[StTerm] { 140 | 141 | val args: List[Term[StTerm]] = List(of) 142 | 143 | def subst(v: Var[StTerm], t: Term[StTerm]): Term[StTerm] = PointerRef(of.subst(v, t)) 144 | 145 | override def toString: String = s"\u2B61$of" 146 | } 147 | -------------------------------------------------------------------------------- /src/tip/analysis/TaintAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast._ 4 | import tip.ast.AstNodeData.{AstNodeWithDeclaration, DeclarationData} 5 | import tip.cfg._ 6 | import tip.lattices._ 7 | import tip.solvers._ 8 | 9 | /** 10 | * Micro-transfer-functions for taint analysis. 11 | */ 12 | trait TaintAnalysisFunctions extends IDEAnalysis[ADeclaration, TwoElementLattice] { 13 | 14 | NoPointers.assertContainsProgram(cfg.prog) 15 | NoRecords.assertContainsProgram(cfg.prog) 16 | 17 | implicit val declData: DeclarationData 18 | 19 | val valuelattice = new TwoElementLattice() 20 | 21 | val edgelattice = new EdgeFunctionLattice(valuelattice) 22 | 23 | import cfg._ 24 | import edgelattice._ 25 | 26 | def edgesCallToEntry(call: CfgCallNode, entry: CfgFunEntryNode)(d: DL): Map[DL, edgelattice.EdgeFunction] = 27 | entry.data.params.zip(call.invocation.args).foldLeft(Map[DL, edgelattice.EdgeFunction]()) { 28 | case (acc, (id, exp)) => 29 | acc ++ assign(d, id, exp) 30 | } 31 | 32 | def edgesExitToAfterCall(exit: CfgFunExitNode, aftercall: CfgAfterCallNode)(d: DL): Map[DL, edgelattice.EdgeFunction] = 33 | assign(d, aftercall.targetIdentifier.declaration, AstOps.returnId) 34 | 35 | def edgesCallToAfterCall(call: CfgCallNode, aftercall: CfgAfterCallNode)(d: DL): Map[DL, edgelattice.EdgeFunction] = 36 | d match { 37 | case Right(_) => Map(d -> IdEdge()) 38 | case Left(a) => if (a == aftercall.targetIdentifier.declaration) Map() else Map(d -> IdEdge()) 39 | } 40 | 41 | def edgesOther(n: CfgNode)(d: DL): Map[DL, edgelattice.EdgeFunction] = 42 | n match { 43 | case r: CfgStmtNode => 44 | r.data match { 45 | 46 | // assignments 47 | case as: AAssignStmt => 48 | as match { 49 | case AAssignStmt(id: AIdentifier, right, _) => 50 | val edges = assign(d, id.declaration, right) 51 | d match { 52 | case Left(a) if id.declaration != a => 53 | edges + (d -> IdEdge()) // not at the variable being written to, so add identity edge 54 | case _ => 55 | edges 56 | } 57 | case AAssignStmt(_, _, _) => NoPointers.LanguageRestrictionViolation(s"$as not allowed", as.loc) 58 | } 59 | 60 | // return statement 61 | case ret: AReturnStmt => assign(d, AstOps.returnId, ret.exp) 62 | 63 | // all other kinds of statements: like no-ops 64 | case _ => Map(d -> IdEdge()) 65 | } 66 | // all other kinds of nodes: like no-ops 67 | case _ => Map(d -> IdEdge()) 68 | } 69 | 70 | /** 71 | * Micro-transfer-functions for assigning an expression to an identifier. 72 | */ 73 | private def assign(d: DL, id: ADeclaration, exp: AExprOrIdentifierDeclaration): Map[DL, edgelattice.EdgeFunction] = ??? //<--- Complete here 74 | } 75 | 76 | /** 77 | * Taint analysis using IDE solver. 78 | */ 79 | class TaintIDEAnalysis(cfg: InterproceduralProgramCfg)(implicit val declData: DeclarationData) 80 | extends IDESolver[ADeclaration, TwoElementLattice](cfg) 81 | with TaintAnalysisFunctions 82 | 83 | /** 84 | * Taint analysis using summary solver. 85 | */ 86 | class TaintSummaryAnalysis(cfg: InterproceduralProgramCfg)(implicit val declData: DeclarationData) 87 | extends SummarySolver[ADeclaration, TwoElementLattice](cfg) 88 | with TaintAnalysisFunctions 89 | -------------------------------------------------------------------------------- /src/tip/analysis/TypeAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.analysis 2 | 3 | import tip.ast._ 4 | import tip.solvers._ 5 | import tip.types._ 6 | import tip.ast.AstNodeData._ 7 | import tip.util.{Log, TipProgramException} 8 | import AstOps._ 9 | 10 | import scala.collection.mutable 11 | 12 | /** 13 | * Unification-based type analysis. 14 | * The analysis associates a [[tip.types.Type]] with each variable declaration and expression node in the AST. 15 | * It is implemented using [[tip.solvers.UnionFindSolver]]. 16 | * 17 | * To novice Scala programmers: 18 | * The parameter `declData` is declared as "implicit", which means that invocations of `TypeAnalysis` obtain its value implicitly: 19 | * The call to `new TypeAnalysis` in Tip.scala does not explicitly provide this parameter, but it is in scope of 20 | * `implicit val declData: TypeData = new DeclarationAnalysis(programNode).analyze()`. 21 | * The TIP implementation uses implicit parameters many places to provide easy access to the declaration information produced 22 | * by `DeclarationAnalysis` and the type information produced by `TypeAnalysis`. 23 | * For more information about implicit parameters in Scala, see [[https://docs.scala-lang.org/tour/implicit-parameters.html]]. 24 | */ 25 | class TypeAnalysis(program: AProgram)(implicit declData: DeclarationData) extends DepthFirstAstVisitor[Unit] with Analysis[TypeData] { 26 | 27 | val log = Log.logger[this.type]() 28 | 29 | val solver = new UnionFindSolver[Type] 30 | 31 | implicit val allFieldNames: List[String] = program.appearingFields.toList.sorted 32 | 33 | /** 34 | * @inheritdoc 35 | */ 36 | def analyze(): TypeData = { 37 | 38 | // generate the constraints by traversing the AST and solve them on-the-fly 39 | try { 40 | visit(program, ()) 41 | } catch { 42 | case e: UnificationFailure => 43 | throw new TipProgramException(s"Type error: ${e.getMessage}") 44 | } 45 | 46 | // check for accesses to absent record fields 47 | new DepthFirstAstVisitor[Unit] { 48 | visit(program, ()) 49 | 50 | override def visit(node: AstNode, arg: Unit): Unit = { 51 | node match { 52 | case ac: AFieldAccess => 53 | if (solver.find(node).isInstanceOf[AbsentFieldType.type]) 54 | throw new TipProgramException(s"Type error: Reading from absent field ${ac.field} ${ac.loc.toStringLong}") 55 | case as: AAssignStmt => 56 | as.left match { 57 | case dfw: ADirectFieldWrite => 58 | if (solver.find(as.right).isInstanceOf[AbsentFieldType.type]) 59 | throw new TipProgramException(s"Type error: Writing to absent field ${dfw.field} ${dfw.loc.toStringLong}") 60 | case ifw: AIndirectFieldWrite => 61 | if (solver.find(as.right).isInstanceOf[AbsentFieldType.type]) 62 | throw new TipProgramException(s"Type error: Writing to absent field ${ifw.field} ${ifw.loc.toStringLong}") 63 | case _ => 64 | } 65 | case _ => 66 | } 67 | visitChildren(node, ()) 68 | } 69 | } 70 | 71 | var ret: TypeData = Map() 72 | 73 | // close the terms and create the TypeData 74 | new DepthFirstAstVisitor[Unit] { 75 | val sol: Map[Var[Type], Term[Type]] = solver.solution() 76 | log.info(s"Solution (not yet closed):\n${sol.map { case (k, v) => s" \u27E6$k\u27E7 = $v" }.mkString("\n")}") 77 | val freshvars: mutable.Map[Var[Type], Var[Type]] = mutable.Map() 78 | visit(program, ()) 79 | 80 | // extract the type for each identifier declaration and each non-identifier expression 81 | override def visit(node: AstNode, arg: Unit): Unit = { 82 | node match { 83 | case _: AIdentifier => 84 | case _: ADeclaration | _: AExpr => 85 | ret += node -> Some(TipTypeOps.close(VarType(node), sol, freshvars).asInstanceOf[Type]) 86 | case _ => 87 | } 88 | visitChildren(node, ()) 89 | } 90 | } 91 | 92 | log.info(s"Inferred types:\n${ret.map { case (k, v) => s" \u27E6$k\u27E7 = ${v.get}" }.mkString("\n")}") 93 | ret 94 | } 95 | 96 | /** 97 | * Generates the constraints for the given sub-AST. 98 | * @param node the node for which it generates the constraints 99 | * @param arg unused for this visitor 100 | */ 101 | def visit(node: AstNode, arg: Unit): Unit = { 102 | log.verb(s"Visiting ${node.getClass.getSimpleName} at ${node.loc}") 103 | node match { 104 | case program: AProgram => ??? // <--- Complete here 105 | case _: ANumber => ??? // <--- Complete here 106 | case _: AInput => ??? // <--- Complete here 107 | case is: AIfStmt => ??? // <--- Complete here 108 | case os: AOutputStmt => ??? // <--- Complete here 109 | case ws: AWhileStmt => ??? // <--- Complete here 110 | case as: AAssignStmt => 111 | as.left match { 112 | case id: AIdentifier => ??? // <--- Complete here 113 | case dw: ADerefWrite => ??? // <--- Complete here 114 | case dfw: ADirectFieldWrite => ??? // <--- Complete here 115 | case ifw: AIndirectFieldWrite => ??? // <--- Complete here 116 | } 117 | case bin: ABinaryOp => 118 | bin.operator match { 119 | case Eqq => ??? // <--- Complete here 120 | case _ => ??? // <--- Complete here 121 | } 122 | case un: AUnaryOp => 123 | un.operator match { 124 | case DerefOp => ??? // <--- Complete here 125 | } 126 | case alloc: AAlloc => ??? // <--- Complete here 127 | case ref: AVarRef => ??? // <--- Complete here 128 | case _: ANull => ??? // <--- Complete here 129 | case fun: AFunDeclaration => ??? // <--- Complete here 130 | case call: ACallFuncExpr => ??? // <--- Complete here 131 | case _: AReturnStmt => 132 | case rec: ARecord => 133 | val fieldmap = rec.fields.foldLeft(Map[String, Term[Type]]()) { (a, b) => 134 | a + (b.field -> b.exp) 135 | } 136 | unify(rec, RecordType(allFieldNames.map { f => 137 | fieldmap.getOrElse(f, AbsentFieldType) 138 | })) 139 | case ac: AFieldAccess => 140 | unify(ac.record, RecordType(allFieldNames.map { f => 141 | if (f == ac.field) VarType(ac) else FreshVarType() 142 | })) 143 | case _ => 144 | } 145 | visitChildren(node, ()) 146 | } 147 | 148 | private def unify(t1: Term[Type], t2: Term[Type]): Unit = { 149 | log.verb(s"Generating constraint $t1 = $t2") 150 | solver.unify(t1, t2) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/tip/ast/Ast.scala: -------------------------------------------------------------------------------- 1 | package tip.ast 2 | import tip.ast.AstPrinters._ 3 | import tip.util.TipProgramException 4 | 5 | /** 6 | * Source code location. 7 | */ 8 | case class Loc(line: Int, col: Int) { 9 | override def toString: String = s"$line:$col" 10 | 11 | def toStringLong: String = s"(line $line, column $col)" 12 | } 13 | 14 | sealed trait Operator 15 | sealed trait BinaryOperator 16 | sealed trait UnaryOperator 17 | 18 | case object Plus extends Operator with BinaryOperator { 19 | override def toString: String = "+" 20 | } 21 | 22 | case object Minus extends Operator with BinaryOperator { 23 | override def toString: String = "-" 24 | } 25 | 26 | case object Times extends Operator with BinaryOperator { 27 | override def toString: String = "*" 28 | } 29 | 30 | case object Divide extends Operator with BinaryOperator { 31 | override def toString: String = "/" 32 | } 33 | 34 | case object Eqq extends Operator with BinaryOperator { 35 | override def toString: String = "==" 36 | } 37 | 38 | case object GreatThan extends Operator with BinaryOperator { 39 | override def toString: String = ">" 40 | } 41 | 42 | case object DerefOp extends Operator with UnaryOperator { 43 | override def toString: String = "*" 44 | } 45 | 46 | /** 47 | * AST node. 48 | * 49 | * (The class extends `Product` to enable functionality used by [[AstOps.UnlabelledNode]].) 50 | */ 51 | sealed abstract class AstNode extends Product { 52 | 53 | /** 54 | * Source code location. 55 | */ 56 | val loc: Loc 57 | 58 | override def toString: String = 59 | s"${this.print(PartialFunction.empty)}[$loc]" 60 | } 61 | 62 | //////////////// Expressions ////////////////////////// 63 | 64 | sealed trait AExprOrIdentifierDeclaration extends AstNode 65 | 66 | sealed trait AExpr extends AExprOrIdentifierDeclaration 67 | 68 | sealed trait AAtomicExpr extends AExpr 69 | 70 | sealed trait ADeclaration extends AstNode 71 | 72 | case class ACallFuncExpr(targetFun: AExpr, args: List[AExpr], loc: Loc) extends AExpr 73 | 74 | case class AIdentifierDeclaration(name: String, loc: Loc) extends ADeclaration with AExprOrIdentifierDeclaration 75 | 76 | case class AIdentifier(name: String, loc: Loc) extends AExpr with AAtomicExpr with ReferenceAssignable 77 | 78 | case class ABinaryOp(operator: BinaryOperator, left: AExpr, right: AExpr, loc: Loc) extends AExpr 79 | 80 | case class AUnaryOp(operator: UnaryOperator, subexp: AExpr, loc: Loc) extends AExpr 81 | 82 | case class ANumber(value: Int, loc: Loc) extends AExpr with AAtomicExpr 83 | 84 | case class AInput(loc: Loc) extends AExpr with AAtomicExpr 85 | 86 | case class AAlloc(exp: AExpr, loc: Loc) extends AExpr with AAtomicExpr 87 | 88 | case class AVarRef(id: AIdentifier, loc: Loc) extends AExpr with AAtomicExpr 89 | 90 | case class ANull(loc: Loc) extends AExpr with AAtomicExpr 91 | 92 | case class ARecord(fields: List[ARecordField], loc: Loc) extends AExpr 93 | 94 | case class ARecordField(field: String, exp: AExpr, loc: Loc) 95 | 96 | case class AFieldAccess(record: AExpr, field: String, loc: Loc) extends AExpr with AAtomicExpr 97 | 98 | //////////////// Statements ////////////////////////// 99 | 100 | sealed trait AStmt extends AstNode 101 | 102 | /** 103 | * A statement in the body of a nested block (cannot be a declaration or a return). 104 | */ 105 | sealed trait AStmtInNestedBlock extends AStmt 106 | 107 | case class AAssignStmt(left: Assignable, right: AExpr, loc: Loc) extends AStmtInNestedBlock 108 | 109 | sealed trait Assignable 110 | 111 | sealed trait ReferenceAssignable extends Assignable 112 | 113 | case class ADerefWrite(exp: AExpr, loc: Loc) extends ReferenceAssignable 114 | 115 | sealed trait FieldAssignable extends Assignable 116 | 117 | case class ADirectFieldWrite(id: AIdentifier, field: String, loc: Loc) extends FieldAssignable 118 | 119 | case class AIndirectFieldWrite(exp: AExpr, field: String, loc: Loc) extends FieldAssignable 120 | 121 | sealed trait ABlock extends AStmt { 122 | 123 | /** 124 | * All the statements in the block, in order. 125 | */ 126 | def body: List[AStmt] 127 | 128 | } 129 | 130 | case class ANestedBlockStmt(body: List[AStmtInNestedBlock], loc: Loc) extends ABlock with AStmtInNestedBlock 131 | 132 | case class AFunBlockStmt(declarations: List[AVarStmt], others: List[AStmtInNestedBlock], ret: AReturnStmt, loc: Loc) extends ABlock { 133 | 134 | /** 135 | * The contents of the block: declarations, others, and return. 136 | */ 137 | val body: List[AStmt] = declarations ++ (others :+ ret) 138 | } 139 | 140 | case class AIfStmt(guard: AExpr, ifBranch: AStmtInNestedBlock, elseBranch: Option[AStmtInNestedBlock], loc: Loc) extends AStmtInNestedBlock 141 | 142 | case class AOutputStmt(exp: AExpr, loc: Loc) extends AStmtInNestedBlock 143 | 144 | case class AReturnStmt(exp: AExpr, loc: Loc) extends AStmt 145 | 146 | case class AErrorStmt(exp: AExpr, loc: Loc) extends AStmtInNestedBlock 147 | 148 | case class AVarStmt(declIds: List[AIdentifierDeclaration], loc: Loc) extends AStmt 149 | 150 | case class AWhileStmt(guard: AExpr, innerBlock: AStmtInNestedBlock, loc: Loc) extends AStmtInNestedBlock 151 | 152 | //////////////// Program and function /////////////// 153 | 154 | case class AProgram(funs: List[AFunDeclaration], loc: Loc) extends AstNode { 155 | 156 | def mainFunction: AFunDeclaration = { 157 | val main = findMainFunction() 158 | if (main.isDefined) main.get 159 | else throw new TipProgramException(s"Missing main function, declared functions are $funs") 160 | } 161 | 162 | def hasMainFunction: Boolean = 163 | findMainFunction().isDefined 164 | 165 | private def findMainFunction(): Option[AFunDeclaration] = 166 | funs.find(decl => decl.name == "main") 167 | 168 | override def toString: String = 169 | s"${this.print(PartialFunction.empty)}" 170 | } 171 | 172 | case class AFunDeclaration(name: String, params: List[AIdentifierDeclaration], stmts: AFunBlockStmt, loc: Loc) extends ADeclaration { 173 | override def toString: String = s"$name(${params.mkString(",")}){...}:$loc" 174 | } 175 | -------------------------------------------------------------------------------- /src/tip/ast/AstNodeData.scala: -------------------------------------------------------------------------------- 1 | package tip.ast 2 | 3 | import tip.ast.AstPrinters._ 4 | import tip.types.Type 5 | 6 | object AstNodeData { 7 | 8 | /** 9 | * Map from identifier node to corresponding declaration node. 10 | * @see [[tip.analysis.DeclarationAnalysis]] 11 | */ 12 | type DeclarationData = Map[AIdentifier, ADeclaration] 13 | 14 | /** 15 | * Map from AST node to type, if available. 16 | * @see [[tip.analysis.TypeAnalysis]] 17 | */ 18 | type TypeData = Map[AstNode, Option[Type]] 19 | 20 | /** 21 | * Implicitly make declaration data available on identifier AST nodes. 22 | * 23 | * To novice Scala programmers: 24 | * This "implicit class" has the effect that every instance of `AIdentifier` effectively 25 | * gets an extra field `declaration` (provided that this class has been imported). 26 | * Note that the value of the field is obtained via the implicit parameter `data`. 27 | * For more information about implicit classes in Scala, see [[https://docs.scala-lang.org/overviews/core/implicit-classes.html]]. 28 | */ 29 | implicit class AstNodeWithDeclaration(n: AIdentifier)(implicit val data: DeclarationData) { 30 | def declaration: ADeclaration = data(n) 31 | } 32 | 33 | /** 34 | * Implicitly make type data available on AST nodes. 35 | * 36 | * (For information about Scala's implicit classes, see [[tip.ast.AstNodeData.AstNodeWithDeclaration]].) 37 | */ 38 | implicit class AstNodeWithType(n: AstNode)(implicit val data: TypeData) { 39 | def theType: Option[Type] = data.getOrElse(n, None) 40 | 41 | private def printer: PartialFunction[AstNode, String] = { 42 | case id: AIdentifierDeclaration => s"${id.name}: ${id.theType.getOrElse("??")}" 43 | case f: AFunDeclaration => 44 | s"${f.name}(${f.params.map(_.name).mkString(",")}): ${f.theType.getOrElse("??")}\n${f.stmts.print(printer)}" 45 | } 46 | 47 | def toTypedString: String = n.print(printer) 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/tip/ast/AstPrinters.scala: -------------------------------------------------------------------------------- 1 | package tip.ast 2 | 3 | object AstPrinters { 4 | 5 | def withRelevantLocations(): PartialFunction[AstNode, String] = { 6 | case id: AIdentifierDeclaration => 7 | s"${id.print(PartialFunction.empty)}:${id.loc}" 8 | case id: AAlloc => 9 | s"${id.print(PartialFunction.empty)}:${id.loc}" 10 | } 11 | 12 | def withAllLocations(): PartialFunction[AstNode, String] = { 13 | case n: AstNode => 14 | s"${n.print(PartialFunction.empty)}:${n.loc}" 15 | } 16 | 17 | /** 18 | * Implicit class that makes a 'print' method available on 'AstNode' objects. 19 | * 20 | * (For information about implicit classes, see [[tip.ast.AstNodeData.AstNodeWithDeclaration]].) 21 | */ 22 | implicit class DefaultRecursivePrinter(n: AstNode) { 23 | 24 | def print(printer: PartialFunction[AstNode, String]): String = 25 | printer.applyOrElse(n, { 26 | n: AstNode => 27 | n match { 28 | case ACallFuncExpr(targetFun, args, _) => 29 | s"${targetFun.print(printer)}(${args.map(_.print(printer)).mkString(",")})" 30 | case AIdentifier(name, _) => 31 | name 32 | case ABinaryOp(operator, left, right, _) => 33 | s"(${left.print(printer)} $operator ${right.print(printer)})" 34 | case AUnaryOp(operator, target, _) => 35 | s"($operator${target.print(printer)})" 36 | case ANumber(value, _) => 37 | value.toString 38 | case AInput(_) => 39 | "input" 40 | case AAlloc(e, _) => 41 | s"alloc ${e.print(printer)}" 42 | case AVarRef(id, _) => 43 | s"&${id.print(printer)}" 44 | case ARecord(fields, _) => 45 | fields.map { f => 46 | s"${f.field}:${f.exp.print(printer)}" 47 | }.mkString("{", ",", "}") 48 | case AFieldAccess(record, field, _) => 49 | s"${record.print(printer)}.$field" 50 | case ANull(_) => 51 | "null" 52 | case AIdentifierDeclaration(name, _) => 53 | name 54 | case AFunDeclaration(name, args, stmts, _) => 55 | s"$name(${args.map(_.print(printer)).mkString(",")})\n${stmts.print(printer)}" 56 | case AAssignStmt(left, right, _) => 57 | val ls = left match { 58 | case AIdentifier(name, _) => 59 | name 60 | case ADerefWrite(exp, _) => 61 | s"*${exp.print(printer)}" 62 | case ADirectFieldWrite(id, field, _) => 63 | s"${id.print(printer)}.$field" 64 | case AIndirectFieldWrite(exp, field, _) => 65 | s"(*${exp.print(printer)}).$field" 66 | } 67 | s"$ls = ${right.print(printer)};" 68 | case AIfStmt(guard, ifBranch, elseBranch, _) => 69 | val elseb = elseBranch.map(x => "else " + x.print(printer)).getOrElse("") 70 | s"if (${guard.print(printer)}) ${ifBranch.print(printer)} $elseb" 71 | case AOutputStmt(exp, _) => 72 | s"output ${exp.print(printer)};" 73 | case AErrorStmt(exp, _) => 74 | s"error ${exp.print(printer)};" 75 | case AWhileStmt(guard, innerBlock, _) => 76 | s"while (${guard.print(printer)}) ${innerBlock.print(printer)}" 77 | case block: ABlock => 78 | s"{\n${block.body.map(_.print(printer)).mkString("\n")}\n}" 79 | case AReturnStmt(exp, _) => 80 | s"return ${exp.print(printer)};" 81 | case AVarStmt(declIds, _) => 82 | s"var ${declIds.map(_.print(printer)).mkString(",")};" 83 | case AProgram(funs, _) => 84 | s"${funs.map(_.print(printer)).mkString("\n\n")}" 85 | } 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/tip/ast/DepthFirstAstVisitor.scala: -------------------------------------------------------------------------------- 1 | package tip.ast 2 | 3 | /** 4 | * A depth-first visitor for ASTs. 5 | * @tparam A argument type 6 | */ 7 | trait DepthFirstAstVisitor[A] { 8 | 9 | def visit(node: AstNode, arg: A): Unit 10 | 11 | /** 12 | * Recursively perform the visit to the sub-node of the passed node, passing the provided argument. 13 | * 14 | * @param node the node whose children need to be visited 15 | * @param arg the argument to be passed to all sub-nodes 16 | */ 17 | def visitChildren(node: AstNode, arg: A): Unit = 18 | node match { 19 | case call: ACallFuncExpr => 20 | visit(call.targetFun, arg) 21 | call.args.foreach(visit(_, arg)) 22 | case bin: ABinaryOp => 23 | visit(bin.left, arg) 24 | visit(bin.right, arg) 25 | case un: AUnaryOp => 26 | visit(un.subexp, arg) 27 | case as: AAssignStmt => 28 | as.left match { 29 | case id: AIdentifier => 30 | visit(id, arg) 31 | case dw: ADerefWrite => 32 | visit(dw.exp, arg) 33 | case dfw: ADirectFieldWrite => 34 | visit(dfw.id, arg) 35 | case ifw: AIndirectFieldWrite => 36 | visit(ifw.exp, arg) 37 | } 38 | visit(as.right, arg) 39 | case block: ABlock => 40 | block.body.foreach(visit(_, arg)) 41 | case iff: AIfStmt => 42 | visit(iff.guard, arg) 43 | visit(iff.ifBranch, arg) 44 | iff.elseBranch.foreach(visit(_, arg)) 45 | case out: AOutputStmt => 46 | visit(out.exp, arg) 47 | case ret: AReturnStmt => 48 | visit(ret.exp, arg) 49 | case err: AErrorStmt => 50 | visit(err.exp, arg) 51 | case varr: AVarStmt => 52 | varr.declIds.foreach(visit(_, arg)) 53 | case whl: AWhileStmt => 54 | visit(whl.guard, arg) 55 | visit(whl.innerBlock, arg) 56 | case funDec: AFunDeclaration => 57 | funDec.params.foreach(visit(_, arg)) 58 | visit(funDec.stmts, arg) 59 | case p: AProgram => 60 | p.funs.foreach(visit(_, arg)) 61 | case acc: AFieldAccess => 62 | visit(acc.record, arg) 63 | case rec: ARecord => 64 | rec.fields.foreach(f => visit(f.exp, arg)) 65 | case alloc: AAlloc => 66 | visit(alloc.exp, arg) 67 | case ref: AVarRef => 68 | visit(ref.id, arg) 69 | case _: AAtomicExpr | _: AIdentifierDeclaration => 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/tip/cfg/CfgNode.scala: -------------------------------------------------------------------------------- 1 | package tip.cfg 2 | 3 | import tip.ast._ 4 | 5 | import scala.collection.mutable 6 | 7 | object CfgNode { 8 | 9 | var lastUid: Int = 0 10 | 11 | def uid: Int = { 12 | lastUid += 1 13 | lastUid 14 | } 15 | } 16 | 17 | /** 18 | * Node in a control-flow graph. 19 | */ 20 | trait CfgNode { 21 | 22 | /** 23 | * Predecessors of the node. 24 | */ 25 | def pred: mutable.Set[CfgNode] 26 | 27 | /** 28 | * Successors of the node. 29 | */ 30 | def succ: mutable.Set[CfgNode] 31 | 32 | /** 33 | * Unique node ID. 34 | */ 35 | def id: Int 36 | 37 | /** 38 | * The AST node contained by this node. 39 | */ 40 | def data: AstNode 41 | 42 | override def equals(obj: scala.Any): Boolean = 43 | obj match { 44 | case o: CfgNode => o.id == this.id 45 | case _ => false 46 | } 47 | 48 | override def hashCode(): Int = id 49 | } 50 | 51 | /** 52 | * Node in a CFG representing a program statement. 53 | * The `data` field holds the statement, or in case of if/while instructions, the branch condition. 54 | */ 55 | case class CfgStmtNode( 56 | override val id: Int = CfgNode.uid, 57 | override val pred: mutable.Set[CfgNode] = mutable.Set[CfgNode](), 58 | override val succ: mutable.Set[CfgNode] = mutable.Set[CfgNode](), 59 | data: AstNode 60 | ) extends CfgNode { 61 | 62 | override def toString: String = s"[Stmt] $data" 63 | } 64 | 65 | /** 66 | * Node in a CFG representing a function call. 67 | * The `data` field holds the assignment statement where the right-hand-side is the function call. 68 | */ 69 | case class CfgCallNode( 70 | override val id: Int = CfgNode.uid, 71 | override val pred: mutable.Set[CfgNode] = mutable.Set[CfgNode](), 72 | override val succ: mutable.Set[CfgNode] = mutable.Set[CfgNode](), 73 | data: AAssignStmt 74 | ) extends CfgNode { 75 | 76 | override def toString: String = s"[Call] $data" 77 | } 78 | 79 | /** 80 | * Node in a CFG representing having returned from a function call. 81 | * The `data` field holds the assignment statement where the right-hand-side is the function call. 82 | */ 83 | case class CfgAfterCallNode( 84 | override val id: Int = CfgNode.uid, 85 | override val pred: mutable.Set[CfgNode] = mutable.Set[CfgNode](), 86 | override val succ: mutable.Set[CfgNode] = mutable.Set[CfgNode](), 87 | data: AAssignStmt 88 | ) extends CfgNode { 89 | 90 | override def toString: String = s"[AfterCall] $data" 91 | } 92 | 93 | /** 94 | * Node in a CFG representing the entry of a function. 95 | */ 96 | case class CfgFunEntryNode( 97 | override val id: Int = CfgNode.uid, 98 | override val pred: mutable.Set[CfgNode] = mutable.Set[CfgNode](), 99 | override val succ: mutable.Set[CfgNode] = mutable.Set[CfgNode](), 100 | data: AFunDeclaration 101 | ) extends CfgNode { 102 | 103 | override def toString: String = s"[FunEntry] $data" 104 | } 105 | 106 | /** 107 | * Node in a CFG representing the exit of a function. 108 | */ 109 | case class CfgFunExitNode( 110 | override val id: Int = CfgNode.uid, 111 | override val pred: mutable.Set[CfgNode] = mutable.Set[CfgNode](), 112 | override val succ: mutable.Set[CfgNode] = mutable.Set[CfgNode](), 113 | data: AFunDeclaration 114 | ) extends CfgNode { 115 | 116 | override def toString: String = s"[FunExit] $data" 117 | } 118 | -------------------------------------------------------------------------------- /src/tip/cfg/CfgOps.scala: -------------------------------------------------------------------------------- 1 | package tip.cfg 2 | 3 | import tip.ast.AstNodeData._ 4 | import tip.ast.AstOps._ 5 | import tip.ast._ 6 | 7 | object CfgOps { 8 | 9 | /** 10 | * An implicit class with convenience methods for operations on CFG nodes. 11 | * 12 | * (For information about implicit classes, see [[tip.ast.AstNodeData.AstNodeWithDeclaration]].) 13 | */ 14 | implicit class CfgNodeOps(n: CfgNode) { 15 | 16 | /** 17 | * Returns the set of identifiers declared by the node, including only local variables. 18 | */ 19 | def declaredVars(implicit declData: DeclarationData): Set[ADeclaration] = 20 | n match { 21 | case r: CfgStmtNode => 22 | r.data.declaredLocals 23 | case _ => Set() 24 | } 25 | 26 | /** 27 | * Returns the set of identifiers declared by the node, including local variables, function parameters, and function identifiers. 28 | */ 29 | def declaredVarsAndParams(implicit declData: DeclarationData): Set[ADeclaration] = 30 | n match { 31 | case r: CfgStmtNode => 32 | r.data.declaredLocals 33 | case r: CfgFunEntryNode => 34 | r.data.params.toSet + r.data 35 | case _ => Set() 36 | } 37 | 38 | /** 39 | * Returns the set of declarations of the identifiers that appear in the node. 40 | */ 41 | def appearingIds(implicit declData: DeclarationData): Set[ADeclaration] = 42 | n match { 43 | case r: CfgStmtNode => 44 | r.data.appearingIds 45 | case _ => Set() 46 | } 47 | 48 | /** 49 | * Returns the set of expressions that appear in the node. 50 | */ 51 | def appearingNonInputExpressions: Set[AExpr] = 52 | n match { 53 | case r: CfgStmtNode => 54 | r.data.appearingNonInputExpressions 55 | case _ => Set() 56 | } 57 | 58 | /** 59 | * Returns the assignment that appears in the node, if any. 60 | */ 61 | def appearingAssignments: Option[AAssignStmt] = 62 | n match { 63 | case r: CfgStmtNode => 64 | r.data match { 65 | case as: AAssignStmt => Some(as) 66 | case _ => None 67 | } 68 | case _ => None 69 | } 70 | 71 | /** 72 | * Returns the set of constants appearing in the node, if any. 73 | */ 74 | def appearingConstants: Set[ANumber] = 75 | n match { 76 | case r: CfgStmtNode => 77 | r.data.appearingConstants 78 | case _ => Set() 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/tip/cfg/FragmentCfg.scala: -------------------------------------------------------------------------------- 1 | package tip.cfg 2 | 3 | import tip.ast.AstNodeData.DeclarationData 4 | import tip.ast._ 5 | import tip.util._ 6 | 7 | import scala.collection.mutable 8 | 9 | object FragmentCfg { 10 | 11 | /** 12 | * Generates a CFG for each function in the given program. 13 | */ 14 | def generateFromProgram(prog: AProgram, nodeBuilder: CfgNode => FragmentCfg)(implicit declData: DeclarationData): Map[AFunDeclaration, FragmentCfg] = 15 | prog.funs.map { f => 16 | f -> FragmentCfg.generateFromFunction(f, nodeBuilder) 17 | }.toMap 18 | 19 | /** 20 | * Constructs an empty CFG. 21 | */ 22 | private def seqUnit(): FragmentCfg = 23 | new FragmentCfg(Set(), Set()) 24 | 25 | /** 26 | * Converts a CFG node to a one-node CFG. 27 | */ 28 | def nodeToGraph(node: CfgNode): FragmentCfg = 29 | new FragmentCfg(Set(node), Set(node)) 30 | 31 | /** 32 | * Generates a CFG from the body of a function. 33 | */ 34 | def generateFromFunction(fun: AFunDeclaration, nodeBuilder: CfgNode => FragmentCfg): FragmentCfg = { 35 | 36 | def recGen(node: AstNode): FragmentCfg = 37 | node match { 38 | case fun: AFunDeclaration => 39 | val blk = recGen(fun.stmts) 40 | val entry = nodeBuilder(CfgFunEntryNode(data = fun)) 41 | val exit = nodeBuilder(CfgFunExitNode(data = fun)) 42 | entry ~ blk ~ exit 43 | case _: AAssignStmt => 44 | nodeBuilder(CfgStmtNode(data = node)) 45 | case block: ABlock => 46 | block.body.foldLeft(seqUnit()) { (g, stmt) => 47 | g ~ recGen(stmt) 48 | } 49 | case iff: AIfStmt => 50 | val ifGuard = nodeBuilder(CfgStmtNode(data = node)) 51 | val trueBranch = recGen(iff.ifBranch) 52 | val falseBranch = iff.elseBranch.map { 53 | recGen(_) 54 | } 55 | val guardedTrue = ifGuard ~ trueBranch 56 | val guardedFalse = falseBranch.map(fb => ifGuard ~ fb) 57 | guardedFalse.fold(guardedTrue | ifGuard)(guardedTrue | _) 58 | case _: AOutputStmt => 59 | nodeBuilder(CfgStmtNode(data = node)) 60 | case _: AReturnStmt => 61 | nodeBuilder(CfgStmtNode(data = node)) 62 | case _: AVarStmt => 63 | nodeBuilder(CfgStmtNode(data = node)) 64 | case whl: AWhileStmt => 65 | val whileG = nodeBuilder(CfgStmtNode(data = node)) 66 | val bodyG = recGen(whl.innerBlock) 67 | val loopingBody = whileG ~ bodyG ~ whileG 68 | loopingBody | whileG 69 | case _: AErrorStmt => 70 | nodeBuilder(CfgStmtNode(data = node)) 71 | case _: AExpr | _: AIdentifierDeclaration | _: AProgram => ??? 72 | } 73 | 74 | recGen(fun) 75 | } 76 | } 77 | 78 | /** 79 | * Fragment of a control-flow graph. 80 | * Describes a fragment of a TIP program, for example one or more statements or function bodies. 81 | * 82 | * @param graphEntries set of entry nodes of the fragment 83 | * @param graphExits set of exit nodes of the fragment 84 | * 85 | * @see [[tip.cfg.InterproceduralProgramCfg]], [[tip.cfg.IntraproceduralProgramCfg]] 86 | */ 87 | class FragmentCfg(private[cfg] val graphEntries: Set[CfgNode], private[cfg] val graphExits: Set[CfgNode]) { 88 | 89 | /** 90 | * Returns true if this is the unit CFG w.r.t. to concatenation. 91 | */ 92 | def isUnit: Boolean = graphEntries.isEmpty && graphExits.isEmpty 93 | 94 | /** 95 | * Returns the concatenation of this CFG with `after`. 96 | */ 97 | def ~(after: FragmentCfg): FragmentCfg = 98 | if (isUnit) 99 | after 100 | else if (after.isUnit) 101 | this 102 | else { 103 | graphExits.foreach(_.succ ++= after.graphEntries) 104 | after.graphEntries.foreach(_.pred ++= graphExits) 105 | new FragmentCfg(graphEntries, after.graphExits) 106 | } 107 | 108 | /** 109 | * Returns the union of this CFG with `other`. 110 | */ 111 | def |(other: FragmentCfg): FragmentCfg = 112 | new FragmentCfg(other.graphEntries.union(graphEntries), other.graphExits.union(graphExits)) 113 | 114 | /** 115 | * Returns the set of nodes in the CFG. 116 | */ 117 | def nodes: Set[CfgNode] = 118 | graphEntries.flatMap { entry => 119 | nodesRec(entry).toSet 120 | } 121 | 122 | protected def nodesRec(n: CfgNode, visited: mutable.Set[CfgNode] = mutable.Set()): mutable.Set[CfgNode] = { 123 | if (!visited.contains(n)) { 124 | visited += n 125 | n.succ.foreach { n => 126 | nodesRec(n, visited) 127 | } 128 | } 129 | visited 130 | } 131 | 132 | /** 133 | * Returns a map associating each node with its rank. 134 | * The rank is defined such that 135 | * rank(x) < rank(y) iff y is visited after x in a depth-first 136 | * visit of the control-flow graph 137 | */ 138 | lazy val rank: Map[CfgNode, Int] = { 139 | def rankRec(elems: List[CfgNode], visited: List[List[CfgNode]], level: Int): Map[CfgNode, Int] = { 140 | val curLevel = elems.map { x => 141 | x -> level 142 | }.toMap 143 | val newNeighbors = elems.flatMap(_.succ).filterNot(visited.flatten.contains).distinct 144 | if (newNeighbors.isEmpty) 145 | Map() ++ curLevel 146 | else 147 | rankRec(newNeighbors, newNeighbors :: visited, level + 1) ++ curLevel 148 | } 149 | rankRec(graphEntries.toList, List(graphEntries.toList), 0) 150 | } 151 | 152 | /** 153 | * Returns a Graphviz dot representation of the CFG. 154 | * Each node is labeled using the given function labeler. 155 | */ 156 | def toDot(labeler: CfgNode => String, idGen: CfgNode => String): String = { 157 | val dotNodes = mutable.Map[CfgNode, DotNode]() 158 | var dotArrows = mutable.MutableList[DotArrow]() 159 | nodes.foreach { n => 160 | dotNodes += (n -> new DotNode(s"${idGen(n)}", labeler(n), Map())) 161 | } 162 | nodes.foreach { n => 163 | n.succ.foreach { dest => 164 | dotArrows += new DotDirArrow(dotNodes(n), dotNodes(dest)) 165 | } 166 | } 167 | dotArrows = dotArrows.sortBy(arr => arr.fromNode.id + "-" + arr.toNode.id) 168 | val allNodes = dotNodes.values.seq.toList.sortBy(n => n.id) 169 | new DotGraph("CFG", allNodes, dotArrows).toDotString 170 | } 171 | } 172 | 173 | /** 174 | * Control-flow graph for an entire program. 175 | * 176 | * @param prog AST of the program 177 | * @param funEntries map from AST function declarations to CFG function entry nodes 178 | * @param funExits map from AST function declarations to CFG function exit nodes 179 | */ 180 | abstract class ProgramCfg(val prog: AProgram, val funEntries: Map[AFunDeclaration, CfgFunEntryNode], val funExits: Map[AFunDeclaration, CfgFunExitNode]) 181 | extends FragmentCfg(funEntries.values.toSet, funExits.values.toSet) 182 | -------------------------------------------------------------------------------- /src/tip/cfg/IntraproceduralProgramCfg.scala: -------------------------------------------------------------------------------- 1 | package tip.cfg 2 | 3 | import tip.ast.AstNodeData.DeclarationData 4 | import tip.ast._ 5 | 6 | object IntraproceduralProgramCfg { 7 | 8 | /** 9 | * Converts the given CFG node into a [[tip.cfg.FragmentCfg]]. 10 | * No call and after-call nodes are generated. 11 | */ 12 | private def simpleNodeBuilder(n: CfgNode): FragmentCfg = 13 | n match { 14 | case fentry: CfgFunEntryNode => 15 | FragmentCfg.nodeToGraph(CfgFunEntryNode(data = fentry.data)) 16 | case fentry: CfgFunExitNode => 17 | FragmentCfg.nodeToGraph(CfgFunExitNode(data = fentry.data)) 18 | case normal: CfgStmtNode => 19 | normal.data match { 20 | case w: AWhileStmt => FragmentCfg.nodeToGraph(CfgStmtNode(data = w.guard)) 21 | case i: AIfStmt => FragmentCfg.nodeToGraph(CfgStmtNode(data = i.guard)) 22 | case o => FragmentCfg.nodeToGraph(CfgStmtNode(data = o)) 23 | } 24 | } 25 | 26 | /** 27 | * Generates an [[IntraproceduralProgramCfg]] from a program. 28 | */ 29 | def generateFromProgram(prog: AProgram)(implicit declData: DeclarationData): IntraproceduralProgramCfg = { 30 | val funGraphs = FragmentCfg.generateFromProgram(prog, simpleNodeBuilder) 31 | val allEntries = funGraphs.mapValues(cfg => { assert(cfg.graphEntries.size == 1); cfg.graphEntries.head.asInstanceOf[CfgFunEntryNode] }) 32 | val allExits = funGraphs.mapValues(cfg => { assert(cfg.graphExits.size == 1); cfg.graphExits.head.asInstanceOf[CfgFunExitNode] }) 33 | new IntraproceduralProgramCfg(prog, allEntries, allExits) 34 | } 35 | } 36 | 37 | /** 38 | * Control-flow graph for a program, where function calls are represented as expressions, without using call/after-call nodes. 39 | */ 40 | class IntraproceduralProgramCfg(prog: AProgram, funEntries: Map[AFunDeclaration, CfgFunEntryNode], funExits: Map[AFunDeclaration, CfgFunExitNode]) 41 | extends ProgramCfg(prog, funEntries, funExits) 42 | -------------------------------------------------------------------------------- /src/tip/concolic/ConcolicEngine.scala: -------------------------------------------------------------------------------- 1 | package tip.concolic 2 | 3 | import tip.ast.AstNodeData._ 4 | import tip.ast.AProgram 5 | import tip.util.Log 6 | import SMTSolver.Symbol 7 | 8 | class ConcolicEngine(val program: AProgram)(implicit declData: DeclarationData) extends SymbolicInterpreter(program) { 9 | 10 | override val log = Log.logger[this.type]() 11 | 12 | def nextExplorationTarget(lastExplored: ExecutionTree, root: ExecutionTreeRoot): Option[(Branch, Boolean)] = 13 | lastExplored.parent match { 14 | case b: Branch => 15 | (b.branches(true), b.branches(false)) match { 16 | case (_: SubTreePlaceholder, _) if b.count(true) == 0 => Some((b, true)) 17 | case (_, _: SubTreePlaceholder) if b.count(false) == 0 => Some((b, false)) 18 | case (_, _) => nextExplorationTarget(b, root) 19 | } 20 | case _ => None 21 | } 22 | 23 | def newInputs(symbols: List[Symbol], lastNode: ExecutionTree, root: ExecutionTreeRoot): Option[List[Int]] = { 24 | if (lastNode == root) { 25 | log.info("Program never branches") 26 | return None 27 | } 28 | val target = nextExplorationTarget(lastNode, root) 29 | log.info(s"Execution tree status: \n${ExecutionTreePrinter.printExecutionTree(root)}") 30 | target match { 31 | case Some((targetNode, value)) => 32 | val pc = targetNode.pathCondition(List((targetNode.symcondition, value))) 33 | log.info(s"Path condition for next run: $pc") 34 | val smt = SMTSolver.pathToSMT(symbols, pc) 35 | log.info(s"SMT script for next run: \n$smt") 36 | SMTSolver.solve(smt) match { 37 | case None => 38 | log.info(s"Path condition is unsatisfiable") 39 | targetNode.unsat(value) 40 | newInputs(symbols, lastNode, root) 41 | case Some(mapping) => 42 | log.info(s"Model: $mapping") 43 | Some(symbols.map(v => mapping.get(v.name).map(_.toInt).getOrElse(scala.util.Random.nextInt))) 44 | } 45 | case _ => None 46 | } 47 | } 48 | 49 | def test(budget: Int = 20): Unit = { 50 | val root = new ExecutionTreeRoot() 51 | 52 | var runs = 0 53 | var inputs: List[Int] = Nil 54 | var results: List[ExecutionResult] = Nil 55 | while (runs <= budget) { 56 | 57 | runs += 1 58 | 59 | log.info("\n") 60 | log.info(s"Starting run $runs") 61 | 62 | val result: ExecutionResult = 63 | try { 64 | semp(root, inputs) match { 65 | case (spec.SymbIntValue(i, _), store) => 66 | log.info(s"Program ran successfully, result: $i") 67 | ExSuccess(store.extra, i) 68 | case _ => ??? 69 | } 70 | } catch { 71 | case err: ExecutionError => 72 | log.info(s"Error found: $err") 73 | ExFailure(err.store.extra, err.getMessage) 74 | } 75 | 76 | results = result :: results 77 | newInputs(result.symbolicVars, result.lastNode, root) match { 78 | case Some(values) => 79 | log.info(s"New input for ${result.symbolicVars}: $values") 80 | inputs = values 81 | case None => 82 | log.info(s"Finished exhaustive exploration in $runs runs") 83 | reportExplorationStatistics(results) 84 | return 85 | } 86 | } 87 | log.info(s"Exhausted search budget after $runs runs") 88 | reportExplorationStatistics(results) 89 | } 90 | 91 | private def reportExplorationStatistics(results: List[ExecutionResult]): Unit = { 92 | val successes = results.collect { case s: ExSuccess => s } 93 | log.info(s"Found ${successes.length} successful input sequences") 94 | successes.foreach(s => log.info(s"Input sequence ${s.usedInputs} produces: \n${s.value}")) 95 | val failures = results.collect { case f: ExFailure => f } 96 | log.info(s"Found ${failures.length} failure-inducing input sequences.") 97 | failures.foreach(f => log.info(s"Input sequence ${f.usedInputs} fails: \n${f.message}.")) 98 | } 99 | 100 | abstract class ExecutionResult(val concolicState: ConcolicState) { 101 | val lastNode = concolicState.ct 102 | val symbolicVars = concolicState.symbols 103 | val usedInputs = concolicState.usedInputs 104 | } 105 | 106 | case class ExSuccess(s: ConcolicState, value: Int) extends ExecutionResult(s) 107 | 108 | case class ExFailure(s: ConcolicState, message: String) extends ExecutionResult(s) 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/tip/concolic/ExecutionTree.scala: -------------------------------------------------------------------------------- 1 | package tip.concolic 2 | 3 | import tip.ast.AExpr 4 | import tip.concolic.ExecutionTree._ 5 | import tip.util.Log 6 | 7 | import scala.collection.mutable 8 | 9 | object ExecutionTree { 10 | val log = Log.logger[this.type]() 11 | } 12 | 13 | /** 14 | * Execution tree node 15 | */ 16 | sealed trait ExecutionTree { self => 17 | 18 | def parent: ExecutionTree 19 | 20 | def pathCondition(suffix: List[(AExpr, Boolean)] = Nil): List[(AExpr, Boolean)] = 21 | parent match { 22 | case b: Branch if b.branches(true) == self => 23 | // self node is in the true branch 24 | (b.symcondition, true) :: b.pathCondition(suffix) 25 | case b: Branch if b.branches(false) == self => 26 | // self node is in the false branch 27 | (b.symcondition, false) :: b.pathCondition(suffix) 28 | case _ => parent.pathCondition(suffix) 29 | } 30 | 31 | def children: List[ExecutionTree] 32 | 33 | def branch(cond: AExpr, symcond: AExpr, value: Boolean): ExecutionTree 34 | 35 | } 36 | 37 | /** 38 | * Special root node of the execution tree 39 | */ 40 | class ExecutionTreeRoot() extends ExecutionTree { 41 | var _children = List[Branch]() 42 | def children: List[ExecutionTree] = _children 43 | override def pathCondition(suffix: List[(AExpr, Boolean)] = Nil): List[(AExpr, Boolean)] = suffix 44 | override def parent: ExecutionTree = ??? // This should never happen 45 | def branch(cond: AExpr, symcond: AExpr, value: Boolean): ExecutionTree = { 46 | if (_children.isEmpty) { 47 | log.info(s"Encountered unseen branching condition: $cond") 48 | val node = new Branch(cond, symcond, this) 49 | _children = List(node) 50 | } 51 | val node = _children.last 52 | log.info(s"Exploring ${if (node.count(value) == 0) "unseen " else ""}$value branch") 53 | node.count(value) += 1 54 | node.branches(value) 55 | } 56 | } 57 | 58 | /** 59 | * A node representing a sub-tree of the execution tree with no observed branching 60 | */ 61 | class SubTreePlaceholder(val parent: Branch) extends ExecutionTree { 62 | override def children: List[ExecutionTree] = List() 63 | 64 | override def branch(cond: AExpr, symcond: AExpr, value: Boolean): ExecutionTree = { 65 | log.info(s"Encountered unseen branching condition: $cond") 66 | val node = new Branch(cond, symcond, parent) 67 | if (parent.branches(true) == this) { 68 | parent.branches(true) = node 69 | } else if (parent.branches(false) == this) { 70 | parent.branches(false) = node 71 | } 72 | log.info(s"Exploring unseen $value branch") 73 | node.count(value) += 1 74 | node.branches(value) 75 | } 76 | } 77 | 78 | /** 79 | * A node representing an unvisited sub-tree of the execution tree, which is known to be unsatisfiable 80 | */ 81 | class UnsatSubTree(val parent: Branch) extends ExecutionTree { 82 | override def children: List[ExecutionTree] = List() 83 | override def branch(cond: AExpr, symcond: AExpr, value: Boolean): ExecutionTree = 84 | ??? //this should never happen 85 | } 86 | 87 | /** 88 | * A node representing a branching in the execution tree 89 | */ 90 | class Branch( 91 | val condition: AExpr, 92 | val symcondition: AExpr, 93 | val parent: ExecutionTree, 94 | val branches: mutable.Map[Boolean, ExecutionTree] = mutable.Map(), 95 | val count: mutable.Map[Boolean, Int] = mutable.Map() 96 | ) extends ExecutionTree { 97 | 98 | branches(true) = new SubTreePlaceholder(this) 99 | branches(false) = new SubTreePlaceholder(this) 100 | count(true) = 0 101 | count(false) = 0 102 | 103 | def children: List[ExecutionTree] = branches.values.toList 104 | 105 | override def branch(cond: AExpr, symcond: AExpr, value: Boolean): ExecutionTree = { 106 | assert(cond == condition) 107 | assert(symcondition == symcond) 108 | log.info(s"Encountered seen branching condition: $cond") 109 | log.info(s"Exploring ${if (count(value) == 0) "unseen " else ""}$value branch") 110 | count(value) += 1 111 | branches(value) 112 | } 113 | 114 | def unsat(branch: Boolean): ExecutionTree = 115 | branches(branch) match { 116 | case _: SubTreePlaceholder => 117 | branches(branch) = new UnsatSubTree(this) 118 | branches(branch) 119 | case _ => 120 | ??? // Impossible: previously satisfiable branch becomes unsatisfiable 121 | } 122 | } 123 | 124 | object ExecutionTreePrinter { 125 | private def printNodeValue(treeNode: ExecutionTree, out: StringBuffer): Unit = { 126 | val str = treeNode match { 127 | case n: SubTreePlaceholder => 128 | if (n.parent.branches(true) == treeNode && n.parent.count(true) > 0 129 | || n.parent.branches(false) == treeNode && n.parent.count(false) > 0) 130 | "" 131 | else 132 | "" 133 | case _: UnsatSubTree => "" 134 | case b: Branch => s"${b.symcondition} (${b.condition})" 135 | case _ => ??? 136 | } 137 | out.append(str) 138 | out.append('\n') 139 | } 140 | 141 | private def printTree(treeNode: ExecutionTree, out: StringBuffer, isTrue: Boolean = true, indent: String = "", root: Boolean = false): Unit = { 142 | treeNode match { 143 | case b: Branch => 144 | printTree(b.branches(true), out, true, indent + (if (isTrue) " " else " | ")) 145 | case _ => 146 | } 147 | 148 | out.append(indent) 149 | if (!root) { 150 | if (isTrue) { 151 | out.append(" /") 152 | } else { 153 | out.append(" \\") 154 | } 155 | out.append(s"----${if (isTrue) "T" else "F"}--- ") 156 | } 157 | 158 | printNodeValue(treeNode, out) 159 | 160 | treeNode match { 161 | case b: Branch => 162 | printTree(b.branches(false), out, false, indent + (if (isTrue && !root) " | " else " ")) 163 | case _ => 164 | } 165 | } 166 | 167 | def printExecutionTree(treeNode: ExecutionTreeRoot): String = { 168 | val sb = new StringBuffer() 169 | printTree(treeNode.children.last, sb, root = true) 170 | sb.toString 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/tip/concolic/SMTSolver.scala: -------------------------------------------------------------------------------- 1 | package tip.concolic 2 | 3 | import tip.ast._ 4 | import smtlib.Interpreter 5 | import smtlib.interpreters.Z3Interpreter 6 | import smtlib.parser.Parser 7 | import smtlib.parser.Commands._ 8 | import smtlib.parser.CommandsResponses._ 9 | import smtlib.parser.Terms._ 10 | import tip.util.Log 11 | 12 | object SMTSolver { 13 | 14 | val log = Log.logger[this.type]() 15 | 16 | /** 17 | * Expressions extended with symbols. 18 | */ 19 | class Symbol(location: Loc, counter: Int) extends AIdentifier(s"s$counter", location) { 20 | override def toString: String = s"s$counter" 21 | } 22 | 23 | def pathToSMT(vars: List[Symbol], path: List[(AExpr, Boolean)]): String = { 24 | def opToSexp(op: BinaryOperator): String = 25 | op match { 26 | case Eqq => "=" 27 | case _ => op.toString 28 | } 29 | 30 | def expToSexp(exp: AExpr): String = 31 | exp match { 32 | case ABinaryOp(op, left, right, _) => 33 | s"(${opToSexp(op)} ${expToSexp(left)} ${expToSexp(right)})" 34 | case n: ANumber => n.value.toString 35 | case n: Symbol => n.name.toString 36 | case _ => exp.toString 37 | } 38 | 39 | def symbolsToSMT(vars: List[Symbol]): String = 40 | vars.map(sv => s"(declare-const ${sv.name} Int)").mkString("\n") 41 | 42 | path.foldLeft(symbolsToSMT(vars))((script: String, cond: (AExpr, Boolean)) => { 43 | val branchrecord = 44 | if (cond._2) 45 | expToSexp(cond._1) 46 | else 47 | "(not " + expToSexp(cond._1) + ")" 48 | script + "\n" + "(assert " + branchrecord + ")" 49 | }) 50 | } 51 | 52 | def runScriptGetModel(script: Script)(implicit interpreter: Interpreter): Option[List[SExpr]] = { 53 | script.commands.foreach(interpreter.eval) 54 | interpreter.eval(CheckSat()) match { 55 | case CheckSatStatus(SatStatus) => 56 | interpreter.eval(GetModel()) match { 57 | case GetModelResponseSuccess(m) => Some(m) 58 | case s => log.info(s"Unhandled sat response: $s"); None 59 | } 60 | case CheckSatStatus(UnsatStatus) => None 61 | case s => log.info(s"Unhandled response code: $s"); None 62 | } 63 | } 64 | 65 | def extractConstModel(model: List[SExpr]): Map[String, BigInt] = { 66 | def extract(cl: SExpr): Option[(String, BigInt)] = cl match { 67 | case DefineFun(fundef) => 68 | if (fundef.params.isEmpty) { 69 | (fundef.body, fundef.returnSort) match { 70 | case (SNumeral(v), smtlib.theories.Ints.IntSort()) => 71 | /* Positive integer */ 72 | Some((fundef.name.name, v)) 73 | case (FunctionApplication(QualifiedIdentifier(SimpleIdentifier(SSymbol("-")), _), List(SNumeral(v))), smtlib.theories.Ints.IntSort()) => 74 | /* Negative numbers are represented as (- x) */ 75 | Some((fundef.name.name, -v)) 76 | case (SHexadecimal(v), smtlib.theories.FixedSizeBitVectors.BitVectorSort(_)) => 77 | /* Bitvector number */ 78 | Some((fundef.name.name, v.toInt)) 79 | /* There's probably more missing cases */ 80 | case _ => log.info(s"Ignoring non-integer model values ($fundef)"); None 81 | } 82 | } else { 83 | log.info(s"Ignoring non-constant values in the model ($fundef)") 84 | None 85 | } 86 | case _ => log.info(s"Ignoring unknown model clause ($cl)"); None 87 | } 88 | 89 | model match { 90 | case Nil => Map[String, BigInt]() 91 | case first :: rest => 92 | extract(first) match { 93 | case Some(m) => extractConstModel(rest) + m 94 | case None => extractConstModel(rest) 95 | } 96 | } 97 | } 98 | 99 | implicit lazy val z3: Z3Interpreter = smtlib.interpreters.Z3Interpreter.buildDefault 100 | 101 | /** Solves a SMTLib script */ 102 | def solve(script: Script): Option[Map[String, BigInt]] = { 103 | reset() 104 | runScriptGetModel(script).map(extractConstModel) 105 | } 106 | 107 | /** 108 | * Solves a SMTLib script represented as a string. For example: 109 | * val s = "(declare-const x Int) (assert (> x 0))" 110 | * tip.SMTUtils.solve(s) // Returns Some(Map(x -> 1)) 111 | */ 112 | def solve(s: String): Option[Map[String, BigInt]] = 113 | solve(Parser.fromString(s).parseScript) 114 | 115 | /** 116 | * Reset the status of the solver. If not called, previous constraints still hold. 117 | */ 118 | def reset(): Unit = z3.eval(Reset()) 119 | } 120 | -------------------------------------------------------------------------------- /src/tip/concolic/SymbolicInterpreter.scala: -------------------------------------------------------------------------------- 1 | package tip.concolic 2 | 3 | import tip.ast._ 4 | import tip.ast.AstNodeData.DeclarationData 5 | import tip.interpreter.Interpreter 6 | import SMTSolver.Symbol 7 | 8 | abstract class SymbolicInterpreter(program: AProgram)(implicit declData: DeclarationData) extends Interpreter(program) { 9 | val spec = SymbolicValues 10 | import spec._ 11 | type Extra = ConcolicState 12 | 13 | case class ConcolicState(symbols: List[Symbol] = Nil, ct: ExecutionTree, counter: Int = 0, inputs: List[Int], usedInputs: List[Int] = Nil) { 14 | def usedInput(symbol: Symbol, num: Int): ConcolicState = 15 | this.copy(symbols = symbols :+ symbol, usedInputs = usedInputs :+ num, counter = counter + 1) 16 | def getInput: Int = 17 | if (counter < inputs.length) { 18 | inputs(counter) 19 | } else { 20 | scala.util.Random.nextInt 21 | } 22 | } 23 | 24 | def semp(ct: ExecutionTree, inputs: List[Int]): (IntValue, Store) = { 25 | val initEnv: Env = Map() 26 | val initStore: Store = Store(Map(), ConcolicState(ct = ct, inputs = inputs)) 27 | semp(initEnv, initStore) 28 | } 29 | 30 | def semp(): IntValue = 31 | semp(new ExecutionTreeRoot(), Nil)._1 32 | 33 | override protected def branchTaken(guard: AExpr, value: EValue, branch: Boolean, store: Store): Store = 34 | value match { 35 | case x: SymbIntValue => 36 | store.setExtra(store.extra.copy(ct = store.extra.ct.branch(guard, x.symbolic, branch))) 37 | case _ => store 38 | } 39 | 40 | override protected def input(exp: AExpr, env: Env, store: Store): (EValue, Store) = { 41 | val newSymbol = new Symbol(exp.loc, store.extra.counter) 42 | val num = store.extra.getInput 43 | val v = SymbIntValue(num, newSymbol) 44 | log.info(s"Generated symbolic input value: $v.") 45 | (v, store.setExtra(store.extra.usedInput(newSymbol, num))) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/tip/concolic/SymbolicValues.scala: -------------------------------------------------------------------------------- 1 | package tip.concolic 2 | 3 | import tip.ast._ 4 | import tip.interpreter.ValueSpecification 5 | 6 | object SymbolicValues extends ValueSpecification { 7 | 8 | val noLoc = Loc(-1, -1) 9 | 10 | var lastLoc = 0 11 | 12 | case class SymbIntValue(i: Int, symbolic: AExpr) extends IntValue 13 | 14 | /** 15 | * Null: expressible and storable, but not denotable 16 | */ 17 | case class ConcreteNullValue() extends NullValue 18 | 19 | /** 20 | * Reference: expressible, storable and denotable 21 | */ 22 | case class ConcreteReferenceValue(i: Int) extends ReferenceValue 23 | 24 | /** 25 | * A procedure: denotable and storable 26 | */ 27 | case class ConcreteFunValue(fun: AFunDeclaration) extends FunValue 28 | 29 | /** 30 | * Record value. 31 | */ 32 | case class ConcreteRecordValue(fields: Map[String, EValue]) extends RecordValue 33 | 34 | val nullValue = ConcreteNullValue() 35 | 36 | val returnLoc = ConcreteReferenceValue(0) 37 | 38 | /** 39 | * Creates a new reference 40 | */ 41 | def newLoc(): ReferenceValue = { lastLoc += 1; ConcreteReferenceValue(lastLoc) } 42 | 43 | def constInt(i: Int): IntValue = SymbIntValue(i, ANumber(i, noLoc)) 44 | 45 | def eqq(x: EValue, y: EValue): IntValue = (x, y) match { 46 | case (i1: SymbIntValue, i2: SymbIntValue) => 47 | SymbIntValue(if (i1.i == i2.i) 1 else 0, ABinaryOp(Eqq, i1.symbolic, i2.symbolic, noLoc)) 48 | case (x: EValue, y: EValue) => 49 | // Equality of non-number is not supported by the solver, use concrete symbolic value. 50 | // Concolic testing becomes incomplete 51 | val num = if (x == y) 1 else 0 52 | SymbIntValue(num, ANumber(num, noLoc)) 53 | } 54 | def eqqInt(x: IntValue, y: IntValue): Boolean = (x, y) match { 55 | case (x: SymbIntValue, y: SymbIntValue) => 56 | x.i == y.i 57 | case _ => ??? 58 | } 59 | def divideInt(x: IntValue, y: IntValue): IntValue = (x, y) match { 60 | case (x: SymbIntValue, y: SymbIntValue) => 61 | // Division not supported by the solver, use concrete symbolic value 62 | // Concolic testing becomes incomplete 63 | SymbIntValue(x.i / y.i, ANumber(x.i / y.i, noLoc)) 64 | case _ => ??? 65 | } 66 | def greatThanInt(x: IntValue, y: IntValue): IntValue = (x, y) match { 67 | case (x: SymbIntValue, y: SymbIntValue) => 68 | SymbIntValue(if (x.i > y.i) 1 else 0, ABinaryOp(GreatThan, x.symbolic, y.symbolic, noLoc)) 69 | case _ => ??? 70 | } 71 | def timesInt(x: IntValue, y: IntValue): IntValue = (x, y) match { 72 | case (x: SymbIntValue, y: SymbIntValue) => 73 | SymbIntValue(x.i * y.i, ABinaryOp(Times, x.symbolic, y.symbolic, noLoc)) 74 | case _ => ??? 75 | } 76 | def plusInt(x: IntValue, y: IntValue): IntValue = (x, y) match { 77 | case (x: SymbIntValue, y: SymbIntValue) => 78 | SymbIntValue(x.i + y.i, ABinaryOp(Plus, x.symbolic, y.symbolic, noLoc)) 79 | case _ => ??? 80 | } 81 | def minusInt(x: IntValue, y: IntValue): IntValue = (x, y) match { 82 | case (x: SymbIntValue, y: SymbIntValue) => 83 | SymbIntValue(x.i - y.i, ABinaryOp(Minus, x.symbolic, y.symbolic, noLoc)) 84 | case _ => ??? 85 | } 86 | 87 | def mkFun(f: AFunDeclaration): FunValue = ConcreteFunValue(f) 88 | 89 | def mkRecord(fields: Map[String, EValue]) = ConcreteRecordValue(fields) 90 | } 91 | -------------------------------------------------------------------------------- /src/tip/interpreter/ConcreteValues.scala: -------------------------------------------------------------------------------- 1 | package tip.interpreter 2 | 3 | import tip.ast.AFunDeclaration 4 | 5 | /** 6 | * Values for concrete execution of TIP progams. 7 | */ 8 | object ConcreteValues extends ValueSpecification { 9 | 10 | var lastLoc = 0 11 | 12 | /** 13 | * Integer value. 14 | */ 15 | case class ConcreteIntValue(i: Int) extends IntValue 16 | 17 | /** 18 | * Null value. 19 | */ 20 | case class ConcreteNullValue() extends NullValue 21 | 22 | /** 23 | * Reference: expressible, storable and denotable 24 | * @param i determines the ID, i.e. the "address" being referred to 25 | */ 26 | case class ConcreteReferenceValue(i: Int) extends ReferenceValue 27 | 28 | /** 29 | * Reference to field. 30 | */ 31 | case class ConcreteReferenceFieldValue(i: Int, field: String) extends ReferenceValue 32 | 33 | /** 34 | * Function value. 35 | */ 36 | case class ConcreteFunValue(fun: AFunDeclaration) extends FunValue 37 | 38 | /** 39 | * Record value. 40 | */ 41 | case class ConcreteRecordValue(fields: Map[String, EValue]) extends RecordValue 42 | 43 | val nullValue = ConcreteNullValue() 44 | 45 | val returnLoc = ConcreteReferenceValue(0) 46 | 47 | def newLoc(): ReferenceValue = { lastLoc += 1; ConcreteReferenceValue(lastLoc) } 48 | 49 | def constInt(i: Int): IntValue = ConcreteIntValue(i) 50 | 51 | def eqq(x: EValue, y: EValue): IntValue = ConcreteIntValue(if (x == y) 1 else 0) 52 | 53 | def eqqInt(x: IntValue, y: IntValue): Boolean = (x, y) match { 54 | case (x: ConcreteIntValue, y: ConcreteIntValue) => x == y 55 | case _ => ??? 56 | } 57 | 58 | def divideInt(x: IntValue, y: IntValue): IntValue = (x, y) match { 59 | case (x: ConcreteIntValue, y: ConcreteIntValue) => ConcreteIntValue(x.i / y.i) 60 | case _ => ??? 61 | } 62 | 63 | def greatThanInt(x: IntValue, y: IntValue): IntValue = (x, y) match { 64 | case (x: ConcreteIntValue, y: ConcreteIntValue) => ConcreteIntValue(if (x.i > y.i) 1 else 0) 65 | case _ => ??? 66 | } 67 | 68 | def timesInt(x: IntValue, y: IntValue): IntValue = (x, y) match { 69 | case (x: ConcreteIntValue, y: ConcreteIntValue) => ConcreteIntValue(x.i * y.i) 70 | case _ => ??? 71 | } 72 | 73 | def plusInt(x: IntValue, y: IntValue): IntValue = (x, y) match { 74 | case (x: ConcreteIntValue, y: ConcreteIntValue) => ConcreteIntValue(x.i + y.i) 75 | case _ => ??? 76 | } 77 | 78 | def minusInt(x: IntValue, y: IntValue): IntValue = (x, y) match { 79 | case (x: ConcreteIntValue, y: ConcreteIntValue) => ConcreteIntValue(x.i - y.i) 80 | case _ => ??? 81 | } 82 | 83 | def mkFun(f: AFunDeclaration): FunValue = ConcreteFunValue(f) 84 | 85 | def mkRecord(fields: Map[String, EValue]) = ConcreteRecordValue(fields) 86 | } 87 | -------------------------------------------------------------------------------- /src/tip/interpreter/ValueSpecification.scala: -------------------------------------------------------------------------------- 1 | package tip.interpreter 2 | 3 | import tip.ast.AFunDeclaration 4 | 5 | /** 6 | * Specification of values. 7 | * (Using the classic terminology by Strachey.) 8 | */ 9 | trait ValueSpecification { 10 | 11 | /** 12 | * Expressible values, the ones that can result from evaluation of expressions. 13 | * 14 | * In TIP, all values are expressible. 15 | */ 16 | trait EValue 17 | 18 | /** 19 | * Storable values, the ones that that can be stored in mutable variables or heap cells. 20 | * 21 | * Storable values are generally a subset of expressible ones; in TIP they are the same. 22 | */ 23 | type Value = EValue 24 | 25 | /** 26 | * Denotable values, the ones that represent memory locations. 27 | * 28 | * In TIP those are references to variables or heap cells. 29 | */ 30 | type Location = ReferenceValue 31 | 32 | /** 33 | * Null: expressible and storable, but not denotable. 34 | */ 35 | trait NullValue extends EValue 36 | 37 | /** 38 | * Integers: expressible and storable, but not denotable. 39 | */ 40 | trait IntValue extends EValue { 41 | 42 | val i: Int 43 | } 44 | 45 | /** 46 | * Reference: expressible, storable and denotable. 47 | */ 48 | trait ReferenceValue extends EValue 49 | 50 | /** 51 | * Functions: expressible and storable, but not denotable. 52 | */ 53 | trait FunValue extends EValue { 54 | 55 | /** 56 | * The function that this value represents 57 | */ 58 | val fun: AFunDeclaration 59 | } 60 | 61 | /** 62 | * Record values: expressible and storable, but not denotable. 63 | */ 64 | trait RecordValue extends EValue { 65 | 66 | val fields: Map[String, EValue] 67 | } 68 | 69 | /** 70 | * The null value. 71 | */ 72 | def nullValue: NullValue 73 | 74 | /** 75 | * The reserved location for storing return values. 76 | */ 77 | def returnLoc: ReferenceValue 78 | 79 | /** 80 | * Produces a fresh location. 81 | */ 82 | def newLoc(): ReferenceValue 83 | 84 | /** 85 | * Makes am integer value from a literal. 86 | */ 87 | def constInt(i: Int): IntValue 88 | 89 | /** 90 | * The `==` operator on arbitrary values. 91 | */ 92 | def eqq(x: EValue, y: EValue): IntValue 93 | 94 | /** 95 | * Equality of integer values. 96 | */ 97 | def eqqInt(x: IntValue, y: IntValue): Boolean 98 | 99 | /** 100 | * The `/` operator on integer values. 101 | */ 102 | def divideInt(x: IntValue, y: IntValue): IntValue 103 | 104 | /** 105 | * The `>=` operator on integer values. 106 | */ 107 | def greatThanInt(x: IntValue, y: IntValue): IntValue 108 | 109 | /** 110 | * The `*` operator on integer values. 111 | */ 112 | def timesInt(x: IntValue, y: IntValue): IntValue 113 | 114 | /** 115 | * Thr `+` operator on integer values. 116 | */ 117 | def plusInt(x: IntValue, y: IntValue): IntValue 118 | 119 | /** 120 | * The `-` operator on integer values. 121 | */ 122 | def minusInt(x: IntValue, y: IntValue): IntValue 123 | 124 | /** 125 | * Makes a function value from a function declaration in the program code. 126 | */ 127 | def mkFun(fun: AFunDeclaration): FunValue 128 | 129 | /** 130 | * Makes a new record value. 131 | */ 132 | def mkRecord(fields: Map[String, EValue]): RecordValue 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/tip/lattices/ConstantPropagationLattice.scala: -------------------------------------------------------------------------------- 1 | package tip.lattices 2 | 3 | /** 4 | * Constant propagation lattice. 5 | */ 6 | object ConstantPropagationLattice extends FlatLattice[Int]() with LatticeWithOps { 7 | 8 | private def apply(op: (Int, Int) => Int, a: Element, b: Element): Element = (a, b) match { 9 | case (FlatEl(x), FlatEl(y)) => FlatEl(op(x, y)) 10 | case (Bot, _) => Bot 11 | case (_, Bot) => Bot 12 | case (_, Top) => Top 13 | case (Top, _) => Top 14 | } 15 | 16 | def num(i: Int): Element = FlatEl(i) 17 | 18 | def plus(a: Element, b: Element): Element = apply(_ + _, a, b) 19 | 20 | def minus(a: Element, b: Element): Element = apply(_ - _, a, b) 21 | 22 | def times(a: Element, b: Element): Element = apply(_ * _, a, b) 23 | 24 | def div(a: Element, b: Element): Element = apply((x, y) => if (y != 0) x / y else Bot, a, b) 25 | 26 | def eqq(a: Element, b: Element): Element = apply((x, y) => if (x == y) 1 else 0, a, b) 27 | 28 | def gt(a: Element, b: Element): Element = apply((x, y) => if (x > y) 1 else 0, a, b) 29 | } 30 | -------------------------------------------------------------------------------- /src/tip/lattices/EdgeEnvLattice.scala: -------------------------------------------------------------------------------- 1 | package tip.lattices 2 | 3 | import tip.solvers.Lambda 4 | 5 | /** 6 | * Lattice of the form ((D -> L) -> (D -> L)). 7 | * Each element is represented by Map[(DL, DL), edgelattice.Element] 8 | * using the bottom element of `EdgeFunctionLattice` as default value. 9 | */ 10 | class EdgeEnvLattice[D, L <: Lattice, EFL <: EdgeFunctionLattice[L]](val edgelattice: EFL) extends Lattice { 11 | 12 | import edgelattice.valuelattice 13 | 14 | type DL = Either[D, Lambda] 15 | 16 | type Element = Map[(DL, DL), edgelattice.Element] 17 | 18 | type StateLatticeElement = Map[D, valuelattice.Element] 19 | 20 | val bottom: Element = Map() 21 | 22 | def lub(x: Element, y: Element): Element = 23 | y.foldLeft(x) { case (tm, (dd, ye)) => tm + (dd -> edgelattice.lub(tm.getOrElse(dd, edgelattice.bottom), ye)) } 24 | 25 | /** 26 | * Applies the function f: (D -> L) -> (D -> L) to the value x: D -> L 27 | * where f is represented by Map[(DL, DL), edgelattice.Element]. 28 | */ 29 | def apply(f: Element, x: StateLatticeElement): StateLatticeElement = 30 | apply(curry(f), x) 31 | 32 | /** 33 | * Applies the function f: (D -> L) -> (D -> L) to the value x: D -> L 34 | * where f is represented by DL => Map[DL, edgelattice.Element]. 35 | */ 36 | def apply(f: DL => Map[DL, edgelattice.Element], x: StateLatticeElement): StateLatticeElement = { 37 | val t1 = x.toList.flatMap { 38 | case (a, v) => 39 | f(Left(a)).toList.flatMap { 40 | case (Left(b), e) => Some(b, e(v)) 41 | case _ => None 42 | } 43 | } 44 | val t2 = f(Right(Lambda())).toList.flatMap { 45 | case (Left(b), e) => Some(b, e(valuelattice.bottom)) 46 | case _ => None 47 | } 48 | build2(t1 ++ t2) 49 | } 50 | 51 | /** 52 | * Composes f and g. 53 | * The resulting element first applies `f` then `g`. 54 | */ 55 | def compose(f: Element, g: Element): Element = 56 | compose2(f, curry(g)) 57 | 58 | /** 59 | * Composes f and g. 60 | * The resulting element first applies `f` then `g`. 61 | */ 62 | def compose2(f: Element, g: DL => Map[DL, edgelattice.Element]): Element = 63 | build(f.toList.flatMap { case ((d1, d2), fe) => g(d2).map { case (d3, ge) => ((d1, d3), ge.composeWith(fe)) } }) 64 | 65 | /** 66 | * Builds a Element from a list of key-value pairs. 67 | */ 68 | private def build(q: List[((DL, DL), edgelattice.Element)]): Element = 69 | q.foldLeft(Map[(DL, DL), edgelattice.Element]()) { case (tm, (dd, e)) => tm + (dd -> edgelattice.lub(tm.getOrElse(dd, edgelattice.bottom), e)) } 70 | 71 | /** 72 | * Builds a StateLatticeElement from a list of key-value pairs. 73 | */ 74 | private def build2(q: List[(D, valuelattice.Element)]): StateLatticeElement = 75 | q.foldLeft(Map[D, valuelattice.Element]()) { case (tm, (d, v)) => tm + (d -> valuelattice.lub(tm.getOrElse(d, valuelattice.bottom), v)) } 76 | .withDefaultValue(valuelattice.bottom) 77 | 78 | /** 79 | * Transforms from Map[(A, B), C] to Map[A, Map[B, C]. 80 | */ 81 | private def curry[A, B, C](m: Map[(A, B), C]): Map[A, Map[B, C]] = 82 | m.foldLeft(Map[A, Map[B, C]]()) { case (t, ((a, b), c)) => t + (a -> (t.getOrElse(a, Map()) + (b -> c))) }.withDefaultValue(Map()) 83 | } 84 | -------------------------------------------------------------------------------- /src/tip/lattices/EdgeFunctionLattice.scala: -------------------------------------------------------------------------------- 1 | package tip.lattices 2 | 3 | /** 4 | * The lattice of edge functions, used by [[tip.solvers.IDEAnalysis]]. 5 | * A map lattice, but maps are represent differently than in `MapLattice`. 6 | * Currently only supports the identity function and constant functions. 7 | */ 8 | class EdgeFunctionLattice[L <: Lattice](val valuelattice: L) extends Lattice { 9 | 10 | type Element = EdgeFunction 11 | 12 | val bottom = ConstEdge(valuelattice.bottom) 13 | 14 | def lub(x: Element, y: Element): Element = x.joinWith(y) 15 | 16 | /** 17 | * An "edge" represents a function L -> L where L is the value lattice. 18 | */ 19 | trait EdgeFunction extends (valuelattice.Element => valuelattice.Element) { 20 | 21 | /** 22 | * Applies the function to the given lattice element. 23 | */ 24 | def apply(x: valuelattice.Element): valuelattice.Element 25 | 26 | /** 27 | * Composes this function with the given one. 28 | * The resulting function first applies `e` then this function. 29 | */ 30 | def composeWith(e: EdgeFunction): EdgeFunction 31 | 32 | /** 33 | * Finds the least upper bound of this function and the given one. 34 | */ 35 | def joinWith(e: EdgeFunction): EdgeFunction 36 | } 37 | 38 | /** 39 | * Edge labeled with identity function. 40 | */ 41 | case class IdEdge() extends EdgeFunction { 42 | 43 | def apply(x: valuelattice.Element): valuelattice.Element = x 44 | 45 | def composeWith(e: EdgeFunction): EdgeFunction = e 46 | 47 | def joinWith(e: EdgeFunction): EdgeFunction = 48 | if (e == this) this 49 | else e.joinWith(this) 50 | 51 | override def toString = "IdEdge()" 52 | } 53 | 54 | /** 55 | * Edge labeled with constant function. 56 | */ 57 | case class ConstEdge(c: valuelattice.Element) extends EdgeFunction { 58 | 59 | def apply(x: valuelattice.Element): valuelattice.Element = c 60 | 61 | def composeWith(e: EdgeFunction): EdgeFunction = this 62 | 63 | def joinWith(e: EdgeFunction): EdgeFunction = 64 | if (e == this || c == valuelattice.top) this 65 | else if (c == valuelattice.bottom) e 66 | else 67 | e match { 68 | case IdEdge() => ??? // never reached with the currently implemented analyses 69 | case ConstEdge(ec) => ConstEdge(valuelattice.lub(c, ec)) 70 | case _ => e.joinWith(this) 71 | } 72 | 73 | override def toString = s"ConstEdge($c)" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/tip/lattices/GenericLattices.scala: -------------------------------------------------------------------------------- 1 | package tip.lattices 2 | 3 | import scala.language.implicitConversions 4 | 5 | /** 6 | * A (semi-)lattice. 7 | */ 8 | trait Lattice { 9 | 10 | /** 11 | * The type of the elements of this lattice. 12 | * 13 | * To novice Scala programmers: 14 | * This is an example of an abstract type member. In this trait, `Element` is just a name for a type. 15 | * It is constrained in sub-traits and sub-classes, similarly to type parameters in generic classes. 16 | * For more information about abstract type members in Scala, see [[https://docs.scala-lang.org/tour/abstract-types.html]]. 17 | */ 18 | type Element 19 | 20 | /** 21 | * The bottom element of this lattice. 22 | */ 23 | val bottom: Element 24 | 25 | /** 26 | * The top element of this lattice. 27 | * Default: not implemented. 28 | */ 29 | def top: Element = ??? 30 | 31 | /** 32 | * The least upper bound of `x` and `y`. 33 | */ 34 | def lub(x: Element, y: Element): Element 35 | 36 | /** 37 | * Returns true whenever `x` <= `y`. 38 | */ 39 | def leq(x: Element, y: Element): Boolean = lub(x, y) == y // rarely used, but easy to implement :-) 40 | } 41 | 42 | /** 43 | * The `n`-th product lattice made of `sublattice` lattices. 44 | */ 45 | class UniformProductLattice[L <: Lattice](val sublattice: L, n: Int) extends Lattice { 46 | 47 | type Element = List[sublattice.Element] 48 | 49 | val bottom: Element = List.fill(n)(sublattice.bottom) 50 | 51 | def lub(x: Element, y: Element): Element = { 52 | if (x.length != y.length) 53 | error() 54 | (x zip y).map { case (xc, yc) => sublattice.lub(xc, yc) } 55 | } 56 | 57 | private def error() = throw new IllegalArgumentException("Products not of same length") 58 | } 59 | 60 | /** 61 | * The flat lattice made of element of `X`. 62 | * Top is greater than every other element, and Bottom is less than every other element. 63 | * No additional ordering is defined. 64 | */ 65 | class FlatLattice[X] extends Lattice { 66 | 67 | sealed trait FlatElement 68 | 69 | case class FlatEl(el: X) extends FlatElement { 70 | override def toString = el.toString 71 | } 72 | 73 | final case object Top extends FlatElement { 74 | override def toString = "Top" 75 | } 76 | 77 | final case object Bot extends FlatElement { 78 | override def toString = "Bot" 79 | } 80 | 81 | type Element = FlatElement 82 | 83 | /** 84 | * Wrap an element of `X` into an element of the flat lattice. 85 | */ 86 | implicit def wrap(a: X): Element = FlatEl(a) 87 | 88 | /** 89 | * Unwrap an element of the lattice to an element of `X`. 90 | * If the element is Top or Bot then IllegalArgumentException is thrown. 91 | * Note that this method is declared as implicit, so the conversion can be done automatically. 92 | */ 93 | implicit def unwrap(a: Element): X = a match { 94 | case FlatEl(n) => n 95 | case _ => throw new IllegalArgumentException(s"Cannot unlift $a") 96 | } 97 | 98 | val bottom: Element = Bot 99 | 100 | override val top: Element = Top 101 | 102 | def lub(x: Element, y: Element): Element = 103 | if (x == Bot || y == Top || x == y) 104 | y 105 | else if (y == Bot || x == Top) 106 | x 107 | else 108 | Top 109 | } 110 | 111 | /** 112 | * The two-element lattice containing only Top and Bot. 113 | */ 114 | class TwoElementLattice extends FlatLattice[Nothing] 115 | 116 | /** 117 | * The product lattice made by `l1` and `l2`. 118 | */ 119 | class PairLattice[L1 <: Lattice, L2 <: Lattice](val sublattice1: L1, val sublattice2: L2) extends Lattice { 120 | 121 | type Element = (sublattice1.Element, sublattice2.Element) 122 | 123 | val bottom: Element = (sublattice1.bottom, sublattice2.bottom) 124 | 125 | def lub(x: Element, y: Element): Element = (sublattice1.lub(x._1, y._1), sublattice2.lub(x._2, y._2)) 126 | } 127 | 128 | /** 129 | * A lattice of maps from a set of elements of type `A` to the lattice `sublattice`. 130 | * Bottom is the default value. 131 | */ 132 | class MapLattice[A, +L <: Lattice](val sublattice: L) extends Lattice { 133 | 134 | type Element = Map[A, sublattice.Element] 135 | 136 | val bottom: Element = Map().withDefaultValue(sublattice.bottom) 137 | 138 | def lub(x: Element, y: Element): Element = 139 | x.keys.foldLeft(y)((m, a) => m + (a -> sublattice.lub(x(a), y(a)))).withDefaultValue(sublattice.bottom) 140 | } 141 | 142 | /** 143 | * The powerset lattice of a set of elements of type `A` with subset ordering. 144 | */ 145 | class PowersetLattice[A] extends Lattice { 146 | 147 | type Element = Set[A] 148 | 149 | val bottom: Element = ??? //<--- Complete here 150 | 151 | def lub(x: Element, y: Element): Element = ??? //<--- Complete here 152 | } 153 | 154 | /** 155 | * The powerset lattice of the given set of elements of type `A` with superset ordering. 156 | */ 157 | class ReversePowersetLattice[A](s: Set[A]) extends Lattice { 158 | 159 | type Element = Set[A] 160 | 161 | val bottom: Element = s 162 | 163 | def lub(x: Element, y: Element): Element = x intersect y 164 | } 165 | 166 | /** 167 | * The lift lattice for `sublattice`. 168 | * Supports implicit lifting and unlifting. 169 | */ 170 | class LiftLattice[+L <: Lattice](val sublattice: L) extends Lattice { 171 | 172 | type Element = Lifted 173 | 174 | sealed trait Lifted 175 | 176 | case object Bottom extends Lifted { 177 | override def toString = "LiftBot" 178 | } 179 | 180 | case class Lift(n: sublattice.Element) extends Lifted 181 | 182 | val bottom: Element = Bottom 183 | 184 | def lub(x: Element, y: Element): Element = 185 | (x, y) match { 186 | case (Bottom, t) => t 187 | case (t, Bottom) => t 188 | case (Lift(a), Lift(b)) => Lift(sublattice.lub(a, b)) 189 | } 190 | 191 | /** 192 | * Lift elements of the sublattice to this lattice. 193 | * Note that this method is declared as implicit, so the conversion can be done automatically. 194 | */ 195 | implicit def lift(x: sublattice.Element): Element = Lift(x) 196 | 197 | /** 198 | * Un-lift elements of this lattice to the sublattice. 199 | * Throws an IllegalArgumentException if trying to unlift the bottom element 200 | * Note that this method is declared as implicit, so the conversion can be done automatically. 201 | */ 202 | implicit def unlift(x: Element): sublattice.Element = x match { 203 | case Lift(s) => s 204 | case Bottom => throw new IllegalArgumentException("Cannot unlift bottom") 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/tip/lattices/LatticeWithOps.scala: -------------------------------------------------------------------------------- 1 | package tip.lattices 2 | 3 | /** 4 | * Lattice with abstract operators. 5 | */ 6 | trait LatticeWithOps extends Lattice { 7 | 8 | /** 9 | * Abstract number. 10 | */ 11 | def num(i: Int): Element 12 | 13 | /** 14 | * Abstract plus. 15 | */ 16 | def plus(a: Element, b: Element): Element 17 | 18 | /** 19 | * Abstract minus. 20 | */ 21 | def minus(a: Element, b: Element): Element 22 | 23 | /** 24 | * Abstract times. 25 | */ 26 | def times(a: Element, b: Element): Element 27 | 28 | /** 29 | * Abstract division. 30 | */ 31 | def div(a: Element, b: Element): Element 32 | 33 | /** 34 | * Abstract equals. 35 | */ 36 | def eqq(a: Element, b: Element): Element 37 | 38 | /** 39 | * Abstract greater-than. 40 | */ 41 | def gt(a: Element, b: Element): Element 42 | } 43 | -------------------------------------------------------------------------------- /src/tip/lattices/SignLattice.scala: -------------------------------------------------------------------------------- 1 | package tip.lattices 2 | 3 | /** 4 | * An element of the sign lattice. 5 | */ 6 | object SignElement extends Enumeration { 7 | val Pos, Neg, Zero = Value 8 | } 9 | 10 | /** 11 | * The sign lattice. 12 | */ 13 | object SignLattice extends FlatLattice[SignElement.Value] with LatticeWithOps { 14 | 15 | import SignElement._ 16 | 17 | private val signValues: Map[Element, Int] = Map(Bot -> 0, FlatEl(Zero) -> 1, FlatEl(Neg) -> 2, FlatEl(Pos) -> 3, Top -> 4) 18 | 19 | private def lookup(op: List[List[Element]], x: Element, y: Element): Element = 20 | op(signValues(x))(signValues(y)) 21 | 22 | private val absPlus: List[List[Element]] = 23 | List( 24 | List(Bot, Bot, Bot, Bot, Bot), 25 | List(Bot, Zero, Neg, Pos, Top), 26 | List(Bot, Neg, Neg, Top, Top), 27 | List(Bot, Pos, Top, Pos, Top), 28 | List(Bot, Top, Top, Top, Top) 29 | ) 30 | 31 | private val absMinus: List[List[Element]] = 32 | List( 33 | List(Bot, Bot, Bot, Bot, Bot), 34 | List(Bot, Zero, Pos, Neg, Top), 35 | List(Bot, Neg, Top, Neg, Top), 36 | List(Bot, Pos, Pos, Top, Top), 37 | List(Bot, Top, Top, Top, Top) 38 | ) 39 | 40 | private val absTimes: List[List[Element]] = 41 | List( 42 | List(Bot, Bot, Bot, Bot, Bot), 43 | List(Bot, Zero, Zero, Zero, Zero), 44 | List(Bot, Zero, Pos, Neg, Top), 45 | List(Bot, Zero, Neg, Pos, Top), 46 | List(Bot, Zero, Top, Top, Top) 47 | ) 48 | 49 | private val absDivide: List[List[Element]] = 50 | List( 51 | List(Bot, Bot, Bot, Bot, Bot), 52 | List(Bot, Bot, Zero, Zero, Top), 53 | List(Bot, Bot, Top, Top, Top), 54 | List(Bot, Bot, Top, Top, Top), 55 | List(Bot, Bot, Top, Top, Top) 56 | ) 57 | 58 | private val absGt: List[List[Element]] = 59 | List( 60 | List(Bot, Bot, Bot, Bot, Bot), 61 | List(Bot, Zero, Pos, Zero, Top), 62 | List(Bot, Zero, Top, Zero, Top), 63 | List(Bot, Pos, Pos, Top, Top), 64 | List(Bot, Top, Top, Top, Top) 65 | ) 66 | 67 | private val absEq: List[List[Element]] = 68 | List( 69 | List(Bot, Bot, Bot, Bot, Bot), 70 | List(Bot, Pos, Zero, Zero, Top), 71 | List(Bot, Zero, Top, Zero, Top), 72 | List(Bot, Zero, Zero, Top, Top), 73 | List(Bot, Top, Top, Top, Top) 74 | ) 75 | 76 | def num(i: Int): Element = 77 | if (i == 0) 78 | Zero 79 | else if (i > 0) 80 | Pos 81 | else 82 | Neg 83 | 84 | def plus(a: Element, b: Element): Element = lookup(absPlus, a, b) 85 | 86 | def minus(a: Element, b: Element): Element = lookup(absMinus, a, b) 87 | 88 | def times(a: Element, b: Element): Element = lookup(absTimes, a, b) 89 | 90 | def div(a: Element, b: Element): Element = lookup(absDivide, a, b) 91 | 92 | def eqq(a: Element, b: Element): Element = lookup(absEq, a, b) 93 | 94 | def gt(a: Element, b: Element): Element = lookup(absGt, a, b) 95 | } 96 | -------------------------------------------------------------------------------- /src/tip/solvers/IDEAnalysis.scala: -------------------------------------------------------------------------------- 1 | package tip.solvers 2 | 3 | import tip.cfg.{CfgAfterCallNode, CfgCallNode, CfgFunEntryNode, CfgFunExitNode, CfgNode, InterproceduralProgramCfg} 4 | import tip.lattices.{EdgeFunctionLattice, Lattice} 5 | 6 | /** 7 | * The special item representing the empty element in IDE. 8 | */ 9 | final case class Lambda() 10 | 11 | /** 12 | * Base trait for IDE analyses. 13 | * @tparam D the type of items 14 | * @tparam L the type of the value lattice 15 | */ 16 | trait IDEAnalysis[D, L <: Lattice] { 17 | 18 | val cfg: InterproceduralProgramCfg 19 | 20 | type DL = Either[D, Lambda] 21 | 22 | /** 23 | * The value lattice. 24 | */ 25 | val valuelattice: L 26 | 27 | /** 28 | * The edge lattice. 29 | */ 30 | val edgelattice: EdgeFunctionLattice[valuelattice.type] 31 | 32 | /** 33 | * Edges for call-to-entry. 34 | */ 35 | def edgesCallToEntry(call: CfgCallNode, entry: CfgFunEntryNode)(d: DL): Map[DL, edgelattice.Element] 36 | 37 | /** 38 | * Edges for exit-to-aftercall. 39 | */ 40 | def edgesExitToAfterCall(exit: CfgFunExitNode, aftercall: CfgAfterCallNode)(d: DL): Map[DL, edgelattice.Element] 41 | 42 | /** 43 | * Edges for call-to-aftercall. 44 | */ 45 | def edgesCallToAfterCall(call: CfgCallNode, aftercall: CfgAfterCallNode)(d: DL): Map[DL, edgelattice.Element] 46 | 47 | /** 48 | * Edges for other CFG nodes. 49 | */ 50 | def edgesOther(n: CfgNode)(d: DL): Map[DL, edgelattice.Element] 51 | } 52 | -------------------------------------------------------------------------------- /src/tip/solvers/SimpleCubicSolver.scala: -------------------------------------------------------------------------------- 1 | package tip.solvers 2 | 3 | import tip.util.Log 4 | 5 | import scala.collection.mutable 6 | import scala.language.implicitConversions 7 | 8 | /** 9 | * Simple cubic solver. 10 | * 11 | * @tparam V type of variables 12 | * @tparam T type of tokens 13 | */ 14 | class SimpleCubicSolver[V, T] { 15 | 16 | private val log = Log.logger[this.type]() 17 | 18 | private var lastTknId: Int = -1 19 | 20 | private def nextTokenId: Int = { 21 | lastTknId += 1; lastTknId 22 | } 23 | 24 | private class Node( 25 | val succ: mutable.Set[V] = mutable.Set(), // note: the edges between nodes go via the variables 26 | val tokenSol: mutable.BitSet = new mutable.BitSet(), // the current solution bitvector 27 | val conditionals: mutable.Map[Int, mutable.Set[(V, V)]] = mutable.Map() // the pending conditional constraints 28 | ) 29 | 30 | /** 31 | * The map from variables to nodes. 32 | */ 33 | private val varToNode: mutable.Map[V, Node] = mutable.Map() 34 | 35 | /** 36 | * Provides an index for each token that we have seen. 37 | */ 38 | private val tokenToInt: mutable.Map[T, Int] = mutable.Map() 39 | 40 | /** 41 | * Worklist of (token, variable) pairs. 42 | */ 43 | private val worklist: mutable.Queue[(Int, V)] = mutable.Queue() 44 | 45 | /** 46 | * Returns the index associated with the given token. 47 | * Allocates a fresh index if the token hasn't been seen before. 48 | */ 49 | implicit private def getTokenInt(tkn: T): Int = 50 | tokenToInt.getOrElseUpdate(tkn, nextTokenId) 51 | 52 | /** 53 | * Retrieves the node associated with the given variable. 54 | * Allocates a fresh node if the variable hasn't been seen before. 55 | */ 56 | private def getOrMakeNode(x: V): Node = 57 | varToNode.getOrElseUpdate(x, new Node) 58 | 59 | /** 60 | * Adds a token to the solution for a variable. 61 | */ 62 | private def addToken(t: Int, x: V): Unit = 63 | if (getOrMakeNode(x).tokenSol.add(t)) 64 | worklist += ((t, x)) 65 | 66 | /** 67 | * Adds an inclusion edge. 68 | */ 69 | private def addEdge(x: V, y: V): Unit = 70 | if (x != y) { 71 | val nx = getOrMakeNode(x) 72 | if (nx.succ.add(y)) { 73 | getOrMakeNode(y) 74 | log.verb(s"Adding edge \u27E6$x\u27E7 -> \u27E6$y\u27E7") 75 | for (t <- nx.tokenSol) 76 | addToken(t, y) 77 | } 78 | } 79 | 80 | /** 81 | * Processes items in the worklist. 82 | */ 83 | private def propagate(): Unit = 84 | while (worklist.nonEmpty) { 85 | // Pick element from worklist 86 | val (t, x) = worklist.dequeue() 87 | // Process pending constraints 88 | val nx = getOrMakeNode(x) 89 | nx.conditionals.remove(t).foreach { s => 90 | for ((y, z) <- s) 91 | addEdge(y, z) 92 | } 93 | // Propagate token to successors 94 | for (v <- nx.succ) 95 | addToken(t, v) 96 | } 97 | 98 | /** 99 | * Adds a constraint of type t∈⟦x⟧. 100 | */ 101 | def addConstantConstraint(t: T, x: V): Unit = { 102 | log.verb(s"Adding constraint $t \u2208 \u27E6$x\u27E7") 103 | addToken(t, x) 104 | propagate() 105 | } 106 | 107 | /** 108 | * Adds a constraint of type ⟦x⟧⊆⟦y⟧. 109 | */ 110 | def addSubsetConstraint(x: V, y: V): Unit = { 111 | log.verb(s"Adding constraint \u27E6$x\u27E7 \u2286 \u27E6$y\u27E7") 112 | addEdge(x, y) 113 | propagate() 114 | } 115 | 116 | /** 117 | * Adds a constraint of type t∈⟦x⟧⇒⟦y⟧⊆⟦z⟧. 118 | */ 119 | def addConditionalConstraint(t: T, x: V, y: V, z: V): Unit = { 120 | log.verb(s"Adding constraint $t \u2208 \u27E6$x\u27E7 \u21D2 \u27E6$y\u27E7 \u2286 \u27E6$z\u27E7") 121 | val xn = getOrMakeNode(x) 122 | if (xn.tokenSol.contains(t)) { 123 | // Already enabled 124 | addSubsetConstraint(y, z) 125 | } else if (y != z) { 126 | // Not yet enabled, add to pending list 127 | log.verb(s"Condition $t \u2208 \u27E6$x\u27E7 not yet enabled, adding (\u27E6$y\u27E7,\u27E6$z\u27E7) to pending") 128 | xn.conditionals 129 | .getOrElseUpdate(t, mutable.Set[(V, V)]()) 130 | .add((y, z)) 131 | } 132 | } 133 | 134 | /** 135 | * Returns the current solution as a map from variables to token sets. 136 | */ 137 | def getSolution: Map[V, Set[T]] = { 138 | val intToToken = tokenToInt.map(p => p._2 -> p._1).toMap[Int, T] 139 | varToNode.keys.map(v => v -> getOrMakeNode(v).tokenSol.map(intToToken).toSet).toMap 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/tip/solvers/SpecialCubicSolver.scala: -------------------------------------------------------------------------------- 1 | package tip.solvers 2 | 3 | import tip.util.Log 4 | 5 | import scala.collection.mutable 6 | import scala.language.implicitConversions 7 | 8 | /** 9 | * Special cubic solver. 10 | * 11 | * @tparam V type of variables (tokens are a subset of variables) 12 | */ 13 | class SpecialCubicSolver[V] { 14 | 15 | private val log = Log.logger[this.type]() 16 | 17 | private var lastTknId: Int = -1 18 | 19 | private def nextTokenId: Int = { 20 | lastTknId += 1; lastTknId 21 | } 22 | 23 | private class Node( 24 | val succ: mutable.Set[V] = mutable.Set(), // note: the edges between nodes go via the variables 25 | val tokenSol: mutable.BitSet = new mutable.BitSet(), // the current solution bitvector 26 | val fromTriggers: mutable.Set[V] = mutable.Set(), // universally quantified constraints from the current node 27 | val toTriggers: mutable.Set[V] = mutable.Set() // universally quantified constraints to the current node 28 | ) 29 | 30 | /** 31 | * The map from variables to nodes. 32 | */ 33 | private val varToNode: mutable.Map[V, Node] = mutable.Map() 34 | 35 | /** 36 | * Provides an index for each token that we have seen. 37 | */ 38 | private val tokenToInt: mutable.Map[V, Int] = mutable.Map() 39 | 40 | /** 41 | * Maps each token index to the corresponding token. 42 | */ 43 | private val intToToken: mutable.ArrayBuffer[V] = mutable.ArrayBuffer() 44 | 45 | /** 46 | * Worklist of (token, variable) pairs. 47 | */ 48 | private val worklist: mutable.Queue[(Int, V)] = mutable.Queue() 49 | 50 | /** 51 | * Returns the index associated with the given token. 52 | * Allocates a fresh index if the token hasn't been seen before. 53 | */ 54 | implicit private def getTokenInt(tkn: V): Int = 55 | tokenToInt.getOrElseUpdate(tkn, { intToToken += tkn; nextTokenId }) 56 | 57 | /** 58 | * Retrieves the node associated with the given variable. 59 | * Allocates a fresh node if the variable hasn't been seen before. 60 | */ 61 | private def getOrMakeNode(x: V): Node = 62 | varToNode.getOrElseUpdate(x, new Node) 63 | 64 | /** 65 | * Adds a token to the solution for a variable. 66 | */ 67 | private def addToken(t: Int, x: V): Unit = 68 | if (getOrMakeNode(x).tokenSol.add(t)) 69 | worklist += ((t, x)) 70 | 71 | /** 72 | * Adds an inclusion edge. 73 | */ 74 | private def addEdge(x: V, y: V): Unit = 75 | if (x != y) { 76 | val nx = getOrMakeNode(x) 77 | if (nx.succ.add(y)) { 78 | getOrMakeNode(y) 79 | log.verb(s"Adding edge \u27E6$x\u27E7 -> \u27E6$y\u27E7") 80 | for (t <- nx.tokenSol) 81 | addToken(t, y) 82 | } 83 | } 84 | 85 | /** 86 | * Processes items in the worklist. 87 | */ 88 | private def propagate(): Unit = 89 | while (worklist.nonEmpty) { 90 | // Pick element from worklist 91 | val (t, x) = worklist.dequeue() 92 | val tkn = intToToken(t) 93 | val nx = varToNode(x) 94 | // Process quantified constraints 95 | for (y <- nx.fromTriggers) 96 | addEdge(tkn, y) 97 | for (y <- nx.toTriggers) 98 | addEdge(y, tkn) 99 | // Propagate token to successors 100 | for (v <- nx.succ) 101 | addToken(t, v) 102 | } 103 | 104 | /** 105 | * Adds a constraint of type t∈⟦x⟧. 106 | */ 107 | def addConstantConstraint(t: V, x: V): Unit = { 108 | log.verb(s"Adding constraint $t \u2208 \u27E6$x\u27E7") 109 | addToken(t, x) 110 | propagate() 111 | } 112 | 113 | /** 114 | * Adds a constraint of type ⟦x⟧⊆⟦y⟧. 115 | */ 116 | def addSubsetConstraint(x: V, y: V): Unit = { 117 | log.verb(s"Adding constraint \u27E6$x\u27E7 \u2286 \u27E6$y\u27E7") 118 | addEdge(x, y) 119 | propagate() 120 | } 121 | 122 | /** 123 | * Adds a constraint of type ∀t∈⟦x⟧: ⟦t⟧⊆⟦y⟧. 124 | */ 125 | def addUniversallyQuantifiedFromConstraint(x: V, y: V): Unit = { 126 | log.verb(s"Adding constraint \u2200t \u2208 \u27E6$x\u27E7: \u27E6t\u27E7 \u2286 \u27E6$y\u27E7") 127 | val xn = getOrMakeNode(x) 128 | xn.fromTriggers += y 129 | for (t <- xn.tokenSol) 130 | addEdge(intToToken(t), y) 131 | propagate() 132 | } 133 | 134 | /** 135 | * Adds a constraint of type ∀t∈⟦x⟧: ⟦y⟧⊆⟦t⟧. 136 | */ 137 | def addUniversallyQuantifiedToConstraint(x: V, y: V): Unit = { 138 | log.verb(s"Adding constraint \u2200t \u2208 \u27E6$x\u27E7: \u27E6$y\u27E7 \u2286 \u27E6t\u27E7") 139 | val xn = getOrMakeNode(x) 140 | xn.toTriggers += y 141 | for (t <- xn.tokenSol) 142 | addEdge(y, intToToken(t)) 143 | propagate() 144 | } 145 | 146 | /** 147 | * Returns the current solution as a map from variables to token sets. 148 | */ 149 | def getSolution: Map[V, Set[V]] = 150 | varToNode.keys.map(v => v -> getOrMakeNode(v).tokenSol.map(intToToken).toSet).toMap 151 | } 152 | -------------------------------------------------------------------------------- /src/tip/solvers/SummarySolver.scala: -------------------------------------------------------------------------------- 1 | package tip.solvers 2 | 3 | import tip.analysis.{FlowSensitiveAnalysis, ForwardDependencies} 4 | import tip.ast.AstNodeData.DeclarationData 5 | import tip.cfg._ 6 | import tip.lattices.{EdgeEnvLattice, Lattice, LiftLattice, MapLattice} 7 | 8 | import scala.collection.immutable.Set 9 | 10 | /** 11 | * A tabulation solver, variant of IDESolver. 12 | * @tparam D the type of items 13 | * @tparam L the type of the value lattice 14 | */ 15 | abstract class SummarySolver[D, L <: Lattice](val cfg: InterproceduralProgramCfg)(implicit declData: DeclarationData) 16 | extends FlowSensitiveAnalysis(false) 17 | with IDEAnalysis[D, L] { 18 | 19 | class Phase1(val cfg: InterproceduralProgramCfg) extends WorklistFixpointPropagationSolver[CfgNode] with ForwardDependencies { 20 | 21 | val statelattice = new EdgeEnvLattice[D, valuelattice.type, edgelattice.type](edgelattice) 22 | 23 | val lattice = new MapLattice[CfgNode, LiftLattice[statelattice.type]](new LiftLattice(statelattice)) 24 | 25 | import cfg._ 26 | import lattice.sublattice.{lift, Lift} 27 | import edgelattice.IdEdge 28 | 29 | /** 30 | * Initialize worklist with the program entry point and the identity function. 31 | */ 32 | val first = Set(programEntry) 33 | 34 | override val init = lift(Map((Right(Lambda()), Right(Lambda())) -> IdEdge())) 35 | 36 | import statelattice._ 37 | 38 | /** 39 | * Transfer functions. 40 | */ 41 | override def transferUnlifted(n: CfgNode, s: Element): Element = 42 | n match { 43 | case call: CfgCallNode => 44 | for (entry <- call.callees) { 45 | val s2 = propagateReturn(entry.exit, call.afterCallNode) 46 | val s3 = s2.map { case ((_, d), _) => (d, d) -> IdEdge() } 47 | propagate(s3, entry) 48 | } 49 | compose2(s, edgesCallToAfterCall(call, call.afterCallNode)) 50 | case funexit: CfgFunExitNode => 51 | for (aftercall <- funexit.callersAfterCall) 52 | propagateReturn(funexit, aftercall) 53 | bottom // no successors for this kind of node, but we have to return something 54 | case _ => 55 | compose2(s, edgesOther(n)) 56 | } 57 | 58 | private def propagateReturn(funexit: CfgFunExitNode, aftercall: CfgAfterCallNode): Element = 59 | x(aftercall.callNode) match { 60 | case Lift(s1) => 61 | val s2 = compose2(s1, edgesCallToEntry(aftercall.callNode, funexit.entry)) 62 | x(funexit) match { 63 | case Lift(s) => 64 | val s3 = compose(s2, s) 65 | val s4 = compose2(s3, edgesExitToAfterCall(funexit, aftercall)) 66 | propagate(lift(s4), aftercall) 67 | case _ => 68 | } 69 | s2 70 | case _ => 71 | statelattice.bottom 72 | } 73 | } 74 | 75 | class Phase2(val cfg: InterproceduralProgramCfg, val phase1: Phase1) extends WorklistFixpointPropagationFunctions[CfgNode] { 76 | 77 | val statelattice = new MapLattice[D, valuelattice.type](valuelattice) 78 | 79 | val lattice = new MapLattice[CfgNode, statelattice.type](statelattice) 80 | 81 | import cfg._ 82 | import phase1.lattice.sublattice._ 83 | 84 | var x: lattice.Element = _ 85 | 86 | val first = Set(programEntry) 87 | 88 | val init = statelattice.bottom 89 | 90 | def process(n: CfgNode): Unit = { 91 | val s = x(n) 92 | n match { 93 | case funentry: CfgFunEntryNode => 94 | for (call <- callsInFunction(funentry)) 95 | propagate(sublattice.apply(unlift(phase1.x(call)), s), call) 96 | case call: CfgCallNode => 97 | for (funentry <- call.callees) 98 | propagate(sublattice.apply(edgesCallToEntry(call, funentry) _, s), funentry) 99 | case _ => 100 | } 101 | } 102 | 103 | override def analyze(): lattice.Element = { 104 | super.analyze() 105 | nodes.foreach { 106 | case _: CfgFunEntryNode | _: CfgCallNode => // already processed 107 | case n => 108 | x += n -> sublattice.apply(unlift(phase1.x(n)), x(enclosingFunctionEntry(n))) 109 | } 110 | x 111 | } 112 | } 113 | 114 | def analyze(): Map[CfgNode, Map[D, valuelattice.Element]] = { 115 | FixpointSolvers.log.verb(s"Summary pre-analysis") 116 | val p1 = new Phase1(cfg) 117 | val res1 = p1.analyze() 118 | FixpointSolvers.log.verb(s"Result from pre-analysis:\n ${res1.mkString("\n ")}") 119 | FixpointSolvers.log.verb(s"Main analysis") 120 | val p2 = new Phase2(cfg, p1) 121 | val res2 = p2.analyze() 122 | FixpointSolvers.log.verb(s"Result from main analysis:\n ${res2.mkString("\n ")}") 123 | res2 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/tip/solvers/UnificationTerms.scala: -------------------------------------------------------------------------------- 1 | package tip.solvers 2 | 3 | import scala.collection.mutable 4 | 5 | /** 6 | * A generic term: a variable [[Var]], a constructor [[Cons]], or a recursive term [[Mu]]. 7 | * 8 | * @tparam A type parameter describing the kind of constraint system 9 | */ 10 | sealed trait Term[A] { 11 | 12 | /** 13 | * Returns the set of free variables in this term. 14 | */ 15 | def fv: Set[Var[A]] 16 | 17 | /** 18 | * Produces a new term from this term by substituting the variable `v` with the term `t`. 19 | */ 20 | def subst(v: Var[A], t: Term[A]): Term[A] 21 | } 22 | 23 | /** 24 | * A constraint variable. 25 | */ 26 | trait Var[A] extends Term[A] { 27 | 28 | val fv: Set[Var[A]] = Set(this) 29 | 30 | def subst(v: Var[A], t: Term[A]): Term[A] = 31 | if (v == this) t else this 32 | } 33 | 34 | /** 35 | * An n-ary term constructor. 36 | * 0-ary constructors are constants. 37 | */ 38 | trait Cons[A] extends Term[A] { 39 | 40 | /** 41 | * The sub-terms. 42 | */ 43 | val args: List[Term[A]] 44 | 45 | /** 46 | * The arity of the constructor. 47 | */ 48 | def arity: Int = args.length 49 | 50 | lazy val fv: Set[Var[A]] = args.flatMap(_.fv).toSet 51 | 52 | /** 53 | * Checks whether the term `t` matches this term, meaning that it has the same constructor class and the same arity. 54 | */ 55 | def doMatch(t: Term[A]): Boolean = 56 | this.getClass == t.getClass && arity == t.asInstanceOf[Cons[A]].arity 57 | } 58 | 59 | /** 60 | * Recursive term. 61 | * Whenever a term is such that v = t[v] where v appears free in t[v], then we represent it finitely as \u03bc v. t[v]. 62 | * v is a binder in the term, and the copy rule holds: \u03bc v. t[v] == t [ \u03bc v. t[v] ] 63 | */ 64 | trait Mu[A] extends Term[A] { 65 | 66 | /** 67 | * The variable. 68 | */ 69 | val v: Var[A] 70 | 71 | /** 72 | * The term. 73 | */ 74 | val t: Term[A] 75 | 76 | lazy val fv: Set[Var[A]] = t.fv - v 77 | 78 | override def toString: String = s"\u03bc$v.$t" 79 | } 80 | 81 | /** 82 | * Special operations on terms. 83 | */ 84 | trait TermOps[A] { 85 | 86 | /** 87 | * Constructor for [[tip.solvers.Mu]] terms. 88 | */ 89 | def makeMu(v: Var[A], t: Term[A]): Mu[A] 90 | 91 | /** 92 | * Constructor for fresh term variables. 93 | */ 94 | def makeFreshVar(): Var[A] 95 | 96 | /** 97 | * Closes the term by replacing each free variable with its value in the given environment. 98 | * Whenever a recursive term is detected, a [[Mu]] term is generated. 99 | * Remaining free variables are replaced by fresh variables that are implicitly universally quantified. 100 | * 101 | * @param t the term to close 102 | * @param env environment, map from term variables to terms 103 | * @param freshvars map from recursive and unconstrained term variables to fresh term variables 104 | */ 105 | def close(t: Term[A], env: Map[Var[A], Term[A]], freshvars: mutable.Map[Var[A], Var[A]]): Term[A] = { 106 | 107 | def closeRec(t: Term[A], visited: Set[Var[A]] = Set()): Term[A] = 108 | t match { 109 | case v: Var[A] => 110 | if (!visited.contains(v) && env(v) != v) { 111 | // no recursion found, and the variable does not map to itself 112 | val cterm = closeRec(env(v), visited + v) 113 | val f = freshvars.get(v) 114 | if (f.isDefined && cterm.fv.contains(f.get)) { 115 | // recursive term found, make a [[Mu]] 116 | makeMu(f.get, cterm.subst(v, f.get)) 117 | } else 118 | cterm 119 | } else { 120 | // recursive or unconstrained term variables, make a fresh term variable 121 | freshvars.getOrElse(v, { 122 | val w = makeFreshVar() 123 | freshvars += v -> w 124 | w 125 | }) 126 | } 127 | case c: Cons[A] => 128 | // substitute each free variable with its closed term 129 | c.fv.foldLeft(t) { (acc, v) => 130 | acc.subst(v, closeRec(v, visited)) 131 | } 132 | case m: Mu[A] => 133 | makeMu(m.v, closeRec(m.t, visited)) 134 | } 135 | 136 | closeRec(t) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/tip/solvers/UnionFindSolver.scala: -------------------------------------------------------------------------------- 1 | package tip.solvers 2 | 3 | import tip.util.Log 4 | 5 | import scala.collection.mutable 6 | 7 | /** 8 | * Unification solver based on union-find. 9 | * @tparam A type parameter describing the kind of constraint system 10 | */ 11 | class UnionFindSolver[A] { 12 | 13 | val log = Log.logger[this.type]() 14 | 15 | /** 16 | * This map holds the graph structure for the union-find algorithm. 17 | * Each term has a "parent". Two terms are equivalent (unified) if one is reachable from the other along zero or more parent links. 18 | * The parent of a term may be the term itself, in which case it is the representative for its equivalence class. 19 | */ 20 | private val parent = mutable.Map[Term[A], Term[A]]() 21 | 22 | /** 23 | * Performs the unification of the two terms `t1` and `t2`. 24 | * When unifying a variable and a non-variable term, the non-variable term has higher priority for becoming the representative. 25 | */ 26 | def unify(t1: Term[A], t2: Term[A]): Unit = { 27 | log.verb(s"Unifying $t1 and $t2") 28 | 29 | mkSet(t1) 30 | mkSet(t2) 31 | val rep1 = find(t1) 32 | val rep2 = find(t2) 33 | 34 | if (rep1 == rep2) return 35 | 36 | (rep1, rep2) match { 37 | case (v1: Var[A], v2: Var[A]) => 38 | mkUnion(v1, v2) 39 | case (v1: Var[A], t2: Term[A]) => 40 | mkUnion(v1, t2) 41 | case (t1: Term[A], v2: Var[A]) => 42 | mkUnion(v2, t1) 43 | case (f1: Cons[A], f2: Cons[A]) if f1.doMatch(f2) => 44 | mkUnion(f1, f2) 45 | f1.args.zip(f2.args).foreach { 46 | case (a1, a2) => 47 | log.verb(s"Unifying subterms $a1 and $a2") 48 | unify(a1, a2) 49 | } 50 | case (x, y) => 51 | throw new UnificationFailure(s"Cannot unify $t1 and $t2 (with representatives $x and $y)") 52 | } 53 | } 54 | 55 | /** 56 | * Returns the canonical element of the equivalence class of the term `t`. 57 | * The term is added as a new equivalence class if it has not been encountered before. 58 | * Uses path compression. 59 | */ 60 | def find(t: Term[A]): Term[A] = { 61 | mkSet(t) 62 | if (parent(t) != t) 63 | parent += t -> find(parent(t)) 64 | parent(t) 65 | } 66 | 67 | /** 68 | * Perform the union of the equivalence classes of `t1` and `t2`, such that `t2` becomes the new canonical element. 69 | * We assume `t1` and `t2` to be distinct canonical elements. 70 | * This implementation does not use [[https://en.wikipedia.org/wiki/Disjoint-set_data_structure union-by-rank]]. 71 | */ 72 | private def mkUnion(t1: Term[A], t2: Term[A]): Unit = 73 | parent += t1 -> t2 74 | 75 | /** 76 | * Creates an equivalence class for the term `t`, if it does not exists already. 77 | */ 78 | private def mkSet(t: Term[A]): Unit = 79 | if (!parent.contains(t)) 80 | parent += (t -> t) 81 | 82 | /** 83 | * Returns the solution of the solver. 84 | * Note that the terms in the solution have not yet been closed, i.e. they may contain constraint variables. 85 | * @return a map associating to each variable the representative of its equivalence class 86 | */ 87 | def solution(): Map[Var[A], Term[A]] = 88 | // for each constraint variable, find its canonical representative (using the variable itself as default) 89 | parent.keys.collect { case v: Var[A] => (v, find(v)) }.toMap.withDefault(v => v) 90 | 91 | /** 92 | * Returns all the unifications of the solution. 93 | * @return a map from representative to equivalence class 94 | */ 95 | def unifications(): Map[Term[A], Traversable[Term[A]]] = 96 | parent.keys.groupBy(find).withDefault(Set(_)) 97 | 98 | /** 99 | * Produces a string representation of the solution. 100 | */ 101 | override def toString = 102 | solution().map(p => s"${p._1} = ${p._2}").mkString("\n") 103 | } 104 | 105 | /** 106 | * Exception thrown in case of unification failure. 107 | */ 108 | class UnificationFailure(message: String = null) extends RuntimeException(message) 109 | -------------------------------------------------------------------------------- /src/tip/types/Types.scala: -------------------------------------------------------------------------------- 1 | package tip.types 2 | 3 | import tip.ast._ 4 | import tip.solvers._ 5 | import tip.ast.AstNodeData._ 6 | import scala.language.implicitConversions 7 | 8 | object Type { 9 | 10 | /** 11 | * Implicitly converts any AstNode to its type variable. 12 | * For identifiers the type variable is associated with the declaration; 13 | * for any other kind of AST node the type variable is associated with the node itself. 14 | * 15 | * To novice Scala programmers: 16 | * The keyword `implicit` in front of this function definition enables "implicit conversion" from `AstNode` to `Var[TipType]`. 17 | * This means that whenever Scala finds something of type `AstNode` but needs something of type `Var[TipType]`, this 18 | * function will be invoked implicitly to make the conversion (provided that the function is imported). 19 | * For more information about implicit conversions in Scala, see [[https://docs.scala-lang.org/tour/implicit-conversions.html]]. 20 | */ 21 | implicit def ast2typevar(node: AstNode)(implicit declData: DeclarationData): Var[Type] = 22 | node match { 23 | case id: AIdentifier => VarType(declData(id)) 24 | case _ => VarType(node) 25 | } 26 | 27 | implicit def ast2typevar(nodes: List[AstNode])(implicit declData: DeclarationData): List[Var[Type]] = 28 | nodes.map(ast2typevar) 29 | } 30 | 31 | /** 32 | * Counter for producing fresh IDs. 33 | */ 34 | object Fresh { 35 | 36 | var n = 0 37 | 38 | def next(): Int = { 39 | n += 1 40 | n 41 | } 42 | } 43 | 44 | /** 45 | * A type for a TIP variable or expression. 46 | */ 47 | sealed trait Type 48 | 49 | object TipTypeOps extends TermOps[Type] { 50 | 51 | def makeFreshVar(): Var[Type] = FreshVarType() 52 | 53 | def makeMu(v: Var[Type], t: Term[Type]): Mu[Type] = RecursiveType(v, t) 54 | } 55 | 56 | /** 57 | * Int type. 58 | */ 59 | case class IntType() extends Type with Cons[Type] { 60 | 61 | val args: List[Term[Type]] = List() 62 | 63 | def subst(v: Var[Type], t: Term[Type]): Term[Type] = this 64 | 65 | override def toString: String = "int" 66 | } 67 | 68 | /** 69 | * Function type. 70 | */ 71 | case class FunctionType(params: List[Term[Type]], ret: Term[Type]) extends Type with Cons[Type] { 72 | 73 | val args: List[Term[Type]] = ret :: params 74 | 75 | def subst(v: Var[Type], t: Term[Type]): Term[Type] = 76 | FunctionType(params.map { p => 77 | p.subst(v, t) 78 | }, ret.subst(v, t)) 79 | 80 | override def toString: String = s"(${params.mkString(",")}) -> $ret" 81 | } 82 | 83 | /** 84 | * Pointer type. 85 | */ 86 | case class PointerType(of: Term[Type]) extends Type with Cons[Type] { 87 | 88 | val args: List[Term[Type]] = List(of) 89 | 90 | def subst(v: Var[Type], t: Term[Type]): Term[Type] = PointerType(of.subst(v, t)) 91 | 92 | override def toString: String = s"\u2B61$of" 93 | } 94 | 95 | /** 96 | * Record type. 97 | * 98 | * A record type is represented as a term with a sub-term for every field name in the entire program. 99 | * (This could be represented more concisely by using AbsentFieldType as a default.) 100 | */ 101 | case class RecordType(args: List[Term[Type]])(implicit allFieldNames: List[String]) extends Type with Cons[Type] { 102 | 103 | def subst(v: Var[Type], t: Term[Type]): Term[Type] = 104 | RecordType(args.map { p => 105 | p.subst(v, t) 106 | }) 107 | 108 | override def toString: String = 109 | s"{${allFieldNames 110 | .zip(args) 111 | .map(p => { 112 | s"${p._1}:${p._2}" 113 | }) 114 | .mkString(",")}}" 115 | } 116 | 117 | case object AbsentFieldType extends Type with Cons[Type] { 118 | 119 | val args: List[Term[Type]] = List() 120 | 121 | def subst(v: Var[Type], t: Term[Type]): Term[Type] = this 122 | 123 | override def toString: String = "\u25C7" 124 | } 125 | 126 | /** 127 | * Type variable for a program variable or expression. 128 | */ 129 | case class VarType(node: AstNode) extends Type with Var[Type] { 130 | 131 | require(!node.isInstanceOf[AIdentifier], "Trying to construct type variable for identifier expression"); 132 | 133 | override def toString: String = s"\u27E6$node\u27E7" 134 | } 135 | 136 | /** 137 | * Fresh type variable. 138 | */ 139 | case class FreshVarType(var id: Int = 0) extends Type with Var[Type] { 140 | 141 | id = Fresh.next() 142 | 143 | override def toString: String = s"x$id" 144 | } 145 | 146 | /** 147 | * Recursive type (only created when closing terms). 148 | */ 149 | case class RecursiveType(v: Var[Type], t: Term[Type]) extends Type with Mu[Type] { 150 | 151 | def subst(sv: Var[Type], to: Term[Type]): Term[Type] = 152 | if (sv == v) this else RecursiveType(v, t.subst(sv, to)) 153 | } 154 | -------------------------------------------------------------------------------- /src/tip/util/DotTools.scala: -------------------------------------------------------------------------------- 1 | package tip.util 2 | 3 | /** 4 | * Generator for fresh node IDs. 5 | */ 6 | object IDGenerator { 7 | private var current: Int = 0 8 | 9 | def getNewId: Int = { 10 | current += 1 11 | current 12 | } 13 | } 14 | 15 | /** 16 | * Super-class for elements of a Graphviz dot file. 17 | */ 18 | abstract class DotElement { 19 | 20 | /** 21 | * Produces a dot string representation of this element. 22 | */ 23 | def toDotString: String 24 | } 25 | 26 | /** 27 | * Represents a node in a Graphviz dot file. 28 | */ 29 | class DotNode(val id: String, val label: String, val additionalParams: Map[String, String]) extends DotElement { 30 | 31 | def this(label: String, additionalParams: Map[String, String] = Map()) = 32 | this("n" + IDGenerator.getNewId, label, additionalParams) 33 | 34 | def this() = this("") 35 | 36 | def equals(other: DotNode): Boolean = toDotString.equals(other.toDotString) 37 | 38 | override def toString: String = toDotString 39 | 40 | def toDotString: String = 41 | id + "[label=\"" + Output.escape(label) + "\"" + 42 | additionalParams.map(p => s"${p._1} = ${p._2}").mkString(",") + "]" 43 | 44 | } 45 | 46 | /** 47 | * Represents an edge between two nodes in a Graphviz dot file. 48 | */ 49 | class DotArrow(val fromNode: DotNode, arrow: String, val toNode: DotNode, val label: String) extends DotElement { 50 | 51 | def equals(other: DotArrow): Boolean = toDotString.equals(other.toDotString) 52 | 53 | def toDotString: String = fromNode.id + " " + arrow + " " + toNode.id + "[label=\"" + Output.escape(label) + "\"]" 54 | } 55 | 56 | /** 57 | * Represents a directed edge between two nodes in a Graphviz dot file. 58 | */ 59 | class DotDirArrow(fromNode: DotNode, toNode: DotNode, label: String) extends DotArrow(fromNode, "->", toNode, label) { 60 | def this(fromNode: DotNode, toNode: DotNode) = this(fromNode, toNode, "") 61 | } 62 | 63 | /** 64 | * Represents a Graphviz dot graph. 65 | */ 66 | class DotGraph(val title: String, val nodes: Iterable[DotNode], val edges: Iterable[DotArrow]) extends DotElement { 67 | 68 | def this(nodes: List[DotNode], edges: List[DotArrow]) = this("", nodes, edges) 69 | 70 | def this(title: String) = this(title, List(), List()) 71 | 72 | def this() = this(List(), List()) 73 | 74 | def addGraph(g: DotGraph): DotGraph = { 75 | val ng = g.nodes.foldLeft(this)((g, n) => g.addNode(n)) 76 | g.edges.foldLeft(ng)((g, e) => g.addEdge(e)) 77 | } 78 | 79 | def addNode(n: DotNode): DotGraph = 80 | if (nodes.exists(a => n.equals(a))) this 81 | else new DotGraph(title, nodes ++ List(n), edges) 82 | 83 | def addEdge(e: DotArrow): DotGraph = 84 | if (edges.exists(a => e.equals(a))) this 85 | else new DotGraph(title, nodes, edges ++ List(e)) 86 | 87 | override def toString: String = toDotString 88 | 89 | def toDotString: String = "digraph " + title + "{" + (nodes ++ edges).foldLeft("")((str, elm) => str + elm.toDotString + "\n") + "}" 90 | } 91 | -------------------------------------------------------------------------------- /src/tip/util/Log.scala: -------------------------------------------------------------------------------- 1 | package tip.util 2 | 3 | import scala.reflect.ClassTag 4 | import Log._ 5 | 6 | /** 7 | * Basic logging functionality. 8 | */ 9 | object Log { 10 | 11 | /** 12 | * Log levels. 13 | */ 14 | object Level extends Enumeration { 15 | type Level = Value 16 | val None, Error, Warn, Info, Debug, Verbose = Value 17 | } 18 | 19 | var defaultLevel = Level.Info 20 | 21 | /** 22 | * Constructs a new logger. 23 | * @param forcedLevel log level 24 | * @param ct class the logger belongs to 25 | */ 26 | def logger[A: ClassTag](forcedLevel: Level.Level = defaultLevel)(implicit ct: ClassTag[A]): Logger = 27 | Logger(ct.runtimeClass.getSimpleName, forcedLevel) 28 | } 29 | 30 | /** 31 | * A logger. 32 | */ 33 | final case class Logger(var tag: String, var level: Log.Level.Level) { 34 | 35 | // val TAG_MAX_LEN = 30 36 | // 37 | // tag = s"[${tag.padTo(TAG_MAX_LEN, ' ')}]" 38 | 39 | private def log(message: String, t: Throwable, msgLev: Log.Level.Level): Unit = { 40 | if (msgLev.id <= level.id || msgLev.id < Log.defaultLevel.id) { 41 | var account = 0 42 | val color = msgLev match { 43 | case Level.Error => 44 | account -= 9; Console.BOLD + Console.RED 45 | case Level.Warn => 46 | account -= 9; Console.BOLD + Console.YELLOW 47 | case Level.Info => 48 | account -= 9; Console.BOLD + Console.BLUE 49 | case Level.Verbose => 50 | account -= 9; Console.BOLD + Console.BLUE 51 | case _ => account -= 1; Console.RESET 52 | } 53 | // val preamble = s"$color$tag ${msgLev.toString.toLowerCase}: " 54 | // val space = (1 to (preamble.length + account)).map(_ => " ").mkString("") 55 | // val nmessage = message.replace("\r", "").replace("\n", s"\r\n$space") 56 | // println(s"$preamble$nmessage" + Console.RESET) 57 | print(s"$color[${msgLev.toString.toLowerCase}] ") 58 | print(Console.RESET) 59 | println(message) 60 | } 61 | if (t != null) t.printStackTrace(System.out) 62 | 63 | } 64 | 65 | /** 66 | * Writes a message to the log at level "error" . 67 | */ 68 | def error(message: String): Unit = log(message, null, Log.Level.Error) 69 | 70 | /** 71 | * Writes a message and a stack trace to the log at level "error". 72 | */ 73 | def error(message: String, t: Throwable): Unit = log(message, t, Log.Level.Error) 74 | 75 | /** 76 | * Writes a message to the log at level "warn" . 77 | */ 78 | def warn(message: String): Unit = log(message, null, Log.Level.Warn) 79 | 80 | /** 81 | * Writes a message and a stack trace to the log at level "warn". 82 | */ 83 | def warn(message: String, t: Throwable): Unit = log(message, t, Log.Level.Warn) 84 | 85 | /** 86 | * Writes a message to the log at level "info" . 87 | */ 88 | def info(message: String): Unit = log(message, null, Log.Level.Info) 89 | 90 | /** 91 | * Writes a message and a stack trace to the log at level "info". 92 | */ 93 | def info(message: String, t: Throwable): Unit = log(message, t, Log.Level.Info) 94 | 95 | /** 96 | * Writes a message to the log at level "debug" . 97 | */ 98 | def debug(message: String): Unit = log(message, null, Log.Level.Debug) 99 | 100 | /** 101 | * Writes a message and a stack trace to the log at level "debug". 102 | */ 103 | def debug(message: String, t: Throwable): Unit = log(message, t, Log.Level.Debug) 104 | 105 | /** 106 | * Writes a message to the log at level "verbose" . 107 | */ 108 | def verb(message: String): Unit = log(message, null, Log.Level.Verbose) 109 | 110 | /** 111 | * Writes a message and a stack trace to the log at level "verbose". 112 | */ 113 | def verb(message: String, t: Throwable): Unit = log(message, t, Log.Level.Verbose) 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/tip/util/MapUtils.scala: -------------------------------------------------------------------------------- 1 | package tip.util 2 | 3 | /** 4 | * Implicit classes providing convenience methods for maps. 5 | */ 6 | object MapUtils { 7 | 8 | /** 9 | * Makes a 'reverse' method available on objects of type `Map[A, Set[B]]`. 10 | */ 11 | implicit class ReverseOp[A, B](m: Map[A, Set[B]]) { 12 | def reverse: Map[B, Set[A]] = { 13 | var res = Map[B, Set[A]]() 14 | m.keys.foreach { k => 15 | m(k).foreach { v => 16 | val ins = res.getOrElse(v, Set[A]()) 17 | res += (v -> (ins + k)) 18 | } 19 | } 20 | res 21 | } 22 | } 23 | 24 | /** 25 | * Makes a 'reverse' method available on objects of type `Map[A, B]`. 26 | */ 27 | implicit class ReverseOp2[A, B](m: Map[A, B]) { 28 | def reverse: Map[B, Set[A]] = { 29 | var res = Map[B, Set[A]]() 30 | m.keys.foreach { k => 31 | val ins = res.getOrElse(m(k), Set[A]()) 32 | res += (m(k) -> (ins + k)) 33 | } 34 | res 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/tip/util/Output.scala: -------------------------------------------------------------------------------- 1 | package tip.util 2 | 3 | import java.io.{File, PrintWriter} 4 | 5 | import tip.analysis.{CallContext, FlowSensitiveAnalysis} 6 | import tip.cfg._ 7 | 8 | /** 9 | * Basic outputting functionality. 10 | */ 11 | object Output { 12 | 13 | val log = Log.logger[this.type]() 14 | 15 | /** 16 | * Generate an output to a file. 17 | * @param file the output file 18 | * @param kind output kind (determines the file name suffix) 19 | * @param outFolder the output directory 20 | */ 21 | def output(file: File, kind: OutputKind, content: String, outFolder: File): Unit = { 22 | val extension = kind match { 23 | case OtherOutput(OutputKindE.`cfg`) => "_cfg.dot" 24 | case OtherOutput(OutputKindE.`icfg`) => "_icfg.dot" 25 | case OtherOutput(OutputKindE.`types`) => "_types.ttip" 26 | case OtherOutput(OutputKindE.`normalized`) => "_normalized.tip" 27 | case DataFlowOutput(k) => 28 | s"_$k.dot" 29 | case _ => ??? 30 | } 31 | val outFile = new File(outFolder, s"${file.getName}_$extension") 32 | val pw = new PrintWriter(outFile, "UTF-8") 33 | pw.write(content) 34 | pw.close() 35 | log.info(s"Results of $kind analysis of $file written to $outFile") 36 | } 37 | 38 | /** 39 | * Escapes special characters in the given string. 40 | * Special characters are all Unicode chars except 0x20-0x7e but including \, ", {, and }. 41 | */ 42 | def escape(s: String): String = { 43 | if (s == null) 44 | return null 45 | val b = new StringBuilder() 46 | for (i <- 0 until s.length) { 47 | val c = s.charAt(i) 48 | c match { 49 | case '"' => 50 | b.append("\\\"") 51 | case '\\' => 52 | b.append("\\\\") 53 | case '\b' => 54 | b.append("\\b") 55 | case '\t' => 56 | b.append("\\t") 57 | case '\n' => 58 | b.append("\\n") 59 | case '\r' => 60 | b.append("\\r") 61 | case '\f' => 62 | b.append("\\f") 63 | case '<' => 64 | b.append("\\<") 65 | case '>' => 66 | b.append("\\>") 67 | case '{' => 68 | b.append("\\{") 69 | case '}' => 70 | b.append("\\}") 71 | case _ => 72 | if (c >= 0x20 && c <= 0x7e) 73 | b.append(c) 74 | else { 75 | b.append("\\%04X".format(c.toInt)) 76 | } 77 | } 78 | } 79 | b.toString() 80 | } 81 | 82 | /** 83 | * Helper function for producing string output for a control-flow graph node after an analysis. 84 | * @param res map from control-flow graph nodes to strings, as produced by the analysis 85 | */ 86 | def labeler(res: Map[CfgNode, _], stateAfterNode: Boolean)(n: CfgNode): String = { 87 | val r = res.getOrElse(n, "-") 88 | val desc = n match { 89 | case entry: CfgFunEntryNode => s"Function ${entry.data.name} entry" 90 | case exit: CfgFunExitNode => s"Function ${exit.data.name} exit" 91 | case _ => n.toString 92 | } 93 | if (stateAfterNode) s"$desc\n$r" 94 | else s"$r\n$desc" 95 | } 96 | 97 | /** 98 | * Transforms a map from pairs of call contexts and CFG nodes to values into a map from CFG nodes to strings. 99 | */ 100 | def transform(res: Map[(CallContext, CfgNode), _]): Map[CfgNode, String] = { 101 | val m = collection.mutable.Map[CfgNode, List[String]]().withDefaultValue(Nil) 102 | res.foreach { 103 | case ((c, n), v) => m += n -> (s"$c: $v" :: m(n)) 104 | case _ => ??? 105 | } 106 | m.toMap.mapValues { _.mkString("\n") }.withDefaultValue("") 107 | } 108 | 109 | /** 110 | * Generate an unique ID string for the given AST node. 111 | */ 112 | def dotIder(n: CfgNode): String = 113 | n match { 114 | case real: CfgStmtNode => s"real${real.data.loc.col}_${real.data.loc.line}" 115 | case entry: CfgFunEntryNode => s"entry${entry.data.loc.col}_${entry.data.loc.line}" 116 | case exit: CfgFunExitNode => s"exit${exit.data.loc.col}_${exit.data.loc.line}" 117 | case call: CfgCallNode => s"cally${call.data.loc.col}_${call.data.loc.line}" 118 | case acall: CfgAfterCallNode => s"acall${acall.data.loc.col}_${acall.data.loc.line}" 119 | } 120 | } 121 | 122 | /** 123 | * Different kinds of output (determine output file names). 124 | */ 125 | object OutputKindE extends Enumeration { 126 | val cfg, icfg, types, normalized = Value 127 | } 128 | 129 | sealed trait OutputKind 130 | 131 | /** 132 | * Output kind for a dataflow analysis (named according to the analysis). 133 | */ 134 | case class DataFlowOutput(kind: FlowSensitiveAnalysis.Analysis.Value) extends OutputKind { 135 | override def toString: String = kind.toString 136 | } 137 | 138 | /** 139 | * Other output kinds (for other processing phases than the actual analysis). 140 | */ 141 | case class OtherOutput(kind: OutputKindE.Value) extends OutputKind { 142 | override def toString: String = kind.toString 143 | } 144 | -------------------------------------------------------------------------------- /src/tip/util/TipProgramException.scala: -------------------------------------------------------------------------------- 1 | package tip.util 2 | 3 | /** 4 | * Exception throw in case of an error in the given TIP program. 5 | */ 6 | class TipProgramException(message: String) extends RuntimeException(message) 7 | -------------------------------------------------------------------------------- /tip: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sbt "runMain tip.Tip $*" -------------------------------------------------------------------------------- /tip.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | chcp 65001 > nul 3 | set JAVA_OPTS=-Dfile.encoding=UTF-8 4 | sbt "runMain tip.Tip %*" 5 | --------------------------------------------------------------------------------