framework.src.org.checkerframework.qualframework.poly.MethodParameterInference Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checker-qual Show documentation
Show all versions of checker-qual Show documentation
Checker Qual is the set of annotations (qualifiers) and supporting classes
used by the Checker Framework to type check Java source code. Please
see artifact:
org.checkerframework:checker
package org.checkerframework.qualframework.poly;
import java.util.*;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.qualframework.base.QualifiedTypeMirror;
import org.checkerframework.qualframework.base.QualifierHierarchy;
import org.checkerframework.qualframework.base.TypeHierarchy;
import org.checkerframework.qualframework.base.QualifierMapVisitor;
import org.checkerframework.qualframework.poly.PolyQual.*;
/** Helper class for performing method qualifier parameter inference.
*/
class MethodParameterInference {
/** The names of the qualifier parameters we are trying to infer. */
private List qualParams;
/** The types of the method's formal parameters (ordinary parameters, not
* type or qualifier parameters). */
private List>> formals;
/** The actual parameter types from the method call site, after type
* parameter inference has been done. */
private List>> actuals;
private QualifierHierarchy groundHierarchy;
private QualifierHierarchy> polyQualHierarchy;
private QualifierParameterHierarchy qualParamHierarchy;
private TypeHierarchy> typeHierarchy;
/** This variable will be set to true if an unsatisfiable constraint is
* found. */
private boolean unsatisfiable;
/** The strictest upper bound currently known for each qualifier parameter
* listed in {@link qualParams}. */
private List> upperBounds;
/** The strictest lower bound currently known for each qualifier parameter
* listed in {@link qualParams}. */
private List> lowerBounds;
/** Set to true if the infer() method has already been run. */
private boolean alreadyRan;
public MethodParameterInference(
List qualParams,
List>> formals,
List>> actuals,
QualifierHierarchy groundHierarchy,
QualifierHierarchy> polyQualHierarchy,
QualifierParameterHierarchy qualParamHierarchy,
TypeHierarchy> typeHierarchy) {
this.qualParams = qualParams;
this.formals = formals;
this.actuals = actuals;
this.groundHierarchy = groundHierarchy;
this.polyQualHierarchy = polyQualHierarchy;
this.qualParamHierarchy = qualParamHierarchy;
this.typeHierarchy = typeHierarchy;
this.unsatisfiable = false;
this.lowerBounds = new ArrayList<>();
this.upperBounds = new ArrayList<>();
for (int i = 0; i < qualParams.size(); ++i) {
this.upperBounds.add(polyQualHierarchy.getTop());
this.lowerBounds.add(polyQualHierarchy.getBottom());
}
this.alreadyRan = false;
}
// Methods for creating and manipulating inference variables.
private static final String INFER_TAG = "_INFER";
private QualVar makeInferVar(String name, int i) {
return new QualVar(INFER_TAG + i + ":" + name,
groundHierarchy.getBottom(), groundHierarchy.getTop());
}
private boolean isInferVar(PolyQual q) {
return q instanceof QualVar && ((QualVar)q).getName().startsWith(INFER_TAG);
}
private int inferVarIndex(PolyQual q) {
QualVar v = (QualVar)q;
String name = v.getName();
return Integer.parseInt(name.substring(INFER_TAG.length(), name.indexOf(':')));
}
/** Run qualifier parameter inference using the arguments provided to the
* constructor. */
public Map> infer() {
if (this.alreadyRan) {
throw new IllegalStateException("already ran infer() on this MethodParameterInference object");
}
this.alreadyRan = true;
List, Wildcard>> constraints = findConstraints();
for (Pair, Wildcard> p : constraints) {
if (p == null) {
unsatisfiable = true;
continue;
}
Wildcard subset = p.first;
Wildcard superset = p.second;
processConstraint(subset, superset);
}
return getAssignment();
}
/** Collect the containment constraints that arise from requiring each
* element in {@code actuals} to be a subtype of the corresponding element
* of {@code formals}. The resulting constraints will have inference
* variables in the place of the method's qualifier variables. */
private List, Wildcard>> findConstraints() {
// Replace each qualifier variable named in `this.qualParams` with a
// new inference variable. (An inference variable is just a qualifier
// variable with a special name.)
// Note that non-capture qualifier variables cannot have bounds other
// than bottom/top, so we don't need to look at the bounds on the
// parameter declarations.
List> inferVars = new ArrayList<>();
Map> inferSubst = new HashMap<>();
for (int i = 0; i < qualParams.size(); ++i) {
inferVars.add(makeInferVar(qualParams.get(i), i));
inferSubst.put(qualParams.get(i), new Wildcard(inferVars.get(i)));
}
List>> substitutedFormals = new ArrayList<>();
for (QualifiedTypeMirror> formal : formals) {
substitutedFormals.add(SUBSTITUTE_VISITOR.visit(formal, inferSubst));
}
// Generate constraints by requiring each actual parameter to be a
// subtype of the corresponding formal parameter (with inference
// variables substituted into the formal parameters).
List, Wildcard>> constraints = new ArrayList<>();
qualParamHierarchy.setConstraintTarget(constraints);
for (int i = 0; i < formals.size(); ++i) {
typeHierarchy.isSubtype(actuals.get(i), substitutedFormals.get(i));
}
qualParamHierarchy.setConstraintTarget(null);
return constraints;
}
/** Process a single containment constraint and update {@code upperBounds}
* and {@code lowerBounds} accordingly. */
private void processConstraint(Wildcard subset, Wildcard superset) {
if (!isInferVar(subset.getLowerBound())
&& !isInferVar(subset.getUpperBound())
&& !isInferVar(superset.getLowerBound())
&& !isInferVar(superset.getUpperBound())) {
// There are no vars to infer so the constraint isn't part of the solution
return;
}
addSubtypeBound(superset.getLowerBound(), subset.getLowerBound());
addSubtypeBound(subset.getLowerBound(), subset.getUpperBound());
addSubtypeBound(subset.getUpperBound(), superset.getUpperBound());
}
private void addSubtypeBound(PolyQual subtype, PolyQual supertype) {
if (isInferVar(subtype) && isInferVar(supertype)) {
throw new UnsupportedOperationException();
} else if (isInferVar(subtype)) {
// INFER#1 <: TAINTED. So the upper bound for #1 should be no
// higher than TAINTED.
int id = inferVarIndex(subtype);
PolyQual oldUpper = upperBounds.get(id);
upperBounds.set(id, polyQualHierarchy.greatestLowerBound(oldUpper, supertype));
} else if (isInferVar(supertype)) {
// TAINTED <: INFER#1. So the lower bound for #1 should be no
// lower than TAINTED.
int id = inferVarIndex(supertype);
PolyQual oldLower = lowerBounds.get(id);
lowerBounds.set(id, polyQualHierarchy.leastUpperBound(oldLower, subtype));
} else {
// The constraint UNTAINTED <: TAINTED is always true, so do
// nothing. The constraint TAINTED <: UNTAINTED is always false,
// so mark the current set of constraints as unsatisfiable.
if (!polyQualHierarchy.isSubtype(subtype, supertype)) {
this.unsatisfiable = true;
}
}
}
/** Build a map that gives the inferred qualifier for each qualifier
* parameter, or null if the constraints are unsatisfiable. */
public Map> getAssignment() {
if (unsatisfiable) {
return null;
}
Map> map = new HashMap<>();
for (int i = 0; i < qualParams.size(); ++i) {
map.put(qualParams.get(i), lowerBounds.get(i));
// TODO: Check that `lower <: upper`. The check itself is easy to
// implement, but getting correct error reporting out of
// QPTF.methodFromUse is a little harder.
}
return map;
}
/** Helper visitor to perform substitution at every location in a {@link
* QualifiedTypeMirror}. */
private QualifierMapVisitor, QualParams, Map>> SUBSTITUTE_VISITOR =
new QualifierMapVisitor, QualParams, Map>>() {
@Override
public QualParams process(QualParams params, Map> substs) {
if (params.equals(qualParamHierarchy.getBottom())) {
return qualParamHierarchy.getBottom();
}
return params.substituteAll(substs);
}
};
}