Clang模糊解析C ++

用它现有的libclang API用clang解析C ++的不完整声明是否完全可能? 即解析.cpp文件而不包含所有标题,即时推断声明。 所以,例如下面的文字:

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

将检测未知的符号A,呼叫我的回调,它使用我的魔法启发式来扣除A是一个类,然后以与B和Foo相同的方式调用此回调。 最后,我希望能够推断出我看到一个B类的成员Foo返回A,而东西是一个函数..或者是这个效果。 上下文:我想看看我是否能够快速地解析所有的头文件,从而实现明智的语法突出显示和动态代码分析。

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

C ++语法充满了上下文依赖关系。 Foo()是函数调用还是Foo类的临时构造? 是Foo <Bar>的东西; 一个模板Foo <Bar>实例化和声明可变的东西,还是看起来很奇怪2调用重载操作符<和操作符>? 只能在上下文中进行判断,上下文通常来自解析头文件。

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

因此,问题在于是否可以在每次遇到未知符号时使用Clang API来回调,并使用我自己的非C ++启发式方法给出答案。 如果我的启发式失败,那么显然,解析失败。 我不是在谈论解析Boost库:)我谈论的是非常简单的C ++,可能没有模板,只限于在这种情况下clang可以处理的最小值。


我认为另一种解决方案将比模糊解析更适合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类来自于将未知符号变为可变的项目(因此注释和代码)。


除非您严格限制人们被允许编写的代码,否则在解析所有头文件的情况下解析C ++(以及因此语法突出显示超出关键字/正则表达式)基本上是不可能的。 预处理器特别擅长为你搞砸。

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


我知道这个问题相当古老,但看看这里:

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

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

我只对模糊解析部分感兴趣,并将其分叉在我的github页面上,我修复了几个小问题并使该工具具有自主性(可以在clang的源树之外编译)。 不要试图用C ++ 14(G ++ 6的默认模式)进行编译,因为make_unique会有冲突。

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

链接地址: http://www.djcxy.com/p/94569.html

上一篇: Clang for fuzzy parsing C++

下一篇: Passing a class object as an argument in C++