-
Notifications
You must be signed in to change notification settings - Fork 35
Added enum types and verification #168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
c70cc5a
e77a33b
a359d1f
a6a4055
0ab2098
44758c7
b610928
cdf192e
d7dea40
e3e49d2
aafde53
7a7e074
96905c1
8a4e507
2e46e5c
2754337
2c23a2e
c85ed97
2ad2747
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package testSuite; | ||
|
|
||
| import liquidjava.specification.StateRefinement; | ||
| import liquidjava.specification.StateSet; | ||
|
|
||
| @SuppressWarnings("unused") | ||
| @StateSet({"photoMode", "videoMode", "noMode"}) | ||
| class CorrectEnumUsage { | ||
| enum Mode { | ||
| Photo, Video, Unknown | ||
| } | ||
|
|
||
| Mode mode; | ||
| @StateRefinement(to="noMode(this)") | ||
| public CorrectEnumUsage() {} | ||
|
|
||
| @StateRefinement(from="noMode(this) && mode == Mode.Photo", to="photoMode(this)") | ||
| @StateRefinement(from="noMode(this) && mode == Mode.Video", to="videoMode(this)") | ||
| public void setMode(Mode mode) { | ||
| this.mode = mode; | ||
| } | ||
|
|
||
| @StateRefinement(from="photoMode(this)") | ||
| public void takePhoto() {} | ||
|
|
||
|
|
||
| public static void main(String[] args) { | ||
| // Correct | ||
| CorrectEnumUsage st = new CorrectEnumUsage(); | ||
| st.setMode(Mode.Photo); | ||
| st.takePhoto(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| // State Refinement Error | ||
| package testSuite; | ||
|
|
||
| import liquidjava.specification.StateRefinement; | ||
| import liquidjava.specification.StateSet; | ||
|
|
||
|
|
||
| @SuppressWarnings("unused") | ||
| @StateSet({"photoMode", "videoMode", "noMode"}) | ||
| class ErrorEnumUsage { | ||
| enum Mode { | ||
| Photo, Video, Unknown | ||
| } | ||
|
|
||
| Mode mode; | ||
| @StateRefinement(to="noMode(this)") | ||
| public ErrorEnumUsage() {} | ||
|
|
||
| @StateRefinement(from="noMode(this) && mode == Mode.Photo", to="photoMode(this)") | ||
| @StateRefinement(from="noMode(this) && mode == Mode.Video", to="videoMode(this)") | ||
| public void setMode(Mode mode) { | ||
| this.mode = mode; | ||
| } | ||
|
|
||
| @StateRefinement(from="photoMode(this)") | ||
| public void takePhoto() {} | ||
|
|
||
|
|
||
| public static void main(String[] args) { | ||
| // Correct | ||
| ErrorEnumUsage st = new ErrorEnumUsage(); | ||
| st.setMode(Mode.Video); | ||
| st.takePhoto(); //error | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,8 +7,13 @@ | |
| import liquidjava.diagnostics.errors.LJError; | ||
| import liquidjava.processor.context.Context; | ||
| import liquidjava.processor.refinement_checker.general_checkers.MethodsFunctionsChecker; | ||
| import liquidjava.rj_language.Predicate; | ||
| import liquidjava.utils.constants.Formats; | ||
| import liquidjava.utils.constants.Types; | ||
| import spoon.reflect.declaration.CtClass; | ||
| import spoon.reflect.declaration.CtConstructor; | ||
| import spoon.reflect.declaration.CtEnum; | ||
| import spoon.reflect.declaration.CtEnumValue; | ||
| import spoon.reflect.declaration.CtInterface; | ||
| import spoon.reflect.declaration.CtMethod; | ||
| import spoon.reflect.declaration.CtType; | ||
|
|
@@ -116,4 +121,19 @@ public <R> void visitCtMethod(CtMethod<R> method) { | |
| } | ||
| context.exitContext(); | ||
| } | ||
|
|
||
| @Override | ||
| public <T extends Enum<?>> void visitCtEnum(CtEnum<T> enumRead) { | ||
| String enumName = enumRead.getSimpleName(); | ||
| String qualifiedEnumName = enumRead.getQualifiedName(); | ||
| int ordinal = 0; | ||
| for (CtEnumValue ev : enumRead.getEnumValues()) { | ||
|
rcosta358 marked this conversation as resolved.
Outdated
|
||
| String varName = String.format(Formats.ENUM_VALUE, enumName, ev.getSimpleName()); | ||
| Predicate refinement = Predicate.createEquals(Predicate.createVar(varName), | ||
| Predicate.createLit(String.valueOf(ordinal), Types.INT)); | ||
| context.addGlobalVariableToContext(varName, qualifiedEnumName, enumRead.getReference(), refinement); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why global? Shouldn't it only be visible within the class or package where it was declared?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there is an
When line 134 is changed to
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's not really a shared package because it's not even included in the verification, each test is ran individually from its file or folder.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The issue is: if I declare an enum X in class A, should I be able to use it in class B? It might be fine, just raising some questions about how it should work.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, if we can use enums from different classes, we should make sure they have their fully qualified name to distinguish enums with the same name from different classes.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't realize the testing was that restrictive. I can try and create some test scenarios that take into consideration global and local context. The other option would be to restrict enum verification to only in the same file, but that feels like a cop-out instead of an actual fix/intended functionality. 🤔 |
||
| ordinal++; | ||
| } | ||
| super.visitCtEnum(enumRead); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,13 +19,15 @@ | |
| import liquidjava.rj_language.ast.UnaryExpression; | ||
| import liquidjava.rj_language.ast.Var; | ||
| import liquidjava.utils.Utils; | ||
| import liquidjava.utils.constants.Formats; | ||
| import liquidjava.utils.constants.Keys; | ||
|
|
||
| import org.antlr.v4.runtime.tree.ParseTree; | ||
| import org.apache.commons.lang3.NotImplementedException; | ||
| import rj.grammar.RJParser.AliasCallContext; | ||
| import rj.grammar.RJParser.ArgsContext; | ||
| import rj.grammar.RJParser.DotCallContext; | ||
| import rj.grammar.RJParser.EnumContext; | ||
| import rj.grammar.RJParser.ExpBoolContext; | ||
| import rj.grammar.RJParser.ExpContext; | ||
| import rj.grammar.RJParser.ExpGroupContext; | ||
|
|
@@ -156,9 +158,10 @@ private Expression literalExpressionCreate(ParseTree rc) throws LJError { | |
| return new GroupExpression(create(((LitGroupContext) rc).literalExpression())); | ||
| else if (rc instanceof LitContext) | ||
| return create(((LitContext) rc).literal()); | ||
| else if (rc instanceof VarContext) { | ||
| else if (rc instanceof VarContext) | ||
| return new Var(((VarContext) rc).ID().getText()); | ||
|
|
||
| else if (rc instanceof EnumContext) { | ||
| return new Var(enumCreate((EnumContext) rc)); | ||
|
rcosta358 marked this conversation as resolved.
Outdated
|
||
| } else { | ||
| return create(((InvocationContext) rc).functionCall()); | ||
| } | ||
|
|
@@ -234,6 +237,15 @@ private List<Expression> getArgs(ArgsContext args) throws LJError { | |
| return le; | ||
| } | ||
|
|
||
| private String enumCreate(EnumContext rc) { | ||
| String enumText = rc.enumCall().getText(); | ||
| int lastDot = enumText.lastIndexOf('.'); | ||
| String enumClass = enumText.substring(0, lastDot); | ||
| String enumConst = enumText.substring(lastDot + 1); | ||
| String varName = String.format(Formats.ENUM_VALUE, enumClass, enumConst); | ||
| return varName; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should've gone with a better format, I admit. I wasn't sure due to the rest of the existing types.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might want to "decode" enums when they come back in the error messages back to their Refinement version. Two approaches: 1) having a translation map. 2) storing the source of each variable in the Verification Condition (I'm doing this in aeon, and I think we discussed it for LJ). This is not specific for Enums, we might want to do this for other types. |
||
| } | ||
|
|
||
| private Expression literalCreate(LiteralContext literalContext) throws LJError { | ||
| if (literalContext.BOOL() != null) | ||
| return new LiteralBoolean(literalContext.BOOL().getText()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,7 +48,12 @@ private static Expr<?> getExpr(Context z3, String name, CtTypeReference<?> type) | |
| case "long", "java.lang.Long" -> z3.mkRealConst(name); | ||
| case "float", "double", "java.lang.Float", "java.lang.Double" -> (FPExpr) z3.mkConst(name, z3.mkFPSort64()); | ||
| case "int[]" -> z3.mkArrayConst(name, z3.mkIntSort(), z3.mkIntSort()); | ||
| default -> z3.mkConst(name, z3.mkUninterpretedSort(typeName)); | ||
| case "java.lang.Enum" -> z3.mkIntConst(name); | ||
| default -> { | ||
| if (type.isEnum()) | ||
| yield z3.mkIntConst(name); | ||
| yield z3.mkConst(name, z3.mkUninterpretedSort(typeName)); | ||
| } | ||
|
rcosta358 marked this conversation as resolved.
Outdated
|
||
| }; | ||
| } | ||
|
|
||
|
|
@@ -88,6 +93,7 @@ static Sort getSort(Context z3, String sort) { | |
| case "float", "java.lang.Float" -> z3.mkFPSort32(); | ||
| case "double", "java.lang.Double" -> z3.mkFPSortDouble(); | ||
| case "int[]" -> z3.mkArraySort(z3.mkIntSort(), z3.mkIntSort()); | ||
| case "java.lang.Enum" -> z3.getIntSort(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there a reason for this to be an IntSort if we created the EnumSorts before?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was part of a case I was trying to cover, but I ended up learning that it could only use a sort with an actual |
||
| case "String" -> z3.getStringSort(); | ||
| case "void" -> z3.mkUninterpretedSort("void"); | ||
| default -> z3.mkUninterpretedSort(sort); | ||
|
|
||


Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand you used
OBJECT_TYPEbecause of the rule ambiguity. However, this raises a concern, because it allows for multiple chaining of ids (e.g.,Mode.photo.photo,Mode.photo.photo.photo, etc.) which should not be syntactically valid.We could avoid the ambiguity by forcing the
OBJECT_TYPEto always start with a lowercase letter and declareenumCallasID '.' ID, but Java packages are allowed start with an uppercase letter (even though it's highly discouraged).So, we can introduce another terminal, just for enums:
This way, you guarantee the enums are always represented by two ids separated by a dot, without ambiguity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's understandable, my issue with that, at the time of development, was if ID and ID_UPPER wouldn't, even if they are slightly different, lead to ambiguity due to other grammar rules. I should've explored more formats.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have support for enums that live in another package, via qualified name. I agree with @rcosta358 that we want a first version working, and then we can think about this. To achieve this, we need a name resolution (à lá Spoon).