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
risposta
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 info
CompilationInfo
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.
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 –
- 1. Manipolazione del neurone PyBrain
- 2. Che cos'è JavaScript AST, come giocarci?
- 3. Ottenere genitore del nodo AST in Python
- 4. Rappresentazione del tipo di record AST OCaml
- 5. Interprete AST?
- 6. Implementazione del Clojure sulla parte superiore del motore V8
- 7. Aggiornamento albero AST ANTLR
- 8. API per confrontare AST?
- 9. Ottenere AST per C++?
- 10. Incorpora V8 nell'applicazione OpenCL?
- 11. parser Haskell su AST
- 12. Utilizzo di Eclipse AST
- 13. Elegante modello AST
- 14. Ottimizza la manipolazione del colore su XNA
- 15. Manipolazione del percorso (vulnerabilità della sicurezza)
- 16. Pattern di manipolazione del codice byte
- 17. Manipolazione del DOM in angularJS: best practice?
- 18. Manipolazione del percorso della directory in Delfi?
- 19. Google Javascript V8 - multithreading
- 20. v8 delega ReferenceError
- 21. Come bloccare "V8?
- 22. Edificio v8 senza JIT
- 23. Elaborazione AST Python
- 24. Building AST in OCaml
- 25. Utilizzo di stringify dalla shell v8
- 26. Running V8 Javascript Engine Standalone
- 27. Odoo v8 non riesce a caricare dall'installazione del server remoto
- 28. Nodi di profilo con v8
- 29. Validazione della manipolazione del DOM durante l'utilizzo del selenio
- 30. Come costruire manualmente un AST?
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
Posso mescolare i due, ma ho pensato che anche i nodi dell'ast sono blocchi di base? – user2240085
* 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