mirror of
https://github.com/llvm/llvm-project.git
synced 2025-05-01 03:36:07 +00:00

This was missing a call to `ignoreCFGOmittedNodes()`. As a result, the function would erroneously conclude that a block did not contain an expression consumed in a different block if the expression in question was surrounded by a `ParenExpr` in the consuming block. The patch adds a test that triggers this scenario (and fails without the fix). To prevent this kind of bug in the future, the patch also adds a new method `blockForStmt()` to `AdornedCFG` that calls `ignoreCFGOmittedNodes()` and is preferred over accessing `getStmtToBlock()` directly.
897 lines
31 KiB
C++
897 lines
31 KiB
C++
//===-- Transfer.cpp --------------------------------------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file defines transfer functions that evaluate program statements and
|
|
// update an environment accordingly.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/Analysis/FlowSensitive/Transfer.h"
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/AST/DeclBase.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/Expr.h"
|
|
#include "clang/AST/ExprCXX.h"
|
|
#include "clang/AST/OperationKinds.h"
|
|
#include "clang/AST/Stmt.h"
|
|
#include "clang/AST/StmtVisitor.h"
|
|
#include "clang/Analysis/FlowSensitive/ASTOps.h"
|
|
#include "clang/Analysis/FlowSensitive/AdornedCFG.h"
|
|
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
|
|
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
|
|
#include "clang/Analysis/FlowSensitive/NoopAnalysis.h"
|
|
#include "clang/Analysis/FlowSensitive/RecordOps.h"
|
|
#include "clang/Analysis/FlowSensitive/Value.h"
|
|
#include "clang/Basic/Builtins.h"
|
|
#include "clang/Basic/OperatorKinds.h"
|
|
#include "llvm/Support/Casting.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include <assert.h>
|
|
#include <cassert>
|
|
|
|
#define DEBUG_TYPE "dataflow"
|
|
|
|
namespace clang {
|
|
namespace dataflow {
|
|
|
|
const Environment *StmtToEnvMap::getEnvironment(const Stmt &S) const {
|
|
const CFGBlock *Block = ACFG.blockForStmt(S);
|
|
if (Block == nullptr) {
|
|
assert(false);
|
|
return nullptr;
|
|
}
|
|
if (!ACFG.isBlockReachable(*Block))
|
|
return nullptr;
|
|
if (Block->getBlockID() == CurBlockID)
|
|
return &CurState.Env;
|
|
const auto &State = BlockToState[Block->getBlockID()];
|
|
if (!(State))
|
|
return nullptr;
|
|
return &State->Env;
|
|
}
|
|
|
|
static BoolValue &evaluateBooleanEquality(const Expr &LHS, const Expr &RHS,
|
|
Environment &Env) {
|
|
Value *LHSValue = Env.getValue(LHS);
|
|
Value *RHSValue = Env.getValue(RHS);
|
|
|
|
if (LHSValue == RHSValue)
|
|
return Env.getBoolLiteralValue(true);
|
|
|
|
if (auto *LHSBool = dyn_cast_or_null<BoolValue>(LHSValue))
|
|
if (auto *RHSBool = dyn_cast_or_null<BoolValue>(RHSValue))
|
|
return Env.makeIff(*LHSBool, *RHSBool);
|
|
|
|
if (auto *LHSPtr = dyn_cast_or_null<PointerValue>(LHSValue))
|
|
if (auto *RHSPtr = dyn_cast_or_null<PointerValue>(RHSValue))
|
|
// If the storage locations are the same, the pointers definitely compare
|
|
// the same. If the storage locations are different, they may still alias,
|
|
// so we fall through to the case below that returns an atom.
|
|
if (&LHSPtr->getPointeeLoc() == &RHSPtr->getPointeeLoc())
|
|
return Env.getBoolLiteralValue(true);
|
|
|
|
return Env.makeAtomicBoolValue();
|
|
}
|
|
|
|
static BoolValue &unpackValue(BoolValue &V, Environment &Env) {
|
|
if (auto *Top = llvm::dyn_cast<TopBoolValue>(&V)) {
|
|
auto &A = Env.getDataflowAnalysisContext().arena();
|
|
return A.makeBoolValue(A.makeAtomRef(Top->getAtom()));
|
|
}
|
|
return V;
|
|
}
|
|
|
|
// Unpacks the value (if any) associated with `E` and updates `E` to the new
|
|
// value, if any unpacking occured. Also, does the lvalue-to-rvalue conversion,
|
|
// by skipping past the reference.
|
|
static Value *maybeUnpackLValueExpr(const Expr &E, Environment &Env) {
|
|
auto *Loc = Env.getStorageLocation(E);
|
|
if (Loc == nullptr)
|
|
return nullptr;
|
|
auto *Val = Env.getValue(*Loc);
|
|
|
|
auto *B = dyn_cast_or_null<BoolValue>(Val);
|
|
if (B == nullptr)
|
|
return Val;
|
|
|
|
auto &UnpackedVal = unpackValue(*B, Env);
|
|
if (&UnpackedVal == Val)
|
|
return Val;
|
|
Env.setValue(*Loc, UnpackedVal);
|
|
return &UnpackedVal;
|
|
}
|
|
|
|
static void propagateValue(const Expr &From, const Expr &To, Environment &Env) {
|
|
if (From.getType()->isRecordType())
|
|
return;
|
|
if (auto *Val = Env.getValue(From))
|
|
Env.setValue(To, *Val);
|
|
}
|
|
|
|
static void propagateStorageLocation(const Expr &From, const Expr &To,
|
|
Environment &Env) {
|
|
if (auto *Loc = Env.getStorageLocation(From))
|
|
Env.setStorageLocation(To, *Loc);
|
|
}
|
|
|
|
// Propagates the value or storage location of `From` to `To` in cases where
|
|
// `From` may be either a glvalue or a prvalue. `To` must be a glvalue iff
|
|
// `From` is a glvalue.
|
|
static void propagateValueOrStorageLocation(const Expr &From, const Expr &To,
|
|
Environment &Env) {
|
|
assert(From.isGLValue() == To.isGLValue());
|
|
if (From.isGLValue())
|
|
propagateStorageLocation(From, To, Env);
|
|
else
|
|
propagateValue(From, To, Env);
|
|
}
|
|
|
|
namespace {
|
|
|
|
class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
|
|
public:
|
|
TransferVisitor(const StmtToEnvMap &StmtToEnv, Environment &Env,
|
|
Environment::ValueModel &Model)
|
|
: StmtToEnv(StmtToEnv), Env(Env), Model(Model) {}
|
|
|
|
void VisitBinaryOperator(const BinaryOperator *S) {
|
|
const Expr *LHS = S->getLHS();
|
|
assert(LHS != nullptr);
|
|
|
|
const Expr *RHS = S->getRHS();
|
|
assert(RHS != nullptr);
|
|
|
|
// Do compound assignments up-front, as there are so many of them and we
|
|
// don't want to list all of them in the switch statement below.
|
|
// To avoid generating unnecessary values, we don't create a new value but
|
|
// instead leave it to the specific analysis to do this if desired.
|
|
if (S->isCompoundAssignmentOp())
|
|
propagateStorageLocation(*S->getLHS(), *S, Env);
|
|
|
|
switch (S->getOpcode()) {
|
|
case BO_Assign: {
|
|
auto *LHSLoc = Env.getStorageLocation(*LHS);
|
|
if (LHSLoc == nullptr)
|
|
break;
|
|
|
|
auto *RHSVal = Env.getValue(*RHS);
|
|
if (RHSVal == nullptr)
|
|
break;
|
|
|
|
// Assign a value to the storage location of the left-hand side.
|
|
Env.setValue(*LHSLoc, *RHSVal);
|
|
|
|
// Assign a storage location for the whole expression.
|
|
Env.setStorageLocation(*S, *LHSLoc);
|
|
break;
|
|
}
|
|
case BO_LAnd:
|
|
case BO_LOr: {
|
|
BoolValue &LHSVal = getLogicOperatorSubExprValue(*LHS);
|
|
BoolValue &RHSVal = getLogicOperatorSubExprValue(*RHS);
|
|
|
|
if (S->getOpcode() == BO_LAnd)
|
|
Env.setValue(*S, Env.makeAnd(LHSVal, RHSVal));
|
|
else
|
|
Env.setValue(*S, Env.makeOr(LHSVal, RHSVal));
|
|
break;
|
|
}
|
|
case BO_NE:
|
|
case BO_EQ: {
|
|
auto &LHSEqRHSValue = evaluateBooleanEquality(*LHS, *RHS, Env);
|
|
Env.setValue(*S, S->getOpcode() == BO_EQ ? LHSEqRHSValue
|
|
: Env.makeNot(LHSEqRHSValue));
|
|
break;
|
|
}
|
|
case BO_Comma: {
|
|
propagateValueOrStorageLocation(*RHS, *S, Env);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VisitDeclRefExpr(const DeclRefExpr *S) {
|
|
const ValueDecl *VD = S->getDecl();
|
|
assert(VD != nullptr);
|
|
|
|
// Some `DeclRefExpr`s aren't glvalues, so we can't associate them with a
|
|
// `StorageLocation`, and there's also no sensible `Value` that we can
|
|
// assign to them. Examples:
|
|
// - Non-static member variables
|
|
// - Non static member functions
|
|
// Note: Member operators are an exception to this, but apparently only
|
|
// if the `DeclRefExpr` is used within the callee of a
|
|
// `CXXOperatorCallExpr`. In other cases, for example when applying the
|
|
// address-of operator, the `DeclRefExpr` is a prvalue.
|
|
if (!S->isGLValue())
|
|
return;
|
|
|
|
auto *DeclLoc = Env.getStorageLocation(*VD);
|
|
if (DeclLoc == nullptr)
|
|
return;
|
|
|
|
Env.setStorageLocation(*S, *DeclLoc);
|
|
}
|
|
|
|
void VisitDeclStmt(const DeclStmt *S) {
|
|
// Group decls are converted into single decls in the CFG so the cast below
|
|
// is safe.
|
|
const auto &D = *cast<VarDecl>(S->getSingleDecl());
|
|
|
|
ProcessVarDecl(D);
|
|
}
|
|
|
|
void ProcessVarDecl(const VarDecl &D) {
|
|
// Static local vars are already initialized in `Environment`.
|
|
if (D.hasGlobalStorage())
|
|
return;
|
|
|
|
// If this is the holding variable for a `BindingDecl`, we may already
|
|
// have a storage location set up -- so check. (See also explanation below
|
|
// where we process the `BindingDecl`.)
|
|
if (D.getType()->isReferenceType() && Env.getStorageLocation(D) != nullptr)
|
|
return;
|
|
|
|
assert(Env.getStorageLocation(D) == nullptr);
|
|
|
|
Env.setStorageLocation(D, Env.createObject(D));
|
|
|
|
// `DecompositionDecl` must be handled after we've interpreted the loc
|
|
// itself, because the binding expression refers back to the
|
|
// `DecompositionDecl` (even though it has no written name).
|
|
if (const auto *Decomp = dyn_cast<DecompositionDecl>(&D)) {
|
|
// If VarDecl is a DecompositionDecl, evaluate each of its bindings. This
|
|
// needs to be evaluated after initializing the values in the storage for
|
|
// VarDecl, as the bindings refer to them.
|
|
// FIXME: Add support for ArraySubscriptExpr.
|
|
// FIXME: Consider adding AST nodes used in BindingDecls to the CFG.
|
|
for (const auto *B : Decomp->bindings()) {
|
|
if (auto *ME = dyn_cast_or_null<MemberExpr>(B->getBinding())) {
|
|
auto *DE = dyn_cast_or_null<DeclRefExpr>(ME->getBase());
|
|
if (DE == nullptr)
|
|
continue;
|
|
|
|
// ME and its base haven't been visited because they aren't included
|
|
// in the statements of the CFG basic block.
|
|
VisitDeclRefExpr(DE);
|
|
VisitMemberExpr(ME);
|
|
|
|
if (auto *Loc = Env.getStorageLocation(*ME))
|
|
Env.setStorageLocation(*B, *Loc);
|
|
} else if (auto *VD = B->getHoldingVar()) {
|
|
// Holding vars are used to back the `BindingDecl`s of tuple-like
|
|
// types. The holding var declarations appear after the
|
|
// `DecompositionDecl`, so we have to explicitly process them here
|
|
// to know their storage location. They will be processed a second
|
|
// time when we visit their `VarDecl`s, so we have code that protects
|
|
// against this above.
|
|
ProcessVarDecl(*VD);
|
|
auto *VDLoc = Env.getStorageLocation(*VD);
|
|
assert(VDLoc != nullptr);
|
|
Env.setStorageLocation(*B, *VDLoc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void VisitImplicitCastExpr(const ImplicitCastExpr *S) {
|
|
const Expr *SubExpr = S->getSubExpr();
|
|
assert(SubExpr != nullptr);
|
|
|
|
switch (S->getCastKind()) {
|
|
case CK_IntegralToBoolean: {
|
|
// This cast creates a new, boolean value from the integral value. We
|
|
// model that with a fresh value in the environment, unless it's already a
|
|
// boolean.
|
|
if (auto *SubExprVal =
|
|
dyn_cast_or_null<BoolValue>(Env.getValue(*SubExpr)))
|
|
Env.setValue(*S, *SubExprVal);
|
|
else
|
|
// FIXME: If integer modeling is added, then update this code to create
|
|
// the boolean based on the integer model.
|
|
Env.setValue(*S, Env.makeAtomicBoolValue());
|
|
break;
|
|
}
|
|
|
|
case CK_LValueToRValue: {
|
|
// When an L-value is used as an R-value, it may result in sharing, so we
|
|
// need to unpack any nested `Top`s.
|
|
auto *SubExprVal = maybeUnpackLValueExpr(*SubExpr, Env);
|
|
if (SubExprVal == nullptr)
|
|
break;
|
|
|
|
Env.setValue(*S, *SubExprVal);
|
|
break;
|
|
}
|
|
|
|
case CK_IntegralCast:
|
|
// FIXME: This cast creates a new integral value from the
|
|
// subexpression. But, because we don't model integers, we don't
|
|
// distinguish between this new value and the underlying one. If integer
|
|
// modeling is added, then update this code to create a fresh location and
|
|
// value.
|
|
case CK_UncheckedDerivedToBase:
|
|
case CK_ConstructorConversion:
|
|
case CK_UserDefinedConversion:
|
|
// FIXME: Add tests that excercise CK_UncheckedDerivedToBase,
|
|
// CK_ConstructorConversion, and CK_UserDefinedConversion.
|
|
case CK_NoOp: {
|
|
// FIXME: Consider making `Environment::getStorageLocation` skip noop
|
|
// expressions (this and other similar expressions in the file) instead
|
|
// of assigning them storage locations.
|
|
propagateValueOrStorageLocation(*SubExpr, *S, Env);
|
|
break;
|
|
}
|
|
case CK_NullToPointer: {
|
|
auto &NullPointerVal =
|
|
Env.getOrCreateNullPointerValue(S->getType()->getPointeeType());
|
|
Env.setValue(*S, NullPointerVal);
|
|
break;
|
|
}
|
|
case CK_NullToMemberPointer:
|
|
// FIXME: Implement pointers to members. For now, don't associate a value
|
|
// with this expression.
|
|
break;
|
|
case CK_FunctionToPointerDecay: {
|
|
StorageLocation *PointeeLoc = Env.getStorageLocation(*SubExpr);
|
|
if (PointeeLoc == nullptr)
|
|
break;
|
|
|
|
Env.setValue(*S, Env.create<PointerValue>(*PointeeLoc));
|
|
break;
|
|
}
|
|
case CK_BuiltinFnToFnPtr:
|
|
// Despite its name, the result type of `BuiltinFnToFnPtr` is a function,
|
|
// not a function pointer. In addition, builtin functions can only be
|
|
// called directly; it is not legal to take their address. We therefore
|
|
// don't need to create a value or storage location for them.
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VisitUnaryOperator(const UnaryOperator *S) {
|
|
const Expr *SubExpr = S->getSubExpr();
|
|
assert(SubExpr != nullptr);
|
|
|
|
switch (S->getOpcode()) {
|
|
case UO_Deref: {
|
|
const auto *SubExprVal = Env.get<PointerValue>(*SubExpr);
|
|
if (SubExprVal == nullptr)
|
|
break;
|
|
|
|
Env.setStorageLocation(*S, SubExprVal->getPointeeLoc());
|
|
break;
|
|
}
|
|
case UO_AddrOf: {
|
|
// FIXME: Model pointers to members.
|
|
if (S->getType()->isMemberPointerType())
|
|
break;
|
|
|
|
if (StorageLocation *PointeeLoc = Env.getStorageLocation(*SubExpr))
|
|
Env.setValue(*S, Env.create<PointerValue>(*PointeeLoc));
|
|
break;
|
|
}
|
|
case UO_LNot: {
|
|
auto *SubExprVal = dyn_cast_or_null<BoolValue>(Env.getValue(*SubExpr));
|
|
if (SubExprVal == nullptr)
|
|
break;
|
|
|
|
Env.setValue(*S, Env.makeNot(*SubExprVal));
|
|
break;
|
|
}
|
|
case UO_PreInc:
|
|
case UO_PreDec:
|
|
// Propagate the storage location and clear out any value associated with
|
|
// it (to represent the fact that the value has definitely changed).
|
|
// To avoid generating unnecessary values, we leave it to the specific
|
|
// analysis to create a new value if desired.
|
|
propagateStorageLocation(*S->getSubExpr(), *S, Env);
|
|
if (StorageLocation *Loc = Env.getStorageLocation(*S->getSubExpr()))
|
|
Env.clearValue(*Loc);
|
|
break;
|
|
case UO_PostInc:
|
|
case UO_PostDec:
|
|
// Propagate the old value, then clear out any value associated with the
|
|
// storage location (to represent the fact that the value has definitely
|
|
// changed). See above for rationale.
|
|
propagateValue(*S->getSubExpr(), *S, Env);
|
|
if (StorageLocation *Loc = Env.getStorageLocation(*S->getSubExpr()))
|
|
Env.clearValue(*Loc);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VisitCXXThisExpr(const CXXThisExpr *S) {
|
|
auto *ThisPointeeLoc = Env.getThisPointeeStorageLocation();
|
|
if (ThisPointeeLoc == nullptr)
|
|
// Unions are not supported yet, and will not have a location for the
|
|
// `this` expression's pointee.
|
|
return;
|
|
|
|
Env.setValue(*S, Env.create<PointerValue>(*ThisPointeeLoc));
|
|
}
|
|
|
|
void VisitCXXNewExpr(const CXXNewExpr *S) {
|
|
if (Value *Val = Env.createValue(S->getType()))
|
|
Env.setValue(*S, *Val);
|
|
}
|
|
|
|
void VisitCXXDeleteExpr(const CXXDeleteExpr *S) {
|
|
// Empty method.
|
|
// We consciously don't do anything on deletes. Diagnosing double deletes
|
|
// (for example) should be done by a specific analysis, not by the
|
|
// framework.
|
|
}
|
|
|
|
void VisitReturnStmt(const ReturnStmt *S) {
|
|
if (!Env.getDataflowAnalysisContext().getOptions().ContextSensitiveOpts)
|
|
return;
|
|
|
|
auto *Ret = S->getRetValue();
|
|
if (Ret == nullptr)
|
|
return;
|
|
|
|
if (Ret->isPRValue()) {
|
|
if (Ret->getType()->isRecordType())
|
|
return;
|
|
|
|
auto *Val = Env.getValue(*Ret);
|
|
if (Val == nullptr)
|
|
return;
|
|
|
|
// FIXME: Model NRVO.
|
|
Env.setReturnValue(Val);
|
|
} else {
|
|
auto *Loc = Env.getStorageLocation(*Ret);
|
|
if (Loc == nullptr)
|
|
return;
|
|
|
|
// FIXME: Model NRVO.
|
|
Env.setReturnStorageLocation(Loc);
|
|
}
|
|
}
|
|
|
|
void VisitMemberExpr(const MemberExpr *S) {
|
|
ValueDecl *Member = S->getMemberDecl();
|
|
assert(Member != nullptr);
|
|
|
|
// FIXME: Consider assigning pointer values to function member expressions.
|
|
if (Member->isFunctionOrFunctionTemplate())
|
|
return;
|
|
|
|
// FIXME: if/when we add support for modeling enums, use that support here.
|
|
if (isa<EnumConstantDecl>(Member))
|
|
return;
|
|
|
|
if (auto *D = dyn_cast<VarDecl>(Member)) {
|
|
if (D->hasGlobalStorage()) {
|
|
auto *VarDeclLoc = Env.getStorageLocation(*D);
|
|
if (VarDeclLoc == nullptr)
|
|
return;
|
|
|
|
Env.setStorageLocation(*S, *VarDeclLoc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
RecordStorageLocation *BaseLoc = getBaseObjectLocation(*S, Env);
|
|
if (BaseLoc == nullptr)
|
|
return;
|
|
|
|
auto *MemberLoc = BaseLoc->getChild(*Member);
|
|
if (MemberLoc == nullptr)
|
|
return;
|
|
Env.setStorageLocation(*S, *MemberLoc);
|
|
}
|
|
|
|
void VisitCXXDefaultArgExpr(const CXXDefaultArgExpr *S) {
|
|
const Expr *ArgExpr = S->getExpr();
|
|
assert(ArgExpr != nullptr);
|
|
propagateValueOrStorageLocation(*ArgExpr, *S, Env);
|
|
|
|
if (S->isPRValue() && S->getType()->isRecordType()) {
|
|
auto &Loc = Env.getResultObjectLocation(*S);
|
|
Env.initializeFieldsWithValues(Loc);
|
|
}
|
|
}
|
|
|
|
void VisitCXXDefaultInitExpr(const CXXDefaultInitExpr *S) {
|
|
const Expr *InitExpr = S->getExpr();
|
|
assert(InitExpr != nullptr);
|
|
|
|
// If this is a prvalue of record type, the handler for `*InitExpr` (if one
|
|
// exists) will initialize the result object; there is no value to propgate
|
|
// here.
|
|
if (S->getType()->isRecordType() && S->isPRValue())
|
|
return;
|
|
|
|
propagateValueOrStorageLocation(*InitExpr, *S, Env);
|
|
}
|
|
|
|
void VisitCXXConstructExpr(const CXXConstructExpr *S) {
|
|
const CXXConstructorDecl *ConstructorDecl = S->getConstructor();
|
|
assert(ConstructorDecl != nullptr);
|
|
|
|
// `CXXConstructExpr` can have array type if default-initializing an array
|
|
// of records. We don't handle this specifically beyond potentially inlining
|
|
// the call.
|
|
if (!S->getType()->isRecordType()) {
|
|
transferInlineCall(S, ConstructorDecl);
|
|
return;
|
|
}
|
|
|
|
RecordStorageLocation &Loc = Env.getResultObjectLocation(*S);
|
|
|
|
if (ConstructorDecl->isCopyOrMoveConstructor()) {
|
|
// It is permissible for a copy/move constructor to have additional
|
|
// parameters as long as they have default arguments defined for them.
|
|
assert(S->getNumArgs() != 0);
|
|
|
|
const Expr *Arg = S->getArg(0);
|
|
assert(Arg != nullptr);
|
|
|
|
auto *ArgLoc = Env.get<RecordStorageLocation>(*Arg);
|
|
if (ArgLoc == nullptr)
|
|
return;
|
|
|
|
// Even if the copy/move constructor call is elidable, we choose to copy
|
|
// the record in all cases (which isn't wrong, just potentially not
|
|
// optimal).
|
|
copyRecord(*ArgLoc, Loc, Env);
|
|
return;
|
|
}
|
|
|
|
Env.initializeFieldsWithValues(Loc, S->getType());
|
|
|
|
transferInlineCall(S, ConstructorDecl);
|
|
}
|
|
|
|
void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *S) {
|
|
if (S->getOperator() == OO_Equal) {
|
|
assert(S->getNumArgs() == 2);
|
|
|
|
const Expr *Arg0 = S->getArg(0);
|
|
assert(Arg0 != nullptr);
|
|
|
|
const Expr *Arg1 = S->getArg(1);
|
|
assert(Arg1 != nullptr);
|
|
|
|
// Evaluate only copy and move assignment operators.
|
|
const auto *Method =
|
|
dyn_cast_or_null<CXXMethodDecl>(S->getDirectCallee());
|
|
if (!Method)
|
|
return;
|
|
if (!Method->isCopyAssignmentOperator() &&
|
|
!Method->isMoveAssignmentOperator())
|
|
return;
|
|
|
|
RecordStorageLocation *LocSrc = nullptr;
|
|
if (Arg1->isPRValue()) {
|
|
LocSrc = &Env.getResultObjectLocation(*Arg1);
|
|
} else {
|
|
LocSrc = Env.get<RecordStorageLocation>(*Arg1);
|
|
}
|
|
auto *LocDst = Env.get<RecordStorageLocation>(*Arg0);
|
|
|
|
if (LocSrc == nullptr || LocDst == nullptr)
|
|
return;
|
|
|
|
copyRecord(*LocSrc, *LocDst, Env);
|
|
|
|
// The assignment operator can have an arbitrary return type. We model the
|
|
// return value only if the return type is the same as or a base class of
|
|
// the destination type.
|
|
if (S->getType().getCanonicalType().getUnqualifiedType() !=
|
|
LocDst->getType().getCanonicalType().getUnqualifiedType()) {
|
|
auto ReturnDecl = S->getType()->getAsCXXRecordDecl();
|
|
auto DstDecl = LocDst->getType()->getAsCXXRecordDecl();
|
|
if (ReturnDecl == nullptr || DstDecl == nullptr)
|
|
return;
|
|
if (!DstDecl->isDerivedFrom(ReturnDecl))
|
|
return;
|
|
}
|
|
|
|
if (S->isGLValue())
|
|
Env.setStorageLocation(*S, *LocDst);
|
|
else
|
|
copyRecord(*LocDst, Env.getResultObjectLocation(*S), Env);
|
|
|
|
return;
|
|
}
|
|
|
|
// `CXXOperatorCallExpr` can be a prvalue. Call `VisitCallExpr`() to
|
|
// initialize the prvalue's fields with values.
|
|
VisitCallExpr(S);
|
|
}
|
|
|
|
void VisitCXXRewrittenBinaryOperator(const CXXRewrittenBinaryOperator *RBO) {
|
|
propagateValue(*RBO->getSemanticForm(), *RBO, Env);
|
|
}
|
|
|
|
void VisitCallExpr(const CallExpr *S) {
|
|
// Of clang's builtins, only `__builtin_expect` is handled explicitly, since
|
|
// others (like trap, debugtrap, and unreachable) are handled by CFG
|
|
// construction.
|
|
if (S->isCallToStdMove()) {
|
|
assert(S->getNumArgs() == 1);
|
|
|
|
const Expr *Arg = S->getArg(0);
|
|
assert(Arg != nullptr);
|
|
|
|
auto *ArgLoc = Env.getStorageLocation(*Arg);
|
|
if (ArgLoc == nullptr)
|
|
return;
|
|
|
|
Env.setStorageLocation(*S, *ArgLoc);
|
|
} else if (S->getDirectCallee() != nullptr &&
|
|
S->getDirectCallee()->getBuiltinID() ==
|
|
Builtin::BI__builtin_expect) {
|
|
assert(S->getNumArgs() > 0);
|
|
assert(S->getArg(0) != nullptr);
|
|
auto *ArgVal = Env.getValue(*S->getArg(0));
|
|
if (ArgVal == nullptr)
|
|
return;
|
|
Env.setValue(*S, *ArgVal);
|
|
} else if (const FunctionDecl *F = S->getDirectCallee()) {
|
|
transferInlineCall(S, F);
|
|
|
|
// If this call produces a prvalue of record type, initialize its fields
|
|
// with values.
|
|
if (S->getType()->isRecordType() && S->isPRValue()) {
|
|
RecordStorageLocation &Loc = Env.getResultObjectLocation(*S);
|
|
Env.initializeFieldsWithValues(Loc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *S) {
|
|
const Expr *SubExpr = S->getSubExpr();
|
|
assert(SubExpr != nullptr);
|
|
|
|
StorageLocation &Loc = Env.createStorageLocation(*S);
|
|
Env.setStorageLocation(*S, Loc);
|
|
|
|
if (SubExpr->getType()->isRecordType())
|
|
// Nothing else left to do -- we initialized the record when transferring
|
|
// `SubExpr`.
|
|
return;
|
|
|
|
if (Value *SubExprVal = Env.getValue(*SubExpr))
|
|
Env.setValue(Loc, *SubExprVal);
|
|
}
|
|
|
|
void VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *S) {
|
|
const Expr *SubExpr = S->getSubExpr();
|
|
assert(SubExpr != nullptr);
|
|
|
|
propagateValue(*SubExpr, *S, Env);
|
|
}
|
|
|
|
void VisitCXXStaticCastExpr(const CXXStaticCastExpr *S) {
|
|
if (S->getCastKind() == CK_NoOp) {
|
|
const Expr *SubExpr = S->getSubExpr();
|
|
assert(SubExpr != nullptr);
|
|
|
|
propagateValueOrStorageLocation(*SubExpr, *S, Env);
|
|
}
|
|
}
|
|
|
|
void VisitConditionalOperator(const ConditionalOperator *S) {
|
|
const Environment *TrueEnv = StmtToEnv.getEnvironment(*S->getTrueExpr());
|
|
const Environment *FalseEnv = StmtToEnv.getEnvironment(*S->getFalseExpr());
|
|
|
|
if (TrueEnv == nullptr || FalseEnv == nullptr) {
|
|
// If the true or false branch is dead, we may not have an environment for
|
|
// it. We could handle this specifically by forwarding the value or
|
|
// location of the live branch, but this case is rare enough that this
|
|
// probably isn't worth the additional complexity.
|
|
return;
|
|
}
|
|
|
|
if (S->isGLValue()) {
|
|
StorageLocation *TrueLoc = TrueEnv->getStorageLocation(*S->getTrueExpr());
|
|
StorageLocation *FalseLoc =
|
|
FalseEnv->getStorageLocation(*S->getFalseExpr());
|
|
if (TrueLoc == FalseLoc && TrueLoc != nullptr)
|
|
Env.setStorageLocation(*S, *TrueLoc);
|
|
} else if (!S->getType()->isRecordType()) {
|
|
// The conditional operator can evaluate to either of the values of the
|
|
// two branches. To model this, join these two values together to yield
|
|
// the result of the conditional operator.
|
|
// Note: Most joins happen in `computeBlockInputState()`, but this case is
|
|
// different:
|
|
// - `computeBlockInputState()` (which in turn calls `Environment::join()`
|
|
// joins values associated with the _same_ expression or storage
|
|
// location, then associates the joined value with that expression or
|
|
// storage location. This join has nothing to do with transfer --
|
|
// instead, it joins together the results of performing transfer on two
|
|
// different blocks.
|
|
// - Here, we join values associated with _different_ expressions (the
|
|
// true and false branch), then associate the joined value with a third
|
|
// expression (the conditional operator itself). This join is what it
|
|
// means to perform transfer on the conditional operator.
|
|
if (Value *Val = Environment::joinValues(
|
|
S->getType(), TrueEnv->getValue(*S->getTrueExpr()), *TrueEnv,
|
|
FalseEnv->getValue(*S->getFalseExpr()), *FalseEnv, Env, Model))
|
|
Env.setValue(*S, *Val);
|
|
}
|
|
}
|
|
|
|
void VisitInitListExpr(const InitListExpr *S) {
|
|
QualType Type = S->getType();
|
|
|
|
if (!Type->isRecordType()) {
|
|
// Until array initialization is implemented, we skip arrays and don't
|
|
// need to care about cases where `getNumInits() > 1`.
|
|
if (!Type->isArrayType() && S->getNumInits() == 1)
|
|
propagateValueOrStorageLocation(*S->getInit(0), *S, Env);
|
|
return;
|
|
}
|
|
|
|
// If the initializer list is transparent, there's nothing to do.
|
|
if (S->isSemanticForm() && S->isTransparent())
|
|
return;
|
|
|
|
RecordStorageLocation &Loc = Env.getResultObjectLocation(*S);
|
|
|
|
// Initialization of base classes and fields of record type happens when we
|
|
// visit the nested `CXXConstructExpr` or `InitListExpr` for that base class
|
|
// or field. We therefore only need to deal with fields of non-record type
|
|
// here.
|
|
|
|
RecordInitListHelper InitListHelper(S);
|
|
|
|
for (auto [Field, Init] : InitListHelper.field_inits()) {
|
|
if (Field->getType()->isRecordType())
|
|
continue;
|
|
if (Field->getType()->isReferenceType()) {
|
|
assert(Field->getType().getCanonicalType()->getPointeeType() ==
|
|
Init->getType().getCanonicalType());
|
|
Loc.setChild(*Field, &Env.createObject(Field->getType(), Init));
|
|
continue;
|
|
}
|
|
assert(Field->getType().getCanonicalType().getUnqualifiedType() ==
|
|
Init->getType().getCanonicalType().getUnqualifiedType());
|
|
StorageLocation *FieldLoc = Loc.getChild(*Field);
|
|
// Locations for non-reference fields must always be non-null.
|
|
assert(FieldLoc != nullptr);
|
|
Value *Val = Env.getValue(*Init);
|
|
if (Val == nullptr && isa<ImplicitValueInitExpr>(Init) &&
|
|
Init->getType()->isPointerType())
|
|
Val =
|
|
&Env.getOrCreateNullPointerValue(Init->getType()->getPointeeType());
|
|
if (Val == nullptr)
|
|
Val = Env.createValue(Field->getType());
|
|
if (Val != nullptr)
|
|
Env.setValue(*FieldLoc, *Val);
|
|
}
|
|
|
|
for (const auto &[FieldName, FieldLoc] : Loc.synthetic_fields()) {
|
|
QualType FieldType = FieldLoc->getType();
|
|
if (FieldType->isRecordType()) {
|
|
Env.initializeFieldsWithValues(*cast<RecordStorageLocation>(FieldLoc));
|
|
} else {
|
|
if (Value *Val = Env.createValue(FieldType))
|
|
Env.setValue(*FieldLoc, *Val);
|
|
}
|
|
}
|
|
|
|
// FIXME: Implement array initialization.
|
|
}
|
|
|
|
void VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *S) {
|
|
Env.setValue(*S, Env.getBoolLiteralValue(S->getValue()));
|
|
}
|
|
|
|
void VisitIntegerLiteral(const IntegerLiteral *S) {
|
|
Env.setValue(*S, Env.getIntLiteralValue(S->getValue()));
|
|
}
|
|
|
|
void VisitParenExpr(const ParenExpr *S) {
|
|
// The CFG does not contain `ParenExpr` as top-level statements in basic
|
|
// blocks, however manual traversal to sub-expressions may encounter them.
|
|
// Redirect to the sub-expression.
|
|
auto *SubExpr = S->getSubExpr();
|
|
assert(SubExpr != nullptr);
|
|
Visit(SubExpr);
|
|
}
|
|
|
|
void VisitExprWithCleanups(const ExprWithCleanups *S) {
|
|
// The CFG does not contain `ExprWithCleanups` as top-level statements in
|
|
// basic blocks, however manual traversal to sub-expressions may encounter
|
|
// them. Redirect to the sub-expression.
|
|
auto *SubExpr = S->getSubExpr();
|
|
assert(SubExpr != nullptr);
|
|
Visit(SubExpr);
|
|
}
|
|
|
|
private:
|
|
/// Returns the value for the sub-expression `SubExpr` of a logic operator.
|
|
BoolValue &getLogicOperatorSubExprValue(const Expr &SubExpr) {
|
|
// `SubExpr` and its parent logic operator might be part of different basic
|
|
// blocks. We try to access the value that is assigned to `SubExpr` in the
|
|
// corresponding environment.
|
|
if (const Environment *SubExprEnv = StmtToEnv.getEnvironment(SubExpr))
|
|
if (auto *Val =
|
|
dyn_cast_or_null<BoolValue>(SubExprEnv->getValue(SubExpr)))
|
|
return *Val;
|
|
|
|
// The sub-expression may lie within a basic block that isn't reachable,
|
|
// even if we need it to evaluate the current (reachable) expression
|
|
// (see https://discourse.llvm.org/t/70775). In this case, visit `SubExpr`
|
|
// within the current environment and then try to get the value that gets
|
|
// assigned to it.
|
|
if (Env.getValue(SubExpr) == nullptr)
|
|
Visit(&SubExpr);
|
|
if (auto *Val = dyn_cast_or_null<BoolValue>(Env.getValue(SubExpr)))
|
|
return *Val;
|
|
|
|
// If the value of `SubExpr` is still unknown, we create a fresh symbolic
|
|
// boolean value for it.
|
|
return Env.makeAtomicBoolValue();
|
|
}
|
|
|
|
// If context sensitivity is enabled, try to analyze the body of the callee
|
|
// `F` of `S`. The type `E` must be either `CallExpr` or `CXXConstructExpr`.
|
|
template <typename E>
|
|
void transferInlineCall(const E *S, const FunctionDecl *F) {
|
|
const auto &Options = Env.getDataflowAnalysisContext().getOptions();
|
|
if (!(Options.ContextSensitiveOpts &&
|
|
Env.canDescend(Options.ContextSensitiveOpts->Depth, F)))
|
|
return;
|
|
|
|
const AdornedCFG *ACFG = Env.getDataflowAnalysisContext().getAdornedCFG(F);
|
|
if (!ACFG)
|
|
return;
|
|
|
|
// FIXME: We don't support context-sensitive analysis of recursion, so
|
|
// we should return early here if `F` is the same as the `FunctionDecl`
|
|
// holding `S` itself.
|
|
|
|
auto ExitBlock = ACFG->getCFG().getExit().getBlockID();
|
|
|
|
auto CalleeEnv = Env.pushCall(S);
|
|
|
|
// FIXME: Use the same analysis as the caller for the callee. Note,
|
|
// though, that doing so would require support for changing the analysis's
|
|
// ASTContext.
|
|
auto Analysis = NoopAnalysis(ACFG->getDecl().getASTContext(),
|
|
DataflowAnalysisOptions{Options});
|
|
|
|
auto BlockToOutputState =
|
|
dataflow::runDataflowAnalysis(*ACFG, Analysis, CalleeEnv);
|
|
assert(BlockToOutputState);
|
|
assert(ExitBlock < BlockToOutputState->size());
|
|
|
|
auto &ExitState = (*BlockToOutputState)[ExitBlock];
|
|
assert(ExitState);
|
|
|
|
Env.popCall(S, ExitState->Env);
|
|
}
|
|
|
|
const StmtToEnvMap &StmtToEnv;
|
|
Environment &Env;
|
|
Environment::ValueModel &Model;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void transfer(const StmtToEnvMap &StmtToEnv, const Stmt &S, Environment &Env,
|
|
Environment::ValueModel &Model) {
|
|
TransferVisitor(StmtToEnv, Env, Model).Visit(&S);
|
|
}
|
|
|
|
} // namespace dataflow
|
|
} // namespace clang
|