RLC - [[checkOwningField() did not find a reason!]]
While running CF RLC with inference on the NJR benchmark, I encountered several leaks where CF reported:
[[checkOwningField() did not find a reason!]]
One specific example from the NJR benchmark project url1f1de5fc71_cooijmanstim_hobo_tgz-pJ8-hobo_TestingEnviromentJ8 produced the following leak warning:
src/hobo/TestingEnviroment.java:10: warning: (required.method.not.called) $$ 4 $$ method writeFile $$ field fileHandler $$ hobo.FileHandler $$ [[checkOwningField() did not find a reason!]] $$ ( 163, 187 ) $$ @MustCall method writeFile may not have been invoked on field fileHandler or any of its aliases.
FileHandler fileHandler;
^
The type of object is: hobo.FileHandler.
Reason for going out of scope: [[checkOwningField() did not find a reason!]]
Initially, I suspected this issue might be related to inconsistencies within the inference algorithm. Upon deeper analysis, I identified two clear patterns in the affected cases:
Pattern 1: Inheritance with Owning Fields
When a class inherits @MustCall obligations from its superclass and introduces an additional owning field, inference does not properly generate new inheritable @MustCall annotations for the subclass's owning field.
Example:
import java.io.*;
import org.checkerframework.checker.calledmethods.qual.*;
import org.checkerframework.checker.mustcall.qual.*;
public class NewTest extends InputStream {
@Owning private RandomAccessFile stream;
public NewTest(String fileName) throws FileNotFoundException {
stream = new RandomAccessFile(new File(fileName), "r");
}
@EnsuresCalledMethods(
value = {"this.stream"},
methods = {"close"})
public void releaseStream() throws IOException {
stream.close();
}
@Override
public int read() throws IOException {
return stream.read();
}
}
Pattern 2: Resources Closed Inside Constructors
Another problematic case occurs when the owning resource's required finalizer method is invoked directly within the constructor:
Example:
@InheritableMustCall({<init>})
public class TestingEnviroment {
@Owning FileHandler fileHandler;
@EnsuresCalledMethods(value = { "this.fileHandler" }, methods = { "writeFile" })
public TestingEnviroment() {
fileHandler = new FileHandler("src/fileOutput/test.txt");
fileHandler.writeFile(winCounter[0]+" : "+winCounter[1]);
}
public static void main(String[] args) {
new TestingEnviroment();
}
}
In this scenario, the inference algorithm assigns an @InheritableMustCall({<init>}) annotation, despite the resource already having its required method (writeFile) invoked within the constructor. I think this inference is wrong in this case where the resource is released directly in the constructor.