highlight.js

Wednesday, May 19, 2021

Javet - Patch V8 Function at Source Code Level

Javet is Java + V8 (JAVa + V + EighT). It is an awesome way of embedding Node.js and V8 in Java.

With the release of v0.8.8, Javet supports patching V8 function at source code level on the fly.

Why is That Important?

Functions can be changed on the fly at JavaScript code level via Javet API. Why to choose this approach? Because sometimes local scoped context is required which is usually called closure. E.g:

const a = function () {
    const b = 1;
    return () => b;
}
const x = a();
console.log(x());
// Output is: 1

Local const b is visible to the anonymous function at line 3, but invisible to the function interceptor. Javet provides a way of changing the function at JavaScript source code level so that local scoped context is still visible.

How?

getSourceCode() and setSourceCode(String sourceCode) are designed for getting and setting the source code. setSourceCode(String sourceCode) actually performs the follow steps.

def setSourceCode(sourceCode):
    existingSourceCode = v8Function.getSourceCode()
    (startPosition, endPosition) = v8Function.getPosition()
    newSourceCode = existingSourceCode[:startPosition] + sourceCode + existingSourceCode[endPosition:]
    v8Function.setSourceCode(newSourceCode)
    v8Function.setPosition(startPosition, startPosition + len(sourceCode))

Be careful, setSourceCode(String sourceCode) has radical impacts that may break the execution because all functions during one execution share the same source code but have their own positions. The following diagram shows the rough memory layout. Assuming function (4) has been changed to something else with position changed, function (1) and (2) will not be impacted because their positions remain the same, but function (3) will be broken because its end position is not changed to the end position of function (4) accordingly.


Javet does not scan memory for all impacted function. So, it is caller's responsibility for restoring the original source code after invocation. The pseudo logic is as following.

originalSourceCode = v8ValueFunction.getSourceCode()
v8ValueFunction.setSourceCode(sourceCode)
v8ValueFunction.call(...)
v8ValueFunction.setSourceCode(originalSourceCode)

Why does setSourceCode() sometimes return false? Usually, that means the local scoped context hasn't been generated by V8. getJSScopeType().isClass() == true indicates that state. After callVoid(null), the local scoped context will be created with getJSScopeType().isFunction() == true and setSourceCode() will work. The pseudo logic is as following.

originalSourceCode = v8ValueFunction.getSourceCode()
if (v8ValueFunction.getJSScopeType().isClass()) {
    try {
        v8ValueFunction.callVoid(null);
        // Now v8ValueFunction.getJSScopeType().isFunction() is true
    } catch (JavetException e) {
    }
}
v8ValueFunction.setSourceCode(sourceCode) // true
v8ValueFunction.call(...)
v8ValueFunction.setSourceCode(originalSourceCode)

The rough lifecycle of a V8 function is as following.


What is the Source Code of a Function in V8?

When V8 calculates start position of a function, it does not include the keyword function and function name. E.g.

function abc(a, b, c) { ... } // Source code is (a, b, c) { ... }

(a, b, c) => { ... }          // Source code is (a, b, c) => { ... }

So, please always discard the keyword function and function name when calling setSourceCode().

No comments:

Post a Comment

Cue Club 2 on Apple Silicon

It's been quite a long time for me not to play a decent snooker game since I replaced my Windows laptop with an Apple Silicon one. To be...