use*_*085 8 javascript v8 compilation abstract-syntax-tree
我打算直接在v8代码中实现js代码覆盖.我的初始目标是为抽象语法树中的每个语句添加一个简单的打印.我看到有一个AstVisitor类,它允许你遍历AST.所以我的问题是如何在访问者目前访问的语句后向AST添加语句?
好的,我会总结一下我的实验.首先,我写的内容适用于V8,因为它在Chromium版本r157275中使用,所以事情可能不再起作用了 - 但我会链接到当前版本中的位置.
如上所述,您需要自己的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)) {
Run Code Online (Sandbox Code Playgroud)
我将CompilationInfo指针传递info给自定义访问者,因为需要修改AST.构造函数如下所示:
MyAstVisitor(CompilationInfo* compInfo) :
_ci(compInfo), _nf(compInfo->isolate(), compInfo->zone()), _z(compInfo->zone()){};
Run Code Online (Sandbox Code Playgroud)
_ci,_nf和_z是指针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);
}
Run Code Online (Sandbox Code Playgroud)
在该rewriteStatement方法中,您现在可以检查语句.该_stmts指针指向报表时到底会取代原来的函数体的列表.因此,要在每个语句后添加print语句,首先添加原始语句,然后添加自己的print语句:
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);
}
Run Code Online (Sandbox Code Playgroud)
的最后一个参数NewCall和NewUnresolved一个数字,指定在脚本中的位置.我假设这用于调试/错误消息,以告知错误发生的位置.我至少从未遇到过将其设置为0的问题(kNoPosition也有一个常量).
最后一句话:这实际上并不会在每个语句之后添加一个print语句,因为Blocks(例如循环体)是表示语句列表的语句,而循环是具有条件表达式和主体块的语句.因此,您需要检查当前处理的语句类型并以递归方式查看它.重写块与重写函数体几乎相同.
但是当您开始替换或修改现有语句时,您将遇到问题,因为AST还包含有关分支的信息.因此,如果您为某些条件替换跳转目标,则会破坏您的代码.如果直接将重写功能添加到单个表达式和语句类型而不是创建新的替换它们,我想这可以涵盖.
到目前为止,我希望它有所帮助.
| 归档时间: |
|
| 查看次数: |
1064 次 |
| 最近记录: |