Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
10 changes: 10 additions & 0 deletions include/CppInterOp/CppInterOpTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,16 @@ enum class InterpreterLanguageStandard : unsigned char {
hlsl202y,
lang_unspecified
};

enum class AllocType : unsigned char {
None,
New,
NewArr,
Malloc,
Unknown,
CustomAlloc
};

inline QualKind operator|(QualKind a, QualKind b) {
return static_cast<QualKind>(static_cast<unsigned char>(a) |
static_cast<unsigned char>(b));
Expand Down
135 changes: 135 additions & 0 deletions lib/CppInterOp/CppInterOp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,15 @@
#include "clang/AST/GlobalDecl.h"
#include "clang/AST/Mangle.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/AST/OperationKinds.h"
#include "clang/AST/QualTypeNames.h"
#include "clang/AST/RawCommentList.h"
#include "clang/AST/RecordLayout.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/Type.h"
#include "clang/AST/VTableBuilder.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/LangStandard.h"
Expand Down Expand Up @@ -108,6 +111,7 @@
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <set>
#include <sstream>
#include <stack>
Expand Down Expand Up @@ -1557,6 +1561,137 @@ bool IsFunctionProtoType(ConstTypeRef TyRef) {
return llvm::isa_and_nonnull<clang::FunctionProtoType>(T);
}

static AllocType handleNew(const clang::CXXNewExpr* CNE) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: no header providing "Cpp::AllocType" is directly included [misc-include-cleaner]

(T);
               ^

if (CNE->getNumPlacementArgs() > 0)
return AllocType::None;
if (CNE->isArray())
return AllocType::NewArr;
return AllocType::New;
}

static AllocType AnalyzeAllocType(const clang::FunctionDecl* Fn);

static AllocType handleCall(const clang::CallExpr* CE) {
if (const auto* FD = CE->getDirectCallee()) {
if (FD->getBuiltinID() == Builtin::ID::BImalloc)
Comment thread
keremsahn marked this conversation as resolved.
return AllocType::Malloc;
return AnalyzeAllocType(FD);
}
// Function pointer calle
return AllocType::Unknown;
}

static AllocType handleExpr(const clang::Expr* expr,
std::map<const VarDecl*, AllocType>& varMap) {
const clang::Expr* finExpr = expr->IgnoreParenCasts();
// Case: return new __type__
if (const auto* CNE = dyn_cast<CXXNewExpr>(finExpr))
return handleNew(CNE);

// Case: returns a variable
else if (const auto* DRE = dyn_cast<DeclRefExpr>(finExpr)) {
Comment thread
keremsahn marked this conversation as resolved.
Outdated
if (const auto* VD = dyn_cast<VarDecl>(DRE->getDecl())) {
auto it = varMap.find(VD);
if (it != varMap.end())
return it->second;
}
// FIXME: BindingDecl, NonTypeTemplateParmDecl are not handled
return AllocType::None;
}

// Case: malloc
else if (const auto* CE = dyn_cast<CallExpr>(finExpr)) {
return handleCall(CE);
}
Comment thread
keremsahn marked this conversation as resolved.
Outdated
return AllocType::None;
Comment thread
keremsahn marked this conversation as resolved.
Comment thread
keremsahn marked this conversation as resolved.
Comment thread
keremsahn marked this conversation as resolved.
}

static std::vector<const ReturnStmt*>
getAllRetStmt(const clang::CompoundStmt* CS) {
struct RetStmtVisitor : RecursiveASTVisitor<RetStmtVisitor> {
std::vector<const ReturnStmt*> vec;
bool VisitReturnStmt(ReturnStmt* RS) {
vec.push_back(RS);
return true;
}
};
RetStmtVisitor Visitor;
Visitor.TraverseStmt(const_cast<CompoundStmt*>(CS));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: do not use const_cast to remove const qualifier [cppcoreguidelines-pro-type-const-cast]

isitor;
                               ^

return Visitor.vec;
}

static std::map<const VarDecl*, AllocType>
Comment thread
keremsahn marked this conversation as resolved.
Outdated
getAllVarDecl(const clang::CompoundStmt* CS) {
struct VarVisitor : RecursiveASTVisitor<VarVisitor> {
std::map<const VarDecl*, AllocType> varMap;
bool VisitVarDecl(VarDecl* VD) {
Expr* expr = VD->getInit();
if (expr)
varMap[VD] = handleExpr(expr, varMap);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the handleExpr here cause it to be run 2 times on the same input, because we also have it in VisitBinaryOperator?

@keremsahn keremsahn Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two functions handle different situations.

int* x = new int(n);

AST of this statement:

-DeclStmt <line:16:5, col:24>
|   | `-VarDecl <col:5, col:23> col:10 used x 'int *' cinit
|   |   `-CXXNewExpr <col:14, col:23> 'int *' Function 0x4a8e6ff0 'operator new' 'void *(std::size_t)'
|   |     `-ImplicitCastExpr <col:22> 'int' <LValueToRValue>
|   |       `-DeclRefExpr <col:22> 'int' lvalue ParmVar 0x4a999868 'n' 'int'
y=x;

AST of this:

-BinaryOperator <line:18:5, col:7> 'int *' lvalue '='
|   | |-DeclRefExpr <col:5> 'int *' lvalue Var 0x4a999b10 'y' 'int *'
|   | `-ImplicitCastExpr <col:7> 'int *' <LValueToRValue>
|   |   `-DeclRefExpr <col:7> 'int *' lvalue Var 0x4a9999c8 'x' 'int *'

So basically in variable declarations there is no BinaryOperator node, hence the intersection

else
varMap[VD] = AllocType::None;
return true;
}

bool VisitBinaryOperator(clang::BinaryOperator* BO) {
if (BO->getOpcode() == BO_Assign) {
Comment thread
keremsahn marked this conversation as resolved.
Outdated
Expr* LHS = BO->getLHS();
Comment thread
keremsahn marked this conversation as resolved.
Outdated
LHS = LHS->IgnoreParenCasts();
if (auto* DRE = dyn_cast<DeclRefExpr>(LHS)) {
if (auto* VD = dyn_cast<VarDecl>(DRE->getDecl())) {
Expr* RHS = BO->getRHS();
varMap[VD] = handleExpr(RHS, varMap);
}
}
}
return true;
}
};
VarVisitor Visitor;
Visitor.TraverseStmt(const_cast<CompoundStmt*>(CS));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: do not use const_cast to remove const qualifier [cppcoreguidelines-pro-type-const-cast]

isitor;
                               ^

return Visitor.varMap;
}

static AllocType AnalyzeAllocType(const clang::FunctionDecl* Fn) {
const clang::QualType QT = Fn->getReturnType();
if (!QT->isPointerType())
return AllocType::None;
const Stmt* fnBody = Fn->getBody();
if (!fnBody)
return AllocType::Unknown;
const auto* CmpStmt = dyn_cast<clang::CompoundStmt>(fnBody);
// FIXME:: try catch blocks are not CompoundStmt, only edge case
if (!CmpStmt)
return AllocType::Unknown;
std::map<const VarDecl*, AllocType> varMap = getAllVarDecl(CmpStmt);
std::vector<const ReturnStmt*> allRetStmt = getAllRetStmt(CmpStmt);
std::optional<AllocType> res;
Comment thread
keremsahn marked this conversation as resolved.
for (const ReturnStmt* retStmt : allRetStmt) {
const clang::Expr* retExpr = retStmt->getRetValue();
if (retExpr == nullptr)
continue;
AllocType tmp = handleExpr(retExpr, varMap);
if (!res.has_value())
res = tmp;
// If function's allocation behaviour differs between different cases,
// analyzer returns unknown.
else if (*res != tmp)
return AllocType::Unknown;
}
return res.value_or(AllocType::None);
}

AllocType GetAllocType(ConstFuncRef Fn) {
INTEROP_TRACE(Fn);
if (Fn) {
const auto* D = unwrap<Decl>(Fn);
if (const auto* FD = dyn_cast<FunctionDecl>(D)) {
return INTEROP_RETURN(AnalyzeAllocType(FD));
}
}
return INTEROP_RETURN(AllocType::None);
}

void GetFnTypeSignature(ConstTypeRef fn_type, std::vector<TypeRef>& sig) {
INTEROP_TRACE(fn_type, INTEROP_OUT(sig));
QualType QT = QualType::getFromOpaquePtr(fn_type.data);
Expand Down
9 changes: 9 additions & 0 deletions lib/CppInterOp/CppInterOp.td
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,15 @@ def IsFunctionProtoType : CppInterOpAPI {
];
}

def GetAllocType: CppInterOpAPI {
let Doc = "Analyzes function's body to detect memory allocation behaviour";

let ReturnType = "AllocType";
let Args = [
Arg<"ConstFuncRef", "Fn">
];
}

def GetFnTypeSignature : CppInterOpAPI {
let Doc = [{Pushed the signature type of the given function type,
where the first item is the return type.}];
Expand Down
112 changes: 112 additions & 0 deletions unittests/CppInterOp/FunctionReflectionTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,118 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_FunctionTypes) {
EXPECT_TRUE(Cpp::IsSameType(typ1, typ2));
}

TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetAllocType) {
std::string code = R"(
#include <new>
#include <stdlib.h>

int* func0(int n){ return new int(n); }

void* func1(int n){ return (void*) new int(n); }

void* func2(int n){ return static_cast<void*>(new int(n)); }

int* func3(int n){ int* x = new int(n); int* y; y=x; return y; }

int* func4(int n){int* x = new int(n); int* y = x; return y; }

void* func5(int n){ return malloc(sizeof(int)); }

void* func6(int n){ void* x = malloc(sizeof(int)); return x; }

void** func7(int n){ void** arr = new void*[n]; return arr;}

void** func8(int n){ return new void*[n]; }

int* func9(int n){ int* x = new int(n); int* y = x; int* z = y; return z; }

int* func10(int n){ return static_cast<int*>(malloc(sizeof(int(n)))); }

int* func11(int n){
int* x = new int(n);
int* y = nullptr;
y = x;
x = nullptr;
return y;
}

int* func12(int n){ int* x = static_cast<int*>(malloc(sizeof(int))); return x;}

int* func13(int n){ int* x = new int(n); return (((x))); }

int func14(int n){ return n; }

int* func15(int n);

int* func16(int n) try { return new int(n); } catch(...) { return nullptr; }

int* func17(int* p){ return p; }

int* func18_t(int n){ return nullptr; }
typedef int* (*FnPtr18)(int);
FnPtr18 func18(int n){ return func18_t; }

int* func19_helper(int n){ return nullptr; }
int* func19(int n){ return func19_helper(n); }

int* func20(int* (*fp)(int), int n){ return fp(n); }

int* func21(int n){ static char buf[16]; return new (&buf) int(n); }

int* func22(int n){ []{ return; }(); return new int(n); }
Comment thread
keremsahn marked this conversation as resolved.

int* func23;

int* func24(int n){ return func0(n); }

void** func25(int n){ void** arr = func7(n); return arr; }

int* func26(bool b){
if(b)
return (int*)malloc(sizeof(int));
return new int;
}
)";
TestFixture::CreateInterpreter();
Interp->declare(code);

#define TESTAC(N, EXP) \
EXPECT_EQ( \
Cpp::GetAllocType(Cpp::ConstFuncRef { Cpp::GetNamed("func" #N).data }), \
Cpp::AllocType::EXP)

TESTAC(0, New);
TESTAC(1, New);
TESTAC(2, New);
TESTAC(3, New);
TESTAC(4, New);
TESTAC(5, Malloc);
TESTAC(6, Malloc);
TESTAC(7, NewArr);
TESTAC(8, NewArr);
TESTAC(9, New);
TESTAC(10, Malloc);
TESTAC(11, New);
TESTAC(12, Malloc);
TESTAC(13, New);
TESTAC(14, None);
TESTAC(15, Unknown);
TESTAC(16, Unknown);
TESTAC(17, None);
TESTAC(18, None);
TESTAC(19, None);
TESTAC(20, Unknown);
TESTAC(21, None);
TESTAC(22, New);
TESTAC(23, None);
TESTAC(24, New);
TESTAC(25, NewArr);
TESTAC(26, Unknown);

#undef TESTAC

Cpp::DeleteInterpreter();
}
TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionSignature) {
std::vector<Decl*> Decls;
std::string code = R"(
Expand Down
Loading