File
Blob: src/workerd/jsg/jsvalue.c++
| 1 | #include "jsvalue.h" |
| 2 | |
| 3 | #include "buffersource.h" |
| 4 | #include "ser.h" |
| 5 | #include "simdutf.h" |
| 6 | |
| 7 | #include <v8.h> |
| 8 | |
| 9 | #include <kj/string-tree.h> |
| 10 | #include <kj/string.h> |
| 11 | |
| 12 | #include <cmath> |
| 13 | |
| 14 | namespace workerd::jsg { |
| 15 | |
| 16 | JsValue::JsValue(v8::Local<v8::Value> inner): inner(inner) { |
| 17 | requireOnStack(this); |
| 18 | } |
| 19 | |
| 20 | bool JsValue::operator==(const JsValue& other) const { |
| 21 | return inner == other.inner; |
| 22 | } |
| 23 | |
| 24 | bool JsValue::strictEquals(const JsValue& other) const { |
| 25 | return inner->StrictEquals(other.inner); |
| 26 | } |
| 27 | |
| 28 | JsMap::operator JsObject() { |
| 29 | return JsObject(inner); |
| 30 | } |
| 31 | |
| 32 | void JsMap::set(Lock& js, const JsValue& name, const JsValue& value) { |
| 33 | check(inner->Set(js.v8Context(), name.inner, value.inner)); |
| 34 | } |
| 35 | |
| 36 | void JsMap::set(Lock& js, kj::StringPtr name, const JsValue& value) { |
| 37 | set(js, js.strIntern(name), value); |
| 38 | } |
| 39 | |
| 40 | JsValue JsMap::get(Lock& js, const JsValue& name) { |
| 41 | return JsValue(check(inner->Get(js.v8Context(), name.inner))); |
| 42 | } |
| 43 | |
| 44 | JsValue JsMap::get(Lock& js, kj::StringPtr name) { |
| 45 | return get(js, js.strIntern(name)); |
| 46 | } |
| 47 | |
| 48 | bool JsMap::has(Lock& js, const JsValue& name) { |
| 49 | return check(inner->Has(js.v8Context(), name.inner)); |
| 50 | } |
| 51 | |
| 52 | bool JsMap::has(Lock& js, kj::StringPtr name) { |
| 53 | return has(js, js.strIntern(name)); |
| 54 | } |
| 55 | |
| 56 | void JsMap::delete_(Lock& js, const JsValue& name) { |
| 57 | check(inner->Delete(js.v8Context(), name.inner)); |
| 58 | } |
| 59 | |
| 60 | void JsMap::delete_(Lock& js, kj::StringPtr name) { |
| 61 | delete_(js, js.strIntern(name)); |
| 62 | } |
| 63 | |
| 64 | void JsObject::defineProperty(Lock& js, kj::StringPtr name, const JsValue& value) { |
| 65 | v8::Local<v8::String> nameStr = js.strIntern(name); |
| 66 | check(inner->DefineOwnProperty(js.v8Context(), nameStr, value)); |
| 67 | } |
| 68 | |
| 69 | void JsObject::setReadOnly(Lock& js, kj::StringPtr name, const JsValue& value) { |
| 70 | v8::Local<v8::String> nameStr = js.strIntern(name); |
| 71 | check(inner->DefineOwnProperty(js.v8Context(), nameStr, value, |
| 72 | static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete))); |
| 73 | } |
| 74 | |
| 75 | void JsObject::setNonEnumerable(Lock& js, const JsSymbol& name, const JsValue& value) { |
| 76 | check(inner->DefineOwnProperty( |
| 77 | js.v8Context(), name.inner, value.inner, v8::PropertyAttribute::DontEnum)); |
| 78 | } |
| 79 | |
| 80 | void JsObject::setPrivate(Lock& js, kj::StringPtr name, const JsValue& value) { |
| 81 | auto p = v8::Private::ForApi(js.v8Isolate, v8StrIntern(js.v8Isolate, name)); |
| 82 | check(inner->SetPrivate(js.v8Context(), p, value.inner)); |
| 83 | } |
| 84 | |
| 85 | JsValue JsObject::getPrivate(Lock& js, kj::StringPtr name) { |
| 86 | auto p = v8::Private::ForApi(js.v8Isolate, v8StrIntern(js.v8Isolate, name)); |
| 87 | return JsValue(check(inner->GetPrivate(js.v8Context(), p))); |
| 88 | } |
| 89 | |
| 90 | bool JsObject::hasPrivate(Lock& js, kj::StringPtr name) { |
| 91 | auto p = v8::Private::ForApi(js.v8Isolate, v8StrIntern(js.v8Isolate, name)); |
| 92 | return check(inner->HasPrivate(js.v8Context(), p)); |
| 93 | } |
| 94 | |
| 95 | int JsObject::hashCode() const { |
| 96 | return kj::hashCode(inner->GetIdentityHash()); |
| 97 | } |
| 98 | |
| 99 | kj::String JsObject::getConstructorName() { |
| 100 | return kj::str(inner->GetConstructorName()); |
| 101 | } |
| 102 | |
| 103 | JsArray JsObject::getPropertyNames(Lock& js, |
| 104 | KeyCollectionFilter keyFilter, |
| 105 | PropertyFilter propertyFilter, |
| 106 | IndexFilter indexFilter) { |
| 107 | auto v8keyFilter = keyFilter == KeyCollectionFilter::INCLUDE_PROTOTYPES |
| 108 | ? v8::KeyCollectionMode::kIncludePrototypes |
| 109 | : v8::KeyCollectionMode::kOwnOnly; |
| 110 | auto v8PropertyFilter = static_cast<v8::PropertyFilter>(propertyFilter); |
| 111 | auto v8IndexFilter = indexFilter == IndexFilter::INCLUDE_INDICES |
| 112 | ? v8::IndexFilter::kIncludeIndices |
| 113 | : v8::IndexFilter::kSkipIndices; |
| 114 | return JsArray( |
| 115 | check(inner->GetPropertyNames(js.v8Context(), v8keyFilter, v8PropertyFilter, v8IndexFilter))); |
| 116 | } |
| 117 | |
| 118 | JsArray JsObject::previewEntries(bool* isKeyValue) { |
| 119 | return JsArray(check(inner->PreviewEntries(isKeyValue))); |
| 120 | } |
| 121 | |
| 122 | void JsObject::recursivelyFreeze(Lock& js) { |
| 123 | jsg::recursivelyFreeze(js.v8Context(), inner); |
| 124 | } |
| 125 | |
| 126 | void JsObject::seal(Lock& js) { |
| 127 | check(inner->SetIntegrityLevel(js.v8Context(), v8::IntegrityLevel::kSealed)); |
| 128 | } |
| 129 | |
| 130 | JsObject JsObject::jsonClone(Lock& js) { |
| 131 | auto tmp = JsValue(inner).toJson(js); |
| 132 | auto obj = KJ_ASSERT_NONNULL(JsValue::fromJson(js, tmp).tryCast<jsg::JsObject>()); |
| 133 | return JsObject(obj); |
| 134 | } |
| 135 | |
| 136 | JsValue JsObject::getPrototype(Lock& js) { |
| 137 | if (inner->IsProxy()) { |
| 138 | // Here we emulate the behavior of v8's GetPrototype() function for proxies. |
| 139 | // If the proxy has a getPrototypeOf trap, we call it and return the result. |
| 140 | // Otherwise we return the prototype of the target object. |
| 141 | // Note that we do not check if the target object is extensible or not, or |
| 142 | // if the returned prototype is consistent with the target's prototype if |
| 143 | // the target is not extensible. See the comment below for more details. |
| 144 | auto proxy = inner.As<v8::Proxy>(); |
| 145 | JSG_REQUIRE(!proxy->IsRevoked(), TypeError, "Proxy is revoked"); |
| 146 | auto handler = proxy->GetHandler(); |
| 147 | JSG_REQUIRE(handler->IsObject(), TypeError, "Proxy handler is not an object"); |
| 148 | auto jsHandler = JsObject(handler.As<v8::Object>()); |
| 149 | auto trap = jsHandler.get(js, "getPrototypeOf"_kj); |
| 150 | auto target = proxy->GetTarget(); |
| 151 | if (trap.isUndefined()) { |
| 152 | JSG_REQUIRE(target->IsObject(), TypeError, "Proxy target is not an object"); |
| 153 | // Run this through getPrototype to handle the case where the target is also a proxy. |
| 154 | return JsObject(target.As<v8::Object>()).getPrototype(js); |
| 155 | } |
| 156 | JSG_REQUIRE(trap.isFunction(), TypeError, "Proxy getPrototypeOf trap is not a function"); |
| 157 | v8::Local<v8::Function> fn = ((v8::Local<v8::Value>)trap).As<v8::Function>(); |
| 158 | v8::Local<v8::Value> args[] = {target}; |
| 159 | auto ret = JsValue(check(fn->Call(js.v8Context(), jsHandler.inner, 1, args))); |
| 160 | JSG_REQUIRE(ret.isObject() || ret.isNull(), TypeError, |
| 161 | "Proxy getPrototypeOf trap did not return an object or null"); |
| 162 | // TODO(maybe): V8 performs additional checks on the returned value to |
| 163 | // see if the proxy and the target are extensible or not, and if the |
| 164 | // returned prototype is consistent with the target's prototype if they |
| 165 | // are not extensible. To strictly match v8's behavior we should do the |
| 166 | // same but (a) v8 does not expose the necessary APIs to do so, and (b) |
| 167 | // it is not clear if we actually need to perform the additional check |
| 168 | // given how we are currently using this function. |
| 169 | return ret; |
| 170 | } |
| 171 | #if V8_MAJOR_VERSION >= 15 || (V8_MAJOR_VERSION == 14 && V8_MINOR_VERSION >= 7) |
| 172 | return JsValue(inner->GetPrototype()); |
| 173 | #else |
| 174 | // TODO(cleanup): Remove when unnecessary. |
| 175 | return JsValue(inner->GetPrototypeV2()); |
| 176 | #endif |
| 177 | } |
| 178 | |
| 179 | kj::String JsSymbol::description(Lock& js) const { |
| 180 | auto desc = inner->Description(js.v8Isolate); |
| 181 | if (desc.IsEmpty() || desc->IsUndefined()) { |
| 182 | return kj::String(); |
| 183 | } |
| 184 | return kj::str(desc); |
| 185 | } |
| 186 | |
| 187 | void JsSet::add(Lock& js, const JsValue& value) { |
| 188 | check(inner->Add(js.v8Context(), value.inner)); |
| 189 | } |
| 190 | |
| 191 | bool JsSet::has(Lock& js, const JsValue& value) const { |
| 192 | return check(inner->Has(js.v8Context(), value.inner)); |
| 193 | } |
| 194 | |
| 195 | bool JsSet::delete_(Lock& js, const JsValue& value) { |
| 196 | return check(inner->Delete(js.v8Context(), value.inner)); |
| 197 | } |
| 198 | |
| 199 | void JsSet::addAll(Lock& js, kj::ArrayPtr<const JsValue> values) { |
| 200 | for (const JsValue& value: values) { |
| 201 | check(inner->Add(js.v8Context(), value.inner)); |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | void JsSet::clear() { |
| 206 | inner->Clear(); |
| 207 | } |
| 208 | |
| 209 | size_t JsSet::size() const { |
| 210 | return inner->Size(); |
| 211 | } |
| 212 | |
| 213 | JsSet::operator JsArray() const { |
| 214 | return JsArray(inner->AsArray()); |
| 215 | } |
| 216 | |
| 217 | kj::Maybe<int32_t> JsInt32::value(Lock& js) const { |
| 218 | KJ_ASSERT(!inner.IsEmpty()); |
| 219 | int32_t value; |
| 220 | // The Int32Value(...) operation can fail with a JS exception, in which case |
| 221 | // we return kj::none and the error should be allowed to propagate. |
| 222 | if (inner->Int32Value(js.v8Context()).To(&value)) { |
| 223 | return value; |
| 224 | } |
| 225 | return kj::none; |
| 226 | } |
| 227 | |
| 228 | kj::Maybe<uint32_t> JsUint32::value(Lock& js) const { |
| 229 | KJ_ASSERT(!inner.IsEmpty()); |
| 230 | uint32_t value; |
| 231 | // The Uint32Value(...) operation can fail with a JS exception, in which case |
| 232 | // we return kj::none and the error should be allowed to propagate. |
| 233 | if (inner->Uint32Value(js.v8Context()).To(&value)) { |
| 234 | return value; |
| 235 | } |
| 236 | return kj::none; |
| 237 | }; |
| 238 | |
| 239 | kj::Maybe<int64_t> JsBigInt::toInt64(Lock& js) const { |
| 240 | KJ_ASSERT(!inner.IsEmpty()); |
| 241 | bool lossless = false; |
| 242 | int64_t value = inner->Int64Value(&lossless); |
| 243 | if (!lossless) { |
| 244 | js.v8Isolate->ThrowException(js.rangeError("BigInt value does not fit in int64_t")); |
| 245 | return kj::none; |
| 246 | } |
| 247 | return value; |
| 248 | } |
| 249 | |
| 250 | kj::Maybe<uint64_t> JsBigInt::toUint64(Lock& js) const { |
| 251 | KJ_ASSERT(!inner.IsEmpty()); |
| 252 | bool lossless = false; |
| 253 | uint64_t value = inner->Uint64Value(&lossless); |
| 254 | if (!lossless) { |
| 255 | js.v8Isolate->ThrowException(js.rangeError("BigInt value does not fit in uint64_t")); |
| 256 | return kj::none; |
| 257 | } |
| 258 | return value; |
| 259 | } |
| 260 | |
| 261 | kj::Maybe<double> JsNumber::value(Lock& js) const { |
| 262 | KJ_ASSERT(!inner.IsEmpty()); |
| 263 | double value; |
| 264 | // The NumberValue(...) operation can fail with a JS exception, in which case |
| 265 | // we return kj::none and the error should be allowed to propagate. |
| 266 | if (inner->NumberValue(js.v8Context()).To(&value)) { |
| 267 | return value; |
| 268 | } |
| 269 | return kj::none; |
| 270 | } |
| 271 | |
| 272 | // ECMA-262, 15th edition, 21.1.2.5. Number.isSafeInteger |
| 273 | bool JsNumber::isSafeInteger(Lock& js) const { |
| 274 | KJ_ASSERT(!inner.IsEmpty()); |
| 275 | if (!inner->IsNumber()) return false; |
| 276 | KJ_IF_SOME(value, value(js)) { |
| 277 | if (std::isnan(value) || std::isinf(value) || std::trunc(value) != value) return false; |
| 278 | if (std::abs(value) <= static_cast<double>(MAX_SAFE_INTEGER)) return true; |
| 279 | } |
| 280 | return false; |
| 281 | } |
| 282 | |
| 283 | kj::Maybe<double> JsNumber::toSafeInteger(Lock& js) const { |
| 284 | if (isSafeInteger(js)) { |
| 285 | return inner.As<v8::Number>()->Value(); |
| 286 | } |
| 287 | return kj::none; |
| 288 | } |
| 289 | |
| 290 | bool JsValue::isTruthy(Lock& js) const { |
| 291 | KJ_ASSERT(!inner.IsEmpty()); |
| 292 | return inner->BooleanValue(js.v8Isolate); |
| 293 | } |
| 294 | |
| 295 | kj::String JsValue::toString(Lock& js) const { |
| 296 | KJ_ASSERT(!inner.IsEmpty()); |
| 297 | return kj::str(inner); |
| 298 | } |
| 299 | |
| 300 | JsString JsValue::toJsString(Lock& js) const { |
| 301 | KJ_ASSERT(!inner.IsEmpty()); |
| 302 | return JsString(check(inner->ToString(js.v8Context()))); |
| 303 | } |
| 304 | |
| 305 | kj::String JsValue::typeOf(Lock& js) const { |
| 306 | KJ_ASSERT(!inner.IsEmpty()); |
| 307 | return kj::str(inner->TypeOf(js.v8Isolate)); |
| 308 | } |
| 309 | |
| 310 | #define V(Type) \ |
| 311 | bool JsValue::is##Type() const { \ |
| 312 | return inner->Is##Type(); \ |
| 313 | } |
| 314 | JS_IS_TYPES(V) |
| 315 | #undef V |
| 316 | |
| 317 | kj::String JsValue::toJson(Lock& js) const { |
| 318 | return kj::str(check(v8::JSON::Stringify(js.v8Context(), inner))); |
| 319 | } |
| 320 | |
| 321 | JsValue JsValue::fromJson(Lock& js, kj::ArrayPtr<const char> input) { |
| 322 | return JsValue(check(v8::JSON::Parse(js.v8Context(), js.str(input)))); |
| 323 | } |
| 324 | |
| 325 | JsValue JsValue::fromJson(Lock& js, const JsValue& input) { |
| 326 | return JsValue(check(v8::JSON::Parse(js.v8Context(), input.inner.As<v8::String>()))); |
| 327 | } |
| 328 | |
| 329 | bool JsBoolean::value(Lock& js) const { |
| 330 | return inner->BooleanValue(js.v8Isolate); |
| 331 | } |
| 332 | |
| 333 | uint32_t JsArray::size() const { |
| 334 | return inner->Length(); |
| 335 | } |
| 336 | |
| 337 | JsValue JsArray::get(Lock& js, uint32_t i) const { |
| 338 | return JsValue(check(inner->Get(js.v8Context(), i))); |
| 339 | } |
| 340 | |
| 341 | void JsArray::add(Lock& js, const JsValue& value) { |
| 342 | check(inner->Set(js.v8Context(), size(), value.inner)); |
| 343 | } |
| 344 | |
| 345 | JsArray::operator JsObject() const { |
| 346 | return JsObject(inner.As<v8::Object>()); |
| 347 | } |
| 348 | |
| 349 | kj::String JsString::toString(jsg::Lock& js) const { |
| 350 | auto buf = kj::heapArray<char>(inner->Utf8LengthV2(js.v8Isolate) + 1); |
| 351 | inner->WriteUtf8V2(js.v8Isolate, buf.begin(), buf.size(), v8::String::WriteFlags::kNullTerminate); |
| 352 | return kj::String(kj::mv(buf)); |
| 353 | } |
| 354 | |
| 355 | jsg::USVString JsString::toUSVString(Lock& js) const { |
| 356 | auto buf = kj::heapArray<char>(inner->Utf8LengthV2(js.v8Isolate) + 1); |
| 357 | inner->WriteUtf8V2(js.v8Isolate, buf.begin(), buf.size(), |
| 358 | v8::String::WriteFlags::kNullTerminate | v8::String::WriteFlags::kReplaceInvalidUtf8); |
| 359 | return jsg::USVString(kj::mv(buf)); |
| 360 | } |
| 361 | |
| 362 | jsg::DOMString JsString::toDOMString(Lock& js) const { |
| 363 | auto buf = kj::heapArray<char>(inner->Utf8LengthV2(js.v8Isolate) + 1); |
| 364 | inner->WriteUtf8V2(js.v8Isolate, buf.begin(), buf.size(), v8::String::WriteFlags::kNullTerminate); |
| 365 | return jsg::DOMString(kj::mv(buf)); |
| 366 | } |
| 367 | |
| 368 | int JsString::hashCode() const { |
| 369 | return kj::hashCode(inner->GetIdentityHash()); |
| 370 | } |
| 371 | |
| 372 | JsString JsString::concat(Lock& js, const JsString& one, const JsString& two) { |
| 373 | return JsString(v8::String::Concat(js.v8Isolate, one.inner, two.inner)); |
| 374 | } |
| 375 | |
| 376 | bool JsString::operator==(const JsString& other) const { |
| 377 | return inner->StringEquals(other.inner); |
| 378 | } |
| 379 | |
| 380 | JsString JsString::internalize(Lock& js) const { |
| 381 | return JsString(inner->InternalizeString(js.v8Isolate)); |
| 382 | } |
| 383 | |
| 384 | JsString::WriteIntoStatus JsString::writeInto( |
| 385 | Lock& js, kj::ArrayPtr<char> buffer, WriteFlags options) const { |
| 386 | WriteIntoStatus result = {0, 0}; |
| 387 | if (buffer.size() > 0) { |
| 388 | result.written = |
| 389 | inner->WriteUtf8V2(js.v8Isolate, buffer.begin(), buffer.size(), options, &result.read); |
| 390 | } |
| 391 | return result; |
| 392 | } |
| 393 | |
| 394 | JsString::WriteIntoStatus JsString::writeInto( |
| 395 | Lock& js, kj::ArrayPtr<uint16_t> buffer, WriteFlags options) const { |
| 396 | WriteIntoStatus result = {0, 0}; |
| 397 | if (buffer.size() > 0) { |
| 398 | result.written = kj::min(buffer.size(), length(js)); |
| 399 | inner->WriteV2(js.v8Isolate, 0, result.written, buffer.begin(), options); |
| 400 | result.read = length(js); |
| 401 | } |
| 402 | return result; |
| 403 | } |
| 404 | |
| 405 | JsString::WriteIntoStatus JsString::writeInto( |
| 406 | Lock& js, kj::ArrayPtr<kj::byte> buffer, WriteFlags options) const { |
| 407 | WriteIntoStatus result = {0, 0}; |
| 408 | if (buffer.size() > 0) { |
| 409 | result.written = kj::min(buffer.size(), length(js)); |
| 410 | inner->WriteOneByteV2( |
| 411 | js.v8Isolate, 0, kj::min(length(js), buffer.size()), buffer.begin(), options); |
| 412 | result.read = length(js); |
| 413 | } |
| 414 | return result; |
| 415 | } |
| 416 | |
| 417 | bool JsString::isFlat() const { |
| 418 | return inner->IsFlat(); |
| 419 | } |
| 420 | |
| 421 | bool JsString::containsOnlyOneByte() const { |
| 422 | return inner->ContainsOnlyOneByte(); |
| 423 | } |
| 424 | |
| 425 | kj::Maybe<JsArray> JsRegExp::operator()(Lock& js, const JsString& input) const { |
| 426 | auto result = check(inner->Exec(js.v8Context(), input)); |
| 427 | if (result->IsNullOrUndefined()) return kj::none; |
| 428 | return JsArray(result.As<v8::Array>()); |
| 429 | } |
| 430 | |
| 431 | kj::Maybe<JsArray> JsRegExp::operator()(Lock& js, kj::StringPtr input) const { |
| 432 | auto result = check(inner->Exec(js.v8Context(), js.str(input))); |
| 433 | if (result->IsNull()) return kj::none; |
| 434 | return JsArray(result.As<v8::Array>()); |
| 435 | } |
| 436 | |
| 437 | bool JsRegExp::match(Lock& js, kj::StringPtr input) { |
| 438 | auto result = check(inner->Exec(js.v8Context(), js.str(input))); |
| 439 | return !result->IsNull(); |
| 440 | } |
| 441 | |
| 442 | kj::String JsDate::toUTCString(jsg::Lock& js) const { |
| 443 | JsString str(inner->ToUTCString()); |
| 444 | return str.toString(js); |
| 445 | } |
| 446 | |
| 447 | kj::String JsDate::toISOString(jsg::Lock& js) const { |
| 448 | JsString str(inner->ToISOString()); |
| 449 | return str.toString(js); |
| 450 | } |
| 451 | |
| 452 | JsDate::operator kj::Date() const { |
| 453 | return kj::UNIX_EPOCH + (int64_t(inner->ValueOf()) * kj::MILLISECONDS); |
| 454 | } |
| 455 | |
| 456 | JsRegExp Lock::regexp(kj::StringPtr str, RegExpFlags flags, kj::Maybe<uint32_t> backtrackLimit) { |
| 457 | KJ_IF_SOME(limit, backtrackLimit) { |
| 458 | return JsRegExp(check(v8::RegExp::NewWithBacktrackLimit( |
| 459 | v8Context(), v8Str(v8Isolate, str), static_cast<v8::RegExp::Flags>(flags), limit))); |
| 460 | } |
| 461 | return JsRegExp(check( |
| 462 | v8::RegExp::New(v8Context(), v8Str(v8Isolate, str), static_cast<v8::RegExp::Flags>(flags)))); |
| 463 | } |
| 464 | |
| 465 | JsObject Lock::obj(kj::ArrayPtr<const kj::StringPtr> keys, kj::ArrayPtr<JsValue> values) { |
| 466 | KJ_DASSERT(keys.size() == values.size()); |
| 467 | v8::LocalVector<v8::Name> keys_(v8Isolate, keys.size()); |
| 468 | v8::LocalVector<v8::Value> values_(v8Isolate, keys.size()); |
| 469 | for (size_t i = 0; i < keys.size(); i++) { |
| 470 | keys_[i] = strIntern(keys[i]).inner; |
| 471 | values_[i] = values[i]; |
| 472 | } |
| 473 | return JsObject(v8::Object::New( |
| 474 | v8Isolate, v8::Object::New(v8Isolate), keys_.data(), values_.data(), keys.size())); |
| 475 | } |
| 476 | |
| 477 | JsObject Lock::objNoProto(kj::ArrayPtr<kj::StringPtr> keys, kj::ArrayPtr<JsValue> values) { |
| 478 | KJ_DASSERT(keys.size() == values.size()); |
| 479 | v8::LocalVector<v8::Name> keys_(v8Isolate, keys.size()); |
| 480 | v8::LocalVector<v8::Value> values_(v8Isolate, keys.size()); |
| 481 | for (size_t i = 0; i < keys.size(); i++) { |
| 482 | keys_[i] = strIntern(keys[i]).inner; |
| 483 | values_[i] = values[i]; |
| 484 | } |
| 485 | return JsObject( |
| 486 | v8::Object::New(v8Isolate, v8::Null(v8Isolate), keys_.data(), values_.data(), keys.size())); |
| 487 | } |
| 488 | |
| 489 | JsArray Lock::arr(kj::ArrayPtr<JsValue> values) { |
| 490 | v8::LocalVector<v8::Value> items(v8Isolate, values.size()); |
| 491 | for (size_t n = 0; n < values.size(); n++) { |
| 492 | items[n] = values[n]; |
| 493 | } |
| 494 | return JsArray(v8::Array::New(v8Isolate, items.data(), items.size())); |
| 495 | } |
| 496 | |
| 497 | #define V(Name) \ |
| 498 | JsSymbol Lock::symbol##Name() { \ |
| 499 | return JsSymbol(v8::Symbol::Get##Name(v8Isolate)); \ |
| 500 | } |
| 501 | JS_V8_SYMBOLS(V) |
| 502 | #undef V |
| 503 | |
| 504 | JsDate Lock::date(kj::StringPtr date) { |
| 505 | v8::Local<v8::Value> converted = check(v8::Date::Parse(v8Context(), str(date))); |
| 506 | KJ_REQUIRE(converted->IsDate()); |
| 507 | return JsDate(converted.As<v8::Date>()); |
| 508 | } |
| 509 | |
| 510 | JsPromise Lock::rejectedJsPromise(jsg::JsValue exception) { |
| 511 | v8::EscapableHandleScope handleScope(v8Isolate); |
| 512 | auto context = v8Context(); |
| 513 | auto resolver = check(v8::Promise::Resolver::New(context)); |
| 514 | check(resolver->Reject(context, exception)); |
| 515 | return JsPromise(handleScope.Escape(resolver->GetPromise())); |
| 516 | } |
| 517 | |
| 518 | JsPromise Lock::rejectedJsPromise(kj::Exception&& exception, ExceptionToJsOptions options) { |
| 519 | return rejectedJsPromise(exceptionToJsValue(kj::mv(exception), options).getHandle(*this)); |
| 520 | } |
| 521 | |
| 522 | JsPromise Lock::resolvedJsPromise(jsg::JsValue value) { |
| 523 | v8::EscapableHandleScope handleScope(v8Isolate); |
| 524 | auto context = v8Context(); |
| 525 | auto resolver = check(v8::Promise::Resolver::New(context)); |
| 526 | check(resolver->Resolve(context, value)); |
| 527 | return JsPromise(handleScope.Escape(resolver->GetPromise())); |
| 528 | } |
| 529 | |
| 530 | PromiseState JsPromise::state() { |
| 531 | switch (inner->State()) { |
| 532 | case v8::Promise::PromiseState::kPending: |
| 533 | return PromiseState::PENDING; |
| 534 | case v8::Promise::PromiseState::kFulfilled: |
| 535 | return PromiseState::FULFILLED; |
| 536 | case v8::Promise::PromiseState::kRejected: |
| 537 | return PromiseState::REJECTED; |
| 538 | } |
| 539 | KJ_UNREACHABLE |
| 540 | } |
| 541 | |
| 542 | JsValue JsPromise::result() { |
| 543 | return JsValue(inner->Result()); |
| 544 | } |
| 545 | |
| 546 | JsValue JsProxy::target() { |
| 547 | return JsValue(inner->GetTarget()); |
| 548 | } |
| 549 | |
| 550 | JsValue JsProxy::handler() { |
| 551 | return JsValue(inner->GetHandler()); |
| 552 | } |
| 553 | |
| 554 | JsRef<JsValue> JsValue::addRef(Lock& js) { |
| 555 | return JsRef<JsValue>(js, *this); |
| 556 | } |
| 557 | |
| 558 | JsValue JsValue::structuredClone(Lock& js, kj::Maybe<kj::Array<JsValue>> maybeTransfers) { |
| 559 | return jsg::structuredClone(js, *this, kj::mv(maybeTransfers)); |
| 560 | } |
| 561 | |
| 562 | JsMessage JsMessage::create(Lock& js, const JsValue& exception) { |
| 563 | return JsMessage(v8::Exception::CreateMessage(js.v8Isolate, exception)); |
| 564 | } |
| 565 | |
| 566 | void JsMessage::addJsStackTrace(Lock& js, kj::Vector<kj::String>& lines) { |
| 567 | if (inner.IsEmpty()) return; |
| 568 | |
| 569 | // TODO(someday): Relying on v8::Message to pass around source locations means |
| 570 | // we can't provide the module name for errors like compiling wasm modules. We |
| 571 | // should have our own type, but it requires a refactor of how we pass around errors |
| 572 | // for script startup. |
| 573 | |
| 574 | static constexpr auto addLineCol = [](kj::StringTree str, int line, int col) { |
| 575 | if (line != v8::Message::kNoLineNumberInfo) { |
| 576 | str = kj::strTree(kj::mv(str), ":", line); |
| 577 | if (col != v8::Message::kNoColumnInfo) { |
| 578 | str = kj::strTree(kj::mv(str), ":", col); |
| 579 | } |
| 580 | } |
| 581 | return str; |
| 582 | }; |
| 583 | |
| 584 | auto context = js.v8Context(); |
| 585 | auto trace = inner->GetStackTrace(); |
| 586 | if (trace.IsEmpty() || trace->GetFrameCount() == 0) { |
| 587 | kj::StringTree locationStr; |
| 588 | |
| 589 | auto resourceNameVal = inner->GetScriptResourceName(); |
| 590 | if (resourceNameVal->IsString()) { |
| 591 | auto resourceName = resourceNameVal.As<v8::String>(); |
| 592 | if (!resourceName.IsEmpty() && resourceName->Length() != 0) { |
| 593 | locationStr = kj::strTree(" at ", resourceName); |
| 594 | } |
| 595 | } |
| 596 | |
| 597 | auto lineNumber = jsg::check(inner->GetLineNumber(context)); |
| 598 | auto columnNumber = jsg::check(inner->GetStartColumn(context)); |
| 599 | locationStr = addLineCol(kj::mv(locationStr), lineNumber, columnNumber); |
| 600 | |
| 601 | if (locationStr.size() > 0) { |
| 602 | lines.add(locationStr.flatten()); |
| 603 | } |
| 604 | } else { |
| 605 | for (auto i: kj::zeroTo(trace->GetFrameCount())) { |
| 606 | auto frame = trace->GetFrame(js.v8Isolate, i); |
| 607 | kj::StringTree locationStr; |
| 608 | |
| 609 | auto scriptName = frame->GetScriptName(); |
| 610 | if (!scriptName.IsEmpty() && scriptName->Length() != 0) { |
| 611 | locationStr = kj::strTree(" at ", scriptName); |
| 612 | } else { |
| 613 | locationStr = kj::strTree(" at worker.js"); |
| 614 | } |
| 615 | |
| 616 | auto lineNumber = frame->GetLineNumber(); |
| 617 | auto columnNumber = frame->GetColumn(); |
| 618 | locationStr = addLineCol(kj::mv(locationStr), lineNumber, columnNumber); |
| 619 | |
| 620 | auto func = frame->GetFunctionName(); |
| 621 | if (!func.IsEmpty() && func->Length() != 0) { |
| 622 | locationStr = kj::strTree(kj::mv(locationStr), " in ", func); |
| 623 | } |
| 624 | |
| 625 | lines.add(locationStr.flatten()); |
| 626 | } |
| 627 | } |
| 628 | } |
| 629 | |
| 630 | size_t JsFunction::length(Lock& js) const { |
| 631 | JsObject obj = *this; |
| 632 | auto lengthVal = obj.get(js, "length"_kj); |
| 633 | KJ_IF_SOME(num, lengthVal.tryCast<jsg::JsNumber>()) { |
| 634 | return static_cast<size_t>(num.value(js).orDefault(0)); |
| 635 | } |
| 636 | return 0; |
| 637 | } |
| 638 | |
| 639 | JsString JsFunction::name(Lock& js) const { |
| 640 | JsObject obj = *this; |
| 641 | auto nameVal = obj.get(js, "name"_kj); |
| 642 | // It really shouldn't ever be possible for the name property to be non-string, |
| 643 | // but just in case, we check and throw if that happens. |
| 644 | return JSG_REQUIRE_NONNULL( |
| 645 | nameVal.tryCast<jsg::JsString>(), TypeError, "Function name is not a string"); |
| 646 | } |
| 647 | |
| 648 | JsValue JsFunction::call(Lock& js, const JsValue& recv, v8::LocalVector<v8::Value>& args) const { |
| 649 | v8::Local<v8::Function> fn = *this; |
| 650 | return JsValue(check(fn->Call(js.v8Context(), recv, args.size(), args.data()))); |
| 651 | } |
| 652 | |
| 653 | JsValue JsFunction::callNoReceiver(Lock& js, v8::LocalVector<v8::Value>& args) const { |
| 654 | return call(js, js.null(), args); |
| 655 | } |
| 656 | |
| 657 | uint JsFunction::hashCode() const { |
| 658 | v8::Local<v8::Function> obj = *this; |
| 659 | return kj::hashCode(obj->GetIdentityHash()); |
| 660 | } |
| 661 | |
| 662 | BufferSource Lock::bytes(kj::Array<kj::byte> data) { |
| 663 | return BufferSource(*this, BackingStore::from(*this, kj::mv(data))); |
| 664 | } |
| 665 | |
| 666 | // ====================================================================================== |
| 667 | // JsArrayBuffer |
| 668 | |
| 669 | JsArrayBuffer JsArrayBuffer::create(Lock& js, size_t length) { |
| 670 | JSG_REQUIRE(length < v8::ArrayBuffer::kMaxByteLength, RangeError, "The length is too large"); |
| 671 | auto backing = v8::ArrayBuffer::NewBackingStore(js.v8Isolate, length, |
| 672 | v8::BackingStoreInitializationMode::kZeroInitialized, |
| 673 | v8::BackingStoreOnFailureMode::kReturnNull); |
| 674 | JSG_REQUIRE(backing != nullptr, RangeError, "Failed to allocate memory for ArrayBuffer"); |
| 675 | return create(js, kj::mv(backing)); |
| 676 | } |
| 677 | |
| 678 | JsArrayBuffer JsArrayBuffer::create(Lock& js, kj::ArrayPtr<const kj::byte> data) { |
| 679 | auto buf = create(js, data.size()); |
| 680 | buf.asArrayPtr().copyFrom(data); |
| 681 | return buf; |
| 682 | } |
| 683 | |
| 684 | JsArrayBuffer JsArrayBuffer::create(Lock& js, std::unique_ptr<v8::BackingStore> backingStore) { |
| 685 | return JsArrayBuffer(v8::ArrayBuffer::New(js.v8Isolate, kj::mv(backingStore))); |
| 686 | } |
| 687 | |
| 688 | kj::ArrayPtr<kj::byte> JsArrayBuffer::asArrayPtr() { |
| 689 | v8::Local<v8::ArrayBuffer> inner = *this; |
| 690 | if (inner->WasDetached()) [[unlikely]] { |
| 691 | return nullptr; |
| 692 | } |
| 693 | void* data = inner->GetBackingStore()->Data(); |
| 694 | size_t length = inner->ByteLength(); |
| 695 | return kj::ArrayPtr(static_cast<kj::byte*>(data), length); |
| 696 | } |
| 697 | |
| 698 | kj::ArrayPtr<const kj::byte> JsArrayBuffer::asArrayPtr() const { |
| 699 | v8::Local<v8::ArrayBuffer> inner = *this; |
| 700 | if (inner->WasDetached()) [[unlikely]] { |
| 701 | return nullptr; |
| 702 | } |
| 703 | const void* data = inner->GetBackingStore()->Data(); |
| 704 | size_t length = inner->ByteLength(); |
| 705 | return kj::ArrayPtr(static_cast<const kj::byte*>(data), length); |
| 706 | } |
| 707 | |
| 708 | JsArrayBuffer JsArrayBuffer::slice(Lock& js, size_t newLength) const { |
| 709 | JSG_REQUIRE(newLength <= size(), RangeError, "New length exceeds buffer length"); |
| 710 | auto backing = v8::ArrayBuffer::NewBackingStore(js.v8Isolate, newLength, |
| 711 | v8::BackingStoreInitializationMode::kUninitialized, |
| 712 | v8::BackingStoreOnFailureMode::kReturnNull); |
| 713 | JSG_REQUIRE(backing != nullptr, RangeError, "Failed to allocate memory for ArrayBuffer"); |
| 714 | auto dest = kj::ArrayPtr(static_cast<kj::byte*>(backing->Data()), newLength); |
| 715 | v8::Local<v8::ArrayBuffer> inner = *this; |
| 716 | dest.copyFrom( |
| 717 | kj::ArrayPtr(static_cast<const kj::byte*>(inner->GetBackingStore()->Data()), newLength)); |
| 718 | return JsArrayBuffer(v8::ArrayBuffer::New(js.v8Isolate, kj::mv(backing))); |
| 719 | } |
| 720 | |
| 721 | size_t JsArrayBuffer::size() const { |
| 722 | v8::Local<v8::ArrayBuffer> inner = *this; |
| 723 | return inner->ByteLength(); |
| 724 | } |
| 725 | |
| 726 | kj::Array<kj::byte> JsArrayBuffer::copy() { |
| 727 | auto ptr = asArrayPtr(); |
| 728 | return kj::heapArray(ptr); |
| 729 | } |
| 730 | |
| 731 | // ====================================================================================== |
| 732 | // JsArrayBufferView |
| 733 | |
| 734 | size_t JsArrayBufferView::size() const { |
| 735 | v8::Local<v8::ArrayBufferView> inner = *this; |
| 736 | return inner->ByteLength(); |
| 737 | } |
| 738 | |
| 739 | bool JsArrayBufferView::isIntegerType() const { |
| 740 | v8::Local<v8::ArrayBufferView> inner = *this; |
| 741 | return inner->IsUint8Array() || inner->IsUint8ClampedArray() || inner->IsInt8Array() || |
| 742 | inner->IsUint16Array() || inner->IsInt16Array() || inner->IsUint32Array() || |
| 743 | inner->IsInt32Array() || inner->IsBigInt64Array() || inner->IsBigUint64Array(); |
| 744 | } |
| 745 | |
| 746 | // ====================================================================================== |
| 747 | // JsBufferSource |
| 748 | |
| 749 | kj::ArrayPtr<kj::byte> JsBufferSource::asArrayPtr() { |
| 750 | v8::Local<v8::Value> inner = *this; |
| 751 | if (inner->IsArrayBuffer()) { |
| 752 | auto buf = inner.As<v8::ArrayBuffer>(); |
| 753 | if (buf->WasDetached()) [[unlikely]] { |
| 754 | return nullptr; |
| 755 | } |
| 756 | return kj::ArrayPtr(static_cast<kj::byte*>(buf->Data()), buf->ByteLength()); |
| 757 | } else if (inner->IsSharedArrayBuffer()) { |
| 758 | auto buf = inner.As<v8::SharedArrayBuffer>(); |
| 759 | return kj::ArrayPtr(static_cast<kj::byte*>(buf->Data()), buf->ByteLength()); |
| 760 | } else { |
| 761 | KJ_DASSERT(inner->IsArrayBufferView()); |
| 762 | auto view = inner.As<v8::ArrayBufferView>(); |
| 763 | auto buf = view->Buffer(); |
| 764 | if (buf->WasDetached()) [[unlikely]] { |
| 765 | return nullptr; |
| 766 | } |
| 767 | kj::byte* data = static_cast<kj::byte*>(buf->Data()) + view->ByteOffset(); |
| 768 | return kj::ArrayPtr(data, view->ByteLength()); |
| 769 | } |
| 770 | } |
| 771 | |
| 772 | size_t JsBufferSource::size() const { |
| 773 | v8::Local<v8::Value> inner = *this; |
| 774 | if (inner->IsArrayBuffer()) { |
| 775 | auto buf = inner.As<v8::ArrayBuffer>(); |
| 776 | if (buf->WasDetached()) [[unlikely]] { |
| 777 | return 0; |
| 778 | } |
| 779 | return buf->ByteLength(); |
| 780 | } else if (inner->IsSharedArrayBuffer()) { |
| 781 | auto buf = inner.As<v8::SharedArrayBuffer>(); |
| 782 | return buf->ByteLength(); |
| 783 | } else { |
| 784 | KJ_DASSERT(inner->IsArrayBufferView()); |
| 785 | auto view = inner.As<v8::ArrayBufferView>(); |
| 786 | if (view->Buffer()->WasDetached()) [[unlikely]] { |
| 787 | return 0; |
| 788 | } |
| 789 | return view->ByteLength(); |
| 790 | } |
| 791 | } |
| 792 | |
| 793 | bool JsBufferSource::isIntegerType() const { |
| 794 | v8::Local<v8::Value> inner = *this; |
| 795 | return inner->IsArrayBuffer() || inner->IsSharedArrayBuffer() || inner->IsUint8Array() || |
| 796 | inner->IsUint8ClampedArray() || inner->IsInt8Array() || inner->IsUint16Array() || |
| 797 | inner->IsInt16Array() || inner->IsUint32Array() || inner->IsInt32Array() || |
| 798 | inner->IsBigInt64Array() || inner->IsBigUint64Array(); |
| 799 | } |
| 800 | |
| 801 | bool JsBufferSource::isSharedArrayBuffer() const { |
| 802 | v8::Local<v8::Value> inner = *this; |
| 803 | return inner->IsSharedArrayBuffer(); |
| 804 | } |
| 805 | |
| 806 | bool JsBufferSource::isArrayBuffer() const { |
| 807 | v8::Local<v8::Value> inner = *this; |
| 808 | return inner->IsArrayBuffer(); |
| 809 | } |
| 810 | |
| 811 | bool JsBufferSource::isArrayBufferView() const { |
| 812 | v8::Local<v8::Value> inner = *this; |
| 813 | return inner->IsArrayBufferView(); |
| 814 | } |
| 815 | |
| 816 | kj::Array<kj::byte> JsBufferSource::copy() { |
| 817 | auto ptr = asArrayPtr(); |
| 818 | return kj::heapArray(ptr); |
| 819 | } |
| 820 | |
| 821 | bool JsBufferSource::isResizable() const { |
| 822 | v8::Local<v8::Value> inner = *this; |
| 823 | if (inner->IsArrayBuffer()) { |
| 824 | return inner.As<v8::ArrayBuffer>()->IsResizableByUserJavaScript(); |
| 825 | } else if (inner->IsArrayBufferView()) { |
| 826 | return inner.As<v8::ArrayBufferView>()->Buffer()->IsResizableByUserJavaScript(); |
| 827 | } |
| 828 | return false; |
| 829 | } |
| 830 | |
| 831 | // ====================================================================================== |
| 832 | // JsUint8Array |
| 833 | |
| 834 | JsUint8Array JsUint8Array::create(Lock& js, size_t length) { |
| 835 | JSG_REQUIRE(length < v8::ArrayBuffer::kMaxByteLength, RangeError, "The length is too large"); |
| 836 | auto backing = v8::ArrayBuffer::NewBackingStore(js.v8Isolate, length, |
| 837 | v8::BackingStoreInitializationMode::kZeroInitialized, |
| 838 | v8::BackingStoreOnFailureMode::kReturnNull); |
| 839 | JSG_REQUIRE(backing != nullptr, RangeError, "Failed to allocate memory for Uint8Array"); |
| 840 | return create(js, kj::mv(backing), 0, length); |
| 841 | } |
| 842 | |
| 843 | JsUint8Array JsUint8Array::create(Lock& js, kj::ArrayPtr<const kj::byte> data) { |
| 844 | auto buf = create(js, data.size()); |
| 845 | buf.asArrayPtr().copyFrom(data); |
| 846 | return buf; |
| 847 | } |
| 848 | |
| 849 | JsUint8Array JsUint8Array::create(Lock& js, JsArrayBuffer& buffer) { |
| 850 | v8::Local<v8::ArrayBuffer> ab = buffer; |
| 851 | return JsUint8Array(v8::Uint8Array::New(ab, 0, ab->ByteLength())); |
| 852 | } |
| 853 | |
| 854 | JsUint8Array JsUint8Array::create( |
| 855 | Lock& js, std::unique_ptr<v8::BackingStore> backingStore, size_t byteOffset, size_t length) { |
| 856 | return JsUint8Array(v8::Uint8Array::New( |
| 857 | v8::ArrayBuffer::New(js.v8Isolate, kj::mv(backingStore)), byteOffset, length)); |
| 858 | } |
| 859 | |
| 860 | JsUint8Array JsUint8Array::slice(Lock& js, size_t newLength) const { |
| 861 | JSG_REQUIRE(newLength <= size(), RangeError, "New length exceeds array length"); |
| 862 | auto u8 = v8::Uint8Array::New(inner->Buffer(), inner->ByteOffset(), newLength); |
| 863 | return JsUint8Array(u8); |
| 864 | } |
| 865 | |
| 866 | kj::ArrayPtr<const kj::byte> JsUint8Array::asArrayPtr() const { |
| 867 | auto buf = inner->Buffer(); |
| 868 | if (buf->WasDetached()) [[unlikely]] { |
| 869 | return nullptr; |
| 870 | } |
| 871 | const kj::byte* data = static_cast<const kj::byte*>(buf->Data()) + inner->ByteOffset(); |
| 872 | size_t length = inner->ByteLength(); |
| 873 | return kj::ArrayPtr(data, length); |
| 874 | } |
| 875 | |
| 876 | size_t JsUint8Array::size() const { |
| 877 | return inner->ByteLength(); |
| 878 | } |
| 879 | |
| 880 | kj::Array<kj::byte> JsUint8Array::copy() { |
| 881 | auto ptr = asArrayPtr(); |
| 882 | return kj::heapArray(ptr); |
| 883 | } |
| 884 | |
| 885 | } // namespace workerd::jsg |