javascript - 操纵 V8 ast

标签 javascript compilation v8 abstract-syntax-tree

我打算直接在v8代码中实现一个js代码覆盖。 我最初的目标是为抽象语法树中的每个语句添加一个简单的打印。 我看到有一个 AstVisitor 类,它允许您遍历 AST。 所以我的问题是如何在访问者当前访问的语句之后向 AST 添加语句?

最佳答案

好的,我将总结我的实验。首先,我写的内容适用于 V8,因为它在 Chromium 版本 r157275 中使用,因此可能不再有效 - 但我仍然会链接到当前版本中的位置。

如前所述,您需要自己的 AST 访问者,例如 MyAstVisior , 它继承自 AstVisitor 并且必须实现一堆 VisitXYZ那里的方法。唯一需要检测/检查执行代码的是 VisitFunctionLiteral .执行的代码是一个函数或源(文件)中的一组松散语句,V8 将其包装在一个函数中,然后执行。

然后,就在解析的 AST 转换为代码之前,here (从松散的语句中编译函数)和there (在运行时编译,当第一次执行预定义的函数时),您将访问者传递给函数文字,它将调用 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()){};

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

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)。

一些最后的话:这实际上不会在每个语句之后添加打印语句,因为Blocks (例如循环体)是表示语句列表的语句,循环是具有条件表达式和主体 block 的语句。所以你需要检查当前处理的语句类型并递归地查看它。重写 block 与重写函数体几乎相同。

但是当您开始替换或修改现有语句时,您会遇到问题,因为 AST 还带有有关分支的信息。因此,如果您在某些情况下更换跳转目标,就会破坏您的代码。我想如果直接向单个表达式和语句类型添加重写功能而不是创建新的来替换它们,这可能会被覆盖。

到目前为止,我希望它能有所帮助。

关于javascript - 操纵 V8 ast,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15785548/

相关文章:

makefile - make clean、make clobber、make distclean、make mrproper 和 make realclean 之间有什么区别?

c - 在 Ubuntu 上编译 C 时没有输入文件或 undefined reference

c++ - 编译错误: “lvalue required as left operand of assignment” Unsure why

c++ - 如何从 C++ 类内部获取 v8 对象

javascript - 错误类型错误 : value. forEach 不是函数

javascript - 检测 JavaScript 文件的欺骗

javascript - v8 是否能够根据 `const` 的值消除死代码?

javascript - Node 不会运行来自 Eloquent Javascript 的示例,v8 会。这是怎么回事?

javascript - 如何在点击功能上调用javascript方法?

javascript - 使用 Ajax 的 URL Action 参数