v8embedded-v8

how to get the return value of a JS function from V8?


I'm currently trying to get the return value of a function that I call in JS. The following code can reproduce it (minus v8 includes)

#include "v8.h"
#include "libplatform/libplatform.h"
#include <string>
#include <cassert>

int64_t repro() 
{
    auto isolate = v8::Isolate::New(initializer.create_params_);
    assert(isolate != nullptr);
    v8::Isolate::Scope isolate_scope(isolate);
    v8::HandleScope handle_scope(isolate);
    auto context = v8::Context::New(isolate);
    v8::Context::Scope context_scope(context);
    assert(context.IsEmpty() == false);
    auto global = context->Global();

    std::string script = "function foo() {\n"
                         "  return BigInt(1);\n"
                         "}";

    v8::Local<v8::String> sourceScript =
        v8::String::NewFromUtf8(isolate, script.c_str(),
                                v8::NewStringType::kNormal)
            .ToLocalChecked();
    v8::Local<v8::Script> s =
        v8::Script::Compile(context, sourceScript).ToLocalChecked();
    s->Run(context);

     v8::Local<v8::String> name =
        v8::String::NewFromUtf8(isolate, "foo",
                                v8::NewStringType::kInternalized)
            .ToLocalChecked();
    auto value = global->Get(context, name).ToLocalChecked();
    assert(value->IsFunction());
    auto func = v8::Handle<v8::Function>::Cast(value);

    auto result = func->Call(context, context->Global(), 0, nullptr)
                      .ToLocalChecked();
    assert(result->IsBigInt());
    auto bigint = result->IntegerValue(context);
    assert(bigint.IsNothing() == false);
    return bigint.ToChecked();
}

when I now look at bigint - the type reports as a BigInt, but IsNothing() returns true. What am I doing wrong?

Thank you

Tobias


Solution

  • As the documentation says, v8::Value::IntegerValue() "Returns the equivalent of ToInteger()->Value()", which means it throws an exception (i.e. returns Nothing) when invoked on a BigInt, reflecting the fact that in JavaScript, calling the "abstract operation" ToInteger() on a BigInt throws a TypeError, or in other words: a BigInt doesn't just implicitly convert to a Number.

    To extract a BigInt's value from C++, you can do this:

    int64_t bigint = v8::Local<v8::BigInt>::cast(result)->Int64Value();
    

    Of course that will give an incorrect result when the BigInt's value is bigger than an int64. It takes an optional bool* to indicate whether the conversion to int64 was lossless or truncating. If you need to get to bigger values, you can use the ToWordsArray(...) method.

    how to get the return value of a JS function from V8?

    Exactly like you did:

    v8::MaybeLocal<v8::Value> result = func->Call(...);
    

    Note that using .ToLocalChecked(); is risky: if the function throws an exception instead of returning a value, then .ToLocalChecked() will crash. If you don't control the function's code and hence can't guarantee that it won't throw, then it's better to test whether the result is empty, and handle exceptions gracefully. See V8's samples/ directory, or the documention on v8.dev/docs/, for lots of examples and additional explanations.

    (Side note: I would recommend to use auto much less. It helps to see the types. The differences between, say, v8::Value, v8::Local<v8::Value>, v8::MaybeLocal<v8::Value>, and v8::Local<v8::BigInt> are meaningful, and it helps you write correct code when you don't just hide them behind auto.)