diff --git a/latte/pom.xml b/latte/pom.xml index 6b77334..ee63bd3 100644 --- a/latte/pom.xml +++ b/latte/pom.xml @@ -96,6 +96,12 @@ ${version.spoon} + + org.apache.commons + commons-lang3 + 3.12.0 + + @@ -139,5 +145,15 @@ + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + + diff --git a/latte/src/main/java/context/ClassLevelMaps.java b/latte/src/main/java/context/ClassLevelMaps.java index f3029cc..4d00efa 100644 --- a/latte/src/main/java/context/ClassLevelMaps.java +++ b/latte/src/main/java/context/ClassLevelMaps.java @@ -7,19 +7,20 @@ import org.apache.commons.lang3.tuple.Pair; -import spoon.reflect.declaration.CtClass; -import spoon.reflect.declaration.CtConstructor; -import spoon.reflect.declaration.CtField; -import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.*; import spoon.reflect.reference.CtTypeReference; -public class ClassLevelMaps { +public class +ClassLevelMaps { static ClassLevelMaps instance; Map, CtClass> typeClassMap; Map, Map>> classFields; Map, Map>> classConstructors; Map, Map, CtMethod>> classMethods; + + Map, Map, CtMethod>> externalMethods; + Map, Map, List>> externalMethodParamPermissions; @@ -28,6 +29,9 @@ public ClassLevelMaps() { classFields = new HashMap, Map>>(); classConstructors = new HashMap<>(); classMethods = new HashMap<>(); + + externalMethods = new HashMap<>(); + externalMethodParamPermissions = new HashMap<>(); } public CtClass getClassFrom(CtTypeReference type) { @@ -241,6 +245,62 @@ public static void joinElim( } } + public void addExternalMethod(CtTypeReference klass, CtMethod method) { + Pair mPair = Pair.of(method.getSimpleName(), method.getParameters().size()); + if (externalMethods.containsKey(klass)){ + Map, CtMethod> m = externalMethods.get(klass); + m.put(mPair, method); + } else { + Map, CtMethod> m = new HashMap, CtMethod>(); + m.put(mPair, method); + externalMethods.put(klass, m); + } + } + + public void addExternalMethodParamPermissions( + CtTypeReference typeRef, + String methodName, + int numParams, + List paramAnnotations + ) { + if (externalMethodParamPermissions == null) + externalMethodParamPermissions = new HashMap<>(); + + Pair methodSig = Pair.of(methodName, numParams); + externalMethodParamPermissions + .computeIfAbsent(typeRef, k -> new HashMap<>()) + .put(methodSig, paramAnnotations); + } + + public List getExternalMethodParamPermissions( + CtTypeReference clazz, String methodName, int arity) { + Map, List> methodMap = externalMethodParamPermissions.get(clazz); + if (methodMap == null) return null; + return methodMap.get(Pair.of(methodName, arity)); + } + public boolean hasExternalMethodParamPermissions( + CtTypeReference clazz, String methodName, int arity) { + Map, List> methodMap = + externalMethodParamPermissions.get(clazz); + + if (methodMap == null) return false; + + return methodMap.containsKey(Pair.of(methodName, arity)); + } + public CtMethod getExternalCtMethod(CtTypeReference klass, String methodName, int numParams) { + Pair mPair = Pair.of(methodName, numParams); + if (externalMethods.containsKey(klass)){ + Map, CtMethod> m = externalMethods.get(klass); + if (m.containsKey(mPair)){ + return m.get(mPair); + } + } + return null; + } + + public Object getExternaRefinementsMap() { + return externalMethodParamPermissions; + } } diff --git a/latte/src/main/java/specification/ExternalRefinementsFor.java b/latte/src/main/java/specification/ExternalRefinementsFor.java new file mode 100644 index 0000000..4178530 --- /dev/null +++ b/latte/src/main/java/specification/ExternalRefinementsFor.java @@ -0,0 +1,21 @@ +package specification; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to create refinements for an external library. The annotation receives the path of the + * library e.g. @ExternalRefinementsFor("java.lang.Math") + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface ExternalRefinementsFor { + /** + * The prefix of the external method + * + * @return + */ + public String value(); +} \ No newline at end of file diff --git a/latte/src/main/java/typechecking/ExternalRefinementFirstPass.java b/latte/src/main/java/typechecking/ExternalRefinementFirstPass.java new file mode 100644 index 0000000..7c20389 --- /dev/null +++ b/latte/src/main/java/typechecking/ExternalRefinementFirstPass.java @@ -0,0 +1,101 @@ +package typechecking; + +import context.ClassLevelMaps; +import context.PermissionEnvironment; +import context.SymbolicEnvironment; +import context.UniquenessAnnotation; +import specification.ExternalRefinementsFor; +import spoon.reflect.code.CtBlock; +import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtLiteral; +import spoon.reflect.declaration.*; +import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.visitor.filter.TypeFilter; +import org.apache.commons.lang3.tuple.Pair; + +import java.lang.annotation.Annotation; +import java.util.*; + +/** + * First pass that collects permission annotations (@unique, @shared, etc.) + * for external method parameters and stores them in ClassLevelMaps. + */ +public class ExternalRefinementFirstPass extends LatteAbstractChecker { + + private CtTypeReference currentExtRefTarget = null; + + public ExternalRefinementFirstPass(SymbolicEnvironment symbEnv, + PermissionEnvironment permEnv, + ClassLevelMaps maps) { + super(symbEnv, permEnv, maps); + logInfo("[ External Refinements Pass started ]"); + enterScopes(); + } + + @Override + public void visitCtInterface(CtInterface ctInterface) { + logInfo("Visiting interface: " + ctInterface.getSimpleName(), ctInterface); + // @ExternalRefinementsFor annotation check + CtAnnotation ann = ctInterface.getAnnotation(ctInterface.getFactory().Type().createReference("specification.ExternalRefinementsFor")); + + if (ann == null) { + return; + } + + CtExpression expr = ann.getValues().get("value"); + + if (expr instanceof CtLiteral && ((CtLiteral) expr).getValue() instanceof String) { + currentExtRefTarget = ctInterface.getFactory().Type().createReference((String) ((CtLiteral) expr).getValue()); + } else { + logWarning("Expected a string literal in @ExternalRefinementsFor"); + return; + } + + if (currentExtRefTarget == null) { + logWarning("No target class specified in @ExternalRefinementsFor"); + return; + } + + if (ctInterface.isPublic()) { + logInfo("Processing external interface: " + ctInterface.getQualifiedName(), ctInterface); + scan(ctInterface.getMethods()); + } + super.visitCtInterface(ctInterface); + + currentExtRefTarget = null; + } + + @Override + public void visitCtMethod(CtMethod method) { + logInfo("Visiting method: " + method.getSimpleName(), method); + + if (currentExtRefTarget == null) { + return; + } + + CtBlock body = method.getBody(); + if (body != null && !body.isImplicit()) return; + + List> parameters = method.getParameters(); + List paramAnnotations = new ArrayList<>(); + + for (CtParameter param : parameters) { + UniquenessAnnotation ua = new UniquenessAnnotation(param); + paramAnnotations.add(ua); + } + + Pair methodSig = Pair.of(method.getSimpleName(), parameters.size()); + + logInfo("Registering external refinements for: " + currentExtRefTarget, method); + + maps.addExternalMethod(currentExtRefTarget, method); + maps.addExternalMethodParamPermissions( + currentExtRefTarget, methodSig.getLeft(), methodSig.getRight(), paramAnnotations + ); + super.visitCtMethod(method); + + logInfo("Collected annotations for method: " + methodSig + " => " + paramAnnotations, method); + super.visitCtMethod(method); + } + +} \ No newline at end of file diff --git a/latte/src/main/java/typechecking/LatteClassFirstPass.java b/latte/src/main/java/typechecking/LatteClassFirstPass.java index 1678823..6683c73 100644 --- a/latte/src/main/java/typechecking/LatteClassFirstPass.java +++ b/latte/src/main/java/typechecking/LatteClassFirstPass.java @@ -4,11 +4,9 @@ import context.PermissionEnvironment; import context.SymbolicEnvironment; import context.UniquenessAnnotation; -import spoon.reflect.declaration.CtClass; -import spoon.reflect.declaration.CtConstructor; -import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtField; -import spoon.reflect.declaration.CtMethod; +import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtLiteral; +import spoon.reflect.declaration.*; import spoon.reflect.reference.CtTypeReference; public class LatteClassFirstPass extends LatteAbstractChecker{ @@ -54,6 +52,24 @@ public void visitCtField(CtField f) { @Override public void visitCtMethod(CtMethod m) { + CtTypeReference type = ((CtType) m.getParent()).getTypeErasure(); + CtType parentType = (CtType) m.getParent(); + if(parentType instanceof CtInterface){ + CtAnnotation ann = parentType.getAnnotation( + parentType.getFactory().Type().createReference("specification.ExternalRefinementsFor") + ); + if (ann != null) { + CtExpression expr = ann.getValues().get("value"); + + if (expr instanceof CtLiteral && ((CtLiteral) expr).getValue() instanceof String) { + String refinedClassName = (String) ((CtLiteral) expr).getValue(); + CtTypeReference refinedTypeRef = parentType.getFactory().Type().createReference(refinedClassName); + if(maps.hasExternalMethodParamPermissions(refinedTypeRef, m.getSimpleName(), m.getParameters().size())) + logInfo("External refinement match found for: " + m.getSimpleName() + " in refined class: " + refinedClassName); + } + } + return; + } logInfo("Visiting method: " + m.getSimpleName(), m); maps.addMethod((CtClass) m.getParent(), m); super.visitCtMethod(m); diff --git a/latte/src/main/java/typechecking/LatteProcessor.java b/latte/src/main/java/typechecking/LatteProcessor.java index 2ab807f..358e9e4 100644 --- a/latte/src/main/java/typechecking/LatteProcessor.java +++ b/latte/src/main/java/typechecking/LatteProcessor.java @@ -27,6 +27,7 @@ public void process(CtPackage pkg) { if (!visitedPackages.contains(pkg)) { visitedPackages.add(pkg); + pkg.accept(new ExternalRefinementFirstPass( se, pe, mtc)); pkg.accept(new LatteClassFirstPass( se, pe, mtc)); pkg.accept(new LatteTypeChecker( se, pe, mtc)); } diff --git a/latte/src/main/java/typechecking/LatteTypeChecker.java b/latte/src/main/java/typechecking/LatteTypeChecker.java index 586667c..a3a3d4b 100644 --- a/latte/src/main/java/typechecking/LatteTypeChecker.java +++ b/latte/src/main/java/typechecking/LatteTypeChecker.java @@ -27,6 +27,7 @@ import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtParameter; +import spoon.reflect.reference.CtExecutableReference; import spoon.reflect.reference.CtFieldReference; import spoon.reflect.reference.CtLocalVariableReference; import spoon.reflect.reference.CtTypeReference; @@ -198,30 +199,54 @@ public void visitCtInvocation(CtInvocation invocation) { CtMethod m = maps.getCtMethod(klass, metName, invocation.getArguments().size()); - if (m == null){ - logInfo("Cannot find method {" + metName + "} for {} in the context"); - return; - } List paramSymbValues = new ArrayList<>(); - for (int i = 0; i < paramSize; i++){ - CtExpression arg = invocation.getArguments().get(i); - // Ξ“; Ξ”; Ξ£ ⊒ 𝑒1, ... , 𝑒𝑛 ⇓ 𝜈1, ... , πœˆπ‘› ⊣ Ξ“β€²; Ξ”β€²; Ξ£β€² - SymbolicValue vv = (SymbolicValue) arg.getMetadata(EVAL_KEY); - if (vv == null) logError("Symbolic value for constructor argument not found", invocation); - - CtParameter p = m.getParameters().get(i); - UniquenessAnnotation expectedUA = new UniquenessAnnotation(p); - UniquenessAnnotation vvPerm = permEnv.get(vv); - - logInfo(String.format("Checking constructor argument %s:%s, %s <= %s", p.getSimpleName(), vv, vvPerm, expectedUA)); - // Ξ£β€² ⊒ 𝑒1, ... , 𝑒𝑛 : 𝛼1, ... , 𝛼𝑛 ⊣ Ξ£β€²β€² - if (!permEnv.usePermissionAs(vv, vvPerm, expectedUA)) - logError(String.format("Expected %s but got %s", expectedUA, vvPerm), arg); + if (m == null){ + CtExecutableReference execRef = invocation.getExecutable(); - paramSymbValues.add(vv); + if (maps.hasExternalMethodParamPermissions(e, execRef.getSimpleName(), invocation.getArguments().size())) { + List externalParams = maps.getExternalMethodParamPermissions( + e, execRef.getSimpleName(), invocation.getArguments().size()); + + for (int i = 0; i < invocation.getArguments().size(); i++) { + CtExpression arg = invocation.getArguments().get(i); + + UniquenessAnnotation expectedUA = externalParams.get(i); + + SymbolicValue vv = (SymbolicValue) arg.getMetadata(EVAL_KEY); + if (vv == null) logError("Symbolic value for constructor argument not found", invocation); + + UniquenessAnnotation vvPerm = permEnv.get(vv); + + if (!permEnv.usePermissionAs(vv, vvPerm, expectedUA)) + logError(String.format("Expected %s but got %s in the external refinement", expectedUA, vvPerm), arg); + + paramSymbValues.add(vv); + } + m = maps.getExternalCtMethod(e, metName, invocation.getArguments().size()); + } else { + logInfo("Cannot find method {" + metName + "} for {} in the context"); + return; + } + } else { + for (int i = 0; i < paramSize; i++){ + CtExpression arg = invocation.getArguments().get(i); + // Ξ“; Ξ”; Ξ£ ⊒ 𝑒1, ... , 𝑒𝑛 ⇓ 𝜈1, ... , πœˆπ‘› ⊣ Ξ“β€²; Ξ”β€²; Ξ£β€² + SymbolicValue vv = (SymbolicValue) arg.getMetadata(EVAL_KEY); + if (vv == null) logError("Symbolic value for constructor argument not found", invocation); + + CtParameter p = m.getParameters().get(i); + UniquenessAnnotation expectedUA = new UniquenessAnnotation(p); + UniquenessAnnotation vvPerm = permEnv.get(vv); + + logInfo(String.format("Checking constructor argument %s:%s, %s <= %s", p.getSimpleName(), vv, vvPerm, expectedUA)); + // Ξ£β€² ⊒ 𝑒1, ... , 𝑒𝑛 : 𝛼1, ... , 𝛼𝑛 ⊣ Ξ£β€²β€² + if (!permEnv.usePermissionAs(vv, vvPerm, expectedUA)) + logError(String.format("Expected %s but got %s", expectedUA, vvPerm), arg); + + paramSymbValues.add(vv); + } } - // distinct(Ξ”β€², {πœˆπ‘– : borrowed ≀ 𝛼𝑖 }) // distinct(Ξ”, 𝑆) ⇐⇒ βˆ€πœˆ, πœˆβ€² ∈ 𝑆 : Ξ” ⊒ 𝜈 ⇝ πœˆβ€² =β‡’ 𝜈 = πœˆβ€² List check_distinct = new ArrayList<>(); diff --git a/latte/src/test/examples/LinkedListRefinement.java b/latte/src/test/examples/LinkedListRefinement.java new file mode 100644 index 0000000..052265a --- /dev/null +++ b/latte/src/test/examples/LinkedListRefinement.java @@ -0,0 +1,27 @@ +import specification.*; +import java.util.LinkedList; + +public class LinkedListRefinement { + @Unique Object o1; + + public Object test(Object obj, Object obj2, @Free Object temp) { + LinkedList list = new LinkedList<>(); + + list.add(obj); + list.add(obj); + list.add(1, obj2); + + list.remove(temp); + } +} + +@ExternalRefinementsFor("java.util.LinkedList") +interface LinkedListRefinements2 { + + public void add(@Shared T t); + + public void add(int n, @Shared T t); + + public void remove(@Free T t); + +} \ No newline at end of file diff --git a/latte/src/test/examples/LinkedListRefinementIncorrect.java b/latte/src/test/examples/LinkedListRefinementIncorrect.java new file mode 100644 index 0000000..164a731 --- /dev/null +++ b/latte/src/test/examples/LinkedListRefinementIncorrect.java @@ -0,0 +1,24 @@ +import specification.*; +import java.util.LinkedList; + +public class LinkedListRefinementIncorrect { + @Unique Object o1; + + public Object test(@Free Object obj) { + Object o2 = o1; + LinkedList list = new LinkedList<>(); + + list.add(obj); + list.add(o2); + } +} + +@ExternalRefinementsFor("java.util.LinkedList") +interface LinkedListRefinements { + + public void add(@Free T t); + + public void add(int n, T t); + + public void remove(@Free T t); +} \ No newline at end of file diff --git a/latte/src/test/java/AppTest.java b/latte/src/test/java/AppTest.java index 3bd77b0..bd1d35b 100644 --- a/latte/src/test/java/AppTest.java +++ b/latte/src/test/java/AppTest.java @@ -45,7 +45,8 @@ private static Stream provideCorrectTestCases() { Arguments.of("src/test/examples/MyNodeIfNoElse.java"), Arguments.of("src/test/examples/MyNodeIfPermissionCheck.java"), Arguments.of("src/test/examples/MyNodeInvocationIf.java"), - Arguments.of("src/test/examples/MyNodeIfInvocationPermission.java") + Arguments.of("src/test/examples/MyNodeIfInvocationPermission.java"), + Arguments.of("src/test/examples/LinkedListRefinement.java") ); } @@ -66,7 +67,8 @@ private static Stream provideIncorrectTestCases() { Arguments.of("src/test/examples/MyStackFieldAssignMethod.java", "UNIQUE but got SHARED"), Arguments.of("src/test/examples/FieldAccessNoThis.java", "UNIQUE but got SHARED"), Arguments.of("src/test/examples/FieldAccessRightNoThis.java", "FREE but got UNIQUE"), - Arguments.of("src/test/examples/MyNodeIncorrectIfPermission.java", "Expected UNIQUE but got SHARED") + Arguments.of("src/test/examples/MyNodeIncorrectIfPermission.java", "Expected UNIQUE but got SHARED"), + Arguments.of("src/test/examples/LinkedListRefinementIncorrect.java", "FREE but got UNIQUE") ); }