2012-06-21 8 views
15

Ist es überhaupt möglich, C++ mit unvollständigen Deklarationen mit Clong mit seiner vorhandenen libclang API zu analysieren? I.e. Analysieren Sie die .cpp-Datei, ohne alle Header einzubeziehen, und schließen Sie die Deklarationen direkt ab. so z.B. Der folgende Text:Clang für Fuzzy-Analyse C++

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

Werden unbekanntes Symbol A erkennt, meinen Rückruf anrufen, die abzieht A ist eine Klasse, meine Magie Heuristik, dann rufen Sie diese auf die gleiche Weise mit B und Foo und Sachen Rückruf. Am Ende möchte ich schließen können, dass ich ein Mitglied Foo der Klasse B gesehen habe, das A zurückgibt, und das ist eine Funktion. Oder etwas in diesem Sinne. Kontext: Ich möchte sehen, ob ich sinnvolle Syntaxhervorhebung und on-the-fly-Code-Analyse tun kann, ohne alle Header sehr schnell zu analysieren.

[EDIT] Um zu klären, ich suche sehr stark eingeschränkt C++ Parsing, möglicherweise mit einigen Heuristik, einige der Einschränkungen zu heben.

C++ - Grammatik ist voll von Kontextabhängigkeiten. Ist Foo() ein Funktionsaufruf oder eine Konstruktion eines Temporären der Klasse Foo? Ist Foo <Bar> Zeug; eine Vorlage Foo <Bar> Instanziierung und Deklaration von Variablen Zeug, oder ist es komisch aussehende 2 Anrufe zu überladenen Operator < und Operator>? Es ist nur möglich, im Kontext zu sagen, und der Kontext kommt oft von der Analyse der Header.

Was ich suche, ist eine Möglichkeit, meine benutzerdefinierten Regeln zu verbinden. Z.B. Ich weiß, dass ich Win32-Symbole nicht überlasten, also kann ich sicher annehmen, dass CreateFile immer eine Funktion ist, und ich kenne sogar seine Signatur. Ich weiß auch, dass alle meine Klassen mit einem Großbuchstaben beginnen und Substantive sind, und Funktionen sind normalerweise Verben, so dass ich vernünftig raten kann, dass Foo und Bar Klassennamen sind. In einem komplexeren Szenario weiß ich, dass ich keine side-effekt-freien Ausdrücke wie < b> c schreibe; also kann ich annehmen, dass a immer eine Template Instanziierung ist. Und so weiter.

Also die Frage ist, ob es möglich ist, Clang API zu verwenden, um jedes Mal zurückzurufen, wenn es auf ein unbekanntes Symbol trifft, und es eine Antwort zu geben, die meine eigene Nicht-C++ - Heuristik verwendet. Wenn meine Heuristik fehlschlägt, dann scheitert offensichtlich das Parsen. Und ich spreche nicht von Parsing Boost-Bibliothek :) Ich spreche über sehr einfache C++, wahrscheinlich ohne Vorlagen, auf ein Minimum beschränkt, das in diesem Fall Clam umgehen kann.

+0

Sie können CLang jederzeit direkt ändern. Ich bin nicht sicher, wie leicht es wäre, da es viele Male gibt, wenn Nachschlagen legal dazu führen kann, dass nichts gefunden wird (zB abhängige Kontexte, ADL). –

+0

Brauchen Sie unbedingt Klang? Wenn nicht, macht es vielleicht Sinn, andere Lösungen zu versuchen? Es kann passieren, dass sie besser funktionieren. –

+0

Ja, ich habe mir antlr angeschaut, und es ist machbar, obwohl ich vermute, dass es härter und weniger leistungsfähig sein würde. Tatsächlich benutze ich antlr, um ein begrenztes C++ zu parsen, also wäre es mir vertraut. Gibt es andere echte Alternativen? –

Antwort

3

andere Lösung, die ich denke, als Fuzzy-Parsing mehr die OP passen wird.

Wenn Parsing, Clang hält semantische Informationen über die Sema Teil des Analysators. Wenn Sie auf ein unbekanntes Symbol stoßen, wird Sema auf ExternalSemaSource zurückgesetzt, um Informationen zu diesem Symbol zu erhalten. Dadurch können Sie implementieren, was Sie wollen.

Hier ist ein kurzes Beispiel, wie Sie es einrichten. Es ist nicht ganz funktional (ich mache nichts in der LookupUnqualified Methode), Sie müssen möglicherweise weitere Untersuchungen durchführen, und ich denke, es ist ein guter Anfang.

// 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; 
} 

Die Idee und die DynamicIDHandler Klasse sind aus cling Projekt, bei dem unbekannte Symbole variabel sind (daher die Kommentare und die Code).

5

Wenn Sie den Code, den Benutzer schreiben dürfen, nicht stark einschränken, ist es im Grunde unmöglich, C++ (und damit Syntax-Highlighting über Schlüsselwörter/reguläre Ausdrücke hinaus) zu analysieren, ohne alle Header zu analysieren. Der Pre-Prozessor ist besonders gut darin, Dinge für Sie zu vermasseln.

Es gibt einige Gedanken über die Schwierigkeiten bei der Fuzzy-Analyse (im Rahmen der Visual Studio) hier, die von Interesse sein könnten: http://blogs.msdn.com/b/vcblog/archive/2011/03/03/10136696.aspx

+0

Ja, ich bin auf der Suche nach sehr stark eingeschränkt C++ Parsing, möglicherweise mit einigen Heuristik, um einige der Einschränkungen zu heben. C++ Grammatik ist voll von Kontextabhängigkeiten. Was ich frage mich, ob es möglich ist –

+0

... Ich habe eine Klarstellung in meine Frage hinzugefügt. Danke für die Antwort! –

5

Ich weiß, dass die Frage ziemlich alt ist, haben aber einen Blick here:

LibFuzzy ist eine Bibliothek zum heuristischen Parsing von C++ basierend auf Clangs Lexer. Der Fuzzy-Parser ist fehlertolerant, arbeitet ohne Kenntnis des Buildsystems und auf unvollständigen Quelldateien. Da der Parser notwendigerweise Vermutungen anstellt, kann der resultierende Syntaxbaum teilweise falsch sein.

Es ist ein Teilprojekt von clang-highlight, ein (experimentelles?) Werkzeug, das anscheinend nicht mehr entwickelt wird.

Ich interessiere mich nur für den Fuzzy-Parsing-Teil und verzweigte es auf my github page, wo ich mehrere kleinere Probleme behoben und das Tool autonom gemacht hat (es kann außerhalb des Quellbaums von Clam kompiliert werden). Versuchen Sie nicht, es mit C++ 14 (dem Standardmodus von G ++ 6) zu kompilieren, da es Konflikte mit make_unique geben wird.

Laut this page hat das Clang-Format seinen eigenen Fuzzy-Parser (und wird aktiv entwickelt), aber der Parser war (enger?) Enger an das Werkzeug gekoppelt.

0

OP möchte kein "unscharfes Parsen". Was er will, ist voller Kontext frei Parsen des C++ - Quellcodes, ohne irgendeine Voraussetzung für Namen und Typ Auflösung. Er plant, basierend auf dem Ergebnis der Analyse, fundierte Annahmen über die Typen zu machen.

Clang richtige Tangles Parsing und Name/Typ Auflösung, was bedeutet, es muss alle diese Hintergrundtyp Informationen verfügbar haben, wenn es analysiert. Andere Antworten deuten auf einen LibFuzzy hin, der falsche Parse-Bäume erzeugt, und einen Fuzzy-Parser für das Clang-Format, über den ich nichts weiß. Wenn man darauf besteht, einen klassischen AST zu produzieren, wird keine dieser Lösungen den "richtigen" Baum angesichts mehrdeutiger Parser erzeugen.

Unsere DMS Software Reengineering Toolkit mit seiner C++ Frontend kann Parse C++ Quelle without the type information und ermöglichen exakten "Ast"; diese sind eigentlich abstrakte Syntax dags wo Gabeln in Bäumen verschiedene mögliche Interpretationen des Quellcodes gemäß einer sprachgenauen Grammatik darstellen (mehrdeutig (sub) parses).

Was Clang versucht zu tun, ist es zu vermeiden, diese mehreren Subpars zu erzeugen, indem beim Analysieren Typinformationen verwendet werden. Was DMS tut, erzeugt die mehrdeutigen Parser und in (optional) nach dem Parsing (Attribut-Grammatik-Auswertung) pass, sammle Symboltabelleninformation und eliminiere die Unterparse, die nicht mit den Typen übereinstimmen; für gut geformte Programme erzeugt dies einen einfachen AST ohne Mehrdeutigkeiten.

Wenn OP heuristische Vermutungen über die Typinformationen durchführen möchte, muss er diese möglichen Interpretationen kennen. Wenn sie im Voraus eliminiert werden, kann er nicht einfach erraten, welche Arten benötigt werden. Eine interessante Möglichkeit ist die Änderung der Attributgrammatik (die in Quellform als Teil des C++ - Frontends des DMS bereitgestellt wird), die bereits alle C++ - Typregeln kennt, um dies mit partiellen Informationen zu tun. Das wäre ein enormer Vorsprung, wenn es darum geht, den heuristischen Analysator von Grund auf neu zu konstruieren, da er ungefähr 600 Seiten mit geheimnisvollen Namens- und Typauflösungsregeln aus dem Standard kennt.

You can see examples of the (dag) produced by DMS's parser.