typetools / typetools/checker-framework

RLC - [[checkOwningField() did not find a reason!]]

Open
#6,987 0 comments 1 reaction 1 assignee View on GitHub

@iamsanjaymalakar is already working on this.

Since Mar 12, 2025.

bug ResourceLeakChecker
Dominant language
Java
Stars
1.1k
Forks
440
Avg merge
1d 12h
Merged PRs (30d)
134

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.