Skip to content

Incorrect Cache invalidation in Nullaway 0.12.12 #1351

@felixdesouza

Description

@felixdesouza

I believe #1328 introduces a bug because it doesn't keep its caches in sync.

In an internal repo, we were getting the following error messages when running NullAway 0.12.12:

BugPattern: NullAway
       Stack Trace:
       java.lang.NullPointerException: Cannot invoke "org.checkerframework.nullaway.dataflow.cfg.ControlFlowGraph.getTreeLookup()" because "this.cfg" is null
        at org.checkerframework.nullaway.dataflow.analysis.AbstractAnalysis.getResult(AbstractAnalysis.java:180)
        at com.uber.nullaway.dataflow.DataFlow.resultFor(DataFlow.java:318)
        at com.uber.nullaway.dataflow.DataFlow.resultForExpr(DataFlow.java:296)
        at com.uber.nullaway.dataflow.DataFlow.expressionDataflow(DataFlow.java:241)
        at com.uber.nullaway.dataflow.AccessPathNullnessAnalysis.getNullness(AccessPathNullnessAnalysis.java:115)
        at com.uber.nullaway.NullAway.nullnessFromDataflow(NullAway.java:2746)
        at com.uber.nullaway.NullAway.mayBeNullExpr(NullAway.java:2712)
        at com.uber.nullaway.NullAway.lambda$handleInvocation$1(NullAway.java:2047)
        at com.uber.nullaway.InvocationArguments.forEach(InvocationArguments.java:86)
        at com.uber.nullaway.NullAway.handleInvocation(NullAway.java:2014)
        at com.uber.nullaway.NullAway.matchMethodInvocation(NullAway.java:444)
        at com.google.errorprone.scanner.ErrorProneScanner.processMatchers(ErrorProneScanner.java:509)
        ...

In the following block:

    ControlFlowGraph cfg = cfgCache.getUnchecked(CfgParams.create(path, env));
    AnalysisParams aparams = AnalysisParams.create(transfer, cfg);
    @SuppressWarnings("unchecked")
    Analysis<A, S, T> analysis = (Analysis<A, S, T>) analysisCache.getUnchecked(aparams);
    if (performAnalysis && !alreadyRunAnalyses.contains(aparams)) {
      analysis.performAnalysis(cfg);
      alreadyRunAnalyses.add(aparams);
    }

What I think happens is that:

  • Dataflow#analysisCache will evict an analysis, but it won't clear it from alreadyRunAnalyses
  • When we see the same path again, we will create new analyses, but because the aparams completed before at some point, the if condition will fail as it already contains the particular aparams.
  • It will therefore not run analysis.performAnalysis(cfg) which initialises the fields we care about cfg
  • The analysis you get back will be malformed and you will get the NullPointerException.

The fix is probably along the lines of adding a removal listener to analysisCache that will also remove the key from aparams, keeping the two caches in sync.

What was a bit confusing to me was that it was non-deterministic in that some builds would pass, others would fail. However, when it failed, it would consistently fail at the same point. So perhaps it's some sort of ordering issue when Errorprone/Nullaway is processing the files. This has made it a bit difficult to get a reliable reproduction, but I believe the above steps is the only way to get a malformed Analysis object back.

I'm new to the NullAway repository, so I'm not sure about all the different lifecycles, but how I think this manifests is when you have a
class with lots of different method invocations, but you return to one previously handled

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions