在 macOS 上使用 libClang 解析 C++ 代码时查找标准库头文件时出错

问题描述 投票:0回答:1

我正在开发一个使用 Clang 库来解析 C++ 源代码并进一步静态分析其 AST 的项目。 在 Linux 上一切都很好(该项目特别支持 Ubuntu 发行版),但是最近也将支持 macOS 添加到了开发计划中。

但是,在 macOS 上构建我们的工具并将其链接到 Clang 库(从 HomeBrew 安装)后,由于解决标准库头的包含语句时出现一些奇怪的问题,解析 C++ 源代码失败。

作为一个简单的测试,假设我们想要解析 tinyxml2 项目,它是一个用 C++ 编写的简单 XML 解析器,仅包含 2 个翻译单元。由于它使用 CMake 作为构建系统,因此可以轻松生成

compile_commands.json
文件,内容如下:

[
{
  "directory": "<path to project dir>/tinyxml2/build",
  "command": "/Library/Developer/CommandLineTools/usr/bin/c++  -I<path to project dir>/tinyxml2  -arch arm64 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX14.0.sdk -mmacosx-version-min=13.5 -fvisibility=hidden -fvisibility-inlines-hidden -o CMakeFiles/tinyxml2.dir/tinyxml2.cpp.o -c /Users/mate/Documents/cc/projects/tinyxml2/tinyxml2.cpp",
  "file": "<path to project dir>/tinyxml2/tinyxml2.cpp",
  "output": "CMakeFiles/tinyxml2.dir/tinyxml2.cpp.o"
},
{
  "directory": "<path to project dir>/tinyxml2/build",
  "command": "/Library/Developer/CommandLineTools/usr/bin/c++  -I<path to project dir>/tinyxml2  -arch arm64 -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX14.0.sdk -mmacosx-version-min=13.5 -fvisibility=hidden -fvisibility-inlines-hidden -o CMakeFiles/xmltest.dir/xmltest.cpp.o -c <path to project dir>tinyxml2/xmltest.cpp",
  "file": "<path to project dir>/tinyxml2/xmltest.cpp",
  "output": "CMakeFiles/xmltest.dir/xmltest.cpp.o"
}
]

直接从此文件执行命令会成功并正确构建相应的目标文件。

但是,当使用同样的

compile_commands.json
来参数化libClang时,会出现以下奇怪的错误:

In file included from <path to project dir>/tinyxml2/tinyxml2.cpp:24:
<path to project dir>/tinyxml2/tinyxml2.h:37:13: fatal error: cannot open file '<path to project dir>/tinyxml2/cctype': No such file or directory
#   include <cctype>

由于某种原因,在项目的文件夹中搜索

cctype
系统标头,尽管
-isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX14.0.sdk
标志已被传递并且它包含正确的路径。

更奇怪的是,当我删除

-I<path to project dir>/tinyxml2
标志时,找到了
cctype
系统标头,但出现了另一个错误:

In file included from <path to project dir>/tinyxml2/tinyxml2.cpp:24:
In file included from <path to project dir>/tinyxml2/tinyxml2.h:37:
In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/cctype:37:
In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/__assert:13:
/Library/Developer/CommandLineTools/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/__config:76:21: fatal error: cannot open file '/Library/Developer/CommandLineTools/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/RTK_device_methods.h': No such file or directory
#  if __has_include(<RTK_device_methods.h>) && defined(_LIBCPP_HAS_NO_RANDOM_DEVICE) && defined(_LIBCPP_HAS_NO_LOCALIZATION)

我在 macOS 上的工作经验有限,因此欢迎任何帮助或建议! 在 Linux 上工作时不会出现此类问题。我还尝试确保

compile_commands.json
中使用相同版本的 Clang,该版本与我们的工具链接,但仍然存在相同的问题。 (所以使用
/opt/homebrew/opt/llvm@11/bin/clang++
而不是
/Library/Developer/CommandLineTools/usr/bin/c++
。)

我已经成功在两台不同的 Mac 上重现了这个问题 - 如果这很重要的话,两台 Mac 都有 ARM 处理器。

我从我们的项目中制作了一个 MWE(见下文),它可以重现这种行为。这是一个简单的 CMake 项目,它将生成一个名为

parse_test
的可执行二进制文件。 然后你就可以像
./parse_test <path>/compile_commands.json
一样测试它。

CMakeLists.txt
的内容:

cmake_minimum_required(VERSION 3.16.3)
project(mwe-clang-apple)

# Set CXX standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Find LLVM
find_package(LLVM REQUIRED CONFIG)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")

# Check whether RTTI is on for LLVM
message(STATUS "Using RTTI for LLVM: ${LLVM_ENABLE_RTTI}")
if(NOT LLVM_ENABLE_RTTI)
    message(SEND_ERROR "RTTI is required for LLVM")
endif()

# Check LLVM version
if (${LLVM_PACKAGE_VERSION} VERSION_LESS "10")
    message(SEND_ERROR "Loaded LLVM version must be at least 10.0, ${LLVM_PACKAGE_VERSION} found.")
endif()

# Find Clang
find_package(Clang REQUIRED CONFIG)
message(STATUS "Using ClangConfig.cmake in: ${Clang_DIR}")

include_directories(SYSTEM
  ${LLVM_INCLUDE_DIRS}
  ${CLANG_INCLUDE_DIRS})

link_directories(${LLVM_LIBRARY_DIRS})

add_definitions(${LLVM_DEFINITIONS})

add_executable(parse_test
    main.cpp)

target_link_libraries(parse_test
        clangTooling
        clangFrontend
        clang)

if(APPLE)
    # Use Linux-like linking behaviour, as we reference headers from Clang without implementation
    set_target_properties(parse_test PROPERTIES LINK_FLAGS
            "-undefined dynamic_lookup")
endif(APPLE)

main.cpp
的内容:

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <filesystem>

#include <clang/Tooling/JSONCompilationDatabase.h>
#include <clang/Tooling/Tooling.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/FrontendActions.h>

namespace fs = std::filesystem;

int parseWorker(const clang::tooling::CompileCommand& command_);

int main(int argc, char* argv[])
{
    if (argc < 2) {
        std::cout << "Pass the path to the JSON Compilation Database as an argument!" << std::endl;
        return 1;
    }

    std::string jsonFile_ = std::string(argv[1]);
    std::string errorMsg;

    std::unique_ptr<clang::tooling::JSONCompilationDatabase> compDb
            = clang::tooling::JSONCompilationDatabase::loadFromFile(
                    jsonFile_, errorMsg,
                    clang::tooling::JSONCommandLineSyntax::Gnu);

    if (!errorMsg.empty())
    {
        std::cout << "Error: " << errorMsg << std::endl;
        return 1;
    }

    //--- Read the compilation commands compile database ---//

    std::vector<clang::tooling::CompileCommand> compileCommands =
            compDb->getAllCompileCommands();

    for (const auto& command : compileCommands)
    {
        std::cout << " Parsing " << command.Filename << std::endl;

        int error = parseWorker(command);

        if (error)
            std::cout << " Parsing " << command.Filename << " has been failed." << std::endl;
    }

   return 0;
}

int parseWorker(const clang::tooling::CompileCommand& command_)
{
    //--- Assemble compiler command line ---//

    std::vector<const char*> commandLine;
    commandLine.reserve(command_.CommandLine.size());
    commandLine.push_back("--");
    std::transform(
            command_.CommandLine.begin() + 1, // Skip compiler name
            command_.CommandLine.end(),
            std::back_inserter(commandLine),
            [](const std::string& s){ return s.c_str(); });

    int argc = commandLine.size();

    std::string compilationDbLoadError;
    std::unique_ptr<clang::tooling::FixedCompilationDatabase> compilationDb(
            clang::tooling::FixedCompilationDatabase::loadFromCommandLine(
                    argc,
                    commandLine.data(),
                    compilationDbLoadError));

    if (!compilationDb)
    {
        std::cout
                << "Failed to create compilation database from command-line. "
                << compilationDbLoadError
                << std::endl;
        return 1;
    }

    //--- Start the tool ---//

    fs::path sourceFullPath(command_.Filename);
    if (!sourceFullPath.is_absolute())
        sourceFullPath = fs::path(command_.Directory) / command_.Filename;

    clang::tooling::ClangTool tool(*compilationDb, sourceFullPath.string());

    int error = tool.run(clang::tooling::newFrontendActionFactory<clang::SyntaxOnlyAction>().get());

    return error;
}
c++ clang static-analysis libclang
1个回答
0
投票

不幸的是,在我看来,苹果已经开始与任何想要使用 XCode 和他们提供的东西以外的东西进行开发的人进行全面战争。

我已经成为苹果用户近 30 年了,他们以所谓的安全性为名,系统性地让在普通 *nix 系统上做起来如此简单的事情变得越来越困难。

我最终用 -v 编译了一个源文件...

clang++ -v -g3 -std=c++20 foo.cpp -c

然后获取该输出并将所有包含文件添加到我的命令行中,然后系统地删除它们,直到它满足我需要的最低限度。

我最终将其作为传递给应用程序的额外参数,然后将它们作为参数转发给 libclang 函数来解析文件。

 xtra_incs=-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
    -isystem /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1 \
    -isystem /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/15.0.0/include
© www.soinside.com 2019 - 2024. All rights reserved.