Skip to content

Commit aef5189

Browse files
committed
Mark Uninitialized Fields as Null
1 parent 65e938d commit aef5189

File tree

4 files changed

+69
-4
lines changed

4 files changed

+69
-4
lines changed

liquidjava-example/src/main/java/testSuite/CorrectNullChecks.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
@SuppressWarnings("unused")
99
public class CorrectNullChecks {
1010

11+
Integer x; // implicit null
12+
13+
void test() {
14+
mustBeNull(x);
15+
x = 1; // implicit non-null after assignment
16+
mustBeNotNull(x);
17+
}
18+
19+
void mustBeNull(@Refinement("_ == null") Integer i) {}
20+
void mustBeNotNull(@Refinement("_ != null") Integer i) {}
21+
1122
void testNullInteger() {
1223
Integer i = null;
1324

@@ -82,6 +93,9 @@ void testNonNulls() {
8293

8394
@Refinement("_ != null")
8495
ArrayList<String> lst = new ArrayList<>();
96+
97+
@Refinement("_ != null")
98+
CorrectNullChecks r = this;
8599
}
86100

87101
void testNullChecksInMethods() {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Refinement Error
2+
package testSuite;
3+
4+
import liquidjava.specification.Refinement;
5+
6+
public class ErrorNullCheckFieldAssignment {
7+
8+
String s; // implicit null
9+
10+
void test() {
11+
mustBeNull(s);
12+
s = "hello"; // implicit non-null after assignment
13+
mustBeNull(s); // error
14+
}
15+
16+
void mustBeNull(@Refinement("_ == null") String s) {}
17+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Refinement Error
2+
package testSuite;
3+
4+
import liquidjava.specification.Refinement;
5+
6+
@SuppressWarnings("unused")
7+
public class ErrorNullCheckFieldNonInitialized {
8+
9+
Double d; // implicit null
10+
11+
void test() {
12+
@Refinement("_ != null")
13+
Double d2 = d; // should be error
14+
}
15+
}

liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ public <T> void visitCtLocalVariable(CtLocalVariable<T> localVariable) {
127127
if (localVariable.getAssignment() == null) {
128128
// declaration with no assignment
129129
Optional<Predicate> pred = getRefinementFromAnnotation(localVariable);
130-
RefinedVariable v = context.addVarToContext(varName, localVariable.getType(), pred.orElse(new Predicate()), localVariable);
130+
RefinedVariable v = context.addVarToContext(varName, localVariable.getType(), pred.orElse(new Predicate()),
131+
localVariable);
131132
getMessageFromAnnotation(localVariable).ifPresent(v::setMessage);
132133
} else {
133134
// declaration with assignment
@@ -136,7 +137,7 @@ public <T> void visitCtLocalVariable(CtLocalVariable<T> localVariable) {
136137
if (refinementFound == null) {
137138
refinementFound = new Predicate();
138139
}
139-
if (!Utils.isPrimitiveType(localVariable.getType().getQualifiedName()) && !Utils.isNullLiteral(e)) {
140+
if (!Utils.isPrimitiveType(localVariable.getType().getQualifiedName()) && isNonNullExpr(e)) {
140141
refinementFound = Predicate.createConjunction(refinementFound, Predicate.createNonNullEq());
141142
}
142143
context.addVarToContext(varName, localVariable.getType(), new Predicate(), e);
@@ -245,6 +246,14 @@ public <T> void visitCtField(CtField<T> f) {
245246
if (v instanceof Variable) {
246247
((Variable) v).setLocation("this");
247248
}
249+
// if the field is not initialized and can be null, add instance to context with null equality refinement
250+
if (f.getAssignment() == null && !Utils.isPrimitiveType(f.getType().getQualifiedName())) {
251+
String instanceName = String.format(Formats.INSTANCE, name, context.getCounter());
252+
Predicate initialRefinement = Predicate.createConjunction(ret.substituteVariable(name, instanceName),
253+
Predicate.createNullEq().substituteVariable(Keys.WILDCARD, instanceName));
254+
context.addInstanceToContext(instanceName, f.getType(), initialRefinement, f);
255+
context.addRefinementInstanceToVariable(name, instanceName);
256+
}
248257
}
249258

250259
@Override
@@ -261,9 +270,12 @@ public <T> void visitCtFieldRead(CtFieldRead<T> fieldRead) {
261270
}
262271

263272
} else if (context.hasVariable(String.format(Formats.THIS, fieldName))) {
273+
// resolve to latest instance of this field for flow-sensitive refinement
264274
String thisName = String.format(Formats.THIS, fieldName);
275+
Optional<VariableInstance> ovi = context.getLastVariableInstance(thisName);
276+
String var = ovi.isPresent() ? ovi.get().getName() : thisName;
265277
fieldRead.putMetadata(Keys.REFINEMENT,
266-
Predicate.createEquals(Predicate.createVar(Keys.WILDCARD), Predicate.createVar(thisName)));
278+
Predicate.createEquals(Predicate.createVar(Keys.WILDCARD), Predicate.createVar(var)));
267279

268280
} else if (fieldRead.getVariable().getSimpleName().equals("length")) {
269281
String targetName = fieldRead.getTarget().toString();
@@ -402,7 +414,7 @@ private void checkAssignment(String name, CtTypeReference<?> type, CtExpression<
402414
refinementFound = new Predicate();
403415
}
404416
}
405-
if (!Utils.isPrimitiveType(type.getQualifiedName()) && !Utils.isNullLiteral(assignment)) {
417+
if (!Utils.isPrimitiveType(type.getQualifiedName()) && isNonNullExpr(assignment)) {
406418
refinementFound = Predicate.createConjunction(refinementFound, Predicate.createNonNullEq());
407419
}
408420
Optional<VariableInstance> r = context.getLastVariableInstance(name);
@@ -436,6 +448,13 @@ private Predicate getExpressionRefinements(CtExpression<?> element) throws LJErr
436448
return getRefinement(element);
437449
}
438450

451+
private boolean isNonNullExpr(CtExpression<?> exp) {
452+
if (exp == null || Utils.isNullLiteral(exp))
453+
return false;
454+
return exp instanceof CtLiteral<?> || exp instanceof CtConstructorCall<?> || exp instanceof CtNewArray<?>
455+
|| exp instanceof CtNewClass<?> || exp instanceof CtThisAccess<?>;
456+
}
457+
439458
private Predicate substituteAllVariablesForLastInstance(Predicate c) {
440459
Predicate ret = c;
441460
List<String> ls = c.getVariableNames();

0 commit comments

Comments
 (0)