diff --git a/include/CppInterOp/CppInterOpDispatch.h b/include/CppInterOp/CppInterOpDispatch.h new file mode 100644 index 000000000..036184a4d --- /dev/null +++ b/include/CppInterOp/CppInterOpDispatch.h @@ -0,0 +1,298 @@ +//--------------------------------------------------------------------*- C++ -*- +// CppInterOp Dispatch Mechanism +// author: Aaron Jomy +//===----------------------------------------------------------------------===// +// +// This defines the mechanism which enables dispatching of the CppInterOp API +// without linking to it, preventing any LLVM or Clang symbols from being leaked +// into the client application. +// +//===----------------------------------------------------------------------===// +#ifndef CPPINTEROP_CPPINTEROPDISPATCH_H +#define CPPINTEROP_CPPINTEROPDISPATCH_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +// Configured by CMake, can be overridden by defining before including this +// header +#ifndef CPPINTEROP_LIBRARY_PATH +#define CPPINTEROP_LIBRARY_PATH "@CPPINTEROP_LIBRARY_PATH@" +#endif + +using __CPP_FUNC = void (*)(); + +///\param[in] procname - the name of the FunctionEntry in the symbol lookup +/// table. +/// +///\returns the function address of the requested API, or nullptr if not found +extern "C" CPPINTEROP_API void ( + *CppGetProcAddress(const unsigned char* procname))(void); + +#define EXTERN_CPP_FUNC_SIMPLE(func_name) \ + extern CppAPIType::func_name func_name; + +#define EXTERN_CPP_FUNC_OVERLOADED(func_name, signature) \ + extern CppAPIType::func_name func_name; + +#define LOAD_CPP_FUNCTION_SIMPLE(func_name) \ + func_name = \ + reinterpret_cast(dlGetProcAddress(#func_name)); + +#define LOAD_CPP_FUNCTION_OVERLOADED(func_name, signature) \ + func_name = \ + reinterpret_cast(dlGetProcAddress(#func_name)); + +#define DECLARE_CPP_NULL_SIMPLE(func_name) \ + CppAPIType::func_name func_name = nullptr; + +#define DECLARE_CPP_NULL_OVERLOADED(func_name, signature) \ + CppAPIType::func_name func_name = nullptr; + +// macro that allows declaration and loading of all CppInterOp API functions in +// a consistent way. This is used as our dispatched API list, along with the +// name-address pair table +#define FOR_EACH_CPP_FUNCTION_SIMPLE(DO) \ + DO(CreateInterpreter) \ + DO(GetInterpreter) \ + DO(Process) \ + DO(GetResourceDir) \ + DO(AddIncludePath) \ + DO(LoadLibrary) \ + DO(Declare) \ + DO(DeleteInterpreter) \ + DO(IsNamespace) \ + DO(ObjToString) \ + DO(GetQualifiedCompleteName) \ + DO(GetValueKind) \ + DO(GetNonReferenceType) \ + DO(IsEnumType) \ + DO(GetIntegerTypeFromEnumType) \ + DO(GetReferencedType) \ + DO(IsPointerType) \ + DO(GetPointeeType) \ + DO(GetPointerType) \ + DO(IsReferenceType) \ + DO(GetTypeAsString) \ + DO(GetCanonicalType) \ + DO(HasTypeQualifier) \ + DO(RemoveTypeQualifier) \ + DO(GetUnderlyingType) \ + DO(IsRecordType) \ + DO(IsFunctionPointerType) \ + DO(GetVariableType) \ + DO(GetNamed) \ + DO(GetScopeFromType) \ + DO(GetClassTemplateInstantiationArgs) \ + DO(IsClass) \ + DO(GetType) \ + DO(GetTypeFromScope) \ + DO(GetComplexType) \ + DO(GetIntegerTypeFromEnumScope) \ + DO(GetUnderlyingScope) \ + DO(GetScope) \ + DO(GetGlobalScope) \ + DO(GetScopeFromCompleteName) \ + DO(InstantiateTemplate) \ + DO(GetParentScope) \ + DO(IsTemplate) \ + DO(IsTemplateSpecialization) \ + DO(IsTypedefed) \ + DO(IsClassPolymorphic) \ + DO(Demangle) \ + DO(SizeOf) \ + DO(GetSizeOfType) \ + DO(IsBuiltin) \ + DO(IsComplete) \ + DO(Allocate) \ + DO(Deallocate) \ + DO(Construct) \ + DO(Destruct) \ + DO(IsAbstract) \ + DO(IsEnumScope) \ + DO(IsEnumConstant) \ + DO(IsAggregate) \ + DO(HasDefaultConstructor) \ + DO(IsVariable) \ + DO(GetAllCppNames) \ + DO(GetUsingNamespaces) \ + DO(GetCompleteName) \ + DO(GetDestructor) \ + DO(IsVirtualMethod) \ + DO(GetNumBases) \ + DO(GetName) \ + DO(GetBaseClass) \ + DO(IsSubclass) \ + DO(GetOperator) \ + DO(GetFunctionReturnType) \ + DO(GetBaseClassOffset) \ + DO(GetClassMethods) \ + DO(GetFunctionsUsingName) \ + DO(GetFunctionNumArgs) \ + DO(GetFunctionRequiredArgs) \ + DO(GetFunctionArgName) \ + DO(GetFunctionArgType) \ + DO(GetFunctionArgDefault) \ + DO(IsConstMethod) \ + DO(GetFunctionTemplatedDecls) \ + DO(ExistsFunctionTemplate) \ + DO(IsTemplatedFunction) \ + DO(IsStaticMethod) \ + DO(GetClassTemplatedMethods) \ + DO(BestOverloadFunctionMatch) \ + DO(GetOperatorFromSpelling) \ + DO(IsFunctionDeleted) \ + DO(IsPublicMethod) \ + DO(IsProtectedMethod) \ + DO(IsPrivateMethod) \ + DO(IsConstructor) \ + DO(IsDestructor) \ + DO(GetDatamembers) \ + DO(GetStaticDatamembers) \ + DO(GetEnumConstantDatamembers) \ + DO(LookupDatamember) \ + DO(IsLambdaClass) \ + DO(GetQualifiedName) \ + DO(GetVariableOffset) \ + DO(IsPublicVariable) \ + DO(IsProtectedVariable) \ + DO(IsPrivateVariable) \ + DO(IsStaticVariable) \ + DO(IsConstVariable) \ + DO(GetDimensions) \ + DO(GetEnumConstants) \ + DO(GetEnumConstantType) \ + DO(GetEnumConstantValue) \ + DO(DumpScope) \ + DO(AddSearchPath) \ + DO(Evaluate) \ + DO(IsDebugOutputEnabled) \ + DO(EnableDebugOutput) + +#define FOR_EACH_CPP_FUNCTION_OVERLOADED(DO) \ + DO(MakeFunctionCallable, Cpp::JitCall (*)(Cpp::TCppConstFunction_t)) \ + DO(GetFunctionAddress, Cpp::TCppFuncAddr_t (*)(Cpp::TCppFunction_t)) + +#define EXTRACT_NAME_OVERLOADED(name, sig) name + +#define FOR_EACH_CPP_FUNCTION(DO) \ + FOR_EACH_CPP_FUNCTION_SIMPLE(DO) \ + FOR_EACH_CPP_FUNCTION_OVERLOADED(EXTRACT_NAME_OVERLOADED) + +#define DECLARE_TYPE_SIMPLE(func_name) \ + using func_name = decltype(&Cpp::func_name); + +#define DECLARE_TYPE_OVERLOADED(func_name, signature) \ + using func_name = signature; +namespace CppDispatch { +// Forward all type aliases +using TCppIndex_t = ::Cpp::TCppIndex_t; +using TCppScope_t = ::Cpp::TCppScope_t; +using TCppConstScope_t = ::Cpp::TCppConstScope_t; +using TCppType_t = ::Cpp::TCppType_t; +using TCppFunction_t = ::Cpp::TCppFunction_t; +using TCppConstFunction_t = ::Cpp::TCppConstFunction_t; +using TCppFuncAddr_t = ::Cpp::TCppFuncAddr_t; +using TInterp_t = ::Cpp::TInterp_t; +using TCppObject_t = ::Cpp::TCppObject_t; + +using Operator = ::Cpp::Operator; +using OperatorArity = ::Cpp::OperatorArity; +using QualKind = ::Cpp::QualKind; +using TemplateArgInfo = ::Cpp::TemplateArgInfo; +using ValueKind = ::Cpp::ValueKind; + +using JitCall = ::Cpp::JitCall; +} // end namespace CppDispatch + +namespace CppAPIType { +FOR_EACH_CPP_FUNCTION_SIMPLE(DECLARE_TYPE_SIMPLE) +FOR_EACH_CPP_FUNCTION_OVERLOADED(DECLARE_TYPE_OVERLOADED) +} // end namespace CppAPIType + +#undef DECLARE_TYPE_SIMPLE +#undef DECLARE_TYPE_OVERLOADED + +// TODO: implement overload that takes an existing opened DL handle +inline void* dlGetProcAddress(const char* name, + const char* customLibPath = nullptr) { + if (!name) + return nullptr; + + static std::once_flag loaded; + static void* handle = nullptr; + static void* (*getCppProcAddress)(const char*) = nullptr; + + std::call_once(loaded, [customLibPath]() { + // priority order: 1) custom path argument, or CPPINTEROP_LIBRARY_PATH via + // 2) cmake configured path 3) env vars + const char* libPath = customLibPath; + if (!libPath) { + libPath = std::getenv("CPPINTEROP_LIBRARY_PATH"); + } + if (!libPath || libPath[0] == '\0') { + libPath = CPPINTEROP_LIBRARY_PATH; + } + + handle = dlopen(libPath, RTLD_LOCAL | RTLD_NOW); + if (!handle) { + std::cerr << "[CppInterOp] Failed to load library from " << libPath + << ": " << dlerror() << '\n'; + return; + } + + getCppProcAddress = reinterpret_cast( + dlsym(handle, "CppGetProcAddress")); + if (!getCppProcAddress) { + std::cerr << "[CppInterOp] Failed to find CppGetProcAddress: " + << dlerror() << '\n'; + dlclose(handle); + handle = nullptr; + } + }); + + return getCppProcAddress ? getCppProcAddress(name) : nullptr; +} +namespace CppDispatch { +FOR_EACH_CPP_FUNCTION_SIMPLE(EXTERN_CPP_FUNC_SIMPLE) +FOR_EACH_CPP_FUNCTION_OVERLOADED(EXTERN_CPP_FUNC_OVERLOADED) + +/// Initialize all CppInterOp API from the dynamically loaded library +/// (RTLD_LOCAL) \param[in] customLibPath Optional custom path to +/// libclangCppInterOp.so \returns true if initialization succeeded, false +/// otherwise +inline bool init_functions(const char* customLibPath = nullptr) { + // trigger library loading if custom path provided + if (customLibPath) { + void* test = dlGetProcAddress("GetInterpreter", customLibPath); + if (!test) { + std::cerr << "[CppInterOp] Failed to initialize with custom path: " + << customLibPath << '\n'; + return false; + } + } + + FOR_EACH_CPP_FUNCTION_SIMPLE(LOAD_CPP_FUNCTION_SIMPLE) + FOR_EACH_CPP_FUNCTION_OVERLOADED(LOAD_CPP_FUNCTION_OVERLOADED) + + // test to verify that critical (and consequently all) functions loaded + if (!GetInterpreter || !CreateInterpreter) { + std::cerr << "[CppInterOp] Failed to load critical functions" << std::endl; + return false; + } + + return true; +} +} // namespace CppDispatch + +#endif // CPPINTEROP_CPPINTEROPDISPATCH_H diff --git a/lib/CppInterOp/CMakeLists.txt b/lib/CppInterOp/CMakeLists.txt index 41288fb60..fec2def58 100644 --- a/lib/CppInterOp/CMakeLists.txt +++ b/lib/CppInterOp/CMakeLists.txt @@ -109,6 +109,7 @@ endif() add_llvm_library(clangCppInterOp DISABLE_LLVM_LINK_LLVM_DYLIB CppInterOp.cpp + CppInterOpDispatch.cpp CXCppInterOp.cpp ${DLM} LINK_LIBS diff --git a/lib/CppInterOp/CppInterOpDispatch.cpp b/lib/CppInterOp/CppInterOpDispatch.cpp new file mode 100644 index 000000000..632c8ce95 --- /dev/null +++ b/lib/CppInterOp/CppInterOpDispatch.cpp @@ -0,0 +1,33 @@ +//------------------------------------------------------------------------------ +// CppInterOp Dispatch Implementation +// author: Aaron Jomy +//------------------------------------------------------------------------------ + +#include +#include + +#include + +// Macro for simple functions (direct cast) +#define MAP_ENTRY_SIMPLE(func_name) {#func_name, (__CPP_FUNC)Cpp::func_name}, + +// Macro for overloaded functions (needs static_cast with signature) +#define MAP_ENTRY_OVERLOADED(func_name, signature) \ + {#func_name, (__CPP_FUNC) static_cast(&Cpp::func_name)}, + +static const std::unordered_map + INTEROP_FUNCTIONS = { + FOR_EACH_CPP_FUNCTION_SIMPLE(MAP_ENTRY_SIMPLE) + FOR_EACH_CPP_FUNCTION_OVERLOADED(MAP_ENTRY_OVERLOADED)}; + +#undef MAP_ENTRY_SIMPLE +#undef MAP_ENTRY_OVERLOADED + +static inline __CPP_FUNC _cppinterop_get_proc_address(const char* funcName) { + auto it = INTEROP_FUNCTIONS.find(funcName); + return (it != INTEROP_FUNCTIONS.end()) ? it->second : nullptr; +} + +void (*CppGetProcAddress(const unsigned char* procName))(void) { + return _cppinterop_get_proc_address(reinterpret_cast(procName)); +} diff --git a/unittests/CppInterOp/CMakeLists.txt b/unittests/CppInterOp/CMakeLists.txt index 6e28633c1..3a14c9f41 100644 --- a/unittests/CppInterOp/CMakeLists.txt +++ b/unittests/CppInterOp/CMakeLists.txt @@ -11,6 +11,14 @@ else() set(EXTRA_PATH_TEST_BINARIES /CppInterOpTests/unittests/bin/$/) endif() +# Add the DispatchAPITest only when building shared libraries +if (BUILD_SHARED_LIBS) + set_source_files_properties(DispatchAPITest.cpp PROPERTIES COMPILE_DEFINITIONS + "CPPINTEROP_LIB_DIR=\"${CMAKE_BINARY_DIR}/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}\"" + ) + list(APPEND EXTRA_TEST_SOURCE_FILES DispatchAPITest.cpp) +endif() + add_cppinterop_unittest(CppInterOpTests EnumReflectionTest.cpp FunctionReflectionTest.cpp diff --git a/unittests/CppInterOp/DispatchAPITest.cpp b/unittests/CppInterOp/DispatchAPITest.cpp new file mode 100644 index 000000000..d8171ed74 --- /dev/null +++ b/unittests/CppInterOp/DispatchAPITest.cpp @@ -0,0 +1,57 @@ +#include "Utils.h" + +#include "CppInterOp/CppInterOpDispatch.h" + +#include "gtest/gtest.h" + +#include + +using namespace TestUtils; +using namespace llvm; +using namespace clang; + +TEST(DispatchAPITestTest, IsClassSymbolLookup) { + CppAPIType::IsClass IsClassFn = reinterpret_cast( + dlGetProcAddress("IsClass", CPPINTEROP_LIB_DIR)); + ASSERT_NE(IsClassFn, nullptr) << "failed to locate symbol: " << dlerror(); + std::vector Decls; + GetAllTopLevelDecls("namespace N {} class C{}; int I;", Decls); + EXPECT_FALSE(IsClassFn(Decls[0])); + EXPECT_TRUE(IsClassFn(Decls[1])); + EXPECT_FALSE(IsClassFn(Decls[2])); +} + +TEST(DispatchAPITestTest, Demangle) { + + std::string code = R"( + int add(int x, int y) { return x + y; } + int add(double x, double y) { return x + y; } + )"; + + std::vector Decls; + GetAllTopLevelDecls(code, Decls); + EXPECT_EQ(Decls.size(), 2); + + auto Add_int = clang::GlobalDecl(static_cast(Decls[0])); + auto Add_double = clang::GlobalDecl(static_cast(Decls[1])); + + std::string mangled_add_int; + std::string mangled_add_double; + compat::maybeMangleDeclName(Add_int, mangled_add_int); + compat::maybeMangleDeclName(Add_double, mangled_add_double); + + // CppAPIType:: gives us the specific function pointer types + CppAPIType::Demangle DemangleFn = reinterpret_cast( + dlGetProcAddress("Demangle", CPPINTEROP_LIB_DIR)); + CppAPIType::GetQualifiedCompleteName GetQualifiedCompleteNameFn = + reinterpret_cast( + dlGetProcAddress("GetQualifiedCompleteName")); + + std::string demangled_add_int = DemangleFn(mangled_add_int); + std::string demangled_add_double = DemangleFn(mangled_add_double); + + EXPECT_NE(demangled_add_int.find(GetQualifiedCompleteNameFn(Decls[0])), + std::string::npos); + EXPECT_NE(demangled_add_double.find(GetQualifiedCompleteNameFn(Decls[1])), + std::string::npos); +}