Skip to content

Polymorphic qualifier on a field is not resolved for the field's own declaration initializer #2142

Open

Description

@wmdietl

Follow-up to the discussion on PR #2137 (#1217), which fixed the same class of gap for the
Initialization Checker's own @NotOnlyInitialized field adaptation. This issue is about the
same mechanism at the framework level: GenericAnnotatedTypeFactory#postAsMemberOf resolves a
field's polymorphic qualifier (QualifierPolymorphism#resolve(VariableElement, AnnotatedTypeMirror, AnnotatedTypeMirror), used by any checker with @HasQualifierParameter/poly-qualified fields,
e.g. the Tainting Checker's @PolyTainted) based on the receiver ("owner") type -- but, like
Initialization's postAsMemberOf override, this is only ever invoked for a genuine field-access
expression (this.f, f, obj.f), never for the VariableTree of the field's own declaration.

import org.checkerframework.checker.tainting.qual.PolyTainted;
import org.checkerframework.checker.tainting.qual.Tainted;
import org.checkerframework.checker.tainting.qual.Untainted;
import org.checkerframework.framework.qual.HasQualifierParameter;

@HasQualifierParameter(Tainted.class)
public class PolyFieldInitC {
    // Field declared with an initializer, no explicit constructor (implicit default,
    // same `this` qualifier context as a concrete @Untainted constructor would have).
    @PolyTainted String field = taint();

    // For comparison: an explicit, concrete (non-poly) constructor assigning the same
    // value to the same field.
    public @Untainted PolyFieldInitC(boolean unused) {
        field = taint();
    }

    static @Tainted String taint() {
        throw new Error();
    }
}
javac -processor tainting PolyFieldInitC.java
error: [assignment.type.incompatible] incompatible types in assignment.
    @PolyTainted String field = taint();
                                     ^
  found   : @Tainted String
  required: @PolyTainted String
error: [assignment.type.incompatible] incompatible types in assignment.
        field = taint();
                     ^
  found   : @Tainted String
  required: @Untainted String
2 errors

Both assignments are (correctly, for this particular value) rejected, but for a different reason: the field declaration's own required type is the raw, unresolved @PolyTainted String (because postAsMemberOf/poly.resolve never ran for it), while the constructor's is the correctly-resolved concrete @Untainted String. The underlying types genuinely diverge, not just the diagnostic wording -- I confirmed this with several other values (@Untainted, reading another @PolyTainted field) without finding one where the divergence flips accept vs. reject in this particular (2-level) qualifier hierarchy, but the mechanism gap is the same one PR #2137 found and fixed for Initialization, and it applies to every checker built on @HasQualifierParameter/polymorphic field qualifiers, not just Tainting.

Not yet confirmed: a concrete repro where this actually produces a wrong verdict (a false accept or false reject), as opposed to "only" a differently-represented-but-equally-strict required type. That likely needs a custom qualifier hierarchy with 3+ levels, or a case involving a generic type argument, to expose -- left as an open question for whoever picks this up.

A fix would presumably parallel #1217's approach: in GenericAnnotatedTypeFactory#getAnnotatedTypeLhs's VARIABLE case (or a framework-level hook it can call into), invoke postAsMemberOf (or the relevant piece of it) for a non-static field declaration's implicit this.field target, the same way PR #2137 did for InitializationParentAnnotatedTypeFactory. Since this is now needed in at least two places (Initialization and, per this issue, the qualifier-polymorphism subsystem), it may be worth doing this once at the framework level rather than per-checker.

Environment: master 1c5baeded4, JDK 25.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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