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 to Resource Leak Checker #5005

Merged
merged 3 commits into from
Jan 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
import org.checkerframework.dataflow.cfg.node.StringConversionNode;
import org.checkerframework.dataflow.cfg.node.SwitchExpressionNode;
import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.framework.flow.CFAnalysis;
Expand Down Expand Up @@ -186,6 +187,18 @@ public TransferResult<CFValue, CFStore> visitTernaryExpression(
return result;
}

@Override
public TransferResult<CFValue, CFStore> visitSwitchExpressionNode(
SwitchExpressionNode node, TransferInput<CFValue, CFStore> input) {
TransferResult<CFValue, CFStore> result = super.visitSwitchExpressionNode(node, input);
if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) {
// Add the synthetic variable created during CFG construction to the temporary
// variable map (rather than creating a redundant temp var)
atypeFactory.tempVars.put(node.getTree(), node.getSwitchExpressionVar());
}
return result;
}

/**
* This method either creates or looks up the temp var t for node, and then updates the store to
* give t the same type as {@code node}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
import org.checkerframework.dataflow.cfg.node.SwitchExpressionNode;
import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.framework.flow.CFStore;
Expand Down Expand Up @@ -50,6 +51,18 @@ public TransferResult<CFValue, CFStore> visitTernaryExpression(
return result;
}

@Override
public TransferResult<CFValue, CFStore> visitSwitchExpressionNode(
SwitchExpressionNode node, TransferInput<CFValue, CFStore> input) {
TransferResult<CFValue, CFStore> result = super.visitSwitchExpressionNode(node, input);
if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) {
// Add the synthetic variable created during CFG construction to the temporary
// variable map (rather than creating a redundant temp var)
rlTypeFactory.addTempVar(node.getSwitchExpressionVar(), node.getTree());
}
return result;
}

@Override
public TransferResult<CFValue, CFStore> visitMethodInvocation(
final MethodInvocationNode node, final TransferInput<CFValue, CFStore> input) {
Expand Down
180 changes: 180 additions & 0 deletions checker/tests/resourceleak/java17/SwitchExpressions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// @below-java17-jdk-skip-test
import org.checkerframework.checker.calledmethods.qual.*;
import org.checkerframework.checker.mustcall.qual.*;
import org.checkerframework.common.returnsreceiver.qual.*;

class SwitchExpressions {

@MustCall("a") class Foo {
void a() {}

@This Foo b() {
return this;
}

void c(@CalledMethods("a") Foo this) {}
}

Foo makeFoo() {
return new Foo();
}

static void takeOwnership(@Owning Foo foo) {
foo.a();
}

/** cases where switch expressions are assigned to a variable */
void testSwitchAssigned(int i) {
Foo switch1 =
switch (i) {
case 3 -> new Foo();
default -> makeFoo();
};
switch1.a();

// :: error: required.method.not.called
Foo switch2 =
switch (i) {
case 3 -> new Foo();
default -> makeFoo();
};

// :: error: required.method.not.called
Foo x = new Foo();
Foo switch3 =
switch (i) {
case 3 -> new Foo();
default -> x;
};
switch3.a();

Foo y = new Foo();
Foo switch4 =
switch (i) {
case 3 -> y;
default -> y;
};
switch4.a();

takeOwnership(
switch (i) {
case 3 -> new Foo();
default -> makeFoo();
});

// :: error: required.method.not.called
Foo x2 = new Foo();
takeOwnership(
switch (i) {
case 3 -> x2;
default -> null;
});

int j = 10;
Foo switchInLoop = null;
while (j > 0) {
// :: error: required.method.not.called
switchInLoop =
switch (i) {
case 3 -> null;
default -> new Foo();
};
j--;
}
switchInLoop.a();

(switch (i) {
case 3 -> new Foo();
default -> makeFoo();
})
.a();
}

/**
* tests where switch and cast expressions (possibly nested) may or may not be assigned to a
* variable
*/
void testSwitchCastUnassigned(int i) {
// :: error: required.method.not.called
if ((switch (i) {
case 3 -> new Foo();
default -> null;
})
!= null) {
i = -i;
}

// :: error: required.method.not.called
if (switch (i) {
case 3 -> makeFoo();
default -> null;
}
!= null) {
i = -i;
}

Foo x = new Foo();
if (switch (i) {
case 3 -> x;
default -> null;
}
!= null) {
i = -i;
}
x.a();

// :: error: required.method.not.called
if (((Foo) new Foo()) != null) {
i = -i;
}

// double cast; no error
Foo doubleCast = (Foo) ((Foo) makeFoo());
doubleCast.a();

// nesting casts and switch expressions; no error
Foo deepNesting =
(switch (i) {
case 3 -> (switch (-i) {
case -3 -> makeFoo();
default -> (Foo) makeFoo();
});
default -> ((Foo) new Foo());
});
deepNesting.a();
}

@Owning
Foo testSwitchReturnOk(int i) {
return switch (i) {
case 3 -> new Foo();
default -> makeFoo();
};
}

@Owning
Foo testSwitchReturnBad(int i) {
// :: error: required.method.not.called
Foo x = new Foo();
return switch (i) {
case 3 -> x;
default -> makeFoo();
};
}

@MustCall("toString") static class Sub1 extends Object {}

@MustCall("clone") static class Sub2 extends Object {}

static void testSwitchSubtyping(int i) {
// :: error: required.method.not.called
Object toStringAndClone =
switch (i) {
case 3 -> new Sub1();
default -> new Sub2();
};
// at this point, for soundness, we should be responsible for calling both toString and clone on
// obj...
toStringAndClone.toString();
}
}