#include "clang/Analysis/Analyses/UnsafeBufferUsage.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Lex/Lexer.h"
#include "clang/Lex/Preprocessor.h"
#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include <memory>
#include <optional>
#include <queue>
#include <sstream>
using namespace llvm;
using namespace clang;
using namespace ast_matchers;
#ifndef NDEBUG
namespace {
class StmtDebugPrinter
: public ConstStmtVisitor<StmtDebugPrinter, std::string> {
public:
std::string VisitStmt(const Stmt *S) { return S->getStmtClassName(); }
std::string VisitBinaryOperator(const BinaryOperator *BO) {
return "BinaryOperator(" + BO->getOpcodeStr().str() + ")";
}
std::string VisitUnaryOperator(const UnaryOperator *UO) {
return "UnaryOperator(" + UO->getOpcodeStr(UO->getOpcode()).str() + ")";
}
std::string VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
return "ImplicitCastExpr(" + std::string(ICE->getCastKindName()) + ")";
}
};
static std::string getDREAncestorString(const DeclRefExpr *DRE,
ASTContext &Ctx) {
std::stringstream SS;
const Stmt *St = DRE;
StmtDebugPrinter StmtPriner;
do {
SS << StmtPriner.Visit(St);
DynTypedNodeList StParents = Ctx.getParents(*St);
if (StParents.size() > 1)
return "unavailable due to multiple parents";
if (StParents.size() == 0)
break;
St = StParents.begin()->get<Stmt>();
if (St)
SS << " ==> ";
} while (St);
return SS.str();
}
}
#endif
namespace clang::ast_matchers {
class MatchDescendantVisitor
: public RecursiveASTVisitor<MatchDescendantVisitor> {
public:
typedef RecursiveASTVisitor<MatchDescendantVisitor> VisitorBase;
MatchDescendantVisitor(const internal::DynTypedMatcher *Matcher,
internal::ASTMatchFinder *Finder,
internal::BoundNodesTreeBuilder *Builder,
internal::ASTMatchFinder::BindKind Bind,
const bool ignoreUnevaluatedContext)
: Matcher(Matcher), Finder(Finder), Builder(Builder), Bind(Bind),
Matches(false), ignoreUnevaluatedContext(ignoreUnevaluatedContext) {}
bool findMatch(const DynTypedNode &DynNode) {
Matches = false;
if (const Stmt *StmtNode = DynNode.get<Stmt>()) {
TraverseStmt(const_cast<Stmt *>(StmtNode));
*Builder = ResultBindings;
return Matches;
}
return false;
}
bool TraverseDecl(Decl *Node) {
if (!Node)
return true;
if (!match(*Node))
return false;
if (isa<FunctionDecl, BlockDecl, ObjCMethodDecl>(Node))
return true;
return VisitorBase::TraverseDecl(Node);
}
bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
if (ignoreUnevaluatedContext)
return TraverseStmt(Node->getResultExpr());
return VisitorBase::TraverseGenericSelectionExpr(Node);
}
bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {
if (ignoreUnevaluatedContext)
return true;
return VisitorBase::TraverseUnaryExprOrTypeTraitExpr(Node);
}
bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) {
if (ignoreUnevaluatedContext)
return true;
return VisitorBase::TraverseTypeOfExprTypeLoc(Node);
}
bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) {
if (ignoreUnevaluatedContext)
return true;
return VisitorBase::TraverseDecltypeTypeLoc(Node);
}
bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) {
if (ignoreUnevaluatedContext)
return true;
return VisitorBase::TraverseCXXNoexceptExpr(Node);
}
bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) {
if (ignoreUnevaluatedContext)
return true;
return VisitorBase::TraverseCXXTypeidExpr(Node);
}
bool TraverseStmt(Stmt *Node, DataRecursionQueue *Queue = nullptr) {
if (!Node)
return true;
if (!match(*Node))
return false;
return VisitorBase::TraverseStmt(Node);
}
bool shouldVisitTemplateInstantiations() const { return true; }
bool shouldVisitImplicitCode() const {
return false;
}
private:
template <typename T> bool match(const T &Node) {
internal::BoundNodesTreeBuilder RecursiveBuilder(*Builder);
if (Matcher->matches(DynTypedNode::create(Node), Finder,
&RecursiveBuilder)) {
ResultBindings.addMatch(RecursiveBuilder);
Matches = true;
if (Bind != internal::ASTMatchFinder::BK_All)
return false;
}
return true;
}
const internal::DynTypedMatcher *const Matcher;
internal::ASTMatchFinder *const Finder;
internal::BoundNodesTreeBuilder *const Builder;
internal::BoundNodesTreeBuilder ResultBindings;
const internal::ASTMatchFinder::BindKind Bind;
bool Matches;
bool ignoreUnevaluatedContext;
};
static auto hasPointerType() {
return hasType(hasCanonicalType(pointerType()));
}
static auto hasArrayType() { return hasType(hasCanonicalType(arrayType())); }
AST_MATCHER_P(Stmt, forEachDescendantEvaluatedStmt, internal::Matcher<Stmt>,
innerMatcher) {
const DynTypedMatcher &DTM = static_cast<DynTypedMatcher>(innerMatcher);
MatchDescendantVisitor Visitor(&DTM, Finder, Builder, ASTMatchFinder::BK_All,
true);
return Visitor.findMatch(DynTypedNode::create(Node));
}
AST_MATCHER_P(Stmt, forEachDescendantStmt, internal::Matcher<Stmt>,
innerMatcher) {
const DynTypedMatcher &DTM = static_cast<DynTypedMatcher>(innerMatcher);
MatchDescendantVisitor Visitor(&DTM, Finder, Builder, ASTMatchFinder::BK_All,
false);
return Visitor.findMatch(DynTypedNode::create(Node));
}
AST_MATCHER_P(Stmt, notInSafeBufferOptOut, const UnsafeBufferUsageHandler *,
Handler) {
return !Handler->isSafeBufferOptOut(Node.getBeginLoc());
}
AST_MATCHER_P(Stmt, ignoreUnsafeBufferInContainer,
const UnsafeBufferUsageHandler *, Handler) {
return Handler->ignoreUnsafeBufferInContainer(Node.getBeginLoc());
}
AST_MATCHER_P(CastExpr, castSubExpr, internal::Matcher<Expr>, innerMatcher) {
return innerMatcher.matches(*Node.getSubExpr(), Finder, Builder);
}
AST_MATCHER(UnaryOperator, isPreInc) {
return Node.getOpcode() == UnaryOperator::Opcode::UO_PreInc;
}
static auto isInUnspecifiedLvalueContext(internal::Matcher<Expr> innerMatcher) {
return
expr(anyOf(
implicitCastExpr(
hasCastKind(CastKind::CK_LValueToRValue),
castSubExpr(innerMatcher)),
binaryOperator(
hasAnyOperatorName("="),
hasLHS(innerMatcher)
)
));
}
static internal::Matcher<Stmt>
isInUnspecifiedPointerContext(internal::Matcher<Stmt> InnerMatcher) {
auto CallArgMatcher = callExpr(
forEachArgumentWithParamType(
InnerMatcher,
isAnyPointer() ),
unless(callee(
functionDecl(hasAttr(attr::UnsafeBufferUsage)))));
auto CastOperandMatcher =
castExpr(anyOf(hasCastKind(CastKind::CK_PointerToIntegral),
hasCastKind(CastKind::CK_PointerToBoolean)),
castSubExpr(allOf(hasPointerType(), InnerMatcher)));
auto CompOperandMatcher =
binaryOperator(hasAnyOperatorName("!=", "==", "<", "<=", ">", ">="),
eachOf(hasLHS(allOf(hasPointerType(), InnerMatcher)),
hasRHS(allOf(hasPointerType(), InnerMatcher))));
auto PtrSubtractionMatcher =
binaryOperator(hasOperatorName("-"),
allOf(hasLHS(hasPointerType()),
hasRHS(hasPointerType())),
eachOf(hasLHS(InnerMatcher),
hasRHS(InnerMatcher)));
return stmt(anyOf(CallArgMatcher, CastOperandMatcher, CompOperandMatcher,
PtrSubtractionMatcher));
}
static internal::Matcher<Stmt>
isInUnspecifiedUntypedContext(internal::Matcher<Stmt> InnerMatcher) {
auto CompStmt = compoundStmt(forEach(InnerMatcher));
auto IfStmtThen = ifStmt(hasThen(InnerMatcher));
auto IfStmtElse = ifStmt(hasElse(InnerMatcher));
return stmt(anyOf(CompStmt, IfStmtThen, IfStmtElse));
}
AST_MATCHER(CXXConstructExpr, isSafeSpanTwoParamConstruct) {
assert(Node.getNumArgs() == 2 &&
"expecting a two-parameter std::span constructor");
const Expr *Arg0 = Node.getArg(0)->IgnoreImplicit();
const Expr *Arg1 = Node.getArg(1)->IgnoreImplicit();
auto HaveEqualConstantValues = [&Finder](const Expr *E0, const Expr *E1) {
if (auto E0CV = E0->getIntegerConstantExpr(Finder->getASTContext()))
if (auto E1CV = E1->getIntegerConstantExpr(Finder->getASTContext())) {
return APSInt::compareValues(*E0CV, *E1CV) == 0;
}
return false;
};
auto AreSameDRE = [](const Expr *E0, const Expr *E1) {
if (auto *DRE0 = dyn_cast<DeclRefExpr>(E0))
if (auto *DRE1 = dyn_cast<DeclRefExpr>(E1)) {
return DRE0->getDecl() == DRE1->getDecl();
}
return false;
};
std::optional<APSInt> Arg1CV =
Arg1->getIntegerConstantExpr(Finder->getASTContext());
if (Arg1CV && Arg1CV->isZero())
return true;
switch (Arg0->IgnoreImplicit()->getStmtClass()) {
case Stmt::CXXNewExprClass:
if (auto Size = cast<CXXNewExpr>(Arg0)->getArraySize()) {
return AreSameDRE((*Size)->IgnoreImplicit(), Arg1) ||
HaveEqualConstantValues(*Size, Arg1);
}
if (!cast<CXXNewExpr>(Arg0)->hasPlaceholderType()) {
return Arg1CV && Arg1CV->isOne();
}
break;
case Stmt::UnaryOperatorClass:
if (cast<UnaryOperator>(Arg0)->getOpcode() ==
UnaryOperator::Opcode::UO_AddrOf)
return Arg1CV && Arg1CV->isOne();
break;
default:
break;
}
QualType Arg0Ty = Arg0->IgnoreImplicit()->getType();
if (Arg0Ty->isConstantArrayType()) {
const APSInt ConstArrSize =
APSInt(cast<ConstantArrayType>(Arg0Ty)->getSize());
return Arg1CV && APSInt::compareValues(ConstArrSize, *Arg1CV) == 0;
}
return false;
}
AST_MATCHER(ArraySubscriptExpr, isSafeArraySubscript) {
const auto *BaseDRE =
dyn_cast<DeclRefExpr>(Node.getBase()->IgnoreParenImpCasts());
if (!BaseDRE)
return false;
if (!BaseDRE->getDecl())
return false;
const auto *CATy = Finder->getASTContext().getAsConstantArrayType(
BaseDRE->getDecl()->getType());
if (!CATy)
return false;
if (const auto *IdxLit = dyn_cast<IntegerLiteral>(Node.getIdx())) {
const APInt ArrIdx = IdxLit->getValue();
if (ArrIdx.isNonNegative() &&
ArrIdx.getLimitedValue() < CATy->getLimitedSize())
return true;
}
return false;
}
}
namespace {
using DeclUseList = SmallVector<const DeclRefExpr *, 1>;
using FixItList = SmallVector<FixItHint, 4>;
}
namespace {
class Gadget {
public:
enum class Kind {
#define GADGET(x) x,
#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
};
using Matcher = decltype(stmt());
Gadget(Kind K) : K(K) {}
Kind getKind() const { return K; }
#ifndef NDEBUG
StringRef getDebugName() const {
switch (K) {
#define GADGET(x) \
case Kind::x: \
return #x;
#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
}
llvm_unreachable("Unhandled Gadget::Kind enum");
}
#endif
virtual bool isWarningGadget() const = 0;
virtual SourceLocation getSourceLoc() const = 0;
virtual DeclUseList getClaimedVarUseSites() const = 0;
virtual ~Gadget() = default;
private:
Kind K;
};
class WarningGadget : public Gadget {
public:
WarningGadget(Kind K) : Gadget(K) {}
static bool classof(const Gadget *G) { return G->isWarningGadget(); }
bool isWarningGadget() const final { return true; }
virtual void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
bool IsRelatedToDecl,
ASTContext &Ctx) const = 0;
};
class FixableGadget : public Gadget {
public:
FixableGadget(Kind K) : Gadget(K) {}
static bool classof(const Gadget *G) { return !G->isWarningGadget(); }
bool isWarningGadget() const final { return false; }
virtual std::optional<FixItList> getFixits(const FixitStrategy &) const {
return std::nullopt;
}
virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>
getStrategyImplications() const {
return std::nullopt;
}
};
static auto toSupportedVariable() { return to(varDecl()); }
using FixableGadgetList = std::vector<std::unique_ptr<FixableGadget>>;
using WarningGadgetList = std::vector<std::unique_ptr<WarningGadget>>;
class IncrementGadget : public WarningGadget {
static constexpr const char *const OpTag = "op";
const UnaryOperator *Op;
public:
IncrementGadget(const MatchFinder::MatchResult &Result)
: WarningGadget(Kind::Increment),
Op(Result.Nodes.getNodeAs<UnaryOperator>(OpTag)) {}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::Increment;
}
static Matcher matcher() {
return stmt(
unaryOperator(hasOperatorName("++"),
hasUnaryOperand(ignoringParenImpCasts(hasPointerType())))
.bind(OpTag));
}
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
bool IsRelatedToDecl,
ASTContext &Ctx) const override {
Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);
}
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
DeclUseList getClaimedVarUseSites() const override {
SmallVector<const DeclRefExpr *, 2> Uses;
if (const auto *DRE =
dyn_cast<DeclRefExpr>(Op->getSubExpr()->IgnoreParenImpCasts())) {
Uses.push_back(DRE);
}
return std::move(Uses);
}
};
class DecrementGadget : public WarningGadget {
static constexpr const char *const OpTag = "op";
const UnaryOperator *Op;
public:
DecrementGadget(const MatchFinder::MatchResult &Result)
: WarningGadget(Kind::Decrement),
Op(Result.Nodes.getNodeAs<UnaryOperator>(OpTag)) {}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::Decrement;
}
static Matcher matcher() {
return stmt(
unaryOperator(hasOperatorName("--"),
hasUnaryOperand(ignoringParenImpCasts(hasPointerType())))
.bind(OpTag));
}
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
bool IsRelatedToDecl,
ASTContext &Ctx) const override {
Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);
}
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
DeclUseList getClaimedVarUseSites() const override {
if (const auto *DRE =
dyn_cast<DeclRefExpr>(Op->getSubExpr()->IgnoreParenImpCasts())) {
return {DRE};
}
return {};
}
};
class ArraySubscriptGadget : public WarningGadget {
static constexpr const char *const ArraySubscrTag = "ArraySubscript";
const ArraySubscriptExpr *ASE;
public:
ArraySubscriptGadget(const MatchFinder::MatchResult &Result)
: WarningGadget(Kind::ArraySubscript),
ASE(Result.Nodes.getNodeAs<ArraySubscriptExpr>(ArraySubscrTag)) {}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::ArraySubscript;
}
static Matcher matcher() {
return stmt(arraySubscriptExpr(
hasBase(ignoringParenImpCasts(
anyOf(hasPointerType(), hasArrayType()))),
unless(anyOf(
isSafeArraySubscript(),
hasIndex(
anyOf(integerLiteral(equals(0)), arrayInitIndexExpr())
)
))).bind(ArraySubscrTag));
}
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
bool IsRelatedToDecl,
ASTContext &Ctx) const override {
Handler.handleUnsafeOperation(ASE, IsRelatedToDecl, Ctx);
}
SourceLocation getSourceLoc() const override { return ASE->getBeginLoc(); }
DeclUseList getClaimedVarUseSites() const override {
if (const auto *DRE =
dyn_cast<DeclRefExpr>(ASE->getBase()->IgnoreParenImpCasts())) {
return {DRE};
}
return {};
}
};
class PointerArithmeticGadget : public WarningGadget {
static constexpr const char *const PointerArithmeticTag = "ptrAdd";
static constexpr const char *const PointerArithmeticPointerTag = "ptrAddPtr";
const BinaryOperator *PA;
const Expr *Ptr;
public:
PointerArithmeticGadget(const MatchFinder::MatchResult &Result)
: WarningGadget(Kind::PointerArithmetic),
PA(Result.Nodes.getNodeAs<BinaryOperator>(PointerArithmeticTag)),
Ptr(Result.Nodes.getNodeAs<Expr>(PointerArithmeticPointerTag)) {}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::PointerArithmetic;
}
static Matcher matcher() {
auto HasIntegerType = anyOf(hasType(isInteger()), hasType(enumType()));
auto PtrAtRight =
allOf(hasOperatorName("+"),
hasRHS(expr(hasPointerType()).bind(PointerArithmeticPointerTag)),
hasLHS(HasIntegerType));
auto PtrAtLeft =
allOf(anyOf(hasOperatorName("+"), hasOperatorName("-"),
hasOperatorName("+="), hasOperatorName("-=")),
hasLHS(expr(hasPointerType()).bind(PointerArithmeticPointerTag)),
hasRHS(HasIntegerType));
return stmt(binaryOperator(anyOf(PtrAtLeft, PtrAtRight))
.bind(PointerArithmeticTag));
}
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
bool IsRelatedToDecl,
ASTContext &Ctx) const override {
Handler.handleUnsafeOperation(PA, IsRelatedToDecl, Ctx);
}
SourceLocation getSourceLoc() const override { return PA->getBeginLoc(); }
DeclUseList getClaimedVarUseSites() const override {
if (const auto *DRE = dyn_cast<DeclRefExpr>(Ptr->IgnoreParenImpCasts())) {
return {DRE};
}
return {};
}
};
class SpanTwoParamConstructorGadget : public WarningGadget {
static constexpr const char *const SpanTwoParamConstructorTag =
"spanTwoParamConstructor";
const CXXConstructExpr *Ctor;
public:
SpanTwoParamConstructorGadget(const MatchFinder::MatchResult &Result)
: WarningGadget(Kind::SpanTwoParamConstructor),
Ctor(Result.Nodes.getNodeAs<CXXConstructExpr>(
SpanTwoParamConstructorTag)) {}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::SpanTwoParamConstructor;
}
static Matcher matcher() {
auto HasTwoParamSpanCtorDecl = hasDeclaration(
cxxConstructorDecl(hasDeclContext(isInStdNamespace()), hasName("span"),
parameterCountIs(2)));
return stmt(cxxConstructExpr(HasTwoParamSpanCtorDecl,
unless(isSafeSpanTwoParamConstruct()))
.bind(SpanTwoParamConstructorTag));
}
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
bool IsRelatedToDecl,
ASTContext &Ctx) const override {
Handler.handleUnsafeOperationInContainer(Ctor, IsRelatedToDecl, Ctx);
}
SourceLocation getSourceLoc() const override { return Ctor->getBeginLoc(); }
DeclUseList getClaimedVarUseSites() const override {
if (auto *DRE = dyn_cast<DeclRefExpr>(Ctor->getArg(0))) {
if (isa<VarDecl>(DRE->getDecl()))
return {DRE};
}
return {};
}
};
class PointerInitGadget : public FixableGadget {
private:
static constexpr const char *const PointerInitLHSTag = "ptrInitLHS";
static constexpr const char *const PointerInitRHSTag = "ptrInitRHS";
const VarDecl *PtrInitLHS;
const DeclRefExpr *PtrInitRHS;
public:
PointerInitGadget(const MatchFinder::MatchResult &Result)
: FixableGadget(Kind::PointerInit),
PtrInitLHS(Result.Nodes.getNodeAs<VarDecl>(PointerInitLHSTag)),
PtrInitRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerInitRHSTag)) {}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::PointerInit;
}
static Matcher matcher() {
auto PtrInitStmt = declStmt(hasSingleDecl(
varDecl(hasInitializer(ignoringImpCasts(
declRefExpr(hasPointerType(), toSupportedVariable())
.bind(PointerInitRHSTag))))
.bind(PointerInitLHSTag)));
return stmt(PtrInitStmt);
}
virtual std::optional<FixItList>
getFixits(const FixitStrategy &S) const override;
SourceLocation getSourceLoc() const override {
return PtrInitRHS->getBeginLoc();
}
virtual DeclUseList getClaimedVarUseSites() const override {
return DeclUseList{PtrInitRHS};
}
virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>
getStrategyImplications() const override {
return std::make_pair(PtrInitLHS, cast<VarDecl>(PtrInitRHS->getDecl()));
}
};
class PtrToPtrAssignmentGadget : public FixableGadget {
private:
static constexpr const char *const PointerAssignLHSTag = "ptrLHS";
static constexpr const char *const PointerAssignRHSTag = "ptrRHS";
const DeclRefExpr *PtrLHS;
const DeclRefExpr *PtrRHS;
public:
PtrToPtrAssignmentGadget(const MatchFinder::MatchResult &Result)
: FixableGadget(Kind::PtrToPtrAssignment),
PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)),
PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::PtrToPtrAssignment;
}
static Matcher matcher() {
auto PtrAssignExpr = binaryOperator(
allOf(hasOperatorName("="),
hasRHS(ignoringParenImpCasts(
declRefExpr(hasPointerType(), toSupportedVariable())
.bind(PointerAssignRHSTag))),
hasLHS(declRefExpr(hasPointerType(), toSupportedVariable())
.bind(PointerAssignLHSTag))));
return stmt(isInUnspecifiedUntypedContext(PtrAssignExpr));
}
virtual std::optional<FixItList>
getFixits(const FixitStrategy &S) const override;
SourceLocation getSourceLoc() const override { return PtrLHS->getBeginLoc(); }
virtual DeclUseList getClaimedVarUseSites() const override {
return DeclUseList{PtrLHS, PtrRHS};
}
virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>
getStrategyImplications() const override {
return std::make_pair(cast<VarDecl>(PtrLHS->getDecl()),
cast<VarDecl>(PtrRHS->getDecl()));
}
};
class CArrayToPtrAssignmentGadget : public FixableGadget {
private:
static constexpr const char *const PointerAssignLHSTag = "ptrLHS";
static constexpr const char *const PointerAssignRHSTag = "ptrRHS";
const DeclRefExpr *PtrLHS;
const DeclRefExpr *PtrRHS;
public:
CArrayToPtrAssignmentGadget(const MatchFinder::MatchResult &Result)
: FixableGadget(Kind::CArrayToPtrAssignment),
PtrLHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)),
PtrRHS(Result.Nodes.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::CArrayToPtrAssignment;
}
static Matcher matcher() {
auto PtrAssignExpr = binaryOperator(
allOf(hasOperatorName("="),
hasRHS(ignoringParenImpCasts(
declRefExpr(hasType(hasCanonicalType(constantArrayType())),
toSupportedVariable())
.bind(PointerAssignRHSTag))),
hasLHS(declRefExpr(hasPointerType(), toSupportedVariable())
.bind(PointerAssignLHSTag))));
return stmt(isInUnspecifiedUntypedContext(PtrAssignExpr));
}
virtual std::optional<FixItList>
getFixits(const FixitStrategy &S) const override;
SourceLocation getSourceLoc() const override { return PtrLHS->getBeginLoc(); }
virtual DeclUseList getClaimedVarUseSites() const override {
return DeclUseList{PtrLHS, PtrRHS};
}
virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>
getStrategyImplications() const override {
return {};
}
};
class UnsafeBufferUsageAttrGadget : public WarningGadget {
constexpr static const char *const OpTag = "call_expr";
const CallExpr *Op;
public:
UnsafeBufferUsageAttrGadget(const MatchFinder::MatchResult &Result)
: WarningGadget(Kind::UnsafeBufferUsageAttr),
Op(Result.Nodes.getNodeAs<CallExpr>(OpTag)) {}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::UnsafeBufferUsageAttr;
}
static Matcher matcher() {
auto HasUnsafeFnDecl =
callee(functionDecl(hasAttr(attr::UnsafeBufferUsage)));
return stmt(callExpr(HasUnsafeFnDecl).bind(OpTag));
}
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
bool IsRelatedToDecl,
ASTContext &Ctx) const override {
Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);
}
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
DeclUseList getClaimedVarUseSites() const override { return {}; }
};
class UnsafeBufferUsageCtorAttrGadget : public WarningGadget {
constexpr static const char *const OpTag = "cxx_construct_expr";
const CXXConstructExpr *Op;
public:
UnsafeBufferUsageCtorAttrGadget(const MatchFinder::MatchResult &Result)
: WarningGadget(Kind::UnsafeBufferUsageCtorAttr),
Op(Result.Nodes.getNodeAs<CXXConstructExpr>(OpTag)) {}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::UnsafeBufferUsageCtorAttr;
}
static Matcher matcher() {
auto HasUnsafeCtorDecl =
hasDeclaration(cxxConstructorDecl(hasAttr(attr::UnsafeBufferUsage)));
auto HasTwoParamSpanCtorDecl = SpanTwoParamConstructorGadget::matcher();
return stmt(
cxxConstructExpr(HasUnsafeCtorDecl, unless(HasTwoParamSpanCtorDecl))
.bind(OpTag));
}
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
bool IsRelatedToDecl,
ASTContext &Ctx) const override {
Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);
}
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
DeclUseList getClaimedVarUseSites() const override { return {}; }
};
class DataInvocationGadget : public WarningGadget {
constexpr static const char *const OpTag = "data_invocation_expr";
const ExplicitCastExpr *Op;
public:
DataInvocationGadget(const MatchFinder::MatchResult &Result)
: WarningGadget(Kind::DataInvocation),
Op(Result.Nodes.getNodeAs<ExplicitCastExpr>(OpTag)) {}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::DataInvocation;
}
static Matcher matcher() {
Matcher callExpr = cxxMemberCallExpr(
callee(cxxMethodDecl(hasName("data"), ofClass(hasName("std::span")))));
return stmt(
explicitCastExpr(anyOf(has(callExpr), has(parenExpr(has(callExpr)))))
.bind(OpTag));
}
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
bool IsRelatedToDecl,
ASTContext &Ctx) const override {
Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);
}
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
DeclUseList getClaimedVarUseSites() const override { return {}; }
};
class ULCArraySubscriptGadget : public FixableGadget {
private:
static constexpr const char *const ULCArraySubscriptTag =
"ArraySubscriptUnderULC";
const ArraySubscriptExpr *Node;
public:
ULCArraySubscriptGadget(const MatchFinder::MatchResult &Result)
: FixableGadget(Kind::ULCArraySubscript),
Node(Result.Nodes.getNodeAs<ArraySubscriptExpr>(ULCArraySubscriptTag)) {
assert(Node != nullptr && "Expecting a non-null matching result");
}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::ULCArraySubscript;
}
static Matcher matcher() {
auto ArrayOrPtr = anyOf(hasPointerType(), hasArrayType());
auto BaseIsArrayOrPtrDRE = hasBase(
ignoringParenImpCasts(declRefExpr(ArrayOrPtr, toSupportedVariable())));
auto Target =
arraySubscriptExpr(BaseIsArrayOrPtrDRE).bind(ULCArraySubscriptTag);
return expr(isInUnspecifiedLvalueContext(Target));
}
virtual std::optional<FixItList>
getFixits(const FixitStrategy &S) const override;
SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }
virtual DeclUseList getClaimedVarUseSites() const override {
if (const auto *DRE =
dyn_cast<DeclRefExpr>(Node->getBase()->IgnoreImpCasts())) {
return {DRE};
}
return {};
}
};
class UPCStandalonePointerGadget : public FixableGadget {
private:
static constexpr const char *const DeclRefExprTag = "StandalonePointer";
const DeclRefExpr *Node;
public:
UPCStandalonePointerGadget(const MatchFinder::MatchResult &Result)
: FixableGadget(Kind::UPCStandalonePointer),
Node(Result.Nodes.getNodeAs<DeclRefExpr>(DeclRefExprTag)) {
assert(Node != nullptr && "Expecting a non-null matching result");
}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::UPCStandalonePointer;
}
static Matcher matcher() {
auto ArrayOrPtr = anyOf(hasPointerType(), hasArrayType());
auto target = expr(ignoringParenImpCasts(
declRefExpr(allOf(ArrayOrPtr, toSupportedVariable()))
.bind(DeclRefExprTag)));
return stmt(isInUnspecifiedPointerContext(target));
}
virtual std::optional<FixItList>
getFixits(const FixitStrategy &S) const override;
SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }
virtual DeclUseList getClaimedVarUseSites() const override { return {Node}; }
};
class PointerDereferenceGadget : public FixableGadget {
static constexpr const char *const BaseDeclRefExprTag = "BaseDRE";
static constexpr const char *const OperatorTag = "op";
const DeclRefExpr *BaseDeclRefExpr = nullptr;
const UnaryOperator *Op = nullptr;
public:
PointerDereferenceGadget(const MatchFinder::MatchResult &Result)
: FixableGadget(Kind::PointerDereference),
BaseDeclRefExpr(
Result.Nodes.getNodeAs<DeclRefExpr>(BaseDeclRefExprTag)),
Op(Result.Nodes.getNodeAs<UnaryOperator>(OperatorTag)) {}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::PointerDereference;
}
static Matcher matcher() {
auto Target =
unaryOperator(
hasOperatorName("*"),
has(expr(ignoringParenImpCasts(
declRefExpr(toSupportedVariable()).bind(BaseDeclRefExprTag)))))
.bind(OperatorTag);
return expr(isInUnspecifiedLvalueContext(Target));
}
DeclUseList getClaimedVarUseSites() const override {
return {BaseDeclRefExpr};
}
virtual std::optional<FixItList>
getFixits(const FixitStrategy &S) const override;
SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }
};
class UPCAddressofArraySubscriptGadget : public FixableGadget {
private:
static constexpr const char *const UPCAddressofArraySubscriptTag =
"AddressofArraySubscriptUnderUPC";
const UnaryOperator *Node;
public:
UPCAddressofArraySubscriptGadget(const MatchFinder::MatchResult &Result)
: FixableGadget(Kind::ULCArraySubscript),
Node(Result.Nodes.getNodeAs<UnaryOperator>(
UPCAddressofArraySubscriptTag)) {
assert(Node != nullptr && "Expecting a non-null matching result");
}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::UPCAddressofArraySubscript;
}
static Matcher matcher() {
return expr(isInUnspecifiedPointerContext(expr(ignoringImpCasts(
unaryOperator(
hasOperatorName("&"),
hasUnaryOperand(arraySubscriptExpr(hasBase(
ignoringParenImpCasts(declRefExpr(toSupportedVariable()))))))
.bind(UPCAddressofArraySubscriptTag)))));
}
virtual std::optional<FixItList>
getFixits(const FixitStrategy &) const override;
SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }
virtual DeclUseList getClaimedVarUseSites() const override {
const auto *ArraySubst = cast<ArraySubscriptExpr>(Node->getSubExpr());
const auto *DRE =
cast<DeclRefExpr>(ArraySubst->getBase()->IgnoreParenImpCasts());
return {DRE};
}
};
}
namespace {
class DeclUseTracker {
using UseSetTy = SmallSet<const DeclRefExpr *, 16>;
using DefMapTy = DenseMap<const VarDecl *, const DeclStmt *>;
std::unique_ptr<UseSetTy> Uses{std::make_unique<UseSetTy>()};
DefMapTy Defs{};
public:
DeclUseTracker() = default;
DeclUseTracker(const DeclUseTracker &) = delete;
DeclUseTracker &operator=(const DeclUseTracker &) = delete;
DeclUseTracker(DeclUseTracker &&) = default;
DeclUseTracker &operator=(DeclUseTracker &&) = default;
void discoverUse(const DeclRefExpr *DRE) { Uses->insert(DRE); }
void claimUse(const DeclRefExpr *DRE) {
assert(Uses->count(DRE) &&
"DRE not found or claimed by multiple matchers!");
Uses->erase(DRE);
}
bool hasUnclaimedUses(const VarDecl *VD) const {
return any_of(*Uses, [VD](const DeclRefExpr *DRE) {
return DRE->getDecl()->getCanonicalDecl() == VD->getCanonicalDecl();
});
}
UseSetTy getUnclaimedUses(const VarDecl *VD) const {
UseSetTy ReturnSet;
for (auto use : *Uses) {
if (use->getDecl()->getCanonicalDecl() == VD->getCanonicalDecl()) {
ReturnSet.insert(use);
}
}
return ReturnSet;
}
void discoverDecl(const DeclStmt *DS) {
for (const Decl *D : DS->decls()) {
if (const auto *VD = dyn_cast<VarDecl>(D)) {
Defs[VD] = DS;
}
}
}
const DeclStmt *lookupDecl(const VarDecl *VD) const {
return Defs.lookup(VD);
}
};
}
class UPCPreIncrementGadget : public FixableGadget {
private:
static constexpr const char *const UPCPreIncrementTag =
"PointerPreIncrementUnderUPC";
const UnaryOperator *Node;
public:
UPCPreIncrementGadget(const MatchFinder::MatchResult &Result)
: FixableGadget(Kind::UPCPreIncrement),
Node(Result.Nodes.getNodeAs<UnaryOperator>(UPCPreIncrementTag)) {
assert(Node != nullptr && "Expecting a non-null matching result");
}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::UPCPreIncrement;
}
static Matcher matcher() {
return stmt(isInUnspecifiedPointerContext(expr(ignoringImpCasts(
unaryOperator(isPreInc(),
hasUnaryOperand(declRefExpr(toSupportedVariable())))
.bind(UPCPreIncrementTag)))));
}
virtual std::optional<FixItList>
getFixits(const FixitStrategy &S) const override;
SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }
virtual DeclUseList getClaimedVarUseSites() const override {
return {dyn_cast<DeclRefExpr>(Node->getSubExpr())};
}
};
class UUCAddAssignGadget : public FixableGadget {
private:
static constexpr const char *const UUCAddAssignTag =
"PointerAddAssignUnderUUC";
static constexpr const char *const OffsetTag = "Offset";
const BinaryOperator *Node;
const Expr *Offset = nullptr;
public:
UUCAddAssignGadget(const MatchFinder::MatchResult &Result)
: FixableGadget(Kind::UUCAddAssign),
Node(Result.Nodes.getNodeAs<BinaryOperator>(UUCAddAssignTag)),
Offset(Result.Nodes.getNodeAs<Expr>(OffsetTag)) {
assert(Node != nullptr && "Expecting a non-null matching result");
}
static bool classof(const Gadget *G) {
return G->getKind() == Kind::UUCAddAssign;
}
static Matcher matcher() {
return stmt(isInUnspecifiedUntypedContext(expr(ignoringImpCasts(
binaryOperator(hasOperatorName("+="),
hasLHS(
declRefExpr(
hasPointerType(),
toSupportedVariable())),
hasRHS(expr().bind(OffsetTag)))
.bind(UUCAddAssignTag)))));
}
virtual std::optional<FixItList>
getFixits(const FixitStrategy &S) const override;
SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }
virtual DeclUseList getClaimedVarUseSites() const override {
return {dyn_cast<DeclRefExpr>(Node->getLHS())};
}
};
class DerefSimplePtrArithFixableGadget : public FixableGadget {
static constexpr const char *const BaseDeclRefExprTag = "BaseDRE";
static constexpr const char *const DerefOpTag = "DerefOp";
static constexpr const char *const AddOpTag = "AddOp";
static constexpr const char *const OffsetTag = "Offset";
const DeclRefExpr *BaseDeclRefExpr = nullptr;
const UnaryOperator *DerefOp = nullptr;
const BinaryOperator *AddOp = nullptr;
const IntegerLiteral *Offset = nullptr;
public:
DerefSimplePtrArithFixableGadget(const MatchFinder::MatchResult &Result)
: FixableGadget(Kind::DerefSimplePtrArithFixable),
BaseDeclRefExpr(
Result.Nodes.getNodeAs<DeclRefExpr>(BaseDeclRefExprTag)),
DerefOp(Result.Nodes.getNodeAs<UnaryOperator>(DerefOpTag)),
AddOp(Result.Nodes.getNodeAs<BinaryOperator>(AddOpTag)),
Offset(Result.Nodes.getNodeAs<IntegerLiteral>(OffsetTag)) {}
static Matcher matcher() {
auto ThePtr = expr(hasPointerType(),
ignoringImpCasts(declRefExpr(toSupportedVariable()).
bind(BaseDeclRefExprTag)));
auto PlusOverPtrAndInteger = expr(anyOf(
binaryOperator(hasOperatorName("+"), hasLHS(ThePtr),
hasRHS(integerLiteral().bind(OffsetTag)))
.bind(AddOpTag),
binaryOperator(hasOperatorName("+"), hasRHS(ThePtr),
hasLHS(integerLiteral().bind(OffsetTag)))
.bind(AddOpTag)));
return isInUnspecifiedLvalueContext(unaryOperator(
hasOperatorName("*"),
hasUnaryOperand(ignoringParens(PlusOverPtrAndInteger)))
.bind(DerefOpTag));
}
virtual std::optional<FixItList>
getFixits(const FixitStrategy &s) const final;
SourceLocation getSourceLoc() const override {
return DerefOp->getBeginLoc();
}
virtual DeclUseList getClaimedVarUseSites() const final {
return {BaseDeclRefExpr};
}
};
static std::tuple<FixableGadgetList, WarningGadgetList, DeclUseTracker>
findGadgets(const Decl *D, const UnsafeBufferUsageHandler &Handler,
bool EmitSuggestions) {
struct GadgetFinderCallback : MatchFinder::MatchCallback {
FixableGadgetList FixableGadgets;
WarningGadgetList WarningGadgets;
DeclUseTracker Tracker;
void run(const MatchFinder::MatchResult &Result) override {
#if NDEBUG
#define NEXT return
#else
[[maybe_unused]] int numFound = 0;
#define NEXT ++numFound
#endif
if (const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>("any_dre")) {
Tracker.discoverUse(DRE);
NEXT;
}
if (const auto *DS = Result.Nodes.getNodeAs<DeclStmt>("any_ds")) {
Tracker.discoverDecl(DS);
NEXT;
}
#define FIXABLE_GADGET(name) \
if (Result.Nodes.getNodeAs<Stmt>(#name)) { \
FixableGadgets.push_back(std::make_unique<name##Gadget>(Result)); \
NEXT; \
}
#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
#define WARNING_GADGET(name) \
if (Result.Nodes.getNodeAs<Stmt>(#name)) { \
WarningGadgets.push_back(std::make_unique<name##Gadget>(Result)); \
NEXT; \
}
#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
assert(numFound >= 1 && "Gadgets not found in match result!");
assert(numFound <= 1 && "Conflicting bind tags in gadgets!");
}
};
MatchFinder M;
GadgetFinderCallback CB;
M.addMatcher(
stmt(
forEachDescendantEvaluatedStmt(stmt(anyOf(
#define WARNING_GADGET(x) \
allOf(x ## Gadget::matcher().bind(#x), \
notInSafeBufferOptOut(&Handler)),
#define WARNING_CONTAINER_GADGET(x) \
allOf(x ## Gadget::matcher().bind(#x), \
notInSafeBufferOptOut(&Handler), \
unless(ignoreUnsafeBufferInContainer(&Handler))),
#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
unless(stmt())
)))
),
&CB
);
if (EmitSuggestions) {
M.addMatcher(
stmt(
forEachDescendantStmt(stmt(eachOf(
#define FIXABLE_GADGET(x) \
x ## Gadget::matcher().bind(#x),
#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"
declRefExpr(anyOf(hasPointerType(), hasArrayType()),
to(anyOf(varDecl(), bindingDecl()))).bind("any_dre"),
declStmt().bind("any_ds")
)))
),
&CB
);
}
M.match(*D->getBody(), D->getASTContext());
return {std::move(CB.FixableGadgets), std::move(CB.WarningGadgets),
std::move(CB.Tracker)};
}
template <typename NodeTy> struct CompareNode {
bool operator()(const NodeTy *N1, const NodeTy *N2) const {
return N1->getBeginLoc().getRawEncoding() <
N2->getBeginLoc().getRawEncoding();
}
};
struct WarningGadgetSets {
std::map<const VarDecl *, std::set<const WarningGadget *>,
// To keep keys sorted by their locations in the map so that the
// order is deterministic:
CompareNode<VarDecl>>
byVar;
llvm::SmallVector<const WarningGadget *, 16> noVar;
};
static WarningGadgetSets
groupWarningGadgetsByVar(const WarningGadgetList &AllUnsafeOperations) {
WarningGadgetSets result;
for (auto &G : AllUnsafeOperations) {
DeclUseList ClaimedVarUseSites = G->getClaimedVarUseSites();
bool AssociatedWithVarDecl = false;
for (const DeclRefExpr *DRE : ClaimedVarUseSites) {
if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
result.byVar[VD].insert(G.get());
AssociatedWithVarDecl = true;
}
}
if (!AssociatedWithVarDecl) {
result.noVar.push_back(G.get());
continue;
}
}
return result;
}
struct FixableGadgetSets {
std::map<const VarDecl *, std::set<const FixableGadget *>,
// To keep keys sorted by their locations in the map so that the
// order is deterministic:
CompareNode<VarDecl>>
byVar;
};
static FixableGadgetSets
groupFixablesByVar(FixableGadgetList &&AllFixableOperations) {
FixableGadgetSets FixablesForUnsafeVars;
for (auto &F : AllFixableOperations) {
DeclUseList DREs = F->getClaimedVarUseSites();
for (const DeclRefExpr *DRE : DREs) {
if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
FixablesForUnsafeVars.byVar[VD].insert(F.get());
}
}
}
return FixablesForUnsafeVars;
}
bool clang::internal::anyConflict(const SmallVectorImpl<FixItHint> &FixIts,
const SourceManager &SM) {
std::vector<const FixItHint *> All;
for (const FixItHint &H : FixIts)
All.push_back(&H);
std::sort(All.begin(), All.end(),
[&SM](const FixItHint *H1, const FixItHint *H2) {
return SM.isBeforeInTranslationUnit(H1->RemoveRange.getBegin(),
H2->RemoveRange.getBegin());
});
const FixItHint *CurrHint = nullptr;
for (const FixItHint *Hint : All) {
if (!CurrHint ||
SM.isBeforeInTranslationUnit(CurrHint->RemoveRange.getEnd(),
Hint->RemoveRange.getBegin())) {
CurrHint = Hint;
} else
return true;
}
return false;
}
std::optional<FixItList>
PtrToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {
const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl());
const auto *RightVD = cast<VarDecl>(PtrRHS->getDecl());
switch (S.lookup(LeftVD)) {
case FixitStrategy::Kind::Span:
if (S.lookup(RightVD) == FixitStrategy::Kind::Span)
return FixItList{};
return std::nullopt;
case FixitStrategy::Kind::Wontfix:
return std::nullopt;
case FixitStrategy::Kind::Iterator:
case FixitStrategy::Kind::Array:
return std::nullopt;
case FixitStrategy::Kind::Vector:
llvm_unreachable("unsupported strategies for FixableGadgets");
}
return std::nullopt;
}
static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx,
const DeclRefExpr *DRE);
std::optional<FixItList>
CArrayToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {
const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl());
const auto *RightVD = cast<VarDecl>(PtrRHS->getDecl());
if (S.lookup(LeftVD) == FixitStrategy::Kind::Span) {
if (S.lookup(RightVD) == FixitStrategy::Kind::Wontfix) {
return FixItList{};
}
} else if (S.lookup(LeftVD) == FixitStrategy::Kind::Wontfix) {
if (S.lookup(RightVD) == FixitStrategy::Kind::Array) {
return createDataFixit(RightVD->getASTContext(), PtrRHS);
}
}
return std::nullopt;
}
std::optional<FixItList>
PointerInitGadget::getFixits(const FixitStrategy &S) const {
const auto *LeftVD = PtrInitLHS;
const auto *RightVD = cast<VarDecl>(PtrInitRHS->getDecl());
switch (S.lookup(LeftVD)) {
case FixitStrategy::Kind::Span:
if (S.lookup(RightVD) == FixitStrategy::Kind::Span)
return FixItList{};
return std::nullopt;
case FixitStrategy::Kind::Wontfix:
return std::nullopt;
case FixitStrategy::Kind::Iterator:
case FixitStrategy::Kind::Array:
return std::nullopt;
case FixitStrategy::Kind::Vector:
llvm_unreachable("unsupported strategies for FixableGadgets");
}
return std::nullopt;
}
static bool isNonNegativeIntegerExpr(const Expr *Expr, const VarDecl *VD,
const ASTContext &Ctx) {
if (auto ConstVal = Expr->getIntegerConstantExpr(Ctx)) {
if (ConstVal->isNegative())
return false;
} else if (!Expr->getType()->isUnsignedIntegerType())
return false;
return true;
}
std::optional<FixItList>
ULCArraySubscriptGadget::getFixits(const FixitStrategy &S) const {
if (const auto *DRE =
dyn_cast<DeclRefExpr>(Node->getBase()->IgnoreImpCasts()))
if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
switch (S.lookup(VD)) {
case FixitStrategy::Kind::Span: {
const ASTContext &Ctx =
VD->getASTContext();
if (!isNonNegativeIntegerExpr(Node->getIdx(), VD, Ctx))
return std::nullopt;
return FixItList{};
}
case FixitStrategy::Kind::Array:
return FixItList{};
case FixitStrategy::Kind::Wontfix:
case FixitStrategy::Kind::Iterator:
case FixitStrategy::Kind::Vector:
llvm_unreachable("unsupported strategies for FixableGadgets");
}
}
return std::nullopt;
}
static std::optional<FixItList>
fixUPCAddressofArraySubscriptWithSpan(const UnaryOperator *Node);
std::optional<FixItList>
UPCAddressofArraySubscriptGadget::getFixits(const FixitStrategy &S) const {
auto DREs = getClaimedVarUseSites();
const auto *VD = cast<VarDecl>(DREs.front()->getDecl());
switch (S.lookup(VD)) {
case FixitStrategy::Kind::Span:
return fixUPCAddressofArraySubscriptWithSpan(Node);
case FixitStrategy::Kind::Wontfix:
case FixitStrategy::Kind::Iterator:
case FixitStrategy::Kind::Array:
return std::nullopt;
case FixitStrategy::Kind::Vector:
llvm_unreachable("unsupported strategies for FixableGadgets");
}
return std::nullopt;
}
static StringRef getEndOfLine() {
static const char *const EOL = "\n";
return EOL;
}
std::string getUserFillPlaceHolder(StringRef HintTextToUser = "placeholder") {
std::string s = std::string("<# ");
s += HintTextToUser;
s += " #>";
return s;
}
template <typename NodeTy>
static std::optional<SourceLocation>
getEndCharLoc(const NodeTy *Node, const SourceManager &SM,
const LangOptions &LangOpts) {
unsigned TkLen = Lexer::MeasureTokenLength(Node->getEndLoc(), SM, LangOpts);
SourceLocation Loc = Node->getEndLoc().getLocWithOffset(TkLen - 1);
if (Loc.isValid())
return Loc;
return std::nullopt;
}
template <typename NodeTy>
static std::optional<SourceLocation> getPastLoc(const NodeTy *Node,
const SourceManager &SM,
const LangOptions &LangOpts) {
SourceLocation Loc =
Lexer::getLocForEndOfToken(Node->getEndLoc(), 0, SM, LangOpts);
if (Loc.isValid())
return Loc;
return std::nullopt;
}
static std::optional<StringRef> getExprText(const Expr *E,
const SourceManager &SM,
const LangOptions &LangOpts) {
std::optional<SourceLocation> LastCharLoc = getPastLoc(E, SM, LangOpts);
if (LastCharLoc)
return Lexer::getSourceText(
CharSourceRange::getCharRange(E->getBeginLoc(), *LastCharLoc), SM,
LangOpts);
return std::nullopt;
}
static std::optional<StringRef> getRangeText(SourceRange SR,
const SourceManager &SM,
const LangOptions &LangOpts) {
bool Invalid = false;
CharSourceRange CSR = CharSourceRange::getCharRange(SR);
StringRef Text = Lexer::getSourceText(CSR, SM, LangOpts, &Invalid);
if (!Invalid)
return Text;
return std::nullopt;
}
static SourceLocation getVarDeclIdentifierLoc(const VarDecl *VD) {
return VD->getLocation();
}
static std::optional<StringRef>
getVarDeclIdentifierText(const VarDecl *VD, const SourceManager &SM,
const LangOptions &LangOpts) {
SourceLocation ParmIdentBeginLoc = getVarDeclIdentifierLoc(VD);
SourceLocation ParmIdentEndLoc =
Lexer::getLocForEndOfToken(ParmIdentBeginLoc, 0, SM, LangOpts);
if (ParmIdentEndLoc.isMacroID() &&
!Lexer::isAtEndOfMacroExpansion(ParmIdentEndLoc, SM, LangOpts))
return std::nullopt;
return getRangeText({ParmIdentBeginLoc, ParmIdentEndLoc}, SM, LangOpts);
}
static bool hasUnsupportedSpecifiers(const VarDecl *VD,
const SourceManager &SM) {
bool AttrRangeOverlapping = llvm::any_of(VD->attrs(), [&](Attr *At) -> bool {
return !(SM.isBeforeInTranslationUnit(At->getRange().getEnd(),
VD->getBeginLoc())) &&
!(SM.isBeforeInTranslationUnit(VD->getEndLoc(),
At->getRange().getBegin()));
});
return VD->isInlineSpecified() || VD->isConstexpr() ||
VD->hasConstantInitialization() || !VD->hasLocalStorage() ||
AttrRangeOverlapping;
}
static SourceRange getSourceRangeToTokenEnd(const Decl *D,
const SourceManager &SM,
const LangOptions &LangOpts) {
SourceLocation Begin = D->getBeginLoc();
SourceLocation
End =
Lexer::getLocForEndOfToken(D->getEndLoc(), 0, SM, LangOpts);
return SourceRange(Begin, End);
}
static std::optional<std::string>
getPointeeTypeText(const VarDecl *VD, const SourceManager &SM,
const LangOptions &LangOpts,
std::optional<Qualifiers> *QualifiersToAppend) {
QualType Ty = VD->getType();
QualType PteTy;
assert(Ty->isPointerType() && !Ty->isFunctionPointerType() &&
"Expecting a VarDecl of type of pointer to object type");
PteTy = Ty->getPointeeType();
TypeLoc TyLoc = VD->getTypeSourceInfo()->getTypeLoc().getUnqualifiedLoc();
TypeLoc PteTyLoc;
switch (TyLoc.getTypeLocClass()) {
case TypeLoc::ConstantArray:
case TypeLoc::IncompleteArray:
case TypeLoc::VariableArray:
case TypeLoc::DependentSizedArray:
case TypeLoc::Decayed:
assert(isa<ParmVarDecl>(VD) && "An array type shall not be treated as a "
"pointer type unless it decays.");
PteTyLoc = TyLoc.getNextTypeLoc();
break;
case TypeLoc::Pointer:
PteTyLoc = TyLoc.castAs<PointerTypeLoc>().getPointeeLoc();
break;
default:
return std::nullopt;
}
if (PteTyLoc.isNull())
return std::nullopt;
SourceLocation IdentLoc = getVarDeclIdentifierLoc(VD);
if (!(IdentLoc.isValid() && PteTyLoc.getSourceRange().isValid())) {
return std::nullopt;
}
SourceLocation PteEndOfTokenLoc =
Lexer::getLocForEndOfToken(PteTyLoc.getEndLoc(), 0, SM, LangOpts);
if (!PteEndOfTokenLoc.isValid())
return std::nullopt;
if (!SM.isBeforeInTranslationUnit(PteEndOfTokenLoc, IdentLoc)) {
return std::nullopt;
}
if (PteTy.hasQualifiers()) {
*QualifiersToAppend = PteTy.getQualifiers();
}
return getRangeText({PteTyLoc.getBeginLoc(), PteEndOfTokenLoc}, SM, LangOpts)
->str();
}
static std::optional<StringRef> getFunNameText(const FunctionDecl *FD,
const SourceManager &SM,
const LangOptions &LangOpts) {
SourceLocation BeginLoc = FD->getQualifier()
? FD->getQualifierLoc().getBeginLoc()
: FD->getNameInfo().getBeginLoc();
SourceLocation EndLoc = Lexer::getLocForEndOfToken(
FD->getNameInfo().getEndLoc(), 0, SM, LangOpts);
SourceRange NameRange{BeginLoc, EndLoc};
return getRangeText(NameRange, SM, LangOpts);
}
static std::string
getSpanTypeText(StringRef EltTyText,
std::optional<Qualifiers> Quals = std::nullopt) {
const char *const SpanOpen = "std::span<";
if (Quals)
return SpanOpen + EltTyText.str() + ' ' + Quals->getAsString() + '>';
return SpanOpen + EltTyText.str() + '>';
}
std::optional<FixItList>
DerefSimplePtrArithFixableGadget::getFixits(const FixitStrategy &s) const {
const VarDecl *VD = dyn_cast<VarDecl>(BaseDeclRefExpr->getDecl());
if (VD && s.lookup(VD) == FixitStrategy::Kind::Span) {
ASTContext &Ctx = VD->getASTContext();
if (auto ConstVal = Offset->getIntegerConstantExpr(Ctx))
if (ConstVal->isNegative())
return std::nullopt;
const Expr *LHS = AddOp->getLHS(), *RHS = AddOp->getRHS();
const SourceManager &SM = Ctx.getSourceManager();
const LangOptions &LangOpts = Ctx.getLangOpts();
CharSourceRange StarWithTrailWhitespace =
clang::CharSourceRange::getCharRange(DerefOp->getOperatorLoc(),
LHS->getBeginLoc());
std::optional<SourceLocation> LHSLocation = getPastLoc(LHS, SM, LangOpts);
if (!LHSLocation)
return std::nullopt;
CharSourceRange PlusWithSurroundingWhitespace =
clang::CharSourceRange::getCharRange(*LHSLocation, RHS->getBeginLoc());
std::optional<SourceLocation> AddOpLocation =
getPastLoc(AddOp, SM, LangOpts);
std::optional<SourceLocation> DerefOpLocation =
getPastLoc(DerefOp, SM, LangOpts);
if (!AddOpLocation || !DerefOpLocation)
return std::nullopt;
CharSourceRange ClosingParenWithPrecWhitespace =
clang::CharSourceRange::getCharRange(*AddOpLocation, *DerefOpLocation);
return FixItList{
{FixItHint::CreateRemoval(StarWithTrailWhitespace),
FixItHint::CreateReplacement(PlusWithSurroundingWhitespace, "["),
FixItHint::CreateReplacement(ClosingParenWithPrecWhitespace, "]")}};
}
return std::nullopt;
}
std::optional<FixItList>
PointerDereferenceGadget::getFixits(const FixitStrategy &S) const {
const VarDecl *VD = cast<VarDecl>(BaseDeclRefExpr->getDecl());
switch (S.lookup(VD)) {
case FixitStrategy::Kind::Span: {
ASTContext &Ctx = VD->getASTContext();
SourceManager &SM = Ctx.getSourceManager();
CharSourceRange derefRange = clang::CharSourceRange::getCharRange(
Op->getBeginLoc(), Op->getBeginLoc().getLocWithOffset(1));
if (auto LocPastOperand =
getPastLoc(BaseDeclRefExpr, SM, Ctx.getLangOpts())) {
return FixItList{{FixItHint::CreateRemoval(derefRange),
FixItHint::CreateInsertion(*LocPastOperand, "[0]")}};
}
break;
}
case FixitStrategy::Kind::Iterator:
case FixitStrategy::Kind::Array:
return std::nullopt;
case FixitStrategy::Kind::Vector:
llvm_unreachable("FixitStrategy not implemented yet!");
case FixitStrategy::Kind::Wontfix:
llvm_unreachable("Invalid strategy!");
}
return std::nullopt;
}
static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx,
const DeclRefExpr *DRE) {
const SourceManager &SM = Ctx.getSourceManager();
std::optional<SourceLocation> EndOfOperand =
getPastLoc(DRE, SM, Ctx.getLangOpts());
if (EndOfOperand)
return FixItList{{FixItHint::CreateInsertion(*EndOfOperand, ".data()")}};
return std::nullopt;
}
std::optional<FixItList>
UPCStandalonePointerGadget::getFixits(const FixitStrategy &S) const {
const auto VD = cast<VarDecl>(Node->getDecl());
switch (S.lookup(VD)) {
case FixitStrategy::Kind::Array:
case FixitStrategy::Kind::Span: {
return createDataFixit(VD->getASTContext(), Node);
break;
}
case FixitStrategy::Kind::Wontfix:
case FixitStrategy::Kind::Iterator:
return std::nullopt;
case FixitStrategy::Kind::Vector:
llvm_unreachable("unsupported strategies for FixableGadgets");
}
return std::nullopt;
}
static std::optional<FixItList>
fixUPCAddressofArraySubscriptWithSpan(const UnaryOperator *Node) {
const auto *ArraySub = cast<ArraySubscriptExpr>(Node->getSubExpr());
const auto *DRE = cast<DeclRefExpr>(ArraySub->getBase()->IgnoreImpCasts());
const ASTContext &Ctx = DRE->getDecl()->getASTContext();
const Expr *Idx = ArraySub->getIdx();
const SourceManager &SM = Ctx.getSourceManager();
const LangOptions &LangOpts = Ctx.getLangOpts();
std::stringstream SS;
bool IdxIsLitZero = false;
if (auto ICE = Idx->getIntegerConstantExpr(Ctx))
if ((*ICE).isZero())
IdxIsLitZero = true;
std::optional<StringRef> DreString = getExprText(DRE, SM, LangOpts);
if (!DreString)
return std::nullopt;
if (IdxIsLitZero) {
SS << (*DreString).str() << ".data()";
} else {
std::optional<StringRef> IndexString = getExprText(Idx, SM, LangOpts);
if (!IndexString)
return std::nullopt;
SS << "&" << (*DreString).str() << ".data()"
<< "[" << (*IndexString).str() << "]";
}
return FixItList{
FixItHint::CreateReplacement(Node->getSourceRange(), SS.str())};
}
std::optional<FixItList>
UUCAddAssignGadget::getFixits(const FixitStrategy &S) const {
DeclUseList DREs = getClaimedVarUseSites();
if (DREs.size() != 1)
return std::nullopt;
if (const VarDecl *VD = dyn_cast<VarDecl>(DREs.front()->getDecl())) {
if (S.lookup(VD) == FixitStrategy::Kind::Span) {
FixItList Fixes;
const Stmt *AddAssignNode = Node;
StringRef varName = VD->getName();
const ASTContext &Ctx = VD->getASTContext();
if (!isNonNegativeIntegerExpr(Offset, VD, Ctx))
return std::nullopt;
bool NotParenExpr =
(Offset->IgnoreParens()->getBeginLoc() == Offset->getBeginLoc());
std::string SS = varName.str() + " = " + varName.str() + ".subspan";
if (NotParenExpr)
SS += "(";
std::optional<SourceLocation> AddAssignLocation = getEndCharLoc(
AddAssignNode, Ctx.getSourceManager(), Ctx.getLangOpts());
if (!AddAssignLocation)
return std::nullopt;
Fixes.push_back(FixItHint::CreateReplacement(
SourceRange(AddAssignNode->getBeginLoc(), Node->getOperatorLoc()),
SS));
if (NotParenExpr)
Fixes.push_back(FixItHint::CreateInsertion(
Offset->getEndLoc().getLocWithOffset(1), ")"));
return Fixes;
}
}
return std::nullopt;
}
std::optional<FixItList>
UPCPreIncrementGadget::getFixits(const FixitStrategy &S) const {
DeclUseList DREs = getClaimedVarUseSites();
if (DREs.size() != 1)
return std::nullopt;
if (const VarDecl *VD = dyn_cast<VarDecl>(DREs.front()->getDecl())) {
if (S.lookup(VD) == FixitStrategy::Kind::Span) {
FixItList Fixes;
std::stringstream SS;
StringRef varName = VD->getName();
const ASTContext &Ctx = VD->getASTContext();
SS << "(" << varName.data() << " = " << varName.data()
<< ".subspan(1)).data()";
std::optional<SourceLocation> PreIncLocation =
getEndCharLoc(Node, Ctx.getSourceManager(), Ctx.getLangOpts());
if (!PreIncLocation)
return std::nullopt;
Fixes.push_back(FixItHint::CreateReplacement(
SourceRange(Node->getBeginLoc(), *PreIncLocation), SS.str()));
return Fixes;
}
}
return std::nullopt;
}
static std::optional<FixItList>
FixVarInitializerWithSpan(const Expr *Init, ASTContext &Ctx,
const StringRef UserFillPlaceHolder) {
const SourceManager &SM = Ctx.getSourceManager();
const LangOptions &LangOpts = Ctx.getLangOpts();
if (Init->isNullPointerConstant(
Ctx,
Expr::NullPointerConstantValueDependence::
NPC_ValueDependentIsNotNull)) {
std::optional<SourceLocation> InitLocation =
getEndCharLoc(Init, SM, LangOpts);
if (!InitLocation)
return std::nullopt;
SourceRange SR(Init->getBeginLoc(), *InitLocation);
return FixItList{FixItHint::CreateRemoval(SR)};
}
FixItList FixIts{};
std::string ExtentText = UserFillPlaceHolder.data();
StringRef One = "1";
FixIts.push_back(FixItHint::CreateInsertion(Init->getBeginLoc(), "{"));
if (auto CxxNew = dyn_cast<CXXNewExpr>(Init->IgnoreImpCasts())) {
if (const Expr *Ext = CxxNew->getArraySize().value_or(nullptr)) {
if (!Ext->HasSideEffects(Ctx)) {
std::optional<StringRef> ExtentString = getExprText(Ext, SM, LangOpts);
if (!ExtentString)
return std::nullopt;
ExtentText = *ExtentString;
}
} else if (!CxxNew->isArray())
ExtentText = One;
} else if (Ctx.getAsConstantArrayType(Init->IgnoreImpCasts()->getType())) {
return FixItList{};
} else {
if (auto AddrOfExpr = dyn_cast<UnaryOperator>(Init->IgnoreImpCasts()))
if (AddrOfExpr->getOpcode() == UnaryOperatorKind::UO_AddrOf &&
isa_and_present<DeclRefExpr>(AddrOfExpr->getSubExpr()))
ExtentText = One;
}
SmallString<32> StrBuffer{};
std::optional<SourceLocation> LocPassInit = getPastLoc(Init, SM, LangOpts);
if (!LocPassInit)
return std::nullopt;
StrBuffer.append(", ");
StrBuffer.append(ExtentText);
StrBuffer.append("}");
FixIts.push_back(FixItHint::CreateInsertion(*LocPassInit, StrBuffer.str()));
return FixIts;
}
#ifndef NDEBUG
#define DEBUG_NOTE_DECL_FAIL(D, Msg) \
Handler.addDebugNoteForVar((D), (D)->getBeginLoc(), \
"failed to produce fixit for declaration '" + \
(D)->getNameAsString() + "'" + (Msg))
#else
#define DEBUG_NOTE_DECL_FAIL(D, Msg)
#endif
static std::optional<std::string>
createSpanTypeForVarDecl(const VarDecl *VD, const ASTContext &Ctx) {
assert(VD->getType()->isPointerType());
std::optional<Qualifiers> PteTyQualifiers = std::nullopt;
std::optional<std::string> PteTyText = getPointeeTypeText(
VD, Ctx.getSourceManager(), Ctx.getLangOpts(), &PteTyQualifiers);
if (!PteTyText)
return std::nullopt;
std::string SpanTyText = "std::span<";
SpanTyText.append(*PteTyText);
if (PteTyQualifiers) {
SpanTyText.append(" ");
SpanTyText.append(PteTyQualifiers->getAsString());
}
SpanTyText.append(">");
return SpanTyText;
}
static FixItList fixLocalVarDeclWithSpan(const VarDecl *D, ASTContext &Ctx,
const StringRef UserFillPlaceHolder,
UnsafeBufferUsageHandler &Handler) {
if (hasUnsupportedSpecifiers(D, Ctx.getSourceManager()))
return {};
FixItList FixIts{};
std::optional<std::string> SpanTyText = createSpanTypeForVarDecl(D, Ctx);
if (!SpanTyText) {
DEBUG_NOTE_DECL_FAIL(D, " : failed to generate 'std::span' type");
return {};
}
std::stringstream SS;
SS << *SpanTyText;
if (const Expr *Init = D->getInit()) {
std::optional<FixItList> InitFixIts =
FixVarInitializerWithSpan(Init, Ctx, UserFillPlaceHolder);
if (!InitFixIts)
return {};
FixIts.insert(FixIts.end(), std::make_move_iterator(InitFixIts->begin()),
std::make_move_iterator(InitFixIts->end()));
}
const SourceLocation EndLocForReplacement = D->getTypeSpecEndLoc();
if (!EndLocForReplacement.isValid()) {
DEBUG_NOTE_DECL_FAIL(D, " : failed to locate the end of the declaration");
return {};
}
if (EndLocForReplacement.getLocWithOffset(1) == getVarDeclIdentifierLoc(D))
SS << " ";
FixIts.push_back(FixItHint::CreateReplacement(
SourceRange(D->getBeginLoc(), EndLocForReplacement), SS.str()));
return FixIts;
}
static bool hasConflictingOverload(const FunctionDecl *FD) {
return !FD->getDeclContext()->lookup(FD->getDeclName()).isSingleResult();
}
static std::optional<FixItList>
createOverloadsForFixedParams(const FixitStrategy &S, const FunctionDecl *FD,
const ASTContext &Ctx,
UnsafeBufferUsageHandler &Handler) {
if (hasConflictingOverload(FD))
return std::nullopt;
const SourceManager &SM = Ctx.getSourceManager();
const LangOptions &LangOpts = Ctx.getLangOpts();
const unsigned NumParms = FD->getNumParams();
std::vector<std::string> NewTysTexts(NumParms);
std::vector<bool> ParmsMask(NumParms, false);
bool AtLeastOneParmToFix = false;
for (unsigned i = 0; i < NumParms; i++) {
const ParmVarDecl *PVD = FD->getParamDecl(i);
if (S.lookup(PVD) == FixitStrategy::Kind::Wontfix)
continue;
if (S.lookup(PVD) != FixitStrategy::Kind::Span)
return std::nullopt;
std::optional<Qualifiers> PteTyQuals = std::nullopt;
std::optional<std::string> PteTyText =
getPointeeTypeText(PVD, SM, LangOpts, &PteTyQuals);
if (!PteTyText)
return std::nullopt;
NewTysTexts[i] = getSpanTypeText(*PteTyText, PteTyQuals);
ParmsMask[i] = true;
AtLeastOneParmToFix = true;
}
if (!AtLeastOneParmToFix)
return {};
const auto NewOverloadSignatureCreator =
[&SM, &LangOpts, &NewTysTexts,
&ParmsMask](const FunctionDecl *FD) -> std::optional<std::string> {
std::stringstream SS;
SS << ";";
SS << getEndOfLine().str();
if (auto Prefix = getRangeText(
SourceRange(FD->getBeginLoc(), (*FD->param_begin())->getBeginLoc()),
SM, LangOpts))
SS << Prefix->str();
else
return std::nullopt;
const unsigned NumParms = FD->getNumParams();
for (unsigned i = 0; i < NumParms; i++) {
const ParmVarDecl *Parm = FD->getParamDecl(i);
if (Parm->isImplicit())
continue;
if (ParmsMask[i]) {
SS << NewTysTexts[i];
if (IdentifierInfo *II = Parm->getIdentifier())
SS << ' ' << II->getName().str();
} else if (auto ParmTypeText =
getRangeText(getSourceRangeToTokenEnd(Parm, SM, LangOpts),
SM, LangOpts)) {
SS << ParmTypeText->str();
} else
return std::nullopt;
if (i != NumParms - 1)
SS << ", ";
}
SS << ")";
return SS.str();
};
const auto OldOverloadDefCreator =
[&Handler, &SM, &LangOpts, &NewTysTexts,
&ParmsMask](const FunctionDecl *FD) -> std::optional<std::string> {
std::stringstream SS;
SS << getEndOfLine().str();
if (auto FDPrefix = getRangeText(
SourceRange(FD->getBeginLoc(), FD->getBody()->getBeginLoc()), SM,
LangOpts))
SS << Handler.getUnsafeBufferUsageAttributeTextAt(FD->getBeginLoc(), " ")
<< FDPrefix->str() << "{";
else
return std::nullopt;
if (auto FunQualName = getFunNameText(FD, SM, LangOpts))
SS << "return " << FunQualName->str() << "(";
else
return std::nullopt;
const unsigned NumParms = FD->getNumParams();
for (unsigned i = 0; i < NumParms; i++) {
const ParmVarDecl *Parm = FD->getParamDecl(i);
if (Parm->isImplicit())
continue;
if (!Parm->getIdentifier())
return std::nullopt;
if (ParmsMask[i])
SS << NewTysTexts[i] << "(" << Parm->getIdentifier()->getName().str()
<< ", " << getUserFillPlaceHolder("size") << ")";
else
SS << Parm->getIdentifier()->getName().str();
if (i != NumParms - 1)
SS << ", ";
}
SS << ");}" << getEndOfLine().str();
return SS.str();
};
FixItList FixIts{};
for (FunctionDecl *FReDecl : FD->redecls()) {
std::optional<SourceLocation> Loc = getPastLoc(FReDecl, SM, LangOpts);
if (!Loc)
return {};
if (FReDecl->isThisDeclarationADefinition()) {
assert(FReDecl == FD && "inconsistent function definition");
if (auto OldOverloadDef = OldOverloadDefCreator(FReDecl))
FixIts.emplace_back(FixItHint::CreateInsertion(*Loc, *OldOverloadDef));
else
return {};
} else {
if (!FReDecl->hasAttr<UnsafeBufferUsageAttr>()) {
FixIts.emplace_back(FixItHint::CreateInsertion(
FReDecl->getBeginLoc(), Handler.getUnsafeBufferUsageAttributeTextAt(
FReDecl->getBeginLoc(), " ")));
}
if (auto NewOverloadDecl = NewOverloadSignatureCreator(FReDecl))
FixIts.emplace_back(FixItHint::CreateInsertion(*Loc, *NewOverloadDecl));
else
return {};
}
}
return FixIts;
}
static FixItList fixParamWithSpan(const ParmVarDecl *PVD, const ASTContext &Ctx,
UnsafeBufferUsageHandler &Handler) {
if (hasUnsupportedSpecifiers(PVD, Ctx.getSourceManager())) {
DEBUG_NOTE_DECL_FAIL(PVD, " : has unsupport specifier(s)");
return {};
}
if (PVD->hasDefaultArg()) {
DEBUG_NOTE_DECL_FAIL(PVD, " : has default arg");
return {};
}
std::optional<Qualifiers> PteTyQualifiers = std::nullopt;
std::optional<std::string> PteTyText = getPointeeTypeText(
PVD, Ctx.getSourceManager(), Ctx.getLangOpts(), &PteTyQualifiers);
if (!PteTyText) {
DEBUG_NOTE_DECL_FAIL(PVD, " : invalid pointee type");
return {};
}
std::optional<StringRef> PVDNameText = PVD->getIdentifier()->getName();
if (!PVDNameText) {
DEBUG_NOTE_DECL_FAIL(PVD, " : invalid identifier name");
return {};
}
std::stringstream SS;
std::optional<std::string> SpanTyText = createSpanTypeForVarDecl(PVD, Ctx);
if (PteTyQualifiers)
SS << getSpanTypeText(*PteTyText, PteTyQualifiers);
else
SS << getSpanTypeText(*PteTyText);
if (PVD->getType().hasQualifiers())
SS << ' ' << PVD->getType().getQualifiers().getAsString();
SS << ' ' << PVDNameText->str();
return {FixItHint::CreateReplacement(PVD->getSourceRange(), SS.str())};
}
static FixItList fixVariableWithSpan(const VarDecl *VD,
const DeclUseTracker &Tracker,
ASTContext &Ctx,
UnsafeBufferUsageHandler &Handler) {
const DeclStmt *DS = Tracker.lookupDecl(VD);
if (!DS) {
DEBUG_NOTE_DECL_FAIL(VD,
" : variables declared this way not implemented yet");
return {};
}
if (!DS->isSingleDecl()) {
DEBUG_NOTE_DECL_FAIL(VD, " : multiple VarDecls");
return {};
}
(void)DS;
return fixLocalVarDeclWithSpan(VD, Ctx, getUserFillPlaceHolder(), Handler);
}
static FixItList fixVarDeclWithArray(const VarDecl *D, const ASTContext &Ctx,
UnsafeBufferUsageHandler &Handler) {
FixItList FixIts{};
if (auto CAT = dyn_cast<clang::ConstantArrayType>(D->getType())) {
const QualType &ArrayEltT = CAT->getElementType();
assert(!ArrayEltT.isNull() && "Trying to fix a non-array type variable!");
if (isa<clang::ArrayType>(ArrayEltT.getCanonicalType()))
return {};
const SourceLocation IdentifierLoc = getVarDeclIdentifierLoc(D);
auto MaybeElemTypeTxt =
getRangeText({D->getBeginLoc(), IdentifierLoc}, Ctx.getSourceManager(),
Ctx.getLangOpts());
if (!MaybeElemTypeTxt)
return {};
const llvm::StringRef ElemTypeTxt = MaybeElemTypeTxt->trim();
std::optional<Token> NextTok = Lexer::findNextToken(
IdentifierLoc, Ctx.getSourceManager(), Ctx.getLangOpts());
while (NextTok && !NextTok->is(tok::l_square) &&
NextTok->getLocation() <= D->getSourceRange().getEnd())
NextTok = Lexer::findNextToken(NextTok->getLocation(),
Ctx.getSourceManager(), Ctx.getLangOpts());
if (!NextTok)
return {};
const SourceLocation LSqBracketLoc = NextTok->getLocation();
auto MaybeArraySizeTxt = getRangeText(
{LSqBracketLoc.getLocWithOffset(1), D->getTypeSpecEndLoc()},
Ctx.getSourceManager(), Ctx.getLangOpts());
if (!MaybeArraySizeTxt)
return {};
const llvm::StringRef ArraySizeTxt = MaybeArraySizeTxt->trim();
if (ArraySizeTxt.empty()) {
return {};
}
std::optional<StringRef> IdentText =
getVarDeclIdentifierText(D, Ctx.getSourceManager(), Ctx.getLangOpts());
if (!IdentText) {
DEBUG_NOTE_DECL_FAIL(D, " : failed to locate the identifier");
return {};
}
SmallString<32> Replacement;
raw_svector_ostream OS(Replacement);
OS << "std::array<" << ElemTypeTxt << ", " << ArraySizeTxt << "> "
<< IdentText->str();
FixIts.push_back(FixItHint::CreateReplacement(
SourceRange{D->getBeginLoc(), D->getTypeSpecEndLoc()}, OS.str()));
}
return FixIts;
}
static FixItList fixVariableWithArray(const VarDecl *VD,
const DeclUseTracker &Tracker,
const ASTContext &Ctx,
UnsafeBufferUsageHandler &Handler) {
const DeclStmt *DS = Tracker.lookupDecl(VD);
assert(DS && "Fixing non-local variables not implemented yet!");
if (!DS->isSingleDecl()) {
return {};
}
(void)DS;
return fixVarDeclWithArray(VD, Ctx, Handler);
}
static FixItList
fixVariable(const VarDecl *VD, FixitStrategy::Kind K,
const Decl *D,
const DeclUseTracker &Tracker, ASTContext &Ctx,
UnsafeBufferUsageHandler &Handler) {
if (const auto *PVD = dyn_cast<ParmVarDecl>(VD)) {
auto *FD = dyn_cast<clang::FunctionDecl>(PVD->getDeclContext());
if (!FD || FD != D) {
DEBUG_NOTE_DECL_FAIL(VD, " : function not currently analyzed");
return {};
}
if (FD->isMain() || FD->isConstexpr() ||
FD->getTemplatedKind() != FunctionDecl::TemplatedKind::TK_NonTemplate ||
FD->isVariadic() ||
isa<CXXMethodDecl>(FD) ||
(FD->hasBody() && isa<CXXTryStmt>(FD->getBody())) ||
FD->isOverloadedOperator()) {
DEBUG_NOTE_DECL_FAIL(VD, " : unsupported function decl");
return {};
}
}
switch (K) {
case FixitStrategy::Kind::Span: {
if (VD->getType()->isPointerType()) {
if (const auto *PVD = dyn_cast<ParmVarDecl>(VD))
return fixParamWithSpan(PVD, Ctx, Handler);
if (VD->isLocalVarDecl())
return fixVariableWithSpan(VD, Tracker, Ctx, Handler);
}
DEBUG_NOTE_DECL_FAIL(VD, " : not a pointer");
return {};
}
case FixitStrategy::Kind::Array: {
if (VD->isLocalVarDecl() &&
isa<clang::ConstantArrayType>(VD->getType().getCanonicalType()))
return fixVariableWithArray(VD, Tracker, Ctx, Handler);
DEBUG_NOTE_DECL_FAIL(VD, " : not a local const-size array");
return {};
}
case FixitStrategy::Kind::Iterator:
case FixitStrategy::Kind::Vector:
llvm_unreachable("FixitStrategy not implemented yet!");
case FixitStrategy::Kind::Wontfix:
llvm_unreachable("Invalid strategy!");
}
llvm_unreachable("Unknown strategy!");
}
static bool overlapWithMacro(const FixItList &FixIts) {
return llvm::any_of(FixIts, [](const FixItHint &Hint) {
auto Range = Hint.RemoveRange;
if (Range.getBegin().isMacroID() || Range.getEnd().isMacroID())
return true;
return false;
});
}
static bool isParameterOf(const VarDecl *VD, const Decl *D) {
return isa<ParmVarDecl>(VD) &&
VD->getDeclContext() == dyn_cast<DeclContext>(D);
}
static void eraseVarsForUnfixableGroupMates(
std::map<const VarDecl *, FixItList> &FixItsForVariable,
const VariableGroupsManager &VarGrpMgr) {
SmallVector<const VarDecl *, 8> ToErase;
for (const auto &[VD, Ignore] : FixItsForVariable) {
VarGrpRef Grp = VarGrpMgr.getGroupOfVar(VD);
if (llvm::any_of(Grp,
[&FixItsForVariable](const VarDecl *GrpMember) -> bool {
return !FixItsForVariable.count(GrpMember);
})) {
for (const VarDecl *Member : Grp)
ToErase.push_back(Member);
}
}
for (auto *VarToErase : ToErase)
FixItsForVariable.erase(VarToErase);
}
static FixItList createFunctionOverloadsForParms(
std::map<const VarDecl *, FixItList> &FixItsForVariable ,
const VariableGroupsManager &VarGrpMgr, const FunctionDecl *FD,
const FixitStrategy &S, ASTContext &Ctx,
UnsafeBufferUsageHandler &Handler) {
FixItList FixItsSharedByParms{};
std::optional<FixItList> OverloadFixes =
createOverloadsForFixedParams(S, FD, Ctx, Handler);
if (OverloadFixes) {
FixItsSharedByParms.append(*OverloadFixes);
} else {
for (auto *Member : VarGrpMgr.getGroupOfParms())
FixItsForVariable.erase(Member);
}
return FixItsSharedByParms;
}
static std::map<const VarDecl *, FixItList>
getFixIts(FixableGadgetSets &FixablesForAllVars, const FixitStrategy &S,
ASTContext &Ctx,
const Decl *D,
const DeclUseTracker &Tracker, UnsafeBufferUsageHandler &Handler,
const VariableGroupsManager &VarGrpMgr) {
std::map<const VarDecl *, FixItList> FixItsForVariable;
for (const auto &[VD, Fixables] : FixablesForAllVars.byVar) {
FixItsForVariable[VD] =
fixVariable(VD, S.lookup(VD), D, Tracker, Ctx, Handler);
if (FixItsForVariable[VD].empty()) {
FixItsForVariable.erase(VD);
continue;
}
for (const auto &F : Fixables) {
std::optional<FixItList> Fixits = F->getFixits(S);
if (Fixits) {
FixItsForVariable[VD].insert(FixItsForVariable[VD].end(),
Fixits->begin(), Fixits->end());
continue;
}
#ifndef NDEBUG
Handler.addDebugNoteForVar(
VD, F->getSourceLoc(),
("gadget '" + F->getDebugName() + "' refused to produce a fix")
.str());
#endif
FixItsForVariable.erase(VD);
break;
}
}
eraseVarsForUnfixableGroupMates(FixItsForVariable, VarGrpMgr);
FixItList FixItsSharedByParms{};
if (auto *FD = dyn_cast<FunctionDecl>(D))
FixItsSharedByParms = createFunctionOverloadsForParms(
FixItsForVariable, VarGrpMgr, FD, S, Ctx, Handler);
std::map<const VarDecl *, FixItList> FinalFixItsForVariable{
FixItsForVariable};
for (auto &[Var, Ignore] : FixItsForVariable) {
bool AnyParm = false;
const auto VarGroupForVD = VarGrpMgr.getGroupOfVar(Var, &AnyParm);
for (const VarDecl *GrpMate : VarGroupForVD) {
if (Var == GrpMate)
continue;
if (FixItsForVariable.count(GrpMate))
FinalFixItsForVariable[Var].append(FixItsForVariable[GrpMate]);
}
if (AnyParm) {
assert(!FixItsSharedByParms.empty() &&
"Should not try to fix a parameter that does not belong to a "
"FunctionDecl");
FinalFixItsForVariable[Var].append(FixItsSharedByParms);
}
}
for (auto Iter = FinalFixItsForVariable.begin();
Iter != FinalFixItsForVariable.end();)
if (overlapWithMacro(Iter->second) ||
clang::internal::anyConflict(Iter->second, Ctx.getSourceManager())) {
Iter = FinalFixItsForVariable.erase(Iter);
} else
Iter++;
return FinalFixItsForVariable;
}
template <typename VarDeclIterTy>
static FixitStrategy
getNaiveStrategy(llvm::iterator_range<VarDeclIterTy> UnsafeVars) {
FixitStrategy S;
for (const VarDecl *VD : UnsafeVars) {
if (isa<ConstantArrayType>(VD->getType().getCanonicalType()))
S.set(VD, FixitStrategy::Kind::Array);
else
S.set(VD, FixitStrategy::Kind::Span);
}
return S;
}
class VariableGroupsManagerImpl : public VariableGroupsManager {
const std::vector<VarGrpTy> Groups;
const std::map<const VarDecl *, unsigned> &VarGrpMap;
const llvm::SetVector<const VarDecl *> &GrpsUnionForParms;
public:
VariableGroupsManagerImpl(
const std::vector<VarGrpTy> &Groups,
const std::map<const VarDecl *, unsigned> &VarGrpMap,
const llvm::SetVector<const VarDecl *> &GrpsUnionForParms)
: Groups(Groups), VarGrpMap(VarGrpMap),
GrpsUnionForParms(GrpsUnionForParms) {}
VarGrpRef getGroupOfVar(const VarDecl *Var, bool *HasParm) const override {
if (GrpsUnionForParms.contains(Var)) {
if (HasParm)
*HasParm = true;
return GrpsUnionForParms.getArrayRef();
}
if (HasParm)
*HasParm = false;
auto It = VarGrpMap.find(Var);
if (It == VarGrpMap.end())
return std::nullopt;
return Groups[It->second];
}
VarGrpRef getGroupOfParms() const override {
return GrpsUnionForParms.getArrayRef();
}
};
void clang::checkUnsafeBufferUsage(const Decl *D,
UnsafeBufferUsageHandler &Handler,
bool EmitSuggestions) {
#ifndef NDEBUG
Handler.clearDebugNotes();
#endif
assert(D && D->getBody());
if (const auto *fd = dyn_cast<CXXMethodDecl>(D)) {
if (fd->getParent()->isLambda() && fd->getParent()->isLocalClass())
return;
}
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
for (FunctionDecl *FReDecl : FD->redecls()) {
if (FReDecl->isExternC()) {
EmitSuggestions = false;
break;
}
}
}
WarningGadgetSets UnsafeOps;
FixableGadgetSets FixablesForAllVars;
auto [FixableGadgets, WarningGadgets, Tracker] =
findGadgets(D, Handler, EmitSuggestions);
if (!EmitSuggestions) {
for (const auto &G : WarningGadgets) {
G->handleUnsafeOperation(Handler, false,
D->getASTContext());
}
assert(FixableGadgets.size() == 0 &&
"Fixable gadgets found but suggestions not requested!");
return;
}
if (!WarningGadgets.empty()) {
for (const auto &G : FixableGadgets) {
for (const auto *DRE : G->getClaimedVarUseSites()) {
Tracker.claimUse(DRE);
}
}
}
if (WarningGadgets.empty())
return;
UnsafeOps = groupWarningGadgetsByVar(std::move(WarningGadgets));
FixablesForAllVars = groupFixablesByVar(std::move(FixableGadgets));
std::map<const VarDecl *, FixItList> FixItsForVariableGroup;
for (auto it = FixablesForAllVars.byVar.cbegin();
it != FixablesForAllVars.byVar.cend();) {
if ((!it->first->isLocalVarDecl() && !isa<ParmVarDecl>(it->first))) {
#ifndef NDEBUG
Handler.addDebugNoteForVar(it->first, it->first->getBeginLoc(),
("failed to produce fixit for '" +
it->first->getNameAsString() +
"' : neither local nor a parameter"));
#endif
it = FixablesForAllVars.byVar.erase(it);
} else if (it->first->getType().getCanonicalType()->isReferenceType()) {
#ifndef NDEBUG
Handler.addDebugNoteForVar(it->first, it->first->getBeginLoc(),
("failed to produce fixit for '" +
it->first->getNameAsString() +
"' : has a reference type"));
#endif
it = FixablesForAllVars.byVar.erase(it);
} else if (Tracker.hasUnclaimedUses(it->first)) {
it = FixablesForAllVars.byVar.erase(it);
} else if (it->first->isInitCapture()) {
#ifndef NDEBUG
Handler.addDebugNoteForVar(it->first, it->first->getBeginLoc(),
("failed to produce fixit for '" +
it->first->getNameAsString() +
"' : init capture"));
#endif
it = FixablesForAllVars.byVar.erase(it);
} else {
++it;
}
}
#ifndef NDEBUG
for (const auto &it : UnsafeOps.byVar) {
const VarDecl *const UnsafeVD = it.first;
auto UnclaimedDREs = Tracker.getUnclaimedUses(UnsafeVD);
if (UnclaimedDREs.empty())
continue;
const auto UnfixedVDName = UnsafeVD->getNameAsString();
for (const clang::DeclRefExpr *UnclaimedDRE : UnclaimedDREs) {
std::string UnclaimedUseTrace =
getDREAncestorString(UnclaimedDRE, D->getASTContext());
Handler.addDebugNoteForVar(
UnsafeVD, UnclaimedDRE->getBeginLoc(),
("failed to produce fixit for '" + UnfixedVDName +
"' : has an unclaimed use\nThe unclaimed DRE trace: " +
UnclaimedUseTrace));
}
}
#endif
using DepMapTy = DenseMap<const VarDecl *, llvm::SetVector<const VarDecl *>>;
DepMapTy DependenciesMap{};
DepMapTy PtrAssignmentGraph{};
for (auto it : FixablesForAllVars.byVar) {
for (const FixableGadget *fixable : it.second) {
std::optional<std::pair<const VarDecl *, const VarDecl *>> ImplPair =
fixable->getStrategyImplications();
if (ImplPair) {
std::pair<const VarDecl *, const VarDecl *> Impl = std::move(*ImplPair);
PtrAssignmentGraph[Impl.first].insert(Impl.second);
}
}
}
The following code does a BFS traversal of the `PtrAssignmentGraph`
considering all unsafe vars as starting nodes and constructs an undirected
graph `DependenciesMap`. Constructing the `DependenciesMap` in this manner
elimiates all variables that are unreachable from any unsafe var. In other
words, this removes all dependencies that don't include any unsafe variable
and consequently don't need any fixit generation.
Note: A careful reader would observe that the code traverses
`PtrAssignmentGraph` using `CurrentVar` but adds edges between `Var` and
`Adj` and not between `CurrentVar` and `Adj`. Both approaches would
achieve the same result but the one used here dramatically cuts the
amount of hoops the second part of the algorithm needs to jump, given that
a lot of these connections become "direct". The reader is advised not to
imagine how the graph is transformed because of using `Var` instead of
`CurrentVar`. The reader can continue reading as if `CurrentVar` was used,
and think about why it's equivalent later.
*/
std::set<const VarDecl *> VisitedVarsDirected{};
for (const auto &[Var, ignore] : UnsafeOps.byVar) {
if (VisitedVarsDirected.find(Var) == VisitedVarsDirected.end()) {
std::queue<const VarDecl *> QueueDirected{};
QueueDirected.push(Var);
while (!QueueDirected.empty()) {
const VarDecl *CurrentVar = QueueDirected.front();
QueueDirected.pop();
VisitedVarsDirected.insert(CurrentVar);
auto AdjacentNodes = PtrAssignmentGraph[CurrentVar];
for (const VarDecl *Adj : AdjacentNodes) {
if (VisitedVarsDirected.find(Adj) == VisitedVarsDirected.end()) {
QueueDirected.push(Adj);
}
DependenciesMap[Var].insert(Adj);
DependenciesMap[Adj].insert(Var);
}
}
}
}
std::vector<VarGrpTy> Groups;
std::map<const VarDecl *, unsigned> VarGrpMap;
llvm::SetVector<const VarDecl *>
GrpsUnionForParms;
std::set<const VarDecl *> VisitedVars{};
for (const auto &[Var, ignore] : UnsafeOps.byVar) {
if (VisitedVars.find(Var) == VisitedVars.end()) {
VarGrpTy &VarGroup = Groups.emplace_back();
std::queue<const VarDecl *> Queue{};
Queue.push(Var);
while (!Queue.empty()) {
const VarDecl *CurrentVar = Queue.front();
Queue.pop();
VisitedVars.insert(CurrentVar);
VarGroup.push_back(CurrentVar);
auto AdjacentNodes = DependenciesMap[CurrentVar];
for (const VarDecl *Adj : AdjacentNodes) {
if (VisitedVars.find(Adj) == VisitedVars.end()) {
Queue.push(Adj);
}
}
}
bool HasParm = false;
unsigned GrpIdx = Groups.size() - 1;
for (const VarDecl *V : VarGroup) {
VarGrpMap[V] = GrpIdx;
if (!HasParm && isParameterOf(V, D))
HasParm = true;
}
if (HasParm)
GrpsUnionForParms.insert(VarGroup.begin(), VarGroup.end());
}
}
for (auto I = FixablesForAllVars.byVar.begin();
I != FixablesForAllVars.byVar.end();) {
if (!VisitedVars.count((*I).first)) {
I = FixablesForAllVars.byVar.erase(I);
} else
++I;
}
FixitStrategy NaiveStrategy = getNaiveStrategy(llvm::make_filter_range(
VisitedVars, [&FixablesForAllVars](const VarDecl *V) {
return FixablesForAllVars.byVar.count(V);
}));
VariableGroupsManagerImpl VarGrpMgr(Groups, VarGrpMap, GrpsUnionForParms);
if (isa<NamedDecl>(D))
FixItsForVariableGroup =
getFixIts(FixablesForAllVars, NaiveStrategy, D->getASTContext(), D,
Tracker, Handler, VarGrpMgr);
for (const auto &G : UnsafeOps.noVar) {
G->handleUnsafeOperation(Handler, false,
D->getASTContext());
}
for (const auto &[VD, WarningGadgets] : UnsafeOps.byVar) {
auto FixItsIt = FixItsForVariableGroup.find(VD);
Handler.handleUnsafeVariableGroup(VD, VarGrpMgr,
FixItsIt != FixItsForVariableGroup.end()
? std::move(FixItsIt->second)
: FixItList{},
D, NaiveStrategy);
for (const auto &G : WarningGadgets) {
G->handleUnsafeOperation(Handler, true,
D->getASTContext());
}
}
}