diff --git a/core/metacling/src/TCling.cxx b/core/metacling/src/TCling.cxx index ab59fb66a567d..543f9956abb16 100644 --- a/core/metacling/src/TCling.cxx +++ b/core/metacling/src/TCling.cxx @@ -115,7 +115,7 @@ clang/LLVM technology. #include "cling/Utils/SourceNormalization.h" #include "cling/Interpreter/Exception.h" -#include "clang/Interpreter/CppInterOp.h" +#include #include "llvm/IR/GlobalValue.h" #include "llvm/IR/Module.h" diff --git a/core/metacling/src/TClingDataMemberInfo.cxx b/core/metacling/src/TClingDataMemberInfo.cxx index f4a175002804b..9c8db5ef2c897 100644 --- a/core/metacling/src/TClingDataMemberInfo.cxx +++ b/core/metacling/src/TClingDataMemberInfo.cxx @@ -43,7 +43,7 @@ from the Clang C++ compiler, not CINT. #include "clang/AST/RecordLayout.h" #include "clang/AST/Type.h" -#include "clang/Interpreter/CppInterOp.h" +#include #include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" diff --git a/core/metacling/src/TClingTypeInfo.cxx b/core/metacling/src/TClingTypeInfo.cxx index 1571d016a193c..6f89d96191b16 100644 --- a/core/metacling/src/TClingTypeInfo.cxx +++ b/core/metacling/src/TClingTypeInfo.cxx @@ -37,7 +37,7 @@ but the type metadata comes from the Clang C++ compiler, not CINT. #include "clang/AST/PrettyPrinter.h" #include "clang/Frontend/CompilerInstance.h" -#include "clang/Interpreter/CppInterOp.h" +#include #include #include diff --git a/interpreter/CppInterOp/.readthedocs.yaml b/interpreter/CppInterOp/.readthedocs.yaml index f513721407adf..8d42b0c76009c 100644 --- a/interpreter/CppInterOp/.readthedocs.yaml +++ b/interpreter/CppInterOp/.readthedocs.yaml @@ -5,12 +5,12 @@ sphinx: builder: html build: - os: "ubuntu-22.04" + os: "ubuntu-24.04" tools: python: "3.11" apt_packages: - - clang-13 + - clang-16 - cmake - - libclang-13-dev - - llvm-13-dev - - llvm-13-tools \ No newline at end of file + - libclang-16-dev + - llvm-16-dev + - llvm-16-tools diff --git a/interpreter/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/CMakeLists.txt index 6bf78a214186c..8b2df5b4fc49f 100644 --- a/interpreter/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/CMakeLists.txt @@ -67,13 +67,25 @@ if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR ) include(GNUInstallDirs) ## Define supported version of clang and llvm - set(CLANG_MIN_SUPPORTED 13.0) +if (CPPINTEROP_USE_CLING) + set(CLANG_MIN_SUPPORTED 18.0) +else() + set(CLANG_MIN_SUPPORTED 16.0) +endif(CPPINTEROP_USE_CLING) set(CLANG_MAX_SUPPORTED "20.1.x") set(CLANG_VERSION_UPPER_BOUND 21.0.0) - set(LLD_MIN_SUPPORTED 13.0) +if (CPPINTEROP_USE_CLING) + set(LLD_MIN_SUPPORTED 18.0) +else() + set(LLD_MIN_SUPPORTED 16.0) +endif(CPPINTEROP_USE_CLING) set(LLD_MAX_SUPPORTED "20.1.x") set(LLD_VERSION_UPPER_BOUND 21.0.0) - set(LLVM_MIN_SUPPORTED 13.0) +if (CPPINTEROP_USE_CLING) + set(LLVM_MIN_SUPPORTED 18.0) +else() + set(LLVM_MIN_SUPPORTED 16.0) +endif(CPPINTEROP_USE_CLING) set(LLVM_MAX_SUPPORTED "20.1.x") set(LLVM_VERSION_UPPER_BOUND 21.0.0) @@ -230,21 +242,12 @@ endif() message(STATUS "Found supported version: Clang ${CLANG_PACKAGE_VERSION}") message(STATUS "Using ClangConfig.cmake in: ${Clang_DIR}") - ## Clang 13 require c++14 or later, Clang 16 require c++17 or later. - if (CLANG_VERSION_MAJOR GREATER_EQUAL 16) - if (NOT CMAKE_CXX_STANDARD) - set (CMAKE_CXX_STANDARD 17) - endif() - if (CMAKE_CXX_STANDARD LESS 17) - message(fatal "LLVM/CppInterOp requires c++17 or later") - endif() - elseif (CLANG_VERSION_MAJOR GREATER_EQUAL 13) - if (NOT CMAKE_CXX_STANDARD) - set (CMAKE_CXX_STANDARD 14) - endif() - if (CMAKE_CXX_STANDARD LESS 14) - message(fatal "LLVM/CppInterOp requires c++14 or later") - endif() + ## Clang 16 require c++17 or later. + if (NOT CMAKE_CXX_STANDARD) + set (CMAKE_CXX_STANDARD 17) + endif() + if (CMAKE_CXX_STANDARD LESS 17) + message(fatal "LLVM/CppInterOp requires c++17 or later") endif() ## Find supported Cling diff --git a/interpreter/CppInterOp/Emscripten-build-instructions.md b/interpreter/CppInterOp/Emscripten-build-instructions.md index 6172cc4f94c1f..3bc3b444d2b3e 100644 --- a/interpreter/CppInterOp/Emscripten-build-instructions.md +++ b/interpreter/CppInterOp/Emscripten-build-instructions.md @@ -291,7 +291,8 @@ emmake make -j $(nproc --all) install ## Xeus-cpp-lite Wasm Build Instructions A project which makes use of the wasm build of CppInterOp is xeus-cpp. xeus-cpp is a C++ Jupyter kernel. Assuming you are in -the CppInterOp build folder, you can build the wasm version of xeus-cpp by executing +the CppInterOp build folder, you can build the wasm version of xeus-cpp by executing (replace $LLVM_VERSION with the version +of llvm you are building against) ```bash cd ../.. @@ -305,6 +306,7 @@ emcmake cmake \ -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DXEUS_CPP_EMSCRIPTEN_WASM_BUILD=ON \ -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON \ + -DXEUS_CPP_RESOURCE_DIR=$LLVM_BUILD_DIR/lib/clang/$LLVM_VERSION \ -DSYSROOT_PATH=$SYSROOT_PATH \ .. emmake make -j $(nproc --all) install @@ -314,10 +316,15 @@ To build Jupyter Lite website with this kernel locally that you can use for test ```bash cd ../.. -micromamba create -n xeus-lite-host jupyterlite-core -c conda-forge +micromamba create -n xeus-lite-host jupyterlite-core=0.6 jupyterlite-xeus jupyter_server jupyterlab notebook python-libarchive-c -c conda-forge micromamba activate xeus-lite-host -python -m pip install jupyterlite-xeus jupyter_server -jupyter lite build --XeusAddon.prefix=$PREFIX --contents xeus-cpp/notebooks/xeus-cpp-lite-demo.ipynb +jupyter lite build --XeusAddon.prefix=$PREFIX \ + --contents xeus-cpp/notebooks/xeus-cpp-lite-demo.ipynb \ + --contents xeus-cpp/notebooks/smallpt.ipynb \ + --contents xeus-cpp/notebooks/images/marie.png \ + --contents xeus-cpp/notebooks/audio/audio.wav \ + --XeusAddon.mounts="$PREFIX/share/xeus-cpp/tagfiles:/share/xeus-cpp/tagfiles" \ + --XeusAddon.mounts="$PREFIX/etc/xeus-cpp/tags.d:/etc/xeus-cpp/tags.d" ``` Once the Jupyter Lite site has built you can test the website locally by executing diff --git a/interpreter/CppInterOp/README.md b/interpreter/CppInterOp/README.md index 2cfe882853891..534fc191e9175 100644 --- a/interpreter/CppInterOp/README.md +++ b/interpreter/CppInterOp/README.md @@ -1,44 +1,27 @@ # CppInterOp +
-[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/Ubuntu.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/Ubuntu.yml) - -[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/Ubuntu-arm.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/Ubuntu-arm.yml) - -[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/MacOS.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/MacOS.yml) - -[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/MacOS-arm.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/MacOS-arm.yml) - -[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/Windows.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/Windows.yml) - -[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/Windows-arm.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/Windows-arm.yml) - +[![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/main.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/main.yml) [![Build Status](https://github.com/compiler-research/CppInterOp/actions/workflows/emscripten.yml/badge.svg)](https://github.com/compiler-research/CppInterOp/actions/workflows/emscripten.yml) - [![codecov](https://codecov.io/gh/compiler-research/CppInterOp/branch/main/graph/badge.svg)](https://codecov.io/gh/compiler-research/CppInterOp) - [![Conda-Forge](https://img.shields.io/conda/vn/conda-forge/cppinterop)](https://github.com/conda-forge/cppinterop-feedstock) - [![Anaconda-Server Badge](https://anaconda.org/conda-forge/cppinterop/badges/license.svg)](https://github.com/conda-forge/cppinterop-feedstock) - [![Conda Platforms](https://img.shields.io/conda/pn/conda-forge/cppinterop.svg)](https://anaconda.org/conda-forge/cppinterop) - [![Anaconda-Server Badge](https://anaconda.org/conda-forge/cppinterop/badges/downloads.svg)](https://github.com/conda-forge/cppinterop-feedstock) +
+ CppInterOp exposes API from [Clang](http://clang.llvm.org/) and [LLVM](https://llvm.org) in a backward compatible way. The API support downstream tools that utilize interactive C++ by using the compiler as a service. That is, embed Clang and LLVM as a libraries in their codebases. The API are designed to be minimalistic and aid non-trivial tasks such as language interoperability on the fly. In such scenarios CppInterOp can be used to provide the necessary introspection information to the other side helping the language cross talk. -[Installation](#build-instructions) +
-[Documentation](https://cppinterop.readthedocs.io/en/latest/index.html) +[Installation](#build-instructions) | [Documentation](https://cppinterop.readthedocs.io/en/latest/index.html) | [CppInterOp API Documentation](https://cppinterop.readthedocs.io/en/latest/build/html/index.html) | [![lite-badge](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://compiler-research.github.io/CppInterOp/lab/index.html) -[CppInterOp API Documentation](https://cppinterop.readthedocs.io/en/latest/build/html/index.html) - -Try Jupyter Lite CppInterOp demo by clicking below - -[![lite-badge](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://compiler-research.github.io/CppInterOp/lab/index.html) +
## CppInterOp Introduction @@ -154,9 +137,6 @@ cmake -DLLVM_ENABLE_PROJECTS=clang \ -DCLANG_ENABLE_ARCMT=OFF \ -DCLANG_ENABLE_FORMAT=OFF \ -DCLANG_ENABLE_BOOTSTRAP=OFF \ - -DLLVM_ENABLE_ZSTD=OFF \ - -DLLVM_ENABLE_TERMINFO=OFF \ - -DLLVM_ENABLE_LIBXML2=OFF \ ../llvm cmake --build . --target clang clang-repl --parallel $(nproc --all) ``` @@ -205,9 +185,10 @@ Use the following build instructions to build on Linux and MacOS ```bash git clone https://github.com/root-project/cling.git cd ./cling/ -git checkout tags/v1.0 +git checkout tags/v1.2 +git apply -v ../CppInterOp/patches/llvm/cling1.2-LookupHelper.patch cd .. -git clone --depth=1 -b cling-llvm13 https://github.com/root-project/llvm-project.git +git clone --depth=1 -b cling-llvm18 https://github.com/root-project/llvm-project.git mkdir llvm-project/build cd llvm-project/build cmake -DLLVM_ENABLE_PROJECTS=clang \ @@ -220,13 +201,9 @@ cmake -DLLVM_ENABLE_PROJECTS=clang \ -DCLANG_ENABLE_ARCMT=OFF \ -DCLANG_ENABLE_FORMAT=OFF \ -DCLANG_ENABLE_BOOTSTRAP=OFF \ - -DLLVM_ENABLE_ZSTD=OFF \ - -DLLVM_ENABLE_TERMINFO=OFF \ - -DLLVM_ENABLE_LIBXML2=OFF \ ../llvm cmake --build . --target clang --parallel $(nproc --all) cmake --build . --target cling --parallel $(nproc --all) -cmake --build . --target gtest_main --parallel $(nproc --all) ``` Use the following build instructions to build on Windows @@ -234,10 +211,11 @@ Use the following build instructions to build on Windows ```powershell git clone https://github.com/root-project/cling.git cd .\cling\ -git checkout tags/v1.0 +git checkout tags/v1.2 +git apply -v ..\CppInterOp\patches\llvm\cling1.2-LookupHelper.patch cd .. -git clone --depth=1 -b cling-llvm13 https://github.com/root-project/llvm-project.git -$env:ncpus = %NUMBER_OF_PROCESSORS% +git clone --depth=1 -b cling-llvm18 https://github.com/root-project/llvm-project.git +$env:ncpus = $([Environment]::ProcessorCount) $env:PWD_DIR= $PWD.Path $env:CLING_DIR="$env:PWD_DIR\cling" mkdir llvm-project\build @@ -255,7 +233,6 @@ cmake -DLLVM_ENABLE_PROJECTS=clang ` ../llvm cmake --build . --target clang --parallel $env:ncpus cmake --build . --target cling --parallel $env:ncpus -cmake --build . --target gtest_main --parallel $env:ncpus ``` Note the 'llvm-project' directory location. On linux and MacOS you execute the following @@ -281,9 +258,22 @@ Regardless of whether you are building CppInterOP with Cling or Clang-REPL you w ```bash export CB_PYTHON_DIR="$PWD/cppyy-backend/python" export CPPINTEROP_DIR="$CB_PYTHON_DIR/cppyy_backend" +``` + +If building CppInterOp against clang-repl you will need to define the following + +```bash export CPLUS_INCLUDE_PATH="${CPLUS_INCLUDE_PATH}:${LLVM_DIR}/llvm/include:${LLVM_DIR}/clang/include:${LLVM_DIR}/build/include:${LLVM_DIR}/build/tools/clang/include" ``` +and if building against cling you will need to define the following + +```bash +export CLING_DIR="$(pwd)/cling" +export CLING_BUILD_DIR="$(pwd)/cling/build" +export CPLUS_INCLUDE_PATH="${CLING_DIR}/tools/cling/include:${CLING_BUILD_DIR}/include:${LLVM_DIR}/llvm/include:${LLVM_DIR}/clang/include:${LLVM_BUILD_DIR}/include:${LLVM_BUILD_DIR}/tools/clang/include:$PWD/include" +``` + If on MacOS you will also need the following environment variable defined ```bash @@ -295,9 +285,22 @@ On Windows you define as follows (assumes you have defined $env:PWD_DIR= $PWD.Pa ```powershell $env:CB_PYTHON_DIR="$env:PWD_DIR\cppyy-backend\python" $env:CPPINTEROP_DIR="$env:CB_PYTHON_DIR\cppyy_backend" +``` + +If building against clang-repl you will have the following defined + +```powershell $env:CPLUS_INCLUDE_PATH="$env:CPLUS_INCLUDE_PATH;$env:LLVM_DIR\llvm\include;$env:LLVM_DIR\clang\include;$env:LLVM_DIR\build\include;$env:LLVM_DIR\build\tools\clang\include" ``` +and if building against cling + +```powershell +$env:CLING_DIR="$env:PWD_DIR\cling" +$env:CLING_BUILD_DIR="$env:PWD_DIR\cling\build" +$env:CPLUS_INCLUDE_PATH="$env:CLING_DIR\tools\cling\include;$env:CLING_BUILD_DIR\include;$env:LLVM_DIR\llvm\include;$env:LLVM_DIR\clang\include;$env:LLVM_BUILD_DIR\include;$env:LLVM_BUILD_DIR\tools\clang\include;$env:PWD_DIR\include;" +``` + #### Build CppInterOp Now CppInterOp can be installed. On Linux and MacOS execute diff --git a/interpreter/CppInterOp/VERSION b/interpreter/CppInterOp/VERSION index bd8bf882d0618..9dd863f5f629b 100644 --- a/interpreter/CppInterOp/VERSION +++ b/interpreter/CppInterOp/VERSION @@ -1 +1 @@ -1.7.0 +1.8.0;dev diff --git a/interpreter/CppInterOp/cppinterop-version.tag b/interpreter/CppInterOp/cppinterop-version.tag index a20e2d82d2706..9e113fd24e0f8 100644 --- a/interpreter/CppInterOp/cppinterop-version.tag +++ b/interpreter/CppInterOp/cppinterop-version.tag @@ -1 +1 @@ -v1.7.0 +130dd30548b59d8f618339158ab917c083184968 diff --git a/interpreter/CppInterOp/docs/DebuggingCppInterOp.rst b/interpreter/CppInterOp/docs/DebuggingCppInterOp.rst new file mode 100644 index 0000000000000..067b7812844c0 --- /dev/null +++ b/interpreter/CppInterOp/docs/DebuggingCppInterOp.rst @@ -0,0 +1,176 @@ +Debugging in JIT Compiled Code +------------------------------ + +C++ Language Interoperability Layer - Debugging Guide +====================================================== + +Overview +======== + +This guide provides comprehensive instructions for debugging CppInterOp applications using LLDB. + +Prerequisites +============= + +Before proceeding with debugging, ensure you have the following tools installed: + +- **LLDB**: The LLVM debugger +- **LLVM/Clang**: The C++ compiler and toolchain +- **CppInterOp**: The C++ language interoperability library + +The debugging tools should be available in your LLVM toolchain. On most systems, these are installed alongside your LLVM/Clang installation. + +Setting Up Debug Environment +============================ + +To effectively debug CppInterOp applications, you need to compile your code with debugging symbols and disable optimizations. This ensures that the debugger can accurately map between source code and machine instructions. + +**Compilation Flags** + +When compiling your CppInterOp application, use the following essential flags: + +.. code-block:: bash + + $CXX -I$CPPINTEROP_DIR/include -g -O0 -lclangCppInterOp -Wl,-rpath,$CPPINTEROP_DIR/build/lib + +**Flag Explanation:** + +- ``-g``: Includes debugging information in the executable +- ``-O0``: Disables compiler optimizations for clearer debugging +- ``-I$CPPINTEROP_DIR/include``: Includes CppInterOp headers +- ``-lclangCppInterOp``: Links against the CppInterOp library +- ``-Wl,-rpath,$CPPINTEROP_DIR/build/lib``: Sets runtime library path + +Creating a Debug-Ready Test Program +=================================== + +Here's a comprehensive example that demonstrates common CppInterOp usage patterns suitable for debugging: + +.. code-block:: cpp + + #include "clang/Interpreter/CppInterOp.h" + #include + + void run_code(std::string code) { + Cpp::Declare(code.c_str()); + } + + int main(int argc, char *argv[]) { + Cpp::CreateInterpreter({"-gdwarf-4", "-O0"}); + std::vector Decls; + std::string code = R"( + #include + void f1() { + std::cout << "in f1 function" << std::endl; + } + std::cout << "In codeblock 1" << std::endl; + int a = 100; + int b = 1000; + )"; + run_code(code); + code = R"( + f1(); + )"; + run_code(code); + return 0; + } + + +**Program Structure Explanation:** + +This example demonstrates key debugging scenarios: + +1. **Interpreter Initialization**: The ``Cpp::CreateInterpreter`` call with debug flags +2. **Code Declaration**: Dynamic C++ code execution through ``Cpp::Declare`` +3. **Mixed Execution**: Combination of compiled and interpreted code paths +4. **Variable Scoping**: Local variables in both compiled and interpreted contexts + +Debugging Strategies +==================== + +**Debugging Compiled Code** + +For debugging the main executable and compiled portions of your CppInterOp application: + +.. code-block:: bash + + lldb /path/to/executable + (lldb) settings set plugin.jit-loader.gdb.enable on + (lldb) breakpoint set --name f1 + (lldb) r + 1 location added to breakpoint 1 + In codeblock 1 + Process 49132 stopped + * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 + frame #0: 0x000000010217c008 JIT(0x10215c218) f1() at input_line_1:4:13 + +**Note** + +1. Ensure the JIT loader is enabled to allow LLDB to debug dynamically generated code. +2. Use ``settings set plugin.jit-loader.gdb.enable on`` to enable JIT debugging. +3. Set breakpoints in both compiled and interpreted code using ``breakpoint set --name function_name``. + + +**Some Caveats** + +1. For each block of code, there is a file named ``input_line_`` that contains the code block. This file is in-memory and thus cannot be directly accessed. +2. However, generating actual input_line_ files on disk will let LLDB pick them up and render the source content correctly during debugging. This can be achieved by modifying run_code as follows: + +.. code-block:: cpp + + void run_code(std::string code) { + static size_t i = 0; + i++; + std::string filename = "input_line_" + std::to_string(i); + std::ofstream file(filename); + file << code; + file.close(); + Cpp::Declare(code.c_str()); + } + +.. note:: + + You'll need to manually delete these files later to avoid cluttering the filesystem. + +3. If a function is called from different cell, then it may take multiple step-ins to reach the function definition due to the way CppInterOp handles code blocks. + +Advanced Debugging Techniques +============================= + +**Using LLDB with VS Code** + +For IDE-based debugging: + +1. Install the LLDB extension in VS Code +2. Create a ``launch.json`` configuration: + +.. code-block:: json + + { + "version": "0.2.0", + "configurations": [ + { + "type": "lldb-dap", + "request": "launch", + "name": "Debug", + "program": "/path/to/executable", + "sourcePath" : ["${workspaceFolder}"], + "cwd": "${workspaceFolder}", + "initCommands": [ + "settings set plugin.jit-loader.gdb.enable on", // This is crucial + ] + }, + ] + } + + + +Further Reading +=============== + +- **LLDB Documentation**: `LLDB Debugger `_ +- **CppInterOp Source**: `CppInterOp Repository `_ +- **Clang Documentation**: `Clang Compiler `_ +- **LLVM Debugging Guide**: `LLVM Debug Info `_ + + diff --git a/interpreter/CppInterOp/docs/DevelopersDocumentation.rst b/interpreter/CppInterOp/docs/DevelopersDocumentation.rst index 925acc1cbcbcf..2e997b5632315 100644 --- a/interpreter/CppInterOp/docs/DevelopersDocumentation.rst +++ b/interpreter/CppInterOp/docs/DevelopersDocumentation.rst @@ -78,9 +78,6 @@ following command -DCLANG_ENABLE_ARCMT=OFF \ -DCLANG_ENABLE_FORMAT=OFF \ -DCLANG_ENABLE_BOOTSTRAP=OFF \ - -DLLVM_ENABLE_ZSTD=OFF \ - -DLLVM_ENABLE_TERMINFO=OFF \ - -DLLVM_ENABLE_LIBXML2=OFF \ ../llvm cmake --build . --target clang clang-repl --parallel $(nproc --all) @@ -132,9 +129,10 @@ build instructions to build on Linux and MacOS git clone https://github.com/root-project/cling.git cd ./cling/ - git checkout tags/v1.0 + git checkout tags/v1.2 + git apply -v ../CppInterOp/patches/llvm/cling1.2-LookupHelper.patch cd .. - git clone --depth=1 -b cling-llvm13 https://github.com/root-project/llvm-project.git + git clone --depth=1 -b cling-llvm18 https://github.com/root-project/llvm-project.git mkdir llvm-project/build cd llvm-project/build cmake -DLLVM_ENABLE_PROJECTS=clang \ @@ -147,13 +145,9 @@ build instructions to build on Linux and MacOS -DCLANG_ENABLE_ARCMT=OFF \ -DCLANG_ENABLE_FORMAT=OFF \ -DCLANG_ENABLE_BOOTSTRAP=OFF \ - -DLLVM_ENABLE_ZSTD=OFF \ - -DLLVM_ENABLE_TERMINFO=OFF \ - -DLLVM_ENABLE_LIBXML2=OFF \ ../llvm cmake --build . --target clang --parallel $(nproc --all) cmake --build . --target cling --parallel $(nproc --all) - cmake --build . --target gtest_main --parallel $(nproc --all) Use the following build instructions to build on Windows @@ -161,10 +155,11 @@ Use the following build instructions to build on Windows git clone https://github.com/root-project/cling.git cd .\cling\ - git checkout tags/v1.0 + git checkout tags/v1.2 + git apply -v ..\CppInterOp\patches\llvm\cling1.2-LookupHelper.patch cd .. - git clone --depth=1 -b cling-llvm13 https://github.com/root-project/llvm-project.git - $env:ncpus = %NUMBER_OF_PROCESSORS% + git clone --depth=1 -b cling-llvm18 https://github.com/root-project/llvm-project.git + $env:ncpus = $([Environment]::ProcessorCount) $env:PWD_DIR= $PWD.Path $env:CLING_DIR="$env:PWD_DIR\cling" mkdir llvm-project\build @@ -182,7 +177,6 @@ Use the following build instructions to build on Windows ../llvm cmake --build . --target clang --parallel $env:ncpus cmake --build . --target cling --parallel $env:ncpus - cmake --build . --target gtest_main --parallel $env:ncpus Note the 'llvm-project' directory location. On linux and MacOS you execute the following @@ -215,8 +209,21 @@ define as follows export CB_PYTHON_DIR="$PWD/cppyy-backend/python" export CPPINTEROP_DIR="$CB_PYTHON_DIR/cppyy_backend" + +If building CppInterOp against clang-repl you will need to define the following + +.. code:: bash + export CPLUS_INCLUDE_PATH="${CPLUS_INCLUDE_PATH}:${LLVM_DIR}/llvm/include:${LLVM_DIR}/clang/include:${LLVM_DIR}/build/include:${LLVM_DIR}/build/tools/clang/include" +and if building against cling you will need to define the following + +.. code:: bash + + export CLING_DIR="$(pwd)/cling" + export CLING_BUILD_DIR="$(pwd)/cling/build" + export CPLUS_INCLUDE_PATH="${CLING_DIR}/tools/cling/include:${CLING_BUILD_DIR}/include:${LLVM_DIR}/llvm/include:${LLVM_DIR}/clang/include:${LLVM_BUILD_DIR}/include:${LLVM_BUILD_DIR}/tools/clang/include:$PWD/include" + If on MacOS you will also need the following environment variable defined .. code:: bash @@ -230,8 +237,21 @@ $PWD.Path ) $env:CB_PYTHON_DIR="$env:PWD_DIR\cppyy-backend\python" $env:CPPINTEROP_DIR="$env:CB_PYTHON_DIR\cppyy_backend" + +If building against clang-repl you will have the following defined + +.. code:: powershell + $env:CPLUS_INCLUDE_PATH="$env:CPLUS_INCLUDE_PATH;$env:LLVM_DIR\llvm\include;$env:LLVM_DIR\clang\include;$env:LLVM_DIR\build\include;$env:LLVM_DIR\build\tools\clang\include" +and if building against cling + +.. code:: powershell + + $env:CLING_DIR="$env:PWD_DIR\cling" + $env:CLING_BUILD_DIR="$env:PWD_DIR\cling\build" + $env:CPLUS_INCLUDE_PATH="$env:CLING_DIR\tools\cling\include;$env:CLING_BUILD_DIR\include;$env:LLVM_DIR\llvm\include;$env:LLVM_DIR\clang\include;$env:LLVM_BUILD_DIR\include;$env:LLVM_BUILD_DIR\tools\clang\include;$env:PWD_DIR\include;" + ****************** Build CppInterOp ****************** diff --git a/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst b/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst index 8bd68768dd241..94403e8366e4d 100644 --- a/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst +++ b/interpreter/CppInterOp/docs/Emscripten-build-instructions.rst @@ -315,6 +315,7 @@ Assuming it passes all test you can install by executing the following. A project which makes use of the wasm build of CppInterOp is xeus-cpp. xeus-cpp is a C++ Jupyter kernel. Assuming you are in the CppInterOp build folder, you can build the wasm version of xeus-cpp by executing +(replace $LLVM_VERSION with the version of llvm you are building against) .. code:: bash @@ -329,6 +330,7 @@ build folder, you can build the wasm version of xeus-cpp by executing -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DXEUS_CPP_EMSCRIPTEN_WASM_BUILD=ON \ -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ON \ + -DXEUS_CPP_RESOURCE_DIR=$LLVM_BUILD_DIR/lib/clang/$LLVM_VERSION \ -DSYSROOT_PATH=$SYSROOT_PATH \ .. emmake make -j $(nproc --all) install @@ -339,10 +341,15 @@ for testing execute the following .. code:: bash cd ../.. - micromamba create -n xeus-lite-host jupyterlite-core -c conda-forge + micromamba create -n xeus-lite-host jupyterlite-core=0.6 jupyterlite-xeus jupyter_server jupyterlab notebook python-libarchive-c -c conda-forge micromamba activate xeus-lite-host - python -m pip install jupyterlite-xeus jupyter_server - jupyter lite build --XeusAddon.prefix=$PREFIX --contents xeus-cpp/notebooks/xeus-cpp-lite-demo.ipynb + jupyter lite build --XeusAddon.prefix=$PREFIX \ + --contents xeus-cpp/notebooks/xeus-cpp-lite-demo.ipynb \ + --contents xeus-cpp/notebooks/smallpt.ipynb \ + --contents xeus-cpp/notebooks/images/marie.png \ + --contents xeus-cpp/notebooks/audio/audio.wav \ + --XeusAddon.mounts="$PREFIX/share/xeus-cpp/tagfiles:/share/xeus-cpp/tagfiles" \ + --XeusAddon.mounts="$PREFIX/etc/xeus-cpp/tags.d:/etc/xeus-cpp/tags.d" Once the Jupyter Lite site has built you can test the website locally by executing diff --git a/interpreter/CppInterOp/docs/InstallationAndUsage.rst b/interpreter/CppInterOp/docs/InstallationAndUsage.rst index d4f172cda6422..8063d9c6180db 100644 --- a/interpreter/CppInterOp/docs/InstallationAndUsage.rst +++ b/interpreter/CppInterOp/docs/InstallationAndUsage.rst @@ -78,9 +78,6 @@ following command -DCLANG_ENABLE_ARCMT=OFF \ -DCLANG_ENABLE_FORMAT=OFF \ -DCLANG_ENABLE_BOOTSTRAP=OFF \ - -DLLVM_ENABLE_ZSTD=OFF \ - -DLLVM_ENABLE_TERMINFO=OFF \ - -DLLVM_ENABLE_LIBXML2=OFF \ ../llvm cmake --build . --target clang clang-repl --parallel $(nproc --all) @@ -132,9 +129,10 @@ build instructions to build on Linux and MacOS git clone https://github.com/root-project/cling.git cd ./cling/ - git checkout tags/v1.0 + git checkout tags/v1.2 + git apply -v ../CppInterOp/patches/llvm/cling1.2-LookupHelper.patch cd .. - git clone --depth=1 -b cling-llvm13 https://github.com/root-project/llvm-project.git + git clone --depth=1 -b cling-llvm18 https://github.com/root-project/llvm-project.git mkdir llvm-project/build cd llvm-project/build cmake -DLLVM_ENABLE_PROJECTS=clang \ @@ -147,13 +145,9 @@ build instructions to build on Linux and MacOS -DCLANG_ENABLE_ARCMT=OFF \ -DCLANG_ENABLE_FORMAT=OFF \ -DCLANG_ENABLE_BOOTSTRAP=OFF \ - -DLLVM_ENABLE_ZSTD=OFF \ - -DLLVM_ENABLE_TERMINFO=OFF \ - -DLLVM_ENABLE_LIBXML2=OFF \ ../llvm cmake --build . --target clang --parallel $(nproc --all) cmake --build . --target cling --parallel $(nproc --all) - cmake --build . --target gtest_main --parallel $(nproc --all) Use the following build instructions to build on Windows @@ -161,10 +155,11 @@ Use the following build instructions to build on Windows git clone https://github.com/root-project/cling.git cd .\cling\ - git checkout tags/v1.0 + git checkout tags/v1.2 + git apply -v ..\CppInterOp\patches\llvm\cling1.2-LookupHelper.patch cd .. - git clone --depth=1 -b cling-llvm13 https://github.com/root-project/llvm-project.git - $env:ncpus = %NUMBER_OF_PROCESSORS% + git clone --depth=1 -b cling-llvm18 https://github.com/root-project/llvm-project.git + $env:ncpus = $([Environment]::ProcessorCount) $env:PWD_DIR= $PWD.Path $env:CLING_DIR="$env:PWD_DIR\cling" mkdir llvm-project\build @@ -182,7 +177,6 @@ Use the following build instructions to build on Windows ../llvm cmake --build . --target clang --parallel $env:ncpus cmake --build . --target cling --parallel $env:ncpus - cmake --build . --target gtest_main --parallel $env:ncpus Note the 'llvm-project' directory location. On linux and MacOS you execute the following @@ -215,8 +209,21 @@ define as follows export CB_PYTHON_DIR="$PWD/cppyy-backend/python" export CPPINTEROP_DIR="$CB_PYTHON_DIR/cppyy_backend" + +If building CppInterOp against clang-repl you will need to define the following + +.. code:: bash + export CPLUS_INCLUDE_PATH="${CPLUS_INCLUDE_PATH}:${LLVM_DIR}/llvm/include:${LLVM_DIR}/clang/include:${LLVM_DIR}/build/include:${LLVM_DIR}/build/tools/clang/include" +and if building against cling you will need to define the following + +.. code:: bash + + export CLING_DIR="$(pwd)/cling" + export CLING_BUILD_DIR="$(pwd)/cling/build" + export CPLUS_INCLUDE_PATH="${CLING_DIR}/tools/cling/include:${CLING_BUILD_DIR}/include:${LLVM_DIR}/llvm/include:${LLVM_DIR}/clang/include:${LLVM_BUILD_DIR}/include:${LLVM_BUILD_DIR}/tools/clang/include:$PWD/include" + If on MacOS you will also need the following environment variable defined .. code:: bash @@ -230,8 +237,23 @@ $PWD.Path ) $env:CB_PYTHON_DIR="$env:PWD_DIR\cppyy-backend\python" $env:CPPINTEROP_DIR="$env:CB_PYTHON_DIR\cppyy_backend" + + +If building against clang-repl you will have the following defined + +.. code:: powershell + $env:CPLUS_INCLUDE_PATH="$env:CPLUS_INCLUDE_PATH;$env:LLVM_DIR\llvm\include;$env:LLVM_DIR\clang\include;$env:LLVM_DIR\build\include;$env:LLVM_DIR\build\tools\clang\include" +and if building against cling + +.. code:: powershell + + $env:CLING_DIR="$env:PWD_DIR\cling" + $env:CLING_BUILD_DIR="$env:PWD_DIR\cling\build" + $env:CPLUS_INCLUDE_PATH="$env:CLING_DIR\tools\cling\include;$env:CLING_BUILD_DIR\include;$env:LLVM_DIR\llvm\include;$env:LLVM_DIR\clang\include;$env:LLVM_BUILD_DIR\include;$env:LLVM_BUILD_DIR\tools\clang\include;$env:PWD_DIR\include;" + + ****************** Build CppInterOp ****************** diff --git a/interpreter/CppInterOp/docs/ReleaseNotes.md b/interpreter/CppInterOp/docs/ReleaseNotes.md index 52953671769ae..811519deada86 100644 --- a/interpreter/CppInterOp/docs/ReleaseNotes.md +++ b/interpreter/CppInterOp/docs/ReleaseNotes.md @@ -1,7 +1,7 @@ # Introduction This document contains the release notes for the language interoperability -library CppInterOp, release 1.7.0. CppInterOp is built on top of +library CppInterOp, release 1.8.0. CppInterOp is built on top of [Clang](http://clang.llvm.org) and [LLVM](http://llvm.org%3E) compiler infrastructure. Here we describe the status of CppInterOp in some detail, including major improvements from the previous release and new feature work. @@ -16,7 +16,7 @@ interoperability on the fly. In such scenarios CppInterOp can be used to provide the necessary introspection information to the other side helping the language cross talk. -## What's New in CppInterOp 1.7.0? +## What's New in CppInterOp 1.8.0? Some of the major new features and improvements to CppInterOp are listed here. Generic improvements to CppInterOp as a whole or to its underlying @@ -29,30 +29,27 @@ infrastructure are described first. ## Introspection -- Added `BestOverloadFunctionMatch` and `IsFunction`; removed - `BestTemplateFunctionMatch`. -- Enhanced overload resolution and template instantiation capabilities. -- Improvements to function signature support for `FunctionTemplateDecl`s. -- Extended support for `GetClassTemplatedMethods`, `LookupConstructor`, and - better handling in `IsConstructor`. +- + +## Just-in-Time Compilation + +- ## Incremental C++ -- Improved error propagation in interpreter creation. -- Added undo/unload features with REPL support for the Cling backend. -- Enhancements in interpreter argument handling. +- ## Misc -- Fixed symbol visibility in the C API. -- Fixed symbol visibility issues in the C API. -- Improved CI and Emscripten build system including browser testing support. -- Updated build compatibility with Cling v1.2 and LLVM 20. -- Improved support and tests for Emscripten builds. -- Enabled shared object loading tests in Emscripten. -- Added automated coverage jobs and various test enhancements. -- Refined wrapper generation and fixed indentation consistency. +- + +## Fixed Bugs +[XXX](https://github.com/compiler-research/CppInterOp/issues/XXX) + + ## Special Kudos @@ -62,12 +59,8 @@ listed in the form of Firstname Lastname (#contributions): FirstName LastName (#commits) A B (N) -mcbarton (30) -Aaron Jomy (15) -Anutosh Bhat (6) -Gnimuc (5) -Vipul Cariappa (3) -Vassil Vassilev (2) -Abhinav Kumar (2) -Yupei Qi (1) -jeaye (1) + + diff --git a/interpreter/CppInterOp/docs/conf.py b/interpreter/CppInterOp/docs/conf.py index 9535d9bb53c49..a0082a3af721b 100644 --- a/interpreter/CppInterOp/docs/conf.py +++ b/interpreter/CppInterOp/docs/conf.py @@ -50,8 +50,8 @@ html_extra_path = [CPPINTEROP_ROOT + '/build/docs/'] import subprocess -command = 'mkdir {0}/build; cd {0}/build; cmake ../ -DClang_DIR=/usr/lib/llvm-13/build/lib/cmake/clang\ - -DLLVM_DIR=/usr/lib/llvm-13/build/lib/cmake/llvm -DCPPINTEROP_ENABLE_DOXYGEN=ON\ +command = 'mkdir {0}/build; cd {0}/build; cmake ../ -DClang_DIR=/usr/lib/llvm-16/build/lib/cmake/clang\ + -DLLVM_DIR=/usr/lib/llvm-16/build/lib/cmake/llvm -DCPPINTEROP_ENABLE_DOXYGEN=ON\ -DCPPINTEROP_INCLUDE_DOCS=ON'.format(CPPINTEROP_ROOT) subprocess.call(command, shell=True) subprocess.call('doxygen {0}/build/docs/doxygen.cfg'.format(CPPINTEROP_ROOT), shell=True) diff --git a/interpreter/CppInterOp/docs/index.rst b/interpreter/CppInterOp/docs/index.rst index 1c30932a43c24..b1c198e9c65b0 100644 --- a/interpreter/CppInterOp/docs/index.rst +++ b/interpreter/CppInterOp/docs/index.rst @@ -24,5 +24,6 @@ C++ to simpler, more interactive languages like Python. UsingCppInterOp reference tutorials + DebuggingCppInterOp FAQ DevelopersDocumentation diff --git a/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h b/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h new file mode 100644 index 0000000000000..2f3d330dbb749 --- /dev/null +++ b/interpreter/CppInterOp/include/CppInterOp/CppInterOp.h @@ -0,0 +1,906 @@ +//===--- 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, + kConstructorCall, + 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. + // (self, nargs, args, result, nary) + using GenericCall = void (*)(void*, size_t, void**, void*); + // (result, nary, nargs, args, is_arena) + using ConstructorCall = void (*)(void*, size_t, size_t, void**, void*); + // (self, nary, withFree) + using DestructorCall = void (*)(void*, size_t, int); + +private: + union { + GenericCall m_GenericCall; + ConstructorCall m_ConstructorCall; + DestructorCall m_DestructorCall; + }; + Kind m_Kind; + 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, ConstructorCall C, TCppConstFunction_t Ctor) + : m_ConstructorCall(C), m_Kind(K), m_FD(Ctor) {} + JitCall(Kind K, DestructorCall C, TCppConstFunction_t Dtor) + : m_DestructorCall(C), m_Kind(K), m_FD(Dtor) {} + + /// Checks if the passed arguments are valid for the given function. + CPPINTEROP_API bool AreArgumentsValid(void* result, ArgList args, void* self, + size_t nary) 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) + // Its possible the JitCall object deals with structor decls but went + // through Invoke + + switch (m_Kind) { + case kUnknown: + assert(0 && "Attempted to call an invalid function declaration"); + break; + + case kGenericCall: +#ifndef NDEBUG + // We pass 1UL to nary which is only relevant for structors + assert(AreArgumentsValid(result, args, self, 1UL) && "Invalid args!"); + ReportInvokeStart(result, args, self); +#endif // NDEBUG + m_GenericCall(self, args.m_ArgSize, args.m_Args, result); + break; + + case kConstructorCall: + // Forward if we intended to call a constructor (nary cannot be inferred, + // so we stick to constructing a single object) + InvokeConstructor(result, /*nary=*/1UL, args, self); + break; + case kDestructorCall: + // Forward if we intended to call a dtor with only 1 parameter. + assert(!args.m_Args && "Destructor called with arguments"); + InvokeDestructor(result, /*nary=*/0UL, /*withFree=*/true); + break; + } + // 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); + } + + /// Makes a call to a constructor. + ///\param[in] result - the memory address at which we construct the object + /// (placement new). + ///\param[in] nary - Use array new if we have to construct an array of + /// objects (nary > 1). + ///\param[in] args - a pointer to a argument list and argument size. + // FIXME: Change the type of withFree from int to bool in the wrapper code. + void InvokeConstructor(void* result, unsigned long nary = 1, + ArgList args = {}, void* is_arena = nullptr) const { + assert(m_Kind == kConstructorCall && "Wrong overload!"); +#ifndef NDEBUG + assert(AreArgumentsValid(result, args, /*self=*/nullptr, nary) && + "Invalid args!"); + ReportInvokeStart(result, args, nullptr); +#endif // NDEBUG + m_ConstructorCall(result, nary, args.m_ArgSize, args.m_Args, is_arena); + } +}; + +///\returns the version string information of the library. +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); + +/// Checks if type is a LValue reference +CPPINTEROP_API bool IsLValueReferenceType(TCppType_t type); + +/// Checks if type is a LValue reference +CPPINTEROP_API bool IsRValueReferenceType(TCppType_t type); + +/// Get the type that the reference refers to +CPPINTEROP_API TCppType_t GetNonReferenceType(TCppType_t type); + +/// Get lvalue referenced type, or rvalue if rvalue is true +CPPINTEROP_API TCppType_t GetReferencedType(TCppType_t type, + bool rvalue = false); + +/// Get the pointer to type +CPPINTEROP_API TCppType_t GetPointerType(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 string representation of the operator +CPPINTEROP_API std::string GetSpellingFromOperator(Operator Operator); + +///\returns operator of representing the string +CPPINTEROP_API Operator GetOperatorFromSpelling(const std::string& op); + +///\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 owned instance of the interpreter we need for the various interop +/// services and pushes it onto a stack. +///\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. +///\returns nullptr on failure. +CPPINTEROP_API TInterp_t +CreateInterpreter(const std::vector& Args = {}, + const std::vector& GpuArgs = {}); + +/// Deletes an instance of an interpreter. +///\param[in] I - the interpreter to be deleted, if nullptr, deletes the last. +///\returns false on failure or if \c I is not tracked in the stack. +CPPINTEROP_API bool DeleteInterpreter(TInterp_t I = nullptr); + +/// Activates an instance of an interpreter to handle subsequent API requests +///\param[in] I - the interpreter to be activated. +///\returns false on failure. +CPPINTEROP_API bool ActivateInterpreter(TInterp_t I); + +/// 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 +///\param[in] instantiate_body - also instantiate/define the body +/// +///\returns Instantiated templated class/function/variable pointer +CPPINTEROP_API TCppScope_t +InstantiateTemplate(TCppScope_t tmpl, const TemplateArgInfo* template_args, + size_t template_args_size, bool instantiate_body = false); + +/// 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 required by an object of a given class +/// \c scope Given class for which to allocate memory for +/// \c count is used to indicate the number of objects to allocate for. +CPPINTEROP_API TCppObject_t Allocate(TCppScope_t scope, + TCppIndex_t count = 1UL); + +/// Deallocates memory for a given class. +/// \c scope Class to indicate size of memory to deallocate +/// \c count is used to indicate the number of objects to dallocate for +CPPINTEROP_API void Deallocate(TCppScope_t scope, TCppObject_t address, + TCppIndex_t count = 1UL); + +/// Creates one or more objects of class \c scope by calling its default +/// constructor. +/// \param[in] scope Class to construct +/// \param[in] arena If set, this API uses placement new to construct at this +/// address. +/// \param[in] is used to indicate the number of objects to construct. +CPPINTEROP_API TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr, + TCppIndex_t count = 1UL); + +/// Destroys one or more objects of a class +/// \param[in] This this pointer of the object to destruct. Can also be the +/// starting address of an array of objects +/// \param[in] withFree if true, we call operator delete/free, else just the +/// destructor +/// \param[in] count indicate the number of objects to destruct, if \c This +/// points to an array of objects +CPPINTEROP_API void Destruct(TCppObject_t This, TCppScope_t type, + bool withFree = true, TCppIndex_t count = 0UL); + +/// @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/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h b/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h index f885e19c566ea..638424ea85b1d 100644 --- a/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h +++ b/interpreter/CppInterOp/include/clang-c/CXCppInterOp.h @@ -332,7 +332,8 @@ CINDEX_LINKAGE void clang_deallocate(CXObject address); * Creates an object of class \c scope and calls its default constructor. If \c * arena is set it uses placement new. */ -CINDEX_LINKAGE CXObject clang_construct(CXScope scope, void* arena); +CINDEX_LINKAGE CXObject clang_construct(CXScope scope, void* arena, + size_t count = 1UL); /** * Creates a trampoline function and makes a call to a generic function or @@ -361,7 +362,8 @@ CINDEX_LINKAGE void clang_invoke(CXScope func, void* result, void** args, * * \param withFree Whether to call operator delete/free or not. */ -CINDEX_LINKAGE void clang_destruct(CXObject This, CXScope S, bool withFree); +CINDEX_LINKAGE void clang_destruct(CXObject This, CXScope S, + bool withFree = true, size_t nary = 0UL); /** * @} diff --git a/interpreter/CppInterOp/include/clang/Interpreter/CppInterOp.h b/interpreter/CppInterOp/include/clang/Interpreter/CppInterOp.h index 7d1fdb12a78e8..9b8cdc8c47b4e 100644 --- a/interpreter/CppInterOp/include/clang/Interpreter/CppInterOp.h +++ b/interpreter/CppInterOp/include/clang/Interpreter/CppInterOp.h @@ -1,809 +1,13 @@ -//--------------------------------------------------------------------*- 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 -#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"))) +#include "CppInterOp/CppInterOp.h" +#if defined(_MSC_VER) +#pragma message( \ + "#include is deprecated; use #include ") #else -#define CPPINTEROP_API +#warning \ + "#include is deprecated; use #include " #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/interpreter/CppInterOp/lib/CMakeLists.txt b/interpreter/CppInterOp/lib/CMakeLists.txt index 1951dee7505d2..55808f2729c18 100644 --- a/interpreter/CppInterOp/lib/CMakeLists.txt +++ b/interpreter/CppInterOp/lib/CMakeLists.txt @@ -1 +1 @@ -add_subdirectory(Interpreter) +add_subdirectory(CppInterOp) diff --git a/interpreter/CppInterOp/lib/Interpreter/CMakeLists.txt b/interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt similarity index 98% rename from interpreter/CppInterOp/lib/Interpreter/CMakeLists.txt rename to interpreter/CppInterOp/lib/CppInterOp/CMakeLists.txt index eccfc39af7003..8835511b4f85f 100644 --- a/interpreter/CppInterOp/lib/Interpreter/CMakeLists.txt +++ b/interpreter/CppInterOp/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/interpreter/CppInterOp/lib/Interpreter/CXCppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CXCppInterOp.cpp similarity index 97% rename from interpreter/CppInterOp/lib/Interpreter/CXCppInterOp.cpp rename to interpreter/CppInterOp/lib/CppInterOp/CXCppInterOp.cpp index 031096c9136ec..0546f3a2f2dac 100644 --- a/interpreter/CppInterOp/lib/Interpreter/CXCppInterOp.cpp +++ b/interpreter/CppInterOp/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" @@ -199,9 +200,7 @@ CXTypeKind cxtype_GetTypeKind(QualType T) { TKCASE(Elaborated); TKCASE(Pipe); TKCASE(Attributed); -#if CLANG_VERSION_MAJOR >= 16 TKCASE(BTFTagAttributed); -#endif TKCASE(Atomic); default: return CXType_Unexposed; @@ -572,7 +571,8 @@ bool clang_existsFunctionTemplate(const char* name, CXScope parent) { namespace Cpp { TCppScope_t InstantiateTemplate(compat::Interpreter& I, TCppScope_t tmpl, const TemplateArgInfo* template_args, - size_t template_args_size); + size_t template_args_size, + bool instantiate_body = false); } // namespace Cpp CXScope clang_instantiateTemplate(CXScope tmpl, @@ -598,12 +598,12 @@ void clang_deallocate(CXObject address) { ::operator delete(address); } namespace Cpp { void* Construct(compat::Interpreter& interp, TCppScope_t scope, - void* arena /*=nullptr*/); + void* arena /*=nullptr*/, TCppIndex_t count); } // namespace Cpp -CXObject clang_construct(CXScope scope, void* arena) { +CXObject clang_construct(CXScope scope, void* arena, size_t count) { return Cpp::Construct(*getInterpreter(scope), - static_cast(getDecl(scope)), arena); + static_cast(getDecl(scope)), arena, count); } void clang_invoke(CXScope func, void* result, void** args, size_t n, @@ -614,9 +614,9 @@ void clang_invoke(CXScope func, void* result, void** args, size_t n, namespace Cpp { void Destruct(compat::Interpreter& interp, TCppObject_t This, - clang::Decl* Class, bool withFree); + clang::Decl* Class, bool withFree, size_t nary); } // namespace Cpp -void clang_destruct(CXObject This, CXScope S, bool withFree) { - Cpp::Destruct(*getInterpreter(S), This, getDecl(S), withFree); -} \ No newline at end of file +void clang_destruct(CXObject This, CXScope S, bool withFree, size_t nary) { + Cpp::Destruct(*getInterpreter(S), This, getDecl(S), withFree, nary); +} diff --git a/interpreter/CppInterOp/lib/Interpreter/Compatibility.h b/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h similarity index 80% rename from interpreter/CppInterOp/lib/Interpreter/Compatibility.h rename to interpreter/CppInterOp/lib/CppInterOp/Compatibility.h index 15962504c3b34..0c7c66f5bf334 100644 --- a/interpreter/CppInterOp/lib/Interpreter/Compatibility.h +++ b/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h @@ -99,48 +99,23 @@ namespace compat { using Interpreter = cling::Interpreter; +class SynthesizingCodeRAII : public Interpreter::PushTransactionRAII { +public: + SynthesizingCodeRAII(Interpreter* i) : Interpreter::PushTransactionRAII(i) {} +}; + inline void maybeMangleDeclName(const clang::GlobalDecl& GD, std::string& mangledName) { cling::utils::Analyze::maybeMangleDeclName(GD, mangledName); } -/// For Cling <= LLVM 16, this is a horrible hack obtaining the private -/// llvm::orc::LLJIT by computing the object offsets in the cling::Interpreter -/// instance(IncrementalExecutor): sizeof (m_Opts) + sizeof(m_LLVMContext). The -/// IncrementalJIT and JIT itself have an offset of 0 as the first datamember. -/// -/// The getExecutionEngine() interface has been added for Cling based on LLVM -/// >=18 and should be used in future releases. +/// The getExecutionEngine() interface was been added for Cling based on LLVM +/// >=18. For previous versions, the LLJIT was obtained by computing the object +/// offsets in the cling::Interpreter instance(IncrementalExecutor): +/// sizeof (m_Opts) + sizeof(m_LLVMContext). The IncrementalJIT and JIT itself +/// have an offset of 0 as the first datamember. inline llvm::orc::LLJIT* getExecutionEngine(cling::Interpreter& I) { -#if CLANG_VERSION_MAJOR >= 18 return I.getExecutionEngine(); -#endif - - unsigned m_ExecutorOffset = 0; - -#if CLANG_VERSION_MAJOR == 13 -#ifdef __APPLE__ - m_ExecutorOffset = 62; -#else - m_ExecutorOffset = 72; -#endif // __APPLE__ -#endif - -// Note: The offsets changed in Cling based on LLVM 16 with the introduction of -// a thread safe context - llvm::orc::ThreadSafeContext -#if CLANG_VERSION_MAJOR == 16 -#ifdef __APPLE__ - m_ExecutorOffset = 68; -#else - m_ExecutorOffset = 78; -#endif // __APPLE__ -#endif - - int* IncrementalExecutor = - ((int*)(const_cast(&I))) + m_ExecutorOffset; - int* IncrementalJit = *(int**)IncrementalExecutor + 0; - int* LLJIT = *(int**)IncrementalJit + 0; - return *(llvm::orc::LLJIT**)LLJIT; } inline llvm::Expected @@ -152,12 +127,8 @@ getSymbolAddress(cling::Interpreter& I, llvm::StringRef IRName) { llvm::orc::SymbolNameVector Names; llvm::orc::ExecutionSession& ES = Jit.getExecutionSession(); Names.push_back(ES.intern(IRName)); -#if CLANG_VERSION_MAJOR < 16 - return llvm::make_error(Names); -#else return llvm::make_error(ES.getSymbolStringPool(), std::move(Names)); -#endif // CLANG_VERSION_MAJOR } inline void codeComplete(std::vector& Results, @@ -238,9 +209,6 @@ namespace compat { inline std::unique_ptr createClangInterpreter(std::vector& args) { -#if CLANG_VERSION_MAJOR < 16 - auto ciOrErr = clang::IncrementalCompilerBuilder::create(args); -#else auto has_arg = [](const char* x, llvm::StringRef match = "cuda") { llvm::StringRef Arg = x; Arg = Arg.trim().ltrim('-'); @@ -270,15 +238,11 @@ createClangInterpreter(std::vector& args) { DeviceCI = std::move(*devOrErr); } auto ciOrErr = CudaEnabled ? CB.CreateCudaHost() : CB.CreateCpp(); -#endif // CLANG_VERSION_MAJOR < 16 if (!ciOrErr) { llvm::logAllUnhandledErrors(ciOrErr.takeError(), llvm::errs(), "Failed to build Incremental compiler:"); return nullptr; } -#if CLANG_VERSION_MAJOR < 16 - auto innerOrErr = clang::Interpreter::create(std::move(*ciOrErr)); -#else (*ciOrErr)->LoadRequestedPlugins(); if (CudaEnabled) DeviceCI->LoadRequestedPlugins(); @@ -286,7 +250,6 @@ createClangInterpreter(std::vector& args) { CudaEnabled ? clang::Interpreter::createWithCUDA(std::move(*ciOrErr), std::move(DeviceCI)) : clang::Interpreter::create(std::move(*ciOrErr)); -#endif // CLANG_VERSION_MAJOR < 16 if (!innerOrErr) { llvm::logAllUnhandledErrors(innerOrErr.takeError(), llvm::errs(), @@ -333,29 +296,15 @@ inline void maybeMangleDeclName(const clang::GlobalDecl& GD, RawStr.flush(); } -// Clang 13 - Initial implementation of Interpreter and clang-repl -// Clang 14 - Add new Interpreter methods: getExecutionEngine, -// getSymbolAddress, getSymbolAddressFromLinkerName -// Clang 15 - Add new Interpreter methods: Undo // Clang 18 - Add new Interpreter methods: CodeComplete inline llvm::orc::LLJIT* getExecutionEngine(clang::Interpreter& I) { -#if CLANG_VERSION_MAJOR >= 14 auto* engine = &llvm::cantFail(I.getExecutionEngine()); return const_cast(engine); -#else - assert(0 && "Not implemented in Clang <14!"); - return nullptr; -#endif } inline llvm::Expected getSymbolAddress(clang::Interpreter& I, llvm::StringRef IRName) { -#if CLANG_VERSION_MAJOR < 14 - assert(0 && "Not implemented in Clang <14!"); - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "Not implemented in Clang <14!"); -#endif // CLANG_VERSION_MAJOR < 14 auto AddrOrErr = I.getSymbolAddress(IRName); if (llvm::Error Err = AddrOrErr.takeError()) @@ -373,7 +322,6 @@ getSymbolAddress(clang::Interpreter& I, clang::GlobalDecl GD) { inline llvm::Expected getSymbolAddressFromLinkerName(clang::Interpreter& I, llvm::StringRef LinkerName) { -#if CLANG_VERSION_MAJOR >= 14 const auto& DL = getExecutionEngine(I)->getDataLayout(); char GlobalPrefix = DL.getGlobalPrefix(); std::string LinkerNameTmp(LinkerName); @@ -384,21 +332,10 @@ getSymbolAddressFromLinkerName(clang::Interpreter& I, if (llvm::Error Err = AddrOrErr.takeError()) return std::move(Err); return AddrOrErr->getValue(); -#else - assert(0 && "Not implemented in Clang <14!"); - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "Not implemented in Clang <14!"); -#endif } inline llvm::Error Undo(clang::Interpreter& I, unsigned N = 1) { -#if CLANG_VERSION_MAJOR >= 15 return I.Undo(N); -#else - assert(0 && "Not implemented in Clang <15!"); - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "Not implemented in Clang <15!"); -#endif } inline void codeComplete(std::vector& Results, @@ -444,6 +381,20 @@ namespace Cpp_utils = Cpp::utils; namespace compat { using Interpreter = Cpp::Interpreter; + +class SynthesizingCodeRAII { +private: + Interpreter* m_Interpreter; + +public: + SynthesizingCodeRAII(Interpreter* i) : m_Interpreter(i) {} + ~SynthesizingCodeRAII() { + auto GeneratedPTU = m_Interpreter->Parse(""); + if (!GeneratedPTU) + llvm::logAllUnhandledErrors(GeneratedPTU.takeError(), llvm::errs(), + "Failed to generate PTU:"); + } +}; } #endif // CPPINTEROP_USE_REPL @@ -451,35 +402,14 @@ using Interpreter = Cpp::Interpreter; namespace compat { // Clang >= 14 change type name to string (spaces formatting problem) -#if CLANG_VERSION_MAJOR >= 14 inline std::string FixTypeName(const std::string type_name) { return type_name; } -#else -inline std::string FixTypeName(const std::string type_name) { - std::string result = type_name; - size_t pos = 0; - while ((pos = result.find(" [", pos)) != std::string::npos) { - result.erase(pos, 1); - pos++; - } - return result; -} -#endif -// Clang >= 16 change CLANG_LIBDIR_SUFFIX to CLANG_INSTALL_LIBDIR_BASENAME -#if CLANG_VERSION_MAJOR < 16 -#define CLANG_INSTALL_LIBDIR_BASENAME (llvm::Twine("lib") + CLANG_LIBDIR_SUFFIX) -#endif inline std::string MakeResourceDir(llvm::StringRef Dir) { llvm::SmallString<128> P(Dir); llvm::sys::path::append(P, CLANG_INSTALL_LIBDIR_BASENAME, "clang", -#if CLANG_VERSION_MAJOR < 16 - CLANG_VERSION_STRING -#else - CLANG_VERSION_MAJOR_STRING -#endif - ); + CLANG_VERSION_MAJOR_STRING); return std::string(P.str()); } diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp new file mode 100644 index 0000000000000..bcbc123599805 --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -0,0 +1,3912 @@ +//--------------------------------------------------------------------*- 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/Interpreter/Interpreter.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/CommandLine.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/raw_os_ostream.h" + +#include +#include +#include +#include +#include +#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 + +namespace Cpp { + +using namespace clang; +using namespace llvm; +using namespace std; + +struct InterpreterInfo { + compat::Interpreter* Interpreter = nullptr; + bool isOwned = true; + InterpreterInfo(compat::Interpreter* I, bool Owned) + : Interpreter(I), isOwned(Owned) {} + + // Enable move constructors. + InterpreterInfo(InterpreterInfo&& other) noexcept + : Interpreter(other.Interpreter), isOwned(other.isOwned) { + other.Interpreter = nullptr; + other.isOwned = false; + } + InterpreterInfo& operator=(InterpreterInfo&& other) noexcept { + if (this != &other) { + // Delete current resource if owned + if (isOwned) + delete Interpreter; + + Interpreter = other.Interpreter; + isOwned = other.isOwned; + + other.Interpreter = nullptr; + other.isOwned = false; + } + return *this; + } + + ~InterpreterInfo() { + if (isOwned) + delete Interpreter; + } + + // Disable copy semantics (to avoid accidental double deletes) + InterpreterInfo(const InterpreterInfo&) = delete; + InterpreterInfo& operator=(const InterpreterInfo&) = delete; +}; + +// std::deque avoids relocations and calling the dtor of InterpreterInfo. +static llvm::ManagedStatic> sInterpreters; + +static compat::Interpreter& getInterp() { + assert(!sInterpreters->empty() && + "Interpreter instance must be set before calling this!"); + return *sInterpreters->back().Interpreter; +} +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, + size_t nary) 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; + } + if (Cpp::IsConstructor(m_FD) && nary == 0UL) { + assert(0 && "Number of objects to construct should be atleast 1"); + Valid = false; + } + if (Cpp::IsConstructor(m_FD)) { + const auto* CD = cast((const Decl*)m_FD); + if (CD->getMinRequiredArguments() != 0 && nary > 1) { + assert(0 && + "Cannot pass initialization parameters to array new construction"); + Valid = false; + } + } + assert(m_Kind != kDestructorCall && "Wrong overload!"); + Valid &= m_Kind != kDestructorCall; + return Valid; +} + +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; } + +static void InstantiateFunctionDefinition(Decl* D) { + compat::SynthesizingCodeRAII RAII(&getInterp()); + if (auto* FD = llvm::dyn_cast_or_null(D)) { + getSema().InstantiateFunctionDefinition(SourceLocation(), FD, + /*Recursive=*/true, + /*DefinitionRequired=*/true); + } +} + +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); + PrintingPolicy PP = C.getPrintingPolicy(); + PP.FullyQualifiedName = true; + PP.SuppressUnwrittenScope = true; +#if CLANG_VERSION_MAJOR > 16 + PP.SuppressElaboration = true; +#endif + QT.getAsStringInternal(type_name, PP); + + 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) || + 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()) { + bool needInstantiation = false; + if (IsTemplatedFunction(FD) && !FD->isDefined()) + needInstantiation = true; + if (auto* MD = llvm::dyn_cast(FD)) { + if (IsTemplateSpecialization(MD->getParent())) + needInstantiation = true; + } + + if (needInstantiation) { + InstantiateFunctionDefinition(FD); + } + 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->isLValueReferenceType()) + 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; +} + +static TCppFuncAddr_t GetFunctionAddress(const FunctionDecl* FD) { + const auto get_mangled_name = [](const 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; + }; + + // Constructor and Destructors needs to be handled differently + if (!llvm::isa(FD) && !llvm::isa(FD)) + return GetFunctionAddress(get_mangled_name(FD).c_str()); + + return 0; +} + +TCppFuncAddr_t GetFunctionAddress(TCppFunction_t method) { + auto* D = static_cast(method); + if (auto* FD = llvm::dyn_cast_or_null(D)) + return GetFunctionAddress(FD); + return nullptr; +} + +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(); +} + +bool IsLValueReferenceType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + return QT->isLValueReferenceType(); +} + +bool IsRValueReferenceType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + return QT->isRValueReferenceType(); +} + +TCppType_t GetPointerType(TCppType_t type) { + QualType QT = QualType::getFromOpaquePtr(type); + return getASTContext().getPointerType(QT).getAsOpaquePtr(); +} + +TCppType_t GetReferencedType(TCppType_t type, bool rvalue) { + QualType QT = QualType::getFromOpaquePtr(type); + if (rvalue) + return getASTContext().getRValueReferenceType(QT).getAsOpaquePtr(); + return getASTContext().getLValueReferenceType(QT).getAsOpaquePtr(); +} + +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, + bool array = false) { + // Make a code string that follows this pattern: + // + // ClassName(args...) + // OR + // ClassName[nary] // array of objects + // + + if (array) + callbuf << class_name << "[nary]"; + else + callbuf << class_name; + + // We cannot pass initialization parameters if we call array new + if (N && !array) { + callbuf << "("; + for (unsigned i = 0U; i < N; ++i) { + const ParmVarDecl* PVD = FD->getParamDecl(i); + QualType Ty = PVD->getType(); + QualType QT = Ty.getCanonicalType(); + std::string type_name; + EReferenceType refType = kNotReference; + bool isPointer = false; + collect_type_info(FD, QT, typedefbuf, callbuf, type_name, refType, + isPointer, indent_level, true); + if (i) { + callbuf << ','; + if (i % 2) { + callbuf << ' '; + } else { + callbuf << "\n"; + indent(callbuf, indent_level); + } + } + if (refType != kNotReference) { + callbuf << "(" << type_name.c_str() + << (refType == kLValueReference ? "&" : "&&") << ")*(" + << type_name.c_str() << "*)args[" << i << "]"; + } else if (isPointer) { + callbuf << "*(" << type_name.c_str() << "**)args[" << i << "]"; + } else { + callbuf << "*(" << type_name.c_str() << "*)args[" << i << "]"; + } + } + callbuf << ")"; + } + // This can be zero or default-initialized + else if (const auto* CD = dyn_cast(FD); + CD && CD->isDefaultConstructor() && !array) { + callbuf << "()"; + } +} + +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 && + !FD->isTemplateInstantiation(); + 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); + PrintingPolicy PP = FD->getASTContext().getPrintingPolicy(); + PP.FullyQualifiedName = true; + PP.SuppressUnwrittenScope = true; +#if CLANG_VERSION_MAJOR > 16 + PP.SuppressElaboration = true; +#endif + FD->getNameForDiagnostic(stream, PP, + /*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 << ' ' + << clang::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: + // + // Array new if nary has been passed, and nargs is 0 (must be default ctor) + // if (nary) { + // (*(ClassName**)ret) = (obj) ? new (*(ClassName**)ret) ClassName[nary] : + // new ClassName[nary]; + // } + // else { + // (*(ClassName**)ret) = (obj) ? new (*(ClassName**)ret) ClassName(args...) + // : new ClassName(args...); + // } + { + std::ostringstream typedefbuf; + std::ostringstream callbuf; + // + // Write the return value assignment part. + // + indent(callbuf, indent_level); + const auto* CD = dyn_cast(FD); + + // Activate this block only if array new is possible + // if (nary) { + // (*(ClassName**)ret) = (obj) ? new (*(ClassName**)ret) ClassName[nary] + // : new ClassName[nary]; + // } + // else { + if (CD->isDefaultConstructor()) { + callbuf << "if (nary > 1) {\n"; + indent(callbuf, indent_level); + callbuf << "(*(" << class_name << "**)ret) = "; + callbuf << "(is_arena) ? new (*(" << class_name << "**)ret) "; + make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level, + true); + + callbuf << ": new "; + // + // Write the actual expression. + // + make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level, + true); + // + // End the new expression statement. + // + callbuf << ";\n"; + indent(callbuf, indent_level); + callbuf << "}\n"; + callbuf << "else {\n"; + } + + // Standard branch: + // (*(ClassName**)ret) = (obj) ? new (*(ClassName**)ret) ClassName(args...) + // : new ClassName(args...); + indent(callbuf, indent_level); + callbuf << "(*(" << class_name << "**)ret) = "; + callbuf << "(is_arena) ? 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"; + indent(callbuf, --indent_level); + if (CD->isDefaultConstructor()) + 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 (!GetFunctionAddress(FD)) { + 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); + InstantiateFunctionDefinition(FDmod); + + 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; + if (Cpp::IsConstructor(FD)) { + buf << "(void* ret, unsigned long nary, unsigned long nargs, void** args, " + "void* is_arena)\n" + "{\n"; + } else + buf << "(void* obj, unsigned long nargs, void** args, void* ret)\n" + "{\n"; + + ++indent_level; + if (min_args == num_params) { + // No parameters with defaults. + 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 (const auto* Ctor = dyn_cast(D)) { + if (auto Wrapper = make_wrapper(*interp, cast(D))) + return {JitCall::kConstructorCall, Wrapper, Ctor}; + // FIXME: else error we failed to compile the wrapper. + return {}; + } + + if (auto Wrapper = make_wrapper(*interp, cast(D))) { + return {JitCall::kGenericCall, Wrapper, cast(D)}; + } + // 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()); + } + + sInterpreters->emplace_back(I, /*Owned=*/true); + + return I; +} + +bool DeleteInterpreter(TInterp_t I /*=nullptr*/) { + if (!I) { + sInterpreters->pop_back(); + return true; + } + + auto found = + std::find_if(sInterpreters->begin(), sInterpreters->end(), + [&I](const auto& Info) { return Info.Interpreter == I; }); + if (found == sInterpreters->end()) + return false; // failure + + sInterpreters->erase(found); + return true; +} + +bool ActivateInterpreter(TInterp_t I) { + if (!I) + return false; + + auto found = + std::find_if(sInterpreters->begin(), sInterpreters->end(), + [&I](const auto& Info) { return Info.Interpreter == I; }); + if (found == sInterpreters->end()) + return false; + + if (std::next(found) != sInterpreters->end()) // if not already last element. + std::rotate(found, found + 1, sInterpreters->end()); + + return true; // success +} + +TInterp_t GetInterpreter() { + if (sInterpreters->empty()) + return nullptr; + return sInterpreters->back().Interpreter; +} + +void UseExternalInterpreter(TInterp_t I) { + assert(sInterpreters->empty() && "sInterpreter already in use!"); + sInterpreters->emplace_back(static_cast(I), + /*isOwned=*/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 = CLANG_VERSION_MAJOR_STRING; + // 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, + bool instantiate_body) { + // 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; + } + if (instantiate_body) + InstantiateFunctionDefinition(Specialization); + 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, + bool instantiate_body) { + // Create a list of template arguments. + TemplateArgumentListInfo TLI{}; + for (auto TA : TemplateArgs) + TLI.addArgument( + S.getTrivialTemplateArgumentLoc(TA, QualType(), SourceLocation())); + + return InstantiateTemplate(TemplateD, TLI, S, instantiate_body); +} + +TCppScope_t InstantiateTemplate(compat::Interpreter& I, TCppScope_t tmpl, + const TemplateArgInfo* template_args, + size_t template_args_size, + bool instantiate_body) { + 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, instantiate_body); +} + +TCppScope_t InstantiateTemplate(TCppScope_t tmpl, + const TemplateArgInfo* template_args, + size_t template_args_size, + bool instantiate_body) { + return InstantiateTemplate(getInterp(), tmpl, template_args, + template_args_size, instantiate_body); +} + +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(); +} + +std::string GetSpellingFromOperator(Operator Operator) { + return clang::getOperatorSpelling((clang::OverloadedOperatorKind)Operator); +} + +Operator GetOperatorFromSpelling(const std::string& op) { +#define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, MemberOnly) \ + if ((Spelling) == op) { \ + return (Operator)OO_##Name; \ + } +#include "clang/Basic/OperatorKinds.def" + return Operator::OP_None; +} + +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, TCppIndex_t count) { + return (TCppObject_t)::operator new(Cpp::SizeOf(scope) * count); +} + +void Deallocate(TCppScope_t scope, TCppObject_t address, TCppIndex_t count) { + size_t bytes = Cpp::SizeOf(scope) * count; + ::operator delete(address, bytes); +} + +// FIXME: Add optional arguments to the operator new. +TCppObject_t Construct(compat::Interpreter& interp, TCppScope_t scope, + void* arena /*=nullptr*/, TCppIndex_t count /*=1UL*/) { + 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.InvokeConstructor(&arena, count, {}, + (void*)~0); // Tell Invoke to use placement new. + return arena; + } + + void* obj = nullptr; + JC.InvokeConstructor(&obj, count, {}, nullptr); + return obj; + } + return nullptr; +} + +TCppObject_t Construct(TCppScope_t scope, void* arena /*=nullptr*/, + TCppIndex_t count /*=1UL*/) { + return Construct(getInterp(), scope, arena, count); +} + +void Destruct(compat::Interpreter& interp, TCppObject_t This, Decl* Class, + bool withFree, TCppIndex_t nary) { + if (auto wrapper = make_dtor_wrapper(interp, Class)) { + (*wrapper)(This, nary, withFree); + return; + } + // FIXME: Diagnose. +} + +void Destruct(TCppObject_t This, TCppScope_t scope, bool withFree /*=true*/, + TCppIndex_t count /*=0UL*/) { + auto* Class = static_cast(scope); + Destruct(getInterp(), This, Class, withFree, count); +} + +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/interpreter/CppInterOp/lib/Interpreter/CppInterOpInterpreter.h b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h similarity index 100% rename from interpreter/CppInterOp/lib/Interpreter/CppInterOpInterpreter.h rename to interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h diff --git a/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManager.cpp b/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManager.cpp new file mode 100644 index 0000000000000..2a90300e42bbb --- /dev/null +++ b/interpreter/CppInterOp/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/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManager.h b/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManager.h similarity index 100% rename from interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManager.h rename to interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManager.h diff --git a/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManagerSymbol.cpp b/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManagerSymbol.cpp new file mode 100644 index 0000000000000..44661f90f213c --- /dev/null +++ b/interpreter/CppInterOp/lib/CppInterOp/DynamicLibraryManagerSymbol.cpp @@ -0,0 +1,1374 @@ +//------------------------------------------------------------------------------ +// 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 +// clang-format off +#include +#include // For GetModuleFileNameA +#include // For VirtualQuery +// clang-format on +#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/interpreter/CppInterOp/lib/Interpreter/Paths.cpp b/interpreter/CppInterOp/lib/CppInterOp/Paths.cpp similarity index 59% rename from interpreter/CppInterOp/lib/Interpreter/Paths.cpp rename to interpreter/CppInterOp/lib/CppInterOp/Paths.cpp index 17abb633ed3d7..78732e7a7b0ac 100644 --- a/interpreter/CppInterOp/lib/Interpreter/Paths.cpp +++ b/interpreter/CppInterOp/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/interpreter/CppInterOp/lib/Interpreter/Paths.h b/interpreter/CppInterOp/lib/CppInterOp/Paths.h similarity index 100% rename from interpreter/CppInterOp/lib/Interpreter/Paths.h rename to interpreter/CppInterOp/lib/CppInterOp/Paths.h diff --git a/interpreter/CppInterOp/lib/Interpreter/exports.ld b/interpreter/CppInterOp/lib/CppInterOp/exports.ld similarity index 100% rename from interpreter/CppInterOp/lib/Interpreter/exports.ld rename to interpreter/CppInterOp/lib/CppInterOp/exports.ld diff --git a/interpreter/CppInterOp/lib/Interpreter/CppInterOp.cpp b/interpreter/CppInterOp/lib/Interpreter/CppInterOp.cpp deleted file mode 100755 index 1710b91b625b9..0000000000000 --- a/interpreter/CppInterOp/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/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManager.cpp b/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManager.cpp deleted file mode 100644 index d1d7546f1a43c..0000000000000 --- a/interpreter/CppInterOp/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/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManagerSymbol.cpp b/interpreter/CppInterOp/lib/Interpreter/DynamicLibraryManagerSymbol.cpp deleted file mode 100644 index 31262816e0a43..0000000000000 --- a/interpreter/CppInterOp/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/interpreter/CppInterOp/unittests/CMakeLists.txt b/interpreter/CppInterOp/unittests/CMakeLists.txt index cff3295531fa2..0ea65be871ba1 100644 --- a/interpreter/CppInterOp/unittests/CMakeLists.txt +++ b/interpreter/CppInterOp/unittests/CMakeLists.txt @@ -11,7 +11,6 @@ if(EMSCRIPTEN) set(gtest_libs gtest gmock) else() set(gtest_libs gtest gtest_main) - # Clang prior than clang13 (I think) merges both gmock into gtest. if (TARGET gmock) list(APPEND gtest_libs gmock gmock_main) endif() diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt b/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt index 0ba35053fdcfd..3372dd1477b54 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt +++ b/interpreter/CppInterOp/unittests/CppInterOp/CMakeLists.txt @@ -24,6 +24,7 @@ add_cppinterop_unittest(CppInterOpTests ) if(EMSCRIPTEN) + string(REPLACE "@" "@@" ESCAPED_SYSROOT_PATH "${SYSROOT_PATH}") # Explanation of Emscripten-specific link flags for CppInterOpTests: # # MAIN_MODULE=1: @@ -55,7 +56,7 @@ if(EMSCRIPTEN) PUBLIC "SHELL: -s ALLOW_MEMORY_GROWTH=1" PUBLIC "SHELL: -s STACK_SIZE=32mb" PUBLIC "SHELL: -s INITIAL_MEMORY=128mb" - PUBLIC "SHELL: --preload-file ${SYSROOT_PATH}/include@/include" + PUBLIC "SHELL: --preload-file ${ESCAPED_SYSROOT_PATH}/include@/include" PUBLIC "SHELL: --emrun" ) endif() @@ -96,6 +97,8 @@ target_link_libraries(DynamicLibraryManagerTests ) if(EMSCRIPTEN) + set(TEST_SHARED_LIBRARY_PATH "${CMAKE_CURRENT_BINARY_DIR}/TestSharedLib/unittests/bin/Release/") + string(REPLACE "@" "@@" ESCAPED_TEST_SHARED_LIBRARY_PATH "${TEST_SHARED_LIBRARY_PATH}") # Check explanation of Emscripten-specific link flags for CppInterOpTests above for DynamicLibraryManagerTests as well. target_link_options(DynamicLibraryManagerTests PUBLIC "SHELL: -s MAIN_MODULE=1" @@ -103,9 +106,9 @@ if(EMSCRIPTEN) PUBLIC "SHELL: -s ALLOW_MEMORY_GROWTH=1" PUBLIC "SHELL: -s STACK_SIZE=32mb" PUBLIC "SHELL: -s INITIAL_MEMORY=128mb" - PUBLIC "SHELL: --preload-file ${SYSROOT_PATH}/include@/include" + PUBLIC "SHELL: --preload-file ${ESCAPED_SYSROOT_PATH}/include@/include" PUBLIC "SHELL: --emrun" - PUBLIC "SHELL: --preload-file ${CMAKE_CURRENT_BINARY_DIR}/TestSharedLib/unittests/bin/Release/libTestSharedLib.so@/libTestSharedLib.so" + PUBLIC "SHELL: --preload-file ${ESCAPED_TEST_SHARED_LIBRARY_PATH}/libTestSharedLib.so@/libTestSharedLib.so" ) endif() diff --git a/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp index f697007cf886c..45b41c94d55c6 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/CUDATest.cpp +++ b/interpreter/CppInterOp/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" @@ -9,10 +10,10 @@ using namespace TestUtils; static bool HasCudaSDK() { auto supportsCudaSDK = []() { -#if CLANG_VERSION_MAJOR < 16 +#ifdef CPPINTEROP_USE_CLING // FIXME: Enable this for cling. return false; -#endif // CLANG_VERSION_MAJOR < 16 +#endif if (!Cpp::CreateInterpreter({}, {"--cuda"})) return false; return Cpp::Declare("__global__ void test_func() {}" @@ -24,10 +25,10 @@ static bool HasCudaSDK() { static bool HasCudaRuntime() { auto supportsCuda = []() { -#if CLANG_VERSION_MAJOR < 16 +#ifdef CPPINTEROP_USE_CLING // FIXME: Enable this for cling. return false; -#endif //CLANG_VERSION_MAJOR < 16 +#endif if (!HasCudaSDK()) return false; @@ -43,11 +44,11 @@ static bool HasCudaRuntime() { return hasCuda; } -#if CLANG_VERSION_MAJOR < 16 +#ifdef CPPINTEROP_USE_CLING TEST(DISABLED_CUDATest, Sanity) { #else TEST(CUDATest, Sanity) { -#endif // CLANG_VERSION_MAJOR < 16 +#endif #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; #endif @@ -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/interpreter/CppInterOp/unittests/CppInterOp/DynamicLibraryManagerTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/DynamicLibraryManagerTest.cpp index 6ac7e5179a661..4925af32984f3 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/DynamicLibraryManagerTest.cpp +++ b/interpreter/CppInterOp/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/interpreter/CppInterOp/unittests/CppInterOp/EnumReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/EnumReflectionTest.cpp index a8fc1a0282165..f4b3d47de6c89 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/EnumReflectionTest.cpp +++ b/interpreter/CppInterOp/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/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp index 7e907ae18501a..7ca9f9d4376ed 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/FunctionReflectionTest.cpp +++ b/interpreter/CppInterOp/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 @@ -384,6 +385,11 @@ TEST(FunctionReflectionTest, GetFunctionReturnType) { template auto rttest_make_tlist(T ... args) { return RTTest_TemplatedList{}; } + + template + struct Klass { + auto func(T t) { return t; } + }; )"; GetAllTopLevelDecls(code, Decls, true); @@ -429,6 +435,11 @@ TEST(FunctionReflectionTest, GetFunctionReturnType) { Cpp::GetTypeAsString(Cpp::GetFunctionReturnType( Cpp::BestOverloadFunctionMatch(candidates, explicit_args, args))), "RTTest_TemplatedList"); + + std::vector args2 = {C.DoubleTy.getAsOpaquePtr()}; + EXPECT_EQ(Cpp::GetTypeAsString(Cpp::GetFunctionReturnType(Cpp::GetNamed( + "func", Cpp::InstantiateTemplate(Decls[15], args2.data(), 1)))), + "double"); } TEST(FunctionReflectionTest, GetFunctionNumArgs) { @@ -894,7 +905,7 @@ TEST(FunctionReflectionTest, InstantiateVariadicFunction) { // instantiate VariadicFnExtended auto Instance2 = - Cpp::InstantiateTemplate(Decls[2], args2.data(), args2.size()); + Cpp::InstantiateTemplate(Decls[2], args2.data(), args2.size(), true); EXPECT_TRUE(Cpp::IsTemplatedFunction(Instance2)); FunctionDecl* FD2 = cast((Decl*)Instance2); @@ -1410,6 +1421,8 @@ TEST(FunctionReflectionTest, GetFunctionAddress) { std::stringstream address; address << Cpp::GetFunctionAddress(Decls[0]); EXPECT_EQ(address.str(), output); + + EXPECT_FALSE(Cpp::GetFunctionAddress(Cpp::GetGlobalScope())); } TEST(FunctionReflectionTest, IsVirtualMethod) { @@ -1440,6 +1453,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 { @@ -1474,6 +1491,100 @@ TEST(FunctionReflectionTest, JitCallAdvanced) { clang_Interpreter_dispose(I); } +#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST +#ifndef _WIN32 // Death tests do not work on Windows +TEST(FunctionReflectionTest, JitCallDebug) { +#ifdef EMSCRIPTEN +#if CLANG_VERSION_MAJOR < 20 + GTEST_SKIP() << "Test fails for Emscipten builds"; +#endif +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + + std::vector Decls, SubDecls; + std::string code = R"( + class C { + int x; + C() { + x = 12345; + } + };)"; + + std::vector interpreter_args = {"-include", "new", + "-debug-only=jitcall"}; + GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false, + interpreter_args); + + const auto* CtorD = Cpp::GetDefaultConstructor(Decls[0]); + auto JC = Cpp::MakeFunctionCallable(CtorD); + + EXPECT_TRUE(JC.getKind() == Cpp::JitCall::kConstructorCall); + EXPECT_DEATH( + { JC.InvokeConstructor(/*result=*/nullptr); }, + "Must pass the location of the created object!"); + + void* result = Cpp::Allocate(Decls[0]); + EXPECT_DEATH( + { JC.InvokeConstructor(&result, 0UL); }, + "Number of objects to construct should be atleast 1"); + + // Succeeds + JC.InvokeConstructor(&result, 5UL); + + Decls.clear(); + code = R"( + class C { + public: + int x; + C(int a) { + x = a; + } + ~C() {} + };)"; + + GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false, + interpreter_args); + GetAllSubDecls(Decls[0], SubDecls); + + EXPECT_TRUE(Cpp::IsConstructor(SubDecls[3])); + JC = Cpp::MakeFunctionCallable(SubDecls[3]); + EXPECT_TRUE(JC.getKind() == Cpp::JitCall::kConstructorCall); + + result = Cpp::Allocate(Decls[0], 5); + int i = 42; + void* args0[1] = {(void*)&i}; + EXPECT_DEATH( + { JC.InvokeConstructor(&result, 5UL, {args0, 1}); }, + "Cannot pass initialization parameters to array new construction"); + + JC.InvokeConstructor(&result, 1UL, {args0, 1}, (void*)~0); + + int* obj = reinterpret_cast(reinterpret_cast(result)); + EXPECT_TRUE(*obj == 42); + + // Destructors + Cpp::TCppScope_t scope_C = Cpp::GetNamed("C"); + Cpp::TCppObject_t object_C = Cpp::Construct(scope_C); + + // Make destructor callable and pass arguments + JC = Cpp::MakeFunctionCallable(SubDecls[4]); + EXPECT_DEATH( + { JC.Invoke(&object_C, {args0, 1}); }, + "Destructor called with arguments"); +} +#endif // _WIN32 +#endif + +template T instantiation_in_host() { return T(0); } +#if defined(_WIN32) +template __declspec(dllexport) int instantiation_in_host(); +#elif defined(__GNUC__) +template __attribute__((__visibility__("default"))) int +instantiation_in_host(); +#else +template int instantiation_in_host(); +#endif TEST(FunctionReflectionTest, GetFunctionCallWrapper) { #ifdef EMSCRIPTEN @@ -1728,6 +1839,136 @@ TEST(FunctionReflectionTest, GetFunctionCallWrapper) { Cpp::BestOverloadFunctionMatch(operators, {}, {K1, K2}); auto chrono_op_fn_callable = Cpp::MakeFunctionCallable(kop); EXPECT_EQ(chrono_op_fn_callable.getKind(), Cpp::JitCall::kGenericCall); + + Interp->process(R"( + namespace my_std { + template + struct pair { + T1 first; + T2 second; + pair(T1 fst, T2 snd) : first(fst), second(snd) {} + }; + + template + pair make_pair(T1 x, T2 y) { + return pair(x, y); + } + + template + struct __pair_get; + + template <> + struct __pair_get<0> { + template + static constexpr T1 __get(pair __pair) noexcept { + return __pair.first; + } + }; + + template <> + struct __pair_get<1> { + template + constexpr T2 __get(pair __pair) noexcept { + return __pair.second; + } + }; + + template + static constexpr auto get(pair __t) noexcept { + return __pair_get::__get(__t); + } + } // namespace my_std + + namespace libchemist { + namespace type { + template + class tensor {}; + } // namespace type + + template > + class CanonicalMO {}; + + template class CanonicalMO>; + + auto produce() { return my_std::make_pair(10., type::tensor{}); } + + } // namespace libchemist + + namespace property_types { + namespace type { + template + using canonical_mos = libchemist::CanonicalMO; + } + + auto produce() { return my_std::make_pair(5., type::canonical_mos{}); } + } // namespace property_types + + auto tmp = property_types::produce(); + auto &p = tmp; + )"); + + std::vector unresolved_candidate_methods; + Cpp::GetClassTemplatedMethods("get", Cpp::GetScope("my_std"), + unresolved_candidate_methods); + Cpp::TCppType_t p = Cpp::GetTypeFromScope(Cpp::GetNamed("p")); + EXPECT_TRUE(p); + + Cpp::TCppScope_t fn = Cpp::BestOverloadFunctionMatch( + unresolved_candidate_methods, {{Cpp::GetType("int"), "0"}}, {p}); + EXPECT_TRUE(fn); + + auto fn_callable = Cpp::MakeFunctionCallable(fn); + EXPECT_EQ(fn_callable.getKind(), Cpp::JitCall::kGenericCall); + + Interp->process(R"( + template + bool call_move(T&& t) { + return true; + } + )"); + + unresolved_candidate_methods.clear(); + Cpp::GetClassTemplatedMethods("call_move", Cpp::GetGlobalScope(), + unresolved_candidate_methods); + EXPECT_EQ(unresolved_candidate_methods.size(), 1); + + Cpp::TCppScope_t call_move = Cpp::BestOverloadFunctionMatch( + unresolved_candidate_methods, {}, + {Cpp::GetReferencedType(Cpp::GetType("int"), true)}); + EXPECT_TRUE(call_move); + + auto call_move_callable = Cpp::MakeFunctionCallable(call_move); + EXPECT_EQ(call_move_callable.getKind(), Cpp::JitCall::kGenericCall); + + // instantiation in host, no template function body + Interp->process("template T instantiation_in_host();"); + + unresolved_candidate_methods.clear(); + Cpp::GetClassTemplatedMethods("instantiation_in_host", Cpp::GetGlobalScope(), + unresolved_candidate_methods); + EXPECT_EQ(unresolved_candidate_methods.size(), 1); + + Cpp::TCppScope_t instantiation_in_host = Cpp::BestOverloadFunctionMatch( + unresolved_candidate_methods, {Cpp::GetType("int")}, {}); + EXPECT_TRUE(instantiation_in_host); + + Cpp::JitCall instantiation_in_host_callable = + Cpp::MakeFunctionCallable(instantiation_in_host); + EXPECT_EQ(instantiation_in_host_callable.getKind(), + Cpp::JitCall::kGenericCall); + + instantiation_in_host = Cpp::BestOverloadFunctionMatch( + unresolved_candidate_methods, {Cpp::GetType("double")}, {}); + EXPECT_TRUE(instantiation_in_host); + + Cpp::BeginStdStreamCapture(Cpp::CaptureStreamKind::kStdErr); + instantiation_in_host_callable = + Cpp::MakeFunctionCallable(instantiation_in_host); + std::string err_msg = Cpp::EndStdStreamCapture(); + EXPECT_TRUE(err_msg.find("instantiation with no body") != std::string::npos); + EXPECT_EQ(instantiation_in_host_callable.getKind(), + Cpp::JitCall::kUnknown); // expect to fail } TEST(FunctionReflectionTest, IsConstMethod) { @@ -1878,7 +2119,7 @@ TEST(FunctionReflectionTest, Construct) { testing::internal::CaptureStdout(); auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); auto scope_c = make_scope(static_cast(scope), I); - auto object_c = clang_construct(scope_c, nullptr); + auto object_c = clang_construct(scope_c, nullptr, 1UL); EXPECT_TRUE(object_c != nullptr); output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Constructor Executed"); @@ -1891,6 +2132,49 @@ TEST(FunctionReflectionTest, Construct) { clang_Interpreter_dispose(I); } +// Test zero initialization of PODs and default initialization cases +TEST(FunctionReflectionTest, ConstructPOD) { +#ifdef EMSCRIPTEN +#if CLANG_VERSION_MAJOR < 20 + GTEST_SKIP() << "Test fails for Emscipten builds"; +#endif +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + std::vector interpreter_args = {"-include", "new"}; + Cpp::CreateInterpreter(interpreter_args); + + Interp->declare(R"( + namespace PODS { + struct SomePOD_B { + int fInt; + }; + struct SomePOD_C { + int fInt; + double fDouble; + }; + })"); + + auto *ns = Cpp::GetNamed("PODS"); + Cpp::TCppScope_t scope = Cpp::GetNamed("SomePOD_B", ns); + EXPECT_TRUE(scope); + Cpp::TCppObject_t object = Cpp::Construct(scope); + EXPECT_TRUE(object != nullptr); + int* fInt = reinterpret_cast(reinterpret_cast(object)); + EXPECT_TRUE(*fInt == 0); + + scope = Cpp::GetNamed("SomePOD_C", ns); + EXPECT_TRUE(scope); + object = Cpp::Construct(scope); + EXPECT_TRUE(object); + auto* fDouble = + reinterpret_cast(reinterpret_cast(object) + sizeof(int)); + EXPECT_EQ(*fDouble, 0.0); +} + // Test nested constructor calls TEST(FunctionReflectionTest, ConstructNested) { #ifdef EMSCRIPTEN @@ -1954,6 +2238,61 @@ TEST(FunctionReflectionTest, ConstructNested) { output.clear(); } +TEST(FunctionReflectionTest, ConstructArray) { +#if defined(EMSCRIPTEN) + GTEST_SKIP() << "Test fails for Emscripten builds"; +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif +#if defined(__APPLE__) && (CLANG_VERSION_MAJOR == 16) + GTEST_SKIP() << "Test fails on Clang16 OS X"; +#endif + + Cpp::CreateInterpreter(); + + Interp->declare(R"( + #include + extern "C" int printf(const char*,...); + class C { + int x; + C() { + x = 42; + printf("\nConstructor Executed\n"); + } + }; + )"); + + Cpp::TCppScope_t scope = Cpp::GetNamed("C"); + std::string output; + + size_t a = 5; // Construct an array of 5 objects + void* where = Cpp::Allocate(scope, a); // operator new + + testing::internal::CaptureStdout(); + EXPECT_TRUE(where == Cpp::Construct(scope, where, a)); // placement new + // Check for the value of x which should be at the start of the object. + EXPECT_TRUE(*(int*)where == 42); + // Check for the value of x in the second object + int* obj = reinterpret_cast(reinterpret_cast(where) + + Cpp::SizeOf(scope)); + EXPECT_TRUE(*obj == 42); + + // Check for the value of x in the last object + obj = reinterpret_cast(reinterpret_cast(where) + + (Cpp::SizeOf(scope) * 4)); + EXPECT_TRUE(*obj == 42); + Cpp::Destruct(where, scope, /*withFree=*/false, 5); + Cpp::Deallocate(scope, where, 5); + output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, + "\nConstructor Executed\n\nConstructor Executed\n\nConstructor " + "Executed\n\nConstructor Executed\n\nConstructor Executed\n"); + output.clear(); +} + TEST(FunctionReflectionTest, Destruct) { #ifdef EMSCRIPTEN GTEST_SKIP() << "Test fails for Emscipten builds"; @@ -2001,7 +2340,7 @@ TEST(FunctionReflectionTest, Destruct) { testing::internal::CaptureStdout(); auto* I = clang_createInterpreterFromRawPtr(Cpp::GetInterpreter()); auto scope_c = make_scope(static_cast(scope), I); - auto object_c = clang_construct(scope_c, nullptr); + auto object_c = clang_construct(scope_c, nullptr, 1UL); clang_destruct(object_c, scope_c, true); output = testing::internal::GetCapturedStdout(); EXPECT_EQ(output, "Destructor Executed"); @@ -2011,6 +2350,85 @@ TEST(FunctionReflectionTest, Destruct) { clang_Interpreter_dispose(I); } +TEST(FunctionReflectionTest, DestructArray) { +#ifdef EMSCRIPTEN + GTEST_SKIP() << "Test fails for Emscipten builds"; +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif +#if defined(__APPLE__) && (CLANG_VERSION_MAJOR == 16) + GTEST_SKIP() << "Test fails on Clang16 OS X"; +#endif + + std::vector interpreter_args = {"-include", "new"}; + Cpp::CreateInterpreter(interpreter_args); + + Interp->declare(R"( + #include + extern "C" int printf(const char*,...); + class C { + int x; + C() { + printf("\nCtor Executed\n"); + x = 42; + } + ~C() { + printf("\nDestructor Executed\n"); + } + }; + )"); + + Cpp::TCppScope_t scope = Cpp::GetNamed("C"); + std::string output; + + size_t a = 5; // Construct an array of 5 objects + void* where = Cpp::Allocate(scope, a); // operator new + EXPECT_TRUE(where == Cpp::Construct(scope, where, a)); // placement new + + // verify the array of objects has been constructed + int* obj = reinterpret_cast(reinterpret_cast(where) + + Cpp::SizeOf(scope) * 4); + EXPECT_TRUE(*obj == 42); + + testing::internal::CaptureStdout(); + // destruct 3 out of 5 objects + Cpp::Destruct(where, scope, false, 3); + output = testing::internal::GetCapturedStdout(); + + EXPECT_EQ( + output, + "\nDestructor Executed\n\nDestructor Executed\n\nDestructor Executed\n"); + output.clear(); + testing::internal::CaptureStdout(); + + // destruct the rest + auto *new_head = reinterpret_cast(reinterpret_cast(where) + + (Cpp::SizeOf(scope) * 3)); + Cpp::Destruct(new_head, scope, false, 2); + + output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "\nDestructor Executed\n\nDestructor Executed\n"); + output.clear(); + + // deallocate since we call the destructor withFree = false + Cpp::Deallocate(scope, where, 5); + + // perform the same withFree=true + where = Cpp::Allocate(scope, a); + EXPECT_TRUE(where == Cpp::Construct(scope, where, a)); + testing::internal::CaptureStdout(); + // FIXME : This should work with the array of objects as well + // Cpp::Destruct(where, scope, true, 5); + Cpp::Destruct(where, scope, true); + output = testing::internal::GetCapturedStdout(); + EXPECT_EQ(output, "\nDestructor Executed\n"); + output.clear(); +} + TEST(FunctionReflectionTest, UndoTest) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; @@ -2037,3 +2455,38 @@ TEST(FunctionReflectionTest, UndoTest) { #endif #endif } + +TEST(FunctionReflectionTest, FailingTest1) { +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif +#ifdef EMSCRIPTEN + GTEST_SKIP() << "Test fails for Emscipten builds"; +#endif + Cpp::CreateInterpreter(); + EXPECT_FALSE(Cpp::Declare(R"( + class WithOutEqualOp1 {}; + class WithOutEqualOp2 {}; + + WithOutEqualOp1 o1; + WithOutEqualOp2 o2; + + template + bool is_equal(const C1& c1, const C2& c2) { return (bool)(c1 == c2); } + )")); + + Cpp::TCppType_t o1 = Cpp::GetTypeFromScope(Cpp::GetNamed("o1")); + Cpp::TCppType_t o2 = Cpp::GetTypeFromScope(Cpp::GetNamed("o2")); + std::vector fns; + Cpp::GetClassTemplatedMethods("is_equal", Cpp::GetGlobalScope(), fns); + EXPECT_EQ(fns.size(), 1); + + Cpp::TemplateArgInfo args[2] = {{o1}, {o2}}; + Cpp::TCppScope_t fn = Cpp::InstantiateTemplate(fns[0], args, 2); + EXPECT_TRUE(fn); + + Cpp::JitCall jit_call = Cpp::MakeFunctionCallable(fn); + EXPECT_EQ(jit_call.getKind(), Cpp::JitCall::kUnknown); // expected to fail + EXPECT_FALSE(Cpp::Declare("int x = 1;")); + EXPECT_FALSE(Cpp::Declare("int y = x;")); +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp index 008ea01e78d8d..841913c54f446 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp +++ b/interpreter/CppInterOp/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" @@ -80,6 +80,47 @@ TEST(InterpreterTest, Evaluate) { EXPECT_FALSE(HadError) ; } +TEST(InterpreterTest, DeleteInterpreter) { + auto* I1 = Cpp::CreateInterpreter(); + auto* I2 = Cpp::CreateInterpreter(); + auto* I3 = Cpp::CreateInterpreter(); + EXPECT_TRUE(I1 && I2 && I3) << "Failed to create interpreters"; + + EXPECT_EQ(I3, Cpp::GetInterpreter()) << "I3 is not active"; + + EXPECT_TRUE(Cpp::DeleteInterpreter(nullptr)); + EXPECT_EQ(I2, Cpp::GetInterpreter()); + + auto* I4 = reinterpret_cast(static_cast(~0U)); + EXPECT_FALSE(Cpp::DeleteInterpreter(I4)); + + EXPECT_TRUE(Cpp::DeleteInterpreter(I1)); + EXPECT_EQ(I2, Cpp::GetInterpreter()) << "I2 is not active"; +} + +TEST(InterpreterTest, ActivateInterpreter) { + EXPECT_FALSE(Cpp::ActivateInterpreter(nullptr)); + auto* Cpp14 = Cpp::CreateInterpreter({"-std=c++14"}); + auto* Cpp17 = Cpp::CreateInterpreter({"-std=c++17"}); + auto* Cpp20 = Cpp::CreateInterpreter({"-std=c++20"}); + + EXPECT_TRUE(Cpp14 && Cpp17 && Cpp20); + EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 202002L) + << "Failed to activate C++20"; + + auto* UntrackedI = reinterpret_cast(static_cast(~0U)); + EXPECT_FALSE(Cpp::ActivateInterpreter(UntrackedI)); + + EXPECT_TRUE(Cpp::ActivateInterpreter(Cpp14)); + EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 201402L); + + Cpp::DeleteInterpreter(Cpp14); + EXPECT_EQ(Cpp::GetInterpreter(), Cpp20); + + EXPECT_TRUE(Cpp::ActivateInterpreter(Cpp17)); + EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 201703L); +} + TEST(InterpreterTest, Process) { #ifdef _WIN32 GTEST_SKIP() << "Disabled on Windows. Needs fixing."; @@ -221,7 +262,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 +278,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/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp index b9c2a75c2b975..a1b6909b7d56c 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/JitTest.cpp +++ b/interpreter/CppInterOp/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/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp index 0b3908085980a..a62beaa42ca82 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/interpreter/CppInterOp/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" @@ -789,7 +789,10 @@ TEST(ScopeReflectionTest, GetAllCppNames) { } )"; - GetAllTopLevelDecls(code, Decls); + std::vector interpreter_args = {"-Wno-inaccessible-base"}; + + GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false, + interpreter_args); auto test_get_all_cpp_names = [](Decl* D, const std::vector& truth_names) { @@ -859,14 +862,10 @@ template constexpr T pi = T(3.1415926535897932385L); auto* VD = cast((Decl*)Instance1); VarTemplateDecl* VDTD1 = VD->getSpecializedTemplate(); EXPECT_TRUE(VDTD1->isThisDeclarationADefinition()); -#if CLANG_VERSION_MAJOR > 13 #if CLANG_VERSION_MAJOR <= 18 TemplateArgument TA1 = (*VD->getTemplateArgsInfo())[0].getArgument(); #else TemplateArgument TA1 = (*VD->getTemplateArgsAsWritten())[0].getArgument(); -#endif // CLANG_VERSION_MAJOR -#else - TemplateArgument TA1 = VD->getTemplateArgsInfo()[0].getArgument(); #endif // CLANG_VERSION_MAJOR EXPECT_TRUE(TA1.getAsType()->isIntegerType()); } diff --git a/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp index a3e482596de22..8a5f7e004f63a 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/TypeReflectionTest.cpp +++ b/interpreter/CppInterOp/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" @@ -610,3 +611,11 @@ TEST(TypeReflectionTest, IsFunctionPointerType) { EXPECT_FALSE( Cpp::IsFunctionPointerType(Cpp::GetVariableType(Cpp::GetNamed("i")))); } + +TEST(TypeReflectionTest, OperatorSpelling) { + EXPECT_EQ(Cpp::GetSpellingFromOperator(Cpp::OP_Less), "<"); + EXPECT_EQ(Cpp::GetSpellingFromOperator(Cpp::OP_Plus), "+"); + EXPECT_EQ(Cpp::GetOperatorFromSpelling("->"), Cpp::OP_Arrow); + EXPECT_EQ(Cpp::GetOperatorFromSpelling("()"), Cpp::OP_Call); + EXPECT_EQ(Cpp::GetOperatorFromSpelling("invalid"), Cpp::OP_None); +} diff --git a/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp b/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp index 4daf18c35da64..e048b0df5a02c 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp +++ b/interpreter/CppInterOp/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/interpreter/CppInterOp/unittests/CppInterOp/Utils.h b/interpreter/CppInterOp/unittests/CppInterOp/Utils.h index fb79ee804199f..2b7b12590cc05 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/Utils.h +++ b/interpreter/CppInterOp/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/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp index 571ae5cd5dc25..b9baf6c55aaa2 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/VariableReflectionTest.cpp +++ b/interpreter/CppInterOp/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" @@ -676,4 +677,33 @@ TEST(VariableReflectionTest, Is_Get_Reference) { Cpp::GetVariableType(Decls[5])); EXPECT_FALSE(Cpp::GetNonReferenceType(Cpp::GetVariableType(Decls[5]))); + + EXPECT_TRUE(Cpp::IsLValueReferenceType(Cpp::GetVariableType(Decls[2]))); + EXPECT_EQ(Cpp::GetReferencedType(Cpp::GetVariableType(Decls[1])), + Cpp::GetVariableType(Decls[2])); + EXPECT_TRUE(Cpp::IsRValueReferenceType( + Cpp::GetReferencedType(Cpp::GetVariableType(Decls[1]), true))); +} + +TEST(VariableReflectionTest, GetPointerType) { + Cpp::CreateInterpreter(); + std::vector Decls; + std::string code = R"( + class A {}; + int a; + int *b = &a; + double c; + double *d = &c; + A e; + A *f = &e; + )"; + + GetAllTopLevelDecls(code, Decls); + + EXPECT_EQ(Cpp::GetPointerType(Cpp::GetVariableType(Decls[1])), + Cpp::GetVariableType(Decls[2])); + EXPECT_EQ(Cpp::GetPointerType(Cpp::GetVariableType(Decls[3])), + Cpp::GetVariableType(Decls[4])); + EXPECT_EQ(Cpp::GetPointerType(Cpp::GetVariableType(Decls[5])), + Cpp::GetVariableType(Decls[6])); }