-
-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathNativeScriptException.h
More file actions
175 lines (162 loc) · 9.14 KB
/
Copy pathNativeScriptException.h
File metadata and controls
175 lines (162 loc) · 9.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#ifndef NativeScriptException_h
#define NativeScriptException_h
#include <atomic>
#include <string>
#include <vector>
#include "Common.h"
namespace tns {
class NativeScriptException {
public:
NativeScriptException(const std::string& message);
NativeScriptException(v8::Isolate* isolate, v8::TryCatch& tc,
const std::string& message);
NativeScriptException(v8::Isolate* isolate, const std::string& message,
const std::string& name = "NativeScriptException");
// Carries a pre-built JS error object (e.g. one wrapping a native NSException
// via `nativeException`) so ReThrowToV8 surfaces exactly that object to the
// JS catch handler. `message` is used only for logging.
NativeScriptException(v8::Isolate* isolate, v8::Local<v8::Value> jsError,
const std::string& message);
~NativeScriptException();
void ReThrowToV8(v8::Isolate* isolate);
const std::string& getMessage() const { return message_; }
const std::string& getStackTrace() const { return stackTrace_; }
static void OnUncaughtError(v8::Local<v8::Message> message,
v8::Local<v8::Value> error);
// Isolate-level promise rejection callback (SetPromiseRejectCallback). Feeds
// the per-isolate PromiseRejectionTracker owned by Caches.
static void OnPromiseRejected(v8::PromiseRejectMessage message);
// Entry point for reporting an uncaught sync exception (message listener) and
// reportError. First dispatches an `error` ErrorEvent through the JS listener
// store; if a listener calls preventDefault() the report is fully handled and
// nothing else runs. Otherwise falls through to ReportFatalTail. `message`
// may be empty (rejections/reportError carry no v8::Message); `stackOverride`
// short-circuits stack derivation when non-empty; `logPrefix` distinguishes
// the log line while preserving the fatal-exception framing.
static void ReportToJsHandlersAndLog(v8::Isolate* isolate,
v8::Local<v8::Value> error,
v8::Local<v8::Message> message,
const std::string& stackOverride = "",
const std::string& logPrefix = "");
// Reports an unhandled promise rejection: dispatches a PromiseRejectionEvent
// (not an ErrorEvent) and, when unprevented, falls through to ReportFatalTail
// with the "Unhandled promise rejection:" prefix. Used by
// PromiseRejectionTracker::Drain on the main isolate.
static void ReportUnhandledRejection(v8::Isolate* isolate,
v8::Local<v8::Promise> promise,
v8::Local<v8::Value> reason,
const std::string& stackOverride = "");
// The terminal tail shared by the uncaught-error path, the rejection path and
// the JS `nativeReportFatal` handshake: calls the __on* shim and emits the
// fatal-exception log. Does NOT dispatch any event (the caller has already
// done so, or is reportError dispatching from JS), preventing recursion.
static void ReportFatalTail(v8::Isolate* isolate, v8::Local<v8::Value> error,
v8::Local<v8::Message> message,
const std::string& stackOverride = "",
const std::string& logPrefix = "");
// uncaughtErrorPolicy "throw" handoff slot (per isolate). When the policy is
// "throw", ReportFatalTail deposits the NSException to throw here (typed `id`
// so this header stays usable from the pure-C++ TUs that include it — mirrors
// ArgConverter.h) and schedules a deferred fallback throw. A JS→native
// boundary that originated the error claims the slot while still under its V8
// scopes and @throws the exception synchronously after scope teardown, so a
// native @try/@catch around the boundary can catch it (Android parity).
// Loop-originated errors (timers, microtasks, rejections) are never claimed
// and are thrown by the deferred fallback from a clean, scope-free frame.
//
// Deposits happen under the isolate lock (reporting is serialized); claims
// happen either at the boundary (under the Locker) or in the deferred
// fallback (mutex only). The identity-checked claim prevents a stale fallback
// block from throwing a newer deposit.
static void DepositPendingPolicyThrow(v8::Isolate* isolate, id exception);
// Returns and clears the isolate's pending policy throw, or nil.
static id ClaimPendingPolicyThrow(v8::Isolate* isolate);
// Claims only when the stored exception IS `expected` (pointer identity);
// used by the deferred fallback so an older block cannot steal/throw a newer
// deposit. Returns the claimed exception or nil.
static id ClaimPendingPolicyThrowIfEqual(v8::Isolate* isolate, id expected);
// Clears any pending policy throw for the isolate (called during teardown).
static void OnIsolateTeardown(v8::Isolate* isolate);
static std::string GetErrorStackTrace(
v8::Isolate* isolate, const v8::Local<v8::StackTrace>& stackTrace);
static void ShowErrorModal(v8::Isolate* isolate, const std::string& title,
const std::string& message,
const std::string& stackTrace);
static void SubmitConsoleErrorPayload(v8::Isolate* isolate,
const std::string& payload);
private:
v8::Persistent<v8::Value>* javascriptException_;
std::string name_;
std::string message_;
std::string stackTrace_;
std::string fullMessage_;
static std::string GetErrorMessage(v8::Isolate* isolate,
v8::Local<v8::Value>& error,
const std::string& prependMessage = "");
static std::string GetFullMessage(v8::Isolate* isolate,
const v8::TryCatch& tc,
const std::string& jsExceptionMessage);
static std::string GetFullMessage(v8::Isolate* isolate,
v8::Local<v8::Message> message,
const std::string& jsExceptionMessage);
};
// A promise that was rejected without a handler at a microtask checkpoint.
// `reported` records that the rejection already reached the reporting tail;
// Phase 1 discards reported entries, but the flag is retained so Phase 2 can
// fire `rejectionhandled` for a handler added after reporting.
struct PendingRejection {
v8::Global<v8::Promise> promise;
v8::Global<v8::Value> reason;
bool reported = false;
};
// Per-isolate tracker for unhandled promise rejections, owned by Caches.
// Accessed only from the isolate's own thread (the reject callback runs during
// the microtask checkpoint, the drain runs on the same runtime loop), so no
// locking is required.
class PromiseRejectionTracker {
public:
explicit PromiseRejectionTracker(v8::Isolate* isolate) : isolate_(isolate) {}
void OnReject(v8::Local<v8::Promise> promise, v8::Local<v8::Value> reason);
void OnHandlerAdded(v8::Local<v8::Promise> promise);
void Drain(v8::Local<v8::Context> context);
// Safe to call without holding the isolate lock: OnReject can run on any
// thread that holds the v8::Locker (e.g. a background-thread rejection), so
// the runloop observer polls this atomic instead of touching pending_.
bool HasPending() const {
return pendingCount_.load(std::memory_order_acquire) != 0;
}
private:
// The observer polls this atomic to decide whether a drain is needed. Both
// the still-to-report rejections and the queued rejectionhandled events count
// as work; reportedOutstanding_ does not (it only waits for a late handler).
void SyncPendingCount() {
pendingCount_.store(pending_.size() + pendingRejectionHandled_.size(),
std::memory_order_release);
}
// Drop weak handles the GC has already cleared.
void PruneReportedOutstanding();
// A rejection that was already reported (unhandledrejection fired, prevented
// or not). The original reason is retained so a late rejectionhandled event
// carries it per spec; it is released when the entry is pruned or fired.
struct ReportedRejection {
v8::Global<v8::Promise> promise;
v8::Global<v8::Value> reason;
};
v8::Isolate* isolate_;
std::vector<PendingRejection> pending_;
// Reported rejections still outstanding. The promise handle is phantom-weak
// (SetWeak) so a GC'd promise drops the whole entry (reason included); a
// handler added later moves the entry into pendingRejectionHandled_.
std::vector<ReportedRejection> reportedOutstanding_;
// rejectionhandled events queued by OnHandlerAdded (which runs during a
// microtask checkpoint) to fire as a task on the next drain, per spec. Held
// strong so the promise survives until the event fires.
std::vector<ReportedRejection> pendingRejectionHandled_;
std::atomic<size_t> pendingCount_{0};
// Rejections that arrive while a drain is in progress are deferred to the
// next turn: Drain snapshots and clears `pending_` before iterating, so any
// OnReject during reporting accumulates into a fresh vector.
bool draining_ = false;
};
} // namespace tns
#endif /* NativeScriptException_h */