Skip to content

Commit

Permalink
doc: remove exceptions section (moved)
Browse files Browse the repository at this point in the history
  • Loading branch information
danbev committed Jan 12, 2021
1 parent 5b4376c commit ec5d964
Showing 1 changed file with 0 additions and 358 deletions.
358 changes: 0 additions & 358 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,364 +251,6 @@ Later in `Invoke` in `execution.cc`a:
Looking at this is looks like passing back an empty object will cause an
exception to be triggered?


### Exceptions
When a function is called how is an exception reported/triggered in the call
chain?

```c++
Local<FunctionTemplate> ft = FunctionTemplate::New(isolate_, function_callback);
Local<Function> function = ft->GetFunction(context).ToLocalChecked();
Local<Object> recv = Object::New(isolate_);
MaybeLocal<Value> ret = function->Call(context, recv, 0, nullptr);
```
`function->Call` will end up in `src/api/api.cc` `Function::Call` and will
in turn call `v8::internal::Execution::Call`:
```c++
has_pending_exception = !ToLocal<Value>(
i::Execution::Call(isolate, self, recv_obj, argc, args), &result);
RETURN_ON_FAILED_EXECUTION(Value);
RETURN_ESCAPED(result);
```
Notice that the result of `Call` which is a `MaybeHandle<Object>` will be
passed to ToLocal<Value> which is defined in `api.h`:
```c++
template <class T>
inline bool ToLocal(v8::internal::MaybeHandle<v8::internal::Object> maybe,
Local<T>* local) {
v8::internal::Handle<v8::internal::Object> handle;
if (maybe.ToHandle(&handle)) {
*local = Utils::Convert<v8::internal::Object, T>(handle);
return true;
}
return false;
```
So lets take a look at `Execution::Call` which can be found in `execution/execution.cc`
and it calls:
```c++
return Invoke(isolate, InvokeParams::SetUpForCall(isolate, callable, receiver, argc, argv));
```
`InvokeParams` is a struct in `execution.cc` which has a few static functions
(one being `SetupForCall`) and also the following fields:
```
Handle<Object> target;
Handle<Object> receiver;
int argc;
Handle<Object>* argv;
Handle<Object> new_target;
MicrotaskQueue* microtask_queue;
Execution::MessageHandling message_handling;
MaybeHandle<Object>* exception_out;
bool is_construct;
Execution::Target execution_target;
bool reschedule_terminate;
```
`SetupUpForNew` will set up defaults in addition to set the values for the
constructor, new_target, argc, and argv.
Related to exception handling is:
```c++
params.message_handling = Execution::MessageHandling::kReport;
```
So with that out of the way lets focus on the `Invoke` function in `execution.cc`
around line 240 at the time of this writing.
Now, if the target which is our case is the `Function` is a JSFunction there is
a path which will be true in our case:
```c++
Handle<JSFunction> function = Handle<JSFunction>::cast(params.target);
```
Next `SaveAndSwitchContext` is called:
```c++
SaveAndSwitchContext save(isolate, function->context());
```
Which will call isolate->set_context(function_context).
Next, we have:
```c++
Handle<Object> receiver = params.is_construct
? isolate->factory()->the_hole_value()
: params.receiver;
```
And in our case `params.is_construct` is false:
```c++
(gdb) p params.is_construct
$1 = false
```
So the receiver will just be set to the reciever set on the param which is the
receiver we passed in. Next, we have the call to `Builtins::InvokeApiFunction`
which can be found in `builtins/builtins-api.cc:
```c++
Handle<FunctionTemplateInfo> fun_data = function->IsFunctionTemplateInfo()
? Handle<FunctionTemplateInfo>::cast(function)
: handle(JSFunction::cast(*function).shared().get_api_func_data(),
isolate);
```
```console
(gdb) p function->IsFunctionTemplateInfo()
$2 = false
```
So in our case the following will be executed:
```c++
: handle(JSFunction::cast(*function).shared().get_api_func_data(),
isolate);
```
TODO: Look into JSFunction and SharedFunctionInfo.
```c++
Address small_argv[kBufferSize];
Address* argv;
const int frame_argc = argc + BuiltinArguments::kNumExtraArgsWithReceiver;
```
In our case we did not pass any arguments so argc is 0.
```console
(gdb) p kBufferSize
$7 = 32
(gdb) p argc
$8 = 0
(gdb) p BuiltinArguments::kNumExtraArgsWithReceiver
$9 = 5
```
```c++
if (frame_argc <= kBufferSize) {
argv = small_argv;
```
So in our case we will be using `small_argv` which is an Array of Address:es.
Next, this array will be populated:
```c++
int cursor = frame_argc - 1;
argv[cursor--] = receiver->ptr();
```
So we are setting the argv[4] to the receiver. The next argument set is:
```c++
argv[BuiltinArguments::kPaddingOffset] = ReadOnlyRoots(isolate).the_hole_value().ptr();
argv[BuiltinArguments::kArgcOffset] = Smi::FromInt(frame_argc).ptr();
argv[BuiltinArguments::kTargetOffset] = function->ptr();
argv[BuiltinArguments::kNewTargetOffset] = new_target->ptr()

RelocatableArguments arguments(isolate, frame_argc, &argv[frame_argc - 1]);
result = HandleApiCallHelper<false>(isolate, function, new_target,
fun_data, receiver, arguments);
```
So we can see that we are passing the function, new_target, `HandleApiCallHelper`

```c++
Object raw_call_data = fun_data->call_code();
CallHandlerInfo call_data = CallHandlerInfo::cast(raw_call_data);
Object data_obj = call_data.data();
Handle<Object> result = custom.Call(call_data);
```
`Call` will land in `src/api/api-arguments-inl.h`:
```c++
Handle<Object> FunctionCallbackArguments::Call(CallHandlerInfo handler) {
...
v8::FunctionCallback f = v8::ToCData<v8::FunctionCallback>(handler.callback());

}
```
This is the function callback in our example which is named `function_callback`:
```console
(gdb) p f
$14 = (v8::FunctionCallback) 0x413ec6 <function_callback(v8::FunctionCallbackInfo<v8::Value> const&)>
```
```c++
VMState<EXTERNAL> state(isolate);
ExternalCallbackScope call_scope(isolate, FUNCTION_ADDR(f));
FunctionCallbackInfo<v8::Value> info(values_, argv_, argc_);
f(info);
return GetReturnValue<Object>(isolate);
}
```
This return will return to:
```c++
RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, Object);
```
Which will be expanded by the preprocessor into:
```c++
do {
Isolate* __isolate__ = (isolate);
((void) 0);
if (__isolate__->has_scheduled_exception()) {
__isolate__->PromoteScheduledException();
return MaybeHandle<Object>();
}
} while (false);
```
In our case there was not exception so the following line will be reached:
```c++
if (result.is_null()) {
return isolate->factory()->undefined_value();
}
```
So, we returned an empty/null value from our function and the leads to the
undefined value to be returned. Just noting this as I'm not sure if it is
important or not but CustomArguments has a destructor that will be called
when the `FunctionCallbackArguments` instance goes out of scope:
```
template <typename T>
CustomArguments<T>::~CustomArguments() {
slot_at(kReturnValueOffset).store(Object(kHandleZapValue));
```
Back now in `Builtins::InvokeApiFunction`:
```c++
MaybeHandle<Object> result;
... // HandleApiCallHelper was shown above
if (argv != small_argv) delete[] argv;
return result;
```
So after this return we will be back in `Invoke` in execution.cc:
```c++
auto value = Builtins::InvokeApiFunction(
isolate, params.is_construct, function, receiver, params.argc,
params.argv, Handle<HeapObject>::cast(params.new_target));
bool has_exception = value.is_null();
```
Now, even though are callback returned nothing/null, that was checked for and
instead undefined was returned.

After this we will return to `Execution::Call` which will just return to
`v8::ToLocal` which will turn the Handle into a MaybeHandle.
This will then return us to Function::Call:
```c++
RETURN_ON_FAILED_EXECUTION(Value);
RETURN_ESCAPED(result);
```
`RETURN_ON_FAILED_EXECUTION` will expand to:
```c++
do {
if (has_pending_exception) {
call_depth_scope.Escape();
return MaybeLocal<Value>();
}
} while (false);
```
And RETURN_ESCAPED:
```c++
return handle_scope.Escape(result);;
```
I wonder why this last line is a macro when it is just one line.
Finally we will be back in our test
```c++
MaybeLocal<Value> ret = function->Call(context, recv, 0, nullptr);
```

```console
(gdb) p result.is_null()
$15 = true
```

### Throwing an Exception
When calling a Function one can throw an exception using:
```c++
isolate->ThrowException(String::NewFromUtf8(isolate, "some error").ToLocalChecked());
```
`ThrowException` can be found in `src/api/api.cc` and what it does is:
```c++
ENTER_V8_DO_NOT_USE(isolate);
// If we're passed an empty handle, we throw an undefined exception
// to deal more gracefully with out of memory situations.
if (value.IsEmpty()) {
isolate->ScheduleThrow(i::ReadOnlyRoots(isolate).undefined_value());
} else {
isolate->ScheduleThrow(*Utils::OpenHandle(*value));
}
return v8::Undefined(reinterpret_cast<v8::Isolate*>(isolate));
```
Lets take a closer look at `ScheduleThrow`.
```c++
void Isolate::ScheduleThrow(Object exception) {
Throw(exception);
PropagatePendingExceptionToExternalTryCatch();
if (has_pending_exception()) {
thread_local_top()->scheduled_exception_ = pending_exception();
thread_local_top()->external_caught_exception_ = false;
clear_pending_exception();
}
}
```
`Throw` will end by setting the pending_exception to the exception passed in.
Next, `PropagatePendingExceptionToExternalTryCatch` will be called. This is
where a `TryCatch` handler comes into play. If one had been registered, which as
I'm writing this I did not have one (but will add one to try it out and verify).
The code for this part looks like this:
```c++
v8::TryCatch* handler = try_catch_handler();
handler->can_continue_ = true;
handler->has_terminated_ = false;
handler->exception_ = reinterpret_cast<void*>(pending_exception().ptr());
// Propagate to the external try-catch only if we got an actual message.
if (thread_local_top()->pending_message_obj_.IsTheHole(this)) return true;
handler->message_obj_ = reinterpret_cast<void*>(thread_local_top()->pending_message_obj_.ptr());
```
When a TryCatch is created its constructor will call `RegisterTryCatchHandler`
which will set the thread_local_top try_catch_handler which is retrieved above
with the call to `try_catch_handler()`.

Prior to this there will be a call to `IsJavaScriptHandlerOnTop`:
```c++
// For uncatchable exceptions, the JavaScript handler cannot be on top.
if (!is_catchable_by_javascript(exception)) return false;
```
```c++
return exception != ReadOnlyRoots(heap()).termination_exception();
}
```
TODO: I really need to understand this better and the various ways to
catch/handle exceptions (from C++ and JavaScript).

Next (in PropagatePendingExceptionToExternalTryCatch) we have:
```c++
// Get the top-most JS_ENTRY handler, cannot be on top if it doesn't exist.
Address entry_handler = Isolate::handler(thread_local_top());
if (entry_handler == kNullAddress) return false;
```
Next, he have the following:
```c++
if (!IsExternalHandlerOnTop(exception)) {
thread_local_top()->external_caught_exception_ = false;
return true;
}
```
I'm really confused at the moment with these different handler, we have one
for.


Now, for a javascript function that is executed using `Run` what would be
used in execution.cc Execution::Call would be:
```c++
Handle<Code> code = JSEntry(isolate, params.execution_target, params.is_construct);
```
```c++
Handle<Code> JSEntry(Isolate* isolate, Execution::Target execution_target, bool is_construct) {
if (is_construct) {
return BUILTIN_CODE(isolate, JSConstructEntry);
} else if (execution_target == Execution::Target::kCallable) {
return BUILTIN_CODE(isolate, JSEntry);
isolate->builtins()->builtin_handle(Builtins::kJSEntry)
} else if (execution_target == Execution::Target::kRunMicrotasks) {
return BUILTIN_CODE(isolate, JSRunMicrotasksEntry);
}
UNREACHABLE();
}
```

```c++
if (params.execution_target == Execution::Target::kCallable) {
// clang-format off
// {new_target}, {target}, {receiver}, return value: tagged pointers
// {argv}: pointer to array of tagged pointers
using JSEntryFunction = GeneratedCode<Address(
Address root_register_value, Address new_target, Address target,
Address receiver, intptr_t argc, Address** argv)>;
JSEntryFunction stub_entry =
JSEntryFunction::FromAddress(isolate, code->InstructionStart());
Address orig_func = params.new_target->ptr();
Address func = params.target->ptr();
Address recv = params.receiver->ptr();
Address** argv = reinterpret_cast<Address**>(params.argv);

RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::kJS_Execution);

value = Object(stub_entry.Call(isolate->isolate_data()->isolate_root(),
orig_func, func, recv, params.argc, argv));
```

### Address
`Address` can be found in `include/v8-internal.h`:

Expand Down

0 comments on commit ec5d964

Please sign in to comment.