2013-04-03 18 views
8

Zamierzam zaimplementować pokrycie kodu js bezpośrednio w kodzie V8. Moim początkowym celem jest dodanie prostego wydruku dla każdej instrukcji w drzewie składni abstrakcyjnej. Zauważyłem, że istnieje klasa AstVisitor, która pozwala ci przejść przez AST. , więc moje pytanie brzmi: jak mogę dodać oświadczenie do AST po oświadczeniu, które odwiedza obecnie odwiedzający?Manipulowanie V8 ast

+0

Bloki podstawowe są konstruktem dla wykresów sterowania, a nie dla AST. Czy zamierzasz stworzyć CFG z AST? – delnan

+0

Mogę mieszać dwa, ale myślałem, że węzły ast są również podstawowymi blokami? – user2240085

+0

* Które * węzły? W każdym razie nie jestem świadomy żadnych wspólnych węzłów AST, które pasują do podstawowych bloków (choć z pewnością możliwe jest posiadanie struktury danych, która również zachowuje informacje CFG-owskie i nazywa to "AST"). Na przykład pętla jest zwykle węzłem AST, ale wiele pętli składa się z kilku BB. Węzeł pętli może zawierać listę węzłów instrukcji, ale niektóre z tych instrukcji odpowiadają * części * BB (np. Proste przyporządkowanie), podczas gdy inne rozszerzają się na * kilka * BB (np. Dowolny warunek wbudowany lub pętle zagnieżdżone). Być może niewłaściwie używasz terminu "podstawowy blok"? – delnan

Odpowiedz

5

OK, podsumuję moje eksperymenty. Po pierwsze, to, co piszę, dotyczy V8, ponieważ było używane w wersji Chromium r157275, więc rzeczy mogą już nie działać - ale mimo to jednak będę łączył się z miejscami w bieżącej wersji.

Jak powiedziałeś, potrzebujesz własnego gościa AST, powiedzmy MyAstVisior, który dziedziczy po AstVisitor i musi wdrożyć tam kilka metod VisitXYZ. Jedynym wymaganym do przyrządu/inspekcji wykonanego kodu jest VisitFunctionLiteral. Wygenerowany kod jest albo funkcją, albo zbiorem luźnych instrukcji w źródle (pliku), który V8 owija w funkcję, która jest następnie wykonywana.

Następnie, tuż przed przetworzeniem analizatora AST na kod, here (kompilacja funkcji utworzonej z luźnych instrukcji) i there (kompilacja podczas wykonywania, gdy wstępnie zdefiniowana funkcja jest wykonywana po raz pierwszy), należy podać gość z funkcją dosłownym, co nazywamy VisitFunctionLiteral na odwiedzającego:

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

zdałem CompilationInfo wskaźnik info do niestandardowego odwiedzającego bo trzeba, że ​​do modyfikowania AST. Konstruktor wygląda następująco:

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

_ci, _nf i _z są wskaźnikami do CompilationInfo, AstNodeFactory<AstNullVisitor> i Zone.

Teraz można w VisitFunctionLiteral dokonywać iteracji poprzez treść funkcji, a także wstawiać instrukcje, jeśli chcesz.

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

W metodzie rewriteStatement można teraz sprawdzić instrukcję. Wskaźnik _stmts zawiera listę instrukcji, które na końcu zastąpią oryginalną treść funkcji. Tak aby dodać oświadczenie drukowania po każdej instrukcji najpierw dodać oryginalnego komunikatu, a następnie dodać własne oświadczenie wydruku:

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

Ostatnim parametrem NewCall i NewUnresolved jest liczbą określającą pozycję w skrypcie. Zakładam, że jest to używane do komunikatów o błędach/debugowaniu, aby określić, gdzie wystąpił błąd. Przynajmniej nie napotkałem problemów z ustawieniem go na 0 (jest też stała gdzieś gdzie kNoPosition).

Kilka ostatnich słów: To nie doda instrukcji drukowania po każdym poleceniu, ponieważ Blocks (np. Obiekty pętli) są instrukcjami reprezentującymi listę instrukcji, a pętle są wyrażeniami, które mają wyrażenie warunku i blok bryły. Musisz więc sprawdzić, jaki rodzaj instrukcji jest aktualnie obsługiwany i rekurencyjnie go przeanalizować. Przepisywanie bloków jest prawie takie samo jak przepisywanie treści funkcji.

Ale napotkasz problemy, gdy zaczniesz zastępować lub modyfikować istniejące wyciągi, ponieważ AST również niesie informację o rozgałęzianiu. Więc jeśli zastąpisz cel skoku dla jakiegoś warunku, złamiesz swój kod.Sądzę, że można to uwzględnić, jeśli bezpośrednio dodaje się możliwości przeróbki do pojedynczego wyrażenia i typów instrukcji, zamiast tworzyć nowe, aby je zastąpić.

Do tej pory mam nadzieję, że to pomoże.

+0

Gad. Jako alternatywę przyjrzyj się mojemu podejściu opisanemu w sekcji "Pokrycie gałęzi dla języków arbitralnych" (http://www.semdesigns.com/Company/Publications/TestCoverage.pdf –