Repository navigation
Polymorphic qualifier on a field is not resolved for the field's own declaration initializer #2142
Description
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