mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-29 01:26:07 +00:00

We can use *Set::insert_range to collapse: for (auto Elem : Range) Set.insert(E); down to: Set.insert_range(Range); In some cases, we can further fold that into the set declaration.
316 lines
11 KiB
C++
316 lines
11 KiB
C++
//===-- ASTOps.cc -------------------------------*- 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Operations on AST nodes that are used in flow-sensitive analysis.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/Analysis/FlowSensitive/ASTOps.h"
|
|
#include "clang/AST/ASTLambda.h"
|
|
#include "clang/AST/ComputeDependence.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/Stmt.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
|
|
#include "clang/Basic/LLVM.h"
|
|
#include "llvm/ADT/DenseSet.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include <cassert>
|
|
#include <iterator>
|
|
#include <vector>
|
|
|
|
#define DEBUG_TYPE "dataflow"
|
|
|
|
namespace clang::dataflow {
|
|
|
|
const Expr &ignoreCFGOmittedNodes(const Expr &E) {
|
|
const Expr *Current = &E;
|
|
const Expr *Last = nullptr;
|
|
while (Current != Last) {
|
|
Last = Current;
|
|
if (auto *EWC = dyn_cast<ExprWithCleanups>(Current)) {
|
|
Current = EWC->getSubExpr();
|
|
assert(Current != nullptr);
|
|
}
|
|
if (auto *CE = dyn_cast<ConstantExpr>(Current)) {
|
|
Current = CE->getSubExpr();
|
|
assert(Current != nullptr);
|
|
}
|
|
Current = Current->IgnoreParens();
|
|
assert(Current != nullptr);
|
|
}
|
|
return *Current;
|
|
}
|
|
|
|
const Stmt &ignoreCFGOmittedNodes(const Stmt &S) {
|
|
if (auto *E = dyn_cast<Expr>(&S))
|
|
return ignoreCFGOmittedNodes(*E);
|
|
return S;
|
|
}
|
|
|
|
// FIXME: Does not precisely handle non-virtual diamond inheritance. A single
|
|
// field decl will be modeled for all instances of the inherited field.
|
|
static void getFieldsFromClassHierarchy(QualType Type, FieldSet &Fields) {
|
|
if (Type->isIncompleteType() || Type->isDependentType() ||
|
|
!Type->isRecordType())
|
|
return;
|
|
|
|
Fields.insert_range(Type->getAsRecordDecl()->fields());
|
|
if (auto *CXXRecord = Type->getAsCXXRecordDecl())
|
|
for (const CXXBaseSpecifier &Base : CXXRecord->bases())
|
|
getFieldsFromClassHierarchy(Base.getType(), Fields);
|
|
}
|
|
|
|
/// Gets the set of all fields in the type.
|
|
FieldSet getObjectFields(QualType Type) {
|
|
FieldSet Fields;
|
|
getFieldsFromClassHierarchy(Type, Fields);
|
|
return Fields;
|
|
}
|
|
|
|
bool containsSameFields(const FieldSet &Fields,
|
|
const RecordStorageLocation::FieldToLoc &FieldLocs) {
|
|
if (Fields.size() != FieldLocs.size())
|
|
return false;
|
|
for ([[maybe_unused]] auto [Field, Loc] : FieldLocs)
|
|
if (!Fields.contains(cast_or_null<FieldDecl>(Field)))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/// Returns the fields of a `RecordDecl` that are initialized by an
|
|
/// `InitListExpr` or `CXXParenListInitExpr`, in the order in which they appear
|
|
/// in `InitListExpr::inits()` / `CXXParenListInitExpr::getInitExprs()`.
|
|
/// `InitList->getType()` must be a record type.
|
|
template <class InitListT>
|
|
static std::vector<const FieldDecl *>
|
|
getFieldsForInitListExpr(const InitListT *InitList) {
|
|
const RecordDecl *RD = InitList->getType()->getAsRecordDecl();
|
|
assert(RD != nullptr);
|
|
|
|
std::vector<const FieldDecl *> Fields;
|
|
|
|
if (InitList->getType()->isUnionType()) {
|
|
if (const FieldDecl *Field = InitList->getInitializedFieldInUnion())
|
|
Fields.push_back(Field);
|
|
return Fields;
|
|
}
|
|
|
|
// Unnamed bitfields are only used for padding and do not appear in
|
|
// `InitListExpr`'s inits. However, those fields do appear in `RecordDecl`'s
|
|
// field list, and we thus need to remove them before mapping inits to
|
|
// fields to avoid mapping inits to the wrongs fields.
|
|
llvm::copy_if(
|
|
RD->fields(), std::back_inserter(Fields),
|
|
[](const FieldDecl *Field) { return !Field->isUnnamedBitField(); });
|
|
return Fields;
|
|
}
|
|
|
|
RecordInitListHelper::RecordInitListHelper(const InitListExpr *InitList)
|
|
: RecordInitListHelper(InitList->getType(),
|
|
getFieldsForInitListExpr(InitList),
|
|
InitList->inits()) {}
|
|
|
|
RecordInitListHelper::RecordInitListHelper(
|
|
const CXXParenListInitExpr *ParenInitList)
|
|
: RecordInitListHelper(ParenInitList->getType(),
|
|
getFieldsForInitListExpr(ParenInitList),
|
|
ParenInitList->getInitExprs()) {}
|
|
|
|
RecordInitListHelper::RecordInitListHelper(
|
|
QualType Ty, std::vector<const FieldDecl *> Fields,
|
|
ArrayRef<Expr *> Inits) {
|
|
auto *RD = Ty->getAsCXXRecordDecl();
|
|
assert(RD != nullptr);
|
|
|
|
// Unions initialized with an empty initializer list need special treatment.
|
|
// For structs/classes initialized with an empty initializer list, Clang
|
|
// puts `ImplicitValueInitExpr`s in `InitListExpr::inits()`, but for unions,
|
|
// it doesn't do this -- so we create an `ImplicitValueInitExpr` ourselves.
|
|
SmallVector<Expr *> InitsForUnion;
|
|
if (Ty->isUnionType() && Inits.empty()) {
|
|
assert(Fields.size() <= 1);
|
|
if (!Fields.empty()) {
|
|
ImplicitValueInitForUnion.emplace(Fields.front()->getType());
|
|
InitsForUnion.push_back(&*ImplicitValueInitForUnion);
|
|
}
|
|
Inits = InitsForUnion;
|
|
}
|
|
|
|
size_t InitIdx = 0;
|
|
|
|
assert(Fields.size() + RD->getNumBases() == Inits.size());
|
|
for (const CXXBaseSpecifier &Base : RD->bases()) {
|
|
assert(InitIdx < Inits.size());
|
|
Expr *Init = Inits[InitIdx++];
|
|
BaseInits.emplace_back(&Base, Init);
|
|
}
|
|
|
|
assert(Fields.size() == Inits.size() - InitIdx);
|
|
for (const FieldDecl *Field : Fields) {
|
|
assert(InitIdx < Inits.size());
|
|
Expr *Init = Inits[InitIdx++];
|
|
FieldInits.emplace_back(Field, Init);
|
|
}
|
|
}
|
|
|
|
static void insertIfGlobal(const Decl &D,
|
|
llvm::DenseSet<const VarDecl *> &Globals) {
|
|
if (auto *V = dyn_cast<VarDecl>(&D))
|
|
if (V->hasGlobalStorage())
|
|
Globals.insert(V);
|
|
}
|
|
|
|
static void insertIfLocal(const Decl &D,
|
|
llvm::DenseSet<const VarDecl *> &Locals) {
|
|
if (auto *V = dyn_cast<VarDecl>(&D))
|
|
if (V->hasLocalStorage() && !isa<ParmVarDecl>(V))
|
|
Locals.insert(V);
|
|
}
|
|
|
|
static void insertIfFunction(const Decl &D,
|
|
llvm::DenseSet<const FunctionDecl *> &Funcs) {
|
|
if (auto *FD = dyn_cast<FunctionDecl>(&D))
|
|
Funcs.insert(FD);
|
|
}
|
|
|
|
static MemberExpr *getMemberForAccessor(const CXXMemberCallExpr &C) {
|
|
// Use getCalleeDecl instead of getMethodDecl in order to handle
|
|
// pointer-to-member calls.
|
|
const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(C.getCalleeDecl());
|
|
if (!MethodDecl)
|
|
return nullptr;
|
|
auto *Body = dyn_cast_or_null<CompoundStmt>(MethodDecl->getBody());
|
|
if (!Body || Body->size() != 1)
|
|
return nullptr;
|
|
if (auto *RS = dyn_cast<ReturnStmt>(*Body->body_begin()))
|
|
if (auto *Return = RS->getRetValue())
|
|
return dyn_cast<MemberExpr>(Return->IgnoreParenImpCasts());
|
|
return nullptr;
|
|
}
|
|
|
|
class ReferencedDeclsVisitor : public AnalysisASTVisitor {
|
|
public:
|
|
ReferencedDeclsVisitor(ReferencedDecls &Referenced)
|
|
: Referenced(Referenced) {}
|
|
|
|
void traverseConstructorInits(const CXXConstructorDecl *Ctor) {
|
|
for (const CXXCtorInitializer *Init : Ctor->inits()) {
|
|
if (Init->isMemberInitializer()) {
|
|
Referenced.Fields.insert(Init->getMember());
|
|
} else if (Init->isIndirectMemberInitializer()) {
|
|
for (const auto *I : Init->getIndirectMember()->chain())
|
|
Referenced.Fields.insert(cast<FieldDecl>(I));
|
|
}
|
|
|
|
Expr *InitExpr = Init->getInit();
|
|
|
|
// Also collect declarations referenced in `InitExpr`.
|
|
TraverseStmt(InitExpr);
|
|
|
|
// If this is a `CXXDefaultInitExpr`, also collect declarations referenced
|
|
// within the default expression.
|
|
if (auto *DefaultInit = dyn_cast<CXXDefaultInitExpr>(InitExpr))
|
|
TraverseStmt(DefaultInit->getExpr());
|
|
}
|
|
}
|
|
|
|
bool VisitDecl(Decl *D) override {
|
|
insertIfGlobal(*D, Referenced.Globals);
|
|
insertIfLocal(*D, Referenced.Locals);
|
|
insertIfFunction(*D, Referenced.Functions);
|
|
return true;
|
|
}
|
|
|
|
bool VisitDeclRefExpr(DeclRefExpr *E) override {
|
|
insertIfGlobal(*E->getDecl(), Referenced.Globals);
|
|
insertIfLocal(*E->getDecl(), Referenced.Locals);
|
|
insertIfFunction(*E->getDecl(), Referenced.Functions);
|
|
return true;
|
|
}
|
|
|
|
bool VisitCXXMemberCallExpr(CXXMemberCallExpr *C) override {
|
|
// If this is a method that returns a member variable but does nothing else,
|
|
// model the field of the return value.
|
|
if (MemberExpr *E = getMemberForAccessor(*C))
|
|
if (const auto *FD = dyn_cast<FieldDecl>(E->getMemberDecl()))
|
|
Referenced.Fields.insert(FD);
|
|
return true;
|
|
}
|
|
|
|
bool VisitMemberExpr(MemberExpr *E) override {
|
|
// FIXME: should we be using `E->getFoundDecl()`?
|
|
const ValueDecl *VD = E->getMemberDecl();
|
|
insertIfGlobal(*VD, Referenced.Globals);
|
|
insertIfFunction(*VD, Referenced.Functions);
|
|
if (const auto *FD = dyn_cast<FieldDecl>(VD))
|
|
Referenced.Fields.insert(FD);
|
|
return true;
|
|
}
|
|
|
|
bool VisitInitListExpr(InitListExpr *InitList) override {
|
|
if (InitList->getType()->isRecordType())
|
|
Referenced.Fields.insert_range(getFieldsForInitListExpr(InitList));
|
|
return true;
|
|
}
|
|
|
|
bool VisitCXXParenListInitExpr(CXXParenListInitExpr *ParenInitList) override {
|
|
if (ParenInitList->getType()->isRecordType())
|
|
Referenced.Fields.insert_range(getFieldsForInitListExpr(ParenInitList));
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
ReferencedDecls &Referenced;
|
|
};
|
|
|
|
ReferencedDecls getReferencedDecls(const FunctionDecl &FD) {
|
|
ReferencedDecls Result;
|
|
ReferencedDeclsVisitor Visitor(Result);
|
|
Visitor.TraverseStmt(FD.getBody());
|
|
if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(&FD))
|
|
Visitor.traverseConstructorInits(CtorDecl);
|
|
|
|
// If analyzing a lambda call operator, collect all captures of parameters (of
|
|
// the surrounding function). This collects them even if they are not
|
|
// referenced in the body of the lambda call operator. Non-parameter local
|
|
// variables that are captured are already collected into
|
|
// `ReferencedDecls.Locals` when traversing the call operator body, but we
|
|
// collect parameters here to avoid needing to check at each referencing node
|
|
// whether the parameter is a lambda capture from a surrounding function or is
|
|
// a parameter of the current function. If it becomes necessary to limit this
|
|
// set to the parameters actually referenced in the body, alternative
|
|
// optimizations can be implemented to minimize duplicative work.
|
|
if (const auto *Method = dyn_cast<CXXMethodDecl>(&FD);
|
|
Method && isLambdaCallOperator(Method)) {
|
|
for (const auto &Capture : Method->getParent()->captures()) {
|
|
if (Capture.capturesVariable()) {
|
|
if (const auto *Param =
|
|
dyn_cast<ParmVarDecl>(Capture.getCapturedVar())) {
|
|
Result.LambdaCapturedParams.insert(Param);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
ReferencedDecls getReferencedDecls(const Stmt &S) {
|
|
ReferencedDecls Result;
|
|
ReferencedDeclsVisitor Visitor(Result);
|
|
Visitor.TraverseStmt(const_cast<Stmt *>(&S));
|
|
return Result;
|
|
}
|
|
|
|
} // namespace clang::dataflow
|