├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ideafiles/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ideafiles/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
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 |
--------------------------------------------------------------------------------