diff --git a/include/CppInterOp/CppInterOp.h b/include/CppInterOp/CppInterOp.h index dcecc5c1b..6d5a6b4a6 100644 --- a/include/CppInterOp/CppInterOp.h +++ b/include/CppInterOp/CppInterOp.h @@ -103,6 +103,7 @@ class JitCall { enum Kind : char { kUnknown = 0, kGenericCall, + kConstructorCall, kDestructorCall, }; struct ArgList { @@ -115,12 +116,17 @@ class JitCall { // FIXME: Figure out how to unify the wrapper signatures. // FIXME: Hide these implementation details by moving wrapper generation in // this class. + // (self, nargs, args, result, nary) using GenericCall = void (*)(void*, size_t, void**, void*); - using DestructorCall = void (*)(void*, unsigned long, int); + // (result, nary, nargs, args, is_arena) + using ConstructorCall = void (*)(void*, size_t, size_t, void**, void*); + // (self, nary, withFree) + using DestructorCall = void (*)(void*, size_t, int); private: union { GenericCall m_GenericCall; + ConstructorCall m_ConstructorCall; DestructorCall m_DestructorCall; }; Kind m_Kind; @@ -128,12 +134,14 @@ class JitCall { JitCall() : m_GenericCall(nullptr), m_Kind(kUnknown), m_FD(nullptr) {} JitCall(Kind K, GenericCall C, TCppConstFunction_t FD) : m_GenericCall(C), m_Kind(K), m_FD(FD) {} + JitCall(Kind K, ConstructorCall C, TCppConstFunction_t Ctor) + : m_ConstructorCall(C), m_Kind(K), m_FD(Ctor) {} JitCall(Kind K, DestructorCall C, TCppConstFunction_t Dtor) : m_DestructorCall(C), m_Kind(K), m_FD(Dtor) {} /// Checks if the passed arguments are valid for the given function. - CPPINTEROP_API bool AreArgumentsValid(void* result, ArgList args, - void* self) const; + CPPINTEROP_API bool AreArgumentsValid(void* result, ArgList args, void* self, + size_t nary = 1UL) const; /// This function is used for debugging, it reports when the function was /// called. @@ -164,10 +172,20 @@ class JitCall { // decide to directly. void Invoke(void* result, ArgList args = {}, void* self = nullptr) const { // NOLINTBEGIN(*-type-union-access) - // Forward if we intended to call a dtor with only 1 parameter. - if (m_Kind == kDestructorCall && result && !args.m_Args) { - InvokeDestructor(result, /*nary=*/0UL, /*withFree=*/true); - return; + // Its possible the JitCall object deals with structor decls but went + // through Invoke + if (result && m_Kind != kGenericCall) { + // Forward if we intended to call a dtor with only 1 parameter. + if (m_Kind == kDestructorCall && !args.m_Args) { + InvokeDestructor(result, /*nary=*/0UL, /*withFree=*/true); + return; + } + // Forward if we intended to call a constructor (nary cannot be inferred, + // so we stick to constructing a single object) + if (m_Kind == kConstructorCall) { + InvokeConstructor(result, /*nary=*/1UL, args, self); + return; + } } #ifndef NDEBUG @@ -192,6 +210,24 @@ class JitCall { #endif // NDEBUG m_DestructorCall(object, nary, withFree); } + + /// Makes a call to a constructor. + ///\param[in] result - the memory address at which we construct the object + /// (placement new). + ///\param[in] nary - Use array new if we have to construct an array of + /// objects (nary > 1). + ///\param[in] args - a pointer to a argument list and argument size. + // FIXME: Change the type of withFree from int to bool in the wrapper code. + void InvokeConstructor(void* result, unsigned long nary = 1, + ArgList args = {}, void* is_arena = nullptr) const { + assert(m_Kind == kConstructorCall && "Wrong overload!"); +#ifndef NDEBUG + assert(AreArgumentsValid(result, args, /*self=*/nullptr, nary) && + "Invalid args!"); + ReportInvokeStart(result, args, nullptr); +#endif // NDEBUG + m_ConstructorCall(result, nary, args.m_ArgSize, args.m_Args, is_arena); + } }; ///\returns the version string information of the library. @@ -791,20 +827,33 @@ enum : long int { /// Gets the size/dimensions of a multi-dimension array. CPPINTEROP_API std::vector GetDimensions(TCppType_t type); -/// Allocates memory for a given class. -CPPINTEROP_API TCppObject_t Allocate(TCppScope_t scope); +/// Allocates memory required by an object of a given class +/// \c scope Given class for which to allocate memory for +/// \c count is used to indicate the number of objects to allocate for. +CPPINTEROP_API TCppObject_t Allocate(TCppScope_t scope, + TCppIndex_t count = 1UL); /// Deallocates memory for a given class. -CPPINTEROP_API void Deallocate(TCppScope_t scope, TCppObject_t address); - -/// Creates an object of class \c scope and calls its default constructor. If -/// \c arena is set it uses placement new. -CPPINTEROP_API TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr); - -/// Calls the destructor of object of type \c type. When withFree is true it -/// calls operator delete/free. +/// \c scope Class to indicate size of memory to deallocate +/// \c count is used to indicate the number of objects to dallocate for +CPPINTEROP_API void Deallocate(TCppScope_t scope, TCppObject_t address, + TCppIndex_t count = 1UL); + +/// Creates one or more objects of class \c scope by calling its default +/// constructor. +/// \c arena If set, this API uses placement new to construct at this address. +/// \c count is used to indicate the number of objects to construct. +CPPINTEROP_API TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr, + TCppIndex_t count = 1UL); + +/// Destroys one or more objects of a class +/// \c This this pointer of the object to destruct. Can also be the starting +/// address of an array of objects +/// \c withFree if true, we call operator delete/free, else just the destructor +/// \c count indicate the number of objects to destruct, if \c This points to +/// an array of objects CPPINTEROP_API void Destruct(TCppObject_t This, TCppScope_t type, - bool withFree = true); + bool withFree = true, TCppIndex_t count = 0UL); /// @name Stream Redirection /// diff --git a/include/clang-c/CXCppInterOp.h b/include/clang-c/CXCppInterOp.h index f885e19c5..638424ea8 100644 --- a/include/clang-c/CXCppInterOp.h +++ b/include/clang-c/CXCppInterOp.h @@ -332,7 +332,8 @@ CINDEX_LINKAGE void clang_deallocate(CXObject address); * Creates an object of class \c scope and calls its default constructor. If \c * arena is set it uses placement new. */ -CINDEX_LINKAGE CXObject clang_construct(CXScope scope, void* arena); +CINDEX_LINKAGE CXObject clang_construct(CXScope scope, void* arena, + size_t count = 1UL); /** * Creates a trampoline function and makes a call to a generic function or @@ -361,7 +362,8 @@ CINDEX_LINKAGE void clang_invoke(CXScope func, void* result, void** args, * * \param withFree Whether to call operator delete/free or not. */ -CINDEX_LINKAGE void clang_destruct(CXObject This, CXScope S, bool withFree); +CINDEX_LINKAGE void clang_destruct(CXObject This, CXScope S, + bool withFree = true, size_t nary = 0UL); /** * @} diff --git a/lib/CppInterOp/CXCppInterOp.cpp b/lib/CppInterOp/CXCppInterOp.cpp index a5e6eb07f..5744a7f71 100644 --- a/lib/CppInterOp/CXCppInterOp.cpp +++ b/lib/CppInterOp/CXCppInterOp.cpp @@ -597,12 +597,12 @@ void clang_deallocate(CXObject address) { ::operator delete(address); } namespace Cpp { void* Construct(compat::Interpreter& interp, TCppScope_t scope, - void* arena /*=nullptr*/); + void* arena /*=nullptr*/, TCppIndex_t count); } // namespace Cpp -CXObject clang_construct(CXScope scope, void* arena) { +CXObject clang_construct(CXScope scope, void* arena, size_t count) { return Cpp::Construct(*getInterpreter(scope), - static_cast(getDecl(scope)), arena); + static_cast(getDecl(scope)), arena, count); } void clang_invoke(CXScope func, void* result, void** args, size_t n, @@ -613,9 +613,9 @@ void clang_invoke(CXScope func, void* result, void** args, size_t n, namespace Cpp { void Destruct(compat::Interpreter& interp, TCppObject_t This, - clang::Decl* Class, bool withFree); + clang::Decl* Class, bool withFree, size_t nary); } // namespace Cpp -void clang_destruct(CXObject This, CXScope S, bool withFree) { - Cpp::Destruct(*getInterpreter(S), This, getDecl(S), withFree); +void clang_destruct(CXObject This, CXScope S, bool withFree, size_t nary) { + Cpp::Destruct(*getInterpreter(S), This, getDecl(S), withFree, nary); } diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index b80f66fe6..26b666898 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -53,6 +53,7 @@ #include #include +#include #include #include #include @@ -132,7 +133,8 @@ static clang::Sema& getSema() { return getInterp().getCI()->getSema(); } static clang::ASTContext& getASTContext() { return getSema().getASTContext(); } #define DEBUG_TYPE "jitcall" -bool JitCall::AreArgumentsValid(void* result, ArgList args, void* self) const { +bool JitCall::AreArgumentsValid(void* result, ArgList args, void* self, + size_t nary) const { bool Valid = true; if (Cpp::IsConstructor(m_FD)) { assert(result && "Must pass the location of the created object!"); @@ -156,6 +158,18 @@ bool JitCall::AreArgumentsValid(void* result, ArgList args, void* self) const { assert(0 && "We are discarding the return type of the function!"); Valid = false; } + if (Cpp::IsConstructor(m_FD) && nary == 0UL) { + assert(0 && "Number of objects to construct should be atleast 1"); + Valid = false; + } + if (Cpp::IsConstructor(m_FD)) { + const auto* CD = cast((const Decl*)m_FD); + if (CD->getMinRequiredArguments() != 0 && nary > 1) { + assert(0 && + "Cannot pass initialization parameters to array new construction"); + Valid = false; + } + } assert(m_Kind != kDestructorCall && "Wrong overload!"); Valid &= m_Kind != kDestructorCall; return Valid; @@ -1920,42 +1934,58 @@ void collect_type_info(const FunctionDecl* FD, QualType& QT, void make_narg_ctor(const FunctionDecl* FD, const unsigned N, std::ostringstream& typedefbuf, std::ostringstream& callbuf, - const std::string& class_name, int indent_level) { + const std::string& class_name, int indent_level, + bool array = false) { // Make a code string that follows this pattern: // // ClassName(args...) + // OR + // ClassName[nary] // array of objects // - callbuf << class_name << "("; - for (unsigned i = 0U; i < N; ++i) { - const ParmVarDecl* PVD = FD->getParamDecl(i); - QualType Ty = PVD->getType(); - QualType QT = Ty.getCanonicalType(); - std::string type_name; - EReferenceType refType = kNotReference; - bool isPointer = false; - collect_type_info(FD, QT, typedefbuf, callbuf, type_name, refType, - isPointer, indent_level, true); - if (i) { - callbuf << ','; - if (i % 2) { - callbuf << ' '; + if (array) + callbuf << class_name << "[nary]"; + else + callbuf << class_name; + + // We cannot pass initialization parameters if we call array new + if (N && !array) { + callbuf << "("; + for (unsigned i = 0U; i < N; ++i) { + const ParmVarDecl* PVD = FD->getParamDecl(i); + QualType Ty = PVD->getType(); + QualType QT = Ty.getCanonicalType(); + std::string type_name; + EReferenceType refType = kNotReference; + bool isPointer = false; + collect_type_info(FD, QT, typedefbuf, callbuf, type_name, refType, + isPointer, indent_level, true); + if (i) { + callbuf << ','; + if (i % 2) { + callbuf << ' '; + } else { + callbuf << "\n"; + indent(callbuf, indent_level); + } + } + if (refType != kNotReference) { + callbuf << "(" << type_name.c_str() + << (refType == kLValueReference ? "&" : "&&") << ")*(" + << type_name.c_str() << "*)args[" << i << "]"; + } else if (isPointer) { + callbuf << "*(" << type_name.c_str() << "**)args[" << i << "]"; } else { - callbuf << "\n"; - indent(callbuf, indent_level + 1); + callbuf << "*(" << type_name.c_str() << "*)args[" << i << "]"; } } - if (refType != kNotReference) { - callbuf << "(" << type_name.c_str() - << (refType == kLValueReference ? "&" : "&&") << ")*(" - << type_name.c_str() << "*)args[" << i << "]"; - } else if (isPointer) { - callbuf << "*(" << type_name.c_str() << "**)args[" << i << "]"; - } else { - callbuf << "*(" << type_name.c_str() << "*)args[" << i << "]"; - } + callbuf << ")"; + } + // This can be zero or default-initialized + else if (const auto* CD = dyn_cast(FD); + CD && CD->isDefaultConstructor() && !array) { + callbuf << "()"; } - callbuf << ")"; } const DeclContext* get_non_transparent_decl_context(const FunctionDecl* FD) { @@ -2114,9 +2144,15 @@ void make_narg_ctor_with_return(const FunctionDecl* FD, const unsigned N, std::ostringstream& buf, int indent_level) { // Make a code string that follows this pattern: // - // (*(ClassName**)ret) = (obj) ? - // new (*(ClassName**)ret) ClassName(args...) : new ClassName(args...); - // + // Array new if nary has been passed, and nargs is 0 (must be default ctor) + // if (nary) { + // (*(ClassName**)ret) = (obj) ? new (*(ClassName**)ret) ClassName[nary] : + // new ClassName[nary]; + // } + // else { + // (*(ClassName**)ret) = (obj) ? new (*(ClassName**)ret) ClassName(args...) + // : new ClassName(args...); + // } { std::ostringstream typedefbuf; std::ostringstream callbuf; @@ -2124,8 +2160,43 @@ void make_narg_ctor_with_return(const FunctionDecl* FD, const unsigned N, // Write the return value assignment part. // indent(callbuf, indent_level); + const auto* CD = dyn_cast(FD); + + // Activate this block only if array new is possible + // if (nary) { + // (*(ClassName**)ret) = (obj) ? new (*(ClassName**)ret) ClassName[nary] + // : new ClassName[nary]; + // } + // else { + if (CD->isDefaultConstructor()) { + callbuf << "if (nary > 1) {\n"; + indent(callbuf, indent_level); + callbuf << "(*(" << class_name << "**)ret) = "; + callbuf << "(is_arena) ? new (*(" << class_name << "**)ret) "; + make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level, + true); + + callbuf << ": new "; + // + // Write the actual expression. + // + make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level, + true); + // + // End the new expression statement. + // + callbuf << ";\n"; + indent(callbuf, indent_level); + callbuf << "}\n"; + callbuf << "else {\n"; + } + + // Standard branch: + // (*(ClassName**)ret) = (obj) ? new (*(ClassName**)ret) ClassName(args...) + // : new ClassName(args...); + indent(callbuf, indent_level); callbuf << "(*(" << class_name << "**)ret) = "; - callbuf << "(obj) ? new (*(" << class_name << "**)ret) "; + callbuf << "(is_arena) ? new (*(" << class_name << "**)ret) "; make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level); callbuf << ": new "; @@ -2137,6 +2208,10 @@ void make_narg_ctor_with_return(const FunctionDecl* FD, const unsigned N, // End the new expression statement. // callbuf << ";\n"; + indent(callbuf, --indent_level); + if (CD->isDefaultConstructor()) + callbuf << "}\n"; + // // Output the whole new expression and return statement. // @@ -2651,8 +2726,14 @@ int get_wrapper_code(compat::Interpreter& I, const FunctionDecl* FD, "__attribute__((annotate(\"__cling__ptrcheck(off)\")))\n" "extern \"C\" void "; buf << wrapper_name; - buf << "(void* obj, int nargs, void** args, void* ret)\n" - "{\n"; + if (Cpp::IsConstructor(FD)) { + buf << "(void* ret, unsigned long nary, unsigned long nargs, void** args, " + "void* is_arena)\n" + "{\n"; + } else + buf << "(void* obj, unsigned long nargs, void** args, void* ret)\n" + "{\n"; + ++indent_level; if (min_args == num_params) { // No parameters with defaults. @@ -2907,6 +2988,13 @@ CPPINTEROP_API JitCall MakeFunctionCallable(TInterp_t I, return {}; } + if (const auto* Ctor = dyn_cast(D)) { + if (auto Wrapper = make_wrapper(*interp, cast(D))) + return {JitCall::kConstructorCall, Wrapper, Ctor}; + // FIXME: else error we failed to compile the wrapper. + return {}; + } + if (auto Wrapper = make_wrapper(*interp, cast(D))) { return {JitCall::kGenericCall, Wrapper, cast(D)}; } @@ -3646,17 +3734,18 @@ void GetOperator(TCppScope_t scope, Operator op, } } -TCppObject_t Allocate(TCppScope_t scope) { - return (TCppObject_t)::operator new(Cpp::SizeOf(scope)); +TCppObject_t Allocate(TCppScope_t scope, TCppIndex_t count) { + return (TCppObject_t)::operator new(Cpp::SizeOf(scope) * count); } -void Deallocate(TCppScope_t scope, TCppObject_t address) { - ::operator delete(address); +void Deallocate(TCppScope_t scope, TCppObject_t address, TCppIndex_t count) { + size_t bytes = Cpp::SizeOf(scope) * count; + ::operator delete(address, bytes); } // FIXME: Add optional arguments to the operator new. TCppObject_t Construct(compat::Interpreter& interp, TCppScope_t scope, - void* arena /*=nullptr*/) { + void* arena /*=nullptr*/, TCppIndex_t count /*=1UL*/) { auto* Class = (Decl*)scope; // FIXME: Diagnose. if (!HasDefaultConstructor(Class)) @@ -3665,33 +3754,36 @@ TCppObject_t Construct(compat::Interpreter& interp, TCppScope_t scope, auto* const Ctor = GetDefaultConstructor(interp, Class); if (JitCall JC = MakeFunctionCallable(&interp, Ctor)) { if (arena) { - JC.Invoke(&arena, {}, (void*)~0); // Tell Invoke to use placement new. + JC.InvokeConstructor(&arena, count, {}, + (void*)~0); // Tell Invoke to use placement new. return arena; } void* obj = nullptr; - JC.Invoke(&obj); + JC.InvokeConstructor(&obj, count, {}, nullptr); return obj; } return nullptr; } -TCppObject_t Construct(TCppScope_t scope, void* arena /*=nullptr*/) { - return Construct(getInterp(), scope, arena); +TCppObject_t Construct(TCppScope_t scope, void* arena /*=nullptr*/, + TCppIndex_t count /*=1UL*/) { + return Construct(getInterp(), scope, arena, count); } void Destruct(compat::Interpreter& interp, TCppObject_t This, Decl* Class, - bool withFree) { + bool withFree, TCppIndex_t nary) { if (auto wrapper = make_dtor_wrapper(interp, Class)) { - (*wrapper)(This, /*nary=*/0, withFree); + (*wrapper)(This, nary, withFree); return; } // FIXME: Diagnose. } -void Destruct(TCppObject_t This, TCppScope_t scope, bool withFree /*=true*/) { +void Destruct(TCppObject_t This, TCppScope_t scope, bool withFree /*=true*/, + TCppIndex_t count /*=0UL*/) { auto* Class = static_cast(scope); - Destruct(getInterp(), This, Class, withFree); + Destruct(getInterp(), This, Class, withFree, count); } class StreamCaptureInfo { diff --git a/unittests/CppInterOp/FunctionReflectionTest.cpp b/unittests/CppInterOp/FunctionReflectionTest.cpp index 38d6e5462..6e4cd77fd 100644 --- a/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -1491,6 +1491,80 @@ TEST(FunctionReflectionTest, JitCallAdvanced) { clang_Interpreter_dispose(I); } +#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST +#ifndef _WIN32 // Death tests do not work on Windows +TEST(FunctionReflectionTest, JitCallDebug) { +#ifdef EMSCRIPTEN +#if CLANG_VERSION_MAJOR < 20 + GTEST_SKIP() << "Test fails for Emscipten builds"; +#endif +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + + std::vector Decls, SubDecls; + std::string code = R"( + class C { + int x; + C() { + x = 12345; + } + };)"; + + std::vector interpreter_args = {"-include", "new", + "-debug-only=jitcall"}; + GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false, + interpreter_args); + + const auto* CtorD = Cpp::GetDefaultConstructor(Decls[0]); + auto JC = Cpp::MakeFunctionCallable(CtorD); + + EXPECT_TRUE(JC.getKind() == Cpp::JitCall::kConstructorCall); + EXPECT_DEATH( + { JC.InvokeConstructor(/*result=*/nullptr); }, + "Must pass the location of the created object!"); + + void* result = Cpp::Allocate(Decls[0]); + EXPECT_DEATH( + { JC.InvokeConstructor(&result, 0UL); }, + "Number of objects to construct should be atleast 1"); + + // Succeeds + JC.InvokeConstructor(&result, 5UL); + + Decls.clear(); + code = R"( + class C { + public: + int x; + C(int a) { + x = a; + } + };)"; + + GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false, + interpreter_args); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_TRUE(Cpp::IsConstructor(SubDecls[3])); + JC = Cpp::MakeFunctionCallable(SubDecls[3]); + EXPECT_TRUE(JC.getKind() == Cpp::JitCall::kConstructorCall); + + result = Cpp::Allocate(Decls[0], 5); + int i = 42; + void* args0[1] = {(void*)&i}; + EXPECT_DEATH( + { JC.InvokeConstructor(&result, 5UL, {args0, 1}); }, + "Cannot pass initialization parameters to array new construction"); + + JC.InvokeConstructor(&result, 1UL, {args0, 1}, (void*)~0); + + int* obj = reinterpret_cast(reinterpret_cast(result)); + EXPECT_TRUE(*obj == 42); +} +#endif // _WIN32 +#endif + template T instantiation_in_host() { return T(0); } #if defined(_WIN32) template __declspec(dllexport) int instantiation_in_host(); @@ -2014,7 +2088,7 @@ TEST(FunctionReflectionTest, Construct) { testing::internal::CaptureStdout(); auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); auto scope_c = make_scope(static_cast(scope), I); - auto object_c = clang_construct(scope_c, nullptr); + auto object_c = clang_construct(scope_c, nullptr, 1UL); EXPECT_TRUE(object_c != nullptr); output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Constructor Executed"); @@ -2027,6 +2101,49 @@ TEST(FunctionReflectionTest, Construct) { clang_Interpreter_dispose(I); } +// Test zero initialization of PODs and default initialization cases +TEST(FunctionReflectionTest, ConstructPOD) { +#ifdef EMSCRIPTEN +#if CLANG_VERSION_MAJOR < 20 + GTEST_SKIP() << "Test fails for Emscipten builds"; +#endif +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + std::vector interpreter_args = {"-include", "new"}; + Cpp::CreateInterpreter(interpreter_args); + + Interp->declare(R"( + namespace PODS { + struct SomePOD_B { + int fInt; + }; + struct SomePOD_C { + int fInt; + double fDouble; + }; + })"); + + auto *ns = Cpp::GetNamed("PODS"); + Cpp::TCppScope_t scope = Cpp::GetNamed("SomePOD_B", ns); + EXPECT_TRUE(scope); + Cpp::TCppObject_t object = Cpp::Construct(scope); + EXPECT_TRUE(object != nullptr); + int* fInt = reinterpret_cast(reinterpret_cast(object)); + EXPECT_TRUE(*fInt == 0); + + scope = Cpp::GetNamed("SomePOD_C", ns); + EXPECT_TRUE(scope); + object = Cpp::Construct(scope); + EXPECT_TRUE(object); + auto* fDouble = + reinterpret_cast(reinterpret_cast(object) + sizeof(int)); + EXPECT_EQ(*fDouble, 0.0); +} + // Test nested constructor calls TEST(FunctionReflectionTest, ConstructNested) { #ifdef EMSCRIPTEN @@ -2090,6 +2207,61 @@ TEST(FunctionReflectionTest, ConstructNested) { output.clear(); } +TEST(FunctionReflectionTest, ConstructArray) { +#if defined(EMSCRIPTEN) + GTEST_SKIP() << "Test fails for Emscripten builds"; +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif +#if defined(__APPLE__) && (CLANG_VERSION_MAJOR == 16) + GTEST_SKIP() << "Test fails on Clang16 OS X"; +#endif + + Cpp::CreateInterpreter(); + + Interp->declare(R"( + #include + extern "C" int printf(const char*,...); + class C { + int x; + C() { + x = 42; + printf("\nConstructor Executed\n"); + } + }; + )"); + + Cpp::TCppScope_t scope = Cpp::GetNamed("C"); + std::string output; + + size_t a = 5; // Construct an array of 5 objects + void* where = Cpp::Allocate(scope, a); // operator new + + testing::internal::CaptureStdout(); + EXPECT_TRUE(where == Cpp::Construct(scope, where, a)); // placement new + // Check for the value of x which should be at the start of the object. + EXPECT_TRUE(*(int*)where == 42); + // Check for the value of x in the second object + int* obj = reinterpret_cast(reinterpret_cast(where) + + Cpp::SizeOf(scope)); + EXPECT_TRUE(*obj == 42); + + // Check for the value of x in the last object + obj = reinterpret_cast(reinterpret_cast(where) + + (Cpp::SizeOf(scope) * 4)); + EXPECT_TRUE(*obj == 42); + Cpp::Destruct(where, scope, /*withFree=*/false, 5); + Cpp::Deallocate(scope, where, 5); + output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, + "\nConstructor Executed\n\nConstructor Executed\n\nConstructor " + "Executed\n\nConstructor Executed\n\nConstructor Executed\n"); + output.clear(); +} + TEST(FunctionReflectionTest, Destruct) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -2137,7 +2309,7 @@ TEST(FunctionReflectionTest, Destruct) { testing::internal::CaptureStdout(); auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); auto scope_c = make_scope(static_cast(scope), I); - auto object_c = clang_construct(scope_c, nullptr); + auto object_c = clang_construct(scope_c, nullptr, 1UL); clang_destruct(object_c, scope_c, true); output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Destructor Executed"); @@ -2147,6 +2319,85 @@ TEST(FunctionReflectionTest, Destruct) { clang_Interpreter_dispose(I); } +TEST(FunctionReflectionTest, DestructArray) { +#ifdef EMSCRIPTEN + GTEST_SKIP() << "Test fails for Emscipten builds"; +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif +#if defined(__APPLE__) && (CLANG_VERSION_MAJOR == 16) + GTEST_SKIP() << "Test fails on Clang16 OS X"; +#endif + + std::vector interpreter_args = {"-include", "new"}; + Cpp::CreateInterpreter(interpreter_args); + + Interp->declare(R"( + #include + extern "C" int printf(const char*,...); + class C { + int x; + C() { + printf("\nCtor Executed\n"); + x = 42; + } + ~C() { + printf("\nDestructor Executed\n"); + } + }; + )"); + + Cpp::TCppScope_t scope = Cpp::GetNamed("C"); + std::string output; + + size_t a = 5; // Construct an array of 5 objects + void* where = Cpp::Allocate(scope, a); // operator new + EXPECT_TRUE(where == Cpp::Construct(scope, where, a)); // placement new + + // verify the array of objects has been constructed + int* obj = reinterpret_cast(reinterpret_cast(where) + + Cpp::SizeOf(scope) * 4); + EXPECT_TRUE(*obj == 42); + + testing::internal::CaptureStdout(); + // destruct 3 out of 5 objects + Cpp::Destruct(where, scope, false, 3); + output = testing::internal::GetCapturedStdout(); + + EXPECT_EQ( + output, + "\nDestructor Executed\n\nDestructor Executed\n\nDestructor Executed\n"); + output.clear(); + testing::internal::CaptureStdout(); + + // destruct the rest + auto *new_head = reinterpret_cast(reinterpret_cast(where) + + (Cpp::SizeOf(scope) * 3)); + Cpp::Destruct(new_head, scope, false, 2); + + output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "\nDestructor Executed\n\nDestructor Executed\n"); + output.clear(); + + // deallocate since we call the destructor withFree = false + Cpp::Deallocate(scope, where, 5); + + // perform the same withFree=true + where = Cpp::Allocate(scope, a); + EXPECT_TRUE(where == Cpp::Construct(scope, where, a)); + testing::internal::CaptureStdout(); + // FIXME : This should work with the array of objects as well + // Cpp::Destruct(where, scope, true, 5); + Cpp::Destruct(where, scope, true); + output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "\nDestructor Executed\n"); + output.clear(); +} + TEST(FunctionReflectionTest, UndoTest) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing.";