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
Odpowiedz
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.
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 –
Bloki podstawowe są konstruktem dla wykresów sterowania, a nie dla AST. Czy zamierzasz stworzyć CFG z AST? – delnan
Mogę mieszać dwa, ale myślałem, że węzły ast są również podstawowymi blokami? – user2240085
* 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