22

是否有可能使用其现有的 libclang API 使用 clang 解析具有不完整声明的 C++?即在不包括所有标题的情况下解析.cpp 文件,即时推断声明。所以,例如以下文本:

A B::Foo(){return stuff();}

将检测未知符号 A,调用我的回调,使用我的魔法启发式扣除 A 是一个类,然后以与 B 和 Foo 等相同的方式调用此回调。最后,我希望能够推断出我看到 B 类的成员 Foo 返回 A,而 stuff 是一个函数。或者类似的东西。上下文:我想看看我是否可以在不快速解析所有标题的情况下进行合理的语法突出显示和动态代码分析。

[编辑] 为了澄清,我正在寻找非常严格的 C++ 解析,可能有一些启发式来解除一些限制。

C++ 语法充满了上下文依赖。Foo() 是函数调用还是类 Foo 的临时构造?是 Foo<Bar> 的东西;模板 Foo<Bar> 实例化和声明变量,还是看起来很奇怪的 2 次重载 operator < 和 operator > 调用?只能在上下文中判断,而上下文通常来自解析标题。

我正在寻找的是一种插入我的自定义约定规则的方法。例如,我知道我不会重载 Win32 符号,所以我可以放心地假设 CreateFile始终是一个函数,我什至知道它的签名。我也知道我所有的类都以大写字母开头并且是名词,而函数通常是动词,所以我可以合理地猜测 Foo 和 Bar 是类名。在更复杂的场景中,我知道我不会编写像 a < b > c 这样的无副作用表达式;所以我可以假设 a 总是一个模板实例化。等等。

所以,问题是每次遇到未知符号时是否可以使用 Clang API 进行回调,并使用我自己的非 C++ 启发式给出答案。如果我的启发式失败,那么解析失败,很明显。而且我不是在谈论解析 Boost 库:) 我说的是非常简单的 C++,可能没有模板,在这种情况下仅限于 clang 可以处理的最低限度。

4

4 回答 4

6

我知道这个问题已经相当老了,但请看这里

LibFuzzy 是一个基于 Clang 的 Lexer 启发式解析 C++ 的库。模糊解析器是容错的,在不了解构建系统和不完整的源文件的情况下工作。由于解析器必须进行猜测,因此生成的语法树可能部分错误。

它是 clang-highlight 的一个子项目,一个似乎不再开发的(实验性?)工具。

我只对模糊解析部分感兴趣,并在我的 github 页面上对其进行了分叉,在那里我修复了几个小问题并使工具自治(它可以在 clang 的源代码树之外编译)。不要尝试用 C++14(这是 G++ 6 的默认模式)编译它,因为会与make_unique.

根据这个页面,clang-format 有自己的模糊解析器(并且正在积极开发),但解析器(是?)与该工具更紧密地耦合。

于 2016-05-13T11:33:39.967 回答
5

除非您严格限制允许人们编写的代码,否则基本上不可能在不解析所有标题的情况下很好地解析 C++(因此语法高亮超出关键字/正则表达式)。预处理器特别擅长为您解决问题。

关于模糊解析的困难(在视觉工作室的上下文中)有一些想法,这可能是有趣的:http: //blogs.msdn.com/b/vcblog/archive/2011/03/03/10136696.aspx

于 2012-06-21T07:48:12.500 回答
4

我认为另一种解决方案更适合 OP 而不是模糊解析。

解析时,clang 通过分析器的Sema部分维护语义信息。当遇到未知符号时,Sema将回退到ExternalSemaSource以获取有关此符号的一些信息。通过这个,你可以实现你想要的。

这是一个如何设置它的快速示例。它并不完全起作用(我没有在LookupUnqualified方法中做任何事情),您可能需要做进一步的调查,我认为这是一个好的开始。

// Declares clang::SyntaxOnlyAction.
#include <clang/Frontend/FrontendActions.h>
#include <clang/Tooling/CommonOptionsParser.h>
#include <clang/Tooling/Tooling.h>
#include <llvm/Support/CommandLine.h>
#include <clang/AST/AST.h>
#include <clang/AST/ASTConsumer.h>
#include <clang/AST/RecursiveASTVisitor.h>
#include <clang/Frontend/ASTConsumers.h>
#include <clang/Frontend/FrontendActions.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Tooling/CommonOptionsParser.h>
#include <clang/Tooling/Tooling.h>
#include <clang/Rewrite/Core/Rewriter.h>
#include <llvm/Support/raw_ostream.h>
#include <clang/Sema/ExternalSemaSource.h>
#include <clang/Sema/Sema.h>
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Parse/Parser.h"
#include "clang/Parse/ParseAST.h"
#include <clang/Sema/Lookup.h>

#include <iostream>
using namespace clang;
using namespace clang::tooling;
using namespace llvm;

class ExampleVisitor : public RecursiveASTVisitor<ExampleVisitor> {
private:
  ASTContext *astContext;

public:
  explicit ExampleVisitor(CompilerInstance *CI, StringRef file)
      : astContext(&(CI->getASTContext())) {}

  virtual bool VisitVarDecl(VarDecl *d) {
    std::cout << d->getNameAsString() << "@\n";
    return true;
  }
};

class ExampleASTConsumer : public ASTConsumer {
private:
  ExampleVisitor visitor;

public:
  explicit ExampleASTConsumer(CompilerInstance *CI, StringRef file)
      : visitor(CI, file) {}
  virtual void HandleTranslationUnit(ASTContext &Context) {
    // de cette façon, on applique le visiteur sur l'ensemble de la translation
    // unit
    visitor.TraverseDecl(Context.getTranslationUnitDecl());
  }
};

class DynamicIDHandler : public clang::ExternalSemaSource {
public:
  DynamicIDHandler(clang::Sema *Sema)
      : m_Sema(Sema), m_Context(Sema->getASTContext()) {}
  ~DynamicIDHandler() = default;

  /// \brief Provides last resort lookup for failed unqualified lookups
  ///
  /// If there is failed lookup, tell sema to create an artificial declaration
  /// which is of dependent type. So the lookup result is marked as dependent
  /// and the diagnostics are suppressed. After that is's an interpreter's
  /// responsibility to fix all these fake declarations and lookups.
  /// It is done by the DynamicExprTransformer.
  ///
  /// @param[out] R The recovered symbol.
  /// @param[in] S The scope in which the lookup failed.
  virtual bool LookupUnqualified(clang::LookupResult &R, clang::Scope *S) {
     DeclarationName Name = R.getLookupName();
     std::cout << Name.getAsString() << "\n";
    // IdentifierInfo *II = Name.getAsIdentifierInfo();
    // SourceLocation Loc = R.getNameLoc();
    // VarDecl *Result =
    //     // VarDecl::Create(m_Context, R.getSema().getFunctionLevelDeclContext(),
    //     //                 Loc, Loc, II, m_Context.DependentTy,
    //     //                 /*TypeSourceInfo*/ 0, SC_None, SC_None);
    // if (Result) {
    //   R.addDecl(Result);
    //   // Say that we can handle the situation. Clang should try to recover
    //   return true;
    // } else{
    //   return false;
    // }
    return false;
  }

private:
  clang::Sema *m_Sema;
  clang::ASTContext &m_Context;
};

// *****************************************************************************/

LangOptions getFormattingLangOpts(bool Cpp03 = false) {
  LangOptions LangOpts;
  LangOpts.CPlusPlus = 1;
  LangOpts.CPlusPlus11 = Cpp03 ? 0 : 1;
  LangOpts.CPlusPlus14 = Cpp03 ? 0 : 1;
  LangOpts.LineComment = 1;
  LangOpts.Bool = 1;
  LangOpts.ObjC1 = 1;
  LangOpts.ObjC2 = 1;
  return LangOpts;
}

int main() {
  using clang::CompilerInstance;
  using clang::TargetOptions;
  using clang::TargetInfo;
  using clang::FileEntry;
  using clang::Token;
  using clang::ASTContext;
  using clang::ASTConsumer;
  using clang::Parser;
  using clang::DiagnosticOptions;
  using clang::TextDiagnosticPrinter;

  CompilerInstance ci;
  ci.getLangOpts() = getFormattingLangOpts(false);
  DiagnosticOptions diagnosticOptions;
  ci.createDiagnostics();

  std::shared_ptr<clang::TargetOptions> pto = std::make_shared<clang::TargetOptions>();
  pto->Triple = llvm::sys::getDefaultTargetTriple();

  TargetInfo *pti = TargetInfo::CreateTargetInfo(ci.getDiagnostics(), pto);

  ci.setTarget(pti);
  ci.createFileManager();
  ci.createSourceManager(ci.getFileManager());
  ci.createPreprocessor(clang::TU_Complete);
  ci.getPreprocessorOpts().UsePredefines = false;
  ci.createASTContext();

  ci.setASTConsumer(
      llvm::make_unique<ExampleASTConsumer>(&ci, "../src/test.cpp"));

  ci.createSema(TU_Complete, nullptr);
  auto &sema = ci.getSema();
  sema.Initialize();
  DynamicIDHandler handler(&sema);
  sema.addExternalSource(&handler);

  const FileEntry *pFile = ci.getFileManager().getFile("../src/test.cpp");
  ci.getSourceManager().setMainFileID(ci.getSourceManager().createFileID(
      pFile, clang::SourceLocation(), clang::SrcMgr::C_User));
  ci.getDiagnosticClient().BeginSourceFile(ci.getLangOpts(),
                                           &ci.getPreprocessor());
  clang::ParseAST(sema,true,false);
  ci.getDiagnosticClient().EndSourceFile();

  return 0;
}

这个想法和DynamicIDHandler类来自未知符号是可变的项目(因此是注释和代码)。

于 2016-05-20T08:02:18.910 回答
1

OP 不想要“模糊解析”。他想要的是对 C++ 源代码进行完全上下文无关的解析,而不需要任何名称和类型解析。他计划根据解析结果对类型进行有根据的猜测。

Clang 正确的缠结解析和名称/类型解析,这意味着它在解析时必须具有所有可用的背景类型信息。其他答案表明 LibFuzzy 会产生不正确的解析树,以及一些用于 clang 格式的模糊解析器,我对此一无所知。如果坚持生成经典的 AST,那么这些解决方案都不会在面对模棱两可的解析时生成“正确”的树。

我们的 DMS Software Reengineering Toolkit 及其 C++ 前端可以在没有类型信息的情况下解析 C++ 源代码,并生成准确的“AST”;这些实际上是抽象语法 dag,其中树中的分叉表示根据语言精确语法(歧义(子)解析)对源代码的不同可能解释。

Clang 试图做的是通过在解析时使用类型信息来避免产生这些多个子解析。DMS所做的是产生歧义解析,并在(可选)解析后(属性-语法-评估)传递中,收集符号表信息并消除与类型不一致的子解析;对于格式良好的程序,这会产生一个没有歧义的普通 AST。

如果 OP 想要对类型信息进行启发式猜测,他将需要知道这些可能的解释。如果提前被淘汰,他就无法直截了当地猜测可能需要哪些类型。一个有趣的可能性是修改属性语法(作为 DMS 的 C++ 前端的一部分以源代码形式提供)的想法,它已经知道所有 C++ 类型规则,以使用部分信息执行此操作。鉴于它必须从标准中了解大约 600 页的神秘名称和类型解析规则,这将是从头开始构建启发式分析器的一个巨大的开端。

您可以看到 DMS 解析器生成的 (dag) 示例。

于 2016-05-13T13:51:11.643 回答