我打算直接在v8代码中实现js代码覆盖率。我的初始目标是为抽象语法树中的每个语句添加一个简单的打印功能。我看到有一个AstVisitor
类,它允许您遍历AST。因此,我的问题是如何在visitor当前访问的语句之后向AST添加语句?
我打算直接在v8代码中实现js代码覆盖率。我的初始目标是为抽象语法树中的每个语句添加一个简单的打印功能。我看到有一个AstVisitor
类,它允许您遍历AST。因此,我的问题是如何在visitor当前访问的语句之后向AST添加语句?
好的,我将总结我的实验。首先,我所写的内容适用于Chromium版本r157275中使用的V8,因此可能不再适用-但我仍将链接到当前版本的位置。
如前所述,您需要自己的AST访问者,比如MyAstVisior
,它继承自AstVisitor
,并必须从那里实现一堆VisitXYZ
方法。检查执行代码所需的唯一方法是VisitFunctionLiteral
。执行的代码可以是函数,也可以是源代码中的一组松散语句,V8会将其包装在一个函数中,然后执行。
然后,在解析的AST转换为代码之前,这里(将松散语句制作成函数的编译)和那里(运行时编译,当首次执行预定义函数时),您将访问者传递给函数文字,它将调用访问者上的VisitFunctionLiteral
:
MyAstVisitor myAV(info);
info->function()->Accept(&myAV);
// next line is the V8 compile call
if (!MakeCode(info)) {
我将CompilationInfo
指针info
传递给自定义访问者,因为需要它来修改AST。构造函数如下:
MyAstVisitor(CompilationInfo* compInfo) :
_ci(compInfo), _nf(compInfo->isolate(), compInfo->zone()), _z(compInfo->zone()){};
CompilationInfo
、AstNodeFactory<AstNullVisitor>
和 Zone
的指针。
现在,在 VisitFunctionLiteral
中,您可以遍历函数体,并在需要时插入语句。
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);
}
在rewriteStatement
方法中,现在可以检查语句。指针_stmts
保存了语句列表,最终将替换原始函数体。因此,要在每个语句后添加打印语句,首先添加原始语句,然后添加自己的打印语句:
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);
}
NewCall
和NewUnresolved
的最后一个参数是一个数字,用于指定脚本中的位置。我认为这是用于调试或错误信息,以便告诉出现错误的具体位置。至少我从未遇到过将其设置为0的问题(也有一个常量kNoPosition)。