2013-04-03 17 views
8

Ho intenzione di implementare una copertura del codice js direttamente nel codice v8. Il mio obiettivo iniziale è aggiungere una semplice stampa per ogni istruzione nella struttura sintattica astratta. Ho visto che esiste una classe AstVisitor, che consente di attraversare l'AST. quindi la mia domanda è: come posso aggiungere una dichiarazione all'AST dopo l'affermazione che il visitatore sta visitando attualmente?Manipolazione del V8 ast

+0

I blocchi di base sono un costrutto per i grafici del flusso di controllo, non per gli AST. Hai intenzione di creare un CFG da AST? – delnan

+0

Posso mescolare i due, ma ho pensato che anche i nodi dell'ast sono blocchi di base? – user2240085

+0

* Quali * nodi? In ogni caso, non sono a conoscenza di nodi AST comuni che corrispondono ai blocchi di base (sebbene sia certamente possibile avere una struttura dati che mantiene anche informazioni CFG-ish e chiama "AST"). Ad esempio, un ciclo è in genere un nodo AST, ma molti anelli sono costituiti da diversi BB. Il nodo di loop può contenere un elenco di nodi di istruzioni, ma alcune di queste istruzioni corrispondono a * parte * di un BB (ad esempio, assegnazione semplice), mentre altri si espandono in * diversi * BB (ad esempio qualsiasi condizione inline o cicli annidati). Forse stai abusando del termine "blocco di base"? – delnan

risposta

5

Ok, riassumerò i miei esperimenti. Innanzitutto, ciò che scrivo si applica al V8 in quanto è stato utilizzato in Chromium versione r157275, quindi le cose potrebbero non funzionare più - ma collegherò comunque ai posti nella versione corrente.

Come detto, è necessario il proprio visitatore AST, ad esempio MyAstVisior, che eredita da AstVisitor e deve implementare un gruppo di metodi VisitXYZ da lì. L'unico necessario per lo strumento/ispezionare codice eseguito è VisitFunctionLiteral. Il codice eseguito è una funzione o un insieme di istruzioni sciolte in una sorgente (file) che V8 esegue in una funzione che viene poi eseguita.

Poi, poco prima di un AST analizzata viene convertito in codice, here (compilazione del modulo funzione di fatto le dichiarazioni sciolti) e there (compilazione durante il runtime, quando una funzione predefinita viene eseguita per la prima volta), si passa la tua visitatore alla funzione letterale, che chiamerà VisitFunctionLiteral sul visitatore:

MyAstVisitor myAV(info); 
info->function()->Accept(&myAV); 
// next line is the V8 compile call 
if (!MakeCode(info)) { 

ho passato il puntatore infoCompilationInfo al visitatore personalizzato perché si ha bisogno che per modificare l'AST. Il costruttore si presenta così:

MyAstVisitor(CompilationInfo* compInfo) : 
    _ci(compInfo), _nf(compInfo->isolate(), compInfo->zone()), _z(compInfo->zone()){}; 

_ci, _nf e _z sono puntatori a CompilationInfo, AstNodeFactory<AstNullVisitor> e Zone.

Ora in VisitFunctionLiteral è possibile scorrere il corpo della funzione e anche inserire istruzioni se lo si desidera.

void MyAstVisitor::VisitFunctionLiteral(FunctionLiteral* funLit){ 
    // fetch the function body 
    ZoneList<Statement*>* body = funLit->body(); 
    // create a statement list used to collect the instrumented statements 
    ZoneList<Statement*>* _stmts = new (_z) ZoneList<Statement*>(body->length(), _z); 
    // iterate over the function body and rewrite each statement 
    for (int i = 0; i < body->length(); i++) { 
     // the rewritten statements are put into the collector 
     rewriteStatement(body->at(i), _stmts); 
    } 
    // replace the original function body with the instrumented one 
    body->Clear(); 
    body->AddAll(_stmts->ToVector(), _z); 
} 

Nel metodo rewriteStatement è ora possibile esaminare la dichiarazione. Il puntatore _stmts contiene un elenco di istruzioni che alla fine sostituiranno il corpo della funzione originale. Quindi, per aggiungere un'istruzione di stampa dopo ogni istruzione in primo luogo aggiunge la dichiarazione originale e quindi aggiungere il proprio estratto conto di stampa:

void MyAstVisitor::rewriteStatement(Statement* stmt, ZoneList<Statement*>* collector){ 
    // add original statement 
    collector->Add(stmt, _z); 

    // create and add print statement, assuming you define print somewhere in JS: 

    // 1) create handle (VariableProxy) for print function 
    Vector<const char> fName("print", 5); 
    Handle<String> fNameStr = Isolate::Current()->factory()->NewStringFromAscii(fName, TENURED); 
    fNameStr = Isolate::Current()->factory()->SymbolFromString(fNameStr); 
    // create the proxy - (it is vital to use _ci->function()->scope(), _ci->scope() crashes) 
    VariableProxy* _printVP = _ci->function()->scope()->NewUnresolved(&_nf, fNameStr, Interface::NewUnknown(_z), 0); 

    // 2) create message 
    Vector<const char> tmp("Hello World!", 12); 
    Handle<String> v8String = Isolate::Current()->factory()->NewStringFromAscii(tmp, TENURED); 
    Literal* msg = _nf.NewLiteral(v8String); 

    // 3) create argument list, call expression, expression statement and add the latter to the collector 
    ZoneList<Expression*>* args = new (_z) ZoneList<Expression*>(1, _z); 
    args->Add(msg); 
    Call* printCall = _nf.NewCall(_printVP, args, 0); 
    ExpressionStatement* printStmt = _nf.NewExpressionStatement(printCall); 
    collector->Add(printStmt, _z); 
} 

L'ultimo parametro di NewCall e NewUnresolved è un numero che specifica la posizione nello script. Presumo che questo sia usato per i messaggi di debug/errore per dire dove si è verificato un errore. Almeno non ho mai riscontrato problemi con l'impostazione su 0 (c'è anche una costante da qualche parte in kNoPosition).

Alcune parole finali: questo non effettivamente aggiungere una dichiarazione di stampa dopo ogni istruzione, perché Blocks (ad esempio corpi di loop) sono affermazioni che rappresentano un elenco di istruzioni e loop sono dichiarazioni che hanno un espressione condizioni e un blocco di prova. Quindi è necessario verificare quale tipo di istruzione viene attualmente gestita e in modo ricorsivo esaminarlo. Riscrivere i blocchi equivale a riscrivere un corpo di una funzione.

Ma si incontrano problemi quando si inizia a sostituire o modificare le istruzioni esistenti, poiché l'AST contiene anche informazioni sulla ramificazione. Quindi se sostituisci un obiettivo di salto per alcune condizioni, interrompi il codice.Immagino che questo possa essere coperto se uno aggiunge direttamente capacità di riscrittura ai singoli tipi di espressioni e di istruzioni invece di crearne di nuovi per sostituirli.

Finora, spero che sia d'aiuto.

+0

Gad. In alternativa, considera il mio approccio descritto in "Copertura delle filiali per le lingue arbitrarie semplificata" http://www.semdesigns.com/Company/Publications/TestCoverage.pdf –