Skip to content

Commit edde206

Browse files
Implement inbound checks (#24)
* feat: add bridge return checks * feat: add field read check * feat: add unchecked method return checks * chore: spotless
1 parent 0e177a5 commit edde206

File tree

12 files changed

+140
-23
lines changed

12 files changed

+140
-23
lines changed

checker/src/main/java/io/github/eisop/runtimeframework/checker/nullness/NullnessVerifier.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
public class NullnessVerifier implements RuntimeVerifier {
1111

1212
private static final ClassDesc VERIFIER = ClassDesc.of(NullnessRuntimeVerifier.class.getName());
13-
private static final ClassDesc ATTRIBUTION_KIND =
14-
ClassDesc.of(AttributionKind.class.getName());
13+
private static final ClassDesc ATTRIBUTION_KIND = ClassDesc.of(AttributionKind.class.getName());
1514

1615
private static final String METHOD_DEFAULT = "checkNotNull";
1716
private static final MethodTypeDesc DESC_DEFAULT =

checker/src/test/resources/test-cases/nullness-bridge/InheritanceBridgeTest.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,19 @@ public static void main(String[] args) {
2828
test.finalAction(null);
2929
// cannot bridge final methods, no error here
3030

31-
String unsafe = test.returnAction();
31+
// :: error: (Return value of inherited method returnAction must be NonNull)
32+
test.returnAction();
33+
34+
// :: error: (Return value of inherited method returnAction must be NonNull)
3235
// :: error: (Local Variable Assignment (Slot 2) must be NonNull)
36+
String unsafe = test.returnAction();
3337

38+
// :: error: (Return value of inherited method returnAction must be NonNull)
3439
@Nullable String again = test.returnAction();
3540
}
3641

3742
@Override
3843
public void overrideMe(@NonNull String inputA, @Nullable String inputB) {
3944
System.out.println("safe version of this method" + inputA + inputB);
4045
}
41-
}
46+
}

checker/src/test/resources/test-cases/nullness-field-read/InstanceFieldRead.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,22 @@ static class UncheckedLib {
88
public String poison = null;
99
}
1010

11+
public static void consume(@Nullable Object o) {}
12+
1113
public static void main(String[] args) {
1214
UncheckedLib lib = new UncheckedLib();
1315

14-
String s = lib.poison;
16+
// 1. Read without storage (Argument passing)
17+
// :: error: (Read Field 'poison' must be NonNull)
18+
consume(lib.poison);
19+
20+
// 2. Assignment
21+
// :: error: (Read Field 'poison' must be NonNull)
1522
// :: error: (Local Variable Assignment (Slot 2) must be NonNull)
23+
String s = lib.poison;
1624

17-
@Nullable String q = lib.poison;
25+
// 3. Nullable Assignment (Still checks read)
26+
// :: error: (Read Field 'poison' must be NonNull)
27+
@Nullable String q = lib.poison;
1828
}
19-
}
29+
}
Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import org.checkerframework.checker.nullness.qual.Nullable;
12
import io.github.eisop.runtimeframework.qual.AnnotatedFor;
23

34
@AnnotatedFor("nullness")
@@ -7,8 +8,16 @@ static class UncheckedLib {
78
public static String POISON = null;
89
}
910

11+
public static void consume(@Nullable Object o) {}
12+
1013
public static void main(String[] args) {
11-
String s = UncheckedLib.POISON;
14+
// 1. Read without storage
15+
// :: error: (Read Field 'POISON' must be NonNull)
16+
consume(UncheckedLib.POISON);
17+
18+
// 2. Assignment
19+
// :: error: (Read Field 'POISON' must be NonNull)
1220
// :: error: (Local Variable Assignment (Slot 1) must be NonNull)
21+
String s = UncheckedLib.POISON;
1322
}
14-
}
23+
}

checker/src/test/resources/test-cases/nullness-invoke/InstanceBoundary.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ public static void main(String[] args) {
1313
UncheckedLib lib = new UncheckedLib();
1414

1515
String s = lib.getNull();
16+
// :: error: (Return value of getNull (Boundary) must be NonNull)
1617
// :: error: (Local Variable Assignment (Slot 2) must be NonNull)
1718

1819
lib.getNull();
19-
// currently no explicit check on the return if its not stored
20+
// :: error: (Return value of getNull (Boundary) must be NonNull)
2021

2122
}
2223
}

checker/src/test/resources/test-cases/nullness-invoke/NullableBoundary.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ public static String getNull() {
1212

1313
public static void main(String[] args) {
1414
@Nullable String s = UncheckedLib.getNull();
15+
// :: error: (Return value of getNull (Boundary) must be NonNull)
1516
}
1617
}

checker/src/test/resources/test-cases/nullness-invoke/StaticBoundary.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public static String getNull() {
1212

1313
public static void main(String[] args) {
1414
String s = UncheckedLib.getNull();
15+
// :: error: (Return value of getNull (Boundary) must be NonNull)
1516
// :: error: (Local Variable Assignment (Slot 1) must be NonNull)
1617
}
1718

checker/src/test/resources/test-cases/nullness-parameter/FieldArgument.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@ static class UncheckedLib {
1010
}
1111

1212
public static void main(String[] args) {
13+
// :: error: (Read Field 'POISON' must be NonNull)
14+
// :: error: (Parameter 0 must be NonNull)
1315
consume(UncheckedLib.POISON);
14-
nullableConsume(UncheckedLib.POISON);
16+
17+
// :: error: (Read Field 'POISON' must be NonNull)
18+
nullableConsume(UncheckedLib.POISON);
1519
}
1620

1721
public static void consume(@NonNull String arg) {
18-
// :: error: (Parameter 0 must be NonNull)
1922
}
2023

2124
public static void nullableConsume(@Nullable String arg) {
2225
}
23-
}
26+
}

framework/src/main/java/io/github/eisop/runtimeframework/core/AnnotationInstrumenter.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,14 @@ protected void generateUncheckedReturnCheck(
154154

155155
@Override
156156
protected void generateMethodCallCheck(CodeBuilder b, InvokeInstruction invoke) {
157-
// empty for now, only need to generate checks when a method call is stored somehwhere
157+
RuntimeVerifier target =
158+
policy.getBoundaryCallCheck(invoke.owner().asInternalName(), invoke.typeSymbol());
158159

160+
if (target != null) {
161+
b.dup();
162+
target.generateCheck(
163+
b, TypeKind.REFERENCE, "Return value of " + invoke.name().stringValue() + " (Boundary)");
164+
}
159165
}
160166

161167
@Override
@@ -225,6 +231,16 @@ private void emitBridge(ClassBuilder builder, ParentMethod parentMethod) {
225231
ClassDesc.of(
226232
parentMethod.owner().thisClass().asInternalName().replace('/', '.'));
227233
codeBuilder.invokespecial(parentDesc, methodName, desc);
234+
235+
RuntimeVerifier returnTarget = policy.getBridgeReturnCheck(parentMethod);
236+
if (returnTarget != null) {
237+
codeBuilder.dup();
238+
returnTarget.generateCheck(
239+
codeBuilder,
240+
TypeKind.REFERENCE,
241+
"Return value of inherited method " + methodName);
242+
}
243+
228244
returnResult(
229245
codeBuilder, ClassDesc.ofDescriptor(desc.returnType().descriptorString()));
230246
});

framework/src/main/java/io/github/eisop/runtimeframework/core/RuntimeInstrumenter.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,7 @@ public void accept(ClassBuilder classBuilder, ClassElement classElement) {
6666
} else if (isFieldRead(fInst)) {
6767
codeBuilder.with(element);
6868
if (isCheckedScope) {
69-
// generateFieldReadCheck(codeBuilder, fInst, classModel);
70-
// Currently disabling field read checks as the GETFIELD
71-
// and GETSTATIC instructions are not actually dangerous
72-
// on their own. Its when we STORE a field we read from
73-
// that an issue could arise
74-
// TODO: consider method of turning on and off different
75-
// boundary sites
69+
generateFieldReadCheck(codeBuilder, fInst, classModel);
7670
}
7771
}
7872
} else if (element instanceof ReturnInstruction rInst) {

0 commit comments

Comments
 (0)