2012-06-21 19 views
15

¿Es posible analizar C++ con declaraciones incompletas con clang con su API libclang existente? Es decir. analizar el archivo .cpp sin incluir todos los encabezados, deducir declaraciones sobre la marcha. por lo tanto, p. El siguiente texto:Clang para el análisis difuso C++

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

detectará desconocida símbolo A, llamar a mi devolución de llamada que deduce A es una clase usando mi heurística magia, a continuación, llamar a esta devolución de llamada de la misma manera con B y Foo y esas cosas. Al final quiero poder inferir que vi a un miembro Foo de la clase B que devuelve A, y eso es una función ... O algo por el estilo. contexto: Quiero ver si puedo hacer un resaltado de sintaxis sensible y sobre la marcha de análisis de código sin analizar todos los encabezados muy rápidamente.

[EDIT] Para aclarar, estoy buscando un análisis de C++ muy restringido, posiblemente con cierta heurística para levantar algunas de las restricciones.

La gramática de C++ está llena de dependencias de contexto. Es Foo() una llamada de función o una construcción de un temporal de la clase Foo? Es Foo <Bar> cosas; una plantilla Foo <Bar> creación de instancias y declaración de cosas variables, o ¿son 2 llamadas extrañas al operador sobrecargado < y operador>? Solo es posible decir en contexto, y el contexto a menudo proviene del análisis de los encabezados.

Lo que estoy buscando es una forma de conectar mis reglas de convención personalizadas. P.ej. Sé que no sobrecargo los símbolos de Win32, así que puedo asumir con seguridad que CreateFile es siempre una función, e incluso sé su firma. También sé que todas mis clases comienzan con una letra mayúscula y son sustantivos, y las funciones suelen ser verbos, por lo que puedo suponer razonablemente que Foo y Bar son nombres de clase. En un escenario más complejo, sé que no escribo expresiones libres de efectos secundarios como < b> c; así que puedo suponer que a siempre es una instanciación de plantilla. Y así.

Entonces, la pregunta es si es posible usar Clang API para devolver la llamada cada vez que se encuentra con un símbolo desconocido, y darle una respuesta usando mi propia heurística no C++. Si mi heurística falla, entonces el análisis fracasa, obviamente. Y no estoy hablando de analizar la biblioteca Boost :) Estoy hablando de C++ muy simple, probablemente sin plantillas, restringido a un mínimo que clang puede manejar en este caso.

+0

Siempre puede modificar CLang directamente. No estoy seguro de lo fácil que sería, ya que hay muchas ocasiones en que la búsqueda puede dar como resultado que no se encuentre nada (por ejemplo, contextos dependientes, ADL). –

+0

¿Definitivamente necesitas clang? Si no, tal vez tiene sentido probar otras soluciones? Puede suceder que funcionen mejor. –

+0

Sí, miré a antlr, y es factible, aunque sospecho que sería más difícil y menos eficiente ... De hecho, estoy usando antlr para analizar algunos C++ limitados, por lo que sería familiar para mí. ¿Hay otras alternativas reales? –

Respuesta

3

Otra solución que creo que más se adapte a la OP de análisis difusa.

Al analizar, clang mantiene la información semántica a través de la Sema parte del analizador. Cuando se encuentre con un símbolo desconocido, Sema retrocederá a ExternalSemaSource para obtener información sobre este símbolo. A través de esto, puedes implementar lo que quieras.

Aquí hay un ejemplo rápido de cómo configurarlo. No es del todo funcional (no estoy haciendo nada en el método LookupUnqualified), es posible que deba realizar más investigaciones y creo que es un buen comienzo.

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

La idea y la clase DynamicIDHandler son de cling proyecto donde los símbolos son variables desconocidas (por lo tanto, los comentarios y el código).

5

A menos que restrinja en gran medida el código que las personas pueden escribir, es básicamente imposible hacer un buen trabajo de análisis C++ (y por lo tanto sintaxis resaltando más palabras clave/expresiones regulares) sin analizar todos los encabezados. El pre-procesador es particularmente bueno para arruinar las cosas para ti.

Hay algunas reflexiones sobre las dificultades de análisis difusa (en el contexto de Visual Studio) aquí, que pueden ser de interés: http://blogs.msdn.com/b/vcblog/archive/2011/03/03/10136696.aspx

+0

Sí, estoy buscando un análisis de C++ muy restringido, posiblemente con cierta heurística para levantar algunas de las restricciones. La gramática C++ está llena de dependencias de contexto. Lo que me pregunto si es posible –

+0

... Agregué una aclaración en mi pregunta. ¡Gracias por la respuesta! –

5

sé que la pregunta es bastante antiguo, pero tienen un aspecto here:

LibFuzzy es una biblioteca para analizar heurísticamente C++ basada en Clang's Lexer. El analizador difuso es tolerante a errores, funciona sin conocimiento de el sistema de compilación y en archivos fuente incompletos. Como el analizador necesariamente hace conjeturas, el árbol de sintaxis resultante puede ser parcialmente incorrecto.

Es un subproyecto de clang-highlight, una herramienta (¿experimental?) Que parece que ya no está desarrollada.

Solo me interesa la parte de análisis difuso y la bifurqué en my github page donde solucioné varios problemas menores y la hice autónoma (se puede compilar fuera del árbol de fuentes de clang). No intente compilarlo con C++ 14 (que es el modo predeterminado de G ++ 6), ya que habrá conflictos con make_unique.

De acuerdo con this page, el formato clang tiene su propio analizador difuso (y se desarrolla activamente), pero el analizador sintáctico se (¿?) Está más estrechamente acoplado a la herramienta.

0

OP no quiere "análisis difuso". Lo que él quiere es un contexto completo: libre análisis del código fuente de C++, sin ningún requisito para el nombre y la resolución del tipo. Planea hacer conjeturas sobre los tipos en función del resultado del análisis.

Correlación correcta de enredos y resolución de nombre/tipo, lo que significa que debe tener toda esa información de tipo de fondo disponible cuando se analiza. Otras respuestas sugieren una LibFuzzy que produce árboles de análisis incorrectos, y algún analizador difuso para el formato de clang del que no sé nada. Si uno insiste en producir un AST clásico, ninguna de estas soluciones producirá el árbol "correcto" frente a los análisis ambiguos.

Nuestra DMS Software Reingeniería Toolkit con su C++ extremo frontal puede de análisis sintáctico C++ fuente without the type information, y produce "AST" exactos; estos son realmente diagramas de sintaxis abstractos donde las horquillas en los árboles representan diferentes interpretaciones posibles del código fuente de acuerdo con una gramática precisa del lenguaje (ambigua (sub) sintáctica).

Lo que Clang intenta hacer es evitar producir estos múltiples sub-análisis utilizando información de tipo a medida que analiza. Lo que DMS hace es producir los análisis ambiguos, y en el pase (opcional) posterior al análisis (evaluación de la gramática del atributo), recopilar información de la tabla de símbolos y eliminar los sub-análisis que son inconsistentes con los tipos; para programas bien formados, esto produce un AST simple sin dejar ambigüedades.

Si OP quiere hacer suposiciones heurísticas sobre la información del tipo, necesitará conocer estas posibles interpretaciones. Si se eliminan de antemano, no puede adivinar de manera directa qué tipos podrían necesitarse. Una posibilidad interesante es la idea de modificar la gramática de atributos (provista en forma de fuente como parte de la interfaz de C++ del DMS), que ya conoce todas las reglas de tipo C++, para hacer esto con información parcial. Eso sería una gran ventaja para construir el analizador heurístico desde cero, dado que tiene que conocer unas 600 páginas de nombre arcano y reglas de resolución de tipo del estándar.

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