Skip to content

[analyzer] Support C++23 static operator calls #84972

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ Static Analyzer
- Fixed crashing on loops if the loop variable was declared in switch blocks
but not under any case blocks if ``unroll-loops=true`` analyzer config is
set. (#GH68819)
- Support C++23 static operator calls. (#GH84972)

New features
^^^^^^^^^^^^
Expand Down
72 changes: 72 additions & 0 deletions clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ namespace ento {

enum CallEventKind {
CE_Function,
CE_CXXStaticOperator,
CE_CXXMember,
CE_CXXMemberOperator,
CE_CXXDestructor,
Expand Down Expand Up @@ -709,6 +710,77 @@ class CXXInstanceCall : public AnyFunctionCall {
}
};

/// Represents a static C++ operator call.
///
/// "A" in this example.
/// However, "B" and "C" are represented by SimpleFunctionCall.
/// \code
/// struct S {
/// int pad;
/// static void operator()(int x, int y);
/// };
/// S s{10};
/// void (*fptr)(int, int) = &S::operator();
///
/// s(1, 2); // A
/// S::operator()(1, 2); // B
/// fptr(1, 2); // C
/// \endcode
class CXXStaticOperatorCall : public SimpleFunctionCall {
friend class CallEventManager;

protected:
CXXStaticOperatorCall(const CXXOperatorCallExpr *CE, ProgramStateRef St,
const LocationContext *LCtx,
CFGBlock::ConstCFGElementRef ElemRef)
: SimpleFunctionCall(CE, St, LCtx, ElemRef) {}
CXXStaticOperatorCall(const CXXStaticOperatorCall &Other) = default;

void cloneTo(void *Dest) const override {
new (Dest) CXXStaticOperatorCall(*this);
}

public:
const CXXOperatorCallExpr *getOriginExpr() const override {
return cast<CXXOperatorCallExpr>(SimpleFunctionCall::getOriginExpr());
}

unsigned getNumArgs() const override {
// Ignore the object parameter that is not used for static member functions.
assert(getOriginExpr()->getNumArgs() > 0);
return getOriginExpr()->getNumArgs() - 1;
}

const Expr *getArgExpr(unsigned Index) const override {
// Ignore the object parameter that is not used for static member functions.
return getOriginExpr()->getArg(Index + 1);
}

std::optional<unsigned>
getAdjustedParameterIndex(unsigned ASTArgumentIndex) const override {
// Ignore the object parameter that is not used for static member functions.
if (ASTArgumentIndex == 0)
return std::nullopt;
return ASTArgumentIndex - 1;
}

unsigned getASTArgumentIndex(unsigned CallArgumentIndex) const override {
// Account for the object parameter for the static member function.
return CallArgumentIndex + 1;
}

OverloadedOperatorKind getOverloadedOperator() const {
return getOriginExpr()->getOperator();
}

Kind getKind() const override { return CE_CXXStaticOperator; }
StringRef getKindAsString() const override { return "CXXStaticOperatorCall"; }

static bool classof(const CallEvent *CA) {
return CA->getKind() == CE_CXXStaticOperator;
}
};

/// Represents a non-static C++ member function call.
///
/// Example: \c obj.fun()
Expand Down
5 changes: 4 additions & 1 deletion clang/lib/StaticAnalyzer/Core/CallEvent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1408,9 +1408,12 @@ CallEventManager::getSimpleCall(const CallExpr *CE, ProgramStateRef State,

if (const auto *OpCE = dyn_cast<CXXOperatorCallExpr>(CE)) {
const FunctionDecl *DirectCallee = OpCE->getDirectCallee();
if (const auto *MD = dyn_cast<CXXMethodDecl>(DirectCallee))
if (const auto *MD = dyn_cast<CXXMethodDecl>(DirectCallee)) {
if (MD->isImplicitObjectMemberFunction())
return create<CXXMemberOperatorCall>(OpCE, State, LCtx, ElemRef);
if (MD->isStatic())
return create<CXXStaticOperatorCall>(OpCE, State, LCtx, ElemRef);
}

} else if (CE->getCallee()->getType()->isBlockPointerType()) {
return create<BlockCall>(CE, State, LCtx, ElemRef);
Expand Down
1 change: 1 addition & 0 deletions clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred,
const StackFrameContext *CallerSFC = CurLC->getStackFrame();
switch (Call.getKind()) {
case CE_Function:
case CE_CXXStaticOperator:
case CE_Block:
break;
case CE_CXXMember:
Expand Down
38 changes: 38 additions & 0 deletions clang/test/Analysis/cxx23-static-operator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// RUN: %clang_analyze_cc1 -std=c++2b -verify %s \
// RUN: -analyzer-checker=core,debug.ExprInspection

template <typename T> void clang_analyzer_dump(T);

struct Adder {
int data;
static int operator()(int x, int y) {
clang_analyzer_dump(x); // expected-warning {{1}}
clang_analyzer_dump(y); // expected-warning {{2}}
return x + y;
}
};

void static_operator_call_inlines() {
Adder s{10};
clang_analyzer_dump(s(1, 2)); // expected-warning {{3}}
}

struct DataWithCtor {
int x;
int y;
DataWithCtor(int parm) : x(parm + 10), y(parm + 20) {
clang_analyzer_dump(this); // expected-warning {{&v}}
}
};

struct StaticSubscript {
static void operator[](DataWithCtor v) {
clang_analyzer_dump(v.x); // expected-warning {{20}}
clang_analyzer_dump(v.y); // expected-warning {{30}}
}
};

void top() {
StaticSubscript s;
s[DataWithCtor{10}];
}