use of org.checkerframework.dataflow.expression.LocalVariable in project checker-framework by typetools.
the class MustCallConsistencyAnalyzer method updateObligationsWithInvocationResult.
/**
* Given a node representing a method or constructor call, updates the set of Obligations to
* account for the result, which is treated as a new resource alias. Adds the new resource alias
* to the set of an Obligation in {@code obligations}: either an existing Obligation if the result
* is definitely resource-aliased with it, or a new Obligation if not.
*
* @param obligations the currently-tracked Obligations. This is always side-effected: either a
* new resource alias is added to the resource alias set of an existing Obligation, or a new
* Obligation with a single-element resource alias set is created and added.
* @param node the invocation node whose result is to be tracked; must be {@link
* MethodInvocationNode} or {@link ObjectCreationNode}
*/
private void updateObligationsWithInvocationResult(Set<Obligation> obligations, Node node) {
Tree tree = node.getTree();
// Only track the result of the call if there is a temporary variable for the call node
// (because if there is no temporary, then the invocation must produce an untrackable value,
// such as a primitive type).
LocalVariableNode tmpVar = typeFactory.getTempVarForNode(node);
if (tmpVar == null) {
return;
}
// `mustCallAliases` is a (possibly-empty) list of arguments passed in a MustCallAlias position.
List<Node> mustCallAliases = getMustCallAliasArgumentNodes(node);
// If call returns @This, add the receiver to mustCallAliases.
if (node instanceof MethodInvocationNode && typeFactory.returnsThis((MethodInvocationTree) tree)) {
mustCallAliases.add(removeCastsAndGetTmpVarIfPresent(((MethodInvocationNode) node).getTarget().getReceiver()));
}
if (mustCallAliases.isEmpty()) {
// If mustCallAliases is an empty List, add tmpVarAsResourceAlias to a new set.
ResourceAlias tmpVarAsResourceAlias = new ResourceAlias(new LocalVariable(tmpVar), tree);
obligations.add(new Obligation(ImmutableSet.of(tmpVarAsResourceAlias)));
} else {
for (Node mustCallAlias : mustCallAliases) {
if (mustCallAlias instanceof FieldAccessNode) {
// Do not track the call result if the MustCallAlias argument is a field. Handling of
// @Owning fields is a completely separate check, and there is never a need to track an
// alias of a non-@Owning field, as by definition such a field does not have must-call
// obligations!
} else if (mustCallAlias instanceof LocalVariableNode) {
// If mustCallAlias is a local variable already being tracked, add tmpVarAsResourceAlias
// to the set containing mustCallAlias.
Obligation obligationContainingMustCallAlias = getObligationForVar(obligations, (LocalVariableNode) mustCallAlias);
if (obligationContainingMustCallAlias != null) {
ResourceAlias tmpVarAsResourceAlias = new ResourceAlias(new LocalVariable(tmpVar), tree, obligationContainingMustCallAlias.derivedFromMustCallAlias());
Set<ResourceAlias> newResourceAliasSet = FluentIterable.from(obligationContainingMustCallAlias.resourceAliases).append(tmpVarAsResourceAlias).toSet();
obligations.remove(obligationContainingMustCallAlias);
obligations.add(new Obligation(newResourceAliasSet));
// It is not an error if there is no Obligation containing the must-call alias. In that
// case, what has usually happened is that no Obligation was created in the first place.
// For example, when checking the invocation of a "wrapper stream" constructor, if the
// argument in the must-call alias position is some stream with no must-call obligations
// like a ByteArrayInputStream, then no Obligation object will have been created for it
// and therefore obligationContainingMustCallAlias will be null.
}
}
}
}
}
use of org.checkerframework.dataflow.expression.LocalVariable 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.dataflow.expression.LocalVariable in project checker-framework by typetools.
the class MustCallConsistencyAnalyzer method computeOwningParameters.
/**
* Finds {@link Owning} formal parameters for the method corresponding to a CFG.
*
* @param cfg the CFG
* @return the owning formal parameters of the method that corresponds to the given cfg, or an
* empty set if the given CFG doesn't correspond to a method body
*/
private Set<Obligation> computeOwningParameters(ControlFlowGraph cfg) {
// TODO what about lambdas?
if (cfg.getUnderlyingAST().getKind() == Kind.METHOD) {
MethodTree method = ((UnderlyingAST.CFGMethod) cfg.getUnderlyingAST()).getMethod();
Set<Obligation> result = new LinkedHashSet<>(1);
for (VariableTree param : method.getParameters()) {
Element paramElement = TreeUtils.elementFromDeclaration(param);
boolean hasMustCallAlias = typeFactory.hasMustCallAlias(paramElement);
if (hasMustCallAlias || (typeFactory.declaredTypeHasMustCall(param) && !checker.hasOption(MustCallChecker.NO_LIGHTWEIGHT_OWNERSHIP) && paramElement.getAnnotation(Owning.class) != null)) {
result.add(new Obligation(ImmutableSet.of(new ResourceAlias(new LocalVariable(paramElement), param, hasMustCallAlias))));
// Increment numMustCall for each @Owning parameter tracked by the enclosing method.
incrementNumMustCall(paramElement);
}
}
return result;
}
return Collections.emptySet();
}
use of org.checkerframework.dataflow.expression.LocalVariable in project checker-framework by typetools.
the class StringToJavaExpression method atLambdaParameter.
/**
* Parses a string as if it were written at one of the parameters of {@code lambdaTree}.
* Parameters of the lambda are expressed as {@link LocalVariable}s.
*
* @param expression a Java expression to parse
* @param lambdaTree the lambda tree
* @param parentPath path to the parent of {@code lambdaTree}; required because the expression can
* reference final local variables of the enclosing method
* @param checker checker used to get the {@link
* javax.annotation.processing.ProcessingEnvironment} and current {@link
* com.sun.source.tree.CompilationUnitTree}
* @return a {@code JavaExpression} for {@code expression}
* @throws JavaExpressionParseException if {@code expression} cannot be parsed
*/
static JavaExpression atLambdaParameter(String expression, LambdaExpressionTree lambdaTree, TreePath parentPath, SourceChecker checker) throws JavaExpressionParseException {
TypeMirror enclosingType = TreeUtils.typeOf(TreePathUtil.enclosingClass(parentPath));
JavaExpression receiver = JavaExpression.getPseudoReceiver(parentPath, enclosingType);
// If receiver isn't a ThisReference, then the lambda is in a static context and "this"
// cannot be referenced in the expression.
ThisReference thisReference = receiver instanceof ThisReference ? (ThisReference) receiver : null;
List<JavaExpression> paramsAsLocals = new ArrayList<>(lambdaTree.getParameters().size());
List<FormalParameter> parameters = new ArrayList<>(lambdaTree.getParameters().size());
int oneBasedIndex = 1;
for (VariableTree arg : lambdaTree.getParameters()) {
LocalVariable param = (LocalVariable) JavaExpression.fromVariableTree(arg);
paramsAsLocals.add(param);
parameters.add(new FormalParameter(oneBasedIndex, (VariableElement) param.getElement()));
oneBasedIndex++;
}
JavaExpression javaExpr = JavaExpressionParseUtil.parse(expression, enclosingType, thisReference, parameters, parentPath, checker.getPathToCompilationUnit(), checker.getProcessingEnvironment());
return ViewpointAdaptJavaExpression.viewpointAdapt(javaExpr, paramsAsLocals);
}
use of org.checkerframework.dataflow.expression.LocalVariable in project checker-framework by typetools.
the class InitializationVisitor method checkContract.
@Override
protected boolean checkContract(JavaExpression expr, AnnotationMirror necessaryAnnotation, AnnotationMirror inferredAnnotation, CFAbstractStore<?, ?> store) {
// also use the information about initialized fields to check contracts
final AnnotationMirror invariantAnno = atypeFactory.getFieldInvariantAnnotation();
if (!atypeFactory.getQualifierHierarchy().isSubtype(invariantAnno, necessaryAnnotation) || !(expr instanceof FieldAccess)) {
return super.checkContract(expr, necessaryAnnotation, inferredAnnotation, store);
}
if (expr.containsUnknown()) {
return false;
}
FieldAccess fa = (FieldAccess) expr;
if (fa.getReceiver() instanceof ThisReference || fa.getReceiver() instanceof ClassName) {
@SuppressWarnings("unchecked") Store s = (Store) store;
if (s.isFieldInitialized(fa.getField())) {
AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(fa.getField());
// is this an invariant-field?
if (AnnotationUtils.containsSame(fieldType.getAnnotations(), invariantAnno)) {
return true;
}
}
} else {
@SuppressWarnings("unchecked") Value value = (Value) store.getValue(fa.getReceiver());
Set<AnnotationMirror> receiverAnnoSet;
if (value != null) {
receiverAnnoSet = value.getAnnotations();
} else if (fa.getReceiver() instanceof LocalVariable) {
Element elem = ((LocalVariable) fa.getReceiver()).getElement();
AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(elem);
receiverAnnoSet = receiverType.getAnnotations();
} else {
// Is there anything better we could do?
return false;
}
boolean isReceiverInitialized = false;
for (AnnotationMirror anno : receiverAnnoSet) {
if (atypeFactory.isInitialized(anno)) {
isReceiverInitialized = true;
}
}
AnnotatedTypeMirror fieldType = atypeFactory.getAnnotatedType(fa.getField());
// has the invariant type.
if (isReceiverInitialized && AnnotationUtils.containsSame(fieldType.getAnnotations(), invariantAnno)) {
return true;
}
}
return super.checkContract(expr, necessaryAnnotation, inferredAnnotation, store);
}
Aggregations