Skip to content

Commit cce3b53

Browse files
committed
[memory analysis] extend GetAllocType to see through optional-like wrapper constructions
Restrict the pointer-arithmetic Unknown-downgrade in VisitBinaryOperator to pointer-typed variables only, so unrelated scalar compound-assignments no longer poison tracked results. Allow AnalyzeAllocType to also traverse record-typed (class/struct) return values, not just raw pointers, so wrapper types like std::optional<T*> get analyzed instead of short-circuiting to None. In handleExpr: - Unwrap unary operator* calls (e.g. on an optional) to recurse into the wrapped value instead of falling back to a call analysis. - Detect nullptr/NULL literals directly via CXXNullPtrLiteralExpr/ GNUNullExpr, since the existing CK_NullToPointer cast walk cannot see through a MaterializeTemporaryExpr (e.g. nullptr_t passed to optional's converting constructor). - Recognize implicit converting-constructor calls (ExprWithCleanups -> ImplicitCastExpr(CK_ConstructorConversion) -> CXXConstructExpr) and recurse into the constructor's argument, so New/Malloc/etc. tags propagate through implicit wrapping (e.g. ). - Add isNullOpt() to recognize std::nullopt/cpp::nullopt construction as AllocType::Null. Known limitation (documented via FIXME + test): optional's copy/move constructor is not a converting constructor, so copying an optional loses its tracked AllocType and reports None. Add FunctionReflection_GetAllocType coverage (func60-func69) for: implicit optional-wrapping of new, std::nullopt join behavior, default-constructed empty structs, direct/list-initialized plain wrapper structs, operator* unwrap (unary vs. binary), direct nullptr returns through optional, and pointer arithmetic inside a for-loop increment clause.
1 parent ff9874a commit cce3b53

2 files changed

Lines changed: 130 additions & 5 deletions

File tree

lib/CppInterOp/CppInterOp.cpp

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1798,7 +1798,7 @@ struct AllocationTraverser : RecursiveASTVisitor<AllocationTraverser> {
17981798
varMap[VD] = handleExpr(RHS);
17991799
return true;
18001800
}
1801-
if (BO->isCompoundAssignmentOp()) {
1801+
if (VD->getType()->isPointerType() && BO->isCompoundAssignmentOp()) {
18021802
if (undoLog)
18031803
undoLog->try_emplace(VD, varMap[VD]);
18041804
varMap[VD] = AllocType::Unknown;
@@ -1870,6 +1870,20 @@ struct AllocationTraverser : RecursiveASTVisitor<AllocationTraverser> {
18701870
return AllocType::New;
18711871
}
18721872

1873+
static bool isNullOpt(const clang::CXXConstructExpr* CCE) {
1874+
// Expected: std/cpp::optional constructor
1875+
const clang::CXXConstructorDecl* CCD = CCE->getConstructor();
1876+
// optional(nullopt_t) noexcept; -> one arg
1877+
if (CCD->getNumParams() != 1)
1878+
return false;
1879+
clang::QualType ParamTy =
1880+
CCD->getParamDecl(0)->getType().getNonReferenceType();
1881+
// FIXME: Copy constructor of optional is not handled
1882+
if (const auto* CRD = ParamTy->getAsCXXRecordDecl())
1883+
return CRD->getName() == "nullopt_t";
1884+
return false;
1885+
}
1886+
18731887
std::optional<AllocType> handleExpr(const clang::Expr* expr) {
18741888
const clang::Expr* finExpr = expr->IgnoreParenCasts();
18751889
// Case: return new __type__
@@ -1888,17 +1902,44 @@ struct AllocationTraverser : RecursiveASTVisitor<AllocationTraverser> {
18881902
}
18891903

18901904
// Case: malloc or another func call
1891-
if (const auto* CE = dyn_cast<CallExpr>(finExpr))
1905+
if (const auto* CE = dyn_cast<CallExpr>(finExpr)) {
1906+
// Case: std::optional<int*> ptr = new int; return *ptr;
1907+
if (const auto* COCE = dyn_cast<clang::CXXOperatorCallExpr>(CE)) {
1908+
if (COCE->getOperator() == OverloadedOperatorKind::OO_Star &&
1909+
!COCE->isInfixBinaryOp())
1910+
return handleExpr(COCE->getArg(0));
1911+
}
18921912
return handleCall(CE);
1913+
}
18931914

18941915
// Case: NULL, nullptr or (int*)(__integerLiteral__)
1916+
if (llvm::isa<clang::CXXNullPtrLiteralExpr>(finExpr) ||
1917+
llvm::isa<clang::GNUNullExpr>(finExpr))
1918+
return AllocType::Null;
18951919
const clang::Expr* parExpr = expr->IgnoreParens();
18961920
while (const auto* CE = dyn_cast<CastExpr>(parExpr)) {
18971921
if (CE->getCastKind() == CK_NullToPointer)
18981922
return AllocType::Null;
18991923
parExpr = CE->getSubExpr();
19001924
}
19011925

1926+
// Case: constructor cast
1927+
if (auto* CCE = dyn_cast<clang::CXXConstructExpr>(finExpr)) {
1928+
// std::nullopt or cpp::nullopt
1929+
if (isNullOpt(CCE))
1930+
return AllocType::Null;
1931+
const clang::Expr* stripped = expr->IgnoreParens();
1932+
// ExprWithCleanups -> ImplicitCastExpr -> CXXConstructExpr pattern
1933+
if (const auto* EWC = dyn_cast<clang::ExprWithCleanups>(stripped))
1934+
stripped = EWC->getSubExpr();
1935+
// We do not want to analyze explicit conversions
1936+
auto* ICE = dyn_cast<clang::ImplicitCastExpr>(stripped);
1937+
bool isImplicitConv =
1938+
ICE && ICE->getCastKind() == CK_ConstructorConversion;
1939+
// ImplicitCastExpr -> CXXConstructExpr pattern
1940+
if (isImplicitConv)
1941+
return handleExpr(CCE->getArg(0));
1942+
}
19021943
return AllocType::None;
19031944
}
19041945
};
@@ -1908,8 +1949,8 @@ static std::optional<AllocType>
19081949
AnalyzeAllocType(const clang::FunctionDecl* Fn,
19091950
std::unordered_map<const clang::FunctionDecl*,
19101951
std::optional<AllocType>>& visitedFuncs) {
1911-
const clang::QualType QT = Fn->getReturnType();
1912-
if (!QT->isPointerType())
1952+
QualType QT = Fn->getReturnType();
1953+
if (!QT->isPointerType() && !QT->isRecordType())
19131954
return AllocType::None;
19141955
const Stmt* fnBody = Fn->getBody();
19151956
if (!fnBody)

unittests/CppInterOp/FunctionReflectionTest.cpp

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,8 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetAllocType) {
833833
std::string code = R"(
834834
#include <new>
835835
#include <stdlib.h>
836+
#include <optional>
837+
#include <initializer_list>
836838
837839
int* func0(int n){ return new int(n); }
838840
@@ -1206,8 +1208,79 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetAllocType) {
12061208
void* func58(){ return __builtin_operator_new(64); }
12071209
void* func59(){int* m = (int*)0; return malloc(sizeof(int));}
12081210
1211+
template <typename T>
1212+
std::optional<T*> func60_helper(){
1213+
T* ptr = new T;
1214+
return ptr;
1215+
}
1216+
1217+
std::optional<int*> func60(){
1218+
return func60_helper<int>();
1219+
}
1220+
1221+
std::optional<int*> func61(int n){
1222+
if(n>0)
1223+
return std::nullopt;
1224+
return new int;
1225+
}
1226+
1227+
struct Empty { Empty() {} };
1228+
Empty func62() {
1229+
Empty e;
1230+
return e;
1231+
}
1232+
1233+
struct Wrapper { int a, b; Wrapper(int x, int y) : a(x), b(y) {} };
1234+
Wrapper func63() {
1235+
int a = 1, b = 2;
1236+
return Wrapper(a, b);
1237+
}
1238+
1239+
Wrapper func64() {
1240+
int a = 1, b = 2;
1241+
return {a, b};
1242+
}
1243+
1244+
int* func65(){
1245+
auto ptr = func61(10);
1246+
if(ptr)
1247+
return *ptr;
1248+
return nullptr;
1249+
}
1250+
1251+
struct Box {
1252+
int* p;
1253+
Box(int* p) : p(p) {}
1254+
Box operator*(const Box& o) const { return Box(nullptr); }
1255+
};
1256+
Box func66() {
1257+
Box a(new int);
1258+
Box b(nullptr);
1259+
return a * b;
1260+
}
1261+
1262+
std::optional<int*> func67(){
1263+
return nullptr;
1264+
}
1265+
1266+
int* func68(){
1267+
int tmp = 10;
1268+
int* m = (int*)tmp;
1269+
for(int i = 0; i < 10; m+=1){
1270+
return m;
1271+
}
1272+
return m;
1273+
}
1274+
1275+
std::optional<int*> func69() {
1276+
std::optional<int*> a = new int;
1277+
//FIXME: Copy constructor is called, which takes one arg but is not nullopt constructor
1278+
//so it returns none, look at isNullOpt(const clang::CXXConstructExpr* CCE) function
1279+
std::optional<int*> b = a;
1280+
return b;
1281+
}
12091282
)";
1210-
TestFixture::CreateInterpreter();
1283+
TestFixture::CreateInterpreter({"-std=c++17"});
12111284
Interp->declare(code);
12121285

12131286
#define TESTAC(N, EXP) \
@@ -1275,10 +1348,21 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetAllocType) {
12751348
TESTAC(57, None);
12761349
TESTAC(58, OperatorNew);
12771350
TESTAC(59, Malloc);
1351+
TESTAC(60, New);
1352+
TESTAC(61, New);
1353+
TESTAC(62, None);
1354+
TESTAC(63, None);
1355+
TESTAC(64, None);
1356+
TESTAC(65, New);
1357+
TESTAC(66, None);
1358+
TESTAC(67, Null);
1359+
TESTAC(68, Unknown);
1360+
TESTAC(69, None);
12781361
#undef TESTAC
12791362

12801363
Cpp::DeleteInterpreter();
12811364
}
1365+
12821366
TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionSignature) {
12831367
std::vector<Decl*> Decls;
12841368
std::string code = R"(

0 commit comments

Comments
 (0)