use of org.checkerframework.checker.mustcall.MustCallAnnotatedTypeFactory in project checker-framework by typetools.
the class MustCallConsistencyAnalyzer method propagateObligationsToSuccessorBlocks.
/**
* Propagates a set of Obligations to successors, and performs consistency checks when variables
* are going out of scope.
*
* <p>The basic algorithm loops over the successor blocks of the current block. For each
* successor, it checks every Obligation in obligations. If the successor is an exit block or all
* of an Obligation's resource aliases might be going out of scope, then a consistency check
* occurs (with two exceptions, both related to temporary variables that don't actually get
* assigned; see code comments for details) and an error is issued if it fails. If the successor
* is any other kind of block and there is information about at least one of the Obligation's
* aliases in the successor store (i.e. the resource itself definitely does not go out of scope),
* then the Obligation is passed forward to the successor ("propagated") with any definitely
* out-of-scope aliases removed from its resource alias set.
*
* @param obligations Obligations for the current block
* @param currentBlock the current block
* @param visited block-Obligations pairs already analyzed or already on the worklist
* @param worklist current worklist
*/
private void propagateObligationsToSuccessorBlocks(Set<Obligation> obligations, Block currentBlock, Set<BlockWithObligations> visited, Deque<BlockWithObligations> worklist) {
List<Node> currentBlockNodes = currentBlock.getNodes();
// loop performs a consistency check for that Obligation.
for (Pair<Block, @Nullable TypeMirror> successorAndExceptionType : getSuccessorsExceptIgnoredExceptions(currentBlock)) {
Block successor = successorAndExceptionType.first;
// If nonnull, currentBlock is an ExceptionBlock.
TypeMirror exceptionType = successorAndExceptionType.second;
// successorObligations eventually contains the Obligations to propagate to successor. The
// loop below mutates it.
Set<Obligation> successorObligations = new LinkedHashSet<>();
// A detailed reason to give in the case that the last resource alias of an Obligation
// goes out of scope without a called-methods type that satisfies the corresponding
// must-call obligation along the current control-flow edge. Computed here for efficiency;
// used in the loop over the Obligations, below.
String exitReasonForErrorMessage = exceptionType == null ? // doesn't seem to provide additional helpful information.
"regular method exit" : "possible exceptional exit due to " + ((ExceptionBlock) currentBlock).getNode().getTree() + " with exception type " + exceptionType;
// Computed outside the Obligation loop for efficiency.
CFStore regularStoreOfSuccessor = analysis.getInput(successor).getRegularStore();
for (Obligation obligation : obligations) {
// This boolean is true if there is no evidence that the Obligation does not go out of
// scope - that is, if there is definitely a resource alias that is in scope in the
// successor.
boolean obligationGoesOutOfScopeBeforeSuccessor = true;
for (ResourceAlias resourceAlias : obligation.resourceAliases) {
if (aliasInScopeInSuccessor(regularStoreOfSuccessor, resourceAlias)) {
obligationGoesOutOfScopeBeforeSuccessor = false;
break;
}
}
// should occur.
if (successor.getType() == BlockType.SPECIAL_BLOCK || /* special blocks are exit blocks */
obligationGoesOutOfScopeBeforeSuccessor) {
MustCallAnnotatedTypeFactory mcAtf = typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class);
// are ignored. Whether exceptionType is null captures the logic of both of these cases.
if (exceptionType != null) {
Node exceptionalNode = NodeUtils.removeCasts(((ExceptionBlock) currentBlock).getNode());
LocalVariableNode tmpVarForExcNode = typeFactory.getTempVarForNode(exceptionalNode);
if (tmpVarForExcNode != null && obligation.resourceAliases.size() == 1 && obligation.canBeSatisfiedThrough(tmpVarForExcNode)) {
continue;
}
}
// unwrapped at various points in the analysis.
if (currentBlockNodes.size() == 1 && inCast(currentBlockNodes.get(0))) {
successorObligations.add(obligation);
continue;
}
// being resolved some other way.
if (obligation.derivedFromMustCallAlias()) {
checker.reportError(obligation.resourceAliases.asList().get(0).tree, "mustcallalias.out.of.scope", exitReasonForErrorMessage);
continue;
}
// Which stores from the called-methods and must-call checkers are used in
// the consistency check varies depending on the context. The rules are:
// 1. if the current block has no nodes (and therefore the store must come from a block
// rather than a node):
// 1a. if there is information about any alias in the resource alias set
// in the successor store, use the successor's CM and MC stores, which
// contain whatever information is true after this block finishes.
// 1b. if there is not any information about any alias in the resource alias
// set in the successor store, use the current blocks' CM and MC stores,
// which contain whatever information is true before this (empty) block.
// 2. if the current block has one or more nodes, always use the CM store after
// the last node. To decide which MC store to use:
// 2a. if the last node in the block is the invocation of an @CreatesMustCallFor
// method that might throw an exception, and the consistency check is for
// an exceptional path, use the MC store immediately before the method invocation,
// because the method threw an exception rather than finishing and therefore did
// not actually create any must-call obligation, so the MC store after might
// contain must-call obligations that do not need to be fulfilled along this path.
// 2b. in all other cases, use the MC store from after the last node in the block.
CFStore mcStore, cmStore;
if (currentBlockNodes.size() == 0) /* currentBlock is special or conditional */
{
cmStore = obligationGoesOutOfScopeBeforeSuccessor ? // 1a. (CM)
analysis.getInput(currentBlock).getRegularStore() : // 1b. (CM)
regularStoreOfSuccessor;
mcStore = mcAtf.getStoreForBlock(obligationGoesOutOfScopeBeforeSuccessor, // 1a. (MC)
currentBlock, // 1b. (MC)
successor);
} else {
// In this case, current block has at least one node.
// Use the called-methods store immediately after the last node in currentBlock.
// 2. (CM)
Node last = currentBlockNodes.get(currentBlockNodes.size() - 1);
cmStore = typeFactory.getStoreAfter(last);
// an exception. Otherwise, use the store after.
if (exceptionType != null && isInvocationOfCreatesMustCallForMethod(last)) {
// 2a. (MC)
mcStore = mcAtf.getStoreBefore(last);
} else {
// 2b. (MC)
mcStore = mcAtf.getStoreAfter(last);
}
}
checkMustCall(obligation, cmStore, mcStore, exitReasonForErrorMessage);
} else {
// In this case, there is info in the successor store about some alias in the Obligation.
// Handles the possibility that some resource in the Obligation may go out of scope.
Set<ResourceAlias> copyOfResourceAliases = new LinkedHashSet<>(obligation.resourceAliases);
copyOfResourceAliases.removeIf(alias -> !aliasInScopeInSuccessor(regularStoreOfSuccessor, alias));
successorObligations.add(new Obligation(copyOfResourceAliases));
}
}
propagate(new BlockWithObligations(successor, successorObligations), visited, worklist);
}
}
use of org.checkerframework.checker.mustcall.MustCallAnnotatedTypeFactory in project checker-framework by typetools.
the class MustCallConsistencyAnalyzer method updateObligationsForPseudoAssignment.
/**
* Update a set of tracked Obligations to account for a (pseudo-)assignment to some variable, as
* in a gen-kill dataflow analysis problem. That is, add ("gen") and remove ("kill") resource
* aliases from Obligations in the {@code obligations} set as appropriate based on the
* (pseudo-)assignment performed by {@code node}. This method may also remove an Obligation
* entirely if the analysis concludes that its resource alias set is empty because the last
* tracked alias to it has been overwritten (including checking that the must-call obligations
* were satisfied before the assignment).
*
* <p>Pseudo-assignments may include operations that "assign" to a temporary variable, exposing
* the possible value flow into the variable. E.g., for a ternary expression {@code b ? x : y}
* whose temporary variable is {@code t}, this method may process "assignments" {@code t = x} and
* {@code t = y}, thereby capturing the two possible values of {@code t}.
*
* @param obligations the tracked Obligations, which will be side-effected
* @param node the node performing the pseudo-assignment; it is not necessarily an assignment node
* @param lhsVar the left-hand side variable for the pseudo-assignment
* @param rhs the right-hand side for the pseudo-assignment, which must have been converted to a
* temporary variable (via a call to {@link
* ResourceLeakAnnotatedTypeFactory#getTempVarForNode})
*/
private void updateObligationsForPseudoAssignment(Set<Obligation> obligations, Node node, LocalVariableNode lhsVar, Node rhs) {
// Replacements to eventually perform in Obligations. This map is kept to avoid a
// ConcurrentModificationException in the loop below.
Map<Obligation, Obligation> replacements = new LinkedHashMap<>();
// Cache to re-use on subsequent iterations.
ResourceAlias aliasForAssignment = null;
for (Obligation obligation : obligations) {
// This is a non-null value iff the resource alias set for obligation needs to
// change because of the pseudo-assignment. The value of this variable is the new
// alias set for `obligation` if it is non-null.
Set<ResourceAlias> newResourceAliasesForObligation = null;
// Always kill the lhs var if it is present in the resource alias set for this Obligation
// by removing it from the resource alias set.
ResourceAlias aliasForLhs = obligation.getResourceAlias(lhsVar);
if (aliasForLhs != null) {
newResourceAliasesForObligation = new LinkedHashSet<>(obligation.resourceAliases);
newResourceAliasesForObligation.remove(aliasForLhs);
}
// by adding it to the resource alias set.
if (rhs instanceof LocalVariableNode && obligation.canBeSatisfiedThrough((LocalVariableNode) rhs)) {
LocalVariableNode rhsVar = (LocalVariableNode) rhs;
if (newResourceAliasesForObligation == null) {
newResourceAliasesForObligation = new LinkedHashSet<>(obligation.resourceAliases);
}
if (aliasForAssignment == null) {
// It is possible to observe assignments to temporary variables, e.g.,
// synthetic assignments to ternary expression variables in the CFG. For such
// cases, use the tree associated with the temp var for the resource alias,
// as that is the tree where errors should be reported.
Tree treeForAlias = typeFactory.isTempVar(lhsVar) ? typeFactory.getTreeForTempVar(lhsVar) : node.getTree();
aliasForAssignment = new ResourceAlias(new LocalVariable(lhsVar), treeForAlias);
}
newResourceAliasesForObligation.add(aliasForAssignment);
// Remove temp vars from tracking once they are assigned to another location.
if (typeFactory.isTempVar(rhsVar)) {
ResourceAlias aliasForRhs = obligation.getResourceAlias(rhsVar);
if (aliasForRhs != null) {
newResourceAliasesForObligation.remove(aliasForRhs);
}
}
}
// Obligation.
if (newResourceAliasesForObligation == null) {
continue;
}
if (newResourceAliasesForObligation.isEmpty()) {
// Because the last reference to the resource has been overwritten, check the must-call
// obligation.
MustCallAnnotatedTypeFactory mcAtf = typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class);
checkMustCall(obligation, typeFactory.getStoreBefore(node), mcAtf.getStoreBefore(node), "variable overwritten by assignment " + node.getTree());
replacements.put(obligation, null);
} else {
replacements.put(obligation, new Obligation(newResourceAliasesForObligation));
}
}
// Finally, update the set of Obligations according to the replacements.
for (Map.Entry<Obligation, Obligation> entry : replacements.entrySet()) {
obligations.remove(entry.getKey());
if (entry.getValue() != null && !entry.getValue().resourceAliases.isEmpty()) {
obligations.add(entry.getValue());
}
}
}
use of org.checkerframework.checker.mustcall.MustCallAnnotatedTypeFactory in project checker-framework by typetools.
the class MustCallConsistencyAnalyzer method checkReassignmentToField.
/**
* Issues an error if the given re-assignment to a non-final, owning field is not valid. A
* re-assignment is valid if the called methods type of the lhs before the assignment satisfies
* the must-call obligations of the field.
*
* @param obligations current tracked Obligations
* @param node an assignment to a non-final, owning field
*/
private void checkReassignmentToField(Set<Obligation> obligations, AssignmentNode node) {
Node lhsNode = node.getTarget();
if (!(lhsNode instanceof FieldAccessNode)) {
throw new TypeSystemError("checkReassignmentToField: non-field node " + node + " of class " + node.getClass());
}
FieldAccessNode lhs = (FieldAccessNode) lhsNode;
Node receiver = lhs.getReceiver();
// TODO: it would be better to defer getting the path until after checking
// for a CreatesMustCallFor annotation, because getting the path can be expensive.
// It might be possible to exploit the CFG structure to find the containing
// method (rather than using the path, as below), because if a method is being
// analyzed then it should be the root of the CFG (I think).
TreePath currentPath = typeFactory.getPath(node.getTree());
MethodTree enclosingMethodTree = TreePathUtil.enclosingMethod(currentPath);
if (enclosingMethodTree == null) {
// also a declaration must be a field initializer.
if (node.getTree().getKind() == Tree.Kind.VARIABLE) {
return;
} else {
// Issue an error if the field has a non-empty must-call type.
MustCallAnnotatedTypeFactory mcTypeFactory = typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class);
AnnotationMirror mcAnno = mcTypeFactory.getAnnotatedType(lhs.getElement()).getAnnotation(MustCall.class);
List<String> mcValues = AnnotationUtils.getElementValueArray(mcAnno, mcTypeFactory.getMustCallValueElement(), String.class);
if (mcValues.isEmpty()) {
return;
}
Element lhsElement = TreeUtils.elementFromTree(lhs.getTree());
checker.reportError(node.getTree(), "required.method.not.called", formatMissingMustCallMethods(mcValues), "field " + lhsElement.getSimpleName().toString(), lhsElement.asType().toString(), "Field assignment outside method or declaration might overwrite field's current value");
return;
}
}
// on the method declaration), or 2) the rhs is a null literal (so there's nothing to reset).
if (!(receiver instanceof LocalVariableNode && varTrackedInObligations(obligations, (LocalVariableNode) receiver)) && !(node.getExpression() instanceof NullLiteralNode)) {
checkEnclosingMethodIsCreatesMustCallFor(node, enclosingMethodTree);
}
MustCallAnnotatedTypeFactory mcTypeFactory = typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class);
// Get the Must Call type for the field. If there's info about this field in the store, use
// that. Otherwise, use the declared type of the field
CFStore mcStore = mcTypeFactory.getStoreBefore(lhs);
CFValue mcValue = mcStore.getValue(lhs);
AnnotationMirror mcAnno;
if (mcValue == null) {
// No store value, so use the declared type.
mcAnno = mcTypeFactory.getAnnotatedType(lhs.getElement()).getAnnotation(MustCall.class);
} else {
mcAnno = AnnotationUtils.getAnnotationByClass(mcValue.getAnnotations(), MustCall.class);
}
List<String> mcValues = AnnotationUtils.getElementValueArray(mcAnno, mcTypeFactory.getMustCallValueElement(), String.class);
if (mcValues.isEmpty()) {
return;
}
// Get the store before the RHS rather than the assignment node, because the CFG always has
// the RHS first. If the RHS has side-effects, then the assignment node's store will have
// had its inferred types erased.
Node rhs = node.getExpression();
CFStore cmStoreBefore = typeFactory.getStoreBefore(rhs);
CFValue cmValue = cmStoreBefore == null ? null : cmStoreBefore.getValue(lhs);
AnnotationMirror cmAnno = null;
if (cmValue != null) {
for (AnnotationMirror anno : cmValue.getAnnotations()) {
if (AnnotationUtils.areSameByName(anno, "org.checkerframework.checker.calledmethods.qual.CalledMethods")) {
cmAnno = anno;
break;
}
}
}
if (cmAnno == null) {
cmAnno = typeFactory.top;
}
if (!calledMethodsSatisfyMustCall(mcValues, cmAnno)) {
Element lhsElement = TreeUtils.elementFromTree(lhs.getTree());
if (!checker.shouldSkipUses(lhsElement)) {
checker.reportError(node.getTree(), "required.method.not.called", formatMissingMustCallMethods(mcValues), "field " + lhsElement.getSimpleName().toString(), lhsElement.asType().toString(), " Non-final owning field might be overwritten");
}
}
}
use of org.checkerframework.checker.mustcall.MustCallAnnotatedTypeFactory in project checker-framework by typetools.
the class ResourceLeakVisitor method visitMethod.
@Override
public Void visitMethod(MethodTree node, Void p) {
ExecutableElement elt = TreeUtils.elementFromDeclaration(node);
MustCallAnnotatedTypeFactory mcAtf = rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class);
List<String> cmcfValues = getCreatesMustCallForValues(elt, mcAtf, rlTypeFactory);
if (!cmcfValues.isEmpty()) {
// overwritten by a CMCF method, but the CMCF effect wouldn't occur.
for (ExecutableElement overridden : ElementUtils.getOverriddenMethods(elt, this.types)) {
List<String> overriddenCmcfValues = getCreatesMustCallForValues(overridden, mcAtf, rlTypeFactory);
if (!overriddenCmcfValues.containsAll(cmcfValues)) {
String foundCmcfValueString = String.join(", ", cmcfValues);
String neededCmcfValueString = String.join(", ", overriddenCmcfValues);
String actualClassname = ElementUtils.getEnclosingClassName(elt);
String overriddenClassname = ElementUtils.getEnclosingClassName(overridden);
checker.reportError(node, "creates.mustcall.for.override.invalid", actualClassname + "#" + elt, overriddenClassname + "#" + overridden, foundCmcfValueString, neededCmcfValueString);
}
}
}
return super.visitMethod(node, p);
}
use of org.checkerframework.checker.mustcall.MustCallAnnotatedTypeFactory in project checker-framework by typetools.
the class ResourceLeakTransfer method updateStoreWithTempVar.
/**
* This method either creates or looks up the temp var t for node, and then updates the store to
* give t the same type as node. Temporary variables are supported for expressions throughout this
* checker (and the Must Call Checker) to enable refinement of their types. See the documentation
* of {@link MustCallConsistencyAnalyzer} for more details.
*
* @param node the node to be assigned to a temporary variable
* @param result the transfer result containing the store to be modified
*/
public void updateStoreWithTempVar(TransferResult<CFValue, CFStore> result, Node node) {
// Must-call obligations on primitives are not supported.
if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) {
MustCallAnnotatedTypeFactory mcAtf = rlTypeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class);
LocalVariableNode temp = mcAtf.getTempVar(node);
if (temp != null) {
rlTypeFactory.addTempVar(temp, node.getTree());
JavaExpression localExp = JavaExpression.fromNode(temp);
AnnotationMirror anm = rlTypeFactory.getAnnotatedType(node.getTree()).getAnnotationInHierarchy(rlTypeFactory.top);
insertIntoStores(result, localExp, anm == null ? rlTypeFactory.top : anm);
}
}
}
Aggregations