1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #ifndef SRC_JS_NATIVE_API_V8_H_
17 #define SRC_JS_NATIVE_API_V8_H_
18
19 #include "jsvm_env.h"
20 #include "jsvm_util.h"
21 #include "type_conversion.h"
22
23 inline JSVM_Status SetLastError(JSVM_Env env,
24 JSVM_Status errorCode,
25 uint32_t engineErrorCode = 0,
26 void* engineReserved = nullptr)
27 {
28 env->lastError.errorCode = errorCode;
29 env->lastError.engineErrorCode = engineErrorCode;
30 env->lastError.engineReserved = engineReserved;
31 return errorCode;
32 }
33
34 #define RETURN_STATUS_IF_FALSE(env, condition, status) \
35 do { \
36 if (!(condition)) { \
37 return SetLastError((env), (status)); \
38 } \
39 } while (0)
40
41 #define RETURN_STATUS_IF_FALSE_WITHOUT_ENV(condition, status) \
42 do { \
43 if (!(condition)) { \
44 return status; \
45 } \
46 } while (0)
47
48 #define RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(env, condition, status) \
49 do { \
50 if (!(condition)) { \
51 return SetLastError((env), tryCatch.HasCaught() ? JSVM_PENDING_EXCEPTION : (status)); \
52 } \
53 } while (0)
54
55 #define RETURN_IF_EXCEPTION_HAS_CAUGHT(env) \
56 do { \
57 if (tryCatch.HasCaught()) { \
58 return SetLastError((env), JSVM_PENDING_EXCEPTION); \
59 } \
60 } while (0)
61
62 #define CHECK_ENV(env) \
63 do { \
64 if ((env) == nullptr) { \
65 return JSVM_INVALID_ARG; \
66 } \
67 } while (0)
68
69 #define CHECK_ENV_NOT_IN_GC(env) \
70 do { \
71 CHECK_ENV((env)); \
72 (env)->CheckGCAccess(); \
73 } while (0)
74
75 #define CHECK_ARG(env, arg) RETURN_STATUS_IF_FALSE((env), ((arg) != nullptr), JSVM_INVALID_ARG)
76
77 #define CHECK_ARG_WITHOUT_ENV(arg) RETURN_STATUS_IF_FALSE_WITHOUT_ENV(((arg) != nullptr), JSVM_INVALID_ARG)
78
79 #define CHECK_ARG_NOT_ZERO(env, arg) RETURN_STATUS_IF_FALSE((env), ((arg) != 0), JSVM_INVALID_ARG)
80
81 #define CHECK_ARG_WITH_PREAMBLE(env, arg) \
82 RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), ((arg) != nullptr), JSVM_INVALID_ARG)
83
84 #define CHECK_MAYBE_EMPTY(env, maybe, status) RETURN_STATUS_IF_FALSE((env), !((maybe).IsEmpty()), (status))
85
86 #define CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe, status) \
87 RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), !((maybe).IsEmpty()), (status))
88
89 #define CHECK_MAYBE_NOTHING(env, maybe, status) RETURN_STATUS_IF_FALSE((env), !((maybe).IsNothing()), (status))
90
91 #define CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, maybe, status) \
92 RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), !((maybe).IsNothing()), (status))
93
94 // JSVM_PREAMBLE is not wrapped in do..while: tryCatch must have function scope
95 #define JSVM_PREAMBLE(env) \
96 CHECK_ENV((env)); \
97 RETURN_STATUS_IF_FALSE((env), (env)->lastException.IsEmpty(), JSVM_PENDING_EXCEPTION); \
98 RETURN_STATUS_IF_FALSE((env), (env)->CanCallIntoJS(), \
99 ((env)->GetVersion() == JSVM_VERSION_EXPERIMENTAL ? JSVM_CANNOT_RUN_JS \
100 : JSVM_PENDING_EXCEPTION)); \
101 ClearLastError((env)); \
102 v8impl::TryCatch tryCatch((env))
103
104 #define CHECK_TO_TYPE(env, type, context, result, src, status) \
105 do { \
106 CHECK_ARG((env), (src)); \
107 auto maybe = v8impl::V8LocalValueFromJsValue((src))->To##type((context)); \
108 CHECK_MAYBE_EMPTY((env), maybe, (status)); \
109 (result) = maybe.ToLocalChecked(); \
110 } while (0)
111
112 #define CHECK_TO_TYPE_WITH_PREAMBLE(env, type, context, result, src, status) \
113 do { \
114 CHECK_ARG_WITH_PREAMBLE((env), (src)); \
115 auto maybe = v8impl::V8LocalValueFromJsValue((src))->To##type((context)); \
116 CHECK_MAYBE_EMPTY_WITH_PREAMBLE((env), maybe, (status)); \
117 (result) = maybe.ToLocalChecked(); \
118 } while (0)
119
120 #define CHECK_TO_FUNCTION(env, result, src) \
121 do { \
122 CHECK_ARG((env), (src)); \
123 v8::Local<v8::Value> v8value = v8impl::V8LocalValueFromJsValue((src)); \
124 RETURN_STATUS_IF_FALSE((env), v8value->IsFunction(), JSVM_INVALID_ARG); \
125 (result) = v8value.As<v8::Function>(); \
126 } while (0)
127
128 #define CHECK_TO_OBJECT(env, context, result, src) \
129 CHECK_TO_TYPE((env), Object, (context), (result), (src), JSVM_OBJECT_EXPECTED)
130
131 #define CHECK_TO_BIGINT(env, context, result, src) \
132 CHECK_TO_TYPE((env), BigInt, (context), (result), (src), JSVM_BIGINT_EXPECTED)
133
134 #define CHECK_TO_OBJECT_WITH_PREAMBLE(env, context, result, src) \
135 CHECK_TO_TYPE_WITH_PREAMBLE((env), Object, (context), (result), (src), JSVM_OBJECT_EXPECTED)
136
137 #define CHECK_TO_STRING(env, context, result, src) \
138 CHECK_TO_TYPE((env), String, (context), (result), (src), JSVM_STRING_EXPECTED)
139
140 #define CHECK_TO_NUMBER(env, context, result, src) \
141 CHECK_TO_TYPE((env), Number, (context), (result), (src), JSVM_NUMBER_EXPECTED)
142
143 #define GET_RETURN_STATUS(env) (!tryCatch.HasCaught() ? JSVM_OK : SetLastError((env), JSVM_PENDING_EXCEPTION))
144
145 #define THROW_RANGE_ERROR_IF_FALSE(env, condition, error, message) \
146 do { \
147 if (!(condition)) { \
148 OH_JSVM_ThrowRangeError((env), (error), (message)); \
149 return SetLastError((env), JSVM_GENERIC_FAILURE); \
150 } \
151 } while (0)
152
153 // jsvm-api defines JSVM_AUTO_LENGTH as the indicator that a string
154 // is null terminated. For V8 the equivalent is -1. The assert
155 // validates that our cast of JSVM_AUTO_LENGTH results in -1 as
156 // needed by V8.
157 #define CHECK_NEW_FROM_UTF8_LEN(env, result, str, len) \
158 do { \
159 static_assert(static_cast<int>(JSVM_AUTO_LENGTH) == -1, "Casting JSVM_AUTO_LENGTH to int must result in -1"); \
160 RETURN_STATUS_IF_FALSE((env), ((len) == JSVM_AUTO_LENGTH) || (len) <= INT_MAX, JSVM_INVALID_ARG); \
161 RETURN_STATUS_IF_FALSE((env), (str) != nullptr, JSVM_INVALID_ARG); \
162 auto str_maybe = \
163 v8::String::NewFromUtf8((env)->isolate, (str), v8::NewStringType::kInternalized, static_cast<int>(len)); \
164 CHECK_MAYBE_EMPTY((env), str_maybe, JSVM_GENERIC_FAILURE); \
165 (result) = str_maybe.ToLocalChecked(); \
166 } while (0)
167
168 #define CHECK_NEW_FROM_UTF8(env, result, str) CHECK_NEW_FROM_UTF8_LEN((env), (result), (str), JSVM_AUTO_LENGTH)
169
170 #define CHECK_NEW_STRING_ARGS(env, str, length, result) \
171 do { \
172 CHECK_ENV_NOT_IN_GC((env)); \
173 if ((length) > 0) \
174 CHECK_ARG((env), (str)); \
175 CHECK_ARG((env), (result)); \
176 RETURN_STATUS_IF_FALSE((env), ((length) == JSVM_AUTO_LENGTH) || (length) <= INT_MAX, JSVM_INVALID_ARG); \
177 } while (0)
178
179 #define CREATE_TYPED_ARRAY(env, type, sizeOfElement, buffer, byteOffset, length, out) \
180 do { \
181 if ((sizeOfElement) > 1) { \
182 THROW_RANGE_ERROR_IF_FALSE((env), (byteOffset) % (sizeOfElement) == 0, \
183 "ERR_JSVM_INVALID_TYPEDARRAY_ALIGNMENT", \
184 "start offset of " #type " should be a multiple of " #sizeOfElement); \
185 } \
186 THROW_RANGE_ERROR_IF_FALSE((env), (length) * (sizeOfElement) + (byteOffset) <= (buffer)->ByteLength(), \
187 "ERR_JSVM_INVALID_TYPEDARRAY_LENGTH", "Invalid typed array length"); \
188 (out) = v8::type::New((buffer), (byteOffset), (length)); \
189 } while (0)
190
191 #define JSVM_PRIVATE_KEY(isolate, suffix) (v8impl::GetIsolateData(isolate)->suffix##Key.Get(isolate))
192
193 #define STATUS_CALL(call) \
194 do { \
195 JSVM_Status status = (call); \
196 if (status != JSVM_OK) \
197 return status; \
198 } while (0)
199
200 namespace v8impl {
201
202 class TryCatch : public v8::TryCatch {
203 public:
TryCatch(JSVM_Env env)204 explicit TryCatch(JSVM_Env env) : v8::TryCatch(env->isolate), env(env) {}
205
~TryCatch()206 ~TryCatch()
207 {
208 if (HasCaught()) {
209 env->lastException.Reset(env->isolate, Exception());
210 }
211 }
212
213 private:
214 JSVM_Env env;
215 };
216
217 typedef JSVM_Value (*GetterCallback)(JSVM_Env, JSVM_Value, JSVM_Value, JSVM_Value);
218 typedef JSVM_Value (*SetterCallback)(JSVM_Env, JSVM_Value, JSVM_Value, JSVM_Value, JSVM_Value);
219 typedef JSVM_Value (*DeleterCallback)(JSVM_Env, JSVM_Value, JSVM_Value, JSVM_Value);
220 typedef JSVM_Value (*EnumeratorCallback)(JSVM_Env, JSVM_Value, JSVM_Value);
221
222 struct JSVM_PropertyHandlerCfgStruct {
223 GetterCallback namedGetterCallback;
224 SetterCallback namedSetterCallback;
225 DeleterCallback nameDeleterCallback;
226 EnumeratorCallback namedEnumeratorCallback;
227 GetterCallback indexedGetterCallback;
228 SetterCallback indexedSetterCallback;
229 DeleterCallback indexedDeleterCallback;
230 EnumeratorCallback indexedEnumeratorCallback;
231 JSVM_Ref namedPropertyData;
232 JSVM_Ref indexedPropertyData;
233 };
234
CreatePropertyCfg(JSVM_Env env,JSVM_PropertyHandlerCfg propertyCfg)235 inline JSVM_PropertyHandlerCfgStruct* CreatePropertyCfg(JSVM_Env env, JSVM_PropertyHandlerCfg propertyCfg)
236 {
237 JSVM_PropertyHandlerCfgStruct* newPropertyCfg = new JSVM_PropertyHandlerCfgStruct;
238 if (newPropertyCfg != nullptr && propertyCfg != nullptr) {
239 newPropertyCfg->namedGetterCallback = propertyCfg->genericNamedPropertyGetterCallback;
240 newPropertyCfg->namedSetterCallback = propertyCfg->genericNamedPropertySetterCallback;
241 newPropertyCfg->nameDeleterCallback = propertyCfg->genericNamedPropertyDeleterCallback;
242 newPropertyCfg->namedEnumeratorCallback = propertyCfg->genericNamedPropertyEnumeratorCallback;
243 newPropertyCfg->indexedGetterCallback = propertyCfg->genericIndexedPropertyGetterCallback;
244 newPropertyCfg->indexedSetterCallback = propertyCfg->genericIndexedPropertySetterCallback;
245 newPropertyCfg->indexedDeleterCallback = propertyCfg->genericIndexedPropertyDeleterCallback;
246 newPropertyCfg->indexedEnumeratorCallback = propertyCfg->genericIndexedPropertyEnumeratorCallback;
247 newPropertyCfg->namedPropertyData = nullptr;
248 newPropertyCfg->indexedPropertyData = nullptr;
249 if (propertyCfg->namedPropertyData != nullptr) {
250 v8::Local<v8::Value> v8_value = v8impl::V8LocalValueFromJsValue(propertyCfg->namedPropertyData);
251 v8impl::UserReference* reference = v8impl::UserReference::New(env, v8_value, 1);
252 newPropertyCfg->namedPropertyData = reinterpret_cast<JSVM_Ref>(reference);
253 }
254
255 if (propertyCfg->indexedPropertyData != nullptr) {
256 v8::Local<v8::Value> v8_value = v8impl::V8LocalValueFromJsValue(propertyCfg->indexedPropertyData);
257 v8impl::UserReference* reference = v8impl::UserReference::New(env, v8_value, 1);
258 newPropertyCfg->indexedPropertyData = reinterpret_cast<JSVM_Ref>(reference);
259 }
260 }
261
262 return newPropertyCfg;
263 }
264
CfgFinalizedCallback(JSVM_Env env,void * finalizeData,void * finalizeHint)265 inline void CfgFinalizedCallback(JSVM_Env env, void* finalizeData, void* finalizeHint)
266 {
267 auto cfg = reinterpret_cast<JSVM_PropertyHandlerCfgStruct*>(finalizeData);
268 if (cfg->namedPropertyData != nullptr) {
269 delete reinterpret_cast<v8impl::UserReference*>(cfg->namedPropertyData);
270 }
271 if (cfg->indexedPropertyData != nullptr) {
272 delete reinterpret_cast<v8impl::UserReference*>(cfg->indexedPropertyData);
273 }
274 delete cfg;
275 }
276
277 } // end of namespace v8impl
278
279 #endif // SRC_JS_NATIVE_API_V8_H_
280