Skip to content
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

Add support for switch expressions in dataflow #4982

Merged
merged 47 commits into from
Dec 16, 2021
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c9f2248
Check switch expressions.
smillst Dec 8, 2021
106047c
Add expected errors.
smillst Dec 8, 2021
37608e0
Fix error.
smillst Dec 8, 2021
fc23027
Add FunctionSwitchExpressionScanner.
smillst Dec 9, 2021
10d97d2
Tweaks.
smillst Dec 9, 2021
ca0918d
Fix expected error location.
smillst Dec 9, 2021
aaf2b21
Reformat.
smillst Dec 9, 2021
7843188
Use the term "selector expression" from the Java 17 version of the JLS.
smillst Dec 9, 2021
af9c380
Use the Java 17 term selector expression.
smillst Dec 10, 2021
0af409c
Tmp
smillst Dec 10, 2021
b045bd0
Merge branch 'switch-selector' into switch-expressions-dataflow
smillst Dec 10, 2021
715ab33
Correct name.
smillst Dec 10, 2021
9e8c351
Merge branch 'switch-selector' into switch-expressions-dataflow
smillst Dec 10, 2021
917c0a1
Use field.
smillst Dec 10, 2021
94feba2
Make new method.
smillst Dec 10, 2021
36c5da7
Merge branch 'switch-selector' into switch-expressions-dataflow
smillst Dec 10, 2021
0fca774
Checkpoint.
smillst Dec 10, 2021
a91f369
Handle yield in SwitchExpressions.
smillst Dec 10, 2021
b07926c
Fix error.
smillst Dec 10, 2021
c3d6b37
Fix error.
smillst Dec 10, 2021
06897ed
Javadoc.
smillst Dec 10, 2021
44e29c5
Merge remote-tracking branch 'origin/master' into switch-expressions-…
smillst Dec 10, 2021
23b04ba
Add skip test.
smillst Dec 10, 2021
55a1c16
Javadoc.
smillst Dec 10, 2021
a98585e
Merge ../checker-framework-branch-master into switch-expressions-check
mernst Dec 10, 2021
224c085
More javadoc.
smillst Dec 10, 2021
320b4a5
Fix typo
mernst Dec 10, 2021
28614ba
Add more javadoc.
smillst Dec 10, 2021
e0ec4a1
Minor edits
mernst Dec 11, 2021
1943e54
Merge branch 'switch-expressions-check' of github.com:smillst/checker…
mernst Dec 11, 2021
cfe8d36
Merge ../checker-framework-fork-smillst-branch-switch-expressions-che…
mernst Dec 11, 2021
10fd9fd
Minor edits
mernst Dec 11, 2021
8e90ca3
Minor edits
mernst Dec 11, 2021
8dae16b
Remove skip require javadoc.
smillst Dec 13, 2021
452e390
Add comments.
smillst Dec 13, 2021
da4aa4a
Add comments.
smillst Dec 13, 2021
d460864
Code review.
smillst Dec 13, 2021
c8a55bc
Merge ../checker-framework-branch-master into switch-expressions-check
mernst Dec 13, 2021
fda126f
Correction.
smillst Dec 13, 2021
75b6d97
Merge remote-tracking branch 'origin/master' into switch-expressions-…
smillst Dec 15, 2021
9efc15b
Merge branch 'switch-expressions-check' into switch-expressions-dataflow
smillst Dec 15, 2021
a3f07ea
Add changelog entry.
smillst Dec 15, 2021
c2f0921
Merge branch 'switch-expressions-check' into switch-expressions-dataflow
smillst Dec 15, 2021
35ec20b
Add changelog.
smillst Dec 15, 2021
7714124
Remove comment.
smillst Dec 16, 2021
ddda03f
Merge remote-tracking branch 'origin/master' into switch-expressions-…
smillst Dec 16, 2021
2a24de1
Fix comment.
smillst Dec 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SKIP-REQUIRE-JAVADOC
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Delete after merge.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this file necessary? I don't see wide-ranging uninteresting edits in this pull request.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that NodeVisitor has 80 undocumented methods. I would have to document them all to get the Javadoc test to pass. See https://github.com/typetools/checker-framework/runs/4509004521.

4 changes: 0 additions & 4 deletions checker/tests/nullness/java17/NullnessSwitchExpressions.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ void method2() {
default -> throw new IllegalStateException("Invalid day: " + day);
};

// TODO: This is a false positive.
// :: error: (dereference.of.nullable)
o.toString();
}

Expand All @@ -51,8 +49,6 @@ void method3() {
String s = null;
if (day == Day.THURSDAY) {
s = "hello";
// TODO: This is a false positive.
// :: error: (dereference.of.nullable)
s.toString();
}
yield s;
Expand Down
27 changes: 27 additions & 0 deletions checker/tests/nullness/java17/SwitchExpressionInvariant.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// @below-java17-jdk-skip-test
import java.util.List;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

public class SwitchExpressionInvariant {
public static boolean flag = false;

void method(
List<@NonNull String> nonnullStrings, List<@Nullable String> nullableStrings, int fenum) {

List<@NonNull String> list =
// :: error: (assignment)
switch (fenum) {
// :: error: (switch.expression)
case 1 -> nonnullStrings;
default -> nullableStrings;
};

List<@Nullable String> list2 =
switch (fenum) {
// :: error: (switch.expression)
case 1 -> nonnullStrings;
default -> nullableStrings;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import com.sun.source.tree.SynchronizedTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.TypeParameterTree;
Expand Down Expand Up @@ -148,6 +149,7 @@
import org.checkerframework.dataflow.cfg.node.StringConversionNode;
import org.checkerframework.dataflow.cfg.node.StringLiteralNode;
import org.checkerframework.dataflow.cfg.node.SuperNode;
import org.checkerframework.dataflow.cfg.node.SwitchExpressionNode;
import org.checkerframework.dataflow.cfg.node.SynchronizedNode;
import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
import org.checkerframework.dataflow.cfg.node.ThisNode;
Expand Down Expand Up @@ -250,6 +252,9 @@ public class CFGTranslationPhaseOne extends TreePathScanner<Node, Void> {
/** Nested scopes of try-catch blocks in force at the current program point. */
private final TryStack tryStack;

/** SwitchBuild for the current switch. Used to match yield statements to enclosing switches. */
private SwitchBuilder switchBuilder;

/**
* Maps from AST {@link Tree}s to sets of {@link Node}s. Every Tree that produces a value will
* have at least one corresponding Node. Trees that undergo conversions, such as boxing or
Expand Down Expand Up @@ -475,23 +480,29 @@ public Node scan(Tree tree, Void p) {
return null;
}
// Must use String comparison to support compiling on JDK 11 and earlier.
if (tree.getKind().name().equals("SWITCH_EXPRESSION")) {
return visitSwitchExpression17(tree, p);
// Features added between JDK 12 and JDK 17 inclusive.
switch (tree.getKind().name()) {
// case "BINDING_PATTERN":
// return visitBindingPattern17(path.getLeaf(), p);
case "SWITCH_EXPRESSION":
return visitSwitchExpression17(tree, p);
case "YIELD":
return visitYield17(tree, p);
default:
return super.scan(tree, p);
}
return super.scan(tree, p);

// TODO: Do we need to support yield trees and binding patterns to?
// Features added between JDK 12 and JDK 17 inclusive.
// switch (tree.getKind().name()) {
// case "BINDING_PATTERN":
// return visitBindingPattern17(path.getLeaf(), p);
// case "SWITCH_EXPRESSION":
// return visitSwitchExpression17(tree, p);
// case "YIELD":
// return visitYield17(path.getLeaf(), p);
// default:
// return super.scan(tree, p);
// }
}
/**
* Visit a SwitchExpressionTree
*
* @param yieldTree a YieldTree, typed as Tree to be backward-compatible
* @param p parameter
* @return the result of visiting the switch expression tree
*/
public Node visitYield17(Tree yieldTree, Void p) {
ExpressionTree resultExpression = TreeUtils.yieldTreeGetValue(yieldTree);
switchBuilder.buildSwitchExpressionResult(resultExpression);
return null;
}

/**
Expand All @@ -503,10 +514,8 @@ public Node scan(Tree tree, Void p) {
*/
public Node visitSwitchExpression17(Tree switchExpressionTree, Void p) {
// TODO: Analyze switch expressions properly.
return new MarkerNode(
switchExpressionTree,
"switch expression tree; not analyzed #" + TreeUtils.treeUids.get(switchExpressionTree),
env.getTypeUtils());
SwitchBuilder switchBuilder = new SwitchBuilder(switchExpressionTree);
return switchBuilder.build();
}

/* --------------------------------------------------------- */
Expand Down Expand Up @@ -2128,28 +2137,63 @@ public Node visitSwitch(SwitchTree tree, Void p) {
*/
private class SwitchBuilder {
/** The switch tree. */
private final SwitchTree switchTree;
private final Tree switchTree;

/** The case trees of {@code switchTree} */
private final List<? extends CaseTree> caseTrees;

/**
* The Tree for the selector expression.
*
* <pre>
* switch ( <em> selector expression</em> ) { ... }
* </pre>
*/
private final ExpressionTree selectorExprTree;

/** The labels for the case bodies. */
private final Label[] caseBodyLabels;

/**
* The Node for the assignment of the switch selector expression to a synthetic local variable.
*/
private AssignmentNode selectorExprAssignment;

/**
* If {@link #switchTree} is a switch expression, then this is the synthetic variable tree that
* all results of {@code #switchTree} are assigned. Other, this is null.
*/
private @Nullable VariableTree switchExprVarTree;

/**
* Construct a SwitchBuilder.
*
* @param tree a switch tree
* @param switchTree a switch tree
*/
private SwitchBuilder(SwitchTree tree) {
this.switchTree = tree;
private SwitchBuilder(Tree switchTree) {
this.switchTree = switchTree;
if (switchTree instanceof SwitchTree) {
SwitchTree switchStatementTree = (SwitchTree) switchTree;
this.caseTrees = switchStatementTree.getCases();
this.selectorExprTree = switchStatementTree.getExpression();
} else {
this.caseTrees = TreeUtils.switchExpressionTreeGetCases(switchTree);
this.selectorExprTree = TreeUtils.switchExpressionTreeGetExpression(switchTree);
}
// "+ 1" for the default case. If the switch has an explicit default case, then
// the last element of the array is never used.
this.caseBodyLabels = new Label[switchTree.getCases().size() + 1];
this.caseBodyLabels = new Label[caseTrees.size() + 1];
}

/** Build up the CFG for the switchTree. */
public void build() {
/**
* Build up the CFG for the switchTree.
*
* @return if the switch is a switch expression, then a {@link SwitchExpressionNode}; otherwise,
* null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is that the right thing? I guess probably because elsewhere builds all the CFG nodes for the case bodies and none is needed explicitly for the switch statement itself.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. A switch statement does not have a value so it doesn't need to have a corresponding node.

*/
public @Nullable SwitchExpressionNode build() {
SwitchBuilder oldSwitchBuilder = switchBuilder;
switchBuilder = this;
TryFinallyScopeCell oldBreakTargetL = breakTargetL;
breakTargetL = new TryFinallyScopeCell(new Label());
int cases = caseBodyLabels.length - 1;
Expand All @@ -2160,16 +2204,20 @@ public void build() {

buildSelector();

// Build CFG for the cases.
extendWithNode(
new MarkerNode(
switchTree,
"start of switch statement #" + TreeUtils.treeUids.get(switchTree),
env.getTypeUtils()));
buildSwitchExpressionVar();

if (switchTree.getKind() == Kind.SWITCH) {
extendWithNode(
new MarkerNode(
switchTree,
"start of switch statement #" + TreeUtils.treeUids.get(switchTree),
env.getTypeUtils()));
}

// Build CFG for the cases.
Integer defaultIndex = null;
for (int i = 0; i < cases; ++i) {
CaseTree caseTree = switchTree.getCases().get(i);
CaseTree caseTree = caseTrees.get(i);
if (TreeUtils.caseTreeGetExpressions(caseTree).isEmpty()) {
defaultIndex = i;
} else {
Expand All @@ -2180,17 +2228,35 @@ public void build() {
// The checks of all cases must happen before the default case, therefore we build the
// default case last.
// Fallthrough is still handled correctly with the caseBodyLabels.
buildCase(switchTree.getCases().get(defaultIndex), defaultIndex);
buildCase(caseTrees.get(defaultIndex), defaultIndex);
}

addLabelForNextNode(breakTargetL.peekLabel());
breakTargetL = oldBreakTargetL;
if (switchTree.getKind() == Kind.SWITCH) {
extendWithNode(
new MarkerNode(
switchTree,
"end of switch statement #" + TreeUtils.treeUids.get(switchTree),
env.getTypeUtils()));
}

extendWithNode(
new MarkerNode(
switchTree,
"end of switch statement #" + TreeUtils.treeUids.get(switchTree),
env.getTypeUtils()));
switchBuilder = oldSwitchBuilder;
if (switchExprVarTree != null) {
IdentifierTree switchExprVarUseTree = treeBuilder.buildVariableUse(switchExprVarTree);
handleArtificialTree(switchExprVarUseTree);

LocalVariableNode switchExprVarUseNode = new LocalVariableNode(switchExprVarUseTree);
switchExprVarUseNode.setInSource(false);
extendWithNode(switchExprVarUseNode);
SwitchExpressionNode switchExpressionNode =
new SwitchExpressionNode(
TreeUtils.typeOf(switchTree), switchTree, switchExprVarUseNode);
extendWithNode(switchExpressionNode);
return switchExpressionNode;
} else {
return null;
}
}

/**
Expand All @@ -2201,7 +2267,7 @@ public void build() {
*/
private void buildSelector() {
// Create a synthetic variable to which the switch selector expression will be assigned
TypeMirror selectorExprType = TreeUtils.typeOf(switchTree.getExpression());
TypeMirror selectorExprType = TreeUtils.typeOf(selectorExprTree);
VariableTree selectorVarTree =
treeBuilder.buildVariableDecl(selectorExprType, uniqueName("switch"), findOwner(), null);
handleArtificialTree(selectorVarTree);
Expand All @@ -2217,17 +2283,36 @@ private void buildSelector() {
selectorVarUseNode.setInSource(false);
extendWithNode(selectorVarUseNode);

Node selectorExprNode = unbox(scan(switchTree.getExpression(), null));
Node selectorExprNode = unbox(scan(selectorExprTree, null));

AssignmentTree assign =
treeBuilder.buildAssignment(selectorVarUseTree, switchTree.getExpression());
AssignmentTree assign = treeBuilder.buildAssignment(selectorVarUseTree, selectorExprTree);
handleArtificialTree(assign);

selectorExprAssignment = new AssignmentNode(assign, selectorVarUseNode, selectorExprNode);
selectorExprAssignment.setInSource(false);
extendWithNode(selectorExprAssignment);
}

/**
* If {@link #switchTree} is a switch expression tree, this method creates a synthetic variable
* whose value is the value of the switch expression.
*/
private void buildSwitchExpressionVar() {
if (switchTree.getKind() == Kind.SWITCH) {
// A switch statement does not have a value, so do nothing.
return;
}
TypeMirror switchExprType = TreeUtils.typeOf(switchTree);
switchExprVarTree =
treeBuilder.buildVariableDecl(
switchExprType, uniqueName("switchExpr"), findOwner(), null);
handleArtificialTree(switchExprVarTree);

VariableDeclarationNode switchExprVarNode = new VariableDeclarationNode(switchExprVarTree);
switchExprVarNode.setInSource(false);
extendWithNode(switchExprVarNode);
}

/**
* Build the CGF for the case tree, {@code tree}.
*
Expand Down Expand Up @@ -2256,11 +2341,51 @@ private void buildCase(CaseTree tree, int index) {
scan(stmt, null);
}
} else {
scan(TreeUtils.caseTreeGetBody(tree), null);
Tree bodyTree = TreeUtils.caseTreeGetBody(tree);
if (bodyTree instanceof ExpressionTree) {
buildSwitchExpressionResult((ExpressionTree) bodyTree);
} else {
scan(bodyTree, null);
}
}
extendWithExtendedNode(new UnconditionalJump(nextBodyL));
addLabelForNextNode(nextCaseL);
}

/**
* Does the following for each result expression of a switch expression:
*
* <ol>
* <li>Builds the CFG for the switch expression result.
* <li>Creates an assignment node for the assignment of {@code resultExpression} to {@code
* switchExprVarTree}.
* <li>Adds an unconditional jump to {@link #breakTargetL} (the end of the switch expression.
* </ol>
*
* @param resultExpression the result of a switch expression; either from a yield or an
* expression in a case rule.
*/
void buildSwitchExpressionResult(ExpressionTree resultExpression) {
IdentifierTree switchExprVarUseTree = treeBuilder.buildVariableUse(switchExprVarTree);
handleArtificialTree(switchExprVarUseTree);

LocalVariableNode switchExprVarUseNode = new LocalVariableNode(switchExprVarUseTree);
switchExprVarUseNode.setInSource(false);
extendWithNode(switchExprVarUseNode);

Node resultExprNode = scan(resultExpression, null);

AssignmentTree assign = treeBuilder.buildAssignment(switchExprVarUseTree, resultExpression);
handleArtificialTree(assign);

AssignmentNode assignmentNode =
new AssignmentNode(assign, switchExprVarUseNode, resultExprNode);
assignmentNode.setInSource(false);
extendWithNode(assignmentNode);

assert breakTargetL != null : "no target for yield statement";
extendWithExtendedNode(new UnconditionalJump(breakTargetL.accessLabel()));
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ public R visitTernaryExpression(TernaryExpressionNode n, P p) {
return visitNode(n, p);
}

@Override
public R visitSwitchExpressionNode(SwitchExpressionNode n, P p) {
return visitNode(n, p);
}

@Override
public R visitAssignment(AssignmentNode n, P p) {
return visitNode(n, p);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ public interface NodeVisitor<R, P> {

R visitTernaryExpression(TernaryExpressionNode n, P p);

R visitSwitchExpressionNode(SwitchExpressionNode n, P p);

R visitAssignment(AssignmentNode n, P p);

R visitLocalVariable(LocalVariableNode n, P p);
Expand Down
Loading