package org.jmlspecs.openjmltest.testcases; import org.jmlspecs.openjmltest.TCBase; import org.junit.Test; public class QueryPure extends TCBase { @Override public void setUp() throws Exception { // noCollectDiagnostics = true; // jmldebug = true; super.setUp(); } @Test public void testClass1() { helpTCF("A.java", "//@ pure\n" + // OK "public class A { } \n" ); } @Test public void testClass2() { helpTCF("A.java", "//@ query\n" + // OK "public class A { } \n" ); } @Test public void testClass3() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "@Query\n" + // OK "public class A { } \n" ); } @Test public void testClass4() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "@Pure\n" + // OK "public class A { } \n" ); } @Test public void testClass5() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "@Pure @Query\n" + // BAD "public class A { } \n" ,"/A.java:2: A declaration may not be both pure and query",7 ); } @Test public void testClass6() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "//@ pure query\n" + // BAD "public class A { } \n" ,"/A.java:2: A declaration may not be both pure and query",10 ); } @Test public void testClass7() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "@Pure //@ query\n" + // BAD "public class A { } \n" ,"/A.java:2: A declaration may not be both pure and query",11 ); } @Test public void testClass8() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "@Pure //@ pure\n" + // BAD "public class A { } \n" ,"/A.java:2: org.jmlspecs.annotation.Pure is not a repeatable annotation type",11 // Changed location in Java8 ); } @Test public void testClass9() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "@Query //@ query\n" + // BAD "public class A { } \n" ,"/A.java:2: org.jmlspecs.annotation.Query is not a repeatable annotation type",12 // CHanged location in Java8 ); } @Test public void testMethod1() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Query\n" + // OK " public void v() {}" + "} \n" ); } @Test public void testMethod2() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ query\n" + // OK " public void v() {}" + "} \n" ); } @Test public void testMethod3() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Pure\n" + // OK " public void v() {}" + "} \n" ); } @Test public void testMethod4() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@pure\n" + // OK " public void v() {}" + "} \n" ); } @Test public void testMethod5() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ pure query\n" + // BAD " public void v() {}" + "} \n" ,"/A.java:3: A declaration may not be both pure and query",12 ); } @Test public void testMethod6() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Query @Pure\n" + // BAD " public void v() {}" + "} \n" ,"/A.java:3: A declaration may not be both pure and query",3 ); } @Test public void testMethod7() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Pure //@ query\n" + // BAD " public void v() {}" + "} \n" ,"/A.java:3: A declaration may not be both pure and query",13 ); } @Test public void testMethod8() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Query //@ query\n" + // BAD " public void v() {}" + "} \n" ,"/A.java:3: org.jmlspecs.annotation.Query is not a repeatable annotation type",14 ); } @Test public void testMethod9() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Pure //@ pure\n" + // BAD " public void v() {}" + "} \n" ,"/A.java:3: org.jmlspecs.annotation.Pure is not a repeatable annotation type",13 ); } @Test public void testCacheExample() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ secret public model JMLDataGroup value;\n" + " @Secret protected Integer cache = null; //@ in value; \n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query(\"value\") public int value() { if (cache == null) cache = compute(); return cache; }\n" + " public int use() { return value(); }\n" + "} \n" ); } @Test public void testSimplerCacheExample() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ model public secret Object value;\n" + " @Secret protected Integer cache = null; //@ in value; \n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute(); return cache; }\n" + " public int use() { return value(); }\n" + "} \n" ); } @Test public void testAnotherCacheExample() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Secret Integer cache = null; \n" + // Requires allowing non-model fields to be datagroups " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query(\"cache\") public int value() { if (cache == null) cache = compute(); return cache; }\n" + " public int use() { return value(); }\n" + "} \n" ); } @Test public void testAnotherValidExample() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute(); return cache; }\n" + " public int use() { return value(); }\n" + " @Secret Integer cache = null; //@ in value; \n" + // To use the implicit declaration, value here must be after the Query "} \n" ); } @Test public void testInvariant() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute(); return cache; }\n" + " public int use() { return value(); }\n" + " @Secret public Integer cache = null; //@ in value; \n" + // To use the implicit declaration, value here must be after the Query " //@ @Secret(\"value\") public invariant cache != null ==> cache == compute();\n" + "} \n" ); } @Test public void testForwardRef() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Secret Integer cache = null; //@ in value; \n" + " //@ secret model Object value;\n" + // we're allowing forward reference "} \n" ); } @Test public void testCircular() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ secret model Integer cache = null; //@ in value; \n" + " //@ secret model Object value; in cache; \n" + // error - circular "} \n" ,"/A.java:3: This field participates in a circular datagroup inclusion chain: cache",28 ,"/A.java:4: This field participates in a circular datagroup inclusion chain: value",27 ); } @Test public void testQuery0() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Secret Integer cache = null; //@ in value; \n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute(); return cache; }\n" + " public int use() { return value(); }\n" + "} \n" ); } @Test public void testQuery1() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ model secret public Object value;\n" + " @Secret public Integer cache = null; //@ in value; \n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == cache;\n" + // ERROR - no use of secret in specs " @Query public int value() { if (cache == null) cache = compute(); return cache; }\n" + " public int use() { return value(); }\n" + "} \n" ,"/A.java:6: Secret fields may not be read in non-secret context: cache",26 ); } @Test public void testQuery2() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ model secret Object value;\n" + " @Secret Integer cache = null; //@ in value; \n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute(); return cache; }\n" + " public int use() { return cache; }\n" + // ERROR - no use of secret in open method "} \n" ,"/A.java:8: Secret fields may not be read in non-secret context: cache",29 ); } @Test public void testQuery3() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { f = 0; if (cache == null) cache = compute(); return cache; }\n" + // ERROR - no assignment except to secret " public int use() { return value(); }\n" + " int f;\n" + " @Secret Integer cache = null; //@ in value; \n" + "} \n" ,"/A.java:5: The field f is not writable since it is not in the value secret datagroup",31 ); } @Test public void testQuery4() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ @Secret public model Object o;\n " + " @Secret int q; //@ in o;\n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { q = 0; if (cache == null) cache = compute(); return cache; }\n" + // ERROR - no assignment except to own secret " public int use() { return value(); }\n" + " int f;\n" + " @Secret Integer cache = null; //@ in value; \n" + "} \n" ,"/A.java:7: A field may not be read in a secret context unless it is in the same secret datagroup: q not in value",31 ,"/A.java:7: The field q is not writable since it is not in the value secret datagroup",31 ); } @Test public void testQuery5() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ @Secret public model Object o;\n " + " @Secret int q; //@ in o;\n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute() + q; return cache; }\n" + // ERROR - no reading other secret " public int use() { return value(); }\n" + " int f;\n" + " @Secret Integer cache = null; //@ in value; \n" + "} \n" ,"/A.java:7: A field may not be read in a secret context unless it is in the same secret datagroup: q not in value",70 ); } @Test public void testQuery6() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ @Secret public model Object value;\n" + " //@ @Secret public model Object o; in value; \n " + " @Secret int q = 5; //@ in o;\n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute() + q; return cache; }\n" + // OK - q is nested in value " public int use() { return value(); }\n" + " int f;\n" + " @Secret Integer cache = null; //@ in value; \n" + "} \n" ); } @Test public void testQuery7() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ @Secret public model Object o;\n " + " @Secret public int q; //@ in o;\n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute(); return cache; }\n" + " public int use() { return value(); }\n" + " int f;\n" + " @Secret public Integer cache = null; //@ in value; \n" + " //@ @Secret(\"value\") public invariant cache != null ==> cache == compute() + q;\n" +// ERROR - no reading other secret "} \n" ,"/A.java:11: A field may not be read in a secret context unless it is in the same secret datagroup: q not in value",80 ); } // Note the difference between this test and the one below - here the attempt to resolve value on line 3 used to fail because it is // processed before the datagroup 'value' is created @Test public void testQuery8() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Secret public Object o; //@ in value; \n " + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute(); return cache; }\n" + // creates a datagroup named 'value' " public int use() { return value(); }\n" + " int f;\n" + " @Secret public Integer cache = null; //@ in value; \n" + " //@ @Secret(\"value\") public invariant cache != null ==> cache == compute() + 0;\n" + "} \n" ); } @Test public void testQuery8c() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " @Secret public int q = 5; //@ in o;\n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute(); return cache; }\n" + // creates a datagroup named 'value' " public int use() { return value(); }\n" + " int f;\n" + " @Secret public Integer cache = null; //@ in value; \n" + " //@ @Secret(\"value\") public invariant cache != null ==> cache == compute() + q;\n" + // OK - q is nested in value " //@ @Secret public model Object o; in value; \n " + "} \n" ); } // This test typechecks OK because the use of 'value' on line 3 is not resolved until after all Java declarations are // resolved - particularly value(), which will create the datagroup named value @Test public void testQuery8b() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ @Secret public model Object o; in value; \n " + " @Secret public int q = 5; //@ in o;\n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute(); return cache; }\n" + // creates a datagroup named 'value' " public int use() { return value(); }\n" + " int f;\n" + " @Secret public Integer cache = null; //@ in value; \n" + " //@ @Secret(\"value\") public invariant cache != null ==> cache == compute() + q;\n" + // OK - q is nested in value "} \n" ); } @Test public void testQuery8OK() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ @Secret public model Object value;\n" + " //@ @Secret public model Object o; in value; \n " + " @Secret public int q = 5; //@ in o;\n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute(); return cache; }\n" + " public int use() { return value(); }\n" + " int f;\n" + " @Secret public Integer cache = null; //@ in value; \n" + " //@ @Secret(\"value\") public invariant cache != null ==> cache == compute() + q;\n" + // OK - q is nested in value "} \n" ); } @Test public void testQuery8a() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ @Secret public model Object value;\n" + " //@ @Secret public model Object o; in value; \n " + " int f; @Secret int q = 5; //@ in o;\n" + " @Pure public int compute() { return 0; }\n" + " //@ ensures \\result == compute();\n" + " @Query public int value() { if (cache == null) cache = compute(); return cache; }\n" + " public int use() { return value(); }\n" + " @Secret Integer cache = null; //@ in value; \n" + " //@ @Secret public invariant true;\n" + // BAD SYNTAX " //@ @Secret(0) public invariant true;\n" + // BAD SYNTAX " //@ @Secret(\"org\") public invariant true;\n" + // BAD SYNTAX " //@ @Secret(\"value\",\"value\") public invariant true;\n" + // BAD SYNTAX " //@ @Secret(\"v\") public invariant true;\n" + // ERROR - not found "} \n" ,"/A.java:11: A secret annotation on an invariant must have exactly one argument",22 ,"/A.java:12: incompatible types: int cannot be converted to java.lang.String",15 ,"/A.java:13: cannot find symbol\n symbol: variable org\n location: class A",15 ,"/A.java:14: annotation values must be of the form 'name=value'",15 ,"/A.java:14: annotation values must be of the form 'name=value'",23 ,"/A.java:14: A secret annotation on an invariant must have exactly one argument",39 ,"/A.java:15: cannot find symbol\n symbol: variable v\n location: class A",15 ); } @Test public void testQuery9() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ @Secret public model int value;\n" + " //@ @Secret public model Object o; in value; \n " + " @Secret int q = 5; //@ in o;\n" + " @Pure public int compute() { return 0; }\n" + " int f;\n" + " @Secret Integer cache = null; //@ in value; \n" + " @Secret(\"value\") public int mm() { cache = null; q = 0; f = 0; }\n" + // ERROR - can't write f; "} \n" ,"/A.java:9: The field f is not writable since it is not in the value secret datagroup",59 ); } @Test public void testQuery10() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ @Secret model int value;\n" + " //@ @Secret public model Object o; \n " + " @Secret int q = 5; //@ in o;\n" + " @Pure public int compute() { return 0; }\n" + " int f;\n" + " @Secret Integer cache = null; //@ in value; \n" + " @Secret(\"value\") public int mm() { q = 0; }\n" + // ERROR - can't read or write q "} \n" ,"/A.java:9: A field may not be read in a secret context unless it is in the same secret datagroup: q not in value",39 ,"/A.java:9: The field q is not writable since it is not in the value secret datagroup",39 ); } @Test public void testQuery11() { helpTCF("A.java", "import org.jmlspecs.annotation.*;\n" + "public class A { \n" + " //@ @Secret model int value;\n" + " //@ @Secret public model Object o; \n " + " @Secret int q = 5; //@ in o;\n" + " @Pure public int compute() { return 0; }\n" + " int f;\n" + " @Secret Integer cache = null; //@ in value; \n" + " @Secret public void mm() { }\n" + // ERROR - methods must have a argument to @Secret "} \n" ,"/A.java:9: A secret annotation on a method must have exactly one argument",3 ); } // FIXME - what about secret invariants, represents clauses or initializers of secret fields // FIXME - what about reading from/writing to - selections and array references // FIXME - what about calling non-secret methods, constructors }