diff --git a/.github/workflows/clang-tidy-review.yml b/.github/workflows/clang-tidy-review.yml index 825983a05..dc58cccf1 100644 --- a/.github/workflows/clang-tidy-review.yml +++ b/.github/workflows/clang-tidy-review.yml @@ -44,7 +44,9 @@ jobs: -DCMAKE_CXX_COMPILER="$GITHUB_WORKSPACE/llvm/bin/clang++" -DLLVM_DIR="$GITHUB_WORKSPACE/llvm" -DBUILD_SHARED_LIBS=ON - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON && + cd build && + cmake --build . --target googletest --parallel $(nproc --all) - name: Upload artifacts uses: ZedThree/clang-tidy-review/upload@v0.21.0 diff --git a/include/CppInterOp/CppInterOp.h b/include/CppInterOp/CppInterOp.h new file mode 100644 index 000000000..9f23be254 --- /dev/null +++ b/include/CppInterOp/CppInterOp.h @@ -0,0 +1,816 @@ +//===--- CppInterOp.h - A layer for language interoperability ---*- C++ -*-===// +// +// Part of the compiler-research project, under the Apache License v2.0 with +// LLVM Exceptions. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines API for introspection which enables language +// interoperability. +// +//===----------------------------------------------------------------------===// + +#ifndef CPPINTEROP_CPPINTEROP_H +#define CPPINTEROP_CPPINTEROP_H + +#include +#include +#include +#include +#include +#include + +// The cross-platform CPPINTEROP_API macro definition +#if defined _WIN32 || defined __CYGWIN__ +#define CPPINTEROP_API __declspec(dllexport) +#else +#ifdef __GNUC__ +#define CPPINTEROP_API __attribute__((__visibility__("default"))) +#else +#define CPPINTEROP_API +#endif +#endif + +namespace Cpp { +using TCppIndex_t = size_t; +using TCppScope_t = void*; +using TCppType_t = void*; +using TCppFunction_t = void*; +using TCppConstFunction_t = const void*; +using TCppFuncAddr_t = void*; +using TInterp_t = void*; +using TCppObject_t = void*; + +enum Operator : unsigned char { + OP_None, + OP_New, + OP_Delete, + OP_Array_New, + OP_Array_Delete, + OP_Plus, + OP_Minus, + OP_Star, + OP_Slash, + OP_Percent, + OP_Caret, + OP_Amp, + OP_Pipe, + OP_Tilde, + OP_Exclaim, + OP_Equal, + OP_Less, + OP_Greater, + OP_PlusEqual, + OP_MinusEqual, + OP_StarEqual, + OP_SlashEqual, + OP_PercentEqual, + OP_CaretEqual, + OP_AmpEqual, + OP_PipeEqual, + OP_LessLess, + OP_GreaterGreater, + OP_LessLessEqual, + OP_GreaterGreaterEqual, + OP_EqualEqual, + OP_ExclaimEqual, + OP_LessEqual, + OP_GreaterEqual, + OP_Spaceship, + OP_AmpAmp, + OP_PipePipe, + OP_PlusPlus, + OP_MinusMinus, + OP_Comma, + OP_ArrowStar, + OP_Arrow, + OP_Call, + OP_Subscript, + OP_Conditional, + OP_Coawait, +}; + +enum OperatorArity : unsigned char { kUnary = 1, kBinary, kBoth }; + +/// A class modeling function calls for functions produced by the interpreter +/// in compiled code. It provides an information if we are calling a standard +/// function, constructor or destructor. +class JitCall { +public: + friend CPPINTEROP_API JitCall MakeFunctionCallable(TInterp_t I, + TCppConstFunction_t func); + enum Kind : char { + kUnknown = 0, + kGenericCall, + kDestructorCall, + }; + struct ArgList { + void** m_Args = nullptr; + size_t m_ArgSize = 0; + // Clang struggles with =default... + ArgList() {} + ArgList(void** Args, size_t ArgSize) : m_Args(Args), m_ArgSize(ArgSize) {} + }; + // FIXME: Figure out how to unify the wrapper signatures. + // FIXME: Hide these implementation details by moving wrapper generation in + // this class. + using GenericCall = void (*)(void*, size_t, void**, void*); + using DestructorCall = void (*)(void*, unsigned long, int); + +private: + union { + GenericCall m_GenericCall; + DestructorCall m_DestructorCall; + }; + Kind m_Kind; + TCppConstFunction_t m_FD; + 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, 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; + + /// This function is used for debugging, it reports when the function was + /// called. + CPPINTEROP_API void ReportInvokeStart(void* result, ArgList args, + void* self) const; + CPPINTEROP_API void ReportInvokeStart(void* object, unsigned long nary, + int withFree) const; + void ReportInvokeEnd() const; + +public: + Kind getKind() const { return m_Kind; } + bool isValid() const { return getKind() != kUnknown; } + bool isInvalid() const { return !isValid(); } + explicit operator bool() const { return isValid(); } + + // Specialized for calling void functions. + void Invoke(ArgList args = {}, void* self = nullptr) const { + Invoke(/*result=*/nullptr, args, self); + } + + /// Makes a call to a generic function or method. + ///\param[in] result - the location where the return result will be placed. + ///\param[in] args - a pointer to a argument list and argument size. + ///\param[in] self - the 'this pointer' of the object. + // FIXME: Adjust the arguments and their types: args_size can be unsigned; + // self can go in the end and be nullptr by default; result can be a nullptr + // by default. These changes should be synchronized with the wrapper if we + // 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; + } + +#ifndef NDEBUG + assert(AreArgumentsValid(result, args, self) && "Invalid args!"); + ReportInvokeStart(result, args, self); +#endif // NDEBUG + m_GenericCall(self, args.m_ArgSize, args.m_Args, result); + // NOLINTEND(*-type-union-access) + } + /// Makes a call to a destructor. + ///\param[in] object - the pointer of the object whose destructor we call. + ///\param[in] nary - the count of the objects we destruct if we deal with an + /// array of objects. + ///\param[in] withFree - true if we should call operator delete or false if + /// we should call only the destructor. + // FIXME: Change the type of withFree from int to bool in the wrapper code. + void InvokeDestructor(void* object, unsigned long nary = 0, + int withFree = true) const { + assert(m_Kind == kDestructorCall && "Wrong overload!"); +#ifndef NDEBUG + ReportInvokeStart(object, nary, withFree); +#endif // NDEBUG + m_DestructorCall(object, nary, withFree); + } +}; + +///\returns the version string information of the library. +CPPINTEROP_API std::string GetVersion(); + +///\returns the demangled representation of the given mangled_name +CPPINTEROP_API std::string Demangle(const std::string& mangled_name); + +/// Enables or disables the debugging printouts on stderr. +/// Debugging output can be enabled also by the environment variable +/// CPPINTEROP_EXTRA_INTERPRETER_ARGS. For example, +/// CPPINTEROP_EXTRA_INTERPRETER_ARGS="-mllvm -debug-only=jitcall" to produce +/// only debug output for jitcall events. +CPPINTEROP_API void EnableDebugOutput(bool value = true); + +///\returns true if the debugging printouts on stderr are enabled. +CPPINTEROP_API bool IsDebugOutputEnabled(); + +/// Checks if the given class represents an aggregate type). +///\returns true if \c scope is an array or a C++ tag (as per C++ +///[dcl.init.aggr]) \returns true if the scope supports aggregate +/// initialization. +CPPINTEROP_API bool IsAggregate(TCppScope_t scope); + +/// Checks if the scope is a namespace or not. +CPPINTEROP_API bool IsNamespace(TCppScope_t scope); + +/// Checks if the scope is a class or not. +CPPINTEROP_API bool IsClass(TCppScope_t scope); + +/// Checks if the scope is a function. +CPPINTEROP_API bool IsFunction(TCppScope_t scope); + +/// Checks if the type is a function pointer. +CPPINTEROP_API bool IsFunctionPointerType(TCppType_t type); + +/// Checks if the klass polymorphic. +/// which means that the class contains or inherits a virtual function +CPPINTEROP_API bool IsClassPolymorphic(TCppScope_t klass); + +// See TClingClassInfo::IsLoaded +/// Checks if the class definition is present, or not. Performs a +/// template instantiation if necessary. +CPPINTEROP_API bool IsComplete(TCppScope_t scope); + +CPPINTEROP_API size_t SizeOf(TCppScope_t scope); + +/// Checks if it is a "built-in" or a "complex" type. +CPPINTEROP_API bool IsBuiltin(TCppType_t type); + +/// Checks if it is a templated class. +CPPINTEROP_API bool IsTemplate(TCppScope_t handle); + +/// Checks if it is a class template specialization class. +CPPINTEROP_API bool IsTemplateSpecialization(TCppScope_t handle); + +/// Checks if \c handle introduces a typedef name via \c typedef or \c using. +CPPINTEROP_API bool IsTypedefed(TCppScope_t handle); + +CPPINTEROP_API bool IsAbstract(TCppType_t klass); + +/// Checks if it is an enum name (EnumDecl represents an enum name). +CPPINTEROP_API bool IsEnumScope(TCppScope_t handle); + +/// Checks if it is an enum's value (EnumConstantDecl represents +/// each enum constant that is defined). +CPPINTEROP_API bool IsEnumConstant(TCppScope_t handle); + +/// Checks if the passed value is an enum type or not. +CPPINTEROP_API bool IsEnumType(TCppType_t type); + +/// Extracts enum declarations from a specified scope and stores them in +/// vector +CPPINTEROP_API void GetEnums(TCppScope_t scope, + std::vector& Result); + +/// We assume that smart pointer types define both operator* and +/// operator->. +CPPINTEROP_API bool IsSmartPtrType(TCppType_t type); + +/// For the given "class", get the integer type that the enum +/// represents, so that you can store it properly in your specific +/// language. +CPPINTEROP_API TCppType_t GetIntegerTypeFromEnumScope(TCppScope_t handle); + +/// For the given "type", this function gets the integer type that the enum +/// represents, so that you can store it properly in your specific +/// language. +CPPINTEROP_API TCppType_t GetIntegerTypeFromEnumType(TCppType_t handle); + +/// Gets a list of all the enum constants for an enum. +CPPINTEROP_API std::vector GetEnumConstants(TCppScope_t scope); + +/// Gets the enum name when an enum constant is passed. +CPPINTEROP_API TCppType_t GetEnumConstantType(TCppScope_t scope); + +/// Gets the index value (0,1,2, etcetera) of the enum constant +/// that was passed into this function. +CPPINTEROP_API TCppIndex_t GetEnumConstantValue(TCppScope_t scope); + +/// Gets the size of the "type" that is passed in to this function. +CPPINTEROP_API size_t GetSizeOfType(TCppType_t type); + +/// Checks if the passed value is a variable. +CPPINTEROP_API bool IsVariable(TCppScope_t scope); + +/// Gets the name of any named decl (a class, +/// namespace, variable, or a function). +CPPINTEROP_API std::string GetName(TCppScope_t klass); + +/// This is similar to GetName() function, but besides +/// the name, it also gets the template arguments. +CPPINTEROP_API std::string GetCompleteName(TCppScope_t klass); + +/// Gets the "qualified" name (including the namespace) of any +/// named decl (a class, namespace, variable, or a function). +CPPINTEROP_API std::string GetQualifiedName(TCppScope_t klass); + +/// This is similar to GetQualifiedName() function, but besides +/// the "qualified" name (including the namespace), it also +/// gets the template arguments. +CPPINTEROP_API std::string GetQualifiedCompleteName(TCppScope_t klass); + +/// Gets the list of namespaces utilized in the supplied scope. +CPPINTEROP_API std::vector GetUsingNamespaces(TCppScope_t scope); + +/// Gets the global scope of the whole C++ instance. +CPPINTEROP_API TCppScope_t GetGlobalScope(); + +/// Strips the typedef and returns the underlying class, and if the +/// underlying decl is not a class it returns the input unchanged. +CPPINTEROP_API TCppScope_t GetUnderlyingScope(TCppScope_t scope); + +/// Gets the namespace or class (by stripping typedefs) for the name +/// passed as a parameter, and if the parent is not passed, +/// then global scope will be assumed. +CPPINTEROP_API TCppScope_t GetScope(const std::string& name, + TCppScope_t parent = nullptr); + +/// When the namespace is known, then the parent doesn't need +/// to be specified. This will probably be phased-out in +/// future versions of the interop library. +CPPINTEROP_API TCppScope_t GetScopeFromCompleteName(const std::string& name); + +/// This function performs a lookup within the specified parent, +/// a specific named entity (functions, enums, etcetera). +CPPINTEROP_API TCppScope_t GetNamed(const std::string& name, + TCppScope_t parent = nullptr); + +/// Gets the parent of the scope that is passed as a parameter. +CPPINTEROP_API TCppScope_t GetParentScope(TCppScope_t scope); + +/// Gets the scope of the type that is passed as a parameter. +CPPINTEROP_API TCppScope_t GetScopeFromType(TCppType_t type); + +/// Gets the number of Base Classes for the Derived Class that +/// is passed as a parameter. +CPPINTEROP_API TCppIndex_t GetNumBases(TCppScope_t klass); + +/// Gets a specific Base Class using its index. Typically GetNumBases() +/// is used to get the number of Base Classes, and then that number +/// can be used to iterate through the index value to get each specific +/// base class. +CPPINTEROP_API TCppScope_t GetBaseClass(TCppScope_t klass, TCppIndex_t ibase); + +/// Checks if the supplied Derived Class is a sub-class of the +/// provided Base Class. +CPPINTEROP_API bool IsSubclass(TCppScope_t derived, TCppScope_t base); + +/// Each base has its own offset in a Derived Class. This offset can be +/// used to get to the Base Class fields. +CPPINTEROP_API int64_t GetBaseClassOffset(TCppScope_t derived, + TCppScope_t base); + +/// Sets a list of all the Methods that are in the Class that is +/// supplied as a parameter. +///\param[in] klass - Pointer to the scope/class under which the methods have +/// to be retrieved +///\param[out] methods - Vector of methods in the class +CPPINTEROP_API void GetClassMethods(TCppScope_t klass, + std::vector& methods); + +/// Template function pointer list to add proxies for un-instantiated/ +/// non-overloaded templated methods +///\param[in] klass - Pointer to the scope/class under which the methods have +/// to be retrieved +///\param[out] methods - Vector of methods in the class +CPPINTEROP_API void +GetFunctionTemplatedDecls(TCppScope_t klass, + std::vector& methods); + +///\returns if a class has a default constructor. +CPPINTEROP_API bool HasDefaultConstructor(TCppScope_t scope); + +///\returns the default constructor of a class, if any. +CPPINTEROP_API TCppFunction_t GetDefaultConstructor(TCppScope_t scope); + +///\returns the class destructor, if any. +CPPINTEROP_API TCppFunction_t GetDestructor(TCppScope_t scope); + +/// Looks up all the functions that have the name that is +/// passed as a parameter in this function. +CPPINTEROP_API std::vector +GetFunctionsUsingName(TCppScope_t scope, const std::string& name); + +/// Gets the return type of the provided function. +CPPINTEROP_API TCppType_t GetFunctionReturnType(TCppFunction_t func); + +/// Gets the number of Arguments for the provided function. +CPPINTEROP_API TCppIndex_t GetFunctionNumArgs(TCppFunction_t func); + +/// Gets the number of Required Arguments for the provided function. +CPPINTEROP_API TCppIndex_t GetFunctionRequiredArgs(TCppConstFunction_t func); + +/// For each Argument of a function, you can get the Argument Type +/// by providing the Argument Index, based on the number of arguments +/// from the GetFunctionNumArgs() function. +CPPINTEROP_API TCppType_t GetFunctionArgType(TCppFunction_t func, + TCppIndex_t iarg); + +///\returns a stringified version of a given function signature in the form: +/// void N::f(int i, double d, long l = 0, char ch = 'a'). +CPPINTEROP_API std::string GetFunctionSignature(TCppFunction_t func); + +///\returns if a function was marked as \c =delete. +CPPINTEROP_API bool IsFunctionDeleted(TCppConstFunction_t function); + +CPPINTEROP_API bool IsTemplatedFunction(TCppFunction_t func); + +/// This function performs a lookup to check if there is a +/// templated function of that type. +CPPINTEROP_API bool ExistsFunctionTemplate(const std::string& name, + TCppScope_t parent = nullptr); + +/// Sets a list of all the constructor for a scope/class that is +/// supplied as a parameter. +///\param[in] name - This string is used as a constraint, that clients can use +/// to ensure the constructors match the name that they provide +///\param[in] parent - Pointer to the scope/class for which the constructors +/// are being looked up +/// to be retrieved +///\param[out] funcs - vector of handles to all constructors found under the +/// given scope +CPPINTEROP_API void LookupConstructors(const std::string& name, + TCppScope_t parent, + std::vector& funcs); + +/// Sets a list of all the Templated Methods that are in the Class that is +/// supplied as a parameter. +///\returns true if the lookup succeeded, and false if there are no candidates +///\param[in] name - method name +///\param[in] parent - Pointer to the scope/class under which the methods have +/// to be retrieved +///\param[out] funcs - vector of function pointers matching the name +CPPINTEROP_API bool +GetClassTemplatedMethods(const std::string& name, TCppScope_t parent, + std::vector& funcs); + +/// Checks if the provided parameter is a method. +CPPINTEROP_API bool IsMethod(TCppConstFunction_t method); + +/// Checks if the provided parameter is a 'Public' method. +CPPINTEROP_API bool IsPublicMethod(TCppFunction_t method); + +/// Checks if the provided parameter is a 'Protected' method. +CPPINTEROP_API bool IsProtectedMethod(TCppFunction_t method); + +/// Checks if the provided parameter is a 'Private' method. +CPPINTEROP_API bool IsPrivateMethod(TCppFunction_t method); + +/// Checks if the provided parameter is a Constructor. +CPPINTEROP_API bool IsConstructor(TCppConstFunction_t method); + +/// Checks if the provided parameter is a Destructor. +CPPINTEROP_API bool IsDestructor(TCppConstFunction_t method); + +/// Checks if the provided parameter is a 'Static' method. +CPPINTEROP_API bool IsStaticMethod(TCppConstFunction_t method); + +///\returns the address of the function given its potentially mangled name. +CPPINTEROP_API TCppFuncAddr_t GetFunctionAddress(const char* mangled_name); + +///\returns the address of the function given its function declaration. +CPPINTEROP_API TCppFuncAddr_t GetFunctionAddress(TCppFunction_t method); + +/// Checks if the provided parameter is a 'Virtual' method. +CPPINTEROP_API bool IsVirtualMethod(TCppFunction_t method); + +/// Gets all the Fields/Data Members of a Class +CPPINTEROP_API void GetDatamembers(TCppScope_t scope, + std::vector& datamembers); + +/// Gets all the Static Fields/Data Members of a Class +///\param[in] scope - class +///\param[out] funcs - vector of static data members +CPPINTEROP_API void GetStaticDatamembers(TCppScope_t scope, + std::vector& datamembers); + +/// Gets all the Enum Constants declared in a Class +///\param[in] scope - class +///\param[out] funcs - vector of static data members +///\param[in] include_enum_class - include enum constants from enum class +CPPINTEROP_API +void GetEnumConstantDatamembers(TCppScope_t scope, + std::vector& datamembers, + bool include_enum_class = true); + +/// This is a Lookup function to be used specifically for data members. +CPPINTEROP_API TCppScope_t LookupDatamember(const std::string& name, + TCppScope_t parent); + +/// Gets the type of the variable that is passed as a parameter. +CPPINTEROP_API TCppType_t GetVariableType(TCppScope_t var); + +/// Gets the address of the variable, you can use it to get the +/// value stored in the variable. +CPPINTEROP_API intptr_t GetVariableOffset(TCppScope_t var, + TCppScope_t parent = nullptr); + +/// Checks if the provided variable is a 'Public' variable. +CPPINTEROP_API bool IsPublicVariable(TCppScope_t var); + +/// Checks if the provided variable is a 'Protected' variable. +CPPINTEROP_API bool IsProtectedVariable(TCppScope_t var); + +/// Checks if the provided variable is a 'Private' variable. +CPPINTEROP_API bool IsPrivateVariable(TCppScope_t var); + +/// Checks if the provided variable is a 'Static' variable. +CPPINTEROP_API bool IsStaticVariable(TCppScope_t var); + +/// Checks if the provided variable is a 'Constant' variable. +CPPINTEROP_API bool IsConstVariable(TCppScope_t var); + +/// Checks if the provided parameter is a Record (struct). +CPPINTEROP_API bool IsRecordType(TCppType_t type); + +/// Checks if the provided parameter is a Plain Old Data Type (POD). +CPPINTEROP_API bool IsPODType(TCppType_t type); + +/// Checks if type is a pointer +CPPINTEROP_API bool IsPointerType(TCppType_t type); + +/// Get the underlying pointee type +CPPINTEROP_API TCppType_t GetPointeeType(TCppType_t type); + +/// Checks if type is a reference +CPPINTEROP_API bool IsReferenceType(TCppType_t type); + +/// Get the type that the reference refers to +CPPINTEROP_API TCppType_t GetNonReferenceType(TCppType_t type); + +/// Gets the pure, Underlying Type (as opposed to the Using Type). +CPPINTEROP_API TCppType_t GetUnderlyingType(TCppType_t type); + +/// Gets the Type (passed as a parameter) as a String value. +CPPINTEROP_API std::string GetTypeAsString(TCppType_t type); + +/// Gets the Canonical Type string from the std string. A canonical type +/// is the type with any typedef names, syntactic sugars or modifiers stripped +/// out of it. +CPPINTEROP_API TCppType_t GetCanonicalType(TCppType_t type); + +/// Used to either get the built-in type of the provided string, or +/// use the name to lookup the actual type. +CPPINTEROP_API TCppType_t GetType(const std::string& type); + +///\returns the complex of the provided type. +CPPINTEROP_API TCppType_t GetComplexType(TCppType_t element_type); + +/// This will convert a class into its type, so for example, you can +/// use it to declare variables in it. +CPPINTEROP_API TCppType_t GetTypeFromScope(TCppScope_t klass); + +/// Checks if a C++ type derives from another. +CPPINTEROP_API bool IsTypeDerivedFrom(TCppType_t derived, TCppType_t base); + +/// Creates a trampoline function by using the interpreter and returns a +/// uniform interface to call it from compiled code. +CPPINTEROP_API JitCall MakeFunctionCallable(TCppConstFunction_t func); + +CPPINTEROP_API JitCall MakeFunctionCallable(TInterp_t I, + TCppConstFunction_t func); + +/// Checks if a function declared is of const type or not. +CPPINTEROP_API bool IsConstMethod(TCppFunction_t method); + +///\returns the default argument value as string. +CPPINTEROP_API std::string GetFunctionArgDefault(TCppFunction_t func, + TCppIndex_t param_index); + +///\returns the argument name of function as string. +CPPINTEROP_API std::string GetFunctionArgName(TCppFunction_t func, + TCppIndex_t param_index); + +///\returns arity of the operator or kNone +CPPINTEROP_API OperatorArity GetOperatorArity(TCppFunction_t op); + +///\returns list of operator overloads +CPPINTEROP_API void GetOperator(TCppScope_t scope, Operator op, + std::vector& operators, + OperatorArity kind = kBoth); + +/// Creates an instance of the interpreter we need for the various interop +/// services. +///\param[in] Args - the list of arguments for interpreter constructor. +///\param[in] CPPINTEROP_EXTRA_INTERPRETER_ARGS - an env variable, if defined, +/// adds additional arguments to the interpreter. +CPPINTEROP_API TInterp_t +CreateInterpreter(const std::vector& Args = {}, + const std::vector& GpuArgs = {}); + +/// Checks which Interpreter backend was CppInterOp library built with (Cling, +/// Clang-REPL, etcetera). In practice, the selected interpreter should not +/// matter, since the library will function in the same way. +///\returns the current interpreter instance, if any. +CPPINTEROP_API TInterp_t GetInterpreter(); + +/// Sets the Interpreter instance with an external interpreter, meant to +/// be called by an external library that manages it's own interpreter. +/// Sets a flag signifying CppInterOp does not have ownership of the +/// sInterpreter. +///\param[in] Args - the pointer to an external interpreter +CPPINTEROP_API void UseExternalInterpreter(TInterp_t I); + +/// Adds a Search Path for the Interpreter to get the libraries. +CPPINTEROP_API void AddSearchPath(const char* dir, bool isUser = true, + bool prepend = false); + +/// Returns the resource-dir path (for headers). +CPPINTEROP_API const char* GetResourceDir(); + +/// Uses the underlying clang compiler to detect the resource directory. +/// In essence calling clang -print-resource-dir and checks if it ends with +/// a compatible to CppInterOp version. +///\param[in] ClangBinaryName - the name (or the full path) of the compiler +/// to ask. +CPPINTEROP_API std::string +DetectResourceDir(const char* ClangBinaryName = "clang"); + +/// Asks the system compiler for its default include paths. +///\param[out] Paths - the list of include paths returned by eg. +/// `c++ -xc++ -E -v /dev/null 2>&1` +///\param[in] CompilerName - the name (or the full path) of the compiler +/// binary file. +CPPINTEROP_API void +DetectSystemCompilerIncludePaths(std::vector& Paths, + const char* CompilerName = "c++"); + +/// Secondary search path for headers, if not found using the +/// GetResourceDir() function. +CPPINTEROP_API void AddIncludePath(const char* dir); + +// Gets the currently used include paths +///\param[out] IncludePaths - the list of include paths +/// +CPPINTEROP_API void GetIncludePaths(std::vector& IncludePaths, + bool withSystem = false, + bool withFlags = false); + +/// Only Declares a code snippet in \c code and does not execute it. +///\returns 0 on success +CPPINTEROP_API int Declare(const char* code, bool silent = false); + +/// Declares and executes a code snippet in \c code. +///\returns 0 on success +CPPINTEROP_API int Process(const char* code); + +/// Declares, executes and returns the execution result as a intptr_t. +///\returns the expression results as a intptr_t. +CPPINTEROP_API intptr_t Evaluate(const char* code, bool* HadError = nullptr); + +/// Looks up the library if access is enabled. +///\returns the path to the library. +CPPINTEROP_API std::string LookupLibrary(const char* lib_name); + +/// Finds \c lib_stem considering the list of search paths and loads it by +/// calling dlopen. +/// \returns true on success. +CPPINTEROP_API bool LoadLibrary(const char* lib_stem, bool lookup = true); + +/// Finds \c lib_stem considering the list of search paths and unloads it by +/// calling dlclose. +/// function. +CPPINTEROP_API void UnloadLibrary(const char* lib_stem); + +/// Scans all libraries on the library search path for a given potentially +/// mangled symbol name. +///\returns the path to the first library that contains the symbol definition. +CPPINTEROP_API std::string +SearchLibrariesForSymbol(const char* mangled_name, bool search_system /*true*/); + +/// Inserts or replaces a symbol in the JIT with the one provided. This is +/// useful for providing our own implementations of facilities such as printf. +/// +///\param[in] linker_mangled_name - the name of the symbol to be inserted or +/// replaced. +///\param[in] address - the new address of the symbol. +/// +///\returns true on failure. +CPPINTEROP_API bool InsertOrReplaceJitSymbol(const char* linker_mangled_name, + uint64_t address); + +/// Tries to load provided objects in a string format (prettyprint). +CPPINTEROP_API std::string ObjToString(const char* type, void* obj); + +struct TemplateArgInfo { + TCppType_t m_Type; + const char* m_IntegralValue; + TemplateArgInfo(TCppScope_t type, const char* integral_value = nullptr) + : m_Type(type), m_IntegralValue(integral_value) {} +}; +/// Builds a template instantiation for a given templated declaration. +/// Offers a single interface for instantiation of class, function and +/// variable templates +/// +///\param[in] tmpl - Uninstantiated template class/function +///\param[in] template_args - Pointer to vector of template arguments stored +/// in the \c TemplateArgInfo struct +///\param[in] template_args_size - Size of the vector of template arguments +/// passed as \c template_args +/// +///\returns Instantiated templated class/function/variable pointer +CPPINTEROP_API TCppScope_t +InstantiateTemplate(TCppScope_t tmpl, const TemplateArgInfo* template_args, + size_t template_args_size); + +/// Sets the class template instantiation arguments of \c templ_instance. +/// +///\param[in] templ_instance - Pointer to the template instance +///\param[out] args - Vector of instantiation arguments +CPPINTEROP_API void +GetClassTemplateInstantiationArgs(TCppScope_t templ_instance, + std::vector& args); + +/// Instantiates a function template from a given string representation. This +/// function also does overload resolution. +///\returns the instantiated function template declaration. +CPPINTEROP_API TCppFunction_t +InstantiateTemplateFunctionFromString(const char* function_template); + +/// Finds best overload match based on explicit template parameters (if any) +/// and argument types. +/// +///\param[in] candidates - vector of overloads that come under the +/// parent scope and have the same name +///\param[in] explicit_types - set of explicitly instantiated template types +///\param[in] arg_types - set of argument types +///\returns Instantiated function pointer +CPPINTEROP_API TCppFunction_t +BestOverloadFunctionMatch(const std::vector& candidates, + const std::vector& explicit_types, + const std::vector& arg_types); + +CPPINTEROP_API void GetAllCppNames(TCppScope_t scope, + std::set& names); + +CPPINTEROP_API void DumpScope(TCppScope_t scope); + +// FIXME: Rework the GetDimensions and make this enum redundant. +namespace DimensionValue { +enum : long int { + UNKNOWN_SIZE = -1, +}; +} + +/// 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); + +/// 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. +CPPINTEROP_API void Destruct(TCppObject_t This, TCppScope_t type, + bool withFree = true); + +/// @name Stream Redirection +/// +///@{ + +enum CaptureStreamKind : char { + kStdOut = 1, ///< stdout + kStdErr, ///< stderr + // kStdBoth, ///< stdout and stderr + // kSTDSTRM // "&1" or "&2" is not a filename +}; + +/// Begins recording the given standard stream. +///\param[fd_kind] - The stream to be captured +CPPINTEROP_API void BeginStdStreamCapture(CaptureStreamKind fd_kind); + +/// Ends recording the standard stream and returns the result as a string. +CPPINTEROP_API std::string EndStdStreamCapture(); + +///@} + +/// Append all Code completion suggestions to Results. +///\param[out] Results - CC suggestions for code fragment. Suggestions are +/// appended. +///\param[in] code - code fragment to complete +///\param[in] complete_line - position (line) in code for suggestion +///\param[in] complete_column - position (column) in code for suggestion +CPPINTEROP_API void CodeComplete(std::vector& Results, + const char* code, unsigned complete_line = 1U, + unsigned complete_column = 1U); + +/// Reverts the last N operations performed by the interpreter. +///\param[in] N The number of operations to undo. Defaults to 1. +///\returns 0 on success, non-zero on failure. +CPPINTEROP_API int Undo(unsigned N = 1); + +} // end namespace Cpp + +#endif // CPPINTEROP_CPPINTEROP_H diff --git a/include/clang/Interpreter/CppInterOp.h b/include/clang/Interpreter/CppInterOp.h index 7d1fdb12a..ffc93fae3 100644 --- a/include/clang/Interpreter/CppInterOp.h +++ b/include/clang/Interpreter/CppInterOp.h @@ -1,809 +1,8 @@ -//--------------------------------------------------------------------*- C++ -*- -// CLING - the C++ LLVM-based InterpreterG :) -// author: Vassil Vassilev -// -// This file is dual-licensed: you can choose to license it under the University -// of Illinois Open Source License or the GNU Lesser General Public License. See -// LICENSE.TXT for details. -//------------------------------------------------------------------------------ +#ifndef CLANG_CPPINTEROP_H +#define CLANG_CPPINTEROP_H -#ifndef CPPINTEROP_CPPINTEROP_H -#define CPPINTEROP_CPPINTEROP_H +#include "CppInterOp/CppInterOp.h" +#warning \ + "#include is deprecated; use #include -#include -#include -#include -#include - -// The cross-platform CPPINTEROP_API macro definition -#if defined _WIN32 || defined __CYGWIN__ -#define CPPINTEROP_API __declspec(dllexport) -#else -#ifdef __GNUC__ -#define CPPINTEROP_API __attribute__((__visibility__("default"))) -#else -#define CPPINTEROP_API -#endif -#endif - -namespace Cpp { - using TCppIndex_t = size_t; - using TCppScope_t = void*; - using TCppType_t = void*; - using TCppFunction_t = void*; - using TCppConstFunction_t = const void*; - using TCppFuncAddr_t = void*; - using TInterp_t = void*; - using TCppObject_t = void*; - - enum Operator { - OP_None, - OP_New, - OP_Delete, - OP_Array_New, - OP_Array_Delete, - OP_Plus, - OP_Minus, - OP_Star, - OP_Slash, - OP_Percent, - OP_Caret, - OP_Amp, - OP_Pipe, - OP_Tilde, - OP_Exclaim, - OP_Equal, - OP_Less, - OP_Greater, - OP_PlusEqual, - OP_MinusEqual, - OP_StarEqual, - OP_SlashEqual, - OP_PercentEqual, - OP_CaretEqual, - OP_AmpEqual, - OP_PipeEqual, - OP_LessLess, - OP_GreaterGreater, - OP_LessLessEqual, - OP_GreaterGreaterEqual, - OP_EqualEqual, - OP_ExclaimEqual, - OP_LessEqual, - OP_GreaterEqual, - OP_Spaceship, - OP_AmpAmp, - OP_PipePipe, - OP_PlusPlus, - OP_MinusMinus, - OP_Comma, - OP_ArrowStar, - OP_Arrow, - OP_Call, - OP_Subscript, - OP_Conditional, - OP_Coawait, - }; - - enum OperatorArity { kUnary = 1, kBinary, kBoth }; - - /// A class modeling function calls for functions produced by the interpreter - /// in compiled code. It provides an information if we are calling a standard - /// function, constructor or destructor. - class JitCall { - public: - friend CPPINTEROP_API JitCall - MakeFunctionCallable(TInterp_t I, TCppConstFunction_t func); - enum Kind : char { - kUnknown = 0, - kGenericCall, - kDestructorCall, - }; - struct ArgList { - void** m_Args = nullptr; - size_t m_ArgSize = 0; - // Clang struggles with =default... - ArgList() : m_Args(nullptr), m_ArgSize(0) {} - ArgList(void** Args, size_t ArgSize) - : m_Args(Args), m_ArgSize(ArgSize) {} - }; - // FIXME: Figure out how to unify the wrapper signatures. - // FIXME: Hide these implementation details by moving wrapper generation in - // this class. - using GenericCall = void (*)(void*, int, void**, void*); - using DestructorCall = void (*)(void*, unsigned long, int); - private: - union { - GenericCall m_GenericCall; - DestructorCall m_DestructorCall; - }; - const Kind m_Kind; - TCppConstFunction_t m_FD; - JitCall() : m_Kind(kUnknown), m_GenericCall(nullptr), m_FD(nullptr) {} - JitCall(Kind K, GenericCall C, TCppConstFunction_t FD) - : m_Kind(K), m_GenericCall(C), m_FD(FD) {} - JitCall(Kind K, DestructorCall C, TCppConstFunction_t Dtor) - : m_Kind(K), m_DestructorCall(C), 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; - - /// This function is used for debugging, it reports when the function was - /// called. - CPPINTEROP_API void ReportInvokeStart(void* result, ArgList args, - void* self) const; - CPPINTEROP_API void ReportInvokeStart(void* object, unsigned long nary, - int withFree) const; - void ReportInvokeEnd() const; - public: - Kind getKind() const { return m_Kind; } - bool isValid() const { return getKind() != kUnknown; } - bool isInvalid() const { return !isValid(); } - explicit operator bool() const { return isValid(); } - - // Specialized for calling void functions. - void Invoke(ArgList args = {}, void* self = nullptr) const { - Invoke(/*result=*/nullptr, args, self); - } - - /// Makes a call to a generic function or method. - ///\param[in] result - the location where the return result will be placed. - ///\param[in] args - a pointer to a argument list and argument size. - ///\param[in] self - the 'this pointer' of the object. - // FIXME: Adjust the arguments and their types: args_size can be unsigned; - // self can go in the end and be nullptr by default; result can be a nullptr - // by default. These changes should be synchronized with the wrapper if we - // decide to directly. - void Invoke(void* result, ArgList args = {}, void* self = nullptr) const { - // Forward if we intended to call a dtor with only 1 parameter. - if (m_Kind == kDestructorCall && result && !args.m_Args) - return InvokeDestructor(result, /*nary=*/0UL, /*withFree=*/true); - -#ifndef NDEBUG - assert(AreArgumentsValid(result, args, self) && "Invalid args!"); - ReportInvokeStart(result, args, self); -#endif // NDEBUG - m_GenericCall(self, args.m_ArgSize, args.m_Args, result); - } - /// Makes a call to a destructor. - ///\param[in] object - the pointer of the object whose destructor we call. - ///\param[in] nary - the count of the objects we destruct if we deal with an - /// array of objects. - ///\param[in] withFree - true if we should call operator delete or false if - /// we should call only the destructor. - //FIXME: Change the type of withFree from int to bool in the wrapper code. - void InvokeDestructor(void* object, unsigned long nary = 0, - int withFree = true) const { - assert(m_Kind == kDestructorCall && "Wrong overload!"); -#ifndef NDEBUG - ReportInvokeStart(object, nary, withFree); -#endif // NDEBUG - m_DestructorCall(object, nary, withFree); - } - }; - - ///\returns the version string information of the library. - CPPINTEROP_API std::string GetVersion(); - - ///\returns the demangled representation of the given mangled_name - CPPINTEROP_API std::string Demangle(const std::string& mangled_name); - - /// Enables or disables the debugging printouts on stderr. - /// Debugging output can be enabled also by the environment variable - /// CPPINTEROP_EXTRA_INTERPRETER_ARGS. For example, - /// CPPINTEROP_EXTRA_INTERPRETER_ARGS="-mllvm -debug-only=jitcall" to produce - /// only debug output for jitcall events. - CPPINTEROP_API void EnableDebugOutput(bool value = true); - - ///\returns true if the debugging printouts on stderr are enabled. - CPPINTEROP_API bool IsDebugOutputEnabled(); - - /// Checks if the given class represents an aggregate type). - ///\returns true if \c scope is an array or a C++ tag (as per C++ - ///[dcl.init.aggr]) \returns true if the scope supports aggregate - /// initialization. - CPPINTEROP_API bool IsAggregate(TCppScope_t scope); - - /// Checks if the scope is a namespace or not. - CPPINTEROP_API bool IsNamespace(TCppScope_t scope); - - /// Checks if the scope is a class or not. - CPPINTEROP_API bool IsClass(TCppScope_t scope); - - /// Checks if the scope is a function. - CPPINTEROP_API bool IsFunction(TCppScope_t scope); - - /// Checks if the type is a function pointer. - CPPINTEROP_API bool IsFunctionPointerType(TCppType_t type); - - /// Checks if the klass polymorphic. - /// which means that the class contains or inherits a virtual function - CPPINTEROP_API bool IsClassPolymorphic(TCppScope_t klass); - - // See TClingClassInfo::IsLoaded - /// Checks if the class definition is present, or not. Performs a - /// template instantiation if necessary. - CPPINTEROP_API bool IsComplete(TCppScope_t scope); - - CPPINTEROP_API size_t SizeOf(TCppScope_t scope); - - /// Checks if it is a "built-in" or a "complex" type. - CPPINTEROP_API bool IsBuiltin(TCppType_t type); - - /// Checks if it is a templated class. - CPPINTEROP_API bool IsTemplate(TCppScope_t handle); - - /// Checks if it is a class template specialization class. - CPPINTEROP_API bool IsTemplateSpecialization(TCppScope_t handle); - - /// Checks if \c handle introduces a typedef name via \c typedef or \c using. - CPPINTEROP_API bool IsTypedefed(TCppScope_t handle); - - CPPINTEROP_API bool IsAbstract(TCppType_t klass); - - /// Checks if it is an enum name (EnumDecl represents an enum name). - CPPINTEROP_API bool IsEnumScope(TCppScope_t handle); - - /// Checks if it is an enum's value (EnumConstantDecl represents - /// each enum constant that is defined). - CPPINTEROP_API bool IsEnumConstant(TCppScope_t handle); - - /// Checks if the passed value is an enum type or not. - CPPINTEROP_API bool IsEnumType(TCppType_t type); - - /// Extracts enum declarations from a specified scope and stores them in - /// vector - CPPINTEROP_API void GetEnums(TCppScope_t scope, - std::vector& Result); - - /// We assume that smart pointer types define both operator* and - /// operator->. - CPPINTEROP_API bool IsSmartPtrType(TCppType_t type); - - /// For the given "class", get the integer type that the enum - /// represents, so that you can store it properly in your specific - /// language. - CPPINTEROP_API TCppType_t GetIntegerTypeFromEnumScope(TCppScope_t handle); - - /// For the given "type", this function gets the integer type that the enum - /// represents, so that you can store it properly in your specific - /// language. - CPPINTEROP_API TCppType_t GetIntegerTypeFromEnumType(TCppType_t handle); - - /// Gets a list of all the enum constants for an enum. - CPPINTEROP_API std::vector GetEnumConstants(TCppScope_t scope); - - /// Gets the enum name when an enum constant is passed. - CPPINTEROP_API TCppType_t GetEnumConstantType(TCppScope_t scope); - - /// Gets the index value (0,1,2, etcetera) of the enum constant - /// that was passed into this function. - CPPINTEROP_API TCppIndex_t GetEnumConstantValue(TCppScope_t scope); - - /// Gets the size of the "type" that is passed in to this function. - CPPINTEROP_API size_t GetSizeOfType(TCppType_t type); - - /// Checks if the passed value is a variable. - CPPINTEROP_API bool IsVariable(TCppScope_t scope); - - /// Gets the name of any named decl (a class, - /// namespace, variable, or a function). - CPPINTEROP_API std::string GetName(TCppScope_t klass); - - /// This is similar to GetName() function, but besides - /// the name, it also gets the template arguments. - CPPINTEROP_API std::string GetCompleteName(TCppScope_t klass); - - /// Gets the "qualified" name (including the namespace) of any - /// named decl (a class, namespace, variable, or a function). - CPPINTEROP_API std::string GetQualifiedName(TCppScope_t klass); - - /// This is similar to GetQualifiedName() function, but besides - /// the "qualified" name (including the namespace), it also - /// gets the template arguments. - CPPINTEROP_API std::string GetQualifiedCompleteName(TCppScope_t klass); - - /// Gets the list of namespaces utilized in the supplied scope. - CPPINTEROP_API std::vector GetUsingNamespaces(TCppScope_t scope); - - /// Gets the global scope of the whole C++ instance. - CPPINTEROP_API TCppScope_t GetGlobalScope(); - - /// Strips the typedef and returns the underlying class, and if the - /// underlying decl is not a class it returns the input unchanged. - CPPINTEROP_API TCppScope_t GetUnderlyingScope(TCppScope_t scope); - - /// Gets the namespace or class (by stripping typedefs) for the name - /// passed as a parameter, and if the parent is not passed, - /// then global scope will be assumed. - CPPINTEROP_API TCppScope_t GetScope(const std::string& name, - TCppScope_t parent = nullptr); - - /// When the namespace is known, then the parent doesn't need - /// to be specified. This will probably be phased-out in - /// future versions of the interop library. - CPPINTEROP_API TCppScope_t GetScopeFromCompleteName(const std::string& name); - - /// This function performs a lookup within the specified parent, - /// a specific named entity (functions, enums, etcetera). - CPPINTEROP_API TCppScope_t GetNamed(const std::string& name, - TCppScope_t parent = nullptr); - - /// Gets the parent of the scope that is passed as a parameter. - CPPINTEROP_API TCppScope_t GetParentScope(TCppScope_t scope); - - /// Gets the scope of the type that is passed as a parameter. - CPPINTEROP_API TCppScope_t GetScopeFromType(TCppType_t type); - - /// Gets the number of Base Classes for the Derived Class that - /// is passed as a parameter. - CPPINTEROP_API TCppIndex_t GetNumBases(TCppScope_t klass); - - /// Gets a specific Base Class using its index. Typically GetNumBases() - /// is used to get the number of Base Classes, and then that number - /// can be used to iterate through the index value to get each specific - /// base class. - CPPINTEROP_API TCppScope_t GetBaseClass(TCppScope_t klass, TCppIndex_t ibase); - - /// Checks if the supplied Derived Class is a sub-class of the - /// provided Base Class. - CPPINTEROP_API bool IsSubclass(TCppScope_t derived, TCppScope_t base); - - /// Each base has its own offset in a Derived Class. This offset can be - /// used to get to the Base Class fields. - CPPINTEROP_API int64_t GetBaseClassOffset(TCppScope_t derived, - TCppScope_t base); - - /// Sets a list of all the Methods that are in the Class that is - /// supplied as a parameter. - ///\param[in] klass - Pointer to the scope/class under which the methods have - /// to be retrieved - ///\param[out] methods - Vector of methods in the class - CPPINTEROP_API void GetClassMethods(TCppScope_t klass, - std::vector& methods); - - /// Template function pointer list to add proxies for un-instantiated/ - /// non-overloaded templated methods - ///\param[in] klass - Pointer to the scope/class under which the methods have - /// to be retrieved - ///\param[out] methods - Vector of methods in the class - CPPINTEROP_API void - GetFunctionTemplatedDecls(TCppScope_t klass, - std::vector& methods); - - ///\returns if a class has a default constructor. - CPPINTEROP_API bool HasDefaultConstructor(TCppScope_t scope); - - ///\returns the default constructor of a class, if any. - CPPINTEROP_API TCppFunction_t GetDefaultConstructor(TCppScope_t scope); - - ///\returns the class destructor, if any. - CPPINTEROP_API TCppFunction_t GetDestructor(TCppScope_t scope); - - /// Looks up all the functions that have the name that is - /// passed as a parameter in this function. - CPPINTEROP_API std::vector - GetFunctionsUsingName(TCppScope_t scope, const std::string& name); - - /// Gets the return type of the provided function. - CPPINTEROP_API TCppType_t GetFunctionReturnType(TCppFunction_t func); - - /// Gets the number of Arguments for the provided function. - CPPINTEROP_API TCppIndex_t GetFunctionNumArgs(TCppFunction_t func); - - /// Gets the number of Required Arguments for the provided function. - CPPINTEROP_API TCppIndex_t GetFunctionRequiredArgs(TCppConstFunction_t func); - - /// For each Argument of a function, you can get the Argument Type - /// by providing the Argument Index, based on the number of arguments - /// from the GetFunctionNumArgs() function. - CPPINTEROP_API TCppType_t GetFunctionArgType(TCppFunction_t func, - TCppIndex_t iarg); - - ///\returns a stringified version of a given function signature in the form: - /// void N::f(int i, double d, long l = 0, char ch = 'a'). - CPPINTEROP_API std::string GetFunctionSignature(TCppFunction_t func); - - ///\returns if a function was marked as \c =delete. - CPPINTEROP_API bool IsFunctionDeleted(TCppConstFunction_t function); - - CPPINTEROP_API bool IsTemplatedFunction(TCppFunction_t func); - - /// This function performs a lookup to check if there is a - /// templated function of that type. - CPPINTEROP_API bool ExistsFunctionTemplate(const std::string& name, - TCppScope_t parent = nullptr); - - /// Sets a list of all the constructor for a scope/class that is - /// supplied as a parameter. - ///\param[in] name - This string is used as a constraint, that clients can use - /// to ensure the constructors match the name that they provide - ///\param[in] parent - Pointer to the scope/class for which the constructors - /// are being looked up - /// to be retrieved - ///\param[out] funcs - vector of handles to all constructors found under the - /// given scope - CPPINTEROP_API void LookupConstructors(const std::string& name, - TCppScope_t parent, - std::vector& funcs); - - /// Sets a list of all the Templated Methods that are in the Class that is - /// supplied as a parameter. - ///\returns true if the lookup succeeded, and false if there are no candidates - ///\param[in] name - method name - ///\param[in] parent - Pointer to the scope/class under which the methods have - /// to be retrieved - ///\param[out] funcs - vector of function pointers matching the name - CPPINTEROP_API bool - GetClassTemplatedMethods(const std::string& name, TCppScope_t parent, - std::vector& funcs); - - /// Checks if the provided parameter is a method. - CPPINTEROP_API bool IsMethod(TCppConstFunction_t method); - - /// Checks if the provided parameter is a 'Public' method. - CPPINTEROP_API bool IsPublicMethod(TCppFunction_t method); - - /// Checks if the provided parameter is a 'Protected' method. - CPPINTEROP_API bool IsProtectedMethod(TCppFunction_t method); - - /// Checks if the provided parameter is a 'Private' method. - CPPINTEROP_API bool IsPrivateMethod(TCppFunction_t method); - - /// Checks if the provided parameter is a Constructor. - CPPINTEROP_API bool IsConstructor(TCppConstFunction_t method); - - /// Checks if the provided parameter is a Destructor. - CPPINTEROP_API bool IsDestructor(TCppConstFunction_t method); - - /// Checks if the provided parameter is a 'Static' method. - CPPINTEROP_API bool IsStaticMethod(TCppConstFunction_t method); - - ///\returns the address of the function given its potentially mangled name. - CPPINTEROP_API TCppFuncAddr_t GetFunctionAddress(const char* mangled_name); - - ///\returns the address of the function given its function declaration. - CPPINTEROP_API TCppFuncAddr_t GetFunctionAddress(TCppFunction_t method); - - /// Checks if the provided parameter is a 'Virtual' method. - CPPINTEROP_API bool IsVirtualMethod(TCppFunction_t method); - - /// Gets all the Fields/Data Members of a Class - CPPINTEROP_API void GetDatamembers(TCppScope_t scope, - std::vector& datamembers); - - /// Gets all the Static Fields/Data Members of a Class - ///\param[in] scope - class - ///\param[out] funcs - vector of static data members - CPPINTEROP_API void - GetStaticDatamembers(TCppScope_t scope, - std::vector& datamembers); - - /// Gets all the Enum Constants declared in a Class - ///\param[in] scope - class - ///\param[out] funcs - vector of static data members - ///\param[in] include_enum_class - include enum constants from enum class - CPPINTEROP_API - void GetEnumConstantDatamembers(TCppScope_t scope, - std::vector& datamembers, - bool include_enum_class = true); - - /// This is a Lookup function to be used specifically for data members. - CPPINTEROP_API TCppScope_t LookupDatamember(const std::string& name, - TCppScope_t parent); - - /// Gets the type of the variable that is passed as a parameter. - CPPINTEROP_API TCppType_t GetVariableType(TCppScope_t var); - - /// Gets the address of the variable, you can use it to get the - /// value stored in the variable. - CPPINTEROP_API intptr_t GetVariableOffset(TCppScope_t var, - TCppScope_t parent = nullptr); - - /// Checks if the provided variable is a 'Public' variable. - CPPINTEROP_API bool IsPublicVariable(TCppScope_t var); - - /// Checks if the provided variable is a 'Protected' variable. - CPPINTEROP_API bool IsProtectedVariable(TCppScope_t var); - - /// Checks if the provided variable is a 'Private' variable. - CPPINTEROP_API bool IsPrivateVariable(TCppScope_t var); - - /// Checks if the provided variable is a 'Static' variable. - CPPINTEROP_API bool IsStaticVariable(TCppScope_t var); - - /// Checks if the provided variable is a 'Constant' variable. - CPPINTEROP_API bool IsConstVariable(TCppScope_t var); - - /// Checks if the provided parameter is a Record (struct). - CPPINTEROP_API bool IsRecordType(TCppType_t type); - - /// Checks if the provided parameter is a Plain Old Data Type (POD). - CPPINTEROP_API bool IsPODType(TCppType_t type); - - /// Checks if type is a pointer - CPPINTEROP_API bool IsPointerType(TCppType_t type); - - /// Get the underlying pointee type - CPPINTEROP_API TCppType_t GetPointeeType(TCppType_t type); - - /// Checks if type is a reference - CPPINTEROP_API bool IsReferenceType(TCppType_t type); - - /// Get the type that the reference refers to - CPPINTEROP_API TCppType_t GetNonReferenceType(TCppType_t type); - - /// Gets the pure, Underlying Type (as opposed to the Using Type). - CPPINTEROP_API TCppType_t GetUnderlyingType(TCppType_t type); - - /// Gets the Type (passed as a parameter) as a String value. - CPPINTEROP_API std::string GetTypeAsString(TCppType_t type); - - /// Gets the Canonical Type string from the std string. A canonical type - /// is the type with any typedef names, syntactic sugars or modifiers stripped - /// out of it. - CPPINTEROP_API TCppType_t GetCanonicalType(TCppType_t type); - - /// Used to either get the built-in type of the provided string, or - /// use the name to lookup the actual type. - CPPINTEROP_API TCppType_t GetType(const std::string& type); - - ///\returns the complex of the provided type. - CPPINTEROP_API TCppType_t GetComplexType(TCppType_t element_type); - - /// This will convert a class into its type, so for example, you can - /// use it to declare variables in it. - CPPINTEROP_API TCppType_t GetTypeFromScope(TCppScope_t klass); - - /// Checks if a C++ type derives from another. - CPPINTEROP_API bool IsTypeDerivedFrom(TCppType_t derived, TCppType_t base); - - /// Creates a trampoline function by using the interpreter and returns a - /// uniform interface to call it from compiled code. - CPPINTEROP_API JitCall MakeFunctionCallable(TCppConstFunction_t func); - - CPPINTEROP_API JitCall MakeFunctionCallable(TInterp_t I, - TCppConstFunction_t func); - - /// Checks if a function declared is of const type or not. - CPPINTEROP_API bool IsConstMethod(TCppFunction_t method); - - ///\returns the default argument value as string. - CPPINTEROP_API std::string GetFunctionArgDefault(TCppFunction_t func, - TCppIndex_t param_index); - - ///\returns the argument name of function as string. - CPPINTEROP_API std::string GetFunctionArgName(TCppFunction_t func, - TCppIndex_t param_index); - - ///\returns arity of the operator or kNone - CPPINTEROP_API OperatorArity GetOperatorArity(TCppFunction_t op); - - ///\returns list of operator overloads - CPPINTEROP_API void GetOperator(TCppScope_t scope, Operator op, - std::vector& operators, - OperatorArity kind = kBoth); - - /// Creates an instance of the interpreter we need for the various interop - /// services. - ///\param[in] Args - the list of arguments for interpreter constructor. - ///\param[in] CPPINTEROP_EXTRA_INTERPRETER_ARGS - an env variable, if defined, - /// adds additional arguments to the interpreter. - CPPINTEROP_API TInterp_t - CreateInterpreter(const std::vector& Args = {}, - const std::vector& GpuArgs = {}); - - /// Checks which Interpreter backend was CppInterOp library built with (Cling, - /// Clang-REPL, etcetera). In practice, the selected interpreter should not - /// matter, since the library will function in the same way. - ///\returns the current interpreter instance, if any. - CPPINTEROP_API TInterp_t GetInterpreter(); - - /// Sets the Interpreter instance with an external interpreter, meant to - /// be called by an external library that manages it's own interpreter. - /// Sets a flag signifying CppInterOp does not have ownership of the - /// sInterpreter. - ///\param[in] Args - the pointer to an external interpreter - CPPINTEROP_API void UseExternalInterpreter(TInterp_t I); - - /// Adds a Search Path for the Interpreter to get the libraries. - CPPINTEROP_API void AddSearchPath(const char* dir, bool isUser = true, - bool prepend = false); - - /// Returns the resource-dir path (for headers). - CPPINTEROP_API const char* GetResourceDir(); - - /// Uses the underlying clang compiler to detect the resource directory. - /// In essence calling clang -print-resource-dir and checks if it ends with - /// a compatible to CppInterOp version. - ///\param[in] ClangBinaryName - the name (or the full path) of the compiler - /// to ask. - CPPINTEROP_API std::string - DetectResourceDir(const char* ClangBinaryName = "clang"); - - /// Asks the system compiler for its default include paths. - ///\param[out] Paths - the list of include paths returned by eg. - /// `c++ -xc++ -E -v /dev/null 2>&1` - ///\param[in] CompilerName - the name (or the full path) of the compiler - /// binary file. - CPPINTEROP_API void - DetectSystemCompilerIncludePaths(std::vector& Paths, - const char* CompilerName = "c++"); - - /// Secondary search path for headers, if not found using the - /// GetResourceDir() function. - CPPINTEROP_API void AddIncludePath(const char* dir); - - // Gets the currently used include paths - ///\param[out] IncludePaths - the list of include paths - /// - CPPINTEROP_API void GetIncludePaths(std::vector& IncludePaths, - bool withSystem = false, - bool withFlags = false); - - /// Only Declares a code snippet in \c code and does not execute it. - ///\returns 0 on success - CPPINTEROP_API int Declare(const char* code, bool silent = false); - - /// Declares and executes a code snippet in \c code. - ///\returns 0 on success - CPPINTEROP_API int Process(const char* code); - - /// Declares, executes and returns the execution result as a intptr_t. - ///\returns the expression results as a intptr_t. - CPPINTEROP_API intptr_t Evaluate(const char* code, bool* HadError = nullptr); - - /// Looks up the library if access is enabled. - ///\returns the path to the library. - CPPINTEROP_API std::string LookupLibrary(const char* lib_name); - - /// Finds \c lib_stem considering the list of search paths and loads it by - /// calling dlopen. - /// \returns true on success. - CPPINTEROP_API bool LoadLibrary(const char* lib_stem, bool lookup = true); - - /// Finds \c lib_stem considering the list of search paths and unloads it by - /// calling dlclose. - /// function. - CPPINTEROP_API void UnloadLibrary(const char* lib_stem); - - /// Scans all libraries on the library search path for a given potentially - /// mangled symbol name. - ///\returns the path to the first library that contains the symbol definition. - CPPINTEROP_API std::string - SearchLibrariesForSymbol(const char* mangled_name, - bool search_system /*true*/); - - /// Inserts or replaces a symbol in the JIT with the one provided. This is - /// useful for providing our own implementations of facilities such as printf. - /// - ///\param[in] linker_mangled_name - the name of the symbol to be inserted or - /// replaced. - ///\param[in] address - the new address of the symbol. - /// - ///\returns true on failure. - CPPINTEROP_API bool InsertOrReplaceJitSymbol(const char* linker_mangled_name, - uint64_t address); - - /// Tries to load provided objects in a string format (prettyprint). - CPPINTEROP_API std::string ObjToString(const char* type, void* obj); - - struct TemplateArgInfo { - TCppType_t m_Type; - const char* m_IntegralValue; - TemplateArgInfo(TCppScope_t type, const char* integral_value = nullptr) - : m_Type(type), m_IntegralValue(integral_value) {} - }; - /// Builds a template instantiation for a given templated declaration. - /// Offers a single interface for instantiation of class, function and - /// variable templates - /// - ///\param[in] tmpl - Uninstantiated template class/function - ///\param[in] template_args - Pointer to vector of template arguments stored - /// in the \c TemplateArgInfo struct - ///\param[in] template_args_size - Size of the vector of template arguments - /// passed as \c template_args - /// - ///\returns Instantiated templated class/function/variable pointer - CPPINTEROP_API TCppScope_t - InstantiateTemplate(TCppScope_t tmpl, const TemplateArgInfo* template_args, - size_t template_args_size); - - /// Sets the class template instantiation arguments of \c templ_instance. - /// - ///\param[in] templ_instance - Pointer to the template instance - ///\param[out] args - Vector of instantiation arguments - CPPINTEROP_API void - GetClassTemplateInstantiationArgs(TCppScope_t templ_instance, - std::vector& args); - - /// Instantiates a function template from a given string representation. This - /// function also does overload resolution. - ///\returns the instantiated function template declaration. - CPPINTEROP_API TCppFunction_t - InstantiateTemplateFunctionFromString(const char* function_template); - - /// Finds best overload match based on explicit template parameters (if any) - /// and argument types. - /// - ///\param[in] candidates - vector of overloads that come under the - /// parent scope and have the same name - ///\param[in] explicit_types - set of explicitly instantiated template types - ///\param[in] arg_types - set of argument types - ///\returns Instantiated function pointer - CPPINTEROP_API TCppFunction_t - BestOverloadFunctionMatch(const std::vector& candidates, - const std::vector& explicit_types, - const std::vector& arg_types); - - CPPINTEROP_API void GetAllCppNames(TCppScope_t scope, - std::set& names); - - CPPINTEROP_API void DumpScope(TCppScope_t scope); - - namespace DimensionValue { - enum : long int { - UNKNOWN_SIZE = -1, - }; - } - - /// 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); - - /// 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. - CPPINTEROP_API void Destruct(TCppObject_t This, TCppScope_t type, - bool withFree = true); - - /// @name Stream Redirection - /// - ///@{ - - enum CaptureStreamKind : char { - kStdOut = 1, ///< stdout - kStdErr, ///< stderr - // kStdBoth, ///< stdout and stderr - // kSTDSTRM // "&1" or "&2" is not a filename - }; - - /// Begins recording the given standard stream. - ///\param[fd_kind] - The stream to be captured - CPPINTEROP_API void BeginStdStreamCapture(CaptureStreamKind fd_kind); - - /// Ends recording the standard stream and returns the result as a string. - CPPINTEROP_API std::string EndStdStreamCapture(); - - ///@} - - /// Append all Code completion suggestions to Results. - ///\param[out] Results - CC suggestions for code fragment. Suggestions are - /// appended. - ///\param[in] code - code fragment to complete - ///\param[in] complete_line - position (line) in code for suggestion - ///\param[in] complete_column - position (column) in code for suggestion - CPPINTEROP_API void CodeComplete(std::vector& Results, - const char* code, - unsigned complete_line = 1U, - unsigned complete_column = 1U); - - /// Reverts the last N operations performed by the interpreter. - ///\param[in] N The number of operations to undo. Defaults to 1. - ///\returns 0 on success, non-zero on failure. - CPPINTEROP_API int Undo(unsigned N = 1); - -} // end namespace Cpp - -#endif // CPPINTEROP_CPPINTEROP_H +#endif // CLANG_CPPINTEROP_H diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 1951dee75..55808f272 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1 +1 @@ -add_subdirectory(Interpreter) +add_subdirectory(CppInterOp) diff --git a/lib/Interpreter/CMakeLists.txt b/lib/CppInterOp/CMakeLists.txt similarity index 98% rename from lib/Interpreter/CMakeLists.txt rename to lib/CppInterOp/CMakeLists.txt index eccfc39af..8835511b4 100644 --- a/lib/Interpreter/CMakeLists.txt +++ b/lib/CppInterOp/CMakeLists.txt @@ -118,7 +118,7 @@ if(EMSCRIPTEN) # unit tests main_module you get errors due to undefined symbols. The reading of the file # below into a SYMBOLS_LIST variable is a temporary workaround that exports the undefined # symbols from the shared library, until it can be determined why they are not being exported already. - file(READ "${CMAKE_SOURCE_DIR}/lib/Interpreter/exports.ld" SYMBOLS_LIST) + file(READ "${CMAKE_CURRENT_SOURCE_DIR}/exports.ld" SYMBOLS_LIST) # Replace newlines with spaces string(REPLACE "\n" " " SYMBOLS_LIST "${SYMBOLS_LIST}") diff --git a/lib/Interpreter/CXCppInterOp.cpp b/lib/CppInterOp/CXCppInterOp.cpp similarity index 99% rename from lib/Interpreter/CXCppInterOp.cpp rename to lib/CppInterOp/CXCppInterOp.cpp index 031096c91..ddfd07513 100644 --- a/lib/Interpreter/CXCppInterOp.cpp +++ b/lib/CppInterOp/CXCppInterOp.cpp @@ -1,4 +1,6 @@ #include "clang-c/CXCppInterOp.h" +#include "CppInterOp/CppInterOp.h" + #include "Compatibility.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/Decl.h" @@ -7,7 +9,6 @@ #include "clang/AST/RecordLayout.h" #include "clang/AST/Type.h" #include "clang/Frontend/CompilerInstance.h" -#include "clang/Interpreter/CppInterOp.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Sema.h" #include "llvm/ADT/STLExtras.h" @@ -619,4 +620,4 @@ void Destruct(compat::Interpreter& interp, TCppObject_t This, void clang_destruct(CXObject This, CXScope S, bool withFree) { Cpp::Destruct(*getInterpreter(S), This, getDecl(S), withFree); -} \ No newline at end of file +} diff --git a/lib/Interpreter/Compatibility.h b/lib/CppInterOp/Compatibility.h similarity index 100% rename from lib/Interpreter/Compatibility.h rename to lib/CppInterOp/Compatibility.h diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp new file mode 100755 index 000000000..0cafbb113 --- /dev/null +++ b/lib/CppInterOp/CppInterOp.cpp @@ -0,0 +1,3684 @@ +//--------------------------------------------------------------------*- C++ -*- +// CLING - the C++ LLVM-based InterpreterG :) +// author: Vassil Vassilev +// +// This file is dual-licensed: you can choose to license it under the University +// of Illinois Open Source License or the GNU Lesser General Public License. See +// LICENSE.TXT for details. +//------------------------------------------------------------------------------ + +#include "CppInterOp/CppInterOp.h" + +#include "Compatibility.h" + +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclAccessPair.h" +#include "clang/AST/DeclBase.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclarationName.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/GlobalDecl.h" +#include "clang/AST/Mangle.h" +#include "clang/AST/NestedNameSpecifier.h" +#include "clang/AST/QualTypeNames.h" +#include "clang/AST/RecordLayout.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/Type.h" +#include "clang/Basic/DiagnosticSema.h" +#include "clang/Basic/Linkage.h" +#include "clang/Basic/OperatorKinds.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/Specifiers.h" +#include "clang/Basic/Version.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Sema/Lookup.h" +#include "clang/Sema/Overload.h" +#include "clang/Sema/Ownership.h" +#include "clang/Sema/Sema.h" +#if CLANG_VERSION_MAJOR >= 19 +#include "clang/Sema/Redeclaration.h" +#endif +#include "clang/Sema/TemplateDeduction.h" + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Demangle/Demangle.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_os_ostream.h" + +#include +#include +#include +#include +#include +#include + +// Stream redirect. +#ifdef _WIN32 +#include +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +// For exec(). +#include +#define popen(x, y) (_popen(x, y)) +#define pclose (_pclose) +#endif +#else +#include +#include +#endif // WIN32 + +#include + +namespace Cpp { + +using namespace clang; +using namespace llvm; +using namespace std; + +// Flag to indicate ownership when an external interpreter instance is used. +static bool OwningSInterpreter = true; +static compat::Interpreter* sInterpreter = nullptr; +// Valgrind complains about __cxa_pure_virtual called when deleting +// llvm::SectionMemoryManager::~SectionMemoryManager as part of the dtor chain +// of the Interpreter. +// This might fix the issue https://reviews.llvm.org/D107087 +// FIXME: For now we just leak the Interpreter. +struct InterpDeleter { + ~InterpDeleter() = default; +} Deleter; + +static compat::Interpreter& getInterp() { + assert(sInterpreter && + "Interpreter instance must be set before calling this!"); + return *sInterpreter; +} +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 Valid = true; + if (Cpp::IsConstructor(m_FD)) { + assert(result && "Must pass the location of the created object!"); + Valid &= (bool)result; + } + if (Cpp::GetFunctionRequiredArgs(m_FD) > args.m_ArgSize) { + assert(0 && "Must pass at least the minimal number of args!"); + Valid = false; + } + if (args.m_ArgSize) { + assert(args.m_Args != nullptr && "Must pass an argument list!"); + Valid &= (bool)args.m_Args; + } + if (!Cpp::IsConstructor(m_FD) && !Cpp::IsDestructor(m_FD) && + Cpp::IsMethod(m_FD) && !Cpp::IsStaticMethod(m_FD)) { + assert(self && "Must pass the pointer to object"); + Valid &= (bool)self; + } + const auto* FD = cast((const Decl*)m_FD); + if (!FD->getReturnType()->isVoidType() && !result) { + assert(0 && "We are discarding the return type of the function!"); + Valid = false; + } + assert(m_Kind != kDestructorCall && "Wrong overload!"); + Valid &= m_Kind != kDestructorCall; + return Valid; +} + +void JitCall::ReportInvokeStart(void* result, ArgList args, void* self) const { + std::string Name; + llvm::raw_string_ostream OS(Name); + auto FD = (const FunctionDecl*)m_FD; + FD->getNameForDiagnostic(OS, FD->getASTContext().getPrintingPolicy(), + /*Qualified=*/true); + LLVM_DEBUG(dbgs() << "Run '" << Name << "', compiled at: " + << (void*)m_GenericCall << " with result at: " << result + << " , args at: " << args.m_Args << " , arg count: " + << args.m_ArgSize << " , self at: " << self << "\n";); +} + +void JitCall::ReportInvokeStart(void* object, unsigned long nary, + int withFree) const { + std::string Name; + llvm::raw_string_ostream OS(Name); + auto FD = (const FunctionDecl*)m_FD; + FD->getNameForDiagnostic(OS, FD->getASTContext().getPrintingPolicy(), + /*Qualified=*/true); + LLVM_DEBUG(dbgs() << "Finish '" << Name + << "', compiled at: " << (void*)m_DestructorCall); +} + +#undef DEBUG_TYPE + +std::string GetVersion() { + const char* const VERSION = CPPINTEROP_VERSION; + std::string fullVersion = "CppInterOp version"; + fullVersion += VERSION; + fullVersion += "\n (based on " +#ifdef CPPINTEROP_USE_CLING + "cling "; +#else + "clang-repl"; +#endif // CPPINTEROP_USE_CLING + return fullVersion + "[" + clang::getClangFullVersion() + "])\n"; +} + +std::string Demangle(const std::string& mangled_name) { +#if CLANG_VERSION_MAJOR > 16 +#ifdef _WIN32 + std::string demangle = microsoftDemangle(mangled_name, nullptr, nullptr); +#else + std::string demangle = itaniumDemangle(mangled_name); +#endif +#else +#ifdef _WIN32 + std::string demangle = microsoftDemangle(mangled_name.c_str(), nullptr, + nullptr, nullptr, nullptr); +#else + std::string demangle = + itaniumDemangle(mangled_name.c_str(), nullptr, nullptr, nullptr); +#endif +#endif + return demangle; +} + +void EnableDebugOutput(bool value /* =true*/) { llvm::DebugFlag = value; } + +bool IsDebugOutputEnabled() { return llvm::DebugFlag; } + +bool IsAggregate(TCppScope_t scope) { + Decl* D = static_cast(scope); + + // Aggregates are only arrays or tag decls. + if (ValueDecl* ValD = dyn_cast(D)) + if (ValD->getType()->isArrayType()) + return true; + + // struct, class, union + if (CXXRecordDecl* CXXRD = dyn_cast(D)) + return CXXRD->isAggregate(); + + return false; +} + +bool IsNamespace(TCppScope_t scope) { + Decl* D = static_cast(scope); + return isa(D); +} + +bool IsClass(TCppScope_t scope) { + Decl* D = static_cast(scope); + return isa(D); +} + +bool IsFunction(TCppScope_t scope) { + Decl* D = static_cast(scope); + return isa(D); +} + +bool IsFunctionPointerType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + return QT->isFunctionPointerType(); +} + +bool IsClassPolymorphic(TCppScope_t klass) { + Decl* D = static_cast(klass); + if (auto* CXXRD = llvm::dyn_cast(D)) + if (auto* CXXRDD = CXXRD->getDefinition()) + return CXXRDD->isPolymorphic(); + return false; +} + +static SourceLocation GetValidSLoc(Sema& semaRef) { + auto& SM = semaRef.getSourceManager(); + return SM.getLocForStartOfFile(SM.getMainFileID()); +} + +// See TClingClassInfo::IsLoaded +bool IsComplete(TCppScope_t scope) { + if (!scope) + return false; + + Decl* D = static_cast(scope); + + if (isa(D)) { + QualType QT = QualType::getFromOpaquePtr(GetTypeFromScope(scope)); + clang::Sema& S = getSema(); + SourceLocation fakeLoc = GetValidSLoc(S); +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&getInterp()); +#endif // CPPINTEROP_USE_CLING + return S.isCompleteType(fakeLoc, QT); + } + + if (auto* CXXRD = dyn_cast(D)) + return CXXRD->hasDefinition(); + else if (auto* TD = dyn_cast(D)) + return TD->getDefinition(); + + // Everything else is considered complete. + return true; +} + +size_t SizeOf(TCppScope_t scope) { + assert(scope); + if (!IsComplete(scope)) + return 0; + + if (auto* RD = dyn_cast(static_cast(scope))) { + ASTContext& Context = RD->getASTContext(); + const ASTRecordLayout& Layout = Context.getASTRecordLayout(RD); + return Layout.getSize().getQuantity(); + } + + return 0; +} + +bool IsBuiltin(TCppType_t type) { + QualType Ty = QualType::getFromOpaquePtr(type); + if (Ty->isBuiltinType() || Ty->isAnyComplexType()) + return true; + // FIXME: Figure out how to avoid the string comparison. + return llvm::StringRef(Ty.getAsString()).contains("complex"); +} + +bool IsTemplate(TCppScope_t handle) { + auto* D = (clang::Decl*)handle; + return llvm::isa_and_nonnull(D); +} + +bool IsTemplateSpecialization(TCppScope_t handle) { + auto* D = (clang::Decl*)handle; + return llvm::isa_and_nonnull(D); +} + +bool IsTypedefed(TCppScope_t handle) { + auto* D = (clang::Decl*)handle; + return llvm::isa_and_nonnull(D); +} + +bool IsAbstract(TCppType_t klass) { + auto* D = (clang::Decl*)klass; + if (auto* CXXRD = llvm::dyn_cast_or_null(D)) + return CXXRD->isAbstract(); + + return false; +} + +bool IsEnumScope(TCppScope_t handle) { + auto* D = (clang::Decl*)handle; + return llvm::isa_and_nonnull(D); +} + +bool IsEnumConstant(TCppScope_t handle) { + auto* D = (clang::Decl*)handle; + return llvm::isa_and_nonnull(D); +} + +bool IsEnumType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + return QT->isEnumeralType(); +} + +static bool isSmartPointer(const RecordType* RT) { + auto IsUseCountPresent = [](const RecordDecl* Record) { + ASTContext& C = Record->getASTContext(); + return !Record->lookup(&C.Idents.get("use_count")).empty(); + }; + auto IsOverloadedOperatorPresent = [](const RecordDecl* Record, + OverloadedOperatorKind Op) { + ASTContext& C = Record->getASTContext(); + DeclContextLookupResult Result = + Record->lookup(C.DeclarationNames.getCXXOperatorName(Op)); + return !Result.empty(); + }; + + const RecordDecl* Record = RT->getDecl(); + if (IsUseCountPresent(Record)) + return true; + + bool foundStarOperator = IsOverloadedOperatorPresent(Record, OO_Star); + bool foundArrowOperator = IsOverloadedOperatorPresent(Record, OO_Arrow); + if (foundStarOperator && foundArrowOperator) + return true; + + const CXXRecordDecl* CXXRecord = dyn_cast(Record); + if (!CXXRecord) + return false; + + auto FindOverloadedOperators = [&](const CXXRecordDecl* Base) { + // If we find use_count, we are done. + if (IsUseCountPresent(Base)) + return false; // success. + if (!foundStarOperator) + foundStarOperator = IsOverloadedOperatorPresent(Base, OO_Star); + if (!foundArrowOperator) + foundArrowOperator = IsOverloadedOperatorPresent(Base, OO_Arrow); + if (foundStarOperator && foundArrowOperator) + return false; // success. + return true; + }; + + return !CXXRecord->forallBases(FindOverloadedOperators); +} + +bool IsSmartPtrType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + if (const RecordType* RT = QT->getAs()) { + // Add quick checks for the std smart prts to cover most of the cases. + std::string typeString = GetTypeAsString(type); + llvm::StringRef tsRef(typeString); + if (tsRef.starts_with("std::unique_ptr") || + tsRef.starts_with("std::shared_ptr") || + tsRef.starts_with("std::weak_ptr")) + return true; + return isSmartPointer(RT); + } + return false; +} + +TCppType_t GetIntegerTypeFromEnumScope(TCppScope_t handle) { + auto* D = (clang::Decl*)handle; + if (auto* ED = llvm::dyn_cast_or_null(D)) { + return ED->getIntegerType().getAsOpaquePtr(); + } + + return 0; +} + +TCppType_t GetIntegerTypeFromEnumType(TCppType_t enum_type) { + if (!enum_type) + return nullptr; + + QualType QT = QualType::getFromOpaquePtr(enum_type); + if (auto* ET = QT->getAs()) + return ET->getDecl()->getIntegerType().getAsOpaquePtr(); + + return nullptr; +} + +std::vector GetEnumConstants(TCppScope_t handle) { + auto* D = (clang::Decl*)handle; + + if (auto* ED = llvm::dyn_cast_or_null(D)) { + std::vector enum_constants; + for (auto* ECD : ED->enumerators()) { + enum_constants.push_back((TCppScope_t)ECD); + } + + return enum_constants; + } + + return {}; +} + +TCppType_t GetEnumConstantType(TCppScope_t handle) { + if (!handle) + return nullptr; + + auto* D = (clang::Decl*)handle; + if (auto* ECD = llvm::dyn_cast(D)) + return ECD->getType().getAsOpaquePtr(); + + return 0; +} + +TCppIndex_t GetEnumConstantValue(TCppScope_t handle) { + auto* D = (clang::Decl*)handle; + if (auto* ECD = llvm::dyn_cast_or_null(D)) { + const llvm::APSInt& Val = ECD->getInitVal(); + return Val.getExtValue(); + } + return 0; +} + +size_t GetSizeOfType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + if (const TagType* TT = QT->getAs()) + return SizeOf(TT->getDecl()); + + // FIXME: Can we get the size of a non-tag type? + auto TI = getSema().getASTContext().getTypeInfo(QT); + size_t TypeSize = TI.Width; + return TypeSize / 8; +} + +bool IsVariable(TCppScope_t scope) { + auto* D = (clang::Decl*)scope; + return llvm::isa_and_nonnull(D); +} + +std::string GetName(TCppType_t klass) { + auto* D = (clang::NamedDecl*)klass; + + if (llvm::isa_and_nonnull(D)) { + return ""; + } + + if (auto* ND = llvm::dyn_cast_or_null(D)) { + return ND->getNameAsString(); + } + + return ""; +} + +std::string GetCompleteName(TCppType_t klass) { + auto& C = getSema().getASTContext(); + auto* D = (Decl*)klass; + + if (auto* ND = llvm::dyn_cast_or_null(D)) { + if (auto* TD = llvm::dyn_cast(ND)) { + std::string type_name; + QualType QT = C.getTagDeclType(TD); + PrintingPolicy Policy = C.getPrintingPolicy(); + Policy.SuppressUnwrittenScope = true; + Policy.SuppressScope = true; + Policy.AnonymousTagLocations = false; + QT.getAsStringInternal(type_name, Policy); + + return type_name; + } + + return ND->getNameAsString(); + } + + if (llvm::isa_and_nonnull(D)) { + return ""; + } + + return ""; +} + +std::string GetQualifiedName(TCppType_t klass) { + auto* D = (Decl*)klass; + if (auto* ND = llvm::dyn_cast_or_null(D)) { + return ND->getQualifiedNameAsString(); + } + + if (llvm::isa_and_nonnull(D)) { + return ""; + } + + return ""; +} + +// FIXME: Figure out how to merge with GetCompleteName. +std::string GetQualifiedCompleteName(TCppType_t klass) { + auto& C = getSema().getASTContext(); + auto* D = (Decl*)klass; + + if (auto* ND = llvm::dyn_cast_or_null(D)) { + if (auto* TD = llvm::dyn_cast(ND)) { + std::string type_name; + QualType QT = C.getTagDeclType(TD); + QT.getAsStringInternal(type_name, C.getPrintingPolicy()); + + return type_name; + } + + return ND->getQualifiedNameAsString(); + } + + if (llvm::isa_and_nonnull(D)) { + return ""; + } + + return ""; +} + +std::vector GetUsingNamespaces(TCppScope_t scope) { + auto* D = (clang::Decl*)scope; + + if (auto* DC = llvm::dyn_cast_or_null(D)) { + std::vector namespaces; + for (auto UD : DC->using_directives()) { + namespaces.push_back((TCppScope_t)UD->getNominatedNamespace()); + } + return namespaces; + } + + return {}; +} + +TCppScope_t GetGlobalScope() { + return getSema().getASTContext().getTranslationUnitDecl()->getFirstDecl(); +} + +static Decl* GetScopeFromType(QualType QT) { + if (auto* Type = QT.getCanonicalType().getTypePtrOrNull()) { + Type = Type->getPointeeOrArrayElementType(); + Type = Type->getUnqualifiedDesugaredType(); + if (auto* ET = llvm::dyn_cast(Type)) + return ET->getDecl(); + if (auto* FnType = llvm::dyn_cast(Type)) + Type = const_cast(FnType->getReturnType().getTypePtr()); + return Type->getAsCXXRecordDecl(); + } + return 0; +} + +TCppScope_t GetScopeFromType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + return (TCppScope_t)GetScopeFromType(QT); +} + +static clang::Decl* GetUnderlyingScope(clang::Decl* D) { + if (auto* TND = dyn_cast_or_null(D)) { + if (auto* Scope = GetScopeFromType(TND->getUnderlyingType())) + D = Scope; + } else if (auto* USS = dyn_cast_or_null(D)) { + if (auto* Scope = USS->getTargetDecl()) + D = Scope; + } + + return D; +} + +TCppScope_t GetUnderlyingScope(TCppScope_t scope) { + if (!scope) + return 0; + return GetUnderlyingScope((clang::Decl*)scope); +} + +TCppScope_t GetScope(const std::string& name, TCppScope_t parent) { + // FIXME: GetScope should be replaced by a general purpose lookup + // and filter function. The function should be like GetNamed but + // also take in a filter parameter which determines which results + // to pass back + if (name == "") + return GetGlobalScope(); + + auto* ND = (NamedDecl*)GetNamed(name, parent); + + if (!ND || ND == (NamedDecl*)-1) + return 0; + + if (llvm::isa(ND) || llvm::isa(ND) || + llvm::isa(ND) || llvm::isa(ND)) + return (TCppScope_t)(ND->getCanonicalDecl()); + + return 0; +} + +TCppScope_t GetScopeFromCompleteName(const std::string& name) { + std::string delim = "::"; + size_t start = 0; + size_t end = name.find(delim); + TCppScope_t curr_scope = 0; + while (end != std::string::npos) { + curr_scope = GetScope(name.substr(start, end - start), curr_scope); + start = end + delim.length(); + end = name.find(delim, start); + } + return GetScope(name.substr(start, end), curr_scope); +} + +TCppScope_t GetNamed(const std::string& name, + TCppScope_t parent /*= nullptr*/) { + clang::DeclContext* Within = 0; + if (parent) { + auto* D = (clang::Decl*)parent; + D = GetUnderlyingScope(D); + Within = llvm::dyn_cast(D); + } + + auto* ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); + if (ND && ND != (clang::NamedDecl*)-1) { + return (TCppScope_t)(ND->getCanonicalDecl()); + } + + return 0; +} + +TCppScope_t GetParentScope(TCppScope_t scope) { + auto* D = (clang::Decl*)scope; + + if (llvm::isa_and_nonnull(D)) { + return 0; + } + auto* ParentDC = D->getDeclContext(); + + if (!ParentDC) + return 0; + + auto* P = clang::Decl::castFromDeclContext(ParentDC)->getCanonicalDecl(); + + if (auto* TU = llvm::dyn_cast_or_null(P)) + return (TCppScope_t)TU->getFirstDecl(); + + return (TCppScope_t)P; +} + +TCppIndex_t GetNumBases(TCppScope_t klass) { + auto* D = (Decl*)klass; + + if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { + if (CXXRD->hasDefinition()) + return CXXRD->getNumBases(); + } + + return 0; +} + +TCppScope_t GetBaseClass(TCppScope_t klass, TCppIndex_t ibase) { + auto* D = (Decl*)klass; + auto* CXXRD = llvm::dyn_cast_or_null(D); + if (!CXXRD || CXXRD->getNumBases() <= ibase) + return 0; + + auto type = (CXXRD->bases_begin() + ibase)->getType(); + if (auto RT = type->getAs()) + return (TCppScope_t)RT->getDecl(); + + return 0; +} + +// FIXME: Consider dropping this interface as it seems the same as +// IsTypeDerivedFrom. +bool IsSubclass(TCppScope_t derived, TCppScope_t base) { + if (derived == base) + return true; + + if (!derived || !base) + return false; + + auto* derived_D = (clang::Decl*)derived; + auto* base_D = (clang::Decl*)base; + + if (!isa(derived_D) || !isa(base_D)) + return false; + + auto Derived = cast(derived_D); + auto Base = cast(base_D); + return IsTypeDerivedFrom(GetTypeFromScope(Derived), GetTypeFromScope(Base)); +} + +// Copied from VTableBuilder.cpp +// This is an internal helper function for the CppInterOp library (as evident +// by the 'static' declaration), while the similar GetBaseClassOffset() +// function below is exposed to library users. +static unsigned ComputeBaseOffset(const ASTContext& Context, + const CXXRecordDecl* DerivedRD, + const CXXBasePath& Path) { + CharUnits NonVirtualOffset = CharUnits::Zero(); + + unsigned NonVirtualStart = 0; + const CXXRecordDecl* VirtualBase = nullptr; + + // First, look for the virtual base class. + for (int I = Path.size(), E = 0; I != E; --I) { + const CXXBasePathElement& Element = Path[I - 1]; + + if (Element.Base->isVirtual()) { + NonVirtualStart = I; + QualType VBaseType = Element.Base->getType(); + VirtualBase = VBaseType->getAsCXXRecordDecl(); + break; + } + } + + // Now compute the non-virtual offset. + for (unsigned I = NonVirtualStart, E = Path.size(); I != E; ++I) { + const CXXBasePathElement& Element = Path[I]; + + // Check the base class offset. + const ASTRecordLayout& Layout = Context.getASTRecordLayout(Element.Class); + + const CXXRecordDecl* Base = Element.Base->getType()->getAsCXXRecordDecl(); + + NonVirtualOffset += Layout.getBaseClassOffset(Base); + } + + // FIXME: This should probably use CharUnits or something. Maybe we should + // even change the base offsets in ASTRecordLayout to be specified in + // CharUnits. + // return BaseOffset(DerivedRD, VirtuaBose, aBlnVirtualOffset); + if (VirtualBase) { + const ASTRecordLayout& Layout = Context.getASTRecordLayout(DerivedRD); + CharUnits VirtualOffset = Layout.getVBaseClassOffset(VirtualBase); + return (NonVirtualOffset + VirtualOffset).getQuantity(); + } + return NonVirtualOffset.getQuantity(); +} + +int64_t GetBaseClassOffset(TCppScope_t derived, TCppScope_t base) { + if (base == derived) + return 0; + + assert(derived || base); + + auto* DD = (Decl*)derived; + auto* BD = (Decl*)base; + if (!isa(DD) || !isa(BD)) + return -1; + CXXRecordDecl* DCXXRD = cast(DD); + CXXRecordDecl* BCXXRD = cast(BD); + CXXBasePaths Paths(/*FindAmbiguities=*/false, /*RecordPaths=*/true, + /*DetectVirtual=*/false); + DCXXRD->isDerivedFrom(BCXXRD, Paths); + + // FIXME: We might want to cache these requests as they seem expensive. + return ComputeBaseOffset(getSema().getASTContext(), DCXXRD, Paths.front()); +} + +template +static void GetClassDecls(TCppScope_t klass, + std::vector& methods) { + if (!klass) + return; + + auto* D = (clang::Decl*)klass; + + if (auto* TD = dyn_cast(D)) + D = GetScopeFromType(TD->getUnderlyingType()); + + if (!D || !isa(D)) + return; + + auto* CXXRD = dyn_cast(D); +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&getInterp()); +#endif // CPPINTEROP_USE_CLING + getSema().ForceDeclarationOfImplicitMembers(CXXRD); + for (Decl* DI : CXXRD->decls()) { + if (auto* MD = dyn_cast(DI)) + methods.push_back(MD); + else if (auto* USD = dyn_cast(DI)) + if (auto* MD = dyn_cast(USD->getTargetDecl())) + methods.push_back(MD); + } +} + +void GetClassMethods(TCppScope_t klass, std::vector& methods) { + GetClassDecls(klass, methods); +} + +void GetFunctionTemplatedDecls(TCppScope_t klass, + std::vector& methods) { + GetClassDecls(klass, methods); +} + +bool HasDefaultConstructor(TCppScope_t scope) { + auto* D = (clang::Decl*)scope; + + if (auto* CXXRD = llvm::dyn_cast_or_null(D)) + return CXXRD->hasDefaultConstructor(); + + return false; +} + +TCppFunction_t GetDefaultConstructor(compat::Interpreter& interp, + TCppScope_t scope) { + if (!HasDefaultConstructor(scope)) + return nullptr; + + auto* CXXRD = (clang::CXXRecordDecl*)scope; + return interp.getCI()->getSema().LookupDefaultConstructor(CXXRD); +} + +TCppFunction_t GetDefaultConstructor(TCppScope_t scope) { + return GetDefaultConstructor(getInterp(), scope); +} + +TCppFunction_t GetDestructor(TCppScope_t scope) { + auto* D = (clang::Decl*)scope; + + if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { + getSema().ForceDeclarationOfImplicitMembers(CXXRD); + return CXXRD->getDestructor(); + } + + return 0; +} + +void DumpScope(TCppScope_t scope) { + auto* D = (clang::Decl*)scope; + D->dump(); +} + +std::vector GetFunctionsUsingName(TCppScope_t scope, + const std::string& name) { + auto* D = (Decl*)scope; + + if (!scope || name.empty()) + return {}; + + D = GetUnderlyingScope(D); + + std::vector funcs; + llvm::StringRef Name(name); + auto& S = getSema(); + DeclarationName DName = &getASTContext().Idents.get(name); + clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, + For_Visible_Redeclaration); + + Cpp_utils::Lookup::Named(&S, R, Decl::castToDeclContext(D)); + + if (R.empty()) + return funcs; + + R.resolveKind(); + + for (auto* Found : R) + if (llvm::isa(Found)) + funcs.push_back(Found); + + return funcs; +} + +TCppType_t GetFunctionReturnType(TCppFunction_t func) { + auto* D = (clang::Decl*)func; + if (auto* FD = llvm::dyn_cast_or_null(D)) { + QualType Type = FD->getReturnType(); + if (Type->isUndeducedAutoType() && IsTemplatedFunction(FD) && + !FD->isDefined()) { +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&getInterp()); +#endif + getSema().InstantiateFunctionDefinition(SourceLocation(), FD, true, true); + Type = FD->getReturnType(); + } + return Type.getAsOpaquePtr(); + } + + if (auto* FD = llvm::dyn_cast_or_null(D)) + return (FD->getTemplatedDecl())->getReturnType().getAsOpaquePtr(); + + return 0; +} + +TCppIndex_t GetFunctionNumArgs(TCppFunction_t func) { + auto* D = (clang::Decl*)func; + if (auto* FD = llvm::dyn_cast_or_null(D)) + return FD->getNumParams(); + + if (auto* FD = llvm::dyn_cast_or_null(D)) + return (FD->getTemplatedDecl())->getNumParams(); + + return 0; +} + +TCppIndex_t GetFunctionRequiredArgs(TCppConstFunction_t func) { + const auto* D = static_cast(func); + if (auto* FD = llvm::dyn_cast_or_null(D)) + return FD->getMinRequiredArguments(); + + if (auto* FD = llvm::dyn_cast_or_null(D)) + return (FD->getTemplatedDecl())->getMinRequiredArguments(); + + return 0; +} + +TCppType_t GetFunctionArgType(TCppFunction_t func, TCppIndex_t iarg) { + auto* D = (clang::Decl*)func; + + if (auto* FD = llvm::dyn_cast_or_null(D)) { + if (iarg < FD->getNumParams()) { + auto* PVD = FD->getParamDecl(iarg); + return PVD->getOriginalType().getAsOpaquePtr(); + } + } + + return 0; +} + +std::string GetFunctionSignature(TCppFunction_t func) { + if (!func) + return ""; + + auto* D = (clang::Decl*)func; + clang::FunctionDecl* FD; + + if (llvm::dyn_cast(D)) + FD = llvm::dyn_cast(D); + else if (auto* FTD = llvm::dyn_cast(D)) + FD = FTD->getTemplatedDecl(); + else + return ""; + + std::string Signature; + raw_string_ostream SS(Signature); + PrintingPolicy Policy = getASTContext().getPrintingPolicy(); + // Skip printing the body + Policy.TerseOutput = true; + Policy.FullyQualifiedName = true; + Policy.SuppressDefaultTemplateArgs = false; + FD->print(SS, Policy); + SS.flush(); + return Signature; +} + +// Internal functions that are not needed outside the library are +// encompassed in an anonymous namespace as follows. +namespace { +bool IsTemplatedFunction(Decl* D) { + return llvm::isa_and_nonnull(D); +} + +bool IsTemplateInstantiationOrSpecialization(Decl* D) { + if (auto* FD = llvm::dyn_cast_or_null(D)) { + auto TK = FD->getTemplatedKind(); + return TK == + FunctionDecl::TemplatedKind::TK_FunctionTemplateSpecialization || + TK == FunctionDecl::TemplatedKind:: + TK_DependentFunctionTemplateSpecialization || + TK == FunctionDecl::TemplatedKind::TK_FunctionTemplate; + } + + return false; +} +} // namespace + +bool IsFunctionDeleted(TCppConstFunction_t function) { + const auto* FD = + cast(static_cast(function)); + return FD->isDeleted(); +} + +bool IsTemplatedFunction(TCppFunction_t func) { + auto* D = (Decl*)func; + return IsTemplatedFunction(D) || IsTemplateInstantiationOrSpecialization(D); +} + +// FIXME: This lookup is broken, and should no longer be used in favour of +// `GetClassTemplatedMethods` If the candidate set returned is =1, that means +// the template function exists and >1 means overloads +bool ExistsFunctionTemplate(const std::string& name, TCppScope_t parent) { + DeclContext* Within = 0; + if (parent) { + auto* D = (Decl*)parent; + Within = llvm::dyn_cast(D); + } + + auto* ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); + + if ((intptr_t)ND == (intptr_t)0) + return false; + + if ((intptr_t)ND != (intptr_t)-1) + return IsTemplatedFunction(ND) || + IsTemplateInstantiationOrSpecialization(ND); + + // FIXME: Cycle through the Decls and check if there is a templated function + return true; +} + +// Looks up all constructors in the current DeclContext +void LookupConstructors(const std::string& name, TCppScope_t parent, + std::vector& funcs) { + auto* D = (Decl*)parent; + + if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { + getSema().ForceDeclarationOfImplicitMembers(CXXRD); + DeclContextLookupResult Result = getSema().LookupConstructors(CXXRD); + // Obtaining all constructors when we intend to lookup a method under a + // scope can lead to crashes. We avoid that by accumulating constructors + // only if the Decl matches the lookup name. + for (auto* i : Result) + if (GetName(i) == name) + funcs.push_back(i); + } +} + +bool GetClassTemplatedMethods(const std::string& name, TCppScope_t parent, + std::vector& funcs) { + auto* D = (Decl*)parent; + if (!D && name.empty()) + return false; + + // Accumulate constructors + LookupConstructors(name, parent, funcs); + auto& S = getSema(); + D = GetUnderlyingScope(D); + llvm::StringRef Name(name); + DeclarationName DName = &getASTContext().Idents.get(name); + clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, + For_Visible_Redeclaration); + auto* DC = clang::Decl::castToDeclContext(D); + Cpp_utils::Lookup::Named(&S, R, DC); + + if (R.getResultKind() == clang::LookupResult::NotFound && funcs.empty()) + return false; + + // Distinct match, single Decl + else if (R.getResultKind() == clang::LookupResult::Found) { + if (IsTemplatedFunction(R.getFoundDecl())) + funcs.push_back(R.getFoundDecl()); + } + // Loop over overload set + else if (R.getResultKind() == clang::LookupResult::FoundOverloaded) { + for (auto* Found : R) + if (IsTemplatedFunction(Found)) + funcs.push_back(Found); + } + + // TODO: Handle ambiguously found LookupResult + // else if (R.getResultKind() == clang::LookupResult::Ambiguous) { + // auto kind = R.getAmbiguityKind(); + // ... + // Produce a diagnostic describing the ambiguity that resulted + // from name lookup as done in Sema::DiagnoseAmbiguousLookup + // + return !funcs.empty(); +} + +// Adapted from inner workings of Sema::BuildCallExpr +TCppFunction_t +BestOverloadFunctionMatch(const std::vector& candidates, + const std::vector& explicit_types, + const std::vector& arg_types) { + auto& S = getSema(); + auto& C = S.getASTContext(); + +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&getInterp()); +#endif + + // The overload resolution interfaces in Sema require a list of expressions. + // However, unlike handwritten C++, we do not always have a expression. + // Here we synthesize a placeholder expression to be able to use + // Sema::AddOverloadCandidate. Made up expressions are fine because the + // interface uses the list size and the expression types. + struct WrapperExpr : public OpaqueValueExpr { + WrapperExpr() : OpaqueValueExpr(clang::Stmt::EmptyShell()) {} + }; + auto* Exprs = new WrapperExpr[arg_types.size()]; + llvm::SmallVector Args; + Args.reserve(arg_types.size()); + size_t idx = 0; + for (auto i : arg_types) { + QualType Type = QualType::getFromOpaquePtr(i.m_Type); + ExprValueKind ExprKind = ExprValueKind::VK_PRValue; + if (Type->isReferenceType()) + ExprKind = ExprValueKind::VK_LValue; + + new (&Exprs[idx]) OpaqueValueExpr(SourceLocation::getFromRawEncoding(1), + Type.getNonReferenceType(), ExprKind); + Args.push_back(&Exprs[idx]); + ++idx; + } + + // Create a list of template arguments. + llvm::SmallVector TemplateArgs; + TemplateArgs.reserve(explicit_types.size()); + for (auto explicit_type : explicit_types) { + QualType ArgTy = QualType::getFromOpaquePtr(explicit_type.m_Type); + if (explicit_type.m_IntegralValue) { + // We have a non-type template parameter. Create an integral value from + // the string representation. + auto Res = llvm::APSInt(explicit_type.m_IntegralValue); + Res = Res.extOrTrunc(C.getIntWidth(ArgTy)); + TemplateArgs.push_back(TemplateArgument(C, Res, ArgTy)); + } else { + TemplateArgs.push_back(ArgTy); + } + } + + TemplateArgumentListInfo ExplicitTemplateArgs{}; + for (auto TA : TemplateArgs) + ExplicitTemplateArgs.addArgument( + S.getTrivialTemplateArgumentLoc(TA, QualType(), SourceLocation())); + + OverloadCandidateSet Overloads( + SourceLocation(), OverloadCandidateSet::CandidateSetKind::CSK_Normal); + + for (void* i : candidates) { + Decl* D = static_cast(i); + if (auto* FD = dyn_cast(D)) { + S.AddOverloadCandidate(FD, DeclAccessPair::make(FD, FD->getAccess()), + Args, Overloads); + } else if (auto* FTD = dyn_cast(D)) { + // AddTemplateOverloadCandidate is causing a memory leak + // It is a known bug at clang + // call stack: AddTemplateOverloadCandidate -> MakeDeductionFailureInfo + // source: + // https://github.com/llvm/llvm-project/blob/release/19.x/clang/lib/Sema/SemaOverload.cpp#L731-L756 + S.AddTemplateOverloadCandidate( + FTD, DeclAccessPair::make(FTD, FTD->getAccess()), + &ExplicitTemplateArgs, Args, Overloads); + } + } + + OverloadCandidateSet::iterator Best; + Overloads.BestViableFunction(S, SourceLocation(), Best); + + FunctionDecl* Result = Best != Overloads.end() ? Best->Function : nullptr; + delete[] Exprs; + return Result; +} + +// Gets the AccessSpecifier of the function and checks if it is equal to +// the provided AccessSpecifier. +bool CheckMethodAccess(TCppFunction_t method, AccessSpecifier AS) { + auto* D = (Decl*)method; + if (auto* CXXMD = llvm::dyn_cast_or_null(D)) { + return CXXMD->getAccess() == AS; + } + + return false; +} + +bool IsMethod(TCppConstFunction_t method) { + return dyn_cast_or_null( + static_cast(method)); +} + +bool IsPublicMethod(TCppFunction_t method) { + return CheckMethodAccess(method, AccessSpecifier::AS_public); +} + +bool IsProtectedMethod(TCppFunction_t method) { + return CheckMethodAccess(method, AccessSpecifier::AS_protected); +} + +bool IsPrivateMethod(TCppFunction_t method) { + return CheckMethodAccess(method, AccessSpecifier::AS_private); +} + +bool IsConstructor(TCppConstFunction_t method) { + const auto* D = static_cast(method); + if (const auto* FTD = dyn_cast(D)) + return IsConstructor(FTD->getTemplatedDecl()); + return llvm::isa_and_nonnull(D); +} + +bool IsDestructor(TCppConstFunction_t method) { + const auto* D = static_cast(method); + return llvm::isa_and_nonnull(D); +} + +bool IsStaticMethod(TCppConstFunction_t method) { + const auto* D = static_cast(method); + if (auto* CXXMD = llvm::dyn_cast_or_null(D)) { + return CXXMD->isStatic(); + } + + return false; +} + +TCppFuncAddr_t GetFunctionAddress(const char* mangled_name) { + auto& I = getInterp(); + auto FDAorErr = compat::getSymbolAddress(I, mangled_name); + if (llvm::Error Err = FDAorErr.takeError()) + llvm::consumeError(std::move(Err)); // nullptr if missing + else + return llvm::jitTargetAddressToPointer(*FDAorErr); + + return nullptr; +} + +TCppFuncAddr_t GetFunctionAddress(TCppFunction_t method) { + auto* D = (Decl*)method; + + const auto get_mangled_name = [](FunctionDecl* FD) { + auto MangleCtxt = getASTContext().createMangleContext(); + + if (!MangleCtxt->shouldMangleDeclName(FD)) { + return FD->getNameInfo().getName().getAsString(); + } + + std::string mangled_name; + llvm::raw_string_ostream ostream(mangled_name); + + MangleCtxt->mangleName(FD, ostream); + + ostream.flush(); + delete MangleCtxt; + + return mangled_name; + }; + + if (auto* FD = llvm::dyn_cast_or_null(D)) + return GetFunctionAddress(get_mangled_name(FD).c_str()); + + return 0; +} + +bool IsVirtualMethod(TCppFunction_t method) { + auto* D = (Decl*)method; + if (auto* CXXMD = llvm::dyn_cast_or_null(D)) { + return CXXMD->isVirtual(); + } + + return false; +} + +void GetDatamembers(TCppScope_t scope, std::vector& datamembers) { + auto* D = (Decl*)scope; + + if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { + getSema().ForceDeclarationOfImplicitMembers(CXXRD); + + llvm::SmallVector stack_begin; + llvm::SmallVector stack_end; + stack_begin.push_back(CXXRD->decls_begin()); + stack_end.push_back(CXXRD->decls_end()); + while (!stack_begin.empty()) { + if (stack_begin.back() == stack_end.back()) { + stack_begin.pop_back(); + stack_end.pop_back(); + continue; + } + Decl* D = *(stack_begin.back()); + if (auto* FD = llvm::dyn_cast(D)) { + if (FD->isAnonymousStructOrUnion()) { + if (const auto* RT = FD->getType()->getAs()) { + if (auto* CXXRD = llvm::dyn_cast(RT->getDecl())) { + stack_begin.back()++; + stack_begin.push_back(CXXRD->decls_begin()); + stack_end.push_back(CXXRD->decls_end()); + continue; + } + } + } + datamembers.push_back((TCppScope_t)D); + + } else if (auto* USD = llvm::dyn_cast(D)) { + if (llvm::isa(USD->getTargetDecl())) + datamembers.push_back(USD); + } + stack_begin.back()++; + } + } +} + +void GetStaticDatamembers(TCppScope_t scope, + std::vector& datamembers) { + GetClassDecls(scope, datamembers); +} + +void GetEnumConstantDatamembers(TCppScope_t scope, + std::vector& datamembers, + bool include_enum_class) { + std::vector EDs; + GetClassDecls(scope, EDs); + for (TCppScope_t i : EDs) { + auto* ED = static_cast(i); + + bool is_class_tagged = ED->isScopedUsingClassTag(); + if (is_class_tagged && !include_enum_class) + continue; + + std::copy(ED->enumerator_begin(), ED->enumerator_end(), + std::back_inserter(datamembers)); + } +} + +TCppScope_t LookupDatamember(const std::string& name, TCppScope_t parent) { + clang::DeclContext* Within = 0; + if (parent) { + auto* D = (clang::Decl*)parent; + Within = llvm::dyn_cast(D); + } + + auto* ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); + if (ND && ND != (clang::NamedDecl*)-1) { + if (llvm::isa_and_nonnull(ND)) { + return (TCppScope_t)ND; + } + } + + return 0; +} + +TCppType_t GetVariableType(TCppScope_t var) { + auto* D = static_cast(var); + + if (auto DD = llvm::dyn_cast_or_null(D)) { + QualType QT = DD->getType(); + + // Check if the type is a typedef type + if (QT->isTypedefNameType()) { + return QT.getAsOpaquePtr(); + } + + // Else, return the canonical type + QT = QT.getCanonicalType(); + return QT.getAsOpaquePtr(); + } + + if (auto* ECD = llvm::dyn_cast_or_null(D)) + return ECD->getType().getAsOpaquePtr(); + + return 0; +} + +intptr_t GetVariableOffset(compat::Interpreter& I, Decl* D, + CXXRecordDecl* BaseCXXRD) { + if (!D) + return 0; + + auto& C = I.getSema().getASTContext(); + + if (auto* FD = llvm::dyn_cast(D)) { + clang::RecordDecl* FieldParentRecordDecl = FD->getParent(); + intptr_t offset = C.toCharUnitsFromBits(C.getFieldOffset(FD)).getQuantity(); + while (FieldParentRecordDecl->isAnonymousStructOrUnion()) { + clang::RecordDecl* anon = FieldParentRecordDecl; + FieldParentRecordDecl = llvm::dyn_cast(anon->getParent()); + for (auto F = FieldParentRecordDecl->field_begin(); + F != FieldParentRecordDecl->field_end(); ++F) { + const auto* RT = F->getType()->getAs(); + if (!RT) + continue; + if (anon == RT->getDecl()) { + FD = *F; + break; + } + } + offset += C.toCharUnitsFromBits(C.getFieldOffset(FD)).getQuantity(); + } + if (BaseCXXRD && BaseCXXRD != FieldParentRecordDecl) { + // FieldDecl FD belongs to some class C, but the base class BaseCXXRD is + // not C. That means BaseCXXRD derives from C. Offset needs to be + // calculated for Derived class + + // Depth first Search is performed to the class that declares FD from + // the base class + std::vector stack; + std::map direction; + stack.push_back(BaseCXXRD); + while (!stack.empty()) { + CXXRecordDecl* RD = stack.back(); + stack.pop_back(); + size_t num_bases = GetNumBases(RD); + bool flag = false; + for (size_t i = 0; i < num_bases; i++) { + auto* CRD = static_cast(GetBaseClass(RD, i)); + direction[CRD] = RD; + if (CRD == FieldParentRecordDecl) { + flag = true; + break; + } + stack.push_back(CRD); + } + if (flag) + break; + } + if (auto* RD = llvm::dyn_cast(FieldParentRecordDecl)) { + // add in the offsets for the (multi level) base classes + while (BaseCXXRD != RD) { + CXXRecordDecl* Parent = direction.at(RD); + offset += + C.getASTRecordLayout(Parent).getBaseClassOffset(RD).getQuantity(); + RD = Parent; + } + } else { + assert(false && "Unreachable"); + } + } + return offset; + } + + if (auto* VD = llvm::dyn_cast(D)) { + auto GD = GlobalDecl(VD); + std::string mangledName; + compat::maybeMangleDeclName(GD, mangledName); + void* address = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol( + mangledName.c_str()); + + if (!address) + address = I.getAddressOfGlobal(GD); + if (!address) { + if (!VD->hasInit()) { +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&getInterp()); +#endif // CPPINTEROP_USE_CLING + getSema().InstantiateVariableDefinition(SourceLocation(), VD); + } + if (VD->hasInit() && + (VD->isConstexpr() || VD->getType().isConstQualified())) { + if (const APValue* val = VD->evaluateValue()) { + if (VD->getType()->isIntegralType(C)) { + return (intptr_t)val->getInt().getRawData(); + } + } + } + } + if (!address) { + auto Linkage = C.GetGVALinkageForVariable(VD); + // The decl was deferred by CodeGen. Force its emission. + // FIXME: In ASTContext::DeclMustBeEmitted we should check if the + // Decl::isUsed is set or we should be able to access CodeGen's + // addCompilerUsedGlobal. + if (isDiscardableGVALinkage(Linkage)) + VD->addAttr(UsedAttr::CreateImplicit(C)); +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&I); + I.getCI()->getASTConsumer().HandleTopLevelDecl(DeclGroupRef(VD)); +#else // CLANG_REPL + I.getCI()->getASTConsumer().HandleTopLevelDecl(DeclGroupRef(VD)); + // Take the newest llvm::Module produced by CodeGen and send it to JIT. + auto GeneratedPTU = I.Parse(""); + if (!GeneratedPTU) + llvm::logAllUnhandledErrors( + GeneratedPTU.takeError(), llvm::errs(), + "[GetVariableOffset] Failed to generate PTU:"); + + // From cling's BackendPasses.cpp + // FIXME: We need to upstream this code in IncrementalExecutor::addModule + for (auto& GV : GeneratedPTU->TheModule->globals()) { + llvm::GlobalValue::LinkageTypes LT = GV.getLinkage(); + if (GV.isDeclaration() || !GV.hasName() || + GV.getName().starts_with(".str") || !GV.isDiscardableIfUnused(LT) || + LT != llvm::GlobalValue::InternalLinkage) + continue; // nothing to do + GV.setLinkage(llvm::GlobalValue::WeakAnyLinkage); + } + if (auto Err = I.Execute(*GeneratedPTU)) + llvm::logAllUnhandledErrors( + std::move(Err), llvm::errs(), + "[GetVariableOffset] Failed to execute PTU:"); +#endif + } + auto VDAorErr = compat::getSymbolAddress(I, StringRef(mangledName)); + if (!VDAorErr) { + llvm::logAllUnhandledErrors(VDAorErr.takeError(), llvm::errs(), + "Failed to GetVariableOffset:"); + return 0; + } + return (intptr_t)jitTargetAddressToPointer(VDAorErr.get()); + } + + return 0; +} + +intptr_t GetVariableOffset(TCppScope_t var, TCppScope_t parent) { + auto* D = static_cast(var); + auto* RD = llvm::dyn_cast_or_null(static_cast(parent)); + return GetVariableOffset(getInterp(), D, RD); +} + +// Check if the Access Specifier of the variable matches the provided value. +bool CheckVariableAccess(TCppScope_t var, AccessSpecifier AS) { + auto* D = (Decl*)var; + return D->getAccess() == AS; +} + +bool IsPublicVariable(TCppScope_t var) { + return CheckVariableAccess(var, AccessSpecifier::AS_public); +} + +bool IsProtectedVariable(TCppScope_t var) { + return CheckVariableAccess(var, AccessSpecifier::AS_protected); +} + +bool IsPrivateVariable(TCppScope_t var) { + return CheckVariableAccess(var, AccessSpecifier::AS_private); +} + +bool IsStaticVariable(TCppScope_t var) { + auto* D = (Decl*)var; + if (llvm::isa_and_nonnull(D)) { + return true; + } + + return false; +} + +bool IsConstVariable(TCppScope_t var) { + auto* D = (clang::Decl*)var; + + if (auto* VD = llvm::dyn_cast_or_null(D)) { + return VD->getType().isConstQualified(); + } + + return false; +} + +bool IsRecordType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + return QT->isRecordType(); +} + +bool IsPODType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + + if (QT.isNull()) + return false; + + return QT.isPODType(getASTContext()); +} + +bool IsPointerType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + return QT->isPointerType(); +} + +TCppType_t GetPointeeType(TCppType_t type) { + if (!IsPointerType(type)) + return nullptr; + QualType QT = QualType::getFromOpaquePtr(type); + return QT->getPointeeType().getAsOpaquePtr(); +} + +bool IsReferenceType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + return QT->isReferenceType(); +} + +TCppType_t GetNonReferenceType(TCppType_t type) { + if (!IsReferenceType(type)) + return nullptr; + QualType QT = QualType::getFromOpaquePtr(type); + return QT.getNonReferenceType().getAsOpaquePtr(); +} + +TCppType_t GetUnderlyingType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + QT = QT->getCanonicalTypeUnqualified(); + + // Recursively remove array dimensions + while (QT->isArrayType()) + QT = QualType(QT->getArrayElementTypeNoTypeQual(), 0); + + // Recursively reduce pointer depth till we are left with a pointerless + // type. + for (auto PT = QT->getPointeeType(); !PT.isNull(); + PT = QT->getPointeeType()) { + QT = PT; + } + QT = QT->getCanonicalTypeUnqualified(); + return QT.getAsOpaquePtr(); +} + +std::string GetTypeAsString(TCppType_t var) { + QualType QT = QualType::getFromOpaquePtr(var); + // FIXME: Get the default printing policy from the ASTContext. + PrintingPolicy Policy((LangOptions())); + Policy.Bool = true; // Print bool instead of _Bool. + Policy.SuppressTagKeyword = true; // Do not print `class std::string`. + return compat::FixTypeName(QT.getAsString(Policy)); +} + +TCppType_t GetCanonicalType(TCppType_t type) { + if (!type) + return 0; + QualType QT = QualType::getFromOpaquePtr(type); + return QT.getCanonicalType().getAsOpaquePtr(); +} + +// Internal functions that are not needed outside the library are +// encompassed in an anonymous namespace as follows. This function converts +// from a string to the actual type. It is used in the GetType() function. +namespace { +static QualType findBuiltinType(llvm::StringRef typeName, ASTContext& Context) { + bool issigned = false; + bool isunsigned = false; + if (typeName.starts_with("signed ")) { + issigned = true; + typeName = StringRef(typeName.data() + 7, typeName.size() - 7); + } + if (!issigned && typeName.starts_with("unsigned ")) { + isunsigned = true; + typeName = StringRef(typeName.data() + 9, typeName.size() - 9); + } + if (typeName == "char") { + if (isunsigned) + return Context.UnsignedCharTy; + return Context.SignedCharTy; + } + if (typeName == "short") { + if (isunsigned) + return Context.UnsignedShortTy; + return Context.ShortTy; + } + if (typeName == "int") { + if (isunsigned) + return Context.UnsignedIntTy; + return Context.IntTy; + } + if (typeName == "long") { + if (isunsigned) + return Context.UnsignedLongTy; + return Context.LongTy; + } + if (typeName == "long long") { + if (isunsigned) + return Context.UnsignedLongLongTy; + return Context.LongLongTy; + } + if (!issigned && !isunsigned) { + if (typeName == "bool") + return Context.BoolTy; + if (typeName == "float") + return Context.FloatTy; + if (typeName == "double") + return Context.DoubleTy; + if (typeName == "long double") + return Context.LongDoubleTy; + + if (typeName == "wchar_t") + return Context.WCharTy; + if (typeName == "char16_t") + return Context.Char16Ty; + if (typeName == "char32_t") + return Context.Char32Ty; + } + /* Missing + CanQualType WideCharTy; // Same as WCharTy in C++, integer type in C99. + CanQualType WIntTy; // [C99 7.24.1], integer type unchanged by default + promotions. + */ + return QualType(); +} +} // namespace + +TCppType_t GetType(const std::string& name) { + QualType builtin = findBuiltinType(name, getASTContext()); + if (!builtin.isNull()) + return builtin.getAsOpaquePtr(); + + auto* D = (Decl*)GetNamed(name, /* Within= */ 0); + if (auto* TD = llvm::dyn_cast_or_null(D)) { + return QualType(TD->getTypeForDecl(), 0).getAsOpaquePtr(); + } + + return (TCppType_t)0; +} + +TCppType_t GetComplexType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + + return getASTContext().getComplexType(QT).getAsOpaquePtr(); +} + +TCppType_t GetTypeFromScope(TCppScope_t klass) { + if (!klass) + return 0; + + auto* D = (Decl*)klass; + ASTContext& C = getASTContext(); + + if (ValueDecl* VD = dyn_cast(D)) + return VD->getType().getAsOpaquePtr(); + + return C.getTypeDeclType(cast(D)).getAsOpaquePtr(); +} + +// Internal functions that are not needed outside the library are +// encompassed in an anonymous namespace as follows. +namespace { +static unsigned long long gWrapperSerial = 0LL; + +enum EReferenceType { kNotReference, kLValueReference, kRValueReference }; + +// Start of JitCall Helper Functions + +#define DEBUG_TYPE "jitcall" + +// FIXME: Use that routine throughout CallFunc's port in places such as +// make_narg_call. +static inline void indent(ostringstream& buf, int indent_level) { + static const std::string kIndentString(" "); + for (int i = 0; i < indent_level; ++i) + buf << kIndentString; +} + +void* compile_wrapper(compat::Interpreter& I, const std::string& wrapper_name, + const std::string& wrapper, + bool withAccessControl = true) { + LLVM_DEBUG(dbgs() << "Compiling '" << wrapper_name << "'\n"); + return I.compileFunction(wrapper_name, wrapper, false /*ifUnique*/, + withAccessControl); +} + +void get_type_as_string(QualType QT, std::string& type_name, ASTContext& C, + PrintingPolicy Policy) { + // TODO: Implement cling desugaring from utils::AST + // cling::utils::Transform::GetPartiallyDesugaredType() + if (!QT->isTypedefNameType() || QT->isBuiltinType()) + QT = QT.getDesugaredType(C); +#if CLANG_VERSION_MAJOR > 16 + Policy.SuppressElaboration = true; +#endif + Policy.FullyQualifiedName = true; + QT.getAsStringInternal(type_name, Policy); +} + +static void GetDeclName(const clang::Decl* D, ASTContext& Context, + std::string& name) { + // Helper to extract a fully qualified name from a Decl + PrintingPolicy Policy(Context.getPrintingPolicy()); + Policy.SuppressTagKeyword = true; + Policy.SuppressUnwrittenScope = true; + if (const TypeDecl* TD = dyn_cast(D)) { + // This is a class, struct, or union member. + QualType QT; + if (const TypedefDecl* Typedef = dyn_cast(TD)) { + // Handle the typedefs to anonymous types. + QT = Typedef->getTypeSourceInfo()->getType(); + } else + QT = {TD->getTypeForDecl(), 0}; + get_type_as_string(QT, name, Context, Policy); + } else if (const NamedDecl* ND = dyn_cast(D)) { + // This is a namespace member. + raw_string_ostream stream(name); + ND->getNameForDiagnostic(stream, Policy, /*Qualified=*/true); + stream.flush(); + } +} + +void collect_type_info(const FunctionDecl* FD, QualType& QT, + std::ostringstream& typedefbuf, + std::ostringstream& callbuf, std::string& type_name, + EReferenceType& refType, bool& isPointer, + int indent_level, bool forArgument) { + // + // Collect information about the type of a function parameter + // needed for building the wrapper function. + // + ASTContext& C = FD->getASTContext(); + PrintingPolicy Policy(C.getPrintingPolicy()); +#if CLANG_VERSION_MAJOR > 16 + Policy.SuppressElaboration = true; +#endif + refType = kNotReference; + if (QT->isRecordType() && forArgument) { + get_type_as_string(QT, type_name, C, Policy); + return; + } + if (QT->isFunctionPointerType()) { + std::string fp_typedef_name; + { + std::ostringstream nm; + nm << "FP" << gWrapperSerial++; + type_name = nm.str(); + raw_string_ostream OS(fp_typedef_name); + QT.print(OS, Policy, type_name); + OS.flush(); + } + + indent(typedefbuf, indent_level); + + typedefbuf << "typedef " << fp_typedef_name << ";\n"; + return; + } else if (QT->isMemberPointerType()) { + std::string mp_typedef_name; + { + std::ostringstream nm; + nm << "MP" << gWrapperSerial++; + type_name = nm.str(); + raw_string_ostream OS(mp_typedef_name); + QT.print(OS, Policy, type_name); + OS.flush(); + } + + indent(typedefbuf, indent_level); + + typedefbuf << "typedef " << mp_typedef_name << ";\n"; + return; + } else if (QT->isPointerType()) { + isPointer = true; + QT = cast(QT.getCanonicalType())->getPointeeType(); + } else if (QT->isReferenceType()) { + if (QT->isRValueReferenceType()) + refType = kRValueReference; + else + refType = kLValueReference; + QT = cast(QT.getCanonicalType())->getPointeeType(); + } + // Fall through for the array type to deal with reference/pointer ro array + // type. + if (QT->isArrayType()) { + std::string ar_typedef_name; + { + std::ostringstream ar; + ar << "AR" << gWrapperSerial++; + type_name = ar.str(); + raw_string_ostream OS(ar_typedef_name); + QT.print(OS, Policy, type_name); + OS.flush(); + } + indent(typedefbuf, indent_level); + typedefbuf << "typedef " << ar_typedef_name << ";\n"; + return; + } + get_type_as_string(QT, type_name, C, Policy); +} + +void make_narg_ctor(const FunctionDecl* FD, const unsigned N, + std::ostringstream& typedefbuf, std::ostringstream& callbuf, + const std::string& class_name, int indent_level) { + // Make a code string that follows this pattern: + // + // ClassName(args...) + // + + 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 << ' '; + } else { + callbuf << "\n"; + indent(callbuf, indent_level + 1); + } + } + 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 << ")"; +} + +const DeclContext* get_non_transparent_decl_context(const FunctionDecl* FD) { + auto* DC = FD->getDeclContext(); + while (DC->isTransparentContext()) { + DC = DC->getParent(); + assert(DC && "All transparent contexts should have a parent!"); + } + return DC; +} + +void make_narg_call(const FunctionDecl* FD, const std::string& return_type, + const unsigned N, std::ostringstream& typedefbuf, + std::ostringstream& callbuf, const std::string& class_name, + int indent_level) { + // + // Make a code string that follows this pattern: + // + // ((*)obj)->(*(*)args[i], ...) + // + + // Sometimes it's necessary that we cast the function we want to call + // first to its explicit function type before calling it. This is supposed + // to prevent that we accidentally ending up in a function that is not + // the one we're supposed to call here (e.g. because the C++ function + // lookup decides to take another function that better fits). This method + // has some problems, e.g. when we call a function with default arguments + // and we don't provide all arguments, we would fail with this pattern. + // Same applies with member methods which seem to cause parse failures + // even when we supply the object parameter. Therefore we only use it in + // cases where we know it works and set this variable to true when we do. + + // true if not a overloaded operators or the overloaded operator is call + // operator + bool op_flag = !FD->isOverloadedOperator() || + FD->getOverloadedOperator() == clang::OO_Call; + + bool ShouldCastFunction = + !isa(FD) && N == FD->getNumParams() && op_flag; + if (ShouldCastFunction) { + callbuf << "("; + callbuf << "("; + callbuf << return_type << " (&)"; + { + callbuf << "("; + for (unsigned i = 0U; i < N; ++i) { + if (i) { + callbuf << ','; + if (i % 2) { + callbuf << ' '; + } else { + callbuf << "\n"; + indent(callbuf, indent_level + 1); + } + } + const ParmVarDecl* PVD = FD->getParamDecl(i); + QualType Ty = PVD->getType(); + QualType QT = Ty.getCanonicalType(); + std::string arg_type; + ASTContext& C = FD->getASTContext(); + get_type_as_string(QT, arg_type, C, C.getPrintingPolicy()); + callbuf << arg_type; + } + if (FD->isVariadic()) + callbuf << ", ..."; + callbuf << ")"; + } + + callbuf << ")"; + } + + if (const CXXMethodDecl* MD = dyn_cast(FD)) { + // This is a class, struct, or union member. + if (MD->isConst()) + callbuf << "((const " << class_name << "*)obj)->"; + else + callbuf << "((" << class_name << "*)obj)->"; + + if (op_flag) + callbuf << class_name << "::"; + } else if (isa(get_non_transparent_decl_context(FD))) { + // This is a namespace member. + if (op_flag || N <= 1) + callbuf << class_name << "::"; + } + // callbuf << fMethod->Name() << "("; + { + std::string name; + { + std::string complete_name; + llvm::raw_string_ostream stream(complete_name); + FD->getNameForDiagnostic(stream, FD->getASTContext().getPrintingPolicy(), + /*Qualified=*/false); + + // insert space between template argument list and the function name + // this is require if the function is `operator<` + // `operator<` is invalid syntax + // whereas `operator< ` is valid + std::string simple_name = FD->getNameAsString(); + size_t idx = complete_name.find(simple_name, 0) + simple_name.size(); + std::string name_without_template_args = complete_name.substr(0, idx); + std::string template_args = complete_name.substr(idx); + name = name_without_template_args + + (template_args.empty() ? "" : " " + template_args); + } + if (op_flag || N <= 1) + callbuf << name; + } + if (ShouldCastFunction) + callbuf << ")"; + + 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) { + if (op_flag) { + callbuf << ", "; + } else { + callbuf << ' ' << Cpp::getOperatorSpelling(FD->getOverloadedOperator()) + << ' '; + } + } + + 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 { + // pointer falls back to non-pointer case; the argument preserves + // the "pointerness" (i.e. doesn't reference the value). + callbuf << "*(" << type_name.c_str() << "*)args[" << i << "]"; + } + } + callbuf << ")"; +} + +void make_narg_ctor_with_return(const FunctionDecl* FD, const unsigned N, + const std::string& class_name, + 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...); + // + { + std::ostringstream typedefbuf; + std::ostringstream callbuf; + // + // Write the return value assignment part. + // + indent(callbuf, indent_level); + callbuf << "(*(" << class_name << "**)ret) = "; + callbuf << "(obj) ? new (*(" << class_name << "**)ret) "; + make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level); + + callbuf << ": new "; + // + // Write the actual expression. + // + make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level); + // + // End the new expression statement. + // + callbuf << ";\n"; + // + // Output the whole new expression and return statement. + // + buf << typedefbuf.str() << callbuf.str(); + } +} + +void make_narg_call_with_return(compat::Interpreter& I, const FunctionDecl* FD, + const unsigned N, const std::string& class_name, + std::ostringstream& buf, int indent_level) { + // Make a code string that follows this pattern: + // + // if (ret) { + // new (ret) (return_type) ((class_name*)obj)->func(args...); + // } + // else { + // (void)(((class_name*)obj)->func(args...)); + // } + // + if (const CXXConstructorDecl* CD = dyn_cast(FD)) { + if (N <= 1 && llvm::isa(FD)) { + auto SpecMemKind = I.getCI()->getSema().getSpecialMember(CD); + if ((N == 0 && SpecMemKind == CXXSpecialMemberKindDefaultConstructor) || + (N == 1 && (SpecMemKind == CXXSpecialMemberKindCopyConstructor || + SpecMemKind == CXXSpecialMemberKindMoveConstructor))) { + // Using declarations cannot inject special members; do not call + // them as such. This might happen by using `Base(Base&, int = 12)`, + // which is fine to be called as `Derived d(someBase, 42)` but not + // as copy constructor of `Derived`. + return; + } + } + make_narg_ctor_with_return(FD, N, class_name, buf, indent_level); + return; + } + QualType QT = FD->getReturnType(); + if (QT->isVoidType()) { + std::ostringstream typedefbuf; + std::ostringstream callbuf; + indent(callbuf, indent_level); + make_narg_call(FD, "void", N, typedefbuf, callbuf, class_name, + indent_level); + callbuf << ";\n"; + indent(callbuf, indent_level); + callbuf << "return;\n"; + buf << typedefbuf.str() << callbuf.str(); + } else { + indent(buf, indent_level); + + std::string type_name; + EReferenceType refType = kNotReference; + bool isPointer = false; + + std::ostringstream typedefbuf; + std::ostringstream callbuf; + + collect_type_info(FD, QT, typedefbuf, callbuf, type_name, refType, + isPointer, indent_level, false); + + buf << typedefbuf.str(); + + buf << "if (ret) {\n"; + ++indent_level; + { + // + // Write the placement part of the placement new. + // + indent(callbuf, indent_level); + callbuf << "new (ret) "; + // + // Write the type part of the placement new. + // + callbuf << "(" << type_name.c_str(); + if (refType != kNotReference) { + callbuf << "*) (&"; + type_name += "&"; + } else if (isPointer) { + callbuf << "*) ("; + type_name += "*"; + } else { + callbuf << ") ("; + } + // + // Write the actual function call. + // + make_narg_call(FD, type_name, N, typedefbuf, callbuf, class_name, + indent_level); + // + // End the placement new. + // + callbuf << ");\n"; + indent(callbuf, indent_level); + callbuf << "return;\n"; + // + // Output the whole placement new expression and return statement. + // + buf << typedefbuf.str() << callbuf.str(); + } + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + indent(buf, indent_level); + buf << "else {\n"; + ++indent_level; + { + std::ostringstream typedefbuf; + std::ostringstream callbuf; + indent(callbuf, indent_level); + callbuf << "(void)("; + make_narg_call(FD, type_name, N, typedefbuf, callbuf, class_name, + indent_level); + callbuf << ");\n"; + indent(callbuf, indent_level); + callbuf << "return;\n"; + buf << typedefbuf.str() << callbuf.str(); + } + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + } +} + +int get_wrapper_code(compat::Interpreter& I, const FunctionDecl* FD, + std::string& wrapper_name, std::string& wrapper) { + assert(FD && "generate_wrapper called without a function decl!"); + ASTContext& Context = FD->getASTContext(); + // + // Get the class or namespace name. + // + std::string class_name; + const clang::DeclContext* DC = get_non_transparent_decl_context(FD); + GetDeclName(cast(DC), Context, class_name); + // + // Check to make sure that we can + // instantiate and codegen this function. + // + bool needInstantiation = false; + const FunctionDecl* Definition = 0; + if (!FD->isDefined(Definition)) { + FunctionDecl::TemplatedKind TK = FD->getTemplatedKind(); + switch (TK) { + case FunctionDecl::TK_NonTemplate: { + // Ordinary function, not a template specialization. + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + // llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << + // "Cannot make wrapper for a function which is " + // "declared but not defined!"; + // return 0; + } break; + case FunctionDecl::TK_FunctionTemplate: { + // This decl is actually a function template, + // not a function at all. + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a function template!"; + return 0; + } break; + case FunctionDecl::TK_MemberSpecialization: { + // This function is the result of instantiating an ordinary + // member function of a class template, or of instantiating + // an ordinary member function of a class member of a class + // template, or of specializing a member function template + // of a class template, or of specializing a member function + // template of a class member of a class template. + if (!FD->isTemplateInstantiation()) { + // We are either TSK_Undeclared or + // TSK_ExplicitSpecialization. + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + // llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << + // "Cannot make wrapper for a function template " + // "explicit specialization which is declared " + // "but not defined!"; + // return 0; + break; + } + const FunctionDecl* Pattern = FD->getTemplateInstantiationPattern(); + if (!Pattern) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a member function " + "instantiation with no pattern!"; + return 0; + } + FunctionDecl::TemplatedKind PTK = Pattern->getTemplatedKind(); + TemplateSpecializationKind PTSK = + Pattern->getTemplateSpecializationKind(); + if ( + // The pattern is an ordinary member function. + (PTK == FunctionDecl::TK_NonTemplate) || + // The pattern is an explicit specialization, and + // so is not a template. + ((PTK != FunctionDecl::TK_FunctionTemplate) && + ((PTSK == TSK_Undeclared) || + (PTSK == TSK_ExplicitSpecialization)))) { + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + break; + } else if (!Pattern->hasBody()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a member function " + "instantiation with no body!"; + return 0; + } + if (FD->isImplicitlyInstantiable()) { + needInstantiation = true; + } + } break; + case FunctionDecl::TK_FunctionTemplateSpecialization: { + // This function is the result of instantiating a function + // template or possibly an explicit specialization of a + // function template. Could be a namespace scope function or a + // member function. + if (!FD->isTemplateInstantiation()) { + // We are either TSK_Undeclared or + // TSK_ExplicitSpecialization. + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + // llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << + // "Cannot make wrapper for a function template " + // "explicit specialization which is declared " + // "but not defined!"; + // return 0; + break; + } + const FunctionDecl* Pattern = FD->getTemplateInstantiationPattern(); + if (!Pattern) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a function template" + "instantiation with no pattern!"; + return 0; + } + FunctionDecl::TemplatedKind PTK = Pattern->getTemplatedKind(); + TemplateSpecializationKind PTSK = + Pattern->getTemplateSpecializationKind(); + if ( + // The pattern is an ordinary member function. + (PTK == FunctionDecl::TK_NonTemplate) || + // The pattern is an explicit specialization, and + // so is not a template. + ((PTK != FunctionDecl::TK_FunctionTemplate) && + ((PTSK == TSK_Undeclared) || + (PTSK == TSK_ExplicitSpecialization)))) { + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + break; + } + if (!Pattern->hasBody()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a function template" + "instantiation with no body!"; + return 0; + } + if (FD->isImplicitlyInstantiable()) { + needInstantiation = true; + } + } break; + case FunctionDecl::TK_DependentFunctionTemplateSpecialization: { + // This function is the result of instantiating or + // specializing a member function of a class template, + // or a member function of a class member of a class template, + // or a member function template of a class template, or a + // member function template of a class member of a class + // template where at least some part of the function is + // dependent on a template argument. + if (!FD->isTemplateInstantiation()) { + // We are either TSK_Undeclared or + // TSK_ExplicitSpecialization. + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + // llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << + // "Cannot make wrapper for a dependent function " + // "template explicit specialization which is declared " + // "but not defined!"; + // return 0; + break; + } + const FunctionDecl* Pattern = FD->getTemplateInstantiationPattern(); + if (!Pattern) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a dependent function template" + "instantiation with no pattern!"; + return 0; + } + FunctionDecl::TemplatedKind PTK = Pattern->getTemplatedKind(); + TemplateSpecializationKind PTSK = + Pattern->getTemplateSpecializationKind(); + if ( + // The pattern is an ordinary member function. + (PTK == FunctionDecl::TK_NonTemplate) || + // The pattern is an explicit specialization, and + // so is not a template. + ((PTK != FunctionDecl::TK_FunctionTemplate) && + ((PTSK == TSK_Undeclared) || + (PTSK == TSK_ExplicitSpecialization)))) { + // Note: This might be ok, the body might be defined + // in a library, and all we have seen is the + // header file. + break; + } + if (!Pattern->hasBody()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a dependent function template" + "instantiation with no body!"; + return 0; + } + if (FD->isImplicitlyInstantiable()) { + needInstantiation = true; + } + } break; + default: { + // Will only happen if clang implementation changes. + // Protect ourselves in case that happens. + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Unhandled template kind!"; + return 0; + } break; + } + // We do not set needInstantiation to true in these cases: + // + // isInvalidDecl() + // TSK_Undeclared + // TSK_ExplicitInstantiationDefinition + // TSK_ExplicitSpecialization && !getClassScopeSpecializationPattern() + // TSK_ExplicitInstantiationDeclaration && + // getTemplateInstantiationPattern() && + // PatternDecl->hasBody() && + // !PatternDecl->isInlined() + // + // Set it true in these cases: + // + // TSK_ImplicitInstantiation + // TSK_ExplicitInstantiationDeclaration && (!getPatternDecl() || + // !PatternDecl->hasBody() || PatternDecl->isInlined()) + // + } + if (needInstantiation) { + clang::FunctionDecl* FDmod = const_cast(FD); + clang::Sema& S = I.getCI()->getSema(); + // Could trigger deserialization of decls. +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&I); +#endif + S.InstantiateFunctionDefinition(SourceLocation(), FDmod, + /*Recursive=*/true, + /*DefinitionRequired=*/true); + if (!FD->isDefined(Definition)) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Failed to force template instantiation!"; + return 0; + } + } + if (Definition) { + FunctionDecl::TemplatedKind TK = Definition->getTemplatedKind(); + switch (TK) { + case FunctionDecl::TK_NonTemplate: { + // Ordinary function, not a template specialization. + if (Definition->isDeleted()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a deleted function!"; + return 0; + } else if (Definition->isLateTemplateParsed()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a late template parsed " + "function!"; + return 0; + } + // else if (Definition->isDefaulted()) { + // // Might not have a body, but we can still use it. + //} + // else { + // // Has a body. + //} + } break; + case FunctionDecl::TK_FunctionTemplate: { + // This decl is actually a function template, + // not a function at all. + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a function template!"; + return 0; + } break; + case FunctionDecl::TK_MemberSpecialization: { + // This function is the result of instantiating an ordinary + // member function of a class template or of a member class + // of a class template. + if (Definition->isDeleted()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a deleted member function " + "of a specialization!"; + return 0; + } else if (Definition->isLateTemplateParsed()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a late template parsed " + "member function of a specialization!"; + return 0; + } + // else if (Definition->isDefaulted()) { + // // Might not have a body, but we can still use it. + //} + // else { + // // Has a body. + //} + } break; + case FunctionDecl::TK_FunctionTemplateSpecialization: { + // This function is the result of instantiating a function + // template or possibly an explicit specialization of a + // function template. Could be a namespace scope function or a + // member function. + if (Definition->isDeleted()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a deleted function " + "template specialization!"; + return 0; + } else if (Definition->isLateTemplateParsed()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a late template parsed " + "function template specialization!"; + return 0; + } + // else if (Definition->isDefaulted()) { + // // Might not have a body, but we can still use it. + //} + // else { + // // Has a body. + //} + } break; + case FunctionDecl::TK_DependentFunctionTemplateSpecialization: { + // This function is the result of instantiating or + // specializing a member function of a class template, + // or a member function of a class member of a class template, + // or a member function template of a class template, or a + // member function template of a class member of a class + // template where at least some part of the function is + // dependent on a template argument. + if (Definition->isDeleted()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a deleted dependent function " + "template specialization!"; + return 0; + } else if (Definition->isLateTemplateParsed()) { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Cannot make wrapper for a late template parsed " + "dependent function template specialization!"; + return 0; + } + // else if (Definition->isDefaulted()) { + // // Might not have a body, but we can still use it. + //} + // else { + // // Has a body. + //} + } break; + default: { + // Will only happen if clang implementation changes. + // Protect ourselves in case that happens. + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Unhandled template kind!"; + return 0; + } break; + } + } + unsigned min_args = FD->getMinRequiredArguments(); + unsigned num_params = FD->getNumParams(); + // + // Make the wrapper name. + // + { + std::ostringstream buf; + buf << "__cf"; + // const NamedDecl* ND = dyn_cast(FD); + // std::string mn; + // fInterp->maybeMangleDeclName(ND, mn); + // buf << '_' << mn; + buf << '_' << gWrapperSerial++; + wrapper_name = buf.str(); + } + // + // Write the wrapper code. + // FIXME: this should be synthesized into the AST! + // + int indent_level = 0; + std::ostringstream buf; + buf << "#pragma clang diagnostic push\n" + "#pragma clang diagnostic ignored \"-Wformat-security\"\n" + "__attribute__((used)) " + "__attribute__((annotate(\"__cling__ptrcheck(off)\")))\n" + "extern \"C\" void "; + buf << wrapper_name; + buf << "(void* obj, int nargs, void** args, void* ret)\n" + "{\n"; + ++indent_level; + if (min_args == num_params) { + // No parameters with defaults. + make_narg_call_with_return(I, FD, num_params, class_name, buf, + indent_level); + } else { + // We need one function call clause compiled for every + // possible number of arguments per call. + for (unsigned N = min_args; N <= num_params; ++N) { + indent(buf, indent_level); + buf << "if (nargs == " << N << ") {\n"; + ++indent_level; + make_narg_call_with_return(I, FD, N, class_name, buf, indent_level); + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + } + } + --indent_level; + buf << "}\n" + "#pragma clang diagnostic pop"; + wrapper = buf.str(); + return 1; +} + +JitCall::GenericCall make_wrapper(compat::Interpreter& I, + const FunctionDecl* FD) { + static std::map gWrapperStore; + + auto R = gWrapperStore.find(FD); + if (R != gWrapperStore.end()) + return (JitCall::GenericCall)R->second; + + std::string wrapper_name; + std::string wrapper_code; + + if (get_wrapper_code(I, FD, wrapper_name, wrapper_code) == 0) + return 0; + + // + // Compile the wrapper code. + // + bool withAccessControl = true; + // We should be able to call private default constructors. + if (auto Ctor = dyn_cast(FD)) + withAccessControl = !Ctor->isDefaultConstructor(); + void* wrapper = + compile_wrapper(I, wrapper_name, wrapper_code, withAccessControl); + if (wrapper) { + gWrapperStore.insert(std::make_pair(FD, wrapper)); + } else { + llvm::errs() << "TClingCallFunc::make_wrapper" + << ":" + << "Failed to compile\n" + << "==== SOURCE BEGIN ====\n" + << wrapper_code << "\n" + << "==== SOURCE END ====\n"; + } + LLVM_DEBUG(dbgs() << "Compiled '" << (wrapper ? "" : "un") + << "successfully:\n" + << wrapper_code << "'\n"); + return (JitCall::GenericCall)wrapper; +} + +// FIXME: Sink in the code duplication from get_wrapper_code. +static std::string PrepareStructorWrapper(const Decl* D, + const char* wrapper_prefix, + std::string& class_name) { + ASTContext& Context = D->getASTContext(); + GetDeclName(D, Context, class_name); + + // + // Make the wrapper name. + // + string wrapper_name; + { + ostringstream buf; + buf << wrapper_prefix; + // const NamedDecl* ND = dyn_cast(FD); + // string mn; + // fInterp->maybeMangleDeclName(ND, mn); + // buf << '_dtor_' << mn; + buf << '_' << gWrapperSerial++; + wrapper_name = buf.str(); + } + + return wrapper_name; +} + +static JitCall::DestructorCall make_dtor_wrapper(compat::Interpreter& interp, + const Decl* D) { + // Make a code string that follows this pattern: + // + // void + // unique_wrapper_ddd(void* obj, unsigned long nary, int withFree) + // { + // if (withFree) { + // if (!nary) { + // delete (ClassName*) obj; + // } + // else { + // delete[] (ClassName*) obj; + // } + // } + // else { + // typedef ClassName DtorName; + // if (!nary) { + // ((ClassName*)obj)->~DtorName(); + // } + // else { + // for (unsigned long i = nary - 1; i > -1; --i) { + // (((ClassName*)obj)+i)->~DtorName(); + // } + // } + // } + // } + // + //-- + + static map gDtorWrapperStore; + + auto I = gDtorWrapperStore.find(D); + if (I != gDtorWrapperStore.end()) + return (JitCall::DestructorCall)I->second; + + // + // Make the wrapper name. + // + std::string class_name; + string wrapper_name = PrepareStructorWrapper(D, "__dtor", class_name); + // + // Write the wrapper code. + // + int indent_level = 0; + ostringstream buf; + buf << "__attribute__((used)) "; + buf << "extern \"C\" void "; + buf << wrapper_name; + buf << "(void* obj, unsigned long nary, int withFree)\n"; + buf << "{\n"; + // if (withFree) { + // if (!nary) { + // delete (ClassName*) obj; + // } + // else { + // delete[] (ClassName*) obj; + // } + // } + ++indent_level; + indent(buf, indent_level); + buf << "if (withFree) {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "if (!nary) {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "delete (" << class_name << "*) obj;\n"; + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + indent(buf, indent_level); + buf << "else {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "delete[] (" << class_name << "*) obj;\n"; + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + // else { + // typedef ClassName Nm; + // if (!nary) { + // ((Nm*)obj)->~Nm(); + // } + // else { + // for (unsigned long i = nary - 1; i > -1; --i) { + // (((Nm*)obj)+i)->~Nm(); + // } + // } + // } + indent(buf, indent_level); + buf << "else {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "typedef " << class_name << " Nm;\n"; + buf << "if (!nary) {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "((Nm*)obj)->~Nm();\n"; + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + indent(buf, indent_level); + buf << "else {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "do {\n"; + ++indent_level; + indent(buf, indent_level); + buf << "(((Nm*)obj)+(--nary))->~Nm();\n"; + --indent_level; + indent(buf, indent_level); + buf << "} while (nary);\n"; + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + --indent_level; + indent(buf, indent_level); + buf << "}\n"; + // End wrapper. + --indent_level; + buf << "}\n"; + // Done. + string wrapper(buf.str()); + // fprintf(stderr, "%s\n", wrapper.c_str()); + // + // Compile the wrapper code. + // + void* F = compile_wrapper(interp, wrapper_name, wrapper, + /*withAccessControl=*/false); + if (F) { + gDtorWrapperStore.insert(make_pair(D, F)); + } else { + llvm::errs() << "make_dtor_wrapper" + << "Failed to compile\n" + << "==== SOURCE BEGIN ====\n" + << wrapper << "\n ==== SOURCE END ===="; + } + LLVM_DEBUG(dbgs() << "Compiled '" << (F ? "" : "un") << "successfully:\n" + << wrapper << "'\n"); + return (JitCall::DestructorCall)F; +} +#undef DEBUG_TYPE +} // namespace + // End of JitCall Helper Functions + +CPPINTEROP_API JitCall MakeFunctionCallable(TInterp_t I, + TCppConstFunction_t func) { + const auto* D = static_cast(func); + if (!D) + return {}; + + auto* interp = static_cast(I); + + // FIXME: Unify with make_wrapper. + if (const auto* Dtor = dyn_cast(D)) { + if (auto Wrapper = make_dtor_wrapper(*interp, Dtor->getParent())) + return {JitCall::kDestructorCall, Wrapper, Dtor}; + // FIXME: else error we failed to compile the wrapper. + return {}; + } + + if (auto Wrapper = make_wrapper(*interp, cast(D))) { + return {JitCall::kGenericCall, Wrapper, cast(D)}; + } + // FIXME: else error we failed to compile the wrapper. + return {}; +} + +CPPINTEROP_API JitCall MakeFunctionCallable(TCppConstFunction_t func) { + return MakeFunctionCallable(&getInterp(), func); +} + +namespace { +static std::string MakeResourcesPath() { + StringRef Dir; +#ifdef LLVM_BINARY_DIR + Dir = LLVM_BINARY_DIR; +#else + // Dir is bin/ or lib/, depending on where BinaryPath is. + void* MainAddr = (void*)(intptr_t)GetExecutablePath; + std::string BinaryPath = GetExecutablePath(/*Argv0=*/nullptr, MainAddr); + + // build/tools/clang/unittests/Interpreter/Executable -> build/ + StringRef Dir = sys::path::parent_path(BinaryPath); + + Dir = sys::path::parent_path(Dir); + Dir = sys::path::parent_path(Dir); + Dir = sys::path::parent_path(Dir); + Dir = sys::path::parent_path(Dir); + // Dir = sys::path::parent_path(Dir); +#endif // LLVM_BINARY_DIR + return compat::MakeResourceDir(Dir); +} +} // namespace + +TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, + const std::vector& GpuArgs /*={}*/) { + std::string MainExecutableName = sys::fs::getMainExecutable(nullptr, nullptr); + std::string ResourceDir = MakeResourcesPath(); + std::vector ClingArgv = {"-resource-dir", ResourceDir.c_str(), + "-std=c++14"}; + ClingArgv.insert(ClingArgv.begin(), MainExecutableName.c_str()); +#ifdef _WIN32 + // FIXME : Workaround Sema::PushDeclContext assert on windows + ClingArgv.push_back("-fno-delayed-template-parsing"); +#endif + ClingArgv.insert(ClingArgv.end(), Args.begin(), Args.end()); + // To keep the Interpreter creation interface between cling and clang-repl + // to some extent compatible we should put Args and GpuArgs together. On the + // receiving end we should check for -xcuda to know. + if (!GpuArgs.empty()) { + llvm::StringRef Arg0 = GpuArgs[0]; + Arg0 = Arg0.trim().ltrim('-'); + if (Arg0 != "cuda") { + llvm::errs() << "[CreateInterpreter]: Make sure --cuda is passed as the" + << " first argument of the GpuArgs\n"; + return nullptr; + } + } + ClingArgv.insert(ClingArgv.end(), GpuArgs.begin(), GpuArgs.end()); + + // Process externally passed arguments if present. + std::vector ExtraArgs; + auto EnvOpt = llvm::sys::Process::GetEnv("CPPINTEROP_EXTRA_INTERPRETER_ARGS"); + if (EnvOpt) { + StringRef Env(*EnvOpt); + while (!Env.empty()) { + StringRef Arg; + std::tie(Arg, Env) = Env.split(' '); + ExtraArgs.push_back(Arg.str()); + } + } + std::transform(ExtraArgs.begin(), ExtraArgs.end(), + std::back_inserter(ClingArgv), + [&](const std::string& str) { return str.c_str(); }); + +#ifdef CPPINTEROP_USE_CLING + auto I = new compat::Interpreter(ClingArgv.size(), &ClingArgv[0]); +#else + auto Interp = compat::Interpreter::create(static_cast(ClingArgv.size()), + ClingArgv.data()); + if (!Interp) + return nullptr; + auto* I = Interp.release(); +#endif + + // Honor -mllvm. + // + // FIXME: Remove this, one day. + // This should happen AFTER plugins have been loaded! + const CompilerInstance* Clang = I->getCI(); + if (!Clang->getFrontendOpts().LLVMArgs.empty()) { + unsigned NumArgs = Clang->getFrontendOpts().LLVMArgs.size(); + auto Args = std::make_unique(NumArgs + 2); + Args[0] = "clang (LLVM option parsing)"; + for (unsigned i = 0; i != NumArgs; ++i) + Args[i + 1] = Clang->getFrontendOpts().LLVMArgs[i].c_str(); + Args[NumArgs + 1] = nullptr; + llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get()); + } + // FIXME: Enable this assert once we figure out how to fix the multiple + // calls to CreateInterpreter. + // assert(!sInterpreter && "Interpreter already set."); + sInterpreter = I; + return I; +} + +TInterp_t GetInterpreter() { return sInterpreter; } + +void UseExternalInterpreter(TInterp_t I) { + assert(!sInterpreter && "sInterpreter already in use!"); + sInterpreter = static_cast(I); + OwningSInterpreter = false; +} + +void AddSearchPath(const char* dir, bool isUser, bool prepend) { + getInterp().getDynamicLibraryManager()->addSearchPath(dir, isUser, prepend); +} + +const char* GetResourceDir() { + return getInterp().getCI()->getHeaderSearchOpts().ResourceDir.c_str(); +} + +///\returns 0 on success. +static bool exec(const char* cmd, std::vector& outputs) { +#define DEBUG_TYPE "exec" + + std::array buffer; + struct file_deleter { + void operator()(FILE* fp) { pclose(fp); } + }; + std::unique_ptr pipe{popen(cmd, "r")}; + LLVM_DEBUG(dbgs() << "Executing command '" << cmd << "'\n"); + + if (!pipe) { + LLVM_DEBUG(dbgs() << "Execute failed!\n"); + perror("exec: "); + return false; + } + + LLVM_DEBUG(dbgs() << "Execute returned:\n"); + while (fgets(buffer.data(), static_cast(buffer.size()), pipe.get())) { + LLVM_DEBUG(dbgs() << buffer.data()); + llvm::StringRef trimmed = buffer.data(); + outputs.push_back(trimmed.trim().str()); + } + +#undef DEBUG_TYPE + + return true; +} + +std::string DetectResourceDir(const char* ClangBinaryName /* = clang */) { + std::string cmd = std::string(ClangBinaryName) + " -print-resource-dir"; + std::vector outs; + exec(cmd.c_str(), outs); + if (outs.empty() || outs.size() > 1) + return ""; + + std::string detected_resource_dir = outs.back(); + + std::string version = +#if CLANG_VERSION_MAJOR < 16 + CLANG_VERSION_STRING; +#else + CLANG_VERSION_MAJOR_STRING; +#endif + // We need to check if the detected resource directory is compatible. + if (llvm::sys::path::filename(detected_resource_dir) != version) + return ""; + + return detected_resource_dir; +} + +void DetectSystemCompilerIncludePaths(std::vector& Paths, + const char* CompilerName /*= "c++"*/) { + std::string cmd = "LC_ALL=C "; + cmd += CompilerName; + cmd += " -xc++ -E -v /dev/null 2>&1 | sed -n -e '/^.include/,${' -e '/^ " + "\\/.*/p' -e '}'"; + std::vector outs; + exec(cmd.c_str(), Paths); +} + +void AddIncludePath(const char* dir) { getInterp().AddIncludePath(dir); } + +void GetIncludePaths(std::vector& IncludePaths, bool withSystem, + bool withFlags) { + llvm::SmallVector paths(1); + getInterp().GetIncludePaths(paths, withSystem, withFlags); + for (auto& i : paths) + IncludePaths.push_back(i); +} + +namespace { + +class clangSilent { +public: + clangSilent(clang::DiagnosticsEngine& diag) : fDiagEngine(diag) { + fOldDiagValue = fDiagEngine.getSuppressAllDiagnostics(); + fDiagEngine.setSuppressAllDiagnostics(true); + } + + ~clangSilent() { fDiagEngine.setSuppressAllDiagnostics(fOldDiagValue); } + +protected: + clang::DiagnosticsEngine& fDiagEngine; + bool fOldDiagValue; +}; +} // namespace + +int Declare(compat::Interpreter& I, const char* code, bool silent) { + if (silent) { + clangSilent diagSuppr(I.getSema().getDiagnostics()); + return I.declare(code); + } + + return I.declare(code); +} + +int Declare(const char* code, bool silent) { + return Declare(getInterp(), code, silent); +} + +int Process(const char* code) { return getInterp().process(code); } + +intptr_t Evaluate(const char* code, bool* HadError /*=nullptr*/) { +#ifdef CPPINTEROP_USE_CLING + cling::Value V; +#else + clang::Value V; +#endif // CPPINTEROP_USE_CLING + + if (HadError) + *HadError = false; + + auto res = getInterp().evaluate(code, V); + if (res != 0) { // 0 is success + if (HadError) + *HadError = true; + // FIXME: Make this return llvm::Expected + return ~0UL; + } + + return compat::convertTo(V); +} + +std::string LookupLibrary(const char* lib_name) { + return getInterp().getDynamicLibraryManager()->lookupLibrary(lib_name); +} + +bool LoadLibrary(const char* lib_stem, bool lookup) { + compat::Interpreter::CompilationResult res = + getInterp().loadLibrary(lib_stem, lookup); + + return res == compat::Interpreter::kSuccess; +} + +void UnloadLibrary(const char* lib_stem) { + getInterp().getDynamicLibraryManager()->unloadLibrary(lib_stem); +} + +std::string SearchLibrariesForSymbol(const char* mangled_name, + bool search_system /*true*/) { + auto* DLM = getInterp().getDynamicLibraryManager(); + return DLM->searchLibrariesForSymbol(mangled_name, search_system); +} + +bool InsertOrReplaceJitSymbol(compat::Interpreter& I, + const char* linker_mangled_name, + uint64_t address) { + // FIXME: This approach is problematic since we could replace a symbol + // whose address was already taken by clients. + // + // A safer approach would be to define our symbol replacements early in the + // bootstrap process like: + // auto J = LLJITBuilder().create(); + // if (!J) + // return Err; + // + // if (Jupyter) { + // llvm::orc::SymbolMap Overrides; + // Overrides[J->mangleAndIntern("printf")] = + // { ExecutorAddr::fromPtr(&printf), JITSymbolFlags::Exported }; + // Overrides[...] = + // { ... }; + // if (auto Err = + // J->getProcessSymbolsJITDylib().define(absoluteSymbols(std::move(Overrides))) + // return Err; + // } + + // FIXME: If we still want to do symbol replacement we should use the + // ReplacementManager which is available in llvm 18. + using namespace llvm; + using namespace llvm::orc; + + auto Symbol = compat::getSymbolAddress(I, linker_mangled_name); + llvm::orc::LLJIT& Jit = *compat::getExecutionEngine(I); + llvm::orc::ExecutionSession& ES = Jit.getExecutionSession(); +#if CLANG_VERSION_MAJOR < 17 + JITDylib& DyLib = Jit.getMainJITDylib(); +#else + JITDylib& DyLib = *Jit.getProcessSymbolsJITDylib().get(); +#endif // CLANG_VERSION_MAJOR + + if (Error Err = Symbol.takeError()) { + logAllUnhandledErrors(std::move(Err), errs(), + "[InsertOrReplaceJitSymbol] error: "); +#define DEBUG_TYPE "orc" + LLVM_DEBUG(ES.dump(dbgs())); +#undef DEBUG_TYPE + return true; + } + + // Nothing to define, we are redefining the same function. + if (*Symbol && *Symbol == address) { + errs() << "[InsertOrReplaceJitSymbol] warning: redefining '" + << linker_mangled_name << "' with the same address\n"; + return true; + } + + // Let's inject it. + llvm::orc::SymbolMap InjectedSymbols; + auto& DL = compat::getExecutionEngine(I)->getDataLayout(); + char GlobalPrefix = DL.getGlobalPrefix(); + std::string tmp(linker_mangled_name); + if (GlobalPrefix != '\0') { + tmp = std::string(1, GlobalPrefix) + tmp; + } + auto Name = ES.intern(tmp); + InjectedSymbols[Name] = +#if CLANG_VERSION_MAJOR < 17 + JITEvaluatedSymbol(address, +#else + ExecutorSymbolDef(ExecutorAddr(address), +#endif // CLANG_VERSION_MAJOR < 17 + JITSymbolFlags::Exported); + + // We want to replace a symbol with a custom provided one. + if (Symbol && address) + // The symbol be in the DyLib or in-process. + if (auto Err = DyLib.remove({Name})) { + logAllUnhandledErrors(std::move(Err), errs(), + "[InsertOrReplaceJitSymbol] error: "); + return true; + } + + if (Error Err = DyLib.define(absoluteSymbols(InjectedSymbols))) { + logAllUnhandledErrors(std::move(Err), errs(), + "[InsertOrReplaceJitSymbol] error: "); + return true; + } + + return false; +} + +bool InsertOrReplaceJitSymbol(const char* linker_mangled_name, + uint64_t address) { + return InsertOrReplaceJitSymbol(getInterp(), linker_mangled_name, address); +} + +std::string ObjToString(const char* type, void* obj) { + return getInterp().toString(type, obj); +} + +static Decl* InstantiateTemplate(TemplateDecl* TemplateD, + TemplateArgumentListInfo& TLI, Sema& S) { + // This is not right but we don't have a lot of options to choose from as a + // template instantiation requires a valid source location. + SourceLocation fakeLoc = GetValidSLoc(S); + if (auto* FunctionTemplate = dyn_cast(TemplateD)) { + FunctionDecl* Specialization = nullptr; + clang::sema::TemplateDeductionInfo Info(fakeLoc); + Template_Deduction_Result Result = + S.DeduceTemplateArguments(FunctionTemplate, &TLI, Specialization, Info, + /*IsAddressOfFunction*/ true); + if (Result != Template_Deduction_Result_Success) { + // FIXME: Diagnose what happened. + (void)Result; + } + return Specialization; + } + + if (auto* VarTemplate = dyn_cast(TemplateD)) { + DeclResult R = S.CheckVarTemplateId(VarTemplate, fakeLoc, fakeLoc, TLI); + if (R.isInvalid()) { + // FIXME: Diagnose + } + return R.get(); + } + + // This will instantiate tape type and return it. + SourceLocation noLoc; + QualType TT = S.CheckTemplateIdType(TemplateName(TemplateD), noLoc, TLI); + + // Perhaps we can extract this into a new interface. + S.RequireCompleteType(fakeLoc, TT, diag::err_tentative_def_incomplete_type); + return GetScopeFromType(TT); + + // ASTContext &C = S.getASTContext(); + // // Get clad namespace and its identifier clad::. + // CXXScopeSpec CSS; + // CSS.Extend(C, GetCladNamespace(), noLoc, noLoc); + // NestedNameSpecifier* NS = CSS.getScopeRep(); + + // // Create elaborated type with namespace specifier, + // // i.e. class -> clad::class + // return C.getElaboratedType(ETK_None, NS, TT); +} + +Decl* InstantiateTemplate(TemplateDecl* TemplateD, + ArrayRef TemplateArgs, Sema& S) { + // Create a list of template arguments. + TemplateArgumentListInfo TLI{}; + for (auto TA : TemplateArgs) + TLI.addArgument( + S.getTrivialTemplateArgumentLoc(TA, QualType(), SourceLocation())); + + return InstantiateTemplate(TemplateD, TLI, S); +} + +TCppScope_t InstantiateTemplate(compat::Interpreter& I, TCppScope_t tmpl, + const TemplateArgInfo* template_args, + size_t template_args_size) { + auto& S = I.getSema(); + auto& C = S.getASTContext(); + + llvm::SmallVector TemplateArgs; + TemplateArgs.reserve(template_args_size); + for (size_t i = 0; i < template_args_size; ++i) { + QualType ArgTy = QualType::getFromOpaquePtr(template_args[i].m_Type); + if (template_args[i].m_IntegralValue) { + // We have a non-type template parameter. Create an integral value from + // the string representation. + auto Res = llvm::APSInt(template_args[i].m_IntegralValue); + Res = Res.extOrTrunc(C.getIntWidth(ArgTy)); + TemplateArgs.push_back(TemplateArgument(C, Res, ArgTy)); + } else { + TemplateArgs.push_back(ArgTy); + } + } + + TemplateDecl* TmplD = static_cast(tmpl); + + // We will create a new decl, push a transaction. +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&I); +#endif + return InstantiateTemplate(TmplD, TemplateArgs, S); +} + +TCppScope_t InstantiateTemplate(TCppScope_t tmpl, + const TemplateArgInfo* template_args, + size_t template_args_size) { + return InstantiateTemplate(getInterp(), tmpl, template_args, + template_args_size); +} + +void GetClassTemplateInstantiationArgs(TCppScope_t templ_instance, + std::vector& args) { + auto* CTSD = static_cast(templ_instance); + for (const auto& TA : CTSD->getTemplateInstantiationArgs().asArray()) { + switch (TA.getKind()) { + default: + assert(0 && "Not yet supported!"); + break; + case TemplateArgument::Pack: + for (auto SubTA : TA.pack_elements()) + args.push_back({SubTA.getAsType().getAsOpaquePtr()}); + break; + case TemplateArgument::Integral: + // FIXME: Support this case where the problem is where we provide the + // storage for the m_IntegralValue. + // llvm::APSInt Val = TA.getAsIntegral(); + // args.push_back({TA.getIntegralType(), TA.getAsIntegral()}) + // break; + case TemplateArgument::Type: + args.push_back({TA.getAsType().getAsOpaquePtr()}); + } + } +} + +TCppFunction_t +InstantiateTemplateFunctionFromString(const char* function_template) { + // FIXME: Drop this interface and replace it with the proper overload + // resolution handling and template instantiation selection. + + // Try to force template instantiation and overload resolution. + static unsigned long long var_count = 0; + std::string id = "__Cppyy_GetMethTmpl_" + std::to_string(var_count++); + std::string instance = "auto " + id + " = " + function_template + ";\n"; + + if (!Cpp::Declare(instance.c_str(), /*silent=*/false)) { + VarDecl* VD = (VarDecl*)Cpp::GetNamed(id, 0); + DeclRefExpr* DRE = (DeclRefExpr*)VD->getInit()->IgnoreImpCasts(); + return DRE->getDecl(); + } + return nullptr; +} + +void GetAllCppNames(TCppScope_t scope, std::set& names) { + auto* D = (clang::Decl*)scope; + clang::DeclContext* DC; + clang::DeclContext::decl_iterator decl; + + if (auto* TD = dyn_cast_or_null(D)) { + DC = clang::TagDecl::castToDeclContext(TD); + decl = DC->decls_begin(); + decl++; + } else if (auto* ND = dyn_cast_or_null(D)) { + DC = clang::NamespaceDecl::castToDeclContext(ND); + decl = DC->decls_begin(); + } else if (auto* TUD = dyn_cast_or_null(D)) { + DC = clang::TranslationUnitDecl::castToDeclContext(TUD); + decl = DC->decls_begin(); + } else { + return; + } + + for (/* decl set above */; decl != DC->decls_end(); decl++) { + if (auto* ND = llvm::dyn_cast_or_null(*decl)) { + names.insert(ND->getNameAsString()); + } + } +} + +void GetEnums(TCppScope_t scope, std::vector& Result) { + auto* D = static_cast(scope); + + if (!llvm::isa_and_nonnull(D)) + return; + + auto* DC = llvm::dyn_cast(D); + + llvm::SmallVector DCs; + DC->collectAllContexts(DCs); + + // FIXME: We should use a lookup based approach instead of brute force + for (auto* DC : DCs) { + for (auto decl = DC->decls_begin(); decl != DC->decls_end(); decl++) { + if (auto* ND = llvm::dyn_cast_or_null(*decl)) { + Result.push_back(ND->getNameAsString()); + } + } + } +} + +// FIXME: On the CPyCppyy side the receiver is of type +// vector instead of vector +std::vector GetDimensions(TCppType_t type) { + QualType Qual = QualType::getFromOpaquePtr(type); + if (Qual.isNull()) + return {}; + Qual = Qual.getCanonicalType(); + std::vector dims; + if (Qual->isArrayType()) { + const clang::ArrayType* ArrayType = + dyn_cast(Qual.getTypePtr()); + while (ArrayType) { + if (const auto* CAT = dyn_cast_or_null(ArrayType)) { + llvm::APSInt Size(CAT->getSize()); + long int ArraySize = Size.getLimitedValue(); + dims.push_back(ArraySize); + } else /* VariableArrayType, DependentSizedArrayType, IncompleteArrayType + */ + { + dims.push_back(DimensionValue::UNKNOWN_SIZE); + } + ArrayType = ArrayType->getElementType()->getAsArrayTypeUnsafe(); + } + return dims; + } + return dims; +} + +bool IsTypeDerivedFrom(TCppType_t derived, TCppType_t base) { + auto& S = getSema(); + auto fakeLoc = GetValidSLoc(S); + auto derivedType = clang::QualType::getFromOpaquePtr(derived); + auto baseType = clang::QualType::getFromOpaquePtr(base); + +#ifdef CPPINTEROP_USE_CLING + cling::Interpreter::PushTransactionRAII RAII(&getInterp()); +#endif + return S.IsDerivedFrom(fakeLoc, derivedType, baseType); +} + +std::string GetFunctionArgDefault(TCppFunction_t func, + TCppIndex_t param_index) { + auto* D = (clang::Decl*)func; + clang::ParmVarDecl* PI = nullptr; + + if (auto* FD = llvm::dyn_cast_or_null(D)) + PI = FD->getParamDecl(param_index); + + else if (auto* FD = llvm::dyn_cast_or_null(D)) + PI = (FD->getTemplatedDecl())->getParamDecl(param_index); + + if (PI->hasDefaultArg()) { + std::string Result; + llvm::raw_string_ostream OS(Result); + Expr* DefaultArgExpr = const_cast(PI->getDefaultArg()); + DefaultArgExpr->printPretty(OS, nullptr, PrintingPolicy(LangOptions())); + + // FIXME: Floats are printed in clang with the precision of their underlying + // representation and not as written. This is a deficiency in the printing + // mechanism of clang which we require extra work to mitigate. For example + // float PI = 3.14 is printed as 3.1400000000000001 + if (PI->getType()->isFloatingType()) { + if (!Result.empty() && Result.back() == '.') + return Result; + auto DefaultArgValue = std::stod(Result); + std::ostringstream oss; + oss << DefaultArgValue; + Result = oss.str(); + } + return Result; + } + return ""; +} + +bool IsConstMethod(TCppFunction_t method) { + if (!method) + return false; + + auto* D = (clang::Decl*)method; + if (auto* func = dyn_cast(D)) + return func->getMethodQualifiers().hasConst(); + + return false; +} + +std::string GetFunctionArgName(TCppFunction_t func, TCppIndex_t param_index) { + auto* D = (clang::Decl*)func; + clang::ParmVarDecl* PI = nullptr; + + if (auto* FD = llvm::dyn_cast_or_null(D)) + PI = FD->getParamDecl(param_index); + else if (auto* FD = llvm::dyn_cast_or_null(D)) + PI = (FD->getTemplatedDecl())->getParamDecl(param_index); + + return PI->getNameAsString(); +} + +OperatorArity GetOperatorArity(TCppFunction_t op) { + Decl* D = static_cast(op); + if (auto* FD = llvm::dyn_cast(D)) { + if (FD->isOverloadedOperator()) { + switch (FD->getOverloadedOperator()) { +#define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, MemberOnly) \ + case OO_##Name: \ + if ((Unary) && (Binary)) \ + return kBoth; \ + if (Unary) \ + return kUnary; \ + if (Binary) \ + return kBinary; \ + break; +#include "clang/Basic/OperatorKinds.def" + default: + break; + } + } + } + return (OperatorArity)~0U; +} + +void GetOperator(TCppScope_t scope, Operator op, + std::vector& operators, OperatorArity kind) { + Decl* D = static_cast(scope); + if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { + auto fn = [&operators, kind, op](const RecordDecl* RD) { + ASTContext& C = RD->getASTContext(); + DeclContextLookupResult Result = + RD->lookup(C.DeclarationNames.getCXXOperatorName( + (clang::OverloadedOperatorKind)op)); + for (auto* i : Result) { + if (kind & GetOperatorArity(i)) + operators.push_back(i); + } + return true; + }; + fn(CXXRD); + CXXRD->forallBases(fn); + } else if (auto* DC = llvm::dyn_cast_or_null(D)) { + ASTContext& C = getSema().getASTContext(); + DeclContextLookupResult Result = + DC->lookup(C.DeclarationNames.getCXXOperatorName( + (clang::OverloadedOperatorKind)op)); + + for (auto* i : Result) { + if (kind & GetOperatorArity(i)) + operators.push_back(i); + } + } +} + +TCppObject_t Allocate(TCppScope_t scope) { + return (TCppObject_t)::operator new(Cpp::SizeOf(scope)); +} + +void Deallocate(TCppScope_t scope, TCppObject_t address) { + ::operator delete(address); +} + +// FIXME: Add optional arguments to the operator new. +TCppObject_t Construct(compat::Interpreter& interp, TCppScope_t scope, + void* arena /*=nullptr*/) { + auto* Class = (Decl*)scope; + // FIXME: Diagnose. + if (!HasDefaultConstructor(Class)) + return nullptr; + + 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. + return arena; + } + + void* obj = nullptr; + JC.Invoke(&obj); + return obj; + } + return nullptr; +} + +TCppObject_t Construct(TCppScope_t scope, void* arena /*=nullptr*/) { + return Construct(getInterp(), scope, arena); +} + +void Destruct(compat::Interpreter& interp, TCppObject_t This, Decl* Class, + bool withFree) { + if (auto wrapper = make_dtor_wrapper(interp, Class)) { + (*wrapper)(This, /*nary=*/0, withFree); + return; + } + // FIXME: Diagnose. +} + +void Destruct(TCppObject_t This, TCppScope_t scope, bool withFree /*=true*/) { + auto* Class = static_cast(scope); + Destruct(getInterp(), This, Class, withFree); +} + +class StreamCaptureInfo { + struct file_deleter { + void operator()(FILE* fp) { pclose(fp); } + }; + std::unique_ptr m_TempFile; + int m_FD = -1; + int m_DupFD = -1; + +public: +#ifdef _MSC_VER + StreamCaptureInfo(int FD) + : m_TempFile{[]() { + FILE* stream = nullptr; + errno_t err; + err = tmpfile_s(&stream); + if (err) + printf("Cannot create temporary file!\n"); + return stream; + }()}, + m_FD(FD) { +#else + StreamCaptureInfo(int FD) : m_TempFile{tmpfile()}, m_FD(FD) { +#endif + if (!m_TempFile) { + perror("StreamCaptureInfo: Unable to create temp file"); + return; + } + + m_DupFD = dup(FD); + + // Flush now or can drop the buffer when dup2 is called with Fd later. + // This seems only necessary when piping stdout or stderr, but do it + // for ttys to avoid over complicated code for minimal benefit. + ::fflush(FD == STDOUT_FILENO ? stdout : stderr); + if (dup2(fileno(m_TempFile.get()), FD) < 0) + perror("StreamCaptureInfo:"); + } + StreamCaptureInfo(const StreamCaptureInfo&) = delete; + StreamCaptureInfo& operator=(const StreamCaptureInfo&) = delete; + StreamCaptureInfo(StreamCaptureInfo&&) = delete; + StreamCaptureInfo& operator=(StreamCaptureInfo&&) = delete; + + ~StreamCaptureInfo() { assert(m_DupFD == -1 && "Captured output not used?"); } + + std::string GetCapturedString() { + assert(m_DupFD != -1 && "Multiple calls to GetCapturedString"); + + fflush(nullptr); + if (dup2(m_DupFD, m_FD) < 0) + perror("StreamCaptureInfo:"); + // Go to the end of the file. + if (fseek(m_TempFile.get(), 0L, SEEK_END) != 0) + perror("StreamCaptureInfo:"); + + // Get the size of the file. + long bufsize = ftell(m_TempFile.get()); + if (bufsize == -1) + perror("StreamCaptureInfo:"); + + // Allocate our buffer to that size. + std::unique_ptr content(new char[bufsize + 1]); + + // Go back to the start of the file. + if (fseek(m_TempFile.get(), 0L, SEEK_SET) != 0) + perror("StreamCaptureInfo:"); + + // Read the entire file into memory. + size_t newLen = + fread(content.get(), sizeof(char), bufsize, m_TempFile.get()); + if (ferror(m_TempFile.get()) != 0) + fputs("Error reading file", stderr); + else + content[newLen++] = '\0'; // Just to be safe. + + std::string result = content.get(); + close(m_DupFD); + m_DupFD = -1; + return result; + } +}; + +static std::stack& GetRedirectionStack() { + static std::stack sRedirectionStack; + return sRedirectionStack; +} + +void BeginStdStreamCapture(CaptureStreamKind fd_kind) { + GetRedirectionStack().emplace((int)fd_kind); +} + +std::string EndStdStreamCapture() { + assert(GetRedirectionStack().size()); + StreamCaptureInfo& SCI = GetRedirectionStack().top(); + std::string result = SCI.GetCapturedString(); + GetRedirectionStack().pop(); + return result; +} + +void CodeComplete(std::vector& Results, const char* code, + unsigned complete_line /* = 1U */, + unsigned complete_column /* = 1U */) { + compat::codeComplete(Results, getInterp(), code, complete_line, + complete_column); +} + +int Undo(unsigned N) { +#ifdef CPPINTEROP_USE_CLING + auto& I = getInterp(); + cling::Interpreter::PushTransactionRAII RAII(&I); + I.unload(N); + return compat::Interpreter::kSuccess; +#else + return getInterp().undo(N); +#endif +} + +} // end namespace Cpp diff --git a/lib/Interpreter/CppInterOpInterpreter.h b/lib/CppInterOp/CppInterOpInterpreter.h similarity index 100% rename from lib/Interpreter/CppInterOpInterpreter.h rename to lib/CppInterOp/CppInterOpInterpreter.h diff --git a/lib/CppInterOp/DynamicLibraryManager.cpp b/lib/CppInterOp/DynamicLibraryManager.cpp new file mode 100644 index 000000000..2a90300e4 --- /dev/null +++ b/lib/CppInterOp/DynamicLibraryManager.cpp @@ -0,0 +1,506 @@ +//------------------------------------------------------------------------------ +// CLING - the C++ LLVM-based InterpreterG :) +// author: Vassil Vassilev +// +// This file is dual-licensed: you can choose to license it under the University +// of Illinois Open Source License or the GNU Lesser General Public License. See +// LICENSE.TXT for details. +//------------------------------------------------------------------------------ + +#include "DynamicLibraryManager.h" +#include "Compatibility.h" +#include "Paths.h" + +#include "llvm/ADT/StringSet.h" +#include "llvm/BinaryFormat/Magic.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +#if defined(_WIN32) +#include "llvm/BinaryFormat/COFF.h" +#include "llvm/Support/Endian.h" +#endif + +#include +#include +#include + +namespace Cpp { + +using namespace Cpp::utils::platform; +using namespace Cpp::utils; +using namespace llvm; + +DynamicLibraryManager::DynamicLibraryManager() { + const SmallVector kSysLibraryEnv = { + "LD_LIBRARY_PATH", +#if __APPLE__ + "DYLD_LIBRARY_PATH", + "DYLD_FALLBACK_LIBRARY_PATH", + /* + "DYLD_VERSIONED_LIBRARY_PATH", + "DYLD_FRAMEWORK_PATH", + "DYLD_FALLBACK_FRAMEWORK_PATH", + "DYLD_VERSIONED_FRAMEWORK_PATH", + */ +#elif defined(_WIN32) + "PATH", +#endif + }; + + // Behaviour is to not add paths that don't exist...In an interpreted env + // does this make sense? Path could pop into existence at any time. + for (const char* Var : kSysLibraryEnv) { + if (const char* Env = GetEnv(Var)) { + SmallVector CurPaths; + SplitPaths(Env, CurPaths, utils::kPruneNonExistent, + Cpp::utils::platform::kEnvDelim); + for (const auto& Path : CurPaths) + addSearchPath(Path); + } + } + + // $CWD is the last user path searched. + addSearchPath("."); + + SmallVector SysPaths; + Cpp::utils::platform::GetSystemLibraryPaths(SysPaths); + + for (const std::string& P : SysPaths) + addSearchPath(P, /*IsUser*/ false); +} +///\returns substitution of pattern in the front of original with replacement +/// Example: substFront("@rpath/abc", "@rpath/", "/tmp") -> "/tmp/abc" +static std::string substFront(StringRef original, StringRef pattern, + StringRef replacement) { + if (!original.starts_with_insensitive(pattern)) + return original.str(); + SmallString<512> result(replacement); + result.append(original.drop_front(pattern.size())); + return result.str().str(); +} + +///\returns substitution of all known linker variables in \c original +static std::string substAll(StringRef original, StringRef libLoader) { + + // Handle substitutions (MacOS): + // @rpath - This function does not substitute @rpath, because + // this variable is already handled by lookupLibrary where + // @rpath is replaced with all paths from RPATH one by one. + // @executable_path - Main program path. + // @loader_path - Loader library (or main program) path. + // + // Handle substitutions (Linux): + // https://man7.org/linux/man-pages/man8/ld.so.8.html + // $origin - Loader library (or main program) path. + // $lib - lib lib64 + // $platform - x86_64 AT_PLATFORM + + std::string result; +#ifdef __APPLE__ + SmallString<512> mainExecutablePath( + llvm::sys::fs::getMainExecutable(nullptr, nullptr)); + llvm::sys::path::remove_filename(mainExecutablePath); + SmallString<512> loaderPath; + if (libLoader.empty()) { + loaderPath = mainExecutablePath; + } else { + loaderPath = libLoader.str(); + llvm::sys::path::remove_filename(loaderPath); + } + + result = substFront(original, "@executable_path", mainExecutablePath); + result = substFront(result, "@loader_path", loaderPath); + return result; +#else + SmallString<512> loaderPath; + if (libLoader.empty()) { + loaderPath = llvm::sys::fs::getMainExecutable(nullptr, nullptr); + } else { + loaderPath = libLoader.str(); + } + llvm::sys::path::remove_filename(loaderPath); + + result = substFront(original, "$origin", loaderPath); + // result = substFront(result, "$lib", true?"lib":"lib64"); + // result = substFront(result, "$platform", "x86_64"); + return result; +#endif +} + +std::string DynamicLibraryManager::lookupLibInPaths( + StringRef libStem, SmallVector RPath /*={}*/, + SmallVector RunPath /*={}*/, + StringRef libLoader /*=""*/) const { +#define DEBUG_TYPE "Dyld::lookupLibInPaths" + + LLVM_DEBUG(dbgs() << "Dyld::lookupLibInPaths" << libStem.str() + << ", ..., libLoader=" << libLoader << "\n"); + + // Lookup priority is: RPATH, LD_LIBRARY_PATH/m_SearchPaths, RUNPATH + // See: https://en.wikipedia.org/wiki/Rpath + // See: https://amir.rachum.com/blog/2016/09/17/shared-libraries/ + + LLVM_DEBUG(dbgs() << "Dyld::lookupLibInPaths: \n"); + LLVM_DEBUG(dbgs() << ":: RPATH\n"); + for (auto Info : RPath) { + LLVM_DEBUG(dbgs() << ":::: " << Info.str() << "\n"); + } + LLVM_DEBUG(dbgs() << ":: SearchPaths (LD_LIBRARY_PATH, etc...)\n"); + for (auto Info : getSearchPaths()) { + LLVM_DEBUG(dbgs() << ":::: " << Info.Path + << ", user=" << (Info.IsUser ? "true" : "false") << "\n"); + } + LLVM_DEBUG(dbgs() << ":: RUNPATH\n"); + for (auto Info : RunPath) { + LLVM_DEBUG(dbgs() << ":::: " << Info.str() << "\n"); + } + + SmallString<512> ThisPath; + // RPATH + for (auto Info : RPath) { + ThisPath = substAll(Info, libLoader); + llvm::sys::path::append(ThisPath, libStem); + // to absolute path? + LLVM_DEBUG(dbgs() << "## Try: " << ThisPath); + if (isSharedLibrary(ThisPath.str())) { + LLVM_DEBUG(dbgs() << " ... Found (in RPATH)!\n"); + return ThisPath.str().str(); + } + } + // m_SearchPaths + for (const SearchPathInfo& Info : m_SearchPaths) { + ThisPath = Info.Path; + llvm::sys::path::append(ThisPath, libStem); + // to absolute path? + LLVM_DEBUG(dbgs() << "## Try: " << ThisPath); + if (isSharedLibrary(ThisPath.str())) { + LLVM_DEBUG(dbgs() << " ... Found (in SearchPaths)!\n"); + return ThisPath.str().str(); + } + } + // RUNPATH + for (auto Info : RunPath) { + ThisPath = substAll(Info, libLoader); + llvm::sys::path::append(ThisPath, libStem); + // to absolute path? + LLVM_DEBUG(dbgs() << "## Try: " << ThisPath); + if (isSharedLibrary(ThisPath.str())) { + LLVM_DEBUG(dbgs() << " ... Found (in RUNPATH)!\n"); + return ThisPath.str().str(); + } + } + + LLVM_DEBUG(dbgs() << "## NotFound!!!\n"); + + return ""; + +#undef DEBUG_TYPE +} + +std::string DynamicLibraryManager::lookupLibMaybeAddExt( + StringRef libStem, SmallVector RPath /*={}*/, + SmallVector RunPath /*={}*/, + StringRef libLoader /*=""*/) const { +#define DEBUG_TYPE "Dyld::lookupLibMaybeAddExt:" + + using namespace llvm::sys; + + LLVM_DEBUG(dbgs() << "Dyld::lookupLibMaybeAddExt: " << libStem.str() + << ", ..., libLoader=" << libLoader << "\n"); + + std::string foundDyLib = lookupLibInPaths(libStem, RPath, RunPath, libLoader); + + if (foundDyLib.empty()) { + // Add DyLib extension: + SmallString<512> filenameWithExt(libStem); +#if defined(LLVM_ON_UNIX) +#ifdef __APPLE__ + SmallString<512>::iterator IStemEnd = filenameWithExt.end() - 1; +#endif + static const char* DyLibExt = ".so"; +#elif defined(_WIN32) + static const char* DyLibExt = ".dll"; +#else +#error "Unsupported platform." +#endif + filenameWithExt += DyLibExt; + foundDyLib = lookupLibInPaths(filenameWithExt, RPath, RunPath, libLoader); +#ifdef __APPLE__ + if (foundDyLib.empty()) { + filenameWithExt.erase(IStemEnd + 1, filenameWithExt.end()); + filenameWithExt += ".dylib"; + foundDyLib = lookupLibInPaths(filenameWithExt, RPath, RunPath, libLoader); + } +#endif + } + + if (foundDyLib.empty()) + return std::string(); + + // get canonical path name and check if already loaded + const std::string Path = platform::NormalizePath(foundDyLib); + if (Path.empty()) { + LLVM_DEBUG( + dbgs() << "cling::DynamicLibraryManager::lookupLibMaybeAddExt(): " + << "error getting real (canonical) path of library " + << foundDyLib << '\n'); + return foundDyLib; + } + return Path; + +#undef DEBUG_TYPE +} + +std::string DynamicLibraryManager::normalizePath(StringRef path) { +#define DEBUG_TYPE "Dyld::normalizePath:" + // Make the path canonical if the file exists. + const std::string Path = path.str(); + struct stat buffer; + if (::stat(Path.c_str(), &buffer) != 0) + return std::string(); + + const std::string NPath = platform::NormalizePath(Path); + if (NPath.empty()) + LLVM_DEBUG(dbgs() << "Could not normalize: '" << Path << "'"); + return NPath; +#undef DEBUG_TYPE +} + +std::string RPathToStr2(SmallVector V) { + std::string result; + for (auto item : V) + result += item.str() + ","; + if (!result.empty()) + result.pop_back(); + return result; +} + +std::string DynamicLibraryManager::lookupLibrary( + StringRef libStem, SmallVector RPath /*={}*/, + SmallVector RunPath /*={}*/, + StringRef libLoader /*=""*/, bool variateLibStem /*=true*/) const { +#define DEBUG_TYPE "Dyld::lookupLibrary:" + LLVM_DEBUG(dbgs() << "Dyld::lookupLibrary: " << libStem.str() << ", " + << RPathToStr2(RPath) << ", " << RPathToStr2(RunPath) + << ", " << libLoader.str() << "\n"); + + // If it is an absolute path, don't try iterate over the paths. + if (llvm::sys::path::is_absolute(libStem)) { + if (isSharedLibrary(libStem)) + return normalizePath(libStem); + + LLVM_DEBUG(dbgs() << "Dyld::lookupLibrary: '" << libStem.str() << "'" + << "is not a shared library\n"); + return std::string(); + } + + // Subst all known linker variables ($origin, @rpath, etc.) +#ifdef __APPLE__ + // On MacOS @rpath is preplaced by all paths in RPATH one by one. + if (libStem.starts_with_insensitive("@rpath")) { + for (auto& P : RPath) { + std::string result = substFront(libStem, "@rpath", P); + if (isSharedLibrary(result)) + return normalizePath(result); + } + } else { +#endif + std::string result = substAll(libStem, libLoader); + if (isSharedLibrary(result)) + return normalizePath(result); +#ifdef __APPLE__ + } +#endif + + // Expand libStem with paths, extensions, etc. + std::string foundName; + if (variateLibStem) { + foundName = lookupLibMaybeAddExt(libStem, RPath, RunPath, libLoader); + if (foundName.empty()) { + StringRef libStemName = llvm::sys::path::filename(libStem); + if (!libStemName.starts_with("lib")) { + // try with "lib" prefix: + foundName = lookupLibMaybeAddExt( + libStem.str().insert(libStem.size() - libStemName.size(), "lib"), + RPath, RunPath, libLoader); + } + } + } else { + foundName = lookupLibInPaths(libStem, RPath, RunPath, libLoader); + } + + if (!foundName.empty()) + return platform::NormalizePath(foundName); + + return std::string(); +#undef DEBUG_TYPE +} + +DynamicLibraryManager::LoadLibResult +DynamicLibraryManager::loadLibrary(StringRef libStem, bool permanent, + bool resolved) { +#define DEBUG_TYPE "Dyld::loadLibrary:" + LLVM_DEBUG(dbgs() << "Dyld::loadLibrary: " << libStem.str() << ", " + << (permanent ? "permanent" : "not-permanent") << ", " + << (resolved ? "resolved" : "not-resolved") << "\n"); + + std::string canonicalLoadedLib; + if (resolved) { + canonicalLoadedLib = libStem.str(); + } else { + canonicalLoadedLib = lookupLibrary(libStem); + if (canonicalLoadedLib.empty()) + return kLoadLibNotFound; + } + + if (m_LoadedLibraries.find(canonicalLoadedLib) != m_LoadedLibraries.end()) + return kLoadLibAlreadyLoaded; + + // TODO: !permanent case + + std::string errMsg; + DyLibHandle dyLibHandle = platform::DLOpen(canonicalLoadedLib, &errMsg); + if (!dyLibHandle) { + // We emit callback to LibraryLoadingFailed when we get error with error + // message. + // TODO: Implement callbacks + + LLVM_DEBUG(dbgs() << "DynamicLibraryManager::loadLibrary(): " << errMsg); + + return kLoadLibLoadError; + } + + std::pair insRes = m_DyLibs.insert( + std::pair(dyLibHandle, canonicalLoadedLib)); + if (!insRes.second) + return kLoadLibAlreadyLoaded; + m_LoadedLibraries.insert(canonicalLoadedLib); + return kLoadLibSuccess; +#undef DEBUG_TYPE +} + +void DynamicLibraryManager::unloadLibrary(StringRef libStem) { +#define DEBUG_TYPE "Dyld::unloadLibrary:" + std::string canonicalLoadedLib = lookupLibrary(libStem); + if (!isLibraryLoaded(canonicalLoadedLib)) + return; + + DyLibHandle dyLibHandle = nullptr; + for (DyLibs::const_iterator I = m_DyLibs.begin(), E = m_DyLibs.end(); I != E; + ++I) { + if (I->second == canonicalLoadedLib) { + dyLibHandle = I->first; + break; + } + } + + // TODO: !permanent case + + std::string errMsg; + platform::DLClose(dyLibHandle, &errMsg); + if (!errMsg.empty()) { + LLVM_DEBUG(dbgs() << "cling::DynamicLibraryManager::unloadLibrary(): " + << errMsg << '\n'); + } + + // TODO: Implement callbacks + + m_DyLibs.erase(dyLibHandle); + m_LoadedLibraries.erase(canonicalLoadedLib); +#undef DEBUG_TYPE +} + +bool DynamicLibraryManager::isLibraryLoaded(StringRef fullPath) const { + std::string canonPath = normalizePath(fullPath); + if (m_LoadedLibraries.find(canonPath) != m_LoadedLibraries.end()) + return true; + return false; +} + +void DynamicLibraryManager::dump(llvm::raw_ostream* S /*= nullptr*/) const { + llvm::raw_ostream& OS = S ? *S : llvm::outs(); + + // FIXME: print in a stable order the contents of m_SearchPaths + for (const auto& Info : getSearchPaths()) { + if (!Info.IsUser) + OS << "[system] "; + OS << Info.Path.c_str() << "\n"; + } +} + +#if defined(_WIN32) +static bool IsDLL(llvm::StringRef headers) { + using namespace llvm::support::endian; + + uint32_t headeroffset = read32le(headers.data() + 0x3c); + auto peheader = headers.substr(headeroffset, 24); + if (peheader.size() != 24) { + return false; + } + // Read Characteristics from the coff header + uint32_t characteristics = read16le(peheader.data() + 22); + return (characteristics & llvm::COFF::IMAGE_FILE_DLL) != 0; +} +#endif + +bool DynamicLibraryManager::isSharedLibrary(StringRef libFullPath, + bool* exists /*=0*/) { + using namespace llvm; + + auto filetype = sys::fs::get_file_type(libFullPath, /*Follow*/ true); + if (filetype != sys::fs::file_type::regular_file) { + if (exists) { + // get_file_type returns status_error also in case of file_not_found. + *exists = filetype != sys::fs::file_type::status_error; + } + return false; + } + + // Do not use the identify_magic overload taking a path: It will open the + // file and then mmap its contents, possibly causing bus errors when another + // process truncates the file while we are trying to read it. Instead just + // read the first 1024 bytes, which should be enough for identify_magic to + // do its work. + // TODO: Fix the code upstream and consider going back to calling the + // convenience function after a future LLVM upgrade. + std::string path = libFullPath.str(); + std::ifstream in(path, std::ios::binary); + char header[1024] = {0}; + in.read(header, sizeof(header)); + if (in.fail()) { + if (exists) + *exists = false; + return false; + } + + StringRef headerStr(header, in.gcount()); + file_magic Magic = identify_magic(headerStr); + + bool result = +#ifdef __APPLE__ + (Magic == file_magic::macho_fixed_virtual_memory_shared_lib || + Magic == file_magic::macho_dynamically_linked_shared_lib || + Magic == file_magic::macho_dynamically_linked_shared_lib_stub || + Magic == file_magic::macho_universal_binary) +#elif defined(LLVM_ON_UNIX) +#ifdef __CYGWIN__ + (Magic == file_magic::pecoff_executable) +#else + (Magic == file_magic::elf_shared_object) +#endif +#elif defined(_WIN32) + // We should only include dll libraries without including executables, + // object code and others... + (Magic == file_magic::pecoff_executable && IsDLL(headerStr)) +#else +#error "Unsupported platform." +#endif + ; + + return result; +} + +} // end namespace Cpp diff --git a/lib/Interpreter/DynamicLibraryManager.h b/lib/CppInterOp/DynamicLibraryManager.h similarity index 100% rename from lib/Interpreter/DynamicLibraryManager.h rename to lib/CppInterOp/DynamicLibraryManager.h diff --git a/lib/CppInterOp/DynamicLibraryManagerSymbol.cpp b/lib/CppInterOp/DynamicLibraryManagerSymbol.cpp new file mode 100644 index 000000000..5190a8fcb --- /dev/null +++ b/lib/CppInterOp/DynamicLibraryManagerSymbol.cpp @@ -0,0 +1,1372 @@ +//------------------------------------------------------------------------------ +// CLING - the C++ LLVM-based InterpreterG :) +// author: Vassil Vassilev +// author: Alexander Penev +// +// This file is dual-licensed: you can choose to license it under the University +// of Illinois Open Source License or the GNU Lesser General Public License. See +// LICENSE.TXT for details. +//------------------------------------------------------------------------------ + +#include "DynamicLibraryManager.h" +#include "Paths.h" + +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/BinaryFormat/MachO.h" +#include "llvm/Object/COFF.h" +#include "llvm/Object/ELF.h" +#include "llvm/Object/ELFObjectFile.h" +#include "llvm/Object/MachO.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/WithColor.h" + +#include +#include +#include +#include +#include + +#ifdef LLVM_ON_UNIX +#include +#include +#include +#endif // LLVM_ON_UNIX + +#ifdef __APPLE__ +#include +#include +#undef LC_LOAD_DYLIB +#undef LC_RPATH +#endif // __APPLE__ + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include // For GetModuleFileNameA +#include // For VirtualQuery +#include +#endif + +namespace { + +using BasePath = std::string; +using namespace llvm; + +// This is a GNU implementation of hash used in bloom filter! +static uint32_t GNUHash(StringRef S) { + uint32_t H = 5381; + for (uint8_t C : S) + H = (H << 5) + H + C; + return H; +} + +constexpr uint32_t log2u(std::uint32_t n) { + return (n > 1) ? 1 + log2u(n >> 1) : 0; +} + +struct BloomFilter { + + // https://hur.st/bloomfilter + // + // n = ceil(m / (-k / log(1 - exp(log(p) / k)))) + // p = pow(1 - exp(-k / (m / n)), k) + // m = ceil((n * log(p)) / log(1 / pow(2, log(2)))); + // k = round((m / n) * log(2)); + // + // n = symbolsCount + // p = 0.02 + // k = 2 (k1=GNUHash and k2=GNUHash >> bloomShift) + // m = ceil((symbolsCount * log(p)) / log(1 / pow(2, log(2)))); + // bloomShift = min(5 for bits=32 or 6 for bits=64, log2(symbolsCount)) + // bloomSize = ceil((-1.44 * n * log2f(p)) / bits) + + const int m_Bits = 8 * sizeof(uint64_t); + const float m_P = 0.02f; + + bool m_IsInitialized = false; + uint32_t m_SymbolsCount = 0; + uint32_t m_BloomSize = 0; + uint32_t m_BloomShift = 0; + std::vector m_BloomTable; + + bool TestHash(uint32_t hash) const { + // This function is superhot. No branches here, breaks inlining and makes + // overall performance around 4x slower. + assert(m_IsInitialized && "Not yet initialized!"); + uint32_t hash2 = hash >> m_BloomShift; + uint32_t n = (hash >> log2u(m_Bits)) % m_BloomSize; + uint64_t mask = ((1ULL << (hash % m_Bits)) | (1ULL << (hash2 % m_Bits))); + return (mask & m_BloomTable[n]) == mask; + } + + void AddHash(uint32_t hash) { + assert(m_IsInitialized && "Not yet initialized!"); + uint32_t hash2 = hash >> m_BloomShift; + uint32_t n = (hash >> log2u(m_Bits)) % m_BloomSize; + uint64_t mask = ((1ULL << (hash % m_Bits)) | (1ULL << (hash2 % m_Bits))); + m_BloomTable[n] |= mask; + } + + void ResizeTable(uint32_t newSymbolsCount) { + assert(m_SymbolsCount == 0 && "Not supported yet!"); + m_SymbolsCount = newSymbolsCount; + m_BloomSize = ceil((-1.44f * m_SymbolsCount * log2f(m_P)) / m_Bits); + m_BloomShift = std::min(6u, log2u(m_SymbolsCount)); + m_BloomTable.resize(m_BloomSize); + } +}; + +/// An efficient representation of a full path to a library which does not +/// duplicate common path patterns reducing the overall memory footprint. +/// +/// For example, `/home/.../lib/libA.so`, m_Path will contain a pointer +/// to `/home/.../lib/` +/// will be stored and .second `libA.so`. +/// This approach reduces the duplicate paths as at one location there may be +/// plenty of libraries. +struct LibraryPath { + const BasePath& m_Path; + std::string m_LibName; + BloomFilter m_Filter; + StringSet<> m_Symbols; + // std::vector m_LibDeps; + + LibraryPath(const BasePath& Path, const std::string& LibName) + : m_Path(Path), m_LibName(LibName) {} + + bool operator==(const LibraryPath& other) const { + return (&m_Path == &other.m_Path || m_Path == other.m_Path) && + m_LibName == other.m_LibName; + } + + const std::string GetFullName() const { + SmallString<512> Vec(m_Path); + llvm::sys::path::append(Vec, StringRef(m_LibName)); + return Vec.str().str(); + } + + void AddBloom(StringRef symbol) { m_Filter.AddHash(GNUHash(symbol)); } + + StringRef AddSymbol(const std::string& symbol) { + auto it = m_Symbols.insert(symbol); + return it.first->getKey(); + } + + bool hasBloomFilter() const { return m_Filter.m_IsInitialized; } + + bool isBloomFilterEmpty() const { + assert(m_Filter.m_IsInitialized && "Bloom filter not initialized!"); + return m_Filter.m_SymbolsCount == 0; + } + + void InitializeBloomFilter(uint32_t newSymbolsCount) { + assert(!m_Filter.m_IsInitialized && + "Cannot re-initialize non-empty filter!"); + m_Filter.m_IsInitialized = true; + m_Filter.ResizeTable(newSymbolsCount); + } + + bool MayExistSymbol(uint32_t hash) const { + // The library had no symbols and the bloom filter is empty. + if (isBloomFilterEmpty()) + return false; + + return m_Filter.TestHash(hash); + } + + bool ExistSymbol(StringRef symbol) const { + return m_Symbols.find(symbol) != m_Symbols.end(); + } +}; + +/// A helper class keeping track of loaded libraries. It implements a fast +/// search O(1) while keeping deterministic iterability in a memory efficient +/// way. The underlying set uses a custom hasher for better efficiency given the +/// specific problem where the library names (m_LibName) are relatively short +/// strings and the base paths (m_Path) are repetitive long strings. +class LibraryPaths { + struct LibraryPathHashFn { + size_t operator()(const LibraryPath& item) const { + return std::hash()(item.m_Path.length()) ^ + std::hash()(item.m_LibName); + } + }; + + std::vector m_Libs; + std::unordered_set m_LibsH; + +public: + bool HasRegisteredLib(const LibraryPath& Lib) const { + return m_LibsH.count(Lib); + } + + const LibraryPath* GetRegisteredLib(const LibraryPath& Lib) const { + auto search = m_LibsH.find(Lib); + if (search != m_LibsH.end()) + return &(*search); + return nullptr; + } + + const LibraryPath* RegisterLib(const LibraryPath& Lib) { + auto it = m_LibsH.insert(Lib); + assert(it.second && "Already registered!"); + m_Libs.push_back(&*it.first); + return &*it.first; + } + + void UnregisterLib(const LibraryPath& Lib) { + auto found = m_LibsH.find(Lib); + if (found == m_LibsH.end()) + return; + + m_Libs.erase(std::find(m_Libs.begin(), m_Libs.end(), &*found)); + m_LibsH.erase(found); + } + + size_t size() const { + assert(m_Libs.size() == m_LibsH.size()); + return m_Libs.size(); + } + + const std::vector& GetLibraries() const { return m_Libs; } +}; + +#ifndef _WIN32 +// Cached version of system function lstat +static inline mode_t cached_lstat(const char* path) { + static StringMap lstat_cache; + + // If already cached - return cached result + auto it = lstat_cache.find(path); + if (it != lstat_cache.end()) + return it->second; + + // If result not in cache - call system function and cache result + struct stat buf; + mode_t st_mode = (lstat(path, &buf) == -1) ? 0 : buf.st_mode; + lstat_cache.insert(std::pair(path, st_mode)); + return st_mode; +} + +// Cached version of system function readlink +static inline StringRef cached_readlink(const char* pathname) { + static StringMap readlink_cache; + + // If already cached - return cached result + auto it = readlink_cache.find(pathname); + if (it != readlink_cache.end()) + return StringRef(it->second); + + // If result not in cache - call system function and cache result + char buf[PATH_MAX]; + ssize_t len; + if ((len = readlink(pathname, buf, sizeof(buf))) != -1) { + buf[len] = '\0'; + std::string s(buf); + readlink_cache.insert(std::pair(pathname, s)); + return readlink_cache[pathname]; + } + return ""; +} +#endif + +// Cached version of system function realpath +std::string cached_realpath(StringRef path, StringRef base_path = "", + bool is_base_path_real = false, + long symlooplevel = 40) { + if (path.empty()) { + errno = ENOENT; + return ""; + } + + if (!symlooplevel) { + errno = ELOOP; + return ""; + } + + // If already cached - return cached result + static StringMap> cache; + bool relative_path = llvm::sys::path::is_relative(path); + if (!relative_path) { + auto it = cache.find(path); + if (it != cache.end()) { + errno = it->second.second; + return it->second.first; + } + } + + // If result not in cache - call system function and cache result + + StringRef sep(llvm::sys::path::get_separator()); + SmallString<256> result(sep); +#ifndef _WIN32 + SmallVector p; + + // Relative or absolute path + if (relative_path) { + if (is_base_path_real) { + result.assign(base_path); + } else { + if (path[0] == '~' && + (path.size() == 1 || llvm::sys::path::is_separator(path[1]))) { + static SmallString<128> home; + if (home.str().empty()) + llvm::sys::path::home_directory(home); + StringRef(home).split(p, sep, /*MaxSplit*/ -1, /*KeepEmpty*/ false); + } else if (base_path.empty()) { + static SmallString<256> current_path; + if (current_path.str().empty()) + llvm::sys::fs::current_path(current_path); + StringRef(current_path) + .split(p, sep, /*MaxSplit*/ -1, /*KeepEmpty*/ false); + } else { + base_path.split(p, sep, /*MaxSplit*/ -1, /*KeepEmpty*/ false); + } + } + } + path.split(p, sep, /*MaxSplit*/ -1, /*KeepEmpty*/ false); + + // Handle path list items + for (auto item : p) { + if (item == ".") + continue; // skip "." element in "abc/./def" + if (item == "..") { + // collapse "a/b/../c" to "a/c" + size_t s = result.rfind(sep); + if (s != llvm::StringRef::npos) + result.resize(s); + if (result.empty()) + result = sep; + continue; + } + + size_t old_size = result.size(); + llvm::sys::path::append(result, item); + mode_t st_mode = cached_lstat(result.c_str()); + if (S_ISLNK(st_mode)) { + StringRef symlink = cached_readlink(result.c_str()); + if (llvm::sys::path::is_relative(symlink)) { + result.resize(old_size); + result = cached_realpath(symlink, result, true, symlooplevel - 1); + } else { + result = cached_realpath(symlink, "", true, symlooplevel - 1); + } + } else if (st_mode == 0) { + cache.insert(std::pair>( + path, std::pair("", ENOENT))); + errno = ENOENT; + return ""; + } + } +#else + llvm::sys::fs::real_path(path, result); +#endif + cache.insert(std::pair>( + path, std::pair(result.str().str(), errno))); + return result.str().str(); +} + +using namespace llvm; +using namespace llvm::object; + +template +static Expected getDynamicStrTab(const ELFFile* Elf) { + auto DynamicEntriesOrError = Elf->dynamicEntries(); + if (!DynamicEntriesOrError) + return DynamicEntriesOrError.takeError(); + + for (const typename ELFT::Dyn& Dyn : *DynamicEntriesOrError) { + if (Dyn.d_tag == ELF::DT_STRTAB) { + auto MappedAddrOrError = Elf->toMappedAddr(Dyn.getPtr()); + if (!MappedAddrOrError) + return MappedAddrOrError.takeError(); + return StringRef(reinterpret_cast(*MappedAddrOrError)); + } + } + + // If the dynamic segment is not present, we fall back on the sections. + auto SectionsOrError = Elf->sections(); + if (!SectionsOrError) + return SectionsOrError.takeError(); + + for (const typename ELFT::Shdr& Sec : *SectionsOrError) { + if (Sec.sh_type == ELF::SHT_DYNSYM) + return Elf->getStringTableForSymtab(Sec); + } + + return createError("dynamic string table not found"); +} + +static StringRef GetGnuHashSection(llvm::object::ObjectFile* file) { + for (auto S : file->sections()) { + StringRef name = llvm::cantFail(S.getName()); + if (name == ".gnu.hash") { + return llvm::cantFail(S.getContents()); + } + } + return ""; +} + +/// Bloom filter is a stochastic data structure which can tell us if a symbol +/// name does not exist in a library with 100% certainty. If it tells us it +/// exists this may not be true: +/// https://blogs.oracle.com/solaris/gnu-hash-elf-sections-v2 +/// +/// ELF has this optimization in the new linkers by default, It is stored in the +/// gnu.hash section of the object file. +/// +///\returns true if the symbol may be in the library. +static bool MayExistInElfObjectFile(llvm::object::ObjectFile* soFile, + uint32_t hash) { + assert(soFile->isELF() && "Not ELF"); + + // Compute the platform bitness -- either 64 or 32. + const unsigned bits = 8 * soFile->getBytesInAddress(); + + StringRef contents = GetGnuHashSection(soFile); + if (contents.size() < 16) + // We need to search if the library doesn't have .gnu.hash section! + return true; + const char* hashContent = contents.data(); + + // See https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/ for .gnu.hash + // table layout. + uint32_t maskWords = *reinterpret_cast(hashContent + 8); + uint32_t shift2 = *reinterpret_cast(hashContent + 12); + uint32_t hash2 = hash >> shift2; + uint32_t n = (hash / bits) % maskWords; + + const char* bloomfilter = hashContent + 16; + const char* hash_pos = bloomfilter + n * (bits / 8); // * (Bits / 8) + uint64_t word = *reinterpret_cast(hash_pos); + uint64_t bitmask = ((1ULL << (hash % bits)) | (1ULL << (hash2 % bits))); + return (bitmask & word) == bitmask; +} + +} // namespace + +// This function isn't referenced outside its translation unit, but it +// can't use the "static" keyword because its address is used for +// GetMainExecutable (since some platforms don't support taking the +// address of main, and some platforms can't implement GetMainExecutable +// without being given the address of a function in the main executable). +std::string GetExecutablePath() { + // This just needs to be some symbol in the binary; C++ doesn't + // allow taking the address of ::main however. + return Cpp::DynamicLibraryManager::getSymbolLocation(&GetExecutablePath); +} + +namespace Cpp { +class Dyld { + struct BasePathHashFunction { + size_t operator()(const BasePath& item) const { + return std::hash()(item); + } + }; + + struct BasePathEqFunction { + size_t operator()(const BasePath& l, const BasePath& r) const { + return &l == &r || l == r; + } + }; + /// A memory efficient llvm::VectorSet. The class provides O(1) search + /// complexity. It is tuned to compare BasePaths first by checking the + /// address and then the representation which models the base path reuse. + class BasePaths { + public: + std::unordered_set + m_Paths; + + public: + const BasePath& RegisterBasePath(const std::string& Path, + bool* WasInserted = nullptr) { + auto it = m_Paths.insert(Path); + if (WasInserted) + *WasInserted = it.second; + + return *it.first; + } + + bool Contains(StringRef Path) { return m_Paths.count(Path.str()); } + }; + + bool m_FirstRun = true; + bool m_FirstRunSysLib = true; + bool m_UseBloomFilter = true; + bool m_UseHashTable = true; + + const Cpp::DynamicLibraryManager& m_DynamicLibraryManager; + + /// The basename of `/home/.../lib/libA.so`, + /// m_BasePaths will contain `/home/.../lib/` + BasePaths m_BasePaths; + + LibraryPaths m_Libraries; + LibraryPaths m_SysLibraries; + /// Contains a set of libraries which we gave to the user via ResolveSymbol + /// call and next time we should check if the user loaded them to avoid + /// useless iterations. + LibraryPaths m_QueriedLibraries; + + using PermanentlyIgnoreCallbackProto = std::function; + const PermanentlyIgnoreCallbackProto m_ShouldPermanentlyIgnoreCallback; + const StringRef m_ExecutableFormat; + + /// Scan for shared objects which are not yet loaded. They are a our symbol + /// resolution candidate sources. + /// NOTE: We only scan not loaded shared objects. + /// \param[in] searchSystemLibraries - whether to decent to standard system + /// locations for shared objects. + void ScanForLibraries(bool searchSystemLibraries = false); + + /// Builds a bloom filter lookup optimization. + void BuildBloomFilter(LibraryPath* Lib, llvm::object::ObjectFile* BinObjFile, + unsigned IgnoreSymbolFlags = 0) const; + + /// Looks up symbols from a an object file, representing the library. + ///\param[in] Lib - full path to the library. + ///\param[in] mangledName - the mangled name to look for. + ///\param[in] IgnoreSymbolFlags - The symbols to ignore upon a match. + ///\returns true on success. + bool ContainsSymbol(const LibraryPath* Lib, StringRef mangledName, + unsigned IgnoreSymbolFlags = 0) const; + + bool ShouldPermanentlyIgnore(StringRef FileName) const; + void dumpDebugInfo() const; + +public: + Dyld(const Cpp::DynamicLibraryManager& DLM, + PermanentlyIgnoreCallbackProto shouldIgnore, StringRef execFormat) + : m_DynamicLibraryManager(DLM), + m_ShouldPermanentlyIgnoreCallback(shouldIgnore), + m_ExecutableFormat(execFormat) {} + + ~Dyld() {}; + + std::string searchLibrariesForSymbol(StringRef mangledName, + bool searchSystem); +}; + +std::string RPathToStr(SmallVector V) { + std::string result; + for (auto item : V) + result += item.str() + ","; + if (!result.empty()) + result.pop_back(); + return result; +} + +template +void HandleDynTab(const ELFFile* Elf, StringRef FileName, + SmallVector& RPath, + SmallVector& RunPath, + std::vector& Deps, bool& isPIEExecutable) { +#define DEBUG_TYPE "Dyld:" + const char* Data = ""; + if (Expected StrTabOrErr = getDynamicStrTab(Elf)) + Data = StrTabOrErr.get().data(); + + isPIEExecutable = false; + + auto DynamicEntriesOrError = Elf->dynamicEntries(); + if (!DynamicEntriesOrError) { + LLVM_DEBUG(dbgs() << "Dyld: failed to read dynamic entries in" + << "'" << FileName.str() << "'\n"); + return; + } + + for (const typename ELFT::Dyn& Dyn : *DynamicEntriesOrError) { + switch (Dyn.d_tag) { + case ELF::DT_NEEDED: + Deps.push_back(Data + Dyn.d_un.d_val); + break; + case ELF::DT_RPATH: + SplitPaths(Data + Dyn.d_un.d_val, RPath, utils::kAllowNonExistent, + Cpp::utils::platform::kEnvDelim, false); + break; + case ELF::DT_RUNPATH: + SplitPaths(Data + Dyn.d_un.d_val, RunPath, utils::kAllowNonExistent, + Cpp::utils::platform::kEnvDelim, false); + break; + case ELF::DT_FLAGS_1: + // Check if this is not a pie executable. + if (Dyn.d_un.d_val & llvm::ELF::DF_1_PIE) + isPIEExecutable = true; + break; + // (Dyn.d_tag == ELF::DT_NULL) continue; + // (Dyn.d_tag == ELF::DT_AUXILIARY || Dyn.d_tag == ELF::DT_FILTER) + } + } +#undef DEBUG_TYPE +} + +void Dyld::ScanForLibraries(bool searchSystemLibraries /* = false*/) { +#define DEBUG_TYPE "Dyld:ScanForLibraries:" + + const auto& searchPaths = m_DynamicLibraryManager.getSearchPaths(); + + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: system=" + << (searchSystemLibraries ? "true" : "false") << "\n"); + for (const DynamicLibraryManager::SearchPathInfo& Info : searchPaths) + LLVM_DEBUG(dbgs() << ">>>" << Info.Path << ", " + << (Info.IsUser ? "user\n" : "system\n")); + + llvm::SmallSet ScannedPaths; + + for (const DynamicLibraryManager::SearchPathInfo& Info : searchPaths) { + if (Info.IsUser != searchSystemLibraries) { + // Examples which we should handle. + // File Real + // /lib/1/1.so /lib/1/1.so // file + // /lib/1/2.so->/lib/1/1.so /lib/1/1.so // file local link + // /lib/1/3.so->/lib/3/1.so /lib/3/1.so // file external link + // /lib/2->/lib/1 // path link + // /lib/2/1.so /lib/1/1.so // path link, file + // /lib/2/2.so->/lib/1/1.so /lib/1/1.so // path link, file local link + // /lib/2/3.so->/lib/3/1.so /lib/3/1.so // path link, file external link + // + // /lib/3/1.so + // /lib/3/2.so->/system/lib/s.so + // /lib/3/3.so + // /system/lib/1.so + // + // libL.so NEEDED/RPATH libR.so /lib/some-rpath/libR.so // + // needed/dependedt library in libL.so RPATH/RUNPATH or other (in)direct + // dep + // + // Paths = /lib/1 : /lib/2 : /lib/3 + + // m_BasePaths = ["/lib/1", "/lib/3", "/system/lib"] + // m_*Libraries = [<0,"1.so">, <1,"1.so">, <2,"s.so">, <1,"3.so">] + + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries Iter:" << Info.Path + << " -> "); + std::string RealPath = cached_realpath(Info.Path); + + llvm::StringRef DirPath(RealPath); + LLVM_DEBUG(dbgs() << RealPath << "\n"); + + if (!llvm::sys::fs::is_directory(DirPath) || DirPath.empty()) + continue; + + // Already searched? + const BasePath& ScannedBPath = m_BasePaths.RegisterBasePath(RealPath); + if (ScannedPaths.count(&ScannedBPath)) { + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries Already scanned: " + << RealPath << "\n"); + continue; + } + + // FileName must be always full/absolute/resolved file name. + std::function HandleLib = + [&](llvm::StringRef FileName, unsigned level) { + LLVM_DEBUG(dbgs() + << "Dyld::ScanForLibraries HandleLib:" << FileName.str() + << ", level=" << level << " -> "); + + llvm::StringRef FileRealPath = + llvm::sys::path::parent_path(FileName); + llvm::StringRef FileRealName = llvm::sys::path::filename(FileName); + const BasePath& BaseP = + m_BasePaths.RegisterBasePath(FileRealPath.str()); + LibraryPath LibPath(BaseP, FileRealName.str()); // bp, str + + if (m_SysLibraries.GetRegisteredLib(LibPath) || + m_Libraries.GetRegisteredLib(LibPath)) { + LLVM_DEBUG(dbgs() << "Already handled!!!\n"); + return; + } + + if (ShouldPermanentlyIgnore(FileName)) { + LLVM_DEBUG(dbgs() << "PermanentlyIgnored!!!\n"); + return; + } + + if (searchSystemLibraries) + m_SysLibraries.RegisterLib(LibPath); + else + m_Libraries.RegisterLib(LibPath); + + // Handle lib dependencies + llvm::SmallVector RPath; + llvm::SmallVector RunPath; + std::vector Deps; + auto ObjFileOrErr = + llvm::object::ObjectFile::createObjectFile(FileName); + if (llvm::Error Err = ObjFileOrErr.takeError()) { + std::string Message; + handleAllErrors(std::move(Err), [&](llvm::ErrorInfoBase& EIB) { + Message += EIB.message() + "; "; + }); + LLVM_DEBUG( + dbgs() + << "Dyld::ScanForLibraries: Failed to read object file " + << FileName.str() << " Errors: " << Message << "\n"); + return; + } + llvm::object::ObjectFile* BinObjF = ObjFileOrErr.get().getBinary(); + if (BinObjF->isELF()) { + bool isPIEExecutable = false; + + if (const auto* ELF = dyn_cast(BinObjF)) + HandleDynTab(&ELF->getELFFile(), FileName, RPath, RunPath, Deps, + isPIEExecutable); + else if (const auto* ELF = dyn_cast(BinObjF)) + HandleDynTab(&ELF->getELFFile(), FileName, RPath, RunPath, Deps, + isPIEExecutable); + else if (const auto* ELF = dyn_cast(BinObjF)) + HandleDynTab(&ELF->getELFFile(), FileName, RPath, RunPath, Deps, + isPIEExecutable); + else if (const auto* ELF = dyn_cast(BinObjF)) + HandleDynTab(&ELF->getELFFile(), FileName, RPath, RunPath, Deps, + isPIEExecutable); + + if ((level == 0) && isPIEExecutable) { + if (searchSystemLibraries) + m_SysLibraries.UnregisterLib(LibPath); + else + m_Libraries.UnregisterLib(LibPath); + return; + } + } else if (BinObjF->isMachO()) { + MachOObjectFile* Obj = (MachOObjectFile*)BinObjF; + for (const auto& Command : Obj->load_commands()) { + if (Command.C.cmd == MachO::LC_LOAD_DYLIB) { + // Command.C.cmd == MachO::LC_ID_DYLIB || + // Command.C.cmd == MachO::LC_LOAD_WEAK_DYLIB || + // Command.C.cmd == MachO::LC_REEXPORT_DYLIB || + // Command.C.cmd == MachO::LC_LAZY_LOAD_DYLIB || + // Command.C.cmd == MachO::LC_LOAD_UPWARD_DYLIB || + MachO::dylib_command dylibCmd = + Obj->getDylibIDLoadCommand(Command); + Deps.push_back(StringRef(Command.Ptr + dylibCmd.dylib.name)); + } else if (Command.C.cmd == MachO::LC_RPATH) { + MachO::rpath_command rpathCmd = Obj->getRpathCommand(Command); + SplitPaths(Command.Ptr + rpathCmd.path, RPath, + utils::kAllowNonExistent, + Cpp::utils::platform::kEnvDelim, false); + } + } + } else if (BinObjF->isCOFF()) { + // TODO: COFF support + } + + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Deps Info:\n"); + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: RPATH=" + << RPathToStr(RPath) << "\n"); + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: RUNPATH=" + << RPathToStr(RunPath) << "\n"); + int x = 0; + for (StringRef dep : Deps) + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Deps[" << x++ + << "]=" << dep.str() << "\n"); + + // Heuristics for workaround performance problems: + // (H1) If RPATH and RUNPATH == "" -> skip handling Deps + if (RPath.empty() && RunPath.empty()) { + LLVM_DEBUG( + dbgs() + << "Dyld::ScanForLibraries: Skip all deps by Heuristic1: " + << FileName.str() << "\n"); + return; + }; + // (H2) If RPATH subset of LD_LIBRARY_PATH && + // RUNPATH subset of LD_LIBRARY_PATH -> skip handling Deps + if (std::all_of( + RPath.begin(), RPath.end(), + [&](StringRef item) { + return std::any_of( + searchPaths.begin(), searchPaths.end(), + [&](DynamicLibraryManager::SearchPathInfo item1) { + return item == item1.Path; + }); + }) && + std::all_of( + RunPath.begin(), RunPath.end(), [&](StringRef item) { + return std::any_of( + searchPaths.begin(), searchPaths.end(), + [&](DynamicLibraryManager::SearchPathInfo item1) { + return item == item1.Path; + }); + })) { + LLVM_DEBUG( + dbgs() + << "Dyld::ScanForLibraries: Skip all deps by Heuristic2: " + << FileName.str() << "\n"); + return; + } + + // Handle dependencies + for (StringRef dep : Deps) { + std::string dep_full = m_DynamicLibraryManager.lookupLibrary( + dep, RPath, RunPath, FileName, false); + HandleLib(dep_full, level + 1); + } + }; + + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Iterator: " << DirPath + << "\n"); + std::error_code EC; + for (llvm::sys::fs::directory_iterator DirIt(DirPath, EC), DirEnd; + DirIt != DirEnd && !EC; DirIt.increment(EC)) { + + LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Iterator >>> " + << DirIt->path() + << ", type=" << (short)(DirIt->type()) << "\n"); + + const llvm::sys::fs::file_type ft = DirIt->type(); + if (ft == llvm::sys::fs::file_type::regular_file) { + HandleLib(DirIt->path(), 0); + } else if (ft == llvm::sys::fs::file_type::symlink_file) { + std::string DepFileName_str = cached_realpath(DirIt->path()); + llvm::StringRef DepFileName = DepFileName_str; + assert(!llvm::sys::fs::is_symlink_file(DepFileName)); + if (!llvm::sys::fs::is_directory(DepFileName)) + HandleLib(DepFileName, 0); + } + } + + // Register the DirPath as fully scanned. + ScannedPaths.insert(&ScannedBPath); + } + } +#undef DEBUG_TYPE +} + +void Dyld::BuildBloomFilter(LibraryPath* Lib, + llvm::object::ObjectFile* BinObjFile, + unsigned IgnoreSymbolFlags /*= 0*/) const { +#define DEBUG_TYPE "Dyld::BuildBloomFilter:" + assert(m_UseBloomFilter && "Bloom filter is disabled"); + assert(!Lib->hasBloomFilter() && "Already built!"); + + using namespace llvm; + using namespace llvm::object; + + LLVM_DEBUG( + dbgs() << "Dyld::BuildBloomFilter: Start building Bloom filter for: " + << Lib->GetFullName() << "\n"); + + // If BloomFilter is empty then build it. + // Count Symbols and generate BloomFilter + uint32_t SymbolsCount = 0; + std::list symbols; + for (const llvm::object::SymbolRef& S : BinObjFile->symbols()) { + uint32_t Flags = llvm::cantFail(S.getFlags()); + // Do not insert in the table symbols flagged to ignore. + if (Flags & IgnoreSymbolFlags) + continue; + + // Note, we are at last resort and loading library based on a weak + // symbol is allowed. Otherwise, the JIT will issue an unresolved + // symbol error. + // + // There are other weak symbol kinds (marked as 'V') to denote + // typeinfo and vtables. It is unclear whether we should load such + // libraries or from which library we should resolve the symbol. + // We seem to not have a way to differentiate it from the symbol API. + + llvm::Expected SymNameErr = S.getName(); + if (!SymNameErr) { + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Failed to read symbol " + << SymNameErr.get() << "\n"); + continue; + } + + if (SymNameErr.get().empty()) + continue; + + ++SymbolsCount; + symbols.push_back(SymNameErr.get()); + } + + if (BinObjFile->isELF()) { + // ELF file format has .dynstr section for the dynamic symbol table. + const auto* ElfObj = cast(BinObjFile); + + for (const object::SymbolRef& S : ElfObj->getDynamicSymbolIterators()) { + uint32_t Flags = llvm::cantFail(S.getFlags()); + // DO NOT insert to table if symbol was undefined + if (Flags & llvm::object::SymbolRef::SF_Undefined) + continue; + + // Note, we are at last resort and loading library based on a weak + // symbol is allowed. Otherwise, the JIT will issue an unresolved + // symbol error. + // + // There are other weak symbol kinds (marked as 'V') to denote + // typeinfo and vtables. It is unclear whether we should load such + // libraries or from which library we should resolve the symbol. + // We seem to not have a way to differentiate it from the symbol API. + + llvm::Expected SymNameErr = S.getName(); + if (!SymNameErr) { + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Failed to read symbol " + << SymNameErr.get() << "\n"); + continue; + } + + if (SymNameErr.get().empty()) + continue; + + ++SymbolsCount; + symbols.push_back(SymNameErr.get()); + } + } else if (BinObjFile->isCOFF()) { // On Windows, the symbols are present in + // COFF format. + llvm::object::COFFObjectFile* CoffObj = + cast(BinObjFile); + + // In COFF, the symbols are not present in the SymbolTable section + // of the Object file. They are present in the ExportDirectory section. + for (auto I = CoffObj->export_directory_begin(), + E = CoffObj->export_directory_end(); + I != E; I = ++I) { + // All the symbols are already flagged as exported. + // We cannot really ignore symbols based on flags as we do on unix. + StringRef Name; + auto Err = I->getSymbolName(Name); + + if (Err) { + std::string Message; + handleAllErrors(std::move(Err), [&](llvm::ErrorInfoBase& EIB) { + Message += EIB.message() + "; "; + }); + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Failed to read symbol " + << Message << "\n"); + continue; + } + if (Name.empty()) + continue; + + ++SymbolsCount; + symbols.push_back(Name); + } + } + + Lib->InitializeBloomFilter(SymbolsCount); + + if (!SymbolsCount) { + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: No symbols!\n"); + return; + } + + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Symbols:\n"); + for (auto it : symbols) + LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter" + << "- " << it << "\n"); + + // Generate BloomFilter + for (const auto& S : symbols) { + if (m_UseHashTable) + Lib->AddBloom(Lib->AddSymbol(S.str())); + else + Lib->AddBloom(S); + } +#undef DEBUG_TYPE +} + +bool Dyld::ContainsSymbol(const LibraryPath* Lib, StringRef mangledName, + unsigned IgnoreSymbolFlags /*= 0*/) const { +#define DEBUG_TYPE "Dyld::ContainsSymbol:" + const std::string library_filename = Lib->GetFullName(); + + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Find symbol: lib=" + << library_filename << ", mangled=" << mangledName.str() + << "\n"); + + auto ObjF = llvm::object::ObjectFile::createObjectFile(library_filename); + if (llvm::Error Err = ObjF.takeError()) { + std::string Message; + handleAllErrors(std::move(Err), [&](llvm::ErrorInfoBase& EIB) { + Message += EIB.message() + "; "; + }); + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Failed to read object file " + << library_filename << " Errors: " << Message << "\n"); + return false; + } + + llvm::object::ObjectFile* BinObjFile = ObjF.get().getBinary(); + + uint32_t hashedMangle = GNUHash(mangledName); + // Check for the gnu.hash section if ELF. + // If the symbol doesn't exist, exit early. + if (BinObjFile->isELF() && + !MayExistInElfObjectFile(BinObjFile, hashedMangle)) { + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: ELF BloomFilter: Skip symbol <" + << mangledName.str() << ">.\n"); + return false; + } + + if (m_UseBloomFilter) { + // Use our bloom filters and create them if necessary. + if (!Lib->hasBloomFilter()) + BuildBloomFilter(const_cast(Lib), BinObjFile, + IgnoreSymbolFlags); + + // If the symbol does not exist, exit early. In case it may exist, iterate. + if (!Lib->MayExistSymbol(hashedMangle)) { + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: BloomFilter: Skip symbol <" + << mangledName.str() << ">.\n"); + return false; + } + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: BloomFilter: Symbol <" + << mangledName.str() << "> May exist." + << " Search for it. "); + } + + if (m_UseHashTable) { + bool result = Lib->ExistSymbol(mangledName); + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: HashTable: Symbol " + << (result ? "Exist" : "Not exist") << "\n"); + return result; + } + + auto ForeachSymbol = + [&library_filename]( + llvm::iterator_range range, + unsigned IgnoreSymbolFlags, llvm::StringRef mangledName) -> bool { + for (const llvm::object::SymbolRef& S : range) { + uint32_t Flags = llvm::cantFail(S.getFlags()); + // Do not insert in the table symbols flagged to ignore. + if (Flags & IgnoreSymbolFlags) + continue; + + // Note, we are at last resort and loading library based on a weak + // symbol is allowed. Otherwise, the JIT will issue an unresolved + // symbol error. + // + // There are other weak symbol kinds (marked as 'V') to denote + // typeinfo and vtables. It is unclear whether we should load such + // libraries or from which library we should resolve the symbol. + // We seem to not have a way to differentiate it from the symbol API. + + llvm::Expected SymNameErr = S.getName(); + if (!SymNameErr) { + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Failed to read symbol " + << mangledName.str() << "\n"); + continue; + } + + if (SymNameErr.get().empty()) + continue; + + if (SymNameErr.get() == mangledName) { + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Symbol " + << mangledName.str() << " found in " + << library_filename << "\n"); + return true; + } + } + return false; + }; + + // If no hash symbol then iterate to detect symbol + // We Iterate only if BloomFilter and/or SymbolHashTable are not supported. + + LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Iterate all for <" + << mangledName.str() << ">"); + + // Symbol may exist. Iterate. + if (ForeachSymbol(BinObjFile->symbols(), IgnoreSymbolFlags, mangledName)) { + LLVM_DEBUG(dbgs() << " -> found.\n"); + return true; + } + + if (!BinObjFile->isELF()) { + LLVM_DEBUG(dbgs() << " -> not found.\n"); + return false; + } + + // ELF file format has .dynstr section for the dynamic symbol table. + const auto* ElfObj = llvm::cast(BinObjFile); + + bool result = ForeachSymbol(ElfObj->getDynamicSymbolIterators(), + IgnoreSymbolFlags, mangledName); + LLVM_DEBUG(dbgs() << (result ? " -> found.\n" : " -> not found.\n")); + return result; +#undef DEBUG_TYPE +} + +bool Dyld::ShouldPermanentlyIgnore(StringRef FileName) const { +#define DEBUG_TYPE "Dyld:" + assert(!m_ExecutableFormat.empty() && "Failed to find the object format!"); + + if (!Cpp::DynamicLibraryManager::isSharedLibrary(FileName)) + return true; + + // No need to check linked libraries, as this function is only invoked + // for symbols that cannot be found (neither by dlsym nor in the JIT). + if (m_DynamicLibraryManager.isLibraryLoaded(FileName)) + return true; + + auto ObjF = llvm::object::ObjectFile::createObjectFile(FileName); + if (!ObjF) { + LLVM_DEBUG(dbgs() << "[DyLD] Failed to read object file " << FileName + << "\n"); + return true; + } + + llvm::object::ObjectFile* file = ObjF.get().getBinary(); + + LLVM_DEBUG(dbgs() << "Current executable format: " << m_ExecutableFormat + << ". Executable format of " << FileName << " : " + << file->getFileFormatName() << "\n"); + + // Ignore libraries with different format than the executing one. + if (m_ExecutableFormat != file->getFileFormatName()) + return true; + + if (llvm::isa(*file)) { + for (auto S : file->sections()) { + llvm::StringRef name = llvm::cantFail(S.getName()); + if (name == ".text") { + // Check if the library has only debug symbols, usually when + // stripped with objcopy --only-keep-debug. This check is done by + // reading the manual of objcopy and inspection of stripped with + // objcopy libraries. + auto SecRef = static_cast(S); + if (SecRef.getType() == llvm::ELF::SHT_NOBITS) + return true; + + return (SecRef.getFlags() & llvm::ELF::SHF_ALLOC) == 0; + } + } + return true; + } + + // FIXME: Handle osx using isStripped after upgrading to llvm9. + + return m_ShouldPermanentlyIgnoreCallback(FileName); +#undef DEBUG_TYPE +} + +void Dyld::dumpDebugInfo() const { +#define DEBUG_TYPE "Dyld:" + LLVM_DEBUG(dbgs() << "---\n"); + size_t x = 0; + for (auto const& item : m_BasePaths.m_Paths) { + LLVM_DEBUG(dbgs() << "Dyld: - m_BasePaths[" << x++ << "]:" << &item << ": " + << item << "\n"); + } + LLVM_DEBUG(dbgs() << "---\n"); + x = 0; + for (auto const& item : m_Libraries.GetLibraries()) { + LLVM_DEBUG(dbgs() << "Dyld: - m_Libraries[" << x++ << "]:" << &item << ": " + << item->m_Path << ", " << item->m_LibName << "\n"); + } + x = 0; + for (auto const& item : m_SysLibraries.GetLibraries()) { + LLVM_DEBUG(dbgs() << "Dyld: - m_SysLibraries[" << x++ << "]:" << &item + << ": " << item->m_Path << ", " << item->m_LibName + << "\n"); + } +#undef DEBUG_TYPE +} + +std::string Dyld::searchLibrariesForSymbol(StringRef mangledName, + bool searchSystem /* = true*/) { +#define DEBUG_TYPE "Dyld:searchLibrariesForSymbol:" + assert( + !llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(mangledName.str()) && + "Library already loaded, please use dlsym!"); + assert(!mangledName.empty()); + + using namespace llvm::sys::path; + using namespace llvm::sys::fs; + + if (m_FirstRun) { + LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol:" << mangledName.str() + << ", searchSystem=" << (searchSystem ? "true" : "false") + << ", FirstRun(user)... scanning\n"); + + LLVM_DEBUG( + dbgs() + << "Dyld::searchLibrariesForSymbol: Before first ScanForLibraries\n"); + dumpDebugInfo(); + + ScanForLibraries(/* SearchSystemLibraries= */ false); + m_FirstRun = false; + + LLVM_DEBUG( + dbgs() + << "Dyld::searchLibrariesForSymbol: After first ScanForLibraries\n"); + dumpDebugInfo(); + } + + if (m_QueriedLibraries.size() > 0) { + // Last call we were asked if a library contains a symbol. Usually, the + // caller wants to load this library. Check if was loaded and remove it + // from our lists of not-yet-loaded libs. + + LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol: m_QueriedLibraries:\n"); + size_t x = 0; + for (auto item : m_QueriedLibraries.GetLibraries()) { + LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol - [" << x++ << "]:" << &item + << ": " << item->GetFullName() << "\n"); + } + + for (const LibraryPath* P : m_QueriedLibraries.GetLibraries()) { + const std::string LibName = P->GetFullName(); + if (!m_DynamicLibraryManager.isLibraryLoaded(LibName)) + continue; + + m_Libraries.UnregisterLib(*P); + m_SysLibraries.UnregisterLib(*P); + } + // TODO: m_QueriedLibraries.clear ? + } + + // Iterate over files under this path. We want to get each ".so" files + for (const LibraryPath* P : m_Libraries.GetLibraries()) { + if (ContainsSymbol(P, mangledName, /*ignore*/ + llvm::object::SymbolRef::SF_Undefined)) { + if (!m_QueriedLibraries.HasRegisteredLib(*P)) + m_QueriedLibraries.RegisterLib(*P); + + LLVM_DEBUG( + dbgs() << "Dyld::ResolveSymbol: Search found match in [user lib]: " + << P->GetFullName() << "!\n"); + + return P->GetFullName(); + } + } + + if (!searchSystem) + return ""; + + LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: SearchSystem!!!\n"); + + // Lookup in non-system libraries failed. Expand the search to the system. + if (m_FirstRunSysLib) { + LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol:" << mangledName.str() + << ", searchSystem=" << (searchSystem ? "true" : "false") + << ", FirstRun(system)... scanning\n"); + + LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: Before first system " + "ScanForLibraries\n"); + dumpDebugInfo(); + + ScanForLibraries(/* SearchSystemLibraries= */ true); + m_FirstRunSysLib = false; + + LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: After first system " + "ScanForLibraries\n"); + dumpDebugInfo(); + } + + for (const LibraryPath* P : m_SysLibraries.GetLibraries()) { + if (ContainsSymbol(P, mangledName, /*ignore*/ + llvm::object::SymbolRef::SF_Undefined | + llvm::object::SymbolRef::SF_Weak)) { + if (!m_QueriedLibraries.HasRegisteredLib(*P)) + m_QueriedLibraries.RegisterLib(*P); + + LLVM_DEBUG( + dbgs() << "Dyld::ResolveSymbol: Search found match in [system lib]: " + << P->GetFullName() << "!\n"); + + return P->GetFullName(); + } + } + + LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol: Search found no match!\n"); + + return ""; // Search found no match. +#undef DEBUG_TYPE +} + +DynamicLibraryManager::~DynamicLibraryManager() { + static_assert(sizeof(Dyld) > 0, "Incomplete type"); + delete m_Dyld; +} + +void DynamicLibraryManager::initializeDyld( + std::function shouldPermanentlyIgnore) { + // assert(!m_Dyld && "Already initialized!"); + if (m_Dyld) + delete m_Dyld; + + std::string exeP = GetExecutablePath(); + auto ObjF = cantFail(llvm::object::ObjectFile::createObjectFile(exeP)); + + m_Dyld = new Dyld(*this, shouldPermanentlyIgnore, + ObjF.getBinary()->getFileFormatName()); +} + +std::string DynamicLibraryManager::searchLibrariesForSymbol( + StringRef mangledName, bool searchSystem /* = true*/) const { + assert(m_Dyld && "Must call initialize dyld before!"); + return m_Dyld->searchLibrariesForSymbol(mangledName, searchSystem); +} + +std::string DynamicLibraryManager::getSymbolLocation(void* func) { +#if defined(__CYGWIN__) && defined(__GNUC__) + return {}; +#elif defined(_WIN32) + MEMORY_BASIC_INFORMATION mbi; + if (!VirtualQuery(func, &mbi, sizeof(mbi))) + return {}; + + HMODULE hMod = (HMODULE)mbi.AllocationBase; + char moduleName[MAX_PATH]; + + if (!GetModuleFileNameA(hMod, moduleName, sizeof(moduleName))) + return {}; + + return cached_realpath(moduleName); + +#else + // assume we have defined HAVE_DLFCN_H and HAVE_DLADDR + Dl_info info; + if (dladdr((void*)func, &info) == 0) { + // Not in a known shared library, let's give up + return {}; + } else { + std::string result = cached_realpath(info.dli_fname); + if (!result.empty()) + return result; + + // Else absolute path. For all we know that's a binary. + // Some people have dictionaries in binaries, this is how we find their + // path: (see also https://stackoverflow.com/a/1024937/6182509) +#if defined(__APPLE__) + char buf[PATH_MAX] = {0}; + uint32_t bufsize = sizeof(buf); + if (_NSGetExecutablePath(buf, &bufsize) >= 0) + return cached_realpath(buf); + return cached_realpath(info.dli_fname); +#elif defined(LLVM_ON_UNIX) + char buf[PATH_MAX] = {0}; + // Cross our fingers that /proc/self/exe exists. + if (readlink("/proc/self/exe", buf, sizeof(buf)) > 0) + return cached_realpath(buf); + std::string pipeCmd = std::string("which \"") + info.dli_fname + "\""; + FILE* pipe = popen(pipeCmd.c_str(), "r"); + if (!pipe) + return cached_realpath(info.dli_fname); + while (fgets(buf, sizeof(buf), pipe)) + result += buf; + + pclose(pipe); + return cached_realpath(result); +#else +#error "Unsupported platform." +#endif + return {}; + } +#endif +} + +} // namespace Cpp diff --git a/lib/Interpreter/Paths.cpp b/lib/CppInterOp/Paths.cpp similarity index 59% rename from lib/Interpreter/Paths.cpp rename to lib/CppInterOp/Paths.cpp index 17abb633e..78732e7a7 100644 --- a/lib/Interpreter/Paths.cpp +++ b/lib/CppInterOp/Paths.cpp @@ -28,117 +28,116 @@ namespace utils { namespace platform { #if defined(LLVM_ON_UNIX) - const char* const kEnvDelim = ":"; +const char* const kEnvDelim = ":"; #elif defined(_WIN32) - const char* const kEnvDelim = ";"; +const char* const kEnvDelim = ";"; #else - #error "Unknown platform (environmental delimiter)" +#error "Unknown platform (environmental delimiter)" #endif #if defined(LLVM_ON_UNIX) - bool Popen(const std::string& Cmd, llvm::SmallVectorImpl& Buf, - bool RdE) { - if (FILE* PF = ::popen(RdE ? (Cmd + " 2>&1").c_str() : Cmd.c_str(), "r")) { - Buf.resize(0); - const size_t Chunk = Buf.capacity_in_bytes(); - while (true) { - const size_t Len = Buf.size(); - Buf.resize(Len + Chunk); - const size_t R = ::fread(&Buf[Len], sizeof(char), Chunk, PF); - if (R < Chunk) { - Buf.resize(Len + R); - break; - } +bool Popen(const std::string& Cmd, llvm::SmallVectorImpl& Buf, bool RdE) { + if (FILE* PF = ::popen(RdE ? (Cmd + " 2>&1").c_str() : Cmd.c_str(), "r")) { + Buf.resize(0); + const size_t Chunk = Buf.capacity_in_bytes(); + while (true) { + const size_t Len = Buf.size(); + Buf.resize(Len + Chunk); + const size_t R = ::fread(&Buf[Len], sizeof(char), Chunk, PF); + if (R < Chunk) { + Buf.resize(Len + R); + break; } - ::pclose(PF); - return !Buf.empty(); } - return false; + ::pclose(PF); + return !Buf.empty(); } + return false; +} #endif - bool GetSystemLibraryPaths(llvm::SmallVectorImpl& Paths) { +bool GetSystemLibraryPaths(llvm::SmallVectorImpl& Paths) { #if defined(__APPLE__) || defined(__CYGWIN__) - Paths.push_back("/usr/local/lib/"); - Paths.push_back("/usr/X11R6/lib/"); - Paths.push_back("/usr/lib/"); - Paths.push_back("/lib/"); + Paths.push_back("/usr/local/lib/"); + Paths.push_back("/usr/X11R6/lib/"); + Paths.push_back("/usr/lib/"); + Paths.push_back("/lib/"); #ifndef __APPLE__ - Paths.push_back("/lib/x86_64-linux-gnu/"); - Paths.push_back("/usr/local/lib64/"); - Paths.push_back("/usr/lib64/"); - Paths.push_back("/lib64/"); + Paths.push_back("/lib/x86_64-linux-gnu/"); + Paths.push_back("/usr/local/lib64/"); + Paths.push_back("/usr/lib64/"); + Paths.push_back("/lib64/"); #endif #elif defined(LLVM_ON_UNIX) - llvm::SmallString<1024> Buf; - platform::Popen("LD_DEBUG=libs LD_PRELOAD=DOESNOTEXIST ls", Buf, true); - const llvm::StringRef Result = Buf.str(); - - const std::size_t NPos = std::string::npos; - const std::size_t LD = Result.find("(LD_LIBRARY_PATH)"); - std::size_t From = Result.find("search path=", LD == NPos ? 0 : LD); - if (From != NPos) { - std::size_t To = Result.find("(system search path)", From); - if (To != NPos) { - From += 12; - while (To > From && isspace(Result[To - 1])) - --To; - std::string SysPath = Result.substr(From, To - From).str(); - SysPath.erase(std::remove_if(SysPath.begin(), SysPath.end(), ::isspace), - SysPath.end()); - - llvm::SmallVector CurPaths; - SplitPaths(SysPath, CurPaths); - for (const auto& Path : CurPaths) - Paths.push_back(Path.str()); - } + llvm::SmallString<1024> Buf; + platform::Popen("LD_DEBUG=libs LD_PRELOAD=DOESNOTEXIST ls", Buf, true); + const llvm::StringRef Result = Buf.str(); + + const std::size_t NPos = std::string::npos; + const std::size_t LD = Result.find("(LD_LIBRARY_PATH)"); + std::size_t From = Result.find("search path=", LD == NPos ? 0 : LD); + if (From != NPos) { + std::size_t To = Result.find("(system search path)", From); + if (To != NPos) { + From += 12; + while (To > From && isspace(Result[To - 1])) + --To; + std::string SysPath = Result.substr(From, To - From).str(); + SysPath.erase(std::remove_if(SysPath.begin(), SysPath.end(), ::isspace), + SysPath.end()); + + llvm::SmallVector CurPaths; + SplitPaths(SysPath, CurPaths); + for (const auto& Path : CurPaths) + Paths.push_back(Path.str()); } -#endif - return true; } +#endif + return true; +} - std::string NormalizePath(const std::string& Path) { +std::string NormalizePath(const std::string& Path) { - llvm::SmallString<256> Buffer; - std::error_code EC = llvm::sys::fs::real_path(Path, Buffer, true); - if (EC) - return std::string(); - return std::string(Buffer.str()); - } + llvm::SmallString<256> Buffer; + std::error_code EC = llvm::sys::fs::real_path(Path, Buffer, true); + if (EC) + return std::string(); + return std::string(Buffer.str()); +} #if defined(LLVM_ON_UNIX) - static void DLErr(std::string* Err) { - if (!Err) - return; - if (const char* DyLibError = ::dlerror()) - *Err = DyLibError; - } +static void DLErr(std::string* Err) { + if (!Err) + return; + if (const char* DyLibError = ::dlerror()) + *Err = DyLibError; +} - void* DLOpen(const std::string& Path, std::string* Err /* = nullptr */) { - void* Lib = ::dlopen(Path.c_str(), RTLD_LAZY | RTLD_GLOBAL); - DLErr(Err); - return Lib; - } +void* DLOpen(const std::string& Path, std::string* Err /* = nullptr */) { + void* Lib = ::dlopen(Path.c_str(), RTLD_LAZY | RTLD_GLOBAL); + DLErr(Err); + return Lib; +} - void DLClose(void* Lib, std::string* Err /* = nullptr*/) { - ::dlclose(Lib); - DLErr(Err); - } +void DLClose(void* Lib, std::string* Err /* = nullptr*/) { + ::dlclose(Lib); + DLErr(Err); +} #elif defined(_WIN32) - void* DLOpen(const std::string& Path, std::string* Err /* = nullptr */) { - auto lib = llvm::sys::DynamicLibrary::getLibrary(Path.c_str(), Err); - return lib.getOSSpecificHandle(); - } +void* DLOpen(const std::string& Path, std::string* Err /* = nullptr */) { + auto lib = llvm::sys::DynamicLibrary::getLibrary(Path.c_str(), Err); + return lib.getOSSpecificHandle(); +} - void DLClose(void* Lib, std::string* Err /* = nullptr*/) { - auto dl = llvm::sys::DynamicLibrary(Lib); - llvm::sys::DynamicLibrary::closeLibrary(dl); - if (Err) { - *Err = std::string(); - } +void DLClose(void* Lib, std::string* Err /* = nullptr*/) { + auto dl = llvm::sys::DynamicLibrary(Lib); + llvm::sys::DynamicLibrary::closeLibrary(dl); + if (Err) { + *Err = std::string(); } +} #endif } // namespace platform @@ -158,21 +157,25 @@ void CopyIncludePaths(const clang::HeaderSearchOptions& Opts, /// User specified include entries. for (unsigned i = 0, e = Opts.UserEntries.size(); i != e; ++i) { - const HeaderSearchOptions::Entry &E = Opts.UserEntries[i]; + const HeaderSearchOptions::Entry& E = Opts.UserEntries[i]; if (E.IsFramework && E.Group != frontend::Angled) llvm::report_fatal_error("Invalid option set!"); switch (E.Group) { case frontend::After: - if (withFlags) incpaths.push_back("-idirafter"); + if (withFlags) + incpaths.push_back("-idirafter"); break; case frontend::Quoted: - if (withFlags) incpaths.push_back("-iquote"); + if (withFlags) + incpaths.push_back("-iquote"); break; case frontend::System: - if (!withSystem) continue; - if (withFlags) incpaths.push_back("-isystem"); + if (!withSystem) + continue; + if (withFlags) + incpaths.push_back("-isystem"); break; // Option was removed in llvm 20. Git log message below. // git log --grep="index-header" @@ -192,46 +195,61 @@ void CopyIncludePaths(const clang::HeaderSearchOptions& Opts, #if CLANG_VERSION_MAJOR < 20 case frontend::IndexHeaderMap: - if (!withSystem) continue; - if (withFlags) incpaths.push_back("-index-header-map"); - if (withFlags) incpaths.push_back(E.IsFramework? "-F" : "-I"); + if (!withSystem) + continue; + if (withFlags) + incpaths.push_back("-index-header-map"); + if (withFlags) + incpaths.push_back(E.IsFramework ? "-F" : "-I"); break; #endif case frontend::CSystem: - if (!withSystem) continue; - if (withFlags) incpaths.push_back("-c-isystem"); + if (!withSystem) + continue; + if (withFlags) + incpaths.push_back("-c-isystem"); break; case frontend::ExternCSystem: - if (!withSystem) continue; - if (withFlags) incpaths.push_back("-extern-c-isystem"); + if (!withSystem) + continue; + if (withFlags) + incpaths.push_back("-extern-c-isystem"); break; case frontend::CXXSystem: - if (!withSystem) continue; - if (withFlags) incpaths.push_back("-cxx-isystem"); + if (!withSystem) + continue; + if (withFlags) + incpaths.push_back("-cxx-isystem"); break; case frontend::ObjCSystem: - if (!withSystem) continue; - if (withFlags) incpaths.push_back("-objc-isystem"); + if (!withSystem) + continue; + if (withFlags) + incpaths.push_back("-objc-isystem"); break; case frontend::ObjCXXSystem: - if (!withSystem) continue; - if (withFlags) incpaths.push_back("-objcxx-isystem"); + if (!withSystem) + continue; + if (withFlags) + incpaths.push_back("-objcxx-isystem"); break; case frontend::Angled: - if (withFlags) incpaths.push_back(E.IsFramework ? "-F" : "-I"); + if (withFlags) + incpaths.push_back(E.IsFramework ? "-F" : "-I"); break; } incpaths.push_back(E.Path); } if (withSystem && !Opts.ResourceDir.empty()) { - if (withFlags) incpaths.push_back("-resource-dir"); + if (withFlags) + incpaths.push_back("-resource-dir"); incpaths.push_back(Opts.ResourceDir); } if (withSystem && withFlags && !Opts.ModuleCachePath.empty()) { @@ -251,12 +269,12 @@ void CopyIncludePaths(const clang::HeaderSearchOptions& Opts, void LogNonExistentDirectory(llvm::StringRef Path) { #define DEBUG_TYPE "LogNonExistentDirectory" LLVM_DEBUG(dbgs() << " ignoring nonexistent directory \"" << Path << "\"\n"); -#undef DEBUG_TYPE +#undef DEBUG_TYPE } bool SplitPaths(llvm::StringRef PathStr, - llvm::SmallVectorImpl& Paths, - SplitMode Mode, llvm::StringRef Delim, bool Verbose) { + llvm::SmallVectorImpl& Paths, SplitMode Mode, + llvm::StringRef Delim, bool Verbose) { #define DEBUG_TYPE "SplitPths" assert(Delim.size() && "Splitting without a delimiter"); @@ -274,11 +292,11 @@ bool SplitPaths(llvm::StringRef PathStr, bool Exists = llvm::sys::fs::is_directory(Split.first); #if defined(_WIN32) - // Because drive letters will have a colon we have to make sure the split - // occurs at a colon not followed by a path separator. - if (!Exists && WindowsColon && Split.first.size()==1) { - // Both clang and cl.exe support '\' and '/' path separators. - if (Split.second.front() == '\\' || Split.second.front() == '/') { + // Because drive letters will have a colon we have to make sure the split + // occurs at a colon not followed by a path separator. + if (!Exists && WindowsColon && Split.first.size() == 1) { + // Both clang and cl.exe support '\' and '/' path separators. + if (Split.second.front() == '\\' || Split.second.front() == '/') { const std::pair Tmp = Split.second.split(Delim); // Split.first = 'C', but we want 'C:', so Tmp.first.size()+2 @@ -286,8 +304,8 @@ bool SplitPaths(llvm::StringRef PathStr, llvm::StringRef(Split.first.data(), Tmp.first.size() + 2); Split.second = Tmp.second; Exists = llvm::sys::fs::is_directory(Split.first); + } } - } #endif AllExisted = AllExisted && Exists; @@ -323,7 +341,7 @@ bool SplitPaths(llvm::StringRef PathStr, // Trim trailing sep in case of A:B:C:D: if (!PathStr.empty() && PathStr.ends_with(Delim)) - PathStr = PathStr.substr(0, PathStr.size()-Delim.size()); + PathStr = PathStr.substr(0, PathStr.size() - Delim.size()); if (!PathStr.empty()) { if (!llvm::sys::fs::is_directory(PathStr)) { @@ -338,12 +356,12 @@ bool SplitPaths(llvm::StringRef PathStr, return AllExisted; -#undef DEBUG_TYPE +#undef DEBUG_TYPE } -void AddIncludePaths(llvm::StringRef PathStr, - clang::HeaderSearchOptions& HOpts, - const char* Delim /* = Cpp::utils::platform::kEnvDelim */) { +void AddIncludePaths( + llvm::StringRef PathStr, clang::HeaderSearchOptions& HOpts, + const char* Delim /* = Cpp::utils::platform::kEnvDelim */) { #define DEBUG_TYPE "AddIncludePaths" llvm::SmallVector Paths; @@ -367,7 +385,8 @@ void AddIncludePaths(llvm::StringRef PathStr, const bool IsFramework = false; const bool IsSysRootRelative = true; for (llvm::StringRef Path : PathsChecked) - HOpts.AddPath(Path, clang::frontend::Angled, IsFramework, IsSysRootRelative); + HOpts.AddPath(Path, clang::frontend::Angled, IsFramework, + IsSysRootRelative); if (HOpts.Verbose) { LLVM_DEBUG(dbgs() << "Added include paths:\n"); @@ -375,7 +394,7 @@ void AddIncludePaths(llvm::StringRef PathStr, LLVM_DEBUG(dbgs() << " " << Path << "\n"); } -#undef DEBUG_TYPE +#undef DEBUG_TYPE } } // namespace utils diff --git a/lib/Interpreter/Paths.h b/lib/CppInterOp/Paths.h similarity index 100% rename from lib/Interpreter/Paths.h rename to lib/CppInterOp/Paths.h diff --git a/lib/Interpreter/exports.ld b/lib/CppInterOp/exports.ld similarity index 100% rename from lib/Interpreter/exports.ld rename to lib/CppInterOp/exports.ld diff --git a/lib/Interpreter/CppInterOp.cpp b/lib/Interpreter/CppInterOp.cpp deleted file mode 100755 index 1710b91b6..000000000 --- a/lib/Interpreter/CppInterOp.cpp +++ /dev/null @@ -1,3760 +0,0 @@ -//--------------------------------------------------------------------*- C++ -*- -// CLING - the C++ LLVM-based InterpreterG :) -// author: Vassil Vassilev -// -// This file is dual-licensed: you can choose to license it under the University -// of Illinois Open Source License or the GNU Lesser General Public License. See -// LICENSE.TXT for details. -//------------------------------------------------------------------------------ - -#include "clang/Interpreter/CppInterOp.h" - -#include "Compatibility.h" - -#include "clang/AST/CXXInheritance.h" -#include "clang/AST/Decl.h" -#include "clang/AST/DeclAccessPair.h" -#include "clang/AST/DeclBase.h" -#include "clang/AST/DeclCXX.h" -#include "clang/AST/DeclarationName.h" -#include "clang/AST/Expr.h" -#include "clang/AST/ExprCXX.h" -#include "clang/AST/GlobalDecl.h" -#include "clang/AST/Mangle.h" -#include "clang/AST/NestedNameSpecifier.h" -#include "clang/AST/QualTypeNames.h" -#include "clang/AST/RecordLayout.h" -#include "clang/AST/Stmt.h" -#include "clang/AST/Type.h" -#include "clang/Basic/DiagnosticSema.h" -#include "clang/Basic/Linkage.h" -#include "clang/Basic/OperatorKinds.h" -#include "clang/Basic/SourceLocation.h" -#include "clang/Basic/Specifiers.h" -#include "clang/Basic/Version.h" -#include "clang/Frontend/CompilerInstance.h" -#include "clang/Sema/Lookup.h" -#include "clang/Sema/Overload.h" -#include "clang/Sema/Ownership.h" -#include "clang/Sema/Sema.h" -#if CLANG_VERSION_MAJOR >= 19 -#include "clang/Sema/Redeclaration.h" -#endif -#include "clang/Sema/TemplateDeduction.h" - -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Demangle/Demangle.h" -#include "llvm/Support/Casting.h" -#include "llvm/Support/Debug.h" -#include "llvm/Support/raw_os_ostream.h" - -#include -#include -#include -#include -#include -#include - -// Stream redirect. -#ifdef _WIN32 -#include -#ifndef STDOUT_FILENO -#define STDOUT_FILENO 1 -// For exec(). -#include -#define popen(x, y) (_popen(x, y)) -#define pclose (_pclose) -#endif -#else -#include -#include -#endif // WIN32 - -#include - -namespace Cpp { - - using namespace clang; - using namespace llvm; - using namespace std; - - // Flag to indicate ownership when an external interpreter instance is used. - static bool OwningSInterpreter = true; - static compat::Interpreter* sInterpreter = nullptr; - // Valgrind complains about __cxa_pure_virtual called when deleting - // llvm::SectionMemoryManager::~SectionMemoryManager as part of the dtor chain - // of the Interpreter. - // This might fix the issue https://reviews.llvm.org/D107087 - // FIXME: For now we just leak the Interpreter. - struct InterpDeleter { - ~InterpDeleter() = default; - } Deleter; - - static compat::Interpreter& getInterp() { - assert(sInterpreter && - "Interpreter instance must be set before calling this!"); - return *sInterpreter; - } - 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 Valid = true; - if (Cpp::IsConstructor(m_FD)) { - assert(result && "Must pass the location of the created object!"); - Valid &= (bool)result; - } - if (Cpp::GetFunctionRequiredArgs(m_FD) > args.m_ArgSize) { - assert(0 && "Must pass at least the minimal number of args!"); - Valid = false; - } - if (args.m_ArgSize) { - assert(args.m_Args != nullptr && "Must pass an argument list!"); - Valid &= (bool)args.m_Args; - } - if (!Cpp::IsConstructor(m_FD) && !Cpp::IsDestructor(m_FD) && - Cpp::IsMethod(m_FD) && !Cpp::IsStaticMethod(m_FD)) { - assert(self && "Must pass the pointer to object"); - Valid &= (bool)self; - } - const auto* FD = cast((const Decl*)m_FD); - if (!FD->getReturnType()->isVoidType() && !result) { - assert(0 && "We are discarding the return type of the function!"); - Valid = false; - } - assert(m_Kind != kDestructorCall && "Wrong overload!"); - Valid &= m_Kind != kDestructorCall; - return Valid; - } - - void JitCall::ReportInvokeStart(void* result, ArgList args, void* self) const{ - std::string Name; - llvm::raw_string_ostream OS(Name); - auto FD = (const FunctionDecl*) m_FD; - FD->getNameForDiagnostic(OS, FD->getASTContext().getPrintingPolicy(), - /*Qualified=*/true); - LLVM_DEBUG(dbgs() << "Run '" << Name - << "', compiled at: " << (void*) m_GenericCall - << " with result at: " << result - << " , args at: " << args.m_Args - << " , arg count: " << args.m_ArgSize - << " , self at: " << self << "\n"; - ); - } - - void JitCall::ReportInvokeStart(void* object, unsigned long nary, - int withFree) const { - std::string Name; - llvm::raw_string_ostream OS(Name); - auto FD = (const FunctionDecl*) m_FD; - FD->getNameForDiagnostic(OS, FD->getASTContext().getPrintingPolicy(), - /*Qualified=*/true); - LLVM_DEBUG(dbgs() << "Finish '" << Name - << "', compiled at: " << (void*) m_DestructorCall); - } - -#undef DEBUG_TYPE - - std::string GetVersion() { - const char* const VERSION = CPPINTEROP_VERSION; - std::string fullVersion = "CppInterOp version"; - fullVersion += VERSION; - fullVersion += "\n (based on " -#ifdef CPPINTEROP_USE_CLING - "cling "; -#else - "clang-repl"; -#endif // CPPINTEROP_USE_CLING - return fullVersion + "[" + clang::getClangFullVersion() + "])\n"; - } - - std::string Demangle(const std::string& mangled_name) { -#if CLANG_VERSION_MAJOR > 16 -#ifdef _WIN32 - std::string demangle = microsoftDemangle(mangled_name, nullptr, nullptr); -#else - std::string demangle = itaniumDemangle(mangled_name); -#endif -#else -#ifdef _WIN32 - std::string demangle = microsoftDemangle(mangled_name.c_str(), nullptr, - nullptr, nullptr, nullptr); -#else - std::string demangle = - itaniumDemangle(mangled_name.c_str(), nullptr, nullptr, nullptr); -#endif -#endif - return demangle; - } - - void EnableDebugOutput(bool value/* =true*/) { - llvm::DebugFlag = value; - } - - bool IsDebugOutputEnabled() { - return llvm::DebugFlag; - } - - bool IsAggregate(TCppScope_t scope) { - Decl *D = static_cast(scope); - - // Aggregates are only arrays or tag decls. - if (ValueDecl *ValD = dyn_cast(D)) - if (ValD->getType()->isArrayType()) - return true; - - // struct, class, union - if (CXXRecordDecl *CXXRD = dyn_cast(D)) - return CXXRD->isAggregate(); - - return false; - } - - bool IsNamespace(TCppScope_t scope) { - Decl *D = static_cast(scope); - return isa(D); - } - - bool IsClass(TCppScope_t scope) { - Decl *D = static_cast(scope); - return isa(D); - } - - bool IsFunction(TCppScope_t scope) { - Decl* D = static_cast(scope); - return isa(D); - } - - bool IsFunctionPointerType(TCppType_t type) { - QualType QT = QualType::getFromOpaquePtr(type); - return QT->isFunctionPointerType(); - } - - bool IsClassPolymorphic(TCppScope_t klass) { - Decl* D = static_cast(klass); - if (auto* CXXRD = llvm::dyn_cast(D)) - if (auto* CXXRDD = CXXRD->getDefinition()) - return CXXRDD->isPolymorphic(); - return false; - } - - static SourceLocation GetValidSLoc(Sema& semaRef) { - auto& SM = semaRef.getSourceManager(); - return SM.getLocForStartOfFile(SM.getMainFileID()); - } - - // See TClingClassInfo::IsLoaded - bool IsComplete(TCppScope_t scope) { - if (!scope) - return false; - - Decl *D = static_cast(scope); - - if (isa(D)) { - QualType QT = QualType::getFromOpaquePtr(GetTypeFromScope(scope)); - clang::Sema &S = getSema(); - SourceLocation fakeLoc = GetValidSLoc(S); -#ifdef CPPINTEROP_USE_CLING - cling::Interpreter::PushTransactionRAII RAII(&getInterp()); -#endif // CPPINTEROP_USE_CLING - return S.isCompleteType(fakeLoc, QT); - } - - if (auto *CXXRD = dyn_cast(D)) - return CXXRD->hasDefinition(); - else if (auto *TD = dyn_cast(D)) - return TD->getDefinition(); - - // Everything else is considered complete. - return true; - } - - size_t SizeOf(TCppScope_t scope) { - assert (scope); - if (!IsComplete(scope)) - return 0; - - if (auto *RD = dyn_cast(static_cast(scope))) { - ASTContext &Context = RD->getASTContext(); - const ASTRecordLayout &Layout = Context.getASTRecordLayout(RD); - return Layout.getSize().getQuantity(); - } - - return 0; - } - - bool IsBuiltin(TCppType_t type) { - QualType Ty = QualType::getFromOpaquePtr(type); - if (Ty->isBuiltinType() || Ty->isAnyComplexType()) - return true; - // FIXME: Figure out how to avoid the string comparison. - return llvm::StringRef(Ty.getAsString()).contains("complex"); - } - - bool IsTemplate(TCppScope_t handle) { - auto *D = (clang::Decl *)handle; - return llvm::isa_and_nonnull(D); - } - - bool IsTemplateSpecialization(TCppScope_t handle) { - auto *D = (clang::Decl *)handle; - return llvm::isa_and_nonnull(D); - } - - bool IsTypedefed(TCppScope_t handle) { - auto *D = (clang::Decl *)handle; - return llvm::isa_and_nonnull(D); - } - - bool IsAbstract(TCppType_t klass) { - auto *D = (clang::Decl *)klass; - if (auto *CXXRD = llvm::dyn_cast_or_null(D)) - return CXXRD->isAbstract(); - - return false; - } - - bool IsEnumScope(TCppScope_t handle) { - auto *D = (clang::Decl *)handle; - return llvm::isa_and_nonnull(D); - } - - bool IsEnumConstant(TCppScope_t handle) { - auto *D = (clang::Decl *)handle; - return llvm::isa_and_nonnull(D); - } - - bool IsEnumType(TCppType_t type) { - QualType QT = QualType::getFromOpaquePtr(type); - return QT->isEnumeralType(); - } - - static bool isSmartPointer(const RecordType* RT) { - auto IsUseCountPresent = [](const RecordDecl *Record) { - ASTContext &C = Record->getASTContext(); - return !Record->lookup(&C.Idents.get("use_count")).empty(); - }; - auto IsOverloadedOperatorPresent = [](const RecordDecl *Record, - OverloadedOperatorKind Op) { - ASTContext &C = Record->getASTContext(); - DeclContextLookupResult Result = - Record->lookup(C.DeclarationNames.getCXXOperatorName(Op)); - return !Result.empty(); - }; - - const RecordDecl *Record = RT->getDecl(); - if (IsUseCountPresent(Record)) - return true; - - bool foundStarOperator = IsOverloadedOperatorPresent(Record, OO_Star); - bool foundArrowOperator = IsOverloadedOperatorPresent(Record, OO_Arrow); - if (foundStarOperator && foundArrowOperator) - return true; - - const CXXRecordDecl *CXXRecord = dyn_cast(Record); - if (!CXXRecord) - return false; - - auto FindOverloadedOperators = [&](const CXXRecordDecl *Base) { - // If we find use_count, we are done. - if (IsUseCountPresent(Base)) - return false; // success. - if (!foundStarOperator) - foundStarOperator = IsOverloadedOperatorPresent(Base, OO_Star); - if (!foundArrowOperator) - foundArrowOperator = IsOverloadedOperatorPresent(Base, OO_Arrow); - if (foundStarOperator && foundArrowOperator) - return false; // success. - return true; - }; - - return !CXXRecord->forallBases(FindOverloadedOperators); - } - - bool IsSmartPtrType(TCppType_t type) { - QualType QT = QualType::getFromOpaquePtr(type); - if (const RecordType *RT = QT->getAs()) { - // Add quick checks for the std smart prts to cover most of the cases. - std::string typeString = GetTypeAsString(type); - llvm::StringRef tsRef(typeString); - if (tsRef.starts_with("std::unique_ptr") || - tsRef.starts_with("std::shared_ptr") || - tsRef.starts_with("std::weak_ptr")) - return true; - return isSmartPointer(RT); - } - return false; - } - - TCppType_t GetIntegerTypeFromEnumScope(TCppScope_t handle) { - auto *D = (clang::Decl *)handle; - if (auto *ED = llvm::dyn_cast_or_null(D)) { - return ED->getIntegerType().getAsOpaquePtr(); - } - - return 0; - } - - TCppType_t GetIntegerTypeFromEnumType(TCppType_t enum_type) { - if (!enum_type) - return nullptr; - - QualType QT = QualType::getFromOpaquePtr(enum_type); - if (auto *ET = QT->getAs()) - return ET->getDecl()->getIntegerType().getAsOpaquePtr(); - - return nullptr; - } - - std::vector GetEnumConstants(TCppScope_t handle) { - auto *D = (clang::Decl *)handle; - - if (auto *ED = llvm::dyn_cast_or_null(D)) { - std::vector enum_constants; - for (auto *ECD : ED->enumerators()) { - enum_constants.push_back((TCppScope_t) ECD); - } - - return enum_constants; - } - - return {}; - } - - TCppType_t GetEnumConstantType(TCppScope_t handle) { - if (!handle) - return nullptr; - - auto *D = (clang::Decl *)handle; - if (auto *ECD = llvm::dyn_cast(D)) - return ECD->getType().getAsOpaquePtr(); - - return 0; - } - - TCppIndex_t GetEnumConstantValue(TCppScope_t handle) { - auto *D = (clang::Decl *)handle; - if (auto *ECD = llvm::dyn_cast_or_null(D)) { - const llvm::APSInt& Val = ECD->getInitVal(); - return Val.getExtValue(); - } - return 0; - } - - size_t GetSizeOfType(TCppType_t type) { - QualType QT = QualType::getFromOpaquePtr(type); - if (const TagType *TT = QT->getAs()) - return SizeOf(TT->getDecl()); - - // FIXME: Can we get the size of a non-tag type? - auto TI = getSema().getASTContext().getTypeInfo(QT); - size_t TypeSize = TI.Width; - return TypeSize/8; - } - - bool IsVariable(TCppScope_t scope) { - auto *D = (clang::Decl *)scope; - return llvm::isa_and_nonnull(D); - } - - std::string GetName(TCppType_t klass) { - auto *D = (clang::NamedDecl *) klass; - - if (llvm::isa_and_nonnull(D)) { - return ""; - } - - if (auto *ND = llvm::dyn_cast_or_null(D)) { - return ND->getNameAsString(); - } - - return ""; - } - - std::string GetCompleteName(TCppType_t klass) - { - auto &C = getSema().getASTContext(); - auto *D = (Decl *) klass; - - if (auto *ND = llvm::dyn_cast_or_null(D)) { - if (auto *TD = llvm::dyn_cast(ND)) { - std::string type_name; - QualType QT = C.getTagDeclType(TD); - PrintingPolicy Policy = C.getPrintingPolicy(); - Policy.SuppressUnwrittenScope = true; - Policy.SuppressScope = true; - Policy.AnonymousTagLocations = false; - QT.getAsStringInternal(type_name, Policy); - - return type_name; - } - - return ND->getNameAsString(); - } - - if (llvm::isa_and_nonnull(D)) { - return ""; - } - - return ""; - } - - std::string GetQualifiedName(TCppType_t klass) - { - auto *D = (Decl *) klass; - if (auto *ND = llvm::dyn_cast_or_null(D)) { - return ND->getQualifiedNameAsString(); - } - - if (llvm::isa_and_nonnull(D)) { - return ""; - } - - return ""; - } - - //FIXME: Figure out how to merge with GetCompleteName. - std::string GetQualifiedCompleteName(TCppType_t klass) - { - auto &C = getSema().getASTContext(); - auto *D = (Decl *) klass; - - if (auto *ND = llvm::dyn_cast_or_null(D)) { - if (auto *TD = llvm::dyn_cast(ND)) { - std::string type_name; - QualType QT = C.getTagDeclType(TD); - QT.getAsStringInternal(type_name, C.getPrintingPolicy()); - - return type_name; - } - - return ND->getQualifiedNameAsString(); - } - - if (llvm::isa_and_nonnull(D)) { - return ""; - } - - return ""; - } - - std::vector GetUsingNamespaces(TCppScope_t scope) { - auto *D = (clang::Decl *) scope; - - if (auto *DC = llvm::dyn_cast_or_null(D)) { - std::vector namespaces; - for (auto UD : DC->using_directives()) { - namespaces.push_back((TCppScope_t) UD->getNominatedNamespace()); - } - return namespaces; - } - - return {}; - } - - TCppScope_t GetGlobalScope() - { - return getSema().getASTContext().getTranslationUnitDecl()->getFirstDecl(); - } - - static Decl *GetScopeFromType(QualType QT) { - if (auto* Type = QT.getCanonicalType().getTypePtrOrNull()) { - Type = Type->getPointeeOrArrayElementType(); - Type = Type->getUnqualifiedDesugaredType(); - if (auto *ET = llvm::dyn_cast(Type)) - return ET->getDecl(); - if (auto* FnType = llvm::dyn_cast(Type)) - Type = const_cast(FnType->getReturnType().getTypePtr()); - return Type->getAsCXXRecordDecl(); - } - return 0; - } - - TCppScope_t GetScopeFromType(TCppType_t type) - { - QualType QT = QualType::getFromOpaquePtr(type); - return (TCppScope_t) GetScopeFromType(QT); - } - - static clang::Decl* GetUnderlyingScope(clang::Decl * D) { - if (auto *TND = dyn_cast_or_null(D)) { - if (auto* Scope = GetScopeFromType(TND->getUnderlyingType())) - D = Scope; - } else if (auto* USS = dyn_cast_or_null(D)) { - if (auto* Scope = USS->getTargetDecl()) - D = Scope; - } - - return D; - } - - TCppScope_t GetUnderlyingScope(TCppScope_t scope) { - if (!scope) - return 0; - return GetUnderlyingScope((clang::Decl *) scope); - } - - TCppScope_t GetScope(const std::string &name, TCppScope_t parent) - { - // FIXME: GetScope should be replaced by a general purpose lookup - // and filter function. The function should be like GetNamed but - // also take in a filter parameter which determines which results - // to pass back - if (name == "") - return GetGlobalScope(); - - auto *ND = (NamedDecl*)GetNamed(name, parent); - - if (!ND || ND == (NamedDecl *) -1) - return 0; - - if (llvm::isa(ND) || - llvm::isa(ND) || - llvm::isa(ND) || - llvm::isa(ND)) - return (TCppScope_t)(ND->getCanonicalDecl()); - - return 0; - } - - TCppScope_t GetScopeFromCompleteName(const std::string &name) - { - std::string delim = "::"; - size_t start = 0; - size_t end = name.find(delim); - TCppScope_t curr_scope = 0; - while (end != std::string::npos) - { - curr_scope = GetScope(name.substr(start, end - start), curr_scope); - start = end + delim.length(); - end = name.find(delim, start); - } - return GetScope(name.substr(start, end), curr_scope); - } - - TCppScope_t GetNamed(const std::string &name, - TCppScope_t parent /*= nullptr*/) - { - clang::DeclContext *Within = 0; - if (parent) { - auto *D = (clang::Decl *)parent; - D = GetUnderlyingScope(D); - Within = llvm::dyn_cast(D); - } - - auto *ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); - if (ND && ND != (clang::NamedDecl*) -1) { - return (TCppScope_t)(ND->getCanonicalDecl()); - } - - return 0; - } - - TCppScope_t GetParentScope(TCppScope_t scope) - { - auto *D = (clang::Decl *) scope; - - if (llvm::isa_and_nonnull(D)) { - return 0; - } - auto *ParentDC = D->getDeclContext(); - - if (!ParentDC) - return 0; - - auto* P = clang::Decl::castFromDeclContext(ParentDC)->getCanonicalDecl(); - - if (auto* TU = llvm::dyn_cast_or_null(P)) - return (TCppScope_t)TU->getFirstDecl(); - - return (TCppScope_t)P; - } - - TCppIndex_t GetNumBases(TCppScope_t klass) - { - auto *D = (Decl *) klass; - - if (auto *CXXRD = llvm::dyn_cast_or_null(D)) { - if (CXXRD->hasDefinition()) - return CXXRD->getNumBases(); - } - - return 0; - } - - TCppScope_t GetBaseClass(TCppScope_t klass, TCppIndex_t ibase) - { - auto *D = (Decl *) klass; - auto *CXXRD = llvm::dyn_cast_or_null(D); - if (!CXXRD || CXXRD->getNumBases() <= ibase) return 0; - - auto type = (CXXRD->bases_begin() + ibase)->getType(); - if (auto RT = type->getAs()) - return (TCppScope_t)RT->getDecl(); - - return 0; - } - - // FIXME: Consider dropping this interface as it seems the same as - // IsTypeDerivedFrom. - bool IsSubclass(TCppScope_t derived, TCppScope_t base) - { - if (derived == base) - return true; - - if (!derived || !base) - return false; - - auto *derived_D = (clang::Decl *) derived; - auto *base_D = (clang::Decl *) base; - - if (!isa(derived_D) || !isa(base_D)) - return false; - - auto Derived = cast(derived_D); - auto Base = cast(base_D); - return IsTypeDerivedFrom(GetTypeFromScope(Derived), - GetTypeFromScope(Base)); - } - - // Copied from VTableBuilder.cpp - // This is an internal helper function for the CppInterOp library (as evident - // by the 'static' declaration), while the similar GetBaseClassOffset() - // function below is exposed to library users. - static unsigned ComputeBaseOffset(const ASTContext &Context, - const CXXRecordDecl *DerivedRD, - const CXXBasePath &Path) { - CharUnits NonVirtualOffset = CharUnits::Zero(); - - unsigned NonVirtualStart = 0; - const CXXRecordDecl *VirtualBase = nullptr; - - // First, look for the virtual base class. - for (int I = Path.size(), E = 0; I != E; --I) { - const CXXBasePathElement &Element = Path[I - 1]; - - if (Element.Base->isVirtual()) { - NonVirtualStart = I; - QualType VBaseType = Element.Base->getType(); - VirtualBase = VBaseType->getAsCXXRecordDecl(); - break; - } - } - - // Now compute the non-virtual offset. - for (unsigned I = NonVirtualStart, E = Path.size(); I != E; ++I) { - const CXXBasePathElement &Element = Path[I]; - - // Check the base class offset. - const ASTRecordLayout &Layout = Context.getASTRecordLayout(Element.Class); - - const CXXRecordDecl *Base = Element.Base->getType()->getAsCXXRecordDecl(); - - NonVirtualOffset += Layout.getBaseClassOffset(Base); - } - - // FIXME: This should probably use CharUnits or something. Maybe we should - // even change the base offsets in ASTRecordLayout to be specified in - // CharUnits. - //return BaseOffset(DerivedRD, VirtuaBose, aBlnVirtualOffset); - if (VirtualBase) { - const ASTRecordLayout &Layout = Context.getASTRecordLayout(DerivedRD); - CharUnits VirtualOffset = Layout.getVBaseClassOffset(VirtualBase); - return (NonVirtualOffset + VirtualOffset).getQuantity(); - } - return NonVirtualOffset.getQuantity(); - - } - - int64_t GetBaseClassOffset(TCppScope_t derived, TCppScope_t base) { - if (base == derived) - return 0; - - assert(derived || base); - - auto *DD = (Decl *) derived; - auto *BD = (Decl *) base; - if (!isa(DD) || !isa(BD)) - return -1; - CXXRecordDecl *DCXXRD = cast(DD); - CXXRecordDecl *BCXXRD = cast(BD); - CXXBasePaths Paths(/*FindAmbiguities=*/false, /*RecordPaths=*/true, - /*DetectVirtual=*/false); - DCXXRD->isDerivedFrom(BCXXRD, Paths); - - // FIXME: We might want to cache these requests as they seem expensive. - return ComputeBaseOffset(getSema().getASTContext(), DCXXRD, Paths.front()); - } - - template - static void GetClassDecls(TCppScope_t klass, - std::vector& methods) { - if (!klass) - return; - - auto* D = (clang::Decl*)klass; - - if (auto* TD = dyn_cast(D)) - D = GetScopeFromType(TD->getUnderlyingType()); - - if (!D || !isa(D)) - return; - - auto* CXXRD = dyn_cast(D); -#ifdef CPPINTEROP_USE_CLING - cling::Interpreter::PushTransactionRAII RAII(&getInterp()); -#endif // CPPINTEROP_USE_CLING - getSema().ForceDeclarationOfImplicitMembers(CXXRD); - for (Decl* DI : CXXRD->decls()) { - if (auto* MD = dyn_cast(DI)) - methods.push_back(MD); - else if (auto* USD = dyn_cast(DI)) - if (auto* MD = dyn_cast(USD->getTargetDecl())) - methods.push_back(MD); - } - } - - void GetClassMethods(TCppScope_t klass, - std::vector& methods) { - GetClassDecls(klass, methods); - } - - void GetFunctionTemplatedDecls(TCppScope_t klass, - std::vector& methods) { - GetClassDecls(klass, methods); - } - - bool HasDefaultConstructor(TCppScope_t scope) { - auto *D = (clang::Decl *) scope; - - if (auto* CXXRD = llvm::dyn_cast_or_null(D)) - return CXXRD->hasDefaultConstructor(); - - return false; - } - - TCppFunction_t GetDefaultConstructor(compat::Interpreter& interp, - TCppScope_t scope) { - if (!HasDefaultConstructor(scope)) - return nullptr; - - auto *CXXRD = (clang::CXXRecordDecl*)scope; - return interp.getCI()->getSema().LookupDefaultConstructor(CXXRD); - } - - TCppFunction_t GetDefaultConstructor(TCppScope_t scope) { - return GetDefaultConstructor(getInterp(), scope); - } - - TCppFunction_t GetDestructor(TCppScope_t scope) { - auto *D = (clang::Decl *) scope; - - if (auto *CXXRD = llvm::dyn_cast_or_null(D)) { - getSema().ForceDeclarationOfImplicitMembers(CXXRD); - return CXXRD->getDestructor(); - } - - return 0; - } - - void DumpScope(TCppScope_t scope) - { - auto *D = (clang::Decl *) scope; - D->dump(); - } - - std::vector GetFunctionsUsingName( - TCppScope_t scope, const std::string& name) - { - auto *D = (Decl *) scope; - - if (!scope || name.empty()) - return {}; - - D = GetUnderlyingScope(D); - - std::vector funcs; - llvm::StringRef Name(name); - auto &S = getSema(); - DeclarationName DName = &getASTContext().Idents.get(name); - clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, - For_Visible_Redeclaration); - - Cpp_utils::Lookup::Named(&S, R, Decl::castToDeclContext(D)); - - if (R.empty()) - return funcs; - - R.resolveKind(); - - for (auto *Found : R) - if (llvm::isa(Found)) - funcs.push_back(Found); - - return funcs; - } - - TCppType_t GetFunctionReturnType(TCppFunction_t func) - { - auto *D = (clang::Decl *) func; - if (auto* FD = llvm::dyn_cast_or_null(D)) { - QualType Type = FD->getReturnType(); - if (Type->isUndeducedAutoType() && IsTemplatedFunction(FD) && - !FD->isDefined()) { -#ifdef CPPINTEROP_USE_CLING - cling::Interpreter::PushTransactionRAII RAII(&getInterp()); -#endif - getSema().InstantiateFunctionDefinition(SourceLocation(), FD, true, - true); - Type = FD->getReturnType(); - } - return Type.getAsOpaquePtr(); - } - - if (auto* FD = llvm::dyn_cast_or_null(D)) - return (FD->getTemplatedDecl())->getReturnType().getAsOpaquePtr(); - - return 0; - } - - TCppIndex_t GetFunctionNumArgs(TCppFunction_t func) - { - auto *D = (clang::Decl *) func; - if (auto* FD = llvm::dyn_cast_or_null(D)) - return FD->getNumParams(); - - if (auto* FD = llvm::dyn_cast_or_null(D)) - return (FD->getTemplatedDecl())->getNumParams(); - - return 0; - } - - TCppIndex_t GetFunctionRequiredArgs(TCppConstFunction_t func) - { - const auto* D = static_cast(func); - if (auto* FD = llvm::dyn_cast_or_null(D)) - return FD->getMinRequiredArguments(); - - if (auto* FD = llvm::dyn_cast_or_null(D)) - return (FD->getTemplatedDecl())->getMinRequiredArguments(); - - return 0; - } - - TCppType_t GetFunctionArgType(TCppFunction_t func, TCppIndex_t iarg) - { - auto *D = (clang::Decl *) func; - - if (auto *FD = llvm::dyn_cast_or_null(D)) { - if (iarg < FD->getNumParams()) { - auto *PVD = FD->getParamDecl(iarg); - return PVD->getOriginalType().getAsOpaquePtr(); - } - } - - return 0; - } - - std::string GetFunctionSignature(TCppFunction_t func) { - if (!func) - return ""; - - auto *D = (clang::Decl *) func; - clang::FunctionDecl* FD; - - if (llvm::dyn_cast(D)) - FD = llvm::dyn_cast(D); - else if (auto* FTD = llvm::dyn_cast(D)) - FD = FTD->getTemplatedDecl(); - else - return ""; - - std::string Signature; - raw_string_ostream SS(Signature); - PrintingPolicy Policy = getASTContext().getPrintingPolicy(); - // Skip printing the body - Policy.TerseOutput = true; - Policy.FullyQualifiedName = true; - Policy.SuppressDefaultTemplateArgs = false; - FD->print(SS, Policy); - SS.flush(); - return Signature; - } - - // Internal functions that are not needed outside the library are - // encompassed in an anonymous namespace as follows. - namespace { - bool IsTemplatedFunction(Decl *D) { - return llvm::isa_and_nonnull(D); - } - - bool IsTemplateInstantiationOrSpecialization(Decl* D) { - if (auto *FD = llvm::dyn_cast_or_null(D)) { - auto TK = FD->getTemplatedKind(); - return TK == FunctionDecl::TemplatedKind:: - TK_FunctionTemplateSpecialization - || TK == FunctionDecl::TemplatedKind:: - TK_DependentFunctionTemplateSpecialization - || TK == FunctionDecl::TemplatedKind::TK_FunctionTemplate; - } - - return false; - } - } - - bool IsFunctionDeleted(TCppConstFunction_t function) { - const auto* FD = - cast(static_cast(function)); - return FD->isDeleted(); - } - - bool IsTemplatedFunction(TCppFunction_t func) - { - auto *D = (Decl *) func; - return IsTemplatedFunction(D) || IsTemplateInstantiationOrSpecialization(D); - } - - // FIXME: This lookup is broken, and should no longer be used in favour of - // `GetClassTemplatedMethods` If the candidate set returned is =1, that means - // the template function exists and >1 means overloads - bool ExistsFunctionTemplate(const std::string& name, - TCppScope_t parent) - { - DeclContext *Within = 0; - if (parent) { - auto* D = (Decl*)parent; - Within = llvm::dyn_cast(D); - } - - auto *ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); - - if ((intptr_t) ND == (intptr_t) 0) - return false; - - if ((intptr_t) ND != (intptr_t) -1) - return IsTemplatedFunction(ND) || - IsTemplateInstantiationOrSpecialization(ND); - - // FIXME: Cycle through the Decls and check if there is a templated function - return true; - } - - // Looks up all constructors in the current DeclContext - void LookupConstructors(const std::string& name, TCppScope_t parent, - std::vector& funcs) { - auto* D = (Decl*)parent; - - if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { - getSema().ForceDeclarationOfImplicitMembers(CXXRD); - DeclContextLookupResult Result = getSema().LookupConstructors(CXXRD); - // Obtaining all constructors when we intend to lookup a method under a - // scope can lead to crashes. We avoid that by accumulating constructors - // only if the Decl matches the lookup name. - for (auto* i : Result) - if (GetName(i) == name) - funcs.push_back(i); - } - } - - bool GetClassTemplatedMethods(const std::string& name, TCppScope_t parent, - std::vector& funcs) { - auto* D = (Decl*)parent; - if (!D && name.empty()) - return false; - - // Accumulate constructors - LookupConstructors(name, parent, funcs); - auto& S = getSema(); - D = GetUnderlyingScope(D); - llvm::StringRef Name(name); - DeclarationName DName = &getASTContext().Idents.get(name); - clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, - For_Visible_Redeclaration); - auto* DC = clang::Decl::castToDeclContext(D); - Cpp_utils::Lookup::Named(&S, R, DC); - - if (R.getResultKind() == clang::LookupResult::NotFound && funcs.empty()) - return false; - - // Distinct match, single Decl - else if (R.getResultKind() == clang::LookupResult::Found) { - if (IsTemplatedFunction(R.getFoundDecl())) - funcs.push_back(R.getFoundDecl()); - } - // Loop over overload set - else if (R.getResultKind() == clang::LookupResult::FoundOverloaded) { - for (auto* Found : R) - if (IsTemplatedFunction(Found)) - funcs.push_back(Found); - } - - // TODO: Handle ambiguously found LookupResult - // else if (R.getResultKind() == clang::LookupResult::Ambiguous) { - // auto kind = R.getAmbiguityKind(); - // ... - // Produce a diagnostic describing the ambiguity that resulted - // from name lookup as done in Sema::DiagnoseAmbiguousLookup - // - return !funcs.empty(); - } - - // Adapted from inner workings of Sema::BuildCallExpr - TCppFunction_t - BestOverloadFunctionMatch(const std::vector& candidates, - const std::vector& explicit_types, - const std::vector& arg_types) { - auto& S = getSema(); - auto& C = S.getASTContext(); - -#ifdef CPPINTEROP_USE_CLING - cling::Interpreter::PushTransactionRAII RAII(&getInterp()); -#endif - - // The overload resolution interfaces in Sema require a list of expressions. - // However, unlike handwritten C++, we do not always have a expression. - // Here we synthesize a placeholder expression to be able to use - // Sema::AddOverloadCandidate. Made up expressions are fine because the - // interface uses the list size and the expression types. - struct WrapperExpr : public OpaqueValueExpr { - WrapperExpr() : OpaqueValueExpr(clang::Stmt::EmptyShell()) {} - }; - auto* Exprs = new WrapperExpr[arg_types.size()]; - llvm::SmallVector Args; - Args.reserve(arg_types.size()); - size_t idx = 0; - for (auto i : arg_types) { - QualType Type = QualType::getFromOpaquePtr(i.m_Type); - ExprValueKind ExprKind = ExprValueKind::VK_PRValue; - if (Type->isReferenceType()) - ExprKind = ExprValueKind::VK_LValue; - - new (&Exprs[idx]) OpaqueValueExpr(SourceLocation::getFromRawEncoding(1), - Type.getNonReferenceType(), ExprKind); - Args.push_back(&Exprs[idx]); - ++idx; - } - - // Create a list of template arguments. - llvm::SmallVector TemplateArgs; - TemplateArgs.reserve(explicit_types.size()); - for (auto explicit_type : explicit_types) { - QualType ArgTy = QualType::getFromOpaquePtr(explicit_type.m_Type); - if (explicit_type.m_IntegralValue) { - // We have a non-type template parameter. Create an integral value from - // the string representation. - auto Res = llvm::APSInt(explicit_type.m_IntegralValue); - Res = Res.extOrTrunc(C.getIntWidth(ArgTy)); - TemplateArgs.push_back(TemplateArgument(C, Res, ArgTy)); - } else { - TemplateArgs.push_back(ArgTy); - } - } - - TemplateArgumentListInfo ExplicitTemplateArgs{}; - for (auto TA : TemplateArgs) - ExplicitTemplateArgs.addArgument( - S.getTrivialTemplateArgumentLoc(TA, QualType(), SourceLocation())); - - OverloadCandidateSet Overloads( - SourceLocation(), OverloadCandidateSet::CandidateSetKind::CSK_Normal); - - for (void* i : candidates) { - Decl* D = static_cast(i); - if (auto* FD = dyn_cast(D)) { - S.AddOverloadCandidate(FD, DeclAccessPair::make(FD, FD->getAccess()), - Args, Overloads); - } else if (auto* FTD = dyn_cast(D)) { - // AddTemplateOverloadCandidate is causing a memory leak - // It is a known bug at clang - // call stack: AddTemplateOverloadCandidate -> MakeDeductionFailureInfo - // source: - // https://github.com/llvm/llvm-project/blob/release/19.x/clang/lib/Sema/SemaOverload.cpp#L731-L756 - S.AddTemplateOverloadCandidate( - FTD, DeclAccessPair::make(FTD, FTD->getAccess()), - &ExplicitTemplateArgs, Args, Overloads); - } - } - - OverloadCandidateSet::iterator Best; - Overloads.BestViableFunction(S, SourceLocation(), Best); - - FunctionDecl* Result = Best != Overloads.end() ? Best->Function : nullptr; - delete[] Exprs; - return Result; - } - - // Gets the AccessSpecifier of the function and checks if it is equal to - // the provided AccessSpecifier. - bool CheckMethodAccess(TCppFunction_t method, AccessSpecifier AS) - { - auto *D = (Decl *) method; - if (auto *CXXMD = llvm::dyn_cast_or_null(D)) { - return CXXMD->getAccess() == AS; - } - - return false; - } - - bool IsMethod(TCppConstFunction_t method) - { - return dyn_cast_or_null( - static_cast(method)); - } - - bool IsPublicMethod(TCppFunction_t method) - { - return CheckMethodAccess(method, AccessSpecifier::AS_public); - } - - bool IsProtectedMethod(TCppFunction_t method) { - return CheckMethodAccess(method, AccessSpecifier::AS_protected); - } - - bool IsPrivateMethod(TCppFunction_t method) - { - return CheckMethodAccess(method, AccessSpecifier::AS_private); - } - - bool IsConstructor(TCppConstFunction_t method) - { - const auto* D = static_cast(method); - if (const auto* FTD = dyn_cast(D)) - return IsConstructor(FTD->getTemplatedDecl()); - return llvm::isa_and_nonnull(D); - } - - bool IsDestructor(TCppConstFunction_t method) - { - const auto* D = static_cast(method); - return llvm::isa_and_nonnull(D); - } - - bool IsStaticMethod(TCppConstFunction_t method) { - const auto* D = static_cast(method); - if (auto *CXXMD = llvm::dyn_cast_or_null(D)) { - return CXXMD->isStatic(); - } - - return false; - } - - TCppFuncAddr_t GetFunctionAddress(const char* mangled_name) { - auto& I = getInterp(); - auto FDAorErr = compat::getSymbolAddress(I, mangled_name); - if (llvm::Error Err = FDAorErr.takeError()) - llvm::consumeError(std::move(Err)); // nullptr if missing - else - return llvm::jitTargetAddressToPointer(*FDAorErr); - - return nullptr; - } - - TCppFuncAddr_t GetFunctionAddress(TCppFunction_t method) - { - auto *D = (Decl *) method; - - const auto get_mangled_name = [](FunctionDecl* FD) { - auto MangleCtxt = getASTContext().createMangleContext(); - - if (!MangleCtxt->shouldMangleDeclName(FD)) { - return FD->getNameInfo().getName().getAsString(); - } - - std::string mangled_name; - llvm::raw_string_ostream ostream(mangled_name); - - MangleCtxt->mangleName(FD, ostream); - - ostream.flush(); - delete MangleCtxt; - - return mangled_name; - }; - - if (auto* FD = llvm::dyn_cast_or_null(D)) - return GetFunctionAddress(get_mangled_name(FD).c_str()); - - return 0; - } - - bool IsVirtualMethod(TCppFunction_t method) { - auto *D = (Decl *) method; - if (auto *CXXMD = llvm::dyn_cast_or_null(D)) { - return CXXMD->isVirtual(); - } - - return false; - } - - void GetDatamembers(TCppScope_t scope, - std::vector& datamembers) { - auto *D = (Decl *) scope; - - if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { - getSema().ForceDeclarationOfImplicitMembers(CXXRD); - - llvm::SmallVector stack_begin; - llvm::SmallVector stack_end; - stack_begin.push_back(CXXRD->decls_begin()); - stack_end.push_back(CXXRD->decls_end()); - while (!stack_begin.empty()) { - if (stack_begin.back() == stack_end.back()) { - stack_begin.pop_back(); - stack_end.pop_back(); - continue; - } - Decl* D = *(stack_begin.back()); - if (auto* FD = llvm::dyn_cast(D)) { - if (FD->isAnonymousStructOrUnion()) { - if (const auto* RT = FD->getType()->getAs()) { - if (auto* CXXRD = llvm::dyn_cast(RT->getDecl())) { - stack_begin.back()++; - stack_begin.push_back(CXXRD->decls_begin()); - stack_end.push_back(CXXRD->decls_end()); - continue; - } - } - } - datamembers.push_back((TCppScope_t)D); - - } else if (auto* USD = llvm::dyn_cast(D)) { - if (llvm::isa(USD->getTargetDecl())) - datamembers.push_back(USD); - } - stack_begin.back()++; - } - } - } - - void GetStaticDatamembers(TCppScope_t scope, - std::vector& datamembers) { - GetClassDecls(scope, datamembers); - } - - void GetEnumConstantDatamembers(TCppScope_t scope, - std::vector& datamembers, - bool include_enum_class) { - std::vector EDs; - GetClassDecls(scope, EDs); - for (TCppScope_t i : EDs) { - auto* ED = static_cast(i); - - bool is_class_tagged = ED->isScopedUsingClassTag(); - if (is_class_tagged && !include_enum_class) - continue; - - std::copy(ED->enumerator_begin(), ED->enumerator_end(), - std::back_inserter(datamembers)); - } - } - - TCppScope_t LookupDatamember(const std::string& name, TCppScope_t parent) { - clang::DeclContext *Within = 0; - if (parent) { - auto *D = (clang::Decl *)parent; - Within = llvm::dyn_cast(D); - } - - auto *ND = Cpp_utils::Lookup::Named(&getSema(), name, Within); - if (ND && ND != (clang::NamedDecl*) -1) { - if (llvm::isa_and_nonnull(ND)) { - return (TCppScope_t)ND; - } - } - - return 0; - } - - TCppType_t GetVariableType(TCppScope_t var) { - auto* D = static_cast(var); - - if (auto DD = llvm::dyn_cast_or_null(D)) { - QualType QT = DD->getType(); - - // Check if the type is a typedef type - if (QT->isTypedefNameType()) { - return QT.getAsOpaquePtr(); - } - - // Else, return the canonical type - QT = QT.getCanonicalType(); - return QT.getAsOpaquePtr(); - } - - if (auto* ECD = llvm::dyn_cast_or_null(D)) - return ECD->getType().getAsOpaquePtr(); - - return 0; - } - - intptr_t GetVariableOffset(compat::Interpreter& I, Decl* D, - CXXRecordDecl* BaseCXXRD) { - if (!D) - return 0; - - auto& C = I.getSema().getASTContext(); - - if (auto* FD = llvm::dyn_cast(D)) { - clang::RecordDecl* FieldParentRecordDecl = FD->getParent(); - intptr_t offset = - C.toCharUnitsFromBits(C.getFieldOffset(FD)).getQuantity(); - while (FieldParentRecordDecl->isAnonymousStructOrUnion()) { - clang::RecordDecl* anon = FieldParentRecordDecl; - FieldParentRecordDecl = llvm::dyn_cast(anon->getParent()); - for (auto F = FieldParentRecordDecl->field_begin(); - F != FieldParentRecordDecl->field_end(); ++F) { - const auto* RT = F->getType()->getAs(); - if (!RT) - continue; - if (anon == RT->getDecl()) { - FD = *F; - break; - } - } - offset += C.toCharUnitsFromBits(C.getFieldOffset(FD)).getQuantity(); - } - if (BaseCXXRD && BaseCXXRD != FieldParentRecordDecl) { - // FieldDecl FD belongs to some class C, but the base class BaseCXXRD is - // not C. That means BaseCXXRD derives from C. Offset needs to be - // calculated for Derived class - - // Depth first Search is performed to the class that declares FD from - // the base class - std::vector stack; - std::map direction; - stack.push_back(BaseCXXRD); - while (!stack.empty()) { - CXXRecordDecl* RD = stack.back(); - stack.pop_back(); - size_t num_bases = GetNumBases(RD); - bool flag = false; - for (size_t i = 0; i < num_bases; i++) { - auto* CRD = static_cast(GetBaseClass(RD, i)); - direction[CRD] = RD; - if (CRD == FieldParentRecordDecl) { - flag = true; - break; - } - stack.push_back(CRD); - } - if (flag) - break; - } - if (auto* RD = llvm::dyn_cast(FieldParentRecordDecl)) { - // add in the offsets for the (multi level) base classes - while (BaseCXXRD != RD) { - CXXRecordDecl* Parent = direction.at(RD); - offset += C.getASTRecordLayout(Parent) - .getBaseClassOffset(RD) - .getQuantity(); - RD = Parent; - } - } else { - assert(false && "Unreachable"); - } - } - return offset; - } - - if (auto *VD = llvm::dyn_cast(D)) { - auto GD = GlobalDecl(VD); - std::string mangledName; - compat::maybeMangleDeclName(GD, mangledName); - void* address = llvm::sys::DynamicLibrary::SearchForAddressOfSymbol( - mangledName.c_str()); - - if (!address) - address = I.getAddressOfGlobal(GD); - if (!address) { - if (!VD->hasInit()) { -#ifdef CPPINTEROP_USE_CLING - cling::Interpreter::PushTransactionRAII RAII(&getInterp()); -#endif // CPPINTEROP_USE_CLING - getSema().InstantiateVariableDefinition(SourceLocation(), VD); - } - if (VD->hasInit() && - (VD->isConstexpr() || VD->getType().isConstQualified())) { - if (const APValue* val = VD->evaluateValue()) { - if (VD->getType()->isIntegralType(C)) { - return (intptr_t)val->getInt().getRawData(); - } - } - } - } - if (!address) { - auto Linkage = C.GetGVALinkageForVariable(VD); - // The decl was deferred by CodeGen. Force its emission. - // FIXME: In ASTContext::DeclMustBeEmitted we should check if the - // Decl::isUsed is set or we should be able to access CodeGen's - // addCompilerUsedGlobal. - if (isDiscardableGVALinkage(Linkage)) - VD->addAttr(UsedAttr::CreateImplicit(C)); -#ifdef CPPINTEROP_USE_CLING - cling::Interpreter::PushTransactionRAII RAII(&I); - I.getCI()->getASTConsumer().HandleTopLevelDecl(DeclGroupRef(VD)); -#else // CLANG_REPL - I.getCI()->getASTConsumer().HandleTopLevelDecl(DeclGroupRef(VD)); - // Take the newest llvm::Module produced by CodeGen and send it to JIT. - auto GeneratedPTU = I.Parse(""); - if (!GeneratedPTU) - llvm::logAllUnhandledErrors(GeneratedPTU.takeError(), llvm::errs(), - "[GetVariableOffset] Failed to generate PTU:"); - - // From cling's BackendPasses.cpp - // FIXME: We need to upstream this code in IncrementalExecutor::addModule - for (auto &GV : GeneratedPTU->TheModule->globals()) { - llvm::GlobalValue::LinkageTypes LT = GV.getLinkage(); - if (GV.isDeclaration() || !GV.hasName() || - GV.getName().starts_with(".str") || - !GV.isDiscardableIfUnused(LT) || - LT != llvm::GlobalValue::InternalLinkage) - continue; //nothing to do - GV.setLinkage(llvm::GlobalValue::WeakAnyLinkage); - } - if (auto Err = I.Execute(*GeneratedPTU)) - llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), - "[GetVariableOffset] Failed to execute PTU:"); -#endif - } - auto VDAorErr = compat::getSymbolAddress(I, StringRef(mangledName)); - if (!VDAorErr) { - llvm::logAllUnhandledErrors(VDAorErr.takeError(), llvm::errs(), - "Failed to GetVariableOffset:"); - return 0; - } - return (intptr_t)jitTargetAddressToPointer(VDAorErr.get()); - } - - return 0; - } - - intptr_t GetVariableOffset(TCppScope_t var, TCppScope_t parent) { - auto* D = static_cast(var); - auto* RD = - llvm::dyn_cast_or_null(static_cast(parent)); - return GetVariableOffset(getInterp(), D, RD); - } - - // Check if the Access Specifier of the variable matches the provided value. - bool CheckVariableAccess(TCppScope_t var, AccessSpecifier AS) - { - auto *D = (Decl *) var; - return D->getAccess() == AS; - } - - bool IsPublicVariable(TCppScope_t var) - { - return CheckVariableAccess(var, AccessSpecifier::AS_public); - } - - bool IsProtectedVariable(TCppScope_t var) - { - return CheckVariableAccess(var, AccessSpecifier::AS_protected); - } - - bool IsPrivateVariable(TCppScope_t var) - { - return CheckVariableAccess(var, AccessSpecifier::AS_private); - } - - bool IsStaticVariable(TCppScope_t var) - { - auto *D = (Decl *) var; - if (llvm::isa_and_nonnull(D)) { - return true; - } - - return false; - } - - bool IsConstVariable(TCppScope_t var) - { - auto *D = (clang::Decl *) var; - - if (auto *VD = llvm::dyn_cast_or_null(D)) { - return VD->getType().isConstQualified(); - } - - return false; - } - - bool IsRecordType(TCppType_t type) - { - QualType QT = QualType::getFromOpaquePtr(type); - return QT->isRecordType(); - } - - bool IsPODType(TCppType_t type) - { - QualType QT = QualType::getFromOpaquePtr(type); - - if (QT.isNull()) - return false; - - return QT.isPODType(getASTContext()); - } - - bool IsPointerType(TCppType_t type) { - QualType QT = QualType::getFromOpaquePtr(type); - return QT->isPointerType(); - } - - TCppType_t GetPointeeType(TCppType_t type) { - if (!IsPointerType(type)) - return nullptr; - QualType QT = QualType::getFromOpaquePtr(type); - return QT->getPointeeType().getAsOpaquePtr(); - } - - bool IsReferenceType(TCppType_t type) { - QualType QT = QualType::getFromOpaquePtr(type); - return QT->isReferenceType(); - } - - TCppType_t GetNonReferenceType(TCppType_t type) { - if (!IsReferenceType(type)) - return nullptr; - QualType QT = QualType::getFromOpaquePtr(type); - return QT.getNonReferenceType().getAsOpaquePtr(); - } - - TCppType_t GetUnderlyingType(TCppType_t type) - { - QualType QT = QualType::getFromOpaquePtr(type); - QT = QT->getCanonicalTypeUnqualified(); - - // Recursively remove array dimensions - while (QT->isArrayType()) - QT = QualType(QT->getArrayElementTypeNoTypeQual(), 0); - - // Recursively reduce pointer depth till we are left with a pointerless - // type. - for (auto PT = QT->getPointeeType(); !PT.isNull(); PT = QT->getPointeeType()){ - QT = PT; - } - QT = QT->getCanonicalTypeUnqualified(); - return QT.getAsOpaquePtr(); - } - - std::string GetTypeAsString(TCppType_t var) - { - QualType QT = QualType::getFromOpaquePtr(var); - // FIXME: Get the default printing policy from the ASTContext. - PrintingPolicy Policy((LangOptions())); - Policy.Bool = true; // Print bool instead of _Bool. - Policy.SuppressTagKeyword = true; // Do not print `class std::string`. - return compat::FixTypeName(QT.getAsString(Policy)); - } - - TCppType_t GetCanonicalType(TCppType_t type) - { - if (!type) - return 0; - QualType QT = QualType::getFromOpaquePtr(type); - return QT.getCanonicalType().getAsOpaquePtr(); - } - - // Internal functions that are not needed outside the library are - // encompassed in an anonymous namespace as follows. This function converts - // from a string to the actual type. It is used in the GetType() function. - namespace { - static QualType findBuiltinType(llvm::StringRef typeName, ASTContext &Context) - { - bool issigned = false; - bool isunsigned = false; - if (typeName.starts_with("signed ")) { - issigned = true; - typeName = StringRef(typeName.data()+7, typeName.size()-7); - } - if (!issigned && typeName.starts_with("unsigned ")) { - isunsigned = true; - typeName = StringRef(typeName.data()+9, typeName.size()-9); - } - if (typeName == "char") { - if (isunsigned) return Context.UnsignedCharTy; - return Context.SignedCharTy; - } - if (typeName == "short") { - if (isunsigned) return Context.UnsignedShortTy; - return Context.ShortTy; - } - if (typeName == "int") { - if (isunsigned) return Context.UnsignedIntTy; - return Context.IntTy; - } - if (typeName == "long") { - if (isunsigned) return Context.UnsignedLongTy; - return Context.LongTy; - } - if (typeName == "long long") { - if (isunsigned) - return Context.UnsignedLongLongTy; - return Context.LongLongTy; - } - if (!issigned && !isunsigned) { - if (typeName == "bool") - return Context.BoolTy; - if (typeName == "float") - return Context.FloatTy; - if (typeName == "double") - return Context.DoubleTy; - if (typeName == "long double") - return Context.LongDoubleTy; - - if (typeName == "wchar_t") - return Context.WCharTy; - if (typeName == "char16_t") - return Context.Char16Ty; - if (typeName == "char32_t") - return Context.Char32Ty; - } - /* Missing - CanQualType WideCharTy; // Same as WCharTy in C++, integer type in C99. - CanQualType WIntTy; // [C99 7.24.1], integer type unchanged by default promotions. - */ - return QualType(); - } - } - - TCppType_t GetType(const std::string &name) { - QualType builtin = findBuiltinType(name, getASTContext()); - if (!builtin.isNull()) - return builtin.getAsOpaquePtr(); - - auto *D = (Decl *) GetNamed(name, /* Within= */ 0); - if (auto *TD = llvm::dyn_cast_or_null(D)) { - return QualType(TD->getTypeForDecl(), 0).getAsOpaquePtr(); - } - - return (TCppType_t)0; - } - - TCppType_t GetComplexType(TCppType_t type) { - QualType QT = QualType::getFromOpaquePtr(type); - - return getASTContext().getComplexType(QT).getAsOpaquePtr(); - } - - TCppType_t GetTypeFromScope(TCppScope_t klass) { - if (!klass) - return 0; - - auto *D = (Decl *) klass; - ASTContext &C = getASTContext(); - - if (ValueDecl *VD = dyn_cast(D)) - return VD->getType().getAsOpaquePtr(); - - return C.getTypeDeclType(cast(D)).getAsOpaquePtr(); - } - - // Internal functions that are not needed outside the library are - // encompassed in an anonymous namespace as follows. - namespace { - static unsigned long long gWrapperSerial = 0LL; - - enum EReferenceType { kNotReference, kLValueReference, kRValueReference }; - - // Start of JitCall Helper Functions - -#define DEBUG_TYPE "jitcall" - - // FIXME: Use that routine throughout CallFunc's port in places such as - // make_narg_call. - static inline void indent(ostringstream &buf, int indent_level) { - static const std::string kIndentString(" "); - for (int i = 0; i < indent_level; ++i) - buf << kIndentString; - } - - void *compile_wrapper(compat::Interpreter& I, - const std::string& wrapper_name, - const std::string& wrapper, - bool withAccessControl = true) { - LLVM_DEBUG(dbgs() << "Compiling '" << wrapper_name << "'\n"); - return I.compileFunction(wrapper_name, wrapper, false /*ifUnique*/, - withAccessControl); - } - - void get_type_as_string(QualType QT, std::string& type_name, ASTContext& C, - PrintingPolicy Policy) { - //TODO: Implement cling desugaring from utils::AST - // cling::utils::Transform::GetPartiallyDesugaredType() - if (!QT->isTypedefNameType() || QT->isBuiltinType()) - QT = QT.getDesugaredType(C); -#if CLANG_VERSION_MAJOR > 16 - Policy.SuppressElaboration = true; -#endif - Policy.FullyQualifiedName = true; - QT.getAsStringInternal(type_name, Policy); - } - - static void GetDeclName(const clang::Decl* D, ASTContext& Context, - std::string& name) { - // Helper to extract a fully qualified name from a Decl - PrintingPolicy Policy(Context.getPrintingPolicy()); - Policy.SuppressTagKeyword = true; - Policy.SuppressUnwrittenScope = true; - if (const TypeDecl* TD = dyn_cast(D)) { - // This is a class, struct, or union member. - QualType QT; - if (const TypedefDecl* Typedef = dyn_cast(TD)) { - // Handle the typedefs to anonymous types. - QT = Typedef->getTypeSourceInfo()->getType(); - } else - QT = {TD->getTypeForDecl(), 0}; - get_type_as_string(QT, name, Context, Policy); - } else if (const NamedDecl* ND = dyn_cast(D)) { - // This is a namespace member. - raw_string_ostream stream(name); - ND->getNameForDiagnostic(stream, Policy, /*Qualified=*/true); - stream.flush(); - } - } - - void collect_type_info(const FunctionDecl* FD, QualType& QT, - std::ostringstream& typedefbuf, - std::ostringstream& callbuf, std::string& type_name, - EReferenceType& refType, bool& isPointer, - int indent_level, bool forArgument) { - // - // Collect information about the type of a function parameter - // needed for building the wrapper function. - // - ASTContext& C = FD->getASTContext(); - PrintingPolicy Policy(C.getPrintingPolicy()); -#if CLANG_VERSION_MAJOR > 16 - Policy.SuppressElaboration = true; -#endif - refType = kNotReference; - if (QT->isRecordType() && forArgument) { - get_type_as_string(QT, type_name, C, Policy); - return; - } - if (QT->isFunctionPointerType()) { - std::string fp_typedef_name; - { - std::ostringstream nm; - nm << "FP" << gWrapperSerial++; - type_name = nm.str(); - raw_string_ostream OS(fp_typedef_name); - QT.print(OS, Policy, type_name); - OS.flush(); - } - - indent(typedefbuf, indent_level); - - typedefbuf << "typedef " << fp_typedef_name << ";\n"; - return; - } else if (QT->isMemberPointerType()) { - std::string mp_typedef_name; - { - std::ostringstream nm; - nm << "MP" << gWrapperSerial++; - type_name = nm.str(); - raw_string_ostream OS(mp_typedef_name); - QT.print(OS, Policy, type_name); - OS.flush(); - } - - indent(typedefbuf, indent_level); - - typedefbuf << "typedef " << mp_typedef_name << ";\n"; - return; - } else if (QT->isPointerType()) { - isPointer = true; - QT = cast(QT.getCanonicalType())->getPointeeType(); - } else if (QT->isReferenceType()) { - if (QT->isRValueReferenceType()) - refType = kRValueReference; - else - refType = kLValueReference; - QT = cast(QT.getCanonicalType())->getPointeeType(); - } - // Fall through for the array type to deal with reference/pointer ro array - // type. - if (QT->isArrayType()) { - std::string ar_typedef_name; - { - std::ostringstream ar; - ar << "AR" << gWrapperSerial++; - type_name = ar.str(); - raw_string_ostream OS(ar_typedef_name); - QT.print(OS, Policy, type_name); - OS.flush(); - } - indent(typedefbuf, indent_level); - typedefbuf << "typedef " << ar_typedef_name << ";\n"; - return; - } - get_type_as_string(QT, type_name, C, Policy); - } - - void make_narg_ctor(const FunctionDecl* FD, const unsigned N, - std::ostringstream& typedefbuf, - std::ostringstream& callbuf, - const std::string& class_name, int indent_level) { - // Make a code string that follows this pattern: - // - // ClassName(args...) - // - - 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 << ' '; - } else { - callbuf << "\n"; - indent(callbuf, indent_level + 1); - } - } - 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 << ")"; - } - - const DeclContext* get_non_transparent_decl_context(const FunctionDecl* FD) { - auto *DC = FD->getDeclContext(); - while (DC->isTransparentContext()) { - DC = DC->getParent(); - assert(DC && "All transparent contexts should have a parent!"); - } - return DC; - } - - void make_narg_call(const FunctionDecl* FD, const std::string& return_type, - const unsigned N, std::ostringstream& typedefbuf, - std::ostringstream& callbuf, - const std::string& class_name, int indent_level) { - // - // Make a code string that follows this pattern: - // - // ((*)obj)->(*(*)args[i], ...) - // - - // Sometimes it's necessary that we cast the function we want to call - // first to its explicit function type before calling it. This is supposed - // to prevent that we accidentally ending up in a function that is not - // the one we're supposed to call here (e.g. because the C++ function - // lookup decides to take another function that better fits). This method - // has some problems, e.g. when we call a function with default arguments - // and we don't provide all arguments, we would fail with this pattern. - // Same applies with member methods which seem to cause parse failures - // even when we supply the object parameter. Therefore we only use it in - // cases where we know it works and set this variable to true when we do. - - // true if not a overloaded operators or the overloaded operator is call - // operator - bool op_flag = !FD->isOverloadedOperator() || - FD->getOverloadedOperator() == clang::OO_Call; - - bool ShouldCastFunction = - !isa(FD) && N == FD->getNumParams() && op_flag; - if (ShouldCastFunction) { - callbuf << "("; - callbuf << "("; - callbuf << return_type << " (&)"; - { - callbuf << "("; - for (unsigned i = 0U; i < N; ++i) { - if (i) { - callbuf << ','; - if (i % 2) { - callbuf << ' '; - } else { - callbuf << "\n"; - indent(callbuf, indent_level + 1); - } - } - const ParmVarDecl* PVD = FD->getParamDecl(i); - QualType Ty = PVD->getType(); - QualType QT = Ty.getCanonicalType(); - std::string arg_type; - ASTContext& C = FD->getASTContext(); - get_type_as_string(QT, arg_type, C, C.getPrintingPolicy()); - callbuf << arg_type; - } - if (FD->isVariadic()) - callbuf << ", ..."; - callbuf << ")"; - } - - callbuf << ")"; - } - - if (const CXXMethodDecl* MD = dyn_cast(FD)) { - // This is a class, struct, or union member. - if (MD->isConst()) - callbuf << "((const " << class_name << "*)obj)->"; - else - callbuf << "((" << class_name << "*)obj)->"; - - if (op_flag) - callbuf << class_name << "::"; - } else if (isa(get_non_transparent_decl_context(FD))) { - // This is a namespace member. - if (op_flag || N <= 1) - callbuf << class_name << "::"; - } - // callbuf << fMethod->Name() << "("; - { - std::string name; - { - std::string complete_name; - llvm::raw_string_ostream stream(complete_name); - FD->getNameForDiagnostic(stream, - FD->getASTContext().getPrintingPolicy(), - /*Qualified=*/false); - - // insert space between template argument list and the function name - // this is require if the function is `operator<` - // `operator<` is invalid syntax - // whereas `operator< ` is valid - std::string simple_name = FD->getNameAsString(); - size_t idx = complete_name.find(simple_name, 0) + simple_name.size(); - std::string name_without_template_args = complete_name.substr(0, idx); - std::string template_args = complete_name.substr(idx); - name = name_without_template_args + - (template_args.empty() ? "" : " " + template_args); - } - if (op_flag || N <= 1) - callbuf << name; - } - if (ShouldCastFunction) - callbuf << ")"; - - 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) { - if (op_flag) { - callbuf << ", "; - } else { - callbuf << ' ' - << Cpp::getOperatorSpelling(FD->getOverloadedOperator()) - << ' '; - } - } - - 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 { - // pointer falls back to non-pointer case; the argument preserves - // the "pointerness" (i.e. doesn't reference the value). - callbuf << "*(" << type_name.c_str() << "*)args[" << i << "]"; - } - } - callbuf << ")"; - } - - void make_narg_ctor_with_return(const FunctionDecl* FD, const unsigned N, - const std::string& class_name, - 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...); - // - { - std::ostringstream typedefbuf; - std::ostringstream callbuf; - // - // Write the return value assignment part. - // - indent(callbuf, indent_level); - callbuf << "(*(" << class_name << "**)ret) = "; - callbuf << "(obj) ? new (*(" << class_name << "**)ret) "; - make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level); - - callbuf << ": new "; - // - // Write the actual expression. - // - make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level); - // - // End the new expression statement. - // - callbuf << ";\n"; - // - // Output the whole new expression and return statement. - // - buf << typedefbuf.str() << callbuf.str(); - } - } - - void make_narg_call_with_return(compat::Interpreter& I, - const FunctionDecl* FD, const unsigned N, - const std::string& class_name, - std::ostringstream& buf, int indent_level) { - // Make a code string that follows this pattern: - // - // if (ret) { - // new (ret) (return_type) ((class_name*)obj)->func(args...); - // } - // else { - // (void)(((class_name*)obj)->func(args...)); - // } - // - if (const CXXConstructorDecl* CD = dyn_cast(FD)) { - if (N <= 1 && llvm::isa(FD)) { - auto SpecMemKind = I.getCI()->getSema().getSpecialMember(CD); - if ((N == 0 && - SpecMemKind == CXXSpecialMemberKindDefaultConstructor) || - (N == 1 && - (SpecMemKind == CXXSpecialMemberKindCopyConstructor || - SpecMemKind == CXXSpecialMemberKindMoveConstructor))) { - // Using declarations cannot inject special members; do not call - // them as such. This might happen by using `Base(Base&, int = 12)`, - // which is fine to be called as `Derived d(someBase, 42)` but not - // as copy constructor of `Derived`. - return; - } - } - make_narg_ctor_with_return(FD, N, class_name, buf, indent_level); - return; - } - QualType QT = FD->getReturnType(); - if (QT->isVoidType()) { - std::ostringstream typedefbuf; - std::ostringstream callbuf; - indent(callbuf, indent_level); - make_narg_call(FD, "void", N, typedefbuf, callbuf, class_name, - indent_level); - callbuf << ";\n"; - indent(callbuf, indent_level); - callbuf << "return;\n"; - buf << typedefbuf.str() << callbuf.str(); - } else { - indent(buf, indent_level); - - std::string type_name; - EReferenceType refType = kNotReference; - bool isPointer = false; - - std::ostringstream typedefbuf; - std::ostringstream callbuf; - - collect_type_info(FD, QT, typedefbuf, callbuf, type_name, refType, - isPointer, indent_level, false); - - buf << typedefbuf.str(); - - buf << "if (ret) {\n"; - ++indent_level; - { - // - // Write the placement part of the placement new. - // - indent(callbuf, indent_level); - callbuf << "new (ret) "; - // - // Write the type part of the placement new. - // - callbuf << "(" << type_name.c_str(); - if (refType != kNotReference) { - callbuf << "*) (&"; - type_name += "&"; - } else if (isPointer) { - callbuf << "*) ("; - type_name += "*"; - } else { - callbuf << ") ("; - } - // - // Write the actual function call. - // - make_narg_call(FD, type_name, N, typedefbuf, callbuf, class_name, - indent_level); - // - // End the placement new. - // - callbuf << ");\n"; - indent(callbuf, indent_level); - callbuf << "return;\n"; - // - // Output the whole placement new expression and return statement. - // - buf << typedefbuf.str() << callbuf.str(); - } - --indent_level; - indent(buf, indent_level); - buf << "}\n"; - indent(buf, indent_level); - buf << "else {\n"; - ++indent_level; - { - std::ostringstream typedefbuf; - std::ostringstream callbuf; - indent(callbuf, indent_level); - callbuf << "(void)("; - make_narg_call(FD, type_name, N, typedefbuf, callbuf, class_name, - indent_level); - callbuf << ");\n"; - indent(callbuf, indent_level); - callbuf << "return;\n"; - buf << typedefbuf.str() << callbuf.str(); - } - --indent_level; - indent(buf, indent_level); - buf << "}\n"; - } - } - - int get_wrapper_code(compat::Interpreter& I, const FunctionDecl* FD, - std::string& wrapper_name, std::string& wrapper) { - assert(FD && "generate_wrapper called without a function decl!"); - ASTContext& Context = FD->getASTContext(); - // - // Get the class or namespace name. - // - std::string class_name; - const clang::DeclContext* DC = get_non_transparent_decl_context(FD); - GetDeclName(cast(DC), Context, class_name); - // - // Check to make sure that we can - // instantiate and codegen this function. - // - bool needInstantiation = false; - const FunctionDecl* Definition = 0; - if (!FD->isDefined(Definition)) { - FunctionDecl::TemplatedKind TK = FD->getTemplatedKind(); - switch (TK) { - case FunctionDecl::TK_NonTemplate: { - // Ordinary function, not a template specialization. - // Note: This might be ok, the body might be defined - // in a library, and all we have seen is the - // header file. - // llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << - // "Cannot make wrapper for a function which is " - // "declared but not defined!"; - // return 0; - } break; - case FunctionDecl::TK_FunctionTemplate: { - // This decl is actually a function template, - // not a function at all. - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a function template!"; - return 0; - } break; - case FunctionDecl::TK_MemberSpecialization: { - // This function is the result of instantiating an ordinary - // member function of a class template, or of instantiating - // an ordinary member function of a class member of a class - // template, or of specializing a member function template - // of a class template, or of specializing a member function - // template of a class member of a class template. - if (!FD->isTemplateInstantiation()) { - // We are either TSK_Undeclared or - // TSK_ExplicitSpecialization. - // Note: This might be ok, the body might be defined - // in a library, and all we have seen is the - // header file. - // llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << - // "Cannot make wrapper for a function template " - // "explicit specialization which is declared " - // "but not defined!"; - // return 0; - break; - } - const FunctionDecl* Pattern = FD->getTemplateInstantiationPattern(); - if (!Pattern) { - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a member function " - "instantiation with no pattern!"; - return 0; - } - FunctionDecl::TemplatedKind PTK = Pattern->getTemplatedKind(); - TemplateSpecializationKind PTSK = - Pattern->getTemplateSpecializationKind(); - if ( - // The pattern is an ordinary member function. - (PTK == FunctionDecl::TK_NonTemplate) || - // The pattern is an explicit specialization, and - // so is not a template. - ((PTK != FunctionDecl::TK_FunctionTemplate) && - ((PTSK == TSK_Undeclared) || - (PTSK == TSK_ExplicitSpecialization)))) { - // Note: This might be ok, the body might be defined - // in a library, and all we have seen is the - // header file. - break; - } else if (!Pattern->hasBody()) { - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a member function " - "instantiation with no body!"; - return 0; - } - if (FD->isImplicitlyInstantiable()) { - needInstantiation = true; - } - } break; - case FunctionDecl::TK_FunctionTemplateSpecialization: { - // This function is the result of instantiating a function - // template or possibly an explicit specialization of a - // function template. Could be a namespace scope function or a - // member function. - if (!FD->isTemplateInstantiation()) { - // We are either TSK_Undeclared or - // TSK_ExplicitSpecialization. - // Note: This might be ok, the body might be defined - // in a library, and all we have seen is the - // header file. - // llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << - // "Cannot make wrapper for a function template " - // "explicit specialization which is declared " - // "but not defined!"; - // return 0; - break; - } - const FunctionDecl* Pattern = FD->getTemplateInstantiationPattern(); - if (!Pattern) { - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a function template" - "instantiation with no pattern!"; - return 0; - } - FunctionDecl::TemplatedKind PTK = Pattern->getTemplatedKind(); - TemplateSpecializationKind PTSK = - Pattern->getTemplateSpecializationKind(); - if ( - // The pattern is an ordinary member function. - (PTK == FunctionDecl::TK_NonTemplate) || - // The pattern is an explicit specialization, and - // so is not a template. - ((PTK != FunctionDecl::TK_FunctionTemplate) && - ((PTSK == TSK_Undeclared) || - (PTSK == TSK_ExplicitSpecialization)))) { - // Note: This might be ok, the body might be defined - // in a library, and all we have seen is the - // header file. - break; - } - if (!Pattern->hasBody()) { - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a function template" - "instantiation with no body!"; - return 0; - } - if (FD->isImplicitlyInstantiable()) { - needInstantiation = true; - } - } break; - case FunctionDecl::TK_DependentFunctionTemplateSpecialization: { - // This function is the result of instantiating or - // specializing a member function of a class template, - // or a member function of a class member of a class template, - // or a member function template of a class template, or a - // member function template of a class member of a class - // template where at least some part of the function is - // dependent on a template argument. - if (!FD->isTemplateInstantiation()) { - // We are either TSK_Undeclared or - // TSK_ExplicitSpecialization. - // Note: This might be ok, the body might be defined - // in a library, and all we have seen is the - // header file. - // llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << - // "Cannot make wrapper for a dependent function " - // "template explicit specialization which is declared " - // "but not defined!"; - // return 0; - break; - } - const FunctionDecl* Pattern = FD->getTemplateInstantiationPattern(); - if (!Pattern) { - llvm::errs() - << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a dependent function template" - "instantiation with no pattern!"; - return 0; - } - FunctionDecl::TemplatedKind PTK = Pattern->getTemplatedKind(); - TemplateSpecializationKind PTSK = - Pattern->getTemplateSpecializationKind(); - if ( - // The pattern is an ordinary member function. - (PTK == FunctionDecl::TK_NonTemplate) || - // The pattern is an explicit specialization, and - // so is not a template. - ((PTK != FunctionDecl::TK_FunctionTemplate) && - ((PTSK == TSK_Undeclared) || - (PTSK == TSK_ExplicitSpecialization)))) { - // Note: This might be ok, the body might be defined - // in a library, and all we have seen is the - // header file. - break; - } - if (!Pattern->hasBody()) { - llvm::errs() - << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a dependent function template" - "instantiation with no body!"; - return 0; - } - if (FD->isImplicitlyInstantiable()) { - needInstantiation = true; - } - } break; - default: { - // Will only happen if clang implementation changes. - // Protect ourselves in case that happens. - llvm::errs() << "TClingCallFunc::make_wrapper" << ":" << - "Unhandled template kind!"; - return 0; - } break; - } - // We do not set needInstantiation to true in these cases: - // - // isInvalidDecl() - // TSK_Undeclared - // TSK_ExplicitInstantiationDefinition - // TSK_ExplicitSpecialization && !getClassScopeSpecializationPattern() - // TSK_ExplicitInstantiationDeclaration && - // getTemplateInstantiationPattern() && - // PatternDecl->hasBody() && - // !PatternDecl->isInlined() - // - // Set it true in these cases: - // - // TSK_ImplicitInstantiation - // TSK_ExplicitInstantiationDeclaration && (!getPatternDecl() || - // !PatternDecl->hasBody() || PatternDecl->isInlined()) - // - } - if (needInstantiation) { - clang::FunctionDecl* FDmod = const_cast(FD); - clang::Sema& S = I.getCI()->getSema(); - // Could trigger deserialization of decls. -#ifdef CPPINTEROP_USE_CLING - cling::Interpreter::PushTransactionRAII RAII(&I); -#endif - S.InstantiateFunctionDefinition(SourceLocation(), FDmod, - /*Recursive=*/true, - /*DefinitionRequired=*/true); - if (!FD->isDefined(Definition)) { - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Failed to force template instantiation!"; - return 0; - } - } - if (Definition) { - FunctionDecl::TemplatedKind TK = Definition->getTemplatedKind(); - switch (TK) { - case FunctionDecl::TK_NonTemplate: { - // Ordinary function, not a template specialization. - if (Definition->isDeleted()) { - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a deleted function!"; - return 0; - } else if (Definition->isLateTemplateParsed()) { - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a late template parsed " - "function!"; - return 0; - } - // else if (Definition->isDefaulted()) { - // // Might not have a body, but we can still use it. - //} - // else { - // // Has a body. - //} - } break; - case FunctionDecl::TK_FunctionTemplate: { - // This decl is actually a function template, - // not a function at all. - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a function template!"; - return 0; - } break; - case FunctionDecl::TK_MemberSpecialization: { - // This function is the result of instantiating an ordinary - // member function of a class template or of a member class - // of a class template. - if (Definition->isDeleted()) { - llvm::errs() - << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a deleted member function " - "of a specialization!"; - return 0; - } else if (Definition->isLateTemplateParsed()) { - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a late template parsed " - "member function of a specialization!"; - return 0; - } - // else if (Definition->isDefaulted()) { - // // Might not have a body, but we can still use it. - //} - // else { - // // Has a body. - //} - } break; - case FunctionDecl::TK_FunctionTemplateSpecialization: { - // This function is the result of instantiating a function - // template or possibly an explicit specialization of a - // function template. Could be a namespace scope function or a - // member function. - if (Definition->isDeleted()) { - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a deleted function " - "template specialization!"; - return 0; - } else if (Definition->isLateTemplateParsed()) { - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a late template parsed " - "function template specialization!"; - return 0; - } - // else if (Definition->isDefaulted()) { - // // Might not have a body, but we can still use it. - //} - // else { - // // Has a body. - //} - } break; - case FunctionDecl::TK_DependentFunctionTemplateSpecialization: { - // This function is the result of instantiating or - // specializing a member function of a class template, - // or a member function of a class member of a class template, - // or a member function template of a class template, or a - // member function template of a class member of a class - // template where at least some part of the function is - // dependent on a template argument. - if (Definition->isDeleted()) { - llvm::errs() - << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a deleted dependent function " - "template specialization!"; - return 0; - } else if (Definition->isLateTemplateParsed()) { - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Cannot make wrapper for a late template parsed " - "dependent function template specialization!"; - return 0; - } - // else if (Definition->isDefaulted()) { - // // Might not have a body, but we can still use it. - //} - // else { - // // Has a body. - //} - } break; - default: { - // Will only happen if clang implementation changes. - // Protect ourselves in case that happens. - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Unhandled template kind!"; - return 0; - } break; - } - } - unsigned min_args = FD->getMinRequiredArguments(); - unsigned num_params = FD->getNumParams(); - // - // Make the wrapper name. - // - { - std::ostringstream buf; - buf << "__cf"; - // const NamedDecl* ND = dyn_cast(FD); - // std::string mn; - // fInterp->maybeMangleDeclName(ND, mn); - // buf << '_' << mn; - buf << '_' << gWrapperSerial++; - wrapper_name = buf.str(); - } - // - // Write the wrapper code. - // FIXME: this should be synthesized into the AST! - // - int indent_level = 0; - std::ostringstream buf; - buf << "#pragma clang diagnostic push\n" - "#pragma clang diagnostic ignored \"-Wformat-security\"\n" - "__attribute__((used)) " - "__attribute__((annotate(\"__cling__ptrcheck(off)\")))\n" - "extern \"C\" void "; - buf << wrapper_name; - buf << "(void* obj, int nargs, void** args, void* ret)\n" - "{\n"; - ++indent_level; - if (min_args == num_params) { - // No parameters with defaults. - make_narg_call_with_return(I, FD, num_params, class_name, buf, - indent_level); - } else { - // We need one function call clause compiled for every - // possible number of arguments per call. - for (unsigned N = min_args; N <= num_params; ++N) { - indent(buf, indent_level); - buf << "if (nargs == " << N << ") {\n"; - ++indent_level; - make_narg_call_with_return(I, FD, N, class_name, buf, indent_level); - --indent_level; - indent(buf, indent_level); - buf << "}\n"; - } - } - --indent_level; - buf << "}\n" - "#pragma clang diagnostic pop"; - wrapper = buf.str(); - return 1; - } - - JitCall::GenericCall make_wrapper(compat::Interpreter& I, - const FunctionDecl* FD) { - static std::map gWrapperStore; - - auto R = gWrapperStore.find(FD); - if (R != gWrapperStore.end()) - return (JitCall::GenericCall) R->second; - - std::string wrapper_name; - std::string wrapper_code; - - if (get_wrapper_code(I, FD, wrapper_name, wrapper_code) == 0) - return 0; - - // - // Compile the wrapper code. - // - bool withAccessControl = true; - // We should be able to call private default constructors. - if (auto Ctor = dyn_cast(FD)) - withAccessControl = !Ctor->isDefaultConstructor(); - void *wrapper = compile_wrapper(I, wrapper_name, wrapper_code, - withAccessControl); - if (wrapper) { - gWrapperStore.insert(std::make_pair(FD, wrapper)); - } else { - llvm::errs() << "TClingCallFunc::make_wrapper" - << ":" - << "Failed to compile\n" - << "==== SOURCE BEGIN ====\n" - << wrapper_code << "\n" - << "==== SOURCE END ====\n"; - } - LLVM_DEBUG(dbgs() << "Compiled '" << (wrapper ? "" : "un") - << "successfully:\n" << wrapper_code << "'\n"); - return (JitCall::GenericCall)wrapper; - } - - // FIXME: Sink in the code duplication from get_wrapper_code. - static std::string PrepareStructorWrapper(const Decl* D, - const char* wrapper_prefix, - std::string& class_name) { - ASTContext &Context = D->getASTContext(); - GetDeclName(D, Context, class_name); - - // - // Make the wrapper name. - // - string wrapper_name; - { - ostringstream buf; - buf << wrapper_prefix; - //const NamedDecl* ND = dyn_cast(FD); - //string mn; - //fInterp->maybeMangleDeclName(ND, mn); - //buf << '_dtor_' << mn; - buf << '_' << gWrapperSerial++; - wrapper_name = buf.str(); - } - - return wrapper_name; - } - - static JitCall::DestructorCall make_dtor_wrapper(compat::Interpreter& interp, - const Decl *D) { - // Make a code string that follows this pattern: - // - // void - // unique_wrapper_ddd(void* obj, unsigned long nary, int withFree) - // { - // if (withFree) { - // if (!nary) { - // delete (ClassName*) obj; - // } - // else { - // delete[] (ClassName*) obj; - // } - // } - // else { - // typedef ClassName DtorName; - // if (!nary) { - // ((ClassName*)obj)->~DtorName(); - // } - // else { - // for (unsigned long i = nary - 1; i > -1; --i) { - // (((ClassName*)obj)+i)->~DtorName(); - // } - // } - // } - // } - // - //-- - - static map gDtorWrapperStore; - - auto I = gDtorWrapperStore.find(D); - if (I != gDtorWrapperStore.end()) - return (JitCall::DestructorCall) I->second; - - // - // Make the wrapper name. - // - std::string class_name; - string wrapper_name = PrepareStructorWrapper(D, "__dtor", class_name); - // - // Write the wrapper code. - // - int indent_level = 0; - ostringstream buf; - buf << "__attribute__((used)) "; - buf << "extern \"C\" void "; - buf << wrapper_name; - buf << "(void* obj, unsigned long nary, int withFree)\n"; - buf << "{\n"; - // if (withFree) { - // if (!nary) { - // delete (ClassName*) obj; - // } - // else { - // delete[] (ClassName*) obj; - // } - // } - ++indent_level; - indent(buf, indent_level); - buf << "if (withFree) {\n"; - ++indent_level; - indent(buf, indent_level); - buf << "if (!nary) {\n"; - ++indent_level; - indent(buf, indent_level); - buf << "delete (" << class_name << "*) obj;\n"; - --indent_level; - indent(buf, indent_level); - buf << "}\n"; - indent(buf, indent_level); - buf << "else {\n"; - ++indent_level; - indent(buf, indent_level); - buf << "delete[] (" << class_name << "*) obj;\n"; - --indent_level; - indent(buf, indent_level); - buf << "}\n"; - --indent_level; - indent(buf, indent_level); - buf << "}\n"; - // else { - // typedef ClassName Nm; - // if (!nary) { - // ((Nm*)obj)->~Nm(); - // } - // else { - // for (unsigned long i = nary - 1; i > -1; --i) { - // (((Nm*)obj)+i)->~Nm(); - // } - // } - // } - indent(buf, indent_level); - buf << "else {\n"; - ++indent_level; - indent(buf, indent_level); - buf << "typedef " << class_name << " Nm;\n"; - buf << "if (!nary) {\n"; - ++indent_level; - indent(buf, indent_level); - buf << "((Nm*)obj)->~Nm();\n"; - --indent_level; - indent(buf, indent_level); - buf << "}\n"; - indent(buf, indent_level); - buf << "else {\n"; - ++indent_level; - indent(buf, indent_level); - buf << "do {\n"; - ++indent_level; - indent(buf, indent_level); - buf << "(((Nm*)obj)+(--nary))->~Nm();\n"; - --indent_level; - indent(buf, indent_level); - buf << "} while (nary);\n"; - --indent_level; - indent(buf, indent_level); - buf << "}\n"; - --indent_level; - indent(buf, indent_level); - buf << "}\n"; - // End wrapper. - --indent_level; - buf << "}\n"; - // Done. - string wrapper(buf.str()); - //fprintf(stderr, "%s\n", wrapper.c_str()); - // - // Compile the wrapper code. - // - void *F = compile_wrapper(interp, wrapper_name, wrapper, - /*withAccessControl=*/false); - if (F) { - gDtorWrapperStore.insert(make_pair(D, F)); - } else { - llvm::errs() << "make_dtor_wrapper" - << "Failed to compile\n" - << "==== SOURCE BEGIN ====\n" - << wrapper - << "\n ==== SOURCE END ===="; - } - LLVM_DEBUG(dbgs() << "Compiled '" << (F ? "" : "un") - << "successfully:\n" << wrapper << "'\n"); - return (JitCall::DestructorCall)F; - } -#undef DEBUG_TYPE - } // namespace - // End of JitCall Helper Functions - - CPPINTEROP_API JitCall MakeFunctionCallable(TInterp_t I, - TCppConstFunction_t func) { - const auto* D = static_cast(func); - if (!D) - return {}; - - auto* interp = static_cast(I); - - // FIXME: Unify with make_wrapper. - if (const auto* Dtor = dyn_cast(D)) { - if (auto Wrapper = make_dtor_wrapper(*interp, Dtor->getParent())) - return {JitCall::kDestructorCall, Wrapper, Dtor}; - // FIXME: else error we failed to compile the wrapper. - return {}; - } - - if (auto Wrapper = make_wrapper(*interp, cast(D))) { - return {JitCall::kGenericCall, Wrapper, cast(D)}; - } - // FIXME: else error we failed to compile the wrapper. - return {}; - } - - CPPINTEROP_API JitCall MakeFunctionCallable(TCppConstFunction_t func) { - return MakeFunctionCallable(&getInterp(), func); - } - - namespace { - static std::string MakeResourcesPath() { - StringRef Dir; -#ifdef LLVM_BINARY_DIR - Dir = LLVM_BINARY_DIR; -#else - // Dir is bin/ or lib/, depending on where BinaryPath is. - void *MainAddr = (void *)(intptr_t)GetExecutablePath; - std::string BinaryPath = GetExecutablePath(/*Argv0=*/nullptr, MainAddr); - - // build/tools/clang/unittests/Interpreter/Executable -> build/ - StringRef Dir = sys::path::parent_path(BinaryPath); - - Dir = sys::path::parent_path(Dir); - Dir = sys::path::parent_path(Dir); - Dir = sys::path::parent_path(Dir); - Dir = sys::path::parent_path(Dir); - //Dir = sys::path::parent_path(Dir); -#endif // LLVM_BINARY_DIR - return compat::MakeResourceDir(Dir); - } - } // namespace - - TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, - const std::vector& GpuArgs /*={}*/) { - std::string MainExecutableName = - sys::fs::getMainExecutable(nullptr, nullptr); - std::string ResourceDir = MakeResourcesPath(); - std::vector ClingArgv = {"-resource-dir", ResourceDir.c_str(), - "-std=c++14"}; - ClingArgv.insert(ClingArgv.begin(), MainExecutableName.c_str()); -#ifdef _WIN32 - // FIXME : Workaround Sema::PushDeclContext assert on windows - ClingArgv.push_back("-fno-delayed-template-parsing"); -#endif - ClingArgv.insert(ClingArgv.end(), Args.begin(), Args.end()); - // To keep the Interpreter creation interface between cling and clang-repl - // to some extent compatible we should put Args and GpuArgs together. On the - // receiving end we should check for -xcuda to know. - if (!GpuArgs.empty()) { - llvm::StringRef Arg0 = GpuArgs[0]; - Arg0 = Arg0.trim().ltrim('-'); - if (Arg0 != "cuda") { - llvm::errs() << "[CreateInterpreter]: Make sure --cuda is passed as the" - << " first argument of the GpuArgs\n"; - return nullptr; - } - } - ClingArgv.insert(ClingArgv.end(), GpuArgs.begin(), GpuArgs.end()); - - // Process externally passed arguments if present. - std::vector ExtraArgs; - auto EnvOpt = - llvm::sys::Process::GetEnv("CPPINTEROP_EXTRA_INTERPRETER_ARGS"); - if (EnvOpt) { - StringRef Env(*EnvOpt); - while (!Env.empty()) { - StringRef Arg; - std::tie(Arg, Env) = Env.split(' '); - ExtraArgs.push_back(Arg.str()); - } - } - std::transform(ExtraArgs.begin(), ExtraArgs.end(), - std::back_inserter(ClingArgv), - [&](const std::string& str) { return str.c_str(); }); - -#ifdef CPPINTEROP_USE_CLING - auto I = new compat::Interpreter(ClingArgv.size(), &ClingArgv[0]); -#else - auto Interp = compat::Interpreter::create( - static_cast(ClingArgv.size()), ClingArgv.data()); - if (!Interp) - return nullptr; - auto* I = Interp.release(); -#endif - - // Honor -mllvm. - // - // FIXME: Remove this, one day. - // This should happen AFTER plugins have been loaded! - const CompilerInstance* Clang = I->getCI(); - if (!Clang->getFrontendOpts().LLVMArgs.empty()) { - unsigned NumArgs = Clang->getFrontendOpts().LLVMArgs.size(); - auto Args = std::make_unique(NumArgs + 2); - Args[0] = "clang (LLVM option parsing)"; - for (unsigned i = 0; i != NumArgs; ++i) - Args[i + 1] = Clang->getFrontendOpts().LLVMArgs[i].c_str(); - Args[NumArgs + 1] = nullptr; - llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get()); - } - // FIXME: Enable this assert once we figure out how to fix the multiple - // calls to CreateInterpreter. - //assert(!sInterpreter && "Interpreter already set."); - sInterpreter = I; - return I; - } - - TInterp_t GetInterpreter() { return sInterpreter; } - - void UseExternalInterpreter(TInterp_t I) { - assert(!sInterpreter && "sInterpreter already in use!"); - sInterpreter = static_cast(I); - OwningSInterpreter = false; - } - - void AddSearchPath(const char *dir, bool isUser, - bool prepend) { - getInterp().getDynamicLibraryManager()->addSearchPath(dir, isUser, prepend); - } - - const char* GetResourceDir() { - return getInterp().getCI()->getHeaderSearchOpts().ResourceDir.c_str(); - } - - ///\returns 0 on success. - static bool exec(const char* cmd, std::vector& outputs) { -#define DEBUG_TYPE "exec" - - std::array buffer; - struct file_deleter { - void operator()(FILE* fp) { pclose(fp); } - }; - std::unique_ptr pipe{popen(cmd, "r")}; - LLVM_DEBUG(dbgs() << "Executing command '" << cmd << "'\n"); - - if (!pipe) { - LLVM_DEBUG(dbgs() << "Execute failed!\n"); - perror("exec: "); - return false; - } - - LLVM_DEBUG(dbgs() << "Execute returned:\n"); - while (fgets(buffer.data(), static_cast(buffer.size()), pipe.get())) { - LLVM_DEBUG(dbgs() << buffer.data()); - llvm::StringRef trimmed = buffer.data(); - outputs.push_back(trimmed.trim().str()); - } - -#undef DEBUG_TYPE - - return true; - } - - std::string DetectResourceDir(const char* ClangBinaryName /* = clang */) { - std::string cmd = std::string(ClangBinaryName) + " -print-resource-dir"; - std::vector outs; - exec(cmd.c_str(), outs); - if (outs.empty() || outs.size() > 1) - return ""; - - std::string detected_resource_dir = outs.back(); - - std::string version = -#if CLANG_VERSION_MAJOR < 16 - CLANG_VERSION_STRING; -#else - CLANG_VERSION_MAJOR_STRING; -#endif - // We need to check if the detected resource directory is compatible. - if (llvm::sys::path::filename(detected_resource_dir) != version) - return ""; - - return detected_resource_dir; - } - - void DetectSystemCompilerIncludePaths(std::vector& Paths, - const char* CompilerName /*= "c++"*/) { - std::string cmd = "LC_ALL=C "; - cmd += CompilerName; - cmd += " -xc++ -E -v /dev/null 2>&1 | sed -n -e '/^.include/,${' -e '/^ " - "\\/.*/p' -e '}'"; - std::vector outs; - exec(cmd.c_str(), Paths); - } - - void AddIncludePath(const char *dir) { - getInterp().AddIncludePath(dir); - } - - void GetIncludePaths(std::vector& IncludePaths, bool withSystem, - bool withFlags) { - llvm::SmallVector paths(1); - getInterp().GetIncludePaths(paths, withSystem, withFlags); - for (auto& i : paths) - IncludePaths.push_back(i); - } - - namespace { - - class clangSilent { - public: - clangSilent(clang::DiagnosticsEngine &diag) : fDiagEngine(diag) { - fOldDiagValue = fDiagEngine.getSuppressAllDiagnostics(); - fDiagEngine.setSuppressAllDiagnostics(true); - } - - ~clangSilent() { fDiagEngine.setSuppressAllDiagnostics(fOldDiagValue); } - - protected: - clang::DiagnosticsEngine &fDiagEngine; - bool fOldDiagValue; - }; - } // namespace - - int Declare(compat::Interpreter& I, const char* code, bool silent) { - if (silent) { - clangSilent diagSuppr(I.getSema().getDiagnostics()); - return I.declare(code); - } - - return I.declare(code); - } - - int Declare(const char* code, bool silent) { - return Declare(getInterp(), code, silent); - } - - int Process(const char *code) { - return getInterp().process(code); - } - - intptr_t Evaluate(const char *code, - bool *HadError/*=nullptr*/) { -#ifdef CPPINTEROP_USE_CLING - cling::Value V; -#else - clang::Value V; -#endif // CPPINTEROP_USE_CLING - - if (HadError) - *HadError = false; - - auto res = getInterp().evaluate(code, V); - if (res != 0) { // 0 is success - if (HadError) - *HadError = true; - // FIXME: Make this return llvm::Expected - return ~0UL; - } - - return compat::convertTo(V); - } - - std::string LookupLibrary(const char* lib_name) { - return getInterp().getDynamicLibraryManager()->lookupLibrary(lib_name); - } - - bool LoadLibrary(const char* lib_stem, bool lookup) { - compat::Interpreter::CompilationResult res = - getInterp().loadLibrary(lib_stem, lookup); - - return res == compat::Interpreter::kSuccess; - } - - void UnloadLibrary(const char* lib_stem) { - getInterp().getDynamicLibraryManager()->unloadLibrary(lib_stem); - } - - std::string SearchLibrariesForSymbol(const char* mangled_name, - bool search_system /*true*/) { - auto* DLM = getInterp().getDynamicLibraryManager(); - return DLM->searchLibrariesForSymbol(mangled_name, search_system); - } - - bool InsertOrReplaceJitSymbol(compat::Interpreter& I, - const char* linker_mangled_name, - uint64_t address) { - // FIXME: This approach is problematic since we could replace a symbol - // whose address was already taken by clients. - // - // A safer approach would be to define our symbol replacements early in the - // bootstrap process like: - // auto J = LLJITBuilder().create(); - // if (!J) - // return Err; - // - // if (Jupyter) { - // llvm::orc::SymbolMap Overrides; - // Overrides[J->mangleAndIntern("printf")] = - // { ExecutorAddr::fromPtr(&printf), JITSymbolFlags::Exported }; - // Overrides[...] = - // { ... }; - // if (auto Err = - // J->getProcessSymbolsJITDylib().define(absoluteSymbols(std::move(Overrides))) - // return Err; - // } - - // FIXME: If we still want to do symbol replacement we should use the - // ReplacementManager which is available in llvm 18. - using namespace llvm; - using namespace llvm::orc; - - auto Symbol = compat::getSymbolAddress(I, linker_mangled_name); - llvm::orc::LLJIT& Jit = *compat::getExecutionEngine(I); - llvm::orc::ExecutionSession& ES = Jit.getExecutionSession(); -#if CLANG_VERSION_MAJOR < 17 - JITDylib& DyLib = Jit.getMainJITDylib(); -#else - JITDylib& DyLib = *Jit.getProcessSymbolsJITDylib().get(); -#endif // CLANG_VERSION_MAJOR - - if (Error Err = Symbol.takeError()) { - logAllUnhandledErrors(std::move(Err), errs(), - "[InsertOrReplaceJitSymbol] error: "); -#define DEBUG_TYPE "orc" - LLVM_DEBUG(ES.dump(dbgs())); -#undef DEBUG_TYPE - return true; - } - - // Nothing to define, we are redefining the same function. - if (*Symbol && *Symbol == address) { - errs() << "[InsertOrReplaceJitSymbol] warning: redefining '" - << linker_mangled_name << "' with the same address\n"; - return true; - } - - // Let's inject it. - llvm::orc::SymbolMap InjectedSymbols; - auto& DL = compat::getExecutionEngine(I)->getDataLayout(); - char GlobalPrefix = DL.getGlobalPrefix(); - std::string tmp(linker_mangled_name); - if (GlobalPrefix != '\0') { - tmp = std::string(1, GlobalPrefix) + tmp; - } - auto Name = ES.intern(tmp); - InjectedSymbols[Name] = -#if CLANG_VERSION_MAJOR < 17 - JITEvaluatedSymbol(address, -#else - ExecutorSymbolDef(ExecutorAddr(address), -#endif // CLANG_VERSION_MAJOR < 17 - JITSymbolFlags::Exported); - - // We want to replace a symbol with a custom provided one. - if (Symbol && address) - // The symbol be in the DyLib or in-process. - if (auto Err = DyLib.remove({Name})) { - logAllUnhandledErrors(std::move(Err), errs(), - "[InsertOrReplaceJitSymbol] error: "); - return true; - } - - if (Error Err = DyLib.define(absoluteSymbols(InjectedSymbols))) { - logAllUnhandledErrors(std::move(Err), errs(), - "[InsertOrReplaceJitSymbol] error: "); - return true; - } - - return false; - } - - bool InsertOrReplaceJitSymbol(const char* linker_mangled_name, - uint64_t address) { - return InsertOrReplaceJitSymbol(getInterp(), linker_mangled_name, address); - } - - std::string ObjToString(const char *type, void *obj) { - return getInterp().toString(type, obj); - } - - static Decl* InstantiateTemplate(TemplateDecl* TemplateD, - TemplateArgumentListInfo& TLI, Sema& S) { - // This is not right but we don't have a lot of options to choose from as a - // template instantiation requires a valid source location. - SourceLocation fakeLoc = GetValidSLoc(S); - if (auto* FunctionTemplate = dyn_cast(TemplateD)) { - FunctionDecl* Specialization = nullptr; - clang::sema::TemplateDeductionInfo Info(fakeLoc); - Template_Deduction_Result Result = S.DeduceTemplateArguments( - FunctionTemplate, &TLI, Specialization, Info, - /*IsAddressOfFunction*/ true); - if (Result != Template_Deduction_Result_Success) { - // FIXME: Diagnose what happened. - (void)Result; - } - return Specialization; - } - - if (auto* VarTemplate = dyn_cast(TemplateD)) { - DeclResult R = S.CheckVarTemplateId(VarTemplate, fakeLoc, fakeLoc, TLI); - if (R.isInvalid()) { - // FIXME: Diagnose - } - return R.get(); - } - - // This will instantiate tape type and return it. - SourceLocation noLoc; - QualType TT = S.CheckTemplateIdType(TemplateName(TemplateD), noLoc, TLI); - - // Perhaps we can extract this into a new interface. - S.RequireCompleteType(fakeLoc, TT, diag::err_tentative_def_incomplete_type); - return GetScopeFromType(TT); - - // ASTContext &C = S.getASTContext(); - // // Get clad namespace and its identifier clad::. - // CXXScopeSpec CSS; - // CSS.Extend(C, GetCladNamespace(), noLoc, noLoc); - // NestedNameSpecifier* NS = CSS.getScopeRep(); - - // // Create elaborated type with namespace specifier, - // // i.e. class -> clad::class - // return C.getElaboratedType(ETK_None, NS, TT); - } - - Decl* InstantiateTemplate(TemplateDecl* TemplateD, - ArrayRef TemplateArgs, Sema& S) { - // Create a list of template arguments. - TemplateArgumentListInfo TLI{}; - for (auto TA : TemplateArgs) - TLI.addArgument(S.getTrivialTemplateArgumentLoc(TA,QualType(), - SourceLocation())); - - return InstantiateTemplate(TemplateD, TLI, S); - } - - TCppScope_t InstantiateTemplate(compat::Interpreter& I, TCppScope_t tmpl, - const TemplateArgInfo* template_args, - size_t template_args_size) { - auto& S = I.getSema(); - auto& C = S.getASTContext(); - - llvm::SmallVector TemplateArgs; - TemplateArgs.reserve(template_args_size); - for (size_t i = 0; i < template_args_size; ++i) { - QualType ArgTy = QualType::getFromOpaquePtr(template_args[i].m_Type); - if (template_args[i].m_IntegralValue) { - // We have a non-type template parameter. Create an integral value from - // the string representation. - auto Res = llvm::APSInt(template_args[i].m_IntegralValue); - Res = Res.extOrTrunc(C.getIntWidth(ArgTy)); - TemplateArgs.push_back(TemplateArgument(C, Res, ArgTy)); - } else { - TemplateArgs.push_back(ArgTy); - } - } - - TemplateDecl* TmplD = static_cast(tmpl); - - // We will create a new decl, push a transaction. -#ifdef CPPINTEROP_USE_CLING - cling::Interpreter::PushTransactionRAII RAII(&I); -#endif - return InstantiateTemplate(TmplD, TemplateArgs, S); - } - - TCppScope_t InstantiateTemplate(TCppScope_t tmpl, - const TemplateArgInfo* template_args, - size_t template_args_size) { - return InstantiateTemplate(getInterp(), tmpl, template_args, - template_args_size); - } - - void GetClassTemplateInstantiationArgs(TCppScope_t templ_instance, - std::vector &args) { - auto* CTSD = static_cast(templ_instance); - for(const auto& TA : CTSD->getTemplateInstantiationArgs().asArray()) { - switch (TA.getKind()) { - default: - assert(0 && "Not yet supported!"); - break; - case TemplateArgument::Pack: - for (auto SubTA : TA.pack_elements()) - args.push_back({SubTA.getAsType().getAsOpaquePtr()}); - break; - case TemplateArgument::Integral: - // FIXME: Support this case where the problem is where we provide the - // storage for the m_IntegralValue. - //llvm::APSInt Val = TA.getAsIntegral(); - //args.push_back({TA.getIntegralType(), TA.getAsIntegral()}) - //break; - case TemplateArgument::Type: - args.push_back({TA.getAsType().getAsOpaquePtr()}); - } - } - } - - TCppFunction_t - InstantiateTemplateFunctionFromString(const char* function_template) { - // FIXME: Drop this interface and replace it with the proper overload - // resolution handling and template instantiation selection. - - // Try to force template instantiation and overload resolution. - static unsigned long long var_count = 0; - std::string id = "__Cppyy_GetMethTmpl_" + std::to_string(var_count++); - std::string instance = "auto " + id + " = " + function_template + ";\n"; - - if (!Cpp::Declare(instance.c_str(), /*silent=*/false)) { - VarDecl* VD = (VarDecl*)Cpp::GetNamed(id, 0); - DeclRefExpr* DRE = (DeclRefExpr*)VD->getInit()->IgnoreImpCasts(); - return DRE->getDecl(); - } - return nullptr; - } - - void GetAllCppNames(TCppScope_t scope, std::set& names) { - auto *D = (clang::Decl *)scope; - clang::DeclContext *DC; - clang::DeclContext::decl_iterator decl; - - if (auto *TD = dyn_cast_or_null(D)) { - DC = clang::TagDecl::castToDeclContext(TD); - decl = DC->decls_begin(); - decl++; - } else if (auto *ND = dyn_cast_or_null(D)) { - DC = clang::NamespaceDecl::castToDeclContext(ND); - decl = DC->decls_begin(); - } else if (auto *TUD = dyn_cast_or_null(D)) { - DC = clang::TranslationUnitDecl::castToDeclContext(TUD); - decl = DC->decls_begin(); - } else { - return; - } - - for (/* decl set above */; decl != DC->decls_end(); decl++) { - if (auto *ND = llvm::dyn_cast_or_null(*decl)) { - names.insert(ND->getNameAsString()); - } - } - } - - void GetEnums(TCppScope_t scope, std::vector& Result) { - auto* D = static_cast(scope); - - if (!llvm::isa_and_nonnull(D)) - return; - - auto* DC = llvm::dyn_cast(D); - - llvm::SmallVector DCs; - DC->collectAllContexts(DCs); - - // FIXME: We should use a lookup based approach instead of brute force - for (auto* DC : DCs) { - for (auto decl = DC->decls_begin(); decl != DC->decls_end(); decl++) { - if (auto* ND = llvm::dyn_cast_or_null(*decl)) { - Result.push_back(ND->getNameAsString()); - } - } - } - } - - // FIXME: On the CPyCppyy side the receiver is of type - // vector instead of vector - std::vector GetDimensions(TCppType_t type) - { - QualType Qual = QualType::getFromOpaquePtr(type); - if (Qual.isNull()) - return {}; - Qual = Qual.getCanonicalType(); - std::vector dims; - if (Qual->isArrayType()) - { - const clang::ArrayType *ArrayType = dyn_cast(Qual.getTypePtr()); - while (ArrayType) - { - if (const auto *CAT = dyn_cast_or_null(ArrayType)) { - llvm::APSInt Size(CAT->getSize()); - long int ArraySize = Size.getLimitedValue(); - dims.push_back(ArraySize); - } else /* VariableArrayType, DependentSizedArrayType, IncompleteArrayType */ { - dims.push_back(DimensionValue::UNKNOWN_SIZE); - } - ArrayType = ArrayType->getElementType()->getAsArrayTypeUnsafe(); - } - return dims; - } - return dims; - } - - bool IsTypeDerivedFrom(TCppType_t derived, TCppType_t base) - { - auto &S = getSema(); - auto fakeLoc = GetValidSLoc(S); - auto derivedType = clang::QualType::getFromOpaquePtr(derived); - auto baseType = clang::QualType::getFromOpaquePtr(base); - -#ifdef CPPINTEROP_USE_CLING - cling::Interpreter::PushTransactionRAII RAII(&getInterp()); -#endif - return S.IsDerivedFrom(fakeLoc,derivedType,baseType); - } - - std::string GetFunctionArgDefault(TCppFunction_t func, - TCppIndex_t param_index) { - auto *D = (clang::Decl *)func; - clang::ParmVarDecl* PI = nullptr; - - if (auto* FD = llvm::dyn_cast_or_null(D)) - PI = FD->getParamDecl(param_index); - - else if (auto* FD = llvm::dyn_cast_or_null(D)) - PI = (FD->getTemplatedDecl())->getParamDecl(param_index); - - if (PI->hasDefaultArg()) - { - std::string Result; - llvm::raw_string_ostream OS(Result); - Expr *DefaultArgExpr = const_cast(PI->getDefaultArg()); - DefaultArgExpr->printPretty(OS, nullptr, PrintingPolicy(LangOptions())); - - // FIXME: Floats are printed in clang with the precision of their underlying representation - // and not as written. This is a deficiency in the printing mechanism of clang which we require - // extra work to mitigate. For example float PI = 3.14 is printed as 3.1400000000000001 - if (PI->getType()->isFloatingType()) - { - if (!Result.empty() && Result.back() == '.') - return Result; - auto DefaultArgValue = std::stod(Result); - std::ostringstream oss; - oss << DefaultArgValue; - Result = oss.str(); - } - return Result; - } - return ""; - } - - bool IsConstMethod(TCppFunction_t method) - { - if (!method) - return false; - - auto *D = (clang::Decl *)method; - if (auto *func = dyn_cast(D)) - return func->getMethodQualifiers().hasConst(); - - return false; - } - - std::string GetFunctionArgName(TCppFunction_t func, TCppIndex_t param_index) - { - auto *D = (clang::Decl *)func; - clang::ParmVarDecl* PI = nullptr; - - if (auto* FD = llvm::dyn_cast_or_null(D)) - PI = FD->getParamDecl(param_index); - else if (auto* FD = llvm::dyn_cast_or_null(D)) - PI = (FD->getTemplatedDecl())->getParamDecl(param_index); - - return PI->getNameAsString(); - } - - OperatorArity GetOperatorArity(TCppFunction_t op) { - Decl* D = static_cast(op); - if (auto* FD = llvm::dyn_cast(D)) { - if (FD->isOverloadedOperator()) { - switch (FD->getOverloadedOperator()) { -#define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, \ - MemberOnly) \ - case OO_##Name: \ - if ((Unary) && (Binary)) \ - return kBoth; \ - if (Unary) \ - return kUnary; \ - if (Binary) \ - return kBinary; \ - break; -#include "clang/Basic/OperatorKinds.def" - default: - break; - } - } - } - return (OperatorArity)~0U; - } - - void GetOperator(TCppScope_t scope, Operator op, - std::vector& operators, OperatorArity kind) { - Decl* D = static_cast(scope); - if (auto* CXXRD = llvm::dyn_cast_or_null(D)) { - auto fn = [&operators, kind, op](const RecordDecl* RD) { - ASTContext& C = RD->getASTContext(); - DeclContextLookupResult Result = - RD->lookup(C.DeclarationNames.getCXXOperatorName( - (clang::OverloadedOperatorKind)op)); - for (auto* i : Result) { - if (kind & GetOperatorArity(i)) - operators.push_back(i); - } - return true; - }; - fn(CXXRD); - CXXRD->forallBases(fn); - } else if (auto* DC = llvm::dyn_cast_or_null(D)) { - ASTContext& C = getSema().getASTContext(); - DeclContextLookupResult Result = - DC->lookup(C.DeclarationNames.getCXXOperatorName( - (clang::OverloadedOperatorKind)op)); - - for (auto* i : Result) { - if (kind & GetOperatorArity(i)) - operators.push_back(i); - } - } - } - - TCppObject_t Allocate(TCppScope_t scope) { - return (TCppObject_t)::operator new(Cpp::SizeOf(scope)); - } - - void Deallocate(TCppScope_t scope, TCppObject_t address) { - ::operator delete(address); - } - - // FIXME: Add optional arguments to the operator new. - TCppObject_t Construct(compat::Interpreter& interp, TCppScope_t scope, - void* arena /*=nullptr*/) { - auto* Class = (Decl*) scope; - // FIXME: Diagnose. - if (!HasDefaultConstructor(Class)) - return nullptr; - - 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. - return arena; - } - - void *obj = nullptr; - JC.Invoke(&obj); - return obj; - } - return nullptr; - } - - TCppObject_t Construct(TCppScope_t scope, void* arena /*=nullptr*/) { - return Construct(getInterp(), scope, arena); - } - - void Destruct(compat::Interpreter& interp, TCppObject_t This, Decl* Class, - bool withFree) { - if (auto wrapper = make_dtor_wrapper(interp, Class)) { - (*wrapper)(This, /*nary=*/0, withFree); - return; - } - // FIXME: Diagnose. - } - - void Destruct(TCppObject_t This, TCppScope_t scope, bool withFree /*=true*/) { - auto* Class = static_cast(scope); - Destruct(getInterp(), This, Class, withFree); - } - - class StreamCaptureInfo { - struct file_deleter { - void operator()(FILE* fp) { pclose(fp); } - }; - std::unique_ptr m_TempFile; - int m_FD = -1; - int m_DupFD = -1; - - public: -#ifdef _MSC_VER - StreamCaptureInfo(int FD) - : m_TempFile{[]() { - FILE* stream = nullptr; - errno_t err; - err = tmpfile_s(&stream); - if (err) - printf("Cannot create temporary file!\n"); - return stream; - }()}, - m_FD(FD) { -#else - StreamCaptureInfo(int FD) : m_TempFile{tmpfile()}, m_FD(FD) { -#endif - if (!m_TempFile) { - perror("StreamCaptureInfo: Unable to create temp file"); - return; - } - - m_DupFD = dup(FD); - - // Flush now or can drop the buffer when dup2 is called with Fd later. - // This seems only necessary when piping stdout or stderr, but do it - // for ttys to avoid over complicated code for minimal benefit. - ::fflush(FD == STDOUT_FILENO ? stdout : stderr); - if (dup2(fileno(m_TempFile.get()), FD) < 0) - perror("StreamCaptureInfo:"); - } - StreamCaptureInfo(const StreamCaptureInfo&) = delete; - StreamCaptureInfo& operator=(const StreamCaptureInfo&) = delete; - StreamCaptureInfo(StreamCaptureInfo&&) = delete; - StreamCaptureInfo& operator=(StreamCaptureInfo&&) = delete; - - ~StreamCaptureInfo() { - assert(m_DupFD == -1 && "Captured output not used?"); - } - - std::string GetCapturedString() { - assert(m_DupFD != -1 && "Multiple calls to GetCapturedString"); - - fflush(nullptr); - if (dup2(m_DupFD, m_FD) < 0) - perror("StreamCaptureInfo:"); - // Go to the end of the file. - if (fseek(m_TempFile.get(), 0L, SEEK_END) != 0) - perror("StreamCaptureInfo:"); - - // Get the size of the file. - long bufsize = ftell(m_TempFile.get()); - if (bufsize == -1) - perror("StreamCaptureInfo:"); - - // Allocate our buffer to that size. - std::unique_ptr content(new char[bufsize + 1]); - - // Go back to the start of the file. - if (fseek(m_TempFile.get(), 0L, SEEK_SET) != 0) - perror("StreamCaptureInfo:"); - - // Read the entire file into memory. - size_t newLen = - fread(content.get(), sizeof(char), bufsize, m_TempFile.get()); - if (ferror(m_TempFile.get()) != 0) - fputs("Error reading file", stderr); - else - content[newLen++] = '\0'; // Just to be safe. - - std::string result = content.get(); - close(m_DupFD); - m_DupFD = -1; - return result; - } - }; - - static std::stack& GetRedirectionStack() { - static std::stack sRedirectionStack; - return sRedirectionStack; - } - - void BeginStdStreamCapture(CaptureStreamKind fd_kind) { - GetRedirectionStack().emplace((int)fd_kind); - } - - std::string EndStdStreamCapture() { - assert(GetRedirectionStack().size()); - StreamCaptureInfo& SCI = GetRedirectionStack().top(); - std::string result = SCI.GetCapturedString(); - GetRedirectionStack().pop(); - return result; - } - - void CodeComplete(std::vector& Results, const char* code, - unsigned complete_line /* = 1U */, - unsigned complete_column /* = 1U */) { - compat::codeComplete(Results, getInterp(), code, complete_line, - complete_column); - } - - int Undo(unsigned N) { -#ifdef CPPINTEROP_USE_CLING - auto& I = getInterp(); - cling::Interpreter::PushTransactionRAII RAII(&I); - I.unload(N); - return compat::Interpreter::kSuccess; -#else - return getInterp().undo(N); -#endif - } - - } // end namespace Cpp diff --git a/lib/Interpreter/DynamicLibraryManager.cpp b/lib/Interpreter/DynamicLibraryManager.cpp deleted file mode 100644 index d1d7546f1..000000000 --- a/lib/Interpreter/DynamicLibraryManager.cpp +++ /dev/null @@ -1,510 +0,0 @@ -//------------------------------------------------------------------------------ -// CLING - the C++ LLVM-based InterpreterG :) -// author: Vassil Vassilev -// -// This file is dual-licensed: you can choose to license it under the University -// of Illinois Open Source License or the GNU Lesser General Public License. See -// LICENSE.TXT for details. -//------------------------------------------------------------------------------ - -#include "DynamicLibraryManager.h" -#include "Compatibility.h" -#include "Paths.h" - -#include "llvm/ADT/StringSet.h" -#include "llvm/BinaryFormat/Magic.h" -#include "llvm/Support/Debug.h" -#include "llvm/Support/DynamicLibrary.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/Path.h" - -#if defined(_WIN32) -#include "llvm/BinaryFormat/COFF.h" -#include "llvm/Support/Endian.h" -#endif - -#include -#include -#include - -namespace Cpp { - - using namespace Cpp::utils::platform; - using namespace Cpp::utils; - using namespace llvm; - - DynamicLibraryManager::DynamicLibraryManager() { - const SmallVector kSysLibraryEnv = { - "LD_LIBRARY_PATH", - #if __APPLE__ - "DYLD_LIBRARY_PATH", - "DYLD_FALLBACK_LIBRARY_PATH", - /* - "DYLD_VERSIONED_LIBRARY_PATH", - "DYLD_FRAMEWORK_PATH", - "DYLD_FALLBACK_FRAMEWORK_PATH", - "DYLD_VERSIONED_FRAMEWORK_PATH", - */ - #elif defined(_WIN32) - "PATH", - #endif - }; - - // Behaviour is to not add paths that don't exist...In an interpreted env - // does this make sense? Path could pop into existence at any time. - for (const char* Var : kSysLibraryEnv) { - if (const char* Env = GetEnv(Var)) { - SmallVector CurPaths; - SplitPaths(Env, CurPaths, utils::kPruneNonExistent, - Cpp::utils::platform::kEnvDelim); - for (const auto& Path : CurPaths) - addSearchPath(Path); - } - } - - // $CWD is the last user path searched. - addSearchPath("."); - - SmallVector SysPaths; - Cpp::utils::platform::GetSystemLibraryPaths(SysPaths); - - for (const std::string& P : SysPaths) - addSearchPath(P, /*IsUser*/ false); - } - ///\returns substitution of pattern in the front of original with replacement - /// Example: substFront("@rpath/abc", "@rpath/", "/tmp") -> "/tmp/abc" - static std::string substFront(StringRef original, StringRef pattern, - StringRef replacement) { - if (!original.starts_with_insensitive(pattern)) - return original.str(); - SmallString<512> result(replacement); - result.append(original.drop_front(pattern.size())); - return result.str().str(); - } - - ///\returns substitution of all known linker variables in \c original - static std::string substAll(StringRef original, - StringRef libLoader) { - - // Handle substitutions (MacOS): - // @rpath - This function does not substitute @rpath, because - // this variable is already handled by lookupLibrary where - // @rpath is replaced with all paths from RPATH one by one. - // @executable_path - Main program path. - // @loader_path - Loader library (or main program) path. - // - // Handle substitutions (Linux): - // https://man7.org/linux/man-pages/man8/ld.so.8.html - // $origin - Loader library (or main program) path. - // $lib - lib lib64 - // $platform - x86_64 AT_PLATFORM - - std::string result; -#ifdef __APPLE__ - SmallString<512> mainExecutablePath(llvm::sys::fs::getMainExecutable(nullptr, nullptr)); - llvm::sys::path::remove_filename(mainExecutablePath); - SmallString<512> loaderPath; - if (libLoader.empty()) { - loaderPath = mainExecutablePath; - } else { - loaderPath = libLoader.str(); - llvm::sys::path::remove_filename(loaderPath); - } - - result = substFront(original, "@executable_path", mainExecutablePath); - result = substFront(result, "@loader_path", loaderPath); - return result; -#else - SmallString<512> loaderPath; - if (libLoader.empty()) { - loaderPath = llvm::sys::fs::getMainExecutable(nullptr, nullptr); - } else { - loaderPath = libLoader.str(); - } - llvm::sys::path::remove_filename(loaderPath); - - result = substFront(original, "$origin", loaderPath); - //result = substFront(result, "$lib", true?"lib":"lib64"); - //result = substFront(result, "$platform", "x86_64"); - return result; -#endif - } - - std::string - DynamicLibraryManager::lookupLibInPaths(StringRef libStem, - SmallVector RPath /*={}*/, - SmallVector RunPath /*={}*/, - StringRef libLoader /*=""*/) const { -#define DEBUG_TYPE "Dyld::lookupLibInPaths" - - LLVM_DEBUG(dbgs() << "Dyld::lookupLibInPaths" << libStem.str() - << ", ..., libLoader=" << libLoader << "\n"); - - // Lookup priority is: RPATH, LD_LIBRARY_PATH/m_SearchPaths, RUNPATH - // See: https://en.wikipedia.org/wiki/Rpath - // See: https://amir.rachum.com/blog/2016/09/17/shared-libraries/ - - LLVM_DEBUG(dbgs() << "Dyld::lookupLibInPaths: \n"); - LLVM_DEBUG(dbgs() << ":: RPATH\n"); - for (auto Info : RPath) { - LLVM_DEBUG(dbgs() << ":::: " << Info.str() << "\n"); - } - LLVM_DEBUG(dbgs() << ":: SearchPaths (LD_LIBRARY_PATH, etc...)\n"); - for (auto Info : getSearchPaths()) { - LLVM_DEBUG(dbgs() << ":::: " << Info.Path << ", user=" << (Info.IsUser?"true":"false") << "\n"); - } - LLVM_DEBUG(dbgs() << ":: RUNPATH\n"); - for (auto Info : RunPath) { - LLVM_DEBUG(dbgs() << ":::: " << Info.str() << "\n"); - } - - SmallString<512> ThisPath; - // RPATH - for (auto Info : RPath) { - ThisPath = substAll(Info, libLoader); - llvm::sys::path::append(ThisPath, libStem); - // to absolute path? - LLVM_DEBUG(dbgs() << "## Try: " << ThisPath); - if (isSharedLibrary(ThisPath.str())) { - LLVM_DEBUG(dbgs() << " ... Found (in RPATH)!\n"); - return ThisPath.str().str(); - } - } - // m_SearchPaths - for (const SearchPathInfo& Info : m_SearchPaths) { - ThisPath = Info.Path; - llvm::sys::path::append(ThisPath, libStem); - // to absolute path? - LLVM_DEBUG(dbgs() << "## Try: " << ThisPath); - if (isSharedLibrary(ThisPath.str())) { - LLVM_DEBUG(dbgs() << " ... Found (in SearchPaths)!\n"); - return ThisPath.str().str(); - } - } - // RUNPATH - for (auto Info : RunPath) { - ThisPath = substAll(Info, libLoader); - llvm::sys::path::append(ThisPath, libStem); - // to absolute path? - LLVM_DEBUG(dbgs() << "## Try: " << ThisPath); - if (isSharedLibrary(ThisPath.str())) { - LLVM_DEBUG(dbgs() << " ... Found (in RUNPATH)!\n"); - return ThisPath.str().str(); - } - } - - LLVM_DEBUG(dbgs() << "## NotFound!!!\n"); - - return ""; - -#undef DEBUG_TYPE - } - - std::string - DynamicLibraryManager::lookupLibMaybeAddExt(StringRef libStem, - SmallVector RPath /*={}*/, - SmallVector RunPath /*={}*/, - StringRef libLoader /*=""*/) const { -#define DEBUG_TYPE "Dyld::lookupLibMaybeAddExt:" - - using namespace llvm::sys; - - LLVM_DEBUG(dbgs() << "Dyld::lookupLibMaybeAddExt: " << libStem.str() - << ", ..., libLoader=" << libLoader << "\n"); - - std::string foundDyLib = lookupLibInPaths(libStem, RPath, RunPath, libLoader); - - if (foundDyLib.empty()) { - // Add DyLib extension: - SmallString<512> filenameWithExt(libStem); -#if defined(LLVM_ON_UNIX) -#ifdef __APPLE__ - SmallString<512>::iterator IStemEnd = filenameWithExt.end() - 1; -#endif - static const char* DyLibExt = ".so"; -#elif defined(_WIN32) - static const char* DyLibExt = ".dll"; -#else -# error "Unsupported platform." -#endif - filenameWithExt += DyLibExt; - foundDyLib = lookupLibInPaths(filenameWithExt, RPath, RunPath, libLoader); -#ifdef __APPLE__ - if (foundDyLib.empty()) { - filenameWithExt.erase(IStemEnd + 1, filenameWithExt.end()); - filenameWithExt += ".dylib"; - foundDyLib = lookupLibInPaths(filenameWithExt, RPath, RunPath, libLoader); - } -#endif - } - - if (foundDyLib.empty()) - return std::string(); - - // get canonical path name and check if already loaded - const std::string Path = platform::NormalizePath(foundDyLib); - if (Path.empty()) { - LLVM_DEBUG(dbgs() << "cling::DynamicLibraryManager::lookupLibMaybeAddExt(): " - << "error getting real (canonical) path of library " << foundDyLib << '\n'); - return foundDyLib; - } - return Path; - -#undef DEBUG_TYPE - } - - std::string DynamicLibraryManager::normalizePath(StringRef path) { -#define DEBUG_TYPE "Dyld::normalizePath:" - // Make the path canonical if the file exists. - const std::string Path = path.str(); - struct stat buffer; - if (::stat(Path.c_str(), &buffer) != 0) - return std::string(); - - const std::string NPath = platform::NormalizePath(Path); - if (NPath.empty()) - LLVM_DEBUG(dbgs() << "Could not normalize: '" << Path << "'"); - return NPath; -#undef DEBUG_TYPE - } - - std::string RPathToStr2(SmallVector V) { - std::string result; - for (auto item : V) - result += item.str() + ","; - if (!result.empty()) - result.pop_back(); - return result; - } - - std::string - DynamicLibraryManager::lookupLibrary(StringRef libStem, - SmallVector RPath /*={}*/, - SmallVector RunPath /*={}*/, - StringRef libLoader /*=""*/, - bool variateLibStem /*=true*/) const { -#define DEBUG_TYPE "Dyld::lookupLibrary:" - LLVM_DEBUG(dbgs() << "Dyld::lookupLibrary: " << libStem.str() << ", " - << RPathToStr2(RPath) << ", " << RPathToStr2(RunPath) - << ", " << libLoader.str() << "\n"); - - // If it is an absolute path, don't try iterate over the paths. - if (llvm::sys::path::is_absolute(libStem)) { - if (isSharedLibrary(libStem)) - return normalizePath(libStem); - - LLVM_DEBUG(dbgs() << "Dyld::lookupLibrary: '" << libStem.str() << "'" - << "is not a shared library\n"); - return std::string(); - } - - // Subst all known linker variables ($origin, @rpath, etc.) -#ifdef __APPLE__ - // On MacOS @rpath is preplaced by all paths in RPATH one by one. - if (libStem.starts_with_insensitive("@rpath")) { - for (auto& P : RPath) { - std::string result = substFront(libStem, "@rpath", P); - if (isSharedLibrary(result)) - return normalizePath(result); - } - } else { -#endif - std::string result = substAll(libStem, libLoader); - if (isSharedLibrary(result)) - return normalizePath(result); -#ifdef __APPLE__ - } -#endif - - // Expand libStem with paths, extensions, etc. - std::string foundName; - if (variateLibStem) { - foundName = lookupLibMaybeAddExt(libStem, RPath, RunPath, libLoader); - if (foundName.empty()) { - StringRef libStemName = llvm::sys::path::filename(libStem); - if (!libStemName.starts_with("lib")) { - // try with "lib" prefix: - foundName = lookupLibMaybeAddExt( - libStem.str().insert(libStem.size()-libStemName.size(), "lib"), - RPath, - RunPath, - libLoader - ); - } - } - } else { - foundName = lookupLibInPaths(libStem, RPath, RunPath, libLoader); - } - - if (!foundName.empty()) - return platform::NormalizePath(foundName); - - return std::string(); -#undef DEBUG_TYPE - } - - DynamicLibraryManager::LoadLibResult - DynamicLibraryManager::loadLibrary(StringRef libStem, - bool permanent, bool resolved) { -#define DEBUG_TYPE "Dyld::loadLibrary:" - LLVM_DEBUG(dbgs() << "Dyld::loadLibrary: " << libStem.str() << ", " - << (permanent ? "permanent" : "not-permanent") << ", " - << (resolved ? "resolved" : "not-resolved") << "\n"); - - std::string canonicalLoadedLib; - if (resolved) { - canonicalLoadedLib = libStem.str(); - } else { - canonicalLoadedLib = lookupLibrary(libStem); - if (canonicalLoadedLib.empty()) - return kLoadLibNotFound; - } - - if (m_LoadedLibraries.find(canonicalLoadedLib) != m_LoadedLibraries.end()) - return kLoadLibAlreadyLoaded; - - // TODO: !permanent case - - std::string errMsg; - DyLibHandle dyLibHandle = platform::DLOpen(canonicalLoadedLib, &errMsg); - if (!dyLibHandle) { - // We emit callback to LibraryLoadingFailed when we get error with error message. - //TODO: Implement callbacks - - LLVM_DEBUG(dbgs() << "DynamicLibraryManager::loadLibrary(): " << errMsg); - - return kLoadLibLoadError; - } - - std::pair insRes - = m_DyLibs.insert(std::pair(dyLibHandle, - canonicalLoadedLib)); - if (!insRes.second) - return kLoadLibAlreadyLoaded; - m_LoadedLibraries.insert(canonicalLoadedLib); - return kLoadLibSuccess; -#undef DEBUG_TYPE - } - - void DynamicLibraryManager::unloadLibrary(StringRef libStem) { -#define DEBUG_TYPE "Dyld::unloadLibrary:" - std::string canonicalLoadedLib = lookupLibrary(libStem); - if (!isLibraryLoaded(canonicalLoadedLib)) - return; - - DyLibHandle dyLibHandle = nullptr; - for (DyLibs::const_iterator I = m_DyLibs.begin(), E = m_DyLibs.end(); - I != E; ++I) { - if (I->second == canonicalLoadedLib) { - dyLibHandle = I->first; - break; - } - } - - // TODO: !permanent case - - std::string errMsg; - platform::DLClose(dyLibHandle, &errMsg); - if (!errMsg.empty()) { - LLVM_DEBUG(dbgs() << "cling::DynamicLibraryManager::unloadLibrary(): " - << errMsg << '\n'); - } - - //TODO: Implement callbacks - - m_DyLibs.erase(dyLibHandle); - m_LoadedLibraries.erase(canonicalLoadedLib); -#undef DEBUG_TYPE - } - - bool DynamicLibraryManager::isLibraryLoaded(StringRef fullPath) const { - std::string canonPath = normalizePath(fullPath); - if (m_LoadedLibraries.find(canonPath) != m_LoadedLibraries.end()) - return true; - return false; - } - - void DynamicLibraryManager::dump(llvm::raw_ostream* S /*= nullptr*/) const { - llvm::raw_ostream &OS = S ? *S : llvm::outs(); - - // FIXME: print in a stable order the contents of m_SearchPaths - for (const auto& Info : getSearchPaths()) { - if (!Info.IsUser) - OS << "[system] "; - OS << Info.Path.c_str() << "\n"; - } - } - -#if defined(_WIN32) - static bool IsDLL(llvm::StringRef headers) { - using namespace llvm::support::endian; - - uint32_t headeroffset = read32le(headers.data() + 0x3c); - auto peheader = headers.substr(headeroffset, 24); - if (peheader.size() != 24) { - return false; - } - // Read Characteristics from the coff header - uint32_t characteristics = read16le(peheader.data() + 22); - return (characteristics & llvm::COFF::IMAGE_FILE_DLL) != 0; - } -#endif - - bool DynamicLibraryManager::isSharedLibrary(StringRef libFullPath, - bool* exists /*=0*/) { - using namespace llvm; - - auto filetype = sys::fs::get_file_type(libFullPath, /*Follow*/ true); - if (filetype != sys::fs::file_type::regular_file) { - if (exists) { - // get_file_type returns status_error also in case of file_not_found. - *exists = filetype != sys::fs::file_type::status_error; - } - return false; - } - - // Do not use the identify_magic overload taking a path: It will open the - // file and then mmap its contents, possibly causing bus errors when another - // process truncates the file while we are trying to read it. Instead just - // read the first 1024 bytes, which should be enough for identify_magic to - // do its work. - // TODO: Fix the code upstream and consider going back to calling the - // convenience function after a future LLVM upgrade. - std::string path = libFullPath.str(); - std::ifstream in(path, std::ios::binary); - char header[1024] = {0}; - in.read(header, sizeof(header)); - if (in.fail()) { - if (exists) - *exists = false; - return false; - } - - StringRef headerStr(header, in.gcount()); - file_magic Magic = identify_magic(headerStr); - - bool result = -#ifdef __APPLE__ - (Magic == file_magic::macho_fixed_virtual_memory_shared_lib - || Magic == file_magic::macho_dynamically_linked_shared_lib - || Magic == file_magic::macho_dynamically_linked_shared_lib_stub - || Magic == file_magic::macho_universal_binary) -#elif defined(LLVM_ON_UNIX) -#ifdef __CYGWIN__ - (Magic == file_magic::pecoff_executable) -#else - (Magic == file_magic::elf_shared_object) -#endif -#elif defined(_WIN32) - // We should only include dll libraries without including executables, - // object code and others... - (Magic == file_magic::pecoff_executable && IsDLL(headerStr)) -#else -# error "Unsupported platform." -#endif - ; - - return result; - } - -} // end namespace Cpp diff --git a/lib/Interpreter/DynamicLibraryManagerSymbol.cpp b/lib/Interpreter/DynamicLibraryManagerSymbol.cpp deleted file mode 100644 index 31262816e..000000000 --- a/lib/Interpreter/DynamicLibraryManagerSymbol.cpp +++ /dev/null @@ -1,1344 +0,0 @@ -//------------------------------------------------------------------------------ -// CLING - the C++ LLVM-based InterpreterG :) -// author: Vassil Vassilev -// author: Alexander Penev -// -// This file is dual-licensed: you can choose to license it under the University -// of Illinois Open Source License or the GNU Lesser General Public License. See -// LICENSE.TXT for details. -//------------------------------------------------------------------------------ - -#include "DynamicLibraryManager.h" -#include "Paths.h" - -#include "llvm/ADT/SmallSet.h" -#include "llvm/ADT/SmallString.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/ADT/StringSet.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/BinaryFormat/MachO.h" -#include "llvm/Object/COFF.h" -#include "llvm/Object/ELF.h" -#include "llvm/Object/ELFObjectFile.h" -#include "llvm/Object/MachO.h" -#include "llvm/Object/ObjectFile.h" -#include "llvm/Support/Debug.h" -#include "llvm/Support/DynamicLibrary.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/Program.h" -#include "llvm/Support/Format.h" -#include "llvm/Support/WithColor.h" - -#include -#include -#include -#include -#include - - -#ifdef LLVM_ON_UNIX -#include -#include -#include -#endif // LLVM_ON_UNIX - -#ifdef __APPLE__ -#include -#include -#undef LC_LOAD_DYLIB -#undef LC_RPATH -#endif // __APPLE__ - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#define NOMINMAX -#include -#include // For GetModuleFileNameA -#include // For VirtualQuery -#endif - -namespace { - -using BasePath = std::string; -using namespace llvm; - -// This is a GNU implementation of hash used in bloom filter! -static uint32_t GNUHash(StringRef S) { - uint32_t H = 5381; - for (uint8_t C : S) - H = (H << 5) + H + C; - return H; -} - -constexpr uint32_t log2u(std::uint32_t n) { - return (n > 1) ? 1 + log2u(n >> 1) : 0; -} - -struct BloomFilter { - - // https://hur.st/bloomfilter - // - // n = ceil(m / (-k / log(1 - exp(log(p) / k)))) - // p = pow(1 - exp(-k / (m / n)), k) - // m = ceil((n * log(p)) / log(1 / pow(2, log(2)))); - // k = round((m / n) * log(2)); - // - // n = symbolsCount - // p = 0.02 - // k = 2 (k1=GNUHash and k2=GNUHash >> bloomShift) - // m = ceil((symbolsCount * log(p)) / log(1 / pow(2, log(2)))); - // bloomShift = min(5 for bits=32 or 6 for bits=64, log2(symbolsCount)) - // bloomSize = ceil((-1.44 * n * log2f(p)) / bits) - - const int m_Bits = 8 * sizeof(uint64_t); - const float m_P = 0.02f; - - bool m_IsInitialized = false; - uint32_t m_SymbolsCount = 0; - uint32_t m_BloomSize = 0; - uint32_t m_BloomShift = 0; - std::vector m_BloomTable; - - bool TestHash(uint32_t hash) const { - // This function is superhot. No branches here, breaks inlining and makes - // overall performance around 4x slower. - assert(m_IsInitialized && "Not yet initialized!"); - uint32_t hash2 = hash >> m_BloomShift; - uint32_t n = (hash >> log2u(m_Bits)) % m_BloomSize; - uint64_t mask = ((1ULL << (hash % m_Bits)) | (1ULL << (hash2 % m_Bits))); - return (mask & m_BloomTable[n]) == mask; - } - - void AddHash(uint32_t hash) { - assert(m_IsInitialized && "Not yet initialized!"); - uint32_t hash2 = hash >> m_BloomShift; - uint32_t n = (hash >> log2u(m_Bits)) % m_BloomSize; - uint64_t mask = ((1ULL << (hash % m_Bits)) | (1ULL << (hash2 % m_Bits))); - m_BloomTable[n] |= mask; - } - - void ResizeTable(uint32_t newSymbolsCount) { - assert(m_SymbolsCount == 0 && "Not supported yet!"); - m_SymbolsCount = newSymbolsCount; - m_BloomSize = ceil((-1.44f * m_SymbolsCount * log2f(m_P)) / m_Bits); - m_BloomShift = std::min(6u, log2u(m_SymbolsCount)); - m_BloomTable.resize(m_BloomSize); - } - -}; - -/// An efficient representation of a full path to a library which does not -/// duplicate common path patterns reducing the overall memory footprint. -/// -/// For example, `/home/.../lib/libA.so`, m_Path will contain a pointer -/// to `/home/.../lib/` -/// will be stored and .second `libA.so`. -/// This approach reduces the duplicate paths as at one location there may be -/// plenty of libraries. -struct LibraryPath { - const BasePath& m_Path; - std::string m_LibName; - BloomFilter m_Filter; - StringSet<> m_Symbols; - //std::vector m_LibDeps; - - LibraryPath(const BasePath& Path, const std::string& LibName) - : m_Path(Path), m_LibName(LibName) { - } - - bool operator==(const LibraryPath &other) const { - return (&m_Path == &other.m_Path || m_Path == other.m_Path) && - m_LibName == other.m_LibName; - } - - const std::string GetFullName() const { - SmallString<512> Vec(m_Path); - llvm::sys::path::append(Vec, StringRef(m_LibName)); - return Vec.str().str(); - } - - void AddBloom(StringRef symbol) { - m_Filter.AddHash(GNUHash(symbol)); - } - - StringRef AddSymbol(const std::string& symbol) { - auto it = m_Symbols.insert(symbol); - return it.first->getKey(); - } - - bool hasBloomFilter() const { - return m_Filter.m_IsInitialized; - } - - bool isBloomFilterEmpty() const { - assert(m_Filter.m_IsInitialized && "Bloom filter not initialized!"); - return m_Filter.m_SymbolsCount == 0; - } - - void InitializeBloomFilter(uint32_t newSymbolsCount) { - assert(!m_Filter.m_IsInitialized && - "Cannot re-initialize non-empty filter!"); - m_Filter.m_IsInitialized = true; - m_Filter.ResizeTable(newSymbolsCount); - } - - bool MayExistSymbol(uint32_t hash) const { - // The library had no symbols and the bloom filter is empty. - if (isBloomFilterEmpty()) - return false; - - return m_Filter.TestHash(hash); - } - - bool ExistSymbol(StringRef symbol) const { - return m_Symbols.find(symbol) != m_Symbols.end(); - } -}; - - -/// A helper class keeping track of loaded libraries. It implements a fast -/// search O(1) while keeping deterministic iterability in a memory efficient -/// way. The underlying set uses a custom hasher for better efficiency given the -/// specific problem where the library names (m_LibName) are relatively short -/// strings and the base paths (m_Path) are repetitive long strings. -class LibraryPaths { - struct LibraryPathHashFn { - size_t operator()(const LibraryPath& item) const { - return std::hash()(item.m_Path.length()) ^ - std::hash()(item.m_LibName); - } - }; - - std::vector m_Libs; - std::unordered_set m_LibsH; -public: - bool HasRegisteredLib(const LibraryPath& Lib) const { - return m_LibsH.count(Lib); - } - - const LibraryPath* GetRegisteredLib(const LibraryPath& Lib) const { - auto search = m_LibsH.find(Lib); - if (search != m_LibsH.end()) - return &(*search); - return nullptr; - } - - const LibraryPath* RegisterLib(const LibraryPath& Lib) { - auto it = m_LibsH.insert(Lib); - assert(it.second && "Already registered!"); - m_Libs.push_back(&*it.first); - return &*it.first; - } - - void UnregisterLib(const LibraryPath& Lib) { - auto found = m_LibsH.find(Lib); - if (found == m_LibsH.end()) - return; - - m_Libs.erase(std::find(m_Libs.begin(), m_Libs.end(), &*found)); - m_LibsH.erase(found); - } - - size_t size() const { - assert(m_Libs.size() == m_LibsH.size()); - return m_Libs.size(); - } - - const std::vector& GetLibraries() const { - return m_Libs; - } -}; - -#ifndef _WIN32 -// Cached version of system function lstat -static inline mode_t cached_lstat(const char *path) { - static StringMap lstat_cache; - - // If already cached - return cached result - auto it = lstat_cache.find(path); - if (it != lstat_cache.end()) - return it->second; - - // If result not in cache - call system function and cache result - struct stat buf; - mode_t st_mode = (lstat(path, &buf) == -1) ? 0 : buf.st_mode; - lstat_cache.insert(std::pair(path, st_mode)); - return st_mode; -} - -// Cached version of system function readlink -static inline StringRef cached_readlink(const char* pathname) { - static StringMap readlink_cache; - - // If already cached - return cached result - auto it = readlink_cache.find(pathname); - if (it != readlink_cache.end()) - return StringRef(it->second); - - // If result not in cache - call system function and cache result - char buf[PATH_MAX]; - ssize_t len; - if ((len = readlink(pathname, buf, sizeof(buf))) != -1) { - buf[len] = '\0'; - std::string s(buf); - readlink_cache.insert(std::pair(pathname, s)); - return readlink_cache[pathname]; - } - return ""; -} -#endif - -// Cached version of system function realpath -std::string cached_realpath(StringRef path, StringRef base_path = "", - bool is_base_path_real = false, - long symlooplevel = 40) { - if (path.empty()) { - errno = ENOENT; - return ""; - } - - if (!symlooplevel) { - errno = ELOOP; - return ""; - } - - // If already cached - return cached result - static StringMap> cache; - bool relative_path = llvm::sys::path::is_relative(path); - if (!relative_path) { - auto it = cache.find(path); - if (it != cache.end()) { - errno = it->second.second; - return it->second.first; - } - } - - // If result not in cache - call system function and cache result - - StringRef sep(llvm::sys::path::get_separator()); - SmallString<256> result(sep); -#ifndef _WIN32 - SmallVector p; - - // Relative or absolute path - if (relative_path) { - if (is_base_path_real) { - result.assign(base_path); - } else { - if (path[0] == '~' && (path.size() == 1 || llvm::sys::path::is_separator(path[1]))) { - static SmallString<128> home; - if (home.str().empty()) - llvm::sys::path::home_directory(home); - StringRef(home).split(p, sep, /*MaxSplit*/ -1, /*KeepEmpty*/ false); - } else if (base_path.empty()) { - static SmallString<256> current_path; - if (current_path.str().empty()) - llvm::sys::fs::current_path(current_path); - StringRef(current_path).split(p, sep, /*MaxSplit*/ -1, /*KeepEmpty*/ false); - } else { - base_path.split(p, sep, /*MaxSplit*/ -1, /*KeepEmpty*/ false); - } - } - } - path.split(p, sep, /*MaxSplit*/ -1, /*KeepEmpty*/ false); - - // Handle path list items - for (auto item : p) { - if (item == ".") - continue; // skip "." element in "abc/./def" - if (item == "..") { - // collapse "a/b/../c" to "a/c" - size_t s = result.rfind(sep); - if (s != llvm::StringRef::npos) - result.resize(s); - if (result.empty()) - result = sep; - continue; - } - - size_t old_size = result.size(); - llvm::sys::path::append(result, item); - mode_t st_mode = cached_lstat(result.c_str()); - if (S_ISLNK(st_mode)) { - StringRef symlink = cached_readlink(result.c_str()); - if (llvm::sys::path::is_relative(symlink)) { - result.resize(old_size); - result = cached_realpath(symlink, result, true, symlooplevel - 1); - } else { - result = cached_realpath(symlink, "", true, symlooplevel - 1); - } - } else if (st_mode == 0) { - cache.insert(std::pair>( - path, - std::pair("",ENOENT)) - ); - errno = ENOENT; - return ""; - } - } -#else - llvm::sys::fs::real_path(path, result); -#endif - cache.insert(std::pair>( - path, - std::pair(result.str().str(),errno)) - ); - return result.str().str(); -} - -using namespace llvm; -using namespace llvm::object; - -template -static Expected getDynamicStrTab(const ELFFile* Elf) { - auto DynamicEntriesOrError = Elf->dynamicEntries(); - if (!DynamicEntriesOrError) - return DynamicEntriesOrError.takeError(); - - for (const typename ELFT::Dyn& Dyn : *DynamicEntriesOrError) { - if (Dyn.d_tag == ELF::DT_STRTAB) { - auto MappedAddrOrError = Elf->toMappedAddr(Dyn.getPtr()); - if (!MappedAddrOrError) - return MappedAddrOrError.takeError(); - return StringRef(reinterpret_cast(*MappedAddrOrError)); - } - } - - // If the dynamic segment is not present, we fall back on the sections. - auto SectionsOrError = Elf->sections(); - if (!SectionsOrError) - return SectionsOrError.takeError(); - - for (const typename ELFT::Shdr &Sec : *SectionsOrError) { - if (Sec.sh_type == ELF::SHT_DYNSYM) - return Elf->getStringTableForSymtab(Sec); - } - - return createError("dynamic string table not found"); -} - -static StringRef GetGnuHashSection(llvm::object::ObjectFile *file) { - for (auto S : file->sections()) { - StringRef name = llvm::cantFail(S.getName()); - if (name == ".gnu.hash") { - return llvm::cantFail(S.getContents()); - } - } - return ""; -} - -/// Bloom filter is a stochastic data structure which can tell us if a symbol -/// name does not exist in a library with 100% certainty. If it tells us it -/// exists this may not be true: -/// https://blogs.oracle.com/solaris/gnu-hash-elf-sections-v2 -/// -/// ELF has this optimization in the new linkers by default, It is stored in the -/// gnu.hash section of the object file. -/// -///\returns true if the symbol may be in the library. -static bool MayExistInElfObjectFile(llvm::object::ObjectFile *soFile, - uint32_t hash) { - assert(soFile->isELF() && "Not ELF"); - - // Compute the platform bitness -- either 64 or 32. - const unsigned bits = 8 * soFile->getBytesInAddress(); - - StringRef contents = GetGnuHashSection(soFile); - if (contents.size() < 16) - // We need to search if the library doesn't have .gnu.hash section! - return true; - const char* hashContent = contents.data(); - - // See https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/ for .gnu.hash - // table layout. - uint32_t maskWords = *reinterpret_cast(hashContent + 8); - uint32_t shift2 = *reinterpret_cast(hashContent + 12); - uint32_t hash2 = hash >> shift2; - uint32_t n = (hash / bits) % maskWords; - - const char *bloomfilter = hashContent + 16; - const char *hash_pos = bloomfilter + n*(bits/8); // * (Bits / 8) - uint64_t word = *reinterpret_cast(hash_pos); - uint64_t bitmask = ( (1ULL << (hash % bits)) | (1ULL << (hash2 % bits))); - return (bitmask & word) == bitmask; -} - -} // anon namespace - -// This function isn't referenced outside its translation unit, but it -// can't use the "static" keyword because its address is used for -// GetMainExecutable (since some platforms don't support taking the -// address of main, and some platforms can't implement GetMainExecutable -// without being given the address of a function in the main executable). -std::string GetExecutablePath() { - // This just needs to be some symbol in the binary; C++ doesn't - // allow taking the address of ::main however. - return Cpp::DynamicLibraryManager::getSymbolLocation(&GetExecutablePath); -} - -namespace Cpp { - class Dyld { - struct BasePathHashFunction { - size_t operator()(const BasePath& item) const { - return std::hash()(item); - } - }; - - struct BasePathEqFunction { - size_t operator()(const BasePath& l, const BasePath& r) const { - return &l == &r || l == r; - } - }; - /// A memory efficient llvm::VectorSet. The class provides O(1) search - /// complexity. It is tuned to compare BasePaths first by checking the - /// address and then the representation which models the base path reuse. - class BasePaths { - public: - std::unordered_set m_Paths; - public: - const BasePath& RegisterBasePath(const std::string& Path, - bool* WasInserted = nullptr) { - auto it = m_Paths.insert(Path); - if (WasInserted) - *WasInserted = it.second; - - return *it.first; - } - - bool Contains(StringRef Path) { - return m_Paths.count(Path.str()); - } - }; - - bool m_FirstRun = true; - bool m_FirstRunSysLib = true; - bool m_UseBloomFilter = true; - bool m_UseHashTable = true; - - const Cpp::DynamicLibraryManager& m_DynamicLibraryManager; - - /// The basename of `/home/.../lib/libA.so`, - /// m_BasePaths will contain `/home/.../lib/` - BasePaths m_BasePaths; - - LibraryPaths m_Libraries; - LibraryPaths m_SysLibraries; - /// Contains a set of libraries which we gave to the user via ResolveSymbol - /// call and next time we should check if the user loaded them to avoid - /// useless iterations. - LibraryPaths m_QueriedLibraries; - - using PermanentlyIgnoreCallbackProto = std::function; - const PermanentlyIgnoreCallbackProto m_ShouldPermanentlyIgnoreCallback; - const StringRef m_ExecutableFormat; - - /// Scan for shared objects which are not yet loaded. They are a our symbol - /// resolution candidate sources. - /// NOTE: We only scan not loaded shared objects. - /// \param[in] searchSystemLibraries - whether to decent to standard system - /// locations for shared objects. - void ScanForLibraries(bool searchSystemLibraries = false); - - /// Builds a bloom filter lookup optimization. - void BuildBloomFilter(LibraryPath* Lib, llvm::object::ObjectFile *BinObjFile, - unsigned IgnoreSymbolFlags = 0) const; - - - /// Looks up symbols from a an object file, representing the library. - ///\param[in] Lib - full path to the library. - ///\param[in] mangledName - the mangled name to look for. - ///\param[in] IgnoreSymbolFlags - The symbols to ignore upon a match. - ///\returns true on success. - bool ContainsSymbol(const LibraryPath* Lib, StringRef mangledName, - unsigned IgnoreSymbolFlags = 0) const; - - bool ShouldPermanentlyIgnore(StringRef FileName) const; - void dumpDebugInfo() const; - public: - Dyld(const Cpp::DynamicLibraryManager &DLM, - PermanentlyIgnoreCallbackProto shouldIgnore, - StringRef execFormat) - : m_DynamicLibraryManager(DLM), - m_ShouldPermanentlyIgnoreCallback(shouldIgnore), - m_ExecutableFormat(execFormat) { } - - ~Dyld(){}; - - std::string searchLibrariesForSymbol(StringRef mangledName, - bool searchSystem); - }; - - std::string RPathToStr(SmallVector V) { - std::string result; - for (auto item : V) - result += item.str() + ","; - if (!result.empty()) - result.pop_back(); - return result; - } - - template - void HandleDynTab(const ELFFile* Elf, StringRef FileName, - SmallVector& RPath, - SmallVector& RunPath, - std::vector& Deps, - bool& isPIEExecutable) { -#define DEBUG_TYPE "Dyld:" - const char *Data = ""; - if (Expected StrTabOrErr = getDynamicStrTab(Elf)) - Data = StrTabOrErr.get().data(); - - isPIEExecutable = false; - - auto DynamicEntriesOrError = Elf->dynamicEntries(); - if (!DynamicEntriesOrError) { - LLVM_DEBUG(dbgs() << "Dyld: failed to read dynamic entries in" - << "'" << FileName.str() << "'\n"); - return; - } - - for (const typename ELFT::Dyn& Dyn : *DynamicEntriesOrError) { - switch (Dyn.d_tag) { - case ELF::DT_NEEDED: - Deps.push_back(Data + Dyn.d_un.d_val); - break; - case ELF::DT_RPATH: - SplitPaths(Data + Dyn.d_un.d_val, RPath, utils::kAllowNonExistent, - Cpp::utils::platform::kEnvDelim, false); - break; - case ELF::DT_RUNPATH: - SplitPaths(Data + Dyn.d_un.d_val, RunPath, utils::kAllowNonExistent, - Cpp::utils::platform::kEnvDelim, false); - break; - case ELF::DT_FLAGS_1: - // Check if this is not a pie executable. - if (Dyn.d_un.d_val & llvm::ELF::DF_1_PIE) - isPIEExecutable = true; - break; - // (Dyn.d_tag == ELF::DT_NULL) continue; - // (Dyn.d_tag == ELF::DT_AUXILIARY || Dyn.d_tag == ELF::DT_FILTER) - } - } -#undef DEBUG_TYPE - } - - void Dyld::ScanForLibraries(bool searchSystemLibraries/* = false*/) { -#define DEBUG_TYPE "Dyld:ScanForLibraries:" - - const auto &searchPaths = m_DynamicLibraryManager.getSearchPaths(); - - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: system=" << (searchSystemLibraries?"true":"false") << "\n"); - for (const DynamicLibraryManager::SearchPathInfo &Info : searchPaths) - LLVM_DEBUG(dbgs() << ">>>" << Info.Path << ", " << (Info.IsUser?"user\n":"system\n")); - - llvm::SmallSet ScannedPaths; - - for (const DynamicLibraryManager::SearchPathInfo &Info : searchPaths) { - if (Info.IsUser != searchSystemLibraries) { - // Examples which we should handle. - // File Real - // /lib/1/1.so /lib/1/1.so // file - // /lib/1/2.so->/lib/1/1.so /lib/1/1.so // file local link - // /lib/1/3.so->/lib/3/1.so /lib/3/1.so // file external link - // /lib/2->/lib/1 // path link - // /lib/2/1.so /lib/1/1.so // path link, file - // /lib/2/2.so->/lib/1/1.so /lib/1/1.so // path link, file local link - // /lib/2/3.so->/lib/3/1.so /lib/3/1.so // path link, file external link - // - // /lib/3/1.so - // /lib/3/2.so->/system/lib/s.so - // /lib/3/3.so - // /system/lib/1.so - // - // libL.so NEEDED/RPATH libR.so /lib/some-rpath/libR.so // needed/dependedt library in libL.so RPATH/RUNPATH or other (in)direct dep - // - // Paths = /lib/1 : /lib/2 : /lib/3 - - // m_BasePaths = ["/lib/1", "/lib/3", "/system/lib"] - // m_*Libraries = [<0,"1.so">, <1,"1.so">, <2,"s.so">, <1,"3.so">] - - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries Iter:" << Info.Path << " -> "); - std::string RealPath = cached_realpath(Info.Path); - - llvm::StringRef DirPath(RealPath); - LLVM_DEBUG(dbgs() << RealPath << "\n"); - - if (!llvm::sys::fs::is_directory(DirPath) || DirPath.empty()) - continue; - - // Already searched? - const BasePath &ScannedBPath = m_BasePaths.RegisterBasePath(RealPath); - if (ScannedPaths.count(&ScannedBPath)) { - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries Already scanned: " << RealPath << "\n"); - continue; - } - - // FileName must be always full/absolute/resolved file name. - std::function HandleLib = - [&](llvm::StringRef FileName, unsigned level) { - - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries HandleLib:" << FileName.str() - << ", level=" << level << " -> "); - - llvm::StringRef FileRealPath = llvm::sys::path::parent_path(FileName); - llvm::StringRef FileRealName = llvm::sys::path::filename(FileName); - const BasePath& BaseP = - m_BasePaths.RegisterBasePath(FileRealPath.str()); - LibraryPath LibPath(BaseP, FileRealName.str()); //bp, str - - if (m_SysLibraries.GetRegisteredLib(LibPath) || - m_Libraries.GetRegisteredLib(LibPath)) { - LLVM_DEBUG(dbgs() << "Already handled!!!\n"); - return; - } - - if (ShouldPermanentlyIgnore(FileName)) { - LLVM_DEBUG(dbgs() << "PermanentlyIgnored!!!\n"); - return; - } - - if (searchSystemLibraries) - m_SysLibraries.RegisterLib(LibPath); - else - m_Libraries.RegisterLib(LibPath); - - // Handle lib dependencies - llvm::SmallVector RPath; - llvm::SmallVector RunPath; - std::vector Deps; - auto ObjFileOrErr = - llvm::object::ObjectFile::createObjectFile(FileName); - if (llvm::Error Err = ObjFileOrErr.takeError()) { - std::string Message; - handleAllErrors(std::move(Err), [&](llvm::ErrorInfoBase &EIB) { - Message += EIB.message() + "; "; - }); - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Failed to read object file " - << FileName.str() << " Errors: " << Message << "\n"); - return; - } - llvm::object::ObjectFile *BinObjF = ObjFileOrErr.get().getBinary(); - if (BinObjF->isELF()) { - bool isPIEExecutable = false; - - if (const auto* ELF = dyn_cast(BinObjF)) - HandleDynTab(&ELF->getELFFile(), FileName, RPath, RunPath, Deps, - isPIEExecutable); - else if (const auto* ELF = dyn_cast(BinObjF)) - HandleDynTab(&ELF->getELFFile(), FileName, RPath, RunPath, Deps, - isPIEExecutable); - else if (const auto* ELF = dyn_cast(BinObjF)) - HandleDynTab(&ELF->getELFFile(), FileName, RPath, RunPath, Deps, - isPIEExecutable); - else if (const auto* ELF = dyn_cast(BinObjF)) - HandleDynTab(&ELF->getELFFile(), FileName, RPath, RunPath, Deps, - isPIEExecutable); - - if ((level == 0) && isPIEExecutable) { - if (searchSystemLibraries) - m_SysLibraries.UnregisterLib(LibPath); - else - m_Libraries.UnregisterLib(LibPath); - return; - } - } else if (BinObjF->isMachO()) { - MachOObjectFile *Obj = (MachOObjectFile*)BinObjF; - for (const auto &Command : Obj->load_commands()) { - if (Command.C.cmd == MachO::LC_LOAD_DYLIB) { - //Command.C.cmd == MachO::LC_ID_DYLIB || - //Command.C.cmd == MachO::LC_LOAD_WEAK_DYLIB || - //Command.C.cmd == MachO::LC_REEXPORT_DYLIB || - //Command.C.cmd == MachO::LC_LAZY_LOAD_DYLIB || - //Command.C.cmd == MachO::LC_LOAD_UPWARD_DYLIB || - MachO::dylib_command dylibCmd = - Obj->getDylibIDLoadCommand(Command); - Deps.push_back(StringRef(Command.Ptr + dylibCmd.dylib.name)); - } - else if (Command.C.cmd == MachO::LC_RPATH) { - MachO::rpath_command rpathCmd = Obj->getRpathCommand(Command); - SplitPaths(Command.Ptr + rpathCmd.path, RPath, - utils::kAllowNonExistent, - Cpp::utils::platform::kEnvDelim, false); - } - } - } else if (BinObjF->isCOFF()) { - // TODO: COFF support - } - - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Deps Info:\n"); - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: RPATH=" << RPathToStr(RPath) << "\n"); - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: RUNPATH=" << RPathToStr(RunPath) << "\n"); - int x = 0; - for (StringRef dep : Deps) - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Deps[" << x++ << "]=" << dep.str() << "\n"); - - // Heuristics for workaround performance problems: - // (H1) If RPATH and RUNPATH == "" -> skip handling Deps - if (RPath.empty() && RunPath.empty()) { - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Skip all deps by Heuristic1: " << FileName.str() << "\n"); - return; - }; - // (H2) If RPATH subset of LD_LIBRARY_PATH && - // RUNPATH subset of LD_LIBRARY_PATH -> skip handling Deps - if (std::all_of(RPath.begin(), RPath.end(), [&](StringRef item){ return std::any_of(searchPaths.begin(), searchPaths.end(), [&](DynamicLibraryManager::SearchPathInfo item1){ return item==item1.Path; }); }) && - std::all_of(RunPath.begin(), RunPath.end(), [&](StringRef item){ return std::any_of(searchPaths.begin(), searchPaths.end(), [&](DynamicLibraryManager::SearchPathInfo item1){ return item==item1.Path; }); }) ) { - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Skip all deps by Heuristic2: " << FileName.str() << "\n"); - return; - } - - // Handle dependencies - for (StringRef dep : Deps) { - std::string dep_full = - m_DynamicLibraryManager.lookupLibrary(dep, RPath, RunPath, FileName, false); - HandleLib(dep_full, level + 1); - } - - }; - - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Iterator: " << DirPath << "\n"); - std::error_code EC; - for (llvm::sys::fs::directory_iterator DirIt(DirPath, EC), DirEnd; - DirIt != DirEnd && !EC; DirIt.increment(EC)) { - - LLVM_DEBUG(dbgs() << "Dyld::ScanForLibraries: Iterator >>> " - << DirIt->path() << ", type=" - << (short)(DirIt->type()) << "\n"); - - const llvm::sys::fs::file_type ft = DirIt->type(); - if (ft == llvm::sys::fs::file_type::regular_file) { - HandleLib(DirIt->path(), 0); - } else if (ft == llvm::sys::fs::file_type::symlink_file) { - std::string DepFileName_str = cached_realpath(DirIt->path()); - llvm::StringRef DepFileName = DepFileName_str; - assert(!llvm::sys::fs::is_symlink_file(DepFileName)); - if (!llvm::sys::fs::is_directory(DepFileName)) - HandleLib(DepFileName, 0); - } - } - - // Register the DirPath as fully scanned. - ScannedPaths.insert(&ScannedBPath); - } - } -#undef DEBUG_TYPE - } - - void Dyld::BuildBloomFilter(LibraryPath* Lib, - llvm::object::ObjectFile *BinObjFile, - unsigned IgnoreSymbolFlags /*= 0*/) const { -#define DEBUG_TYPE "Dyld::BuildBloomFilter:" - assert(m_UseBloomFilter && "Bloom filter is disabled"); - assert(!Lib->hasBloomFilter() && "Already built!"); - - using namespace llvm; - using namespace llvm::object; - - LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Start building Bloom filter for: " - << Lib->GetFullName() << "\n"); - - // If BloomFilter is empty then build it. - // Count Symbols and generate BloomFilter - uint32_t SymbolsCount = 0; - std::list symbols; - for (const llvm::object::SymbolRef &S : BinObjFile->symbols()) { - uint32_t Flags = llvm::cantFail(S.getFlags()); - // Do not insert in the table symbols flagged to ignore. - if (Flags & IgnoreSymbolFlags) - continue; - - // Note, we are at last resort and loading library based on a weak - // symbol is allowed. Otherwise, the JIT will issue an unresolved - // symbol error. - // - // There are other weak symbol kinds (marked as 'V') to denote - // typeinfo and vtables. It is unclear whether we should load such - // libraries or from which library we should resolve the symbol. - // We seem to not have a way to differentiate it from the symbol API. - - llvm::Expected SymNameErr = S.getName(); - if (!SymNameErr) { - LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Failed to read symbol " - << SymNameErr.get() << "\n"); - continue; - } - - if (SymNameErr.get().empty()) - continue; - - ++SymbolsCount; - symbols.push_back(SymNameErr.get()); - } - - if (BinObjFile->isELF()) { - // ELF file format has .dynstr section for the dynamic symbol table. - const auto *ElfObj = cast(BinObjFile); - - for (const object::SymbolRef &S : ElfObj->getDynamicSymbolIterators()) { - uint32_t Flags = llvm::cantFail(S.getFlags()); - // DO NOT insert to table if symbol was undefined - if (Flags & llvm::object::SymbolRef::SF_Undefined) - continue; - - // Note, we are at last resort and loading library based on a weak - // symbol is allowed. Otherwise, the JIT will issue an unresolved - // symbol error. - // - // There are other weak symbol kinds (marked as 'V') to denote - // typeinfo and vtables. It is unclear whether we should load such - // libraries or from which library we should resolve the symbol. - // We seem to not have a way to differentiate it from the symbol API. - - llvm::Expected SymNameErr = S.getName(); - if (!SymNameErr) { - LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Failed to read symbol " - << SymNameErr.get() << "\n"); - continue; - } - - if (SymNameErr.get().empty()) - continue; - - ++SymbolsCount; - symbols.push_back(SymNameErr.get()); - } - } - else if (BinObjFile->isCOFF()) { // On Windows, the symbols are present in COFF format. - llvm::object::COFFObjectFile* CoffObj = cast(BinObjFile); - - // In COFF, the symbols are not present in the SymbolTable section - // of the Object file. They are present in the ExportDirectory section. - for (auto I=CoffObj->export_directory_begin(), - E=CoffObj->export_directory_end(); I != E; I = ++I) { - // All the symbols are already flagged as exported. - // We cannot really ignore symbols based on flags as we do on unix. - StringRef Name; - auto Err = I->getSymbolName(Name); - - if (Err) { - std::string Message; - handleAllErrors(std::move(Err), [&](llvm::ErrorInfoBase& EIB) { - Message += EIB.message() + "; "; - }); - LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Failed to read symbol " - << Message << "\n"); - continue; - } - if (Name.empty()) - continue; - - ++SymbolsCount; - symbols.push_back(Name); - } - } - - Lib->InitializeBloomFilter(SymbolsCount); - - if (!SymbolsCount) { - LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: No symbols!\n"); - return; - } - - LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter: Symbols:\n"); - for (auto it : symbols) - LLVM_DEBUG(dbgs() << "Dyld::BuildBloomFilter" << "- " << it << "\n"); - - // Generate BloomFilter - for (const auto &S : symbols) { - if (m_UseHashTable) - Lib->AddBloom(Lib->AddSymbol(S.str())); - else - Lib->AddBloom(S); - } -#undef DEBUG_TYPE - } - - bool Dyld::ContainsSymbol(const LibraryPath* Lib, - StringRef mangledName, - unsigned IgnoreSymbolFlags /*= 0*/) const { -#define DEBUG_TYPE "Dyld::ContainsSymbol:" - const std::string library_filename = Lib->GetFullName(); - - LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Find symbol: lib=" - << library_filename << ", mangled=" - << mangledName.str() << "\n"); - - auto ObjF = llvm::object::ObjectFile::createObjectFile(library_filename); - if (llvm::Error Err = ObjF.takeError()) { - std::string Message; - handleAllErrors(std::move(Err), [&](llvm::ErrorInfoBase &EIB) { - Message += EIB.message() + "; "; - }); - LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Failed to read object file " - << library_filename << " Errors: " << Message << "\n"); - return false; - } - - llvm::object::ObjectFile *BinObjFile = ObjF.get().getBinary(); - - uint32_t hashedMangle = GNUHash(mangledName); - // Check for the gnu.hash section if ELF. - // If the symbol doesn't exist, exit early. - if (BinObjFile->isELF() && - !MayExistInElfObjectFile(BinObjFile, hashedMangle)) { - LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: ELF BloomFilter: Skip symbol <" << mangledName.str() << ">.\n"); - return false; - } - - if (m_UseBloomFilter) { - // Use our bloom filters and create them if necessary. - if (!Lib->hasBloomFilter()) - BuildBloomFilter(const_cast(Lib), BinObjFile, - IgnoreSymbolFlags); - - // If the symbol does not exist, exit early. In case it may exist, iterate. - if (!Lib->MayExistSymbol(hashedMangle)) { - LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: BloomFilter: Skip symbol <" << mangledName.str() << ">.\n"); - return false; - } - LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: BloomFilter: Symbol <" << mangledName.str() << "> May exist." - << " Search for it. "); - } - - if (m_UseHashTable) { - bool result = Lib->ExistSymbol(mangledName); - LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: HashTable: Symbol " - << (result ? "Exist" : "Not exist") << "\n"); - return result; - } - - auto ForeachSymbol = - [&library_filename](llvm::iterator_range range, - unsigned IgnoreSymbolFlags, llvm::StringRef mangledName) -> bool { - for (const llvm::object::SymbolRef &S : range) { - uint32_t Flags = llvm::cantFail(S.getFlags()); - // Do not insert in the table symbols flagged to ignore. - if (Flags & IgnoreSymbolFlags) - continue; - - // Note, we are at last resort and loading library based on a weak - // symbol is allowed. Otherwise, the JIT will issue an unresolved - // symbol error. - // - // There are other weak symbol kinds (marked as 'V') to denote - // typeinfo and vtables. It is unclear whether we should load such - // libraries or from which library we should resolve the symbol. - // We seem to not have a way to differentiate it from the symbol API. - - llvm::Expected SymNameErr = S.getName(); - if (!SymNameErr) { - LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Failed to read symbol " - << mangledName.str() << "\n"); - continue; - } - - if (SymNameErr.get().empty()) - continue; - - if (SymNameErr.get() == mangledName) { - LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Symbol " - << mangledName.str() << " found in " - << library_filename << "\n"); - return true; - } - } - return false; - }; - - // If no hash symbol then iterate to detect symbol - // We Iterate only if BloomFilter and/or SymbolHashTable are not supported. - - LLVM_DEBUG(dbgs() << "Dyld::ContainsSymbol: Iterate all for <" - << mangledName.str() << ">"); - - // Symbol may exist. Iterate. - if (ForeachSymbol(BinObjFile->symbols(), IgnoreSymbolFlags, mangledName)) { - LLVM_DEBUG(dbgs() << " -> found.\n"); - return true; - } - - - if (!BinObjFile->isELF()) { - LLVM_DEBUG(dbgs() << " -> not found.\n"); - return false; - } - - // ELF file format has .dynstr section for the dynamic symbol table. - const auto *ElfObj = - llvm::cast(BinObjFile); - - bool result = ForeachSymbol(ElfObj->getDynamicSymbolIterators(), - IgnoreSymbolFlags, mangledName); - LLVM_DEBUG(dbgs() << (result ? " -> found.\n" : " -> not found.\n")); - return result; -#undef DEBUG_TYPE - } - - bool Dyld::ShouldPermanentlyIgnore(StringRef FileName) const { -#define DEBUG_TYPE "Dyld:" - assert(!m_ExecutableFormat.empty() && "Failed to find the object format!"); - - if (!Cpp::DynamicLibraryManager::isSharedLibrary(FileName)) - return true; - - // No need to check linked libraries, as this function is only invoked - // for symbols that cannot be found (neither by dlsym nor in the JIT). - if (m_DynamicLibraryManager.isLibraryLoaded(FileName)) - return true; - - - auto ObjF = llvm::object::ObjectFile::createObjectFile(FileName); - if (!ObjF) { - LLVM_DEBUG(dbgs() << "[DyLD] Failed to read object file " - << FileName << "\n"); - return true; - } - - llvm::object::ObjectFile *file = ObjF.get().getBinary(); - - LLVM_DEBUG(dbgs() << "Current executable format: " << m_ExecutableFormat - << ". Executable format of " << FileName << " : " - << file->getFileFormatName() << "\n"); - - // Ignore libraries with different format than the executing one. - if (m_ExecutableFormat != file->getFileFormatName()) - return true; - - if (llvm::isa(*file)) { - for (auto S : file->sections()) { - llvm::StringRef name = llvm::cantFail(S.getName()); - if (name == ".text") { - // Check if the library has only debug symbols, usually when - // stripped with objcopy --only-keep-debug. This check is done by - // reading the manual of objcopy and inspection of stripped with - // objcopy libraries. - auto SecRef = static_cast(S); - if (SecRef.getType() == llvm::ELF::SHT_NOBITS) - return true; - - return (SecRef.getFlags() & llvm::ELF::SHF_ALLOC) == 0; - } - } - return true; - } - - //FIXME: Handle osx using isStripped after upgrading to llvm9. - - return m_ShouldPermanentlyIgnoreCallback(FileName); -#undef DEBUG_TYPE - } - - void Dyld::dumpDebugInfo() const { -#define DEBUG_TYPE "Dyld:" - LLVM_DEBUG(dbgs() << "---\n"); - size_t x = 0; - for (auto const &item : m_BasePaths.m_Paths) { - LLVM_DEBUG(dbgs() << "Dyld: - m_BasePaths[" << x++ << "]:" - << &item << ": " << item << "\n"); - } - LLVM_DEBUG(dbgs() << "---\n"); - x = 0; - for (auto const &item : m_Libraries.GetLibraries()) { - LLVM_DEBUG(dbgs() << "Dyld: - m_Libraries[" << x++ << "]:" - << &item << ": " << item->m_Path << ", " - << item->m_LibName << "\n"); - } - x = 0; - for (auto const &item : m_SysLibraries.GetLibraries()) { - LLVM_DEBUG(dbgs() << "Dyld: - m_SysLibraries[" << x++ << "]:" - << &item << ": " << item->m_Path << ", " - << item->m_LibName << "\n"); - } -#undef DEBUG_TYPE - } - - std::string Dyld::searchLibrariesForSymbol(StringRef mangledName, - bool searchSystem/* = true*/) { -#define DEBUG_TYPE "Dyld:searchLibrariesForSymbol:" - assert(!llvm::sys::DynamicLibrary::SearchForAddressOfSymbol(mangledName.str()) && - "Library already loaded, please use dlsym!"); - assert(!mangledName.empty()); - - using namespace llvm::sys::path; - using namespace llvm::sys::fs; - - if (m_FirstRun) { - LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol:" << mangledName.str() - << ", searchSystem=" << (searchSystem ? "true" : "false") - << ", FirstRun(user)... scanning\n"); - - LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: Before first ScanForLibraries\n"); - dumpDebugInfo(); - - ScanForLibraries(/* SearchSystemLibraries= */ false); - m_FirstRun = false; - - LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: After first ScanForLibraries\n"); - dumpDebugInfo(); - } - - if (m_QueriedLibraries.size() > 0) { - // Last call we were asked if a library contains a symbol. Usually, the - // caller wants to load this library. Check if was loaded and remove it - // from our lists of not-yet-loaded libs. - - LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol: m_QueriedLibraries:\n"); - size_t x = 0; - for (auto item : m_QueriedLibraries.GetLibraries()) { - LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol - [" << x++ << "]:" - << &item << ": " << item->GetFullName() << "\n"); - } - - for (const LibraryPath* P : m_QueriedLibraries.GetLibraries()) { - const std::string LibName = P->GetFullName(); - if (!m_DynamicLibraryManager.isLibraryLoaded(LibName)) - continue; - - m_Libraries.UnregisterLib(*P); - m_SysLibraries.UnregisterLib(*P); - } - // TODO: m_QueriedLibraries.clear ? - } - - // Iterate over files under this path. We want to get each ".so" files - for (const LibraryPath* P : m_Libraries.GetLibraries()) { - if (ContainsSymbol(P, mangledName, /*ignore*/ - llvm::object::SymbolRef::SF_Undefined)) { - if (!m_QueriedLibraries.HasRegisteredLib(*P)) - m_QueriedLibraries.RegisterLib(*P); - - LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol: Search found match in [user lib]: " - << P->GetFullName() << "!\n"); - - return P->GetFullName(); - } - } - - if (!searchSystem) - return ""; - - LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: SearchSystem!!!\n"); - - // Lookup in non-system libraries failed. Expand the search to the system. - if (m_FirstRunSysLib) { - LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol:" << mangledName.str() - << ", searchSystem=" << (searchSystem ? "true" : "false") - << ", FirstRun(system)... scanning\n"); - - LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: Before first system ScanForLibraries\n"); - dumpDebugInfo(); - - ScanForLibraries(/* SearchSystemLibraries= */ true); - m_FirstRunSysLib = false; - - LLVM_DEBUG(dbgs() << "Dyld::searchLibrariesForSymbol: After first system ScanForLibraries\n"); - dumpDebugInfo(); - } - - for (const LibraryPath* P : m_SysLibraries.GetLibraries()) { - if (ContainsSymbol(P, mangledName, /*ignore*/ - llvm::object::SymbolRef::SF_Undefined | - llvm::object::SymbolRef::SF_Weak)) { - if (!m_QueriedLibraries.HasRegisteredLib(*P)) - m_QueriedLibraries.RegisterLib(*P); - - LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol: Search found match in [system lib]: " - << P->GetFullName() << "!\n"); - - return P->GetFullName(); - } - } - - LLVM_DEBUG(dbgs() << "Dyld::ResolveSymbol: Search found no match!\n"); - - return ""; // Search found no match. -#undef DEBUG_TYPE - } - - DynamicLibraryManager::~DynamicLibraryManager() { - static_assert(sizeof(Dyld) > 0, "Incomplete type"); - delete m_Dyld; - } - - void DynamicLibraryManager::initializeDyld( - std::function shouldPermanentlyIgnore) { - //assert(!m_Dyld && "Already initialized!"); - if (m_Dyld) - delete m_Dyld; - - std::string exeP = GetExecutablePath(); - auto ObjF = - cantFail(llvm::object::ObjectFile::createObjectFile(exeP)); - - m_Dyld = new Dyld(*this, shouldPermanentlyIgnore, - ObjF.getBinary()->getFileFormatName()); - } - - std::string - DynamicLibraryManager::searchLibrariesForSymbol(StringRef mangledName, - bool searchSystem/* = true*/) const { - assert(m_Dyld && "Must call initialize dyld before!"); - return m_Dyld->searchLibrariesForSymbol(mangledName, searchSystem); - } - - std::string DynamicLibraryManager::getSymbolLocation(void *func) { -#if defined(__CYGWIN__) && defined(__GNUC__) - return {}; -#elif defined(_WIN32) - MEMORY_BASIC_INFORMATION mbi; - if (!VirtualQuery (func, &mbi, sizeof (mbi))) - return {}; - - HMODULE hMod = (HMODULE) mbi.AllocationBase; - char moduleName[MAX_PATH]; - - if (!GetModuleFileNameA (hMod, moduleName, sizeof (moduleName))) - return {}; - - return cached_realpath(moduleName); - -#else - // assume we have defined HAVE_DLFCN_H and HAVE_DLADDR - Dl_info info; - if (dladdr((void*)func, &info) == 0) { - // Not in a known shared library, let's give up - return {}; - } else { - std::string result = cached_realpath(info.dli_fname); - if (!result.empty()) - return result; - - // Else absolute path. For all we know that's a binary. - // Some people have dictionaries in binaries, this is how we find their - // path: (see also https://stackoverflow.com/a/1024937/6182509) -# if defined(__APPLE__) - char buf[PATH_MAX] = { 0 }; - uint32_t bufsize = sizeof(buf); - if (_NSGetExecutablePath(buf, &bufsize) >= 0) - return cached_realpath(buf); - return cached_realpath(info.dli_fname); -# elif defined(LLVM_ON_UNIX) - char buf[PATH_MAX] = { 0 }; - // Cross our fingers that /proc/self/exe exists. - if (readlink("/proc/self/exe", buf, sizeof(buf)) > 0) - return cached_realpath(buf); - std::string pipeCmd = std::string("which \"") + info.dli_fname + "\""; - FILE* pipe = popen(pipeCmd.c_str(), "r"); - if (!pipe) - return cached_realpath(info.dli_fname); - while (fgets(buf, sizeof(buf), pipe)) - result += buf; - - pclose(pipe); - return cached_realpath(result); -# else -# error "Unsupported platform." -# endif - return {}; - } -#endif - } - -} // namespace Cpp diff --git a/unittests/CppInterOp/CUDATest.cpp b/unittests/CppInterOp/CUDATest.cpp index f697007cf..522ec08ca 100644 --- a/unittests/CppInterOp/CUDATest.cpp +++ b/unittests/CppInterOp/CUDATest.cpp @@ -1,7 +1,8 @@ #include "Utils.h" +#include "CppInterOp/CppInterOp.h" + #include "clang/Basic/Version.h" -#include "clang/Interpreter/CppInterOp.h" #include "gtest/gtest.h" @@ -76,4 +77,4 @@ TEST(CUDATest, CUDARuntime) { GTEST_SKIP() << "Skipping CUDA tests as CUDA runtime not found"; EXPECT_TRUE(HasCudaRuntime()); -} \ No newline at end of file +} diff --git a/unittests/CppInterOp/DynamicLibraryManagerTest.cpp b/unittests/CppInterOp/DynamicLibraryManagerTest.cpp index 6ac7e5179..4925af329 100644 --- a/unittests/CppInterOp/DynamicLibraryManagerTest.cpp +++ b/unittests/CppInterOp/DynamicLibraryManagerTest.cpp @@ -1,11 +1,12 @@ -#include "gtest/gtest.h" +#include "CppInterOp/CppInterOp.h" #include "clang/Basic/Version.h" -#include "clang/Interpreter/CppInterOp.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" +#include "gtest/gtest.h" + // This function isn't referenced outside its translation unit, but it // can't use the "static" keyword because its address is used for // GetMainExecutable (since some platforms don't support taking the diff --git a/unittests/CppInterOp/EnumReflectionTest.cpp b/unittests/CppInterOp/EnumReflectionTest.cpp index a8fc1a028..f4b3d47de 100644 --- a/unittests/CppInterOp/EnumReflectionTest.cpp +++ b/unittests/CppInterOp/EnumReflectionTest.cpp @@ -1,7 +1,8 @@ #include "Utils.h" +#include "CppInterOp/CppInterOp.h" + #include "clang/AST/ASTContext.h" -#include "clang/Interpreter/CppInterOp.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Sema/Sema.h" @@ -357,4 +358,4 @@ TEST(EnumReflectionTest, GetEnums) { EXPECT_TRUE(std::find(enumNames2.begin(), enumNames2.end(), "Months") != enumNames2.end()); EXPECT_TRUE(std::find(enumNames3.begin(), enumNames3.end(), "Color") != enumNames3.end()); EXPECT_TRUE(enumNames4.empty()); -} \ No newline at end of file +} diff --git a/unittests/CppInterOp/FunctionReflectionTest.cpp b/unittests/CppInterOp/FunctionReflectionTest.cpp index 7e907ae18..b408b2c20 100644 --- a/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/unittests/CppInterOp/FunctionReflectionTest.cpp @@ -1,9 +1,10 @@ #include "Utils.h" +#include "CppInterOp/CppInterOp.h" + #include "clang/AST/ASTContext.h" #include "clang/Basic/Version.h" #include "clang/Frontend/CompilerInstance.h" -#include "clang/Interpreter/CppInterOp.h" #include "clang/Sema/Sema.h" #include @@ -1440,6 +1441,10 @@ TEST(FunctionReflectionTest, JitCallAdvanced) { #endif if (llvm::sys::RunningOnValgrind()) GTEST_SKIP() << "XFAIL due to Valgrind report"; + + Cpp::JitCall JC = Cpp::MakeFunctionCallable(nullptr); + EXPECT_TRUE(JC.getKind() == Cpp::JitCall::kUnknown); + std::vector Decls; std::string code = R"( typedef struct _name { diff --git a/unittests/CppInterOp/InterpreterTest.cpp b/unittests/CppInterOp/InterpreterTest.cpp index 008ea01e7..4c82f0b02 100644 --- a/unittests/CppInterOp/InterpreterTest.cpp +++ b/unittests/CppInterOp/InterpreterTest.cpp @@ -1,7 +1,7 @@ #include "Utils.h" -#include "clang/Interpreter/CppInterOp.h" +#include "CppInterOp/CppInterOp.h" #ifdef CPPINTEROP_USE_CLING #include "cling/Interpreter/Interpreter.h" @@ -221,7 +221,7 @@ TEST(InterpreterTest, DetectSystemCompilerIncludePaths) { EXPECT_FALSE(includes.empty()); } -TEST(InterpreterTest, GetIncludePaths) { +TEST(InterpreterTest, IncludePaths) { std::vector includes; Cpp::GetIncludePaths(includes); EXPECT_FALSE(includes.empty()); @@ -237,6 +237,12 @@ TEST(InterpreterTest, GetIncludePaths) { Cpp::GetIncludePaths(includes, true, true); EXPECT_FALSE(includes.empty()); EXPECT_TRUE(includes.size() >= len); + + len = includes.size(); + Cpp::AddIncludePath("/non/existent/"); + Cpp::GetIncludePaths(includes); + EXPECT_NE(std::find(includes.begin(), includes.end(), "/non/existent/"), + std::end(includes)); } TEST(InterpreterTest, CodeCompletion) { diff --git a/unittests/CppInterOp/JitTest.cpp b/unittests/CppInterOp/JitTest.cpp index b9c2a75c2..a1b6909b7 100644 --- a/unittests/CppInterOp/JitTest.cpp +++ b/unittests/CppInterOp/JitTest.cpp @@ -1,6 +1,6 @@ #include "Utils.h" -#include "clang/Interpreter/CppInterOp.h" +#include "CppInterOp/CppInterOp.h" #include "gtest/gtest.h" diff --git a/unittests/CppInterOp/ScopeReflectionTest.cpp b/unittests/CppInterOp/ScopeReflectionTest.cpp index 0b3908085..5dd57706d 100644 --- a/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -1,6 +1,6 @@ #include "Utils.h" -#include "clang/Interpreter/CppInterOp.h" +#include "CppInterOp/CppInterOp.h" #include "clang-c/CXCppInterOp.h" #include "clang/AST/ASTContext.h" diff --git a/unittests/CppInterOp/TypeReflectionTest.cpp b/unittests/CppInterOp/TypeReflectionTest.cpp index a3e482596..f011899c8 100644 --- a/unittests/CppInterOp/TypeReflectionTest.cpp +++ b/unittests/CppInterOp/TypeReflectionTest.cpp @@ -1,8 +1,9 @@ #include "Utils.h" +#include "CppInterOp/CppInterOp.h" + #include "clang/AST/ASTContext.h" #include "clang/Basic/Version.h" -#include "clang/Interpreter/CppInterOp.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Sema/Sema.h" diff --git a/unittests/CppInterOp/Utils.cpp b/unittests/CppInterOp/Utils.cpp index 4daf18c35..e048b0df5 100644 --- a/unittests/CppInterOp/Utils.cpp +++ b/unittests/CppInterOp/Utils.cpp @@ -1,14 +1,11 @@ #include "Utils.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/Path.h" +#include "CppInterOp/CppInterOp.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/Basic/Version.h" #include "clang/Frontend/CompilerInstance.h" -#include "clang/Interpreter/CppInterOp.h" #include "clang/Sema/Lookup.h" #include "clang/Sema/Sema.h" @@ -71,4 +68,4 @@ void dispose_string(CXString string) { CXScope make_scope(const clang::Decl* D, const CXInterpreter I) { return {CXCursor_UnexposedDecl, 0, {D, nullptr, I}}; -} \ No newline at end of file +} diff --git a/unittests/CppInterOp/Utils.h b/unittests/CppInterOp/Utils.h index fb79ee804..2b7b12590 100644 --- a/unittests/CppInterOp/Utils.h +++ b/unittests/CppInterOp/Utils.h @@ -1,14 +1,16 @@ #ifndef CPPINTEROP_UNITTESTS_LIBCPPINTEROP_UTILS_H #define CPPINTEROP_UNITTESTS_LIBCPPINTEROP_UTILS_H -#include "../../lib/Interpreter/Compatibility.h" +#include "../../lib/CppInterOp/Compatibility.h" + +#include "clang-c/CXCppInterOp.h" +#include "clang-c/CXString.h" #include "llvm/Support/Valgrind.h" + #include #include #include -#include "clang-c/CXCppInterOp.h" -#include "clang-c/CXString.h" using namespace clang; using namespace llvm; diff --git a/unittests/CppInterOp/VariableReflectionTest.cpp b/unittests/CppInterOp/VariableReflectionTest.cpp index 26dad4f25..4ec7b1d64 100644 --- a/unittests/CppInterOp/VariableReflectionTest.cpp +++ b/unittests/CppInterOp/VariableReflectionTest.cpp @@ -1,8 +1,9 @@ #include "Utils.h" +#include "CppInterOp/CppInterOp.h" + #include "clang/AST/ASTContext.h" #include "clang/Basic/Version.h" -#include "clang/Interpreter/CppInterOp.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Sema/Sema.h"