| // Copyright 2015 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/objects.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <memory> |
| #include <sstream> |
| #include <vector> |
| |
| #include "src/objects-inl.h" |
| |
| #include "src/accessors.h" |
| #include "src/allocation-site-scopes.h" |
| #include "src/api-arguments-inl.h" |
| #include "src/api-natives.h" |
| #include "src/api.h" |
| #include "src/arguments.h" |
| #include "src/ast/ast.h" |
| #include "src/ast/scopes.h" |
| #include "src/base/bits.h" |
| #include "src/base/overflowing-math.h" |
| #include "src/base/utils/random-number-generator.h" |
| #include "src/bootstrapper.h" |
| #include "src/builtins/builtins.h" |
| #include "src/compiler.h" |
| #include "src/counters-inl.h" |
| #include "src/counters.h" |
| #include "src/date.h" |
| #include "src/debug/debug.h" |
| #include "src/elements.h" |
| #include "src/execution.h" |
| #include "src/field-index-inl.h" |
| #include "src/field-index.h" |
| #include "src/field-type.h" |
| #include "src/frames-inl.h" |
| #include "src/function-kind.h" |
| #include "src/globals.h" |
| #include "src/heap/heap-inl.h" |
| #include "src/ic/ic.h" |
| #include "src/identity-map.h" |
| #include "src/isolate-inl.h" |
| #include "src/keys.h" |
| #include "src/log.h" |
| #include "src/lookup-inl.h" |
| #include "src/map-updater.h" |
| #include "src/message-template.h" |
| #include "src/microtask-queue.h" |
| #include "src/objects-body-descriptors-inl.h" |
| #include "src/objects/allocation-site-inl.h" |
| #include "src/objects/api-callbacks.h" |
| #include "src/objects/arguments-inl.h" |
| #include "src/objects/bigint.h" |
| #include "src/objects/cell-inl.h" |
| #include "src/objects/code-inl.h" |
| #include "src/objects/compilation-cache-inl.h" |
| #include "src/objects/debug-objects-inl.h" |
| #include "src/objects/embedder-data-array-inl.h" |
| #include "src/objects/foreign.h" |
| #include "src/objects/frame-array-inl.h" |
| #include "src/objects/free-space-inl.h" |
| #include "src/objects/hash-table-inl.h" |
| #include "src/objects/js-array-inl.h" |
| #ifdef V8_INTL_SUPPORT |
| #include "src/objects/js-break-iterator.h" |
| #include "src/objects/js-collator.h" |
| #endif // V8_INTL_SUPPORT |
| #include "src/objects/js-collection-inl.h" |
| #ifdef V8_INTL_SUPPORT |
| #include "src/objects/js-date-time-format.h" |
| #endif // V8_INTL_SUPPORT |
| #include "src/objects/js-generator-inl.h" |
| #ifdef V8_INTL_SUPPORT |
| #include "src/objects/js-list-format.h" |
| #include "src/objects/js-locale.h" |
| #include "src/objects/js-number-format.h" |
| #include "src/objects/js-plural-rules.h" |
| #endif // V8_INTL_SUPPORT |
| #include "src/objects/js-regexp-inl.h" |
| #include "src/objects/js-regexp-string-iterator.h" |
| #ifdef V8_INTL_SUPPORT |
| #include "src/objects/js-relative-time-format.h" |
| #include "src/objects/js-segment-iterator.h" |
| #include "src/objects/js-segmenter.h" |
| #endif // V8_INTL_SUPPORT |
| #include "src/objects/js-weak-refs-inl.h" |
| #include "src/objects/literal-objects-inl.h" |
| #include "src/objects/map-inl.h" |
| #include "src/objects/map.h" |
| #include "src/objects/microtask-inl.h" |
| #include "src/objects/module-inl.h" |
| #include "src/objects/promise-inl.h" |
| #include "src/objects/slots-atomic-inl.h" |
| #include "src/objects/stack-frame-info-inl.h" |
| #include "src/objects/string-comparator.h" |
| #include "src/objects/struct-inl.h" |
| #include "src/ostreams.h" |
| #include "src/parsing/preparse-data.h" |
| #include "src/property-descriptor.h" |
| #include "src/prototype.h" |
| #include "src/regexp/jsregexp.h" |
| #include "src/source-position-table.h" |
| #include "src/string-builder-inl.h" |
| #include "src/string-search.h" |
| #include "src/string-stream.h" |
| #include "src/transitions-inl.h" |
| #include "src/unicode-decoder.h" |
| #include "src/unicode-inl.h" |
| #include "src/utils-inl.h" |
| #include "src/wasm/wasm-engine.h" |
| #include "src/wasm/wasm-objects.h" |
| #include "src/zone/zone.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| ShouldThrow GetShouldThrow(Isolate* isolate, Maybe<ShouldThrow> should_throw) { |
| if (should_throw.IsJust()) return should_throw.FromJust(); |
| |
| LanguageMode mode = isolate->context()->scope_info()->language_mode(); |
| if (mode == LanguageMode::kStrict) return kThrowOnError; |
| |
| for (StackFrameIterator it(isolate); !it.done(); it.Advance()) { |
| if (!(it.frame()->is_optimized() || it.frame()->is_interpreted())) { |
| continue; |
| } |
| // Get the language mode from closure. |
| JavaScriptFrame* js_frame = static_cast<JavaScriptFrame*>(it.frame()); |
| std::vector<SharedFunctionInfo> functions; |
| js_frame->GetFunctions(&functions); |
| LanguageMode closure_language_mode = functions.back()->language_mode(); |
| if (closure_language_mode > mode) { |
| mode = closure_language_mode; |
| } |
| break; |
| } |
| |
| return is_sloppy(mode) ? kDontThrow : kThrowOnError; |
| } |
| |
| bool ComparisonResultToBool(Operation op, ComparisonResult result) { |
| switch (op) { |
| case Operation::kLessThan: |
| return result == ComparisonResult::kLessThan; |
| case Operation::kLessThanOrEqual: |
| return result == ComparisonResult::kLessThan || |
| result == ComparisonResult::kEqual; |
| case Operation::kGreaterThan: |
| return result == ComparisonResult::kGreaterThan; |
| case Operation::kGreaterThanOrEqual: |
| return result == ComparisonResult::kGreaterThan || |
| result == ComparisonResult::kEqual; |
| default: |
| break; |
| } |
| UNREACHABLE(); |
| } |
| |
| std::ostream& operator<<(std::ostream& os, InstanceType instance_type) { |
| switch (instance_type) { |
| #define WRITE_TYPE(TYPE) \ |
| case TYPE: \ |
| return os << #TYPE; |
| INSTANCE_TYPE_LIST(WRITE_TYPE) |
| #undef WRITE_TYPE |
| } |
| UNREACHABLE(); |
| } |
| |
| Handle<FieldType> Object::OptimalType(Isolate* isolate, |
| Representation representation) { |
| if (representation.IsNone()) return FieldType::None(isolate); |
| if (FLAG_track_field_types) { |
| if (representation.IsHeapObject() && IsHeapObject()) { |
| // We can track only JavaScript objects with stable maps. |
| Handle<Map> map(HeapObject::cast(*this)->map(), isolate); |
| if (map->is_stable() && map->IsJSReceiverMap()) { |
| return FieldType::Class(map, isolate); |
| } |
| } |
| } |
| return FieldType::Any(isolate); |
| } |
| |
| Handle<Object> Object::NewStorageFor(Isolate* isolate, Handle<Object> object, |
| Representation representation) { |
| if (!representation.IsDouble()) return object; |
| auto result = isolate->factory()->NewMutableHeapNumberWithHoleNaN(); |
| if (object->IsUninitialized(isolate)) { |
| result->set_value_as_bits(kHoleNanInt64); |
| } else if (object->IsMutableHeapNumber()) { |
| // Ensure that all bits of the double value are preserved. |
| result->set_value_as_bits( |
| MutableHeapNumber::cast(*object)->value_as_bits()); |
| } else { |
| result->set_value(object->Number()); |
| } |
| return result; |
| } |
| |
| Handle<Object> Object::WrapForRead(Isolate* isolate, Handle<Object> object, |
| Representation representation) { |
| DCHECK(!object->IsUninitialized(isolate)); |
| if (!representation.IsDouble()) { |
| DCHECK(object->FitsRepresentation(representation)); |
| return object; |
| } |
| return isolate->factory()->NewHeapNumber( |
| MutableHeapNumber::cast(*object)->value()); |
| } |
| |
| MaybeHandle<JSReceiver> Object::ToObjectImpl(Isolate* isolate, |
| Handle<Object> object, |
| const char* method_name) { |
| DCHECK(!object->IsJSReceiver()); // Use ToObject() for fast path. |
| Handle<Context> native_context = isolate->native_context(); |
| Handle<JSFunction> constructor; |
| if (object->IsSmi()) { |
| constructor = handle(native_context->number_function(), isolate); |
| } else { |
| int constructor_function_index = |
| Handle<HeapObject>::cast(object)->map()->GetConstructorFunctionIndex(); |
| if (constructor_function_index == Map::kNoConstructorFunctionIndex) { |
| if (method_name != nullptr) { |
| THROW_NEW_ERROR( |
| isolate, |
| NewTypeError( |
| MessageTemplate::kCalledOnNullOrUndefined, |
| isolate->factory()->NewStringFromAsciiChecked(method_name)), |
| JSReceiver); |
| } |
| THROW_NEW_ERROR(isolate, |
| NewTypeError(MessageTemplate::kUndefinedOrNullToObject), |
| JSReceiver); |
| } |
| constructor = handle( |
| JSFunction::cast(native_context->get(constructor_function_index)), |
| isolate); |
| } |
| Handle<JSObject> result = isolate->factory()->NewJSObject(constructor); |
| Handle<JSValue>::cast(result)->set_value(*object); |
| return result; |
| } |
| |
| // ES6 section 9.2.1.2, OrdinaryCallBindThis for sloppy callee. |
| // static |
| MaybeHandle<JSReceiver> Object::ConvertReceiver(Isolate* isolate, |
| Handle<Object> object) { |
| if (object->IsJSReceiver()) return Handle<JSReceiver>::cast(object); |
| if (object->IsNullOrUndefined(isolate)) { |
| return isolate->global_proxy(); |
| } |
| return Object::ToObject(isolate, object); |
| } |
| |
| // static |
| MaybeHandle<Object> Object::ConvertToNumberOrNumeric(Isolate* isolate, |
| Handle<Object> input, |
| Conversion mode) { |
| while (true) { |
| if (input->IsNumber()) { |
| return input; |
| } |
| if (input->IsString()) { |
| return String::ToNumber(isolate, Handle<String>::cast(input)); |
| } |
| if (input->IsOddball()) { |
| return Oddball::ToNumber(isolate, Handle<Oddball>::cast(input)); |
| } |
| if (input->IsSymbol()) { |
| THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kSymbolToNumber), |
| Object); |
| } |
| if (input->IsBigInt()) { |
| if (mode == Conversion::kToNumeric) return input; |
| DCHECK_EQ(mode, Conversion::kToNumber); |
| THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kBigIntToNumber), |
| Object); |
| } |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, input, JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(input), |
| ToPrimitiveHint::kNumber), |
| Object); |
| } |
| } |
| |
| // static |
| MaybeHandle<Object> Object::ConvertToInteger(Isolate* isolate, |
| Handle<Object> input) { |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, input, |
| ConvertToNumberOrNumeric(isolate, input, Conversion::kToNumber), Object); |
| if (input->IsSmi()) return input; |
| return isolate->factory()->NewNumber(DoubleToInteger(input->Number())); |
| } |
| |
| // static |
| MaybeHandle<Object> Object::ConvertToInt32(Isolate* isolate, |
| Handle<Object> input) { |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, input, |
| ConvertToNumberOrNumeric(isolate, input, Conversion::kToNumber), Object); |
| if (input->IsSmi()) return input; |
| return isolate->factory()->NewNumberFromInt(DoubleToInt32(input->Number())); |
| } |
| |
| // static |
| MaybeHandle<Object> Object::ConvertToUint32(Isolate* isolate, |
| Handle<Object> input) { |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, input, |
| ConvertToNumberOrNumeric(isolate, input, Conversion::kToNumber), Object); |
| if (input->IsSmi()) return handle(Smi::cast(*input)->ToUint32Smi(), isolate); |
| return isolate->factory()->NewNumberFromUint(DoubleToUint32(input->Number())); |
| } |
| |
| // static |
| MaybeHandle<Name> Object::ConvertToName(Isolate* isolate, |
| Handle<Object> input) { |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, input, Object::ToPrimitive(input, ToPrimitiveHint::kString), |
| Name); |
| if (input->IsName()) return Handle<Name>::cast(input); |
| return ToString(isolate, input); |
| } |
| |
| // ES6 7.1.14 |
| // static |
| MaybeHandle<Object> Object::ConvertToPropertyKey(Isolate* isolate, |
| Handle<Object> value) { |
| // 1. Let key be ToPrimitive(argument, hint String). |
| MaybeHandle<Object> maybe_key = |
| Object::ToPrimitive(value, ToPrimitiveHint::kString); |
| // 2. ReturnIfAbrupt(key). |
| Handle<Object> key; |
| if (!maybe_key.ToHandle(&key)) return key; |
| // 3. If Type(key) is Symbol, then return key. |
| if (key->IsSymbol()) return key; |
| // 4. Return ToString(key). |
| // Extending spec'ed behavior, we'd be happy to return an element index. |
| if (key->IsSmi()) return key; |
| if (key->IsHeapNumber()) { |
| uint32_t uint_value; |
| if (value->ToArrayLength(&uint_value) && |
| uint_value <= static_cast<uint32_t>(Smi::kMaxValue)) { |
| return handle(Smi::FromInt(static_cast<int>(uint_value)), isolate); |
| } |
| } |
| return Object::ToString(isolate, key); |
| } |
| |
| // static |
| MaybeHandle<String> Object::ConvertToString(Isolate* isolate, |
| Handle<Object> input) { |
| while (true) { |
| if (input->IsOddball()) { |
| return handle(Handle<Oddball>::cast(input)->to_string(), isolate); |
| } |
| if (input->IsNumber()) { |
| return isolate->factory()->NumberToString(input); |
| } |
| if (input->IsSymbol()) { |
| THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kSymbolToString), |
| String); |
| } |
| if (input->IsBigInt()) { |
| return BigInt::ToString(isolate, Handle<BigInt>::cast(input)); |
| } |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, input, JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(input), |
| ToPrimitiveHint::kString), |
| String); |
| // The previous isString() check happened in Object::ToString and thus we |
| // put it at the end of the loop in this helper. |
| if (input->IsString()) { |
| return Handle<String>::cast(input); |
| } |
| } |
| } |
| |
| namespace { |
| |
| bool IsErrorObject(Isolate* isolate, Handle<Object> object) { |
| if (!object->IsJSReceiver()) return false; |
| Handle<Symbol> symbol = isolate->factory()->stack_trace_symbol(); |
| return JSReceiver::HasOwnProperty(Handle<JSReceiver>::cast(object), symbol) |
| .FromMaybe(false); |
| } |
| |
| Handle<String> AsStringOrEmpty(Isolate* isolate, Handle<Object> object) { |
| return object->IsString() ? Handle<String>::cast(object) |
| : isolate->factory()->empty_string(); |
| } |
| |
| Handle<String> NoSideEffectsErrorToString(Isolate* isolate, |
| Handle<Object> input) { |
| Handle<JSReceiver> receiver = Handle<JSReceiver>::cast(input); |
| |
| Handle<Name> name_key = isolate->factory()->name_string(); |
| Handle<Object> name = JSReceiver::GetDataProperty(receiver, name_key); |
| Handle<String> name_str = AsStringOrEmpty(isolate, name); |
| |
| Handle<Name> msg_key = isolate->factory()->message_string(); |
| Handle<Object> msg = JSReceiver::GetDataProperty(receiver, msg_key); |
| Handle<String> msg_str = AsStringOrEmpty(isolate, msg); |
| |
| if (name_str->length() == 0) return msg_str; |
| if (msg_str->length() == 0) return name_str; |
| |
| IncrementalStringBuilder builder(isolate); |
| builder.AppendString(name_str); |
| builder.AppendCString(": "); |
| builder.AppendString(msg_str); |
| |
| return builder.Finish().ToHandleChecked(); |
| } |
| |
| } // namespace |
| |
| // static |
| Handle<String> Object::NoSideEffectsToString(Isolate* isolate, |
| Handle<Object> input) { |
| DisallowJavascriptExecution no_js(isolate); |
| |
| if (input->IsString() || input->IsNumber() || input->IsOddball()) { |
| return Object::ToString(isolate, input).ToHandleChecked(); |
| } else if (input->IsBigInt()) { |
| MaybeHandle<String> maybe_string = |
| BigInt::ToString(isolate, Handle<BigInt>::cast(input), 10, kDontThrow); |
| Handle<String> result; |
| if (maybe_string.ToHandle(&result)) return result; |
| // BigInt-to-String conversion can fail on 32-bit platforms where |
| // String::kMaxLength is too small to fit this BigInt. |
| return isolate->factory()->NewStringFromStaticChars( |
| "<a very large BigInt>"); |
| } else if (input->IsFunction()) { |
| // -- F u n c t i o n |
| Handle<String> fun_str; |
| if (input->IsJSBoundFunction()) { |
| fun_str = JSBoundFunction::ToString(Handle<JSBoundFunction>::cast(input)); |
| } else { |
| DCHECK(input->IsJSFunction()); |
| fun_str = JSFunction::ToString(Handle<JSFunction>::cast(input)); |
| } |
| |
| if (fun_str->length() > 128) { |
| IncrementalStringBuilder builder(isolate); |
| builder.AppendString(isolate->factory()->NewSubString(fun_str, 0, 111)); |
| builder.AppendCString("...<omitted>..."); |
| builder.AppendString(isolate->factory()->NewSubString( |
| fun_str, fun_str->length() - 2, fun_str->length())); |
| |
| return builder.Finish().ToHandleChecked(); |
| } |
| return fun_str; |
| } else if (input->IsSymbol()) { |
| // -- S y m b o l |
| Handle<Symbol> symbol = Handle<Symbol>::cast(input); |
| |
| if (symbol->is_private_name()) { |
| return Handle<String>(String::cast(symbol->name()), isolate); |
| } |
| |
| IncrementalStringBuilder builder(isolate); |
| builder.AppendCString("Symbol("); |
| if (symbol->name()->IsString()) { |
| builder.AppendString(handle(String::cast(symbol->name()), isolate)); |
| } |
| builder.AppendCharacter(')'); |
| |
| return builder.Finish().ToHandleChecked(); |
| } else if (input->IsJSReceiver()) { |
| // -- J S R e c e i v e r |
| Handle<JSReceiver> receiver = Handle<JSReceiver>::cast(input); |
| Handle<Object> to_string = JSReceiver::GetDataProperty( |
| receiver, isolate->factory()->toString_string()); |
| |
| if (IsErrorObject(isolate, input) || |
| *to_string == *isolate->error_to_string()) { |
| // When internally formatting error objects, use a side-effects-free |
| // version of Error.prototype.toString independent of the actually |
| // installed toString method. |
| return NoSideEffectsErrorToString(isolate, input); |
| } else if (*to_string == *isolate->object_to_string()) { |
| Handle<Object> ctor = JSReceiver::GetDataProperty( |
| receiver, isolate->factory()->constructor_string()); |
| if (ctor->IsFunction()) { |
| Handle<String> ctor_name; |
| if (ctor->IsJSBoundFunction()) { |
| ctor_name = JSBoundFunction::GetName( |
| isolate, Handle<JSBoundFunction>::cast(ctor)) |
| .ToHandleChecked(); |
| } else if (ctor->IsJSFunction()) { |
| Handle<Object> ctor_name_obj = |
| JSFunction::GetName(isolate, Handle<JSFunction>::cast(ctor)); |
| ctor_name = AsStringOrEmpty(isolate, ctor_name_obj); |
| } |
| |
| if (ctor_name->length() != 0) { |
| IncrementalStringBuilder builder(isolate); |
| builder.AppendCString("#<"); |
| builder.AppendString(ctor_name); |
| builder.AppendCString(">"); |
| |
| return builder.Finish().ToHandleChecked(); |
| } |
| } |
| } |
| } |
| |
| // At this point, input is either none of the above or a JSReceiver. |
| |
| Handle<JSReceiver> receiver; |
| if (input->IsJSReceiver()) { |
| receiver = Handle<JSReceiver>::cast(input); |
| } else { |
| // This is the only case where Object::ToObject throws. |
| DCHECK(!input->IsSmi()); |
| int constructor_function_index = |
| Handle<HeapObject>::cast(input)->map()->GetConstructorFunctionIndex(); |
| if (constructor_function_index == Map::kNoConstructorFunctionIndex) { |
| return isolate->factory()->NewStringFromAsciiChecked("[object Unknown]"); |
| } |
| |
| receiver = Object::ToObjectImpl(isolate, input).ToHandleChecked(); |
| } |
| |
| Handle<String> builtin_tag = handle(receiver->class_name(), isolate); |
| Handle<Object> tag_obj = JSReceiver::GetDataProperty( |
| receiver, isolate->factory()->to_string_tag_symbol()); |
| Handle<String> tag = |
| tag_obj->IsString() ? Handle<String>::cast(tag_obj) : builtin_tag; |
| |
| IncrementalStringBuilder builder(isolate); |
| builder.AppendCString("[object "); |
| builder.AppendString(tag); |
| builder.AppendCString("]"); |
| |
| return builder.Finish().ToHandleChecked(); |
| } |
| |
| // static |
| MaybeHandle<Object> Object::ConvertToLength(Isolate* isolate, |
| Handle<Object> input) { |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, input, ToNumber(isolate, input), Object); |
| if (input->IsSmi()) { |
| int value = std::max(Smi::ToInt(*input), 0); |
| return handle(Smi::FromInt(value), isolate); |
| } |
| double len = DoubleToInteger(input->Number()); |
| if (len <= 0.0) { |
| return handle(Smi::kZero, isolate); |
| } else if (len >= kMaxSafeInteger) { |
| len = kMaxSafeInteger; |
| } |
| return isolate->factory()->NewNumber(len); |
| } |
| |
| // static |
| MaybeHandle<Object> Object::ConvertToIndex(Isolate* isolate, |
| Handle<Object> input, |
| MessageTemplate error_index) { |
| if (input->IsUndefined(isolate)) return handle(Smi::kZero, isolate); |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, input, ToNumber(isolate, input), Object); |
| if (input->IsSmi() && Smi::ToInt(*input) >= 0) return input; |
| double len = DoubleToInteger(input->Number()) + 0.0; |
| auto js_len = isolate->factory()->NewNumber(len); |
| if (len < 0.0 || len > kMaxSafeInteger) { |
| THROW_NEW_ERROR(isolate, NewRangeError(error_index, js_len), Object); |
| } |
| return js_len; |
| } |
| |
| bool Object::BooleanValue(Isolate* isolate) { |
| if (IsSmi()) return Smi::ToInt(*this) != 0; |
| DCHECK(IsHeapObject()); |
| if (IsBoolean()) return IsTrue(isolate); |
| if (IsNullOrUndefined(isolate)) return false; |
| if (IsUndetectable()) return false; // Undetectable object is false. |
| if (IsString()) return String::cast(*this)->length() != 0; |
| if (IsHeapNumber()) return DoubleToBoolean(HeapNumber::cast(*this)->value()); |
| if (IsBigInt()) return BigInt::cast(*this)->ToBoolean(); |
| return true; |
| } |
| |
| Object Object::ToBoolean(Isolate* isolate) { |
| if (IsBoolean()) return *this; |
| return isolate->heap()->ToBoolean(BooleanValue(isolate)); |
| } |
| |
| namespace { |
| |
| // TODO(bmeurer): Maybe we should introduce a marker interface Number, |
| // where we put all these methods at some point? |
| ComparisonResult NumberCompare(double x, double y) { |
| if (std::isnan(x) || std::isnan(y)) { |
| return ComparisonResult::kUndefined; |
| } else if (x < y) { |
| return ComparisonResult::kLessThan; |
| } else if (x > y) { |
| return ComparisonResult::kGreaterThan; |
| } else { |
| return ComparisonResult::kEqual; |
| } |
| } |
| |
| bool NumberEquals(double x, double y) { |
| // Must check explicitly for NaN's on Windows, but -0 works fine. |
| if (std::isnan(x)) return false; |
| if (std::isnan(y)) return false; |
| return x == y; |
| } |
| |
| bool NumberEquals(const Object x, const Object y) { |
| return NumberEquals(x->Number(), y->Number()); |
| } |
| |
| bool NumberEquals(Handle<Object> x, Handle<Object> y) { |
| return NumberEquals(*x, *y); |
| } |
| |
| ComparisonResult Reverse(ComparisonResult result) { |
| if (result == ComparisonResult::kLessThan) { |
| return ComparisonResult::kGreaterThan; |
| } |
| if (result == ComparisonResult::kGreaterThan) { |
| return ComparisonResult::kLessThan; |
| } |
| return result; |
| } |
| |
| } // anonymous namespace |
| |
| // static |
| Maybe<ComparisonResult> Object::Compare(Isolate* isolate, Handle<Object> x, |
| Handle<Object> y) { |
| // ES6 section 7.2.11 Abstract Relational Comparison step 3 and 4. |
| if (!Object::ToPrimitive(x, ToPrimitiveHint::kNumber).ToHandle(&x) || |
| !Object::ToPrimitive(y, ToPrimitiveHint::kNumber).ToHandle(&y)) { |
| return Nothing<ComparisonResult>(); |
| } |
| if (x->IsString() && y->IsString()) { |
| // ES6 section 7.2.11 Abstract Relational Comparison step 5. |
| return Just(String::Compare(isolate, Handle<String>::cast(x), |
| Handle<String>::cast(y))); |
| } |
| if (x->IsBigInt() && y->IsString()) { |
| return Just(BigInt::CompareToString(isolate, Handle<BigInt>::cast(x), |
| Handle<String>::cast(y))); |
| } |
| if (x->IsString() && y->IsBigInt()) { |
| return Just(Reverse(BigInt::CompareToString( |
| isolate, Handle<BigInt>::cast(y), Handle<String>::cast(x)))); |
| } |
| // ES6 section 7.2.11 Abstract Relational Comparison step 6. |
| if (!Object::ToNumeric(isolate, x).ToHandle(&x) || |
| !Object::ToNumeric(isolate, y).ToHandle(&y)) { |
| return Nothing<ComparisonResult>(); |
| } |
| |
| bool x_is_number = x->IsNumber(); |
| bool y_is_number = y->IsNumber(); |
| if (x_is_number && y_is_number) { |
| return Just(NumberCompare(x->Number(), y->Number())); |
| } else if (!x_is_number && !y_is_number) { |
| return Just(BigInt::CompareToBigInt(Handle<BigInt>::cast(x), |
| Handle<BigInt>::cast(y))); |
| } else if (x_is_number) { |
| return Just(Reverse(BigInt::CompareToNumber(Handle<BigInt>::cast(y), x))); |
| } else { |
| return Just(BigInt::CompareToNumber(Handle<BigInt>::cast(x), y)); |
| } |
| } |
| |
| |
| // static |
| Maybe<bool> Object::Equals(Isolate* isolate, Handle<Object> x, |
| Handle<Object> y) { |
| // This is the generic version of Abstract Equality Comparison. Must be in |
| // sync with CodeStubAssembler::Equal. |
| while (true) { |
| if (x->IsNumber()) { |
| if (y->IsNumber()) { |
| return Just(NumberEquals(x, y)); |
| } else if (y->IsBoolean()) { |
| return Just(NumberEquals(*x, Handle<Oddball>::cast(y)->to_number())); |
| } else if (y->IsString()) { |
| return Just(NumberEquals( |
| x, String::ToNumber(isolate, Handle<String>::cast(y)))); |
| } else if (y->IsBigInt()) { |
| return Just(BigInt::EqualToNumber(Handle<BigInt>::cast(y), x)); |
| } else if (y->IsJSReceiver()) { |
| if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(y)) |
| .ToHandle(&y)) { |
| return Nothing<bool>(); |
| } |
| } else { |
| return Just(false); |
| } |
| } else if (x->IsString()) { |
| if (y->IsString()) { |
| return Just(String::Equals(isolate, Handle<String>::cast(x), |
| Handle<String>::cast(y))); |
| } else if (y->IsNumber()) { |
| x = String::ToNumber(isolate, Handle<String>::cast(x)); |
| return Just(NumberEquals(x, y)); |
| } else if (y->IsBoolean()) { |
| x = String::ToNumber(isolate, Handle<String>::cast(x)); |
| return Just(NumberEquals(*x, Handle<Oddball>::cast(y)->to_number())); |
| } else if (y->IsBigInt()) { |
| return Just(BigInt::EqualToString(isolate, Handle<BigInt>::cast(y), |
| Handle<String>::cast(x))); |
| } else if (y->IsJSReceiver()) { |
| if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(y)) |
| .ToHandle(&y)) { |
| return Nothing<bool>(); |
| } |
| } else { |
| return Just(false); |
| } |
| } else if (x->IsBoolean()) { |
| if (y->IsOddball()) { |
| return Just(x.is_identical_to(y)); |
| } else if (y->IsNumber()) { |
| return Just(NumberEquals(Handle<Oddball>::cast(x)->to_number(), *y)); |
| } else if (y->IsString()) { |
| y = String::ToNumber(isolate, Handle<String>::cast(y)); |
| return Just(NumberEquals(Handle<Oddball>::cast(x)->to_number(), *y)); |
| } else if (y->IsBigInt()) { |
| x = Oddball::ToNumber(isolate, Handle<Oddball>::cast(x)); |
| return Just(BigInt::EqualToNumber(Handle<BigInt>::cast(y), x)); |
| } else if (y->IsJSReceiver()) { |
| if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(y)) |
| .ToHandle(&y)) { |
| return Nothing<bool>(); |
| } |
| x = Oddball::ToNumber(isolate, Handle<Oddball>::cast(x)); |
| } else { |
| return Just(false); |
| } |
| } else if (x->IsSymbol()) { |
| if (y->IsSymbol()) { |
| return Just(x.is_identical_to(y)); |
| } else if (y->IsJSReceiver()) { |
| if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(y)) |
| .ToHandle(&y)) { |
| return Nothing<bool>(); |
| } |
| } else { |
| return Just(false); |
| } |
| } else if (x->IsBigInt()) { |
| if (y->IsBigInt()) { |
| return Just(BigInt::EqualToBigInt(BigInt::cast(*x), BigInt::cast(*y))); |
| } |
| return Equals(isolate, y, x); |
| } else if (x->IsJSReceiver()) { |
| if (y->IsJSReceiver()) { |
| return Just(x.is_identical_to(y)); |
| } else if (y->IsUndetectable()) { |
| return Just(x->IsUndetectable()); |
| } else if (y->IsBoolean()) { |
| y = Oddball::ToNumber(isolate, Handle<Oddball>::cast(y)); |
| } else if (!JSReceiver::ToPrimitive(Handle<JSReceiver>::cast(x)) |
| .ToHandle(&x)) { |
| return Nothing<bool>(); |
| } |
| } else { |
| return Just(x->IsUndetectable() && y->IsUndetectable()); |
| } |
| } |
| } |
| |
| bool Object::StrictEquals(Object that) { |
| if (this->IsNumber()) { |
| if (!that->IsNumber()) return false; |
| return NumberEquals(*this, that); |
| } else if (this->IsString()) { |
| if (!that->IsString()) return false; |
| return String::cast(*this)->Equals(String::cast(that)); |
| } else if (this->IsBigInt()) { |
| if (!that->IsBigInt()) return false; |
| return BigInt::EqualToBigInt(BigInt::cast(*this), BigInt::cast(that)); |
| } |
| return *this == that; |
| } |
| |
| // static |
| Handle<String> Object::TypeOf(Isolate* isolate, Handle<Object> object) { |
| if (object->IsNumber()) return isolate->factory()->number_string(); |
| if (object->IsOddball()) |
| return handle(Oddball::cast(*object)->type_of(), isolate); |
| if (object->IsUndetectable()) { |
| return isolate->factory()->undefined_string(); |
| } |
| if (object->IsString()) return isolate->factory()->string_string(); |
| if (object->IsSymbol()) return isolate->factory()->symbol_string(); |
| if (object->IsBigInt()) return isolate->factory()->bigint_string(); |
| if (object->IsCallable()) return isolate->factory()->function_string(); |
| return isolate->factory()->object_string(); |
| } |
| |
| |
| // static |
| MaybeHandle<Object> Object::Add(Isolate* isolate, Handle<Object> lhs, |
| Handle<Object> rhs) { |
| if (lhs->IsNumber() && rhs->IsNumber()) { |
| return isolate->factory()->NewNumber(lhs->Number() + rhs->Number()); |
| } else if (lhs->IsString() && rhs->IsString()) { |
| return isolate->factory()->NewConsString(Handle<String>::cast(lhs), |
| Handle<String>::cast(rhs)); |
| } |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, lhs, Object::ToPrimitive(lhs), Object); |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, rhs, Object::ToPrimitive(rhs), Object); |
| if (lhs->IsString() || rhs->IsString()) { |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, rhs, Object::ToString(isolate, rhs), |
| Object); |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, lhs, Object::ToString(isolate, lhs), |
| Object); |
| return isolate->factory()->NewConsString(Handle<String>::cast(lhs), |
| Handle<String>::cast(rhs)); |
| } |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, rhs, Object::ToNumber(isolate, rhs), |
| Object); |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, lhs, Object::ToNumber(isolate, lhs), |
| Object); |
| return isolate->factory()->NewNumber(lhs->Number() + rhs->Number()); |
| } |
| |
| |
| // static |
| MaybeHandle<Object> Object::OrdinaryHasInstance(Isolate* isolate, |
| Handle<Object> callable, |
| Handle<Object> object) { |
| // The {callable} must have a [[Call]] internal method. |
| if (!callable->IsCallable()) return isolate->factory()->false_value(); |
| |
| // Check if {callable} is a bound function, and if so retrieve its |
| // [[BoundTargetFunction]] and use that instead of {callable}. |
| if (callable->IsJSBoundFunction()) { |
| Handle<Object> bound_callable( |
| Handle<JSBoundFunction>::cast(callable)->bound_target_function(), |
| isolate); |
| return Object::InstanceOf(isolate, object, bound_callable); |
| } |
| |
| // If {object} is not a receiver, return false. |
| if (!object->IsJSReceiver()) return isolate->factory()->false_value(); |
| |
| // Get the "prototype" of {callable}; raise an error if it's not a receiver. |
| Handle<Object> prototype; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, prototype, |
| Object::GetProperty(isolate, callable, |
| isolate->factory()->prototype_string()), |
| Object); |
| if (!prototype->IsJSReceiver()) { |
| THROW_NEW_ERROR( |
| isolate, |
| NewTypeError(MessageTemplate::kInstanceofNonobjectProto, prototype), |
| Object); |
| } |
| |
| // Return whether or not {prototype} is in the prototype chain of {object}. |
| Maybe<bool> result = JSReceiver::HasInPrototypeChain( |
| isolate, Handle<JSReceiver>::cast(object), prototype); |
| if (result.IsNothing()) return MaybeHandle<Object>(); |
| return isolate->factory()->ToBoolean(result.FromJust()); |
| } |
| |
| // static |
| MaybeHandle<Object> Object::InstanceOf(Isolate* isolate, Handle<Object> object, |
| Handle<Object> callable) { |
| // The {callable} must be a receiver. |
| if (!callable->IsJSReceiver()) { |
| THROW_NEW_ERROR(isolate, |
| NewTypeError(MessageTemplate::kNonObjectInInstanceOfCheck), |
| Object); |
| } |
| |
| // Lookup the @@hasInstance method on {callable}. |
| Handle<Object> inst_of_handler; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, inst_of_handler, |
| Object::GetMethod(Handle<JSReceiver>::cast(callable), |
| isolate->factory()->has_instance_symbol()), |
| Object); |
| if (!inst_of_handler->IsUndefined(isolate)) { |
| // Call the {inst_of_handler} on the {callable}. |
| Handle<Object> result; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, result, |
| Execution::Call(isolate, inst_of_handler, callable, 1, &object), |
| Object); |
| return isolate->factory()->ToBoolean(result->BooleanValue(isolate)); |
| } |
| |
| // The {callable} must have a [[Call]] internal method. |
| if (!callable->IsCallable()) { |
| THROW_NEW_ERROR( |
| isolate, NewTypeError(MessageTemplate::kNonCallableInInstanceOfCheck), |
| Object); |
| } |
| |
| // Fall back to OrdinaryHasInstance with {callable} and {object}. |
| Handle<Object> result; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, result, Object::OrdinaryHasInstance(isolate, callable, object), |
| Object); |
| return result; |
| } |
| |
| // static |
| MaybeHandle<Object> Object::GetMethod(Handle<JSReceiver> receiver, |
| Handle<Name> name) { |
| Handle<Object> func; |
| Isolate* isolate = receiver->GetIsolate(); |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, func, JSReceiver::GetProperty(isolate, receiver, name), Object); |
| if (func->IsNullOrUndefined(isolate)) { |
| return isolate->factory()->undefined_value(); |
| } |
| if (!func->IsCallable()) { |
| THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kPropertyNotFunction, |
| func, name, receiver), |
| Object); |
| } |
| return func; |
| } |
| |
| namespace { |
| |
| MaybeHandle<FixedArray> CreateListFromArrayLikeFastPath( |
| Isolate* isolate, Handle<Object> object, ElementTypes element_types) { |
| if (element_types == ElementTypes::kAll) { |
| if (object->IsJSArray()) { |
| Handle<JSArray> array = Handle<JSArray>::cast(object); |
| uint32_t length; |
| if (!array->HasArrayPrototype(isolate) || |
| !array->length()->ToUint32(&length) || !array->HasFastElements() || |
| !JSObject::PrototypeHasNoElements(isolate, *array)) { |
| return MaybeHandle<FixedArray>(); |
| } |
| return array->GetElementsAccessor()->CreateListFromArrayLike( |
| isolate, array, length); |
| } else if (object->IsJSTypedArray()) { |
| Handle<JSTypedArray> array = Handle<JSTypedArray>::cast(object); |
| size_t length = array->length_value(); |
| if (array->WasDetached() || |
| length > static_cast<size_t>(FixedArray::kMaxLength)) { |
| return MaybeHandle<FixedArray>(); |
| } |
| return array->GetElementsAccessor()->CreateListFromArrayLike( |
| isolate, array, static_cast<uint32_t>(length)); |
| } |
| } |
| return MaybeHandle<FixedArray>(); |
| } |
| |
| } // namespace |
| |
| // static |
| MaybeHandle<FixedArray> Object::CreateListFromArrayLike( |
| Isolate* isolate, Handle<Object> object, ElementTypes element_types) { |
| // Fast-path for JSArray and JSTypedArray. |
| MaybeHandle<FixedArray> fast_result = |
| CreateListFromArrayLikeFastPath(isolate, object, element_types); |
| if (!fast_result.is_null()) return fast_result; |
| // 1. ReturnIfAbrupt(object). |
| // 2. (default elementTypes -- not applicable.) |
| // 3. If Type(obj) is not Object, throw a TypeError exception. |
| if (!object->IsJSReceiver()) { |
| THROW_NEW_ERROR(isolate, |
| NewTypeError(MessageTemplate::kCalledOnNonObject, |
| isolate->factory()->NewStringFromAsciiChecked( |
| "CreateListFromArrayLike")), |
| FixedArray); |
| } |
| |
| // 4. Let len be ? ToLength(? Get(obj, "length")). |
| Handle<JSReceiver> receiver = Handle<JSReceiver>::cast(object); |
| Handle<Object> raw_length_number; |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, raw_length_number, |
| Object::GetLengthFromArrayLike(isolate, receiver), |
| FixedArray); |
| uint32_t len; |
| if (!raw_length_number->ToUint32(&len) || |
| len > static_cast<uint32_t>(FixedArray::kMaxLength)) { |
| THROW_NEW_ERROR(isolate, |
| NewRangeError(MessageTemplate::kInvalidArrayLength), |
| FixedArray); |
| } |
| // 5. Let list be an empty List. |
| Handle<FixedArray> list = isolate->factory()->NewFixedArray(len); |
| // 6. Let index be 0. |
| // 7. Repeat while index < len: |
| for (uint32_t index = 0; index < len; ++index) { |
| // 7a. Let indexName be ToString(index). |
| // 7b. Let next be ? Get(obj, indexName). |
| Handle<Object> next; |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, next, |
| JSReceiver::GetElement(isolate, receiver, index), |
| FixedArray); |
| switch (element_types) { |
| case ElementTypes::kAll: |
| // Nothing to do. |
| break; |
| case ElementTypes::kStringAndSymbol: { |
| // 7c. If Type(next) is not an element of elementTypes, throw a |
| // TypeError exception. |
| if (!next->IsName()) { |
| THROW_NEW_ERROR(isolate, |
| NewTypeError(MessageTemplate::kNotPropertyName, next), |
| FixedArray); |
| } |
| // 7d. Append next as the last element of list. |
| // Internalize on the fly so we can use pointer identity later. |
| next = isolate->factory()->InternalizeName(Handle<Name>::cast(next)); |
| break; |
| } |
| } |
| list->set(index, *next); |
| // 7e. Set index to index + 1. (See loop header.) |
| } |
| // 8. Return list. |
| return list; |
| } |
| |
| |
| // static |
| MaybeHandle<Object> Object::GetLengthFromArrayLike(Isolate* isolate, |
| Handle<JSReceiver> object) { |
| Handle<Object> val; |
| Handle<Name> key = isolate->factory()->length_string(); |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, val, JSReceiver::GetProperty(isolate, object, key), Object); |
| return Object::ToLength(isolate, val); |
| } |
| |
| // static |
| MaybeHandle<Object> Object::GetProperty(LookupIterator* it, |
| OnNonExistent on_non_existent) { |
| for (; it->IsFound(); it->Next()) { |
| switch (it->state()) { |
| case LookupIterator::NOT_FOUND: |
| case LookupIterator::TRANSITION: |
| UNREACHABLE(); |
| case LookupIterator::JSPROXY: { |
| bool was_found; |
| Handle<Object> receiver = it->GetReceiver(); |
| // In case of global IC, the receiver is the global object. Replace by |
| // the global proxy. |
| if (receiver->IsJSGlobalObject()) { |
| receiver = handle(JSGlobalObject::cast(*receiver)->global_proxy(), |
| it->isolate()); |
| } |
| MaybeHandle<Object> result = |
| JSProxy::GetProperty(it->isolate(), it->GetHolder<JSProxy>(), |
| it->GetName(), receiver, &was_found); |
| if (!was_found) it->NotFound(); |
| return result; |
| } |
| case LookupIterator::INTERCEPTOR: { |
| bool done; |
| Handle<Object> result; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| it->isolate(), result, |
| JSObject::GetPropertyWithInterceptor(it, &done), Object); |
| if (done) return result; |
| break; |
| } |
| case LookupIterator::ACCESS_CHECK: |
| if (it->HasAccess()) break; |
| return JSObject::GetPropertyWithFailedAccessCheck(it); |
| case LookupIterator::ACCESSOR: |
| return GetPropertyWithAccessor(it); |
| case LookupIterator::INTEGER_INDEXED_EXOTIC: |
| return it->isolate()->factory()->undefined_value(); |
| case LookupIterator::DATA: |
| return it->GetDataValue(); |
| } |
| } |
| |
| if (on_non_existent == OnNonExistent::kThrowReferenceError) { |
| THROW_NEW_ERROR(it->isolate(), |
| NewReferenceError(MessageTemplate::kNotDefined, it->name()), |
| Object); |
| } |
| return it->isolate()->factory()->undefined_value(); |
| } |
| |
| |
| // static |
| MaybeHandle<Object> JSProxy::GetProperty(Isolate* isolate, |
| Handle<JSProxy> proxy, |
| Handle<Name> name, |
| Handle<Object> receiver, |
| bool* was_found) { |
| *was_found = true; |
| |
| DCHECK(!name->IsPrivate()); |
| STACK_CHECK(isolate, MaybeHandle<Object>()); |
| Handle<Name> trap_name = isolate->factory()->get_string(); |
| // 1. Assert: IsPropertyKey(P) is true. |
| // 2. Let handler be the value of the [[ProxyHandler]] internal slot of O. |
| Handle<Object> handler(proxy->handler(), isolate); |
| // 3. If handler is null, throw a TypeError exception. |
| // 4. Assert: Type(handler) is Object. |
| if (proxy->IsRevoked()) { |
| THROW_NEW_ERROR(isolate, |
| NewTypeError(MessageTemplate::kProxyRevoked, trap_name), |
| Object); |
| } |
| // 5. Let target be the value of the [[ProxyTarget]] internal slot of O. |
| Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate); |
| // 6. Let trap be ? GetMethod(handler, "get"). |
| Handle<Object> trap; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, trap, |
| Object::GetMethod(Handle<JSReceiver>::cast(handler), trap_name), Object); |
| // 7. If trap is undefined, then |
| if (trap->IsUndefined(isolate)) { |
| // 7.a Return target.[[Get]](P, Receiver). |
| LookupIterator it = |
| LookupIterator::PropertyOrElement(isolate, receiver, name, target); |
| MaybeHandle<Object> result = Object::GetProperty(&it); |
| *was_found = it.IsFound(); |
| return result; |
| } |
| // 8. Let trapResult be ? Call(trap, handler, «target, P, Receiver»). |
| Handle<Object> trap_result; |
| Handle<Object> args[] = {target, name, receiver}; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, trap_result, |
| Execution::Call(isolate, trap, handler, arraysize(args), args), Object); |
| |
| MaybeHandle<Object> result = |
| JSProxy::CheckGetSetTrapResult(isolate, name, target, trap_result, kGet); |
| if (result.is_null()) { |
| return result; |
| } |
| |
| // 11. Return trap_result |
| return trap_result; |
| } |
| |
| // static |
| MaybeHandle<Object> JSProxy::CheckGetSetTrapResult(Isolate* isolate, |
| Handle<Name> name, |
| Handle<JSReceiver> target, |
| Handle<Object> trap_result, |
| AccessKind access_kind) { |
| // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). |
| PropertyDescriptor target_desc; |
| Maybe<bool> target_found = |
| JSReceiver::GetOwnPropertyDescriptor(isolate, target, name, &target_desc); |
| MAYBE_RETURN_NULL(target_found); |
| // 10. If targetDesc is not undefined, then |
| if (target_found.FromJust()) { |
| // 10.a. If IsDataDescriptor(targetDesc) and targetDesc.[[Configurable]] is |
| // false and targetDesc.[[Writable]] is false, then |
| // 10.a.i. If SameValue(trapResult, targetDesc.[[Value]]) is false, |
| // throw a TypeError exception. |
| bool inconsistent = PropertyDescriptor::IsDataDescriptor(&target_desc) && |
| !target_desc.configurable() && |
| !target_desc.writable() && |
| !trap_result->SameValue(*target_desc.value()); |
| if (inconsistent) { |
| if (access_kind == kGet) { |
| THROW_NEW_ERROR( |
| isolate, |
| NewTypeError(MessageTemplate::kProxyGetNonConfigurableData, name, |
| target_desc.value(), trap_result), |
| Object); |
| } else { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxySetFrozenData, name)); |
| return MaybeHandle<Object>(); |
| } |
| } |
| // 10.b. If IsAccessorDescriptor(targetDesc) and targetDesc.[[Configurable]] |
| // is false and targetDesc.[[Get]] is undefined, then |
| // 10.b.i. If trapResult is not undefined, throw a TypeError exception. |
| if (access_kind == kGet) { |
| inconsistent = PropertyDescriptor::IsAccessorDescriptor(&target_desc) && |
| !target_desc.configurable() && |
| target_desc.get()->IsUndefined(isolate) && |
| !trap_result->IsUndefined(isolate); |
| } else { |
| inconsistent = PropertyDescriptor::IsAccessorDescriptor(&target_desc) && |
| !target_desc.configurable() && |
| target_desc.set()->IsUndefined(isolate); |
| } |
| if (inconsistent) { |
| if (access_kind == kGet) { |
| THROW_NEW_ERROR( |
| isolate, |
| NewTypeError(MessageTemplate::kProxyGetNonConfigurableAccessor, |
| name, trap_result), |
| Object); |
| } else { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxySetFrozenAccessor, name)); |
| return MaybeHandle<Object>(); |
| } |
| } |
| } |
| return isolate->factory()->undefined_value(); |
| } |
| |
| |
| |
| bool Object::ToInt32(int32_t* value) { |
| if (IsSmi()) { |
| *value = Smi::ToInt(*this); |
| return true; |
| } |
| if (IsHeapNumber()) { |
| double num = HeapNumber::cast(*this)->value(); |
| // Check range before conversion to avoid undefined behavior. |
| if (num >= kMinInt && num <= kMaxInt && FastI2D(FastD2I(num)) == num) { |
| *value = FastD2I(num); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // static constexpr object declarations need a definition to make the |
| // compiler happy. |
| constexpr Object Smi::kZero; |
| constexpr Object SharedFunctionInfo::kNoSharedNameSentinel; |
| |
| Handle<SharedFunctionInfo> FunctionTemplateInfo::GetOrCreateSharedFunctionInfo( |
| Isolate* isolate, Handle<FunctionTemplateInfo> info, |
| MaybeHandle<Name> maybe_name) { |
| Object current_info = info->shared_function_info(); |
| if (current_info->IsSharedFunctionInfo()) { |
| return handle(SharedFunctionInfo::cast(current_info), isolate); |
| } |
| Handle<Name> name; |
| Handle<String> name_string; |
| if (maybe_name.ToHandle(&name) && name->IsString()) { |
| name_string = Handle<String>::cast(name); |
| } else if (info->class_name()->IsString()) { |
| name_string = handle(String::cast(info->class_name()), isolate); |
| } else { |
| name_string = isolate->factory()->empty_string(); |
| } |
| FunctionKind function_kind; |
| if (info->remove_prototype()) { |
| function_kind = kConciseMethod; |
| } else { |
| function_kind = kNormalFunction; |
| } |
| Handle<SharedFunctionInfo> result = |
| isolate->factory()->NewSharedFunctionInfoForApiFunction(name_string, info, |
| function_kind); |
| |
| result->set_length(info->length()); |
| result->DontAdaptArguments(); |
| DCHECK(result->IsApiFunction()); |
| |
| info->set_shared_function_info(*result); |
| return result; |
| } |
| |
| bool FunctionTemplateInfo::IsTemplateFor(Map map) { |
| // There is a constraint on the object; check. |
| if (!map->IsJSObjectMap()) return false; |
| // Fetch the constructor function of the object. |
| Object cons_obj = map->GetConstructor(); |
| Object type; |
| if (cons_obj->IsJSFunction()) { |
| JSFunction fun = JSFunction::cast(cons_obj); |
| type = fun->shared()->function_data(); |
| } else if (cons_obj->IsFunctionTemplateInfo()) { |
| type = FunctionTemplateInfo::cast(cons_obj); |
| } else { |
| return false; |
| } |
| // Iterate through the chain of inheriting function templates to |
| // see if the required one occurs. |
| while (type->IsFunctionTemplateInfo()) { |
| if (type == *this) return true; |
| type = FunctionTemplateInfo::cast(type)->GetParentTemplate(); |
| } |
| // Didn't find the required type in the inheritance chain. |
| return false; |
| } |
| |
| // static |
| FunctionTemplateRareData FunctionTemplateInfo::AllocateFunctionTemplateRareData( |
| Isolate* isolate, Handle<FunctionTemplateInfo> function_template_info) { |
| DCHECK(function_template_info->rare_data()->IsUndefined(isolate)); |
| Handle<Struct> struct_obj = |
| isolate->factory()->NewStruct(FUNCTION_TEMPLATE_RARE_DATA_TYPE, TENURED); |
| Handle<FunctionTemplateRareData> rare_data = |
| i::Handle<FunctionTemplateRareData>::cast(struct_obj); |
| function_template_info->set_rare_data(*rare_data); |
| return *rare_data; |
| } |
| |
| // static |
| Handle<TemplateList> TemplateList::New(Isolate* isolate, int size) { |
| Handle<FixedArray> list = |
| isolate->factory()->NewFixedArray(kLengthIndex + size); |
| list->set(kLengthIndex, Smi::kZero); |
| return Handle<TemplateList>::cast(list); |
| } |
| |
| // static |
| Handle<TemplateList> TemplateList::Add(Isolate* isolate, |
| Handle<TemplateList> list, |
| Handle<i::Object> value) { |
| STATIC_ASSERT(kFirstElementIndex == 1); |
| int index = list->length() + 1; |
| Handle<i::FixedArray> fixed_array = Handle<FixedArray>::cast(list); |
| fixed_array = FixedArray::SetAndGrow(isolate, fixed_array, index, value); |
| fixed_array->set(kLengthIndex, Smi::FromInt(index)); |
| return Handle<TemplateList>::cast(fixed_array); |
| } |
| |
| |
| // ES6 9.5.1 |
| // static |
| MaybeHandle<Object> JSProxy::GetPrototype(Handle<JSProxy> proxy) { |
| Isolate* isolate = proxy->GetIsolate(); |
| Handle<String> trap_name = isolate->factory()->getPrototypeOf_string(); |
| |
| STACK_CHECK(isolate, MaybeHandle<Object>()); |
| |
| // 1. Let handler be the value of the [[ProxyHandler]] internal slot. |
| // 2. If handler is null, throw a TypeError exception. |
| // 3. Assert: Type(handler) is Object. |
| // 4. Let target be the value of the [[ProxyTarget]] internal slot. |
| if (proxy->IsRevoked()) { |
| THROW_NEW_ERROR(isolate, |
| NewTypeError(MessageTemplate::kProxyRevoked, trap_name), |
| Object); |
| } |
| Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate); |
| Handle<JSReceiver> handler(JSReceiver::cast(proxy->handler()), isolate); |
| |
| // 5. Let trap be ? GetMethod(handler, "getPrototypeOf"). |
| Handle<Object> trap; |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, trap, |
| Object::GetMethod(handler, trap_name), Object); |
| // 6. If trap is undefined, then return target.[[GetPrototypeOf]](). |
| if (trap->IsUndefined(isolate)) { |
| return JSReceiver::GetPrototype(isolate, target); |
| } |
| // 7. Let handlerProto be ? Call(trap, handler, «target»). |
| Handle<Object> argv[] = {target}; |
| Handle<Object> handler_proto; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, handler_proto, |
| Execution::Call(isolate, trap, handler, arraysize(argv), argv), Object); |
| // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError. |
| if (!(handler_proto->IsJSReceiver() || handler_proto->IsNull(isolate))) { |
| THROW_NEW_ERROR(isolate, |
| NewTypeError(MessageTemplate::kProxyGetPrototypeOfInvalid), |
| Object); |
| } |
| // 9. Let extensibleTarget be ? IsExtensible(target). |
| Maybe<bool> is_extensible = JSReceiver::IsExtensible(target); |
| MAYBE_RETURN_NULL(is_extensible); |
| // 10. If extensibleTarget is true, return handlerProto. |
| if (is_extensible.FromJust()) return handler_proto; |
| // 11. Let targetProto be ? target.[[GetPrototypeOf]](). |
| Handle<Object> target_proto; |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, target_proto, |
| JSReceiver::GetPrototype(isolate, target), Object); |
| // 12. If SameValue(handlerProto, targetProto) is false, throw a TypeError. |
| if (!handler_proto->SameValue(*target_proto)) { |
| THROW_NEW_ERROR( |
| isolate, |
| NewTypeError(MessageTemplate::kProxyGetPrototypeOfNonExtensible), |
| Object); |
| } |
| // 13. Return handlerProto. |
| return handler_proto; |
| } |
| |
| MaybeHandle<Object> Object::GetPropertyWithAccessor(LookupIterator* it) { |
| Isolate* isolate = it->isolate(); |
| Handle<Object> structure = it->GetAccessors(); |
| Handle<Object> receiver = it->GetReceiver(); |
| // In case of global IC, the receiver is the global object. Replace by the |
| // global proxy. |
| if (receiver->IsJSGlobalObject()) { |
| receiver = handle(JSGlobalObject::cast(*receiver)->global_proxy(), isolate); |
| } |
| |
| // We should never get here to initialize a const with the hole value since a |
| // const declaration would conflict with the getter. |
| DCHECK(!structure->IsForeign()); |
| |
| // API style callbacks. |
| Handle<JSObject> holder = it->GetHolder<JSObject>(); |
| if (structure->IsAccessorInfo()) { |
| Handle<Name> name = it->GetName(); |
| Handle<AccessorInfo> info = Handle<AccessorInfo>::cast(structure); |
| if (!info->IsCompatibleReceiver(*receiver)) { |
| THROW_NEW_ERROR(isolate, |
| NewTypeError(MessageTemplate::kIncompatibleMethodReceiver, |
| name, receiver), |
| Object); |
| } |
| |
| if (!info->has_getter()) return isolate->factory()->undefined_value(); |
| |
| if (info->is_sloppy() && !receiver->IsJSReceiver()) { |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, receiver, |
| Object::ConvertReceiver(isolate, receiver), |
| Object); |
| } |
| |
| PropertyCallbackArguments args(isolate, info->data(), *receiver, *holder, |
| Just(kDontThrow)); |
| Handle<Object> result = args.CallAccessorGetter(info, name); |
| RETURN_EXCEPTION_IF_SCHEDULED_EXCEPTION(isolate, Object); |
| if (result.is_null()) return isolate->factory()->undefined_value(); |
| Handle<Object> reboxed_result = handle(*result, isolate); |
| if (info->replace_on_access() && receiver->IsJSReceiver()) { |
| RETURN_ON_EXCEPTION(isolate, |
| Accessors::ReplaceAccessorWithDataProperty( |
| receiver, holder, name, result), |
| Object); |
| } |
| return reboxed_result; |
| } |
| |
| // AccessorPair with 'cached' private property. |
| if (it->TryLookupCachedProperty()) { |
| return Object::GetProperty(it); |
| } |
| |
| // Regular accessor. |
| Handle<Object> getter(AccessorPair::cast(*structure)->getter(), isolate); |
| if (getter->IsFunctionTemplateInfo()) { |
| SaveAndSwitchContext save(isolate, *holder->GetCreationContext()); |
| return Builtins::InvokeApiFunction( |
| isolate, false, Handle<FunctionTemplateInfo>::cast(getter), receiver, 0, |
| nullptr, isolate->factory()->undefined_value()); |
| } else if (getter->IsCallable()) { |
| // TODO(rossberg): nicer would be to cast to some JSCallable here... |
| return Object::GetPropertyWithDefinedGetter( |
| receiver, Handle<JSReceiver>::cast(getter)); |
| } |
| // Getter is not a function. |
| return isolate->factory()->undefined_value(); |
| } |
| |
| // static |
| Address AccessorInfo::redirect(Address address, AccessorComponent component) { |
| ApiFunction fun(address); |
| DCHECK_EQ(ACCESSOR_GETTER, component); |
| ExternalReference::Type type = ExternalReference::DIRECT_GETTER_CALL; |
| return ExternalReference::Create(&fun, type).address(); |
| } |
| |
| Address AccessorInfo::redirected_getter() const { |
| Address accessor = v8::ToCData<Address>(getter()); |
| if (accessor == kNullAddress) return kNullAddress; |
| return redirect(accessor, ACCESSOR_GETTER); |
| } |
| |
| Address CallHandlerInfo::redirected_callback() const { |
| Address address = v8::ToCData<Address>(callback()); |
| ApiFunction fun(address); |
| ExternalReference::Type type = ExternalReference::DIRECT_API_CALL; |
| return ExternalReference::Create(&fun, type).address(); |
| } |
| |
| bool AccessorInfo::IsCompatibleReceiverMap(Handle<AccessorInfo> info, |
| Handle<Map> map) { |
| if (!info->HasExpectedReceiverType()) return true; |
| if (!map->IsJSObjectMap()) return false; |
| return FunctionTemplateInfo::cast(info->expected_receiver_type()) |
| ->IsTemplateFor(*map); |
| } |
| |
| Maybe<bool> Object::SetPropertyWithAccessor( |
| LookupIterator* it, Handle<Object> value, |
| Maybe<ShouldThrow> maybe_should_throw) { |
| Isolate* isolate = it->isolate(); |
| Handle<Object> structure = it->GetAccessors(); |
| Handle<Object> receiver = it->GetReceiver(); |
| // In case of global IC, the receiver is the global object. Replace by the |
| // global proxy. |
| if (receiver->IsJSGlobalObject()) { |
| receiver = handle(JSGlobalObject::cast(*receiver)->global_proxy(), isolate); |
| } |
| |
| // We should never get here to initialize a const with the hole value since a |
| // const declaration would conflict with the setter. |
| DCHECK(!structure->IsForeign()); |
| |
| // API style callbacks. |
| Handle<JSObject> holder = it->GetHolder<JSObject>(); |
| if (structure->IsAccessorInfo()) { |
| Handle<Name> name = it->GetName(); |
| Handle<AccessorInfo> info = Handle<AccessorInfo>::cast(structure); |
| if (!info->IsCompatibleReceiver(*receiver)) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kIncompatibleMethodReceiver, name, receiver)); |
| return Nothing<bool>(); |
| } |
| |
| if (!info->has_setter()) { |
| // TODO(verwaest): We should not get here anymore once all AccessorInfos |
| // are marked as special_data_property. They cannot both be writable and |
| // not have a setter. |
| return Just(true); |
| } |
| |
| if (info->is_sloppy() && !receiver->IsJSReceiver()) { |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, receiver, Object::ConvertReceiver(isolate, receiver), |
| Nothing<bool>()); |
| } |
| |
| // The actual type of setter callback is either |
| // v8::AccessorNameSetterCallback or |
| // i::Accesors::AccessorNameBooleanSetterCallback, depending on whether the |
| // AccessorInfo was created by the API or internally (see accessors.cc). |
| // Here we handle both cases using GenericNamedPropertySetterCallback and |
| // its Call method. |
| PropertyCallbackArguments args(isolate, info->data(), *receiver, *holder, |
| maybe_should_throw); |
| Handle<Object> result = args.CallAccessorSetter(info, name, value); |
| // In the case of AccessorNameSetterCallback, we know that the result value |
| // cannot have been set, so the result of Call will be null. In the case of |
| // AccessorNameBooleanSetterCallback, the result will either be null |
| // (signalling an exception) or a boolean Oddball. |
| RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate, Nothing<bool>()); |
| if (result.is_null()) return Just(true); |
| DCHECK(result->BooleanValue(isolate) || |
| GetShouldThrow(isolate, maybe_should_throw) == kDontThrow); |
| return Just(result->BooleanValue(isolate)); |
| } |
| |
| // Regular accessor. |
| Handle<Object> setter(AccessorPair::cast(*structure)->setter(), isolate); |
| if (setter->IsFunctionTemplateInfo()) { |
| SaveAndSwitchContext save(isolate, *holder->GetCreationContext()); |
| Handle<Object> argv[] = {value}; |
| RETURN_ON_EXCEPTION_VALUE( |
| isolate, Builtins::InvokeApiFunction( |
| isolate, false, Handle<FunctionTemplateInfo>::cast(setter), |
| receiver, arraysize(argv), argv, |
| isolate->factory()->undefined_value()), |
| Nothing<bool>()); |
| return Just(true); |
| } else if (setter->IsCallable()) { |
| // TODO(rossberg): nicer would be to cast to some JSCallable here... |
| return SetPropertyWithDefinedSetter( |
| receiver, Handle<JSReceiver>::cast(setter), value, maybe_should_throw); |
| } |
| |
| RETURN_FAILURE(isolate, GetShouldThrow(isolate, maybe_should_throw), |
| NewTypeError(MessageTemplate::kNoSetterInCallback, |
| it->GetName(), it->GetHolder<JSObject>())); |
| } |
| |
| MaybeHandle<Object> Object::GetPropertyWithDefinedGetter( |
| Handle<Object> receiver, |
| Handle<JSReceiver> getter) { |
| Isolate* isolate = getter->GetIsolate(); |
| |
| // Platforms with simulators like arm/arm64 expose a funny issue. If the |
| // simulator has a separate JS stack pointer from the C++ stack pointer, it |
| // can miss C++ stack overflows in the stack guard at the start of JavaScript |
| // functions. It would be very expensive to check the C++ stack pointer at |
| // that location. The best solution seems to be to break the impasse by |
| // adding checks at possible recursion points. What's more, we don't put |
| // this stack check behind the USE_SIMULATOR define in order to keep |
| // behavior the same between hardware and simulators. |
| StackLimitCheck check(isolate); |
| if (check.JsHasOverflowed()) { |
| isolate->StackOverflow(); |
| return MaybeHandle<Object>(); |
| } |
| |
| return Execution::Call(isolate, getter, receiver, 0, nullptr); |
| } |
| |
| Maybe<bool> Object::SetPropertyWithDefinedSetter( |
| Handle<Object> receiver, Handle<JSReceiver> setter, Handle<Object> value, |
| Maybe<ShouldThrow> should_throw) { |
| Isolate* isolate = setter->GetIsolate(); |
| |
| Handle<Object> argv[] = { value }; |
| RETURN_ON_EXCEPTION_VALUE(isolate, Execution::Call(isolate, setter, receiver, |
| arraysize(argv), argv), |
| Nothing<bool>()); |
| return Just(true); |
| } |
| |
| Map Object::GetPrototypeChainRootMap(Isolate* isolate) const { |
| DisallowHeapAllocation no_alloc; |
| if (IsSmi()) { |
| Context native_context = isolate->context()->native_context(); |
| return native_context->number_function()->initial_map(); |
| } |
| |
| const HeapObject heap_object = HeapObject::cast(*this); |
| return heap_object->map()->GetPrototypeChainRootMap(isolate); |
| } |
| |
| Smi Object::GetOrCreateHash(Isolate* isolate) { |
| DisallowHeapAllocation no_gc; |
| Object hash = Object::GetSimpleHash(*this); |
| if (hash->IsSmi()) return Smi::cast(hash); |
| |
| DCHECK(IsJSReceiver()); |
| return JSReceiver::cast(*this)->GetOrCreateIdentityHash(isolate); |
| } |
| |
| bool Object::SameValue(Object other) { |
| if (other == *this) return true; |
| |
| if (IsNumber() && other->IsNumber()) { |
| double this_value = Number(); |
| double other_value = other->Number(); |
| // SameValue(NaN, NaN) is true. |
| if (this_value != other_value) { |
| return std::isnan(this_value) && std::isnan(other_value); |
| } |
| // SameValue(0.0, -0.0) is false. |
| return (std::signbit(this_value) == std::signbit(other_value)); |
| } |
| if (IsString() && other->IsString()) { |
| return String::cast(*this)->Equals(String::cast(other)); |
| } |
| if (IsBigInt() && other->IsBigInt()) { |
| return BigInt::EqualToBigInt(BigInt::cast(*this), BigInt::cast(other)); |
| } |
| return false; |
| } |
| |
| bool Object::SameValueZero(Object other) { |
| if (other == *this) return true; |
| |
| if (IsNumber() && other->IsNumber()) { |
| double this_value = Number(); |
| double other_value = other->Number(); |
| // +0 == -0 is true |
| return this_value == other_value || |
| (std::isnan(this_value) && std::isnan(other_value)); |
| } |
| if (IsString() && other->IsString()) { |
| return String::cast(*this)->Equals(String::cast(other)); |
| } |
| if (IsBigInt() && other->IsBigInt()) { |
| return BigInt::EqualToBigInt(BigInt::cast(*this), BigInt::cast(other)); |
| } |
| return false; |
| } |
| |
| MaybeHandle<Object> Object::ArraySpeciesConstructor( |
| Isolate* isolate, Handle<Object> original_array) { |
| Handle<Object> default_species = isolate->array_function(); |
| if (original_array->IsJSArray() && |
| Handle<JSArray>::cast(original_array)->HasArrayPrototype(isolate) && |
| isolate->IsArraySpeciesLookupChainIntact()) { |
| return default_species; |
| } |
| Handle<Object> constructor = isolate->factory()->undefined_value(); |
| Maybe<bool> is_array = Object::IsArray(original_array); |
| MAYBE_RETURN_NULL(is_array); |
| if (is_array.FromJust()) { |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, constructor, |
| Object::GetProperty(isolate, original_array, |
| isolate->factory()->constructor_string()), |
| Object); |
| if (constructor->IsConstructor()) { |
| Handle<Context> constructor_context; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, constructor_context, |
| JSReceiver::GetFunctionRealm(Handle<JSReceiver>::cast(constructor)), |
| Object); |
| if (*constructor_context != *isolate->native_context() && |
| *constructor == constructor_context->array_function()) { |
| constructor = isolate->factory()->undefined_value(); |
| } |
| } |
| if (constructor->IsJSReceiver()) { |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, constructor, |
| JSReceiver::GetProperty(isolate, |
| Handle<JSReceiver>::cast(constructor), |
| isolate->factory()->species_symbol()), |
| Object); |
| if (constructor->IsNull(isolate)) { |
| constructor = isolate->factory()->undefined_value(); |
| } |
| } |
| } |
| if (constructor->IsUndefined(isolate)) { |
| return default_species; |
| } else { |
| if (!constructor->IsConstructor()) { |
| THROW_NEW_ERROR(isolate, |
| NewTypeError(MessageTemplate::kSpeciesNotConstructor), |
| Object); |
| } |
| return constructor; |
| } |
| } |
| |
| // ES6 section 7.3.20 SpeciesConstructor ( O, defaultConstructor ) |
| V8_WARN_UNUSED_RESULT MaybeHandle<Object> Object::SpeciesConstructor( |
| Isolate* isolate, Handle<JSReceiver> recv, |
| Handle<JSFunction> default_ctor) { |
| Handle<Object> ctor_obj; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, ctor_obj, |
| JSObject::GetProperty(isolate, recv, |
| isolate->factory()->constructor_string()), |
| Object); |
| |
| if (ctor_obj->IsUndefined(isolate)) return default_ctor; |
| |
| if (!ctor_obj->IsJSReceiver()) { |
| THROW_NEW_ERROR(isolate, |
| NewTypeError(MessageTemplate::kConstructorNotReceiver), |
| Object); |
| } |
| |
| Handle<JSReceiver> ctor = Handle<JSReceiver>::cast(ctor_obj); |
| |
| Handle<Object> species; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, species, |
| JSObject::GetProperty(isolate, ctor, |
| isolate->factory()->species_symbol()), |
| Object); |
| |
| if (species->IsNullOrUndefined(isolate)) { |
| return default_ctor; |
| } |
| |
| if (species->IsConstructor()) return species; |
| |
| THROW_NEW_ERROR( |
| isolate, NewTypeError(MessageTemplate::kSpeciesNotConstructor), Object); |
| } |
| |
| bool Object::IterationHasObservableEffects() { |
| // Check that this object is an array. |
| if (!IsJSArray()) return true; |
| JSArray array = JSArray::cast(*this); |
| Isolate* isolate = array->GetIsolate(); |
| |
| #ifdef V8_ENABLE_FORCE_SLOW_PATH |
| if (isolate->force_slow_path()) return true; |
| #endif |
| |
| // Check that we have the original ArrayPrototype. |
| if (!array->map()->prototype()->IsJSObject()) return true; |
| JSObject array_proto = JSObject::cast(array->map()->prototype()); |
| if (!isolate->is_initial_array_prototype(array_proto)) return true; |
| |
| // Check that the ArrayPrototype hasn't been modified in a way that would |
| // affect iteration. |
| if (!isolate->IsArrayIteratorLookupChainIntact()) return true; |
| |
| // For FastPacked kinds, iteration will have the same effect as simply |
| // accessing each property in order. |
| ElementsKind array_kind = array->GetElementsKind(); |
| if (IsFastPackedElementsKind(array_kind)) return false; |
| |
| // For FastHoley kinds, an element access on a hole would cause a lookup on |
| // the prototype. This could have different results if the prototype has been |
| // changed. |
| if (IsHoleyElementsKind(array_kind) && |
| isolate->IsNoElementsProtectorIntact()) { |
| return false; |
| } |
| return true; |
| } |
| |
| void Object::ShortPrint(FILE* out) const { |
| OFStream os(out); |
| os << Brief(*this); |
| } |
| |
| void Object::ShortPrint(StringStream* accumulator) const { |
| std::ostringstream os; |
| os << Brief(*this); |
| accumulator->Add(os.str().c_str()); |
| } |
| |
| void Object::ShortPrint(std::ostream& os) const { os << Brief(*this); } |
| |
| std::ostream& operator<<(std::ostream& os, const Object& obj) { |
| obj.ShortPrint(os); |
| return os; |
| } |
| |
| void MaybeObject::ShortPrint(FILE* out) { |
| OFStream os(out); |
| os << Brief(*this); |
| } |
| |
| void MaybeObject::ShortPrint(StringStream* accumulator) { |
| std::ostringstream os; |
| os << Brief(*this); |
| accumulator->Add(os.str().c_str()); |
| } |
| |
| void MaybeObject::ShortPrint(std::ostream& os) { os << Brief(*this); } |
| |
| Brief::Brief(const Object v) : value(v->ptr()) {} |
| Brief::Brief(const MaybeObject v) : value(v.ptr()) {} |
| |
| std::ostream& operator<<(std::ostream& os, const Brief& v) { |
| MaybeObject maybe_object(v.value); |
| Smi smi; |
| HeapObject heap_object; |
| if (maybe_object->ToSmi(&smi)) { |
| smi->SmiPrint(os); |
| } else if (maybe_object->IsCleared()) { |
| os << "[cleared]"; |
| } else if (maybe_object->GetHeapObjectIfWeak(&heap_object)) { |
| os << "[weak] "; |
| heap_object->HeapObjectShortPrint(os); |
| } else if (maybe_object->GetHeapObjectIfStrong(&heap_object)) { |
| heap_object->HeapObjectShortPrint(os); |
| } else { |
| UNREACHABLE(); |
| } |
| return os; |
| } |
| |
| void Smi::SmiPrint(std::ostream& os) const { // NOLINT |
| os << value(); |
| } |
| |
| |
| |
| void HeapObject::HeapObjectShortPrint(std::ostream& os) { // NOLINT |
| os << AsHex::Address(this->ptr()) << " "; |
| |
| if (IsString()) { |
| HeapStringAllocator allocator; |
| StringStream accumulator(&allocator); |
| String::cast(*this)->StringShortPrint(&accumulator); |
| os << accumulator.ToCString().get(); |
| return; |
| } |
| if (IsJSObject()) { |
| HeapStringAllocator allocator; |
| StringStream accumulator(&allocator); |
| JSObject::cast(*this)->JSObjectShortPrint(&accumulator); |
| os << accumulator.ToCString().get(); |
| return; |
| } |
| switch (map()->instance_type()) { |
| case MAP_TYPE: { |
| os << "<Map"; |
| Map mapInstance = Map::cast(*this); |
| if (mapInstance->IsJSObjectMap()) { |
| os << "(" << ElementsKindToString(mapInstance->elements_kind()) << ")"; |
| } else if (mapInstance->instance_size() != kVariableSizeSentinel) { |
| os << "[" << mapInstance->instance_size() << "]"; |
| } |
| os << ">"; |
| } break; |
| case AWAIT_CONTEXT_TYPE: { |
| os << "<AwaitContext generator= "; |
| HeapStringAllocator allocator; |
| StringStream accumulator(&allocator); |
| Context::cast(*this)->extension()->ShortPrint(&accumulator); |
| os << accumulator.ToCString().get(); |
| os << '>'; |
| break; |
| } |
| case BLOCK_CONTEXT_TYPE: |
| os << "<BlockContext[" << Context::cast(*this)->length() << "]>"; |
| break; |
| case CATCH_CONTEXT_TYPE: |
| os << "<CatchContext[" << Context::cast(*this)->length() << "]>"; |
| break; |
| case DEBUG_EVALUATE_CONTEXT_TYPE: |
| os << "<DebugEvaluateContext[" << Context::cast(*this)->length() << "]>"; |
| break; |
| case EVAL_CONTEXT_TYPE: |
| os << "<EvalContext[" << Context::cast(*this)->length() << "]>"; |
| break; |
| case FUNCTION_CONTEXT_TYPE: |
| os << "<FunctionContext[" << Context::cast(*this)->length() << "]>"; |
| break; |
| case MODULE_CONTEXT_TYPE: |
| os << "<ModuleContext[" << Context::cast(*this)->length() << "]>"; |
| break; |
| case NATIVE_CONTEXT_TYPE: |
| os << "<NativeContext[" << Context::cast(*this)->length() << "]>"; |
| break; |
| case SCRIPT_CONTEXT_TYPE: |
| os << "<ScriptContext[" << Context::cast(*this)->length() << "]>"; |
| break; |
| case WITH_CONTEXT_TYPE: |
| os << "<WithContext[" << Context::cast(*this)->length() << "]>"; |
| break; |
| case SCRIPT_CONTEXT_TABLE_TYPE: |
| os << "<ScriptContextTable[" << FixedArray::cast(*this)->length() << "]>"; |
| break; |
| case HASH_TABLE_TYPE: |
| os << "<HashTable[" << FixedArray::cast(*this)->length() << "]>"; |
| break; |
| case ORDERED_HASH_MAP_TYPE: |
| os << "<OrderedHashMap[" << FixedArray::cast(*this)->length() << "]>"; |
| break; |
| case ORDERED_HASH_SET_TYPE: |
| os << "<OrderedHashSet[" << FixedArray::cast(*this)->length() << "]>"; |
| break; |
| case ORDERED_NAME_DICTIONARY_TYPE: |
| os << "<OrderedNameDictionary[" << FixedArray::cast(*this)->length() |
| << "]>"; |
| break; |
| case NAME_DICTIONARY_TYPE: |
| os << "<NameDictionary[" << FixedArray::cast(*this)->length() << "]>"; |
| break; |
| case GLOBAL_DICTIONARY_TYPE: |
| os << "<GlobalDictionary[" << FixedArray::cast(*this)->length() << "]>"; |
| break; |
| case NUMBER_DICTIONARY_TYPE: |
| os << "<NumberDictionary[" << FixedArray::cast(*this)->length() << "]>"; |
| break; |
| case SIMPLE_NUMBER_DICTIONARY_TYPE: |
| os << "<SimpleNumberDictionary[" << FixedArray::cast(*this)->length() |
| << "]>"; |
| break; |
| case STRING_TABLE_TYPE: |
| os << "<StringTable[" << FixedArray::cast(*this)->length() << "]>"; |
| break; |
| case FIXED_ARRAY_TYPE: |
| os << "<FixedArray[" << FixedArray::cast(*this)->length() << "]>"; |
| break; |
| case OBJECT_BOILERPLATE_DESCRIPTION_TYPE: |
| os << "<ObjectBoilerplateDescription[" |
| << FixedArray::cast(*this)->length() << "]>"; |
| break; |
| case FIXED_DOUBLE_ARRAY_TYPE: |
| os << "<FixedDoubleArray[" << FixedDoubleArray::cast(*this)->length() |
| << "]>"; |
| break; |
| case BYTE_ARRAY_TYPE: |
| os << "<ByteArray[" << ByteArray::cast(*this)->length() << "]>"; |
| break; |
| case BYTECODE_ARRAY_TYPE: |
| os << "<BytecodeArray[" << BytecodeArray::cast(*this)->length() << "]>"; |
| break; |
| case DESCRIPTOR_ARRAY_TYPE: |
| os << "<DescriptorArray[" |
| << DescriptorArray::cast(*this)->number_of_descriptors() << "]>"; |
| break; |
| case TRANSITION_ARRAY_TYPE: |
| os << "<TransitionArray[" << TransitionArray::cast(*this)->length() |
| << "]>"; |
| break; |
| case PROPERTY_ARRAY_TYPE: |
| os << "<PropertyArray[" << PropertyArray::cast(*this)->length() << "]>"; |
| break; |
| case FEEDBACK_CELL_TYPE: { |
| { |
| ReadOnlyRoots roots = GetReadOnlyRoots(); |
| os << "<FeedbackCell["; |
| if (map() == roots.no_closures_cell_map()) { |
| os << "no feedback"; |
| } else if (map() == roots.no_closures_cell_map()) { |
| os << "no closures"; |
| } else if (map() == roots.one_closure_cell_map()) { |
| os << "one closure"; |
| } else if (map() == roots.many_closures_cell_map()) { |
| os << "many closures"; |
| } else { |
| os << "!!!INVALID MAP!!!"; |
| } |
| os << "]>"; |
| } |
| break; |
| } |
| case FEEDBACK_VECTOR_TYPE: |
| os << "<FeedbackVector[" << FeedbackVector::cast(*this)->length() << "]>"; |
| break; |
| case FREE_SPACE_TYPE: |
| os << "<FreeSpace[" << FreeSpace::cast(*this)->size() << "]>"; |
| break; |
| #define TYPED_ARRAY_SHORT_PRINT(Type, type, TYPE, ctype) \ |
| case FIXED_##TYPE##_ARRAY_TYPE: \ |
| os << "<Fixed" #Type "Array[" << Fixed##Type##Array::cast(*this)->length() \ |
| << "]>"; \ |
| break; |
| |
| TYPED_ARRAYS(TYPED_ARRAY_SHORT_PRINT) |
| #undef TYPED_ARRAY_SHORT_PRINT |
| |
| case PREPARSE_DATA_TYPE: { |
| PreparseData data = PreparseData::cast(*this); |
| os << "<PreparseData[data=" << data->data_length() |
| << " children=" << data->children_length() << "]>"; |
| break; |
| } |
| |
| case UNCOMPILED_DATA_WITHOUT_PREPARSE_DATA_TYPE: { |
| UncompiledDataWithoutPreparseData data = |
| UncompiledDataWithoutPreparseData::cast(*this); |
| os << "<UncompiledDataWithoutPreparseData (" << data->start_position() |
| << ", " << data->end_position() << ")]>"; |
| break; |
| } |
| |
| case UNCOMPILED_DATA_WITH_PREPARSE_DATA_TYPE: { |
| UncompiledDataWithPreparseData data = |
| UncompiledDataWithPreparseData::cast(*this); |
| os << "<UncompiledDataWithPreparseData (" << data->start_position() |
| << ", " << data->end_position() |
| << ") preparsed=" << Brief(data->preparse_data()) << ">"; |
| break; |
| } |
| |
| case SHARED_FUNCTION_INFO_TYPE: { |
| SharedFunctionInfo shared = SharedFunctionInfo::cast(*this); |
| std::unique_ptr<char[]> debug_name = shared->DebugName()->ToCString(); |
| if (debug_name[0] != 0) { |
| os << "<SharedFunctionInfo " << debug_name.get() << ">"; |
| } else { |
| os << "<SharedFunctionInfo>"; |
| } |
| break; |
| } |
| case JS_MESSAGE_OBJECT_TYPE: |
| os << "<JSMessageObject>"; |
| break; |
| #define MAKE_STRUCT_CASE(TYPE, Name, name) \ |
| case TYPE: \ |
| os << "<" #Name; \ |
| Name::cast(*this)->BriefPrintDetails(os); \ |
| os << ">"; \ |
| break; |
| STRUCT_LIST(MAKE_STRUCT_CASE) |
| #undef MAKE_STRUCT_CASE |
| case ALLOCATION_SITE_TYPE: { |
| os << "<AllocationSite"; |
| AllocationSite::cast(*this)->BriefPrintDetails(os); |
| os << ">"; |
| break; |
| } |
| case SCOPE_INFO_TYPE: { |
| ScopeInfo scope = ScopeInfo::cast(*this); |
| os << "<ScopeInfo"; |
| if (scope->length()) os << " " << scope->scope_type() << " "; |
| os << "[" << scope->length() << "]>"; |
| break; |
| } |
| case CODE_TYPE: { |
| Code code = Code::cast(*this); |
| os << "<Code " << Code::Kind2String(code->kind()); |
| if (code->is_builtin()) { |
| os << " " << Builtins::name(code->builtin_index()); |
| } |
| os << ">"; |
| break; |
| } |
| case ODDBALL_TYPE: { |
| if (IsUndefined()) { |
| os << "<undefined>"; |
| } else if (IsTheHole()) { |
| os << "<the_hole>"; |
| } else if (IsNull()) { |
| os << "<null>"; |
| } else if (IsTrue()) { |
| os << "<true>"; |
| } else if (IsFalse()) { |
| os << "<false>"; |
| } else { |
| os << "<Odd Oddball: "; |
| os << Oddball::cast(*this)->to_string()->ToCString().get(); |
| os << ">"; |
| } |
| break; |
| } |
| case SYMBOL_TYPE: { |
| Symbol symbol = Symbol::cast(*this); |
| symbol->SymbolShortPrint(os); |
| break; |
| } |
| case HEAP_NUMBER_TYPE: { |
| os << "<HeapNumber "; |
| HeapNumber::cast(*this)->HeapNumberPrint(os); |
| os << ">"; |
| break; |
| } |
| case MUTABLE_HEAP_NUMBER_TYPE: { |
| os << "<MutableHeapNumber "; |
| MutableHeapNumber::cast(*this)->MutableHeapNumberPrint(os); |
| os << '>'; |
| break; |
| } |
| case BIGINT_TYPE: { |
| os << "<BigInt "; |
| BigInt::cast(*this)->BigIntShortPrint(os); |
| os << ">"; |
| break; |
| } |
| case JS_PROXY_TYPE: |
| os << "<JSProxy>"; |
| break; |
| case FOREIGN_TYPE: |
| os << "<Foreign>"; |
| break; |
| case CELL_TYPE: { |
| os << "<Cell value= "; |
| HeapStringAllocator allocator; |
| StringStream accumulator(&allocator); |
| Cell::cast(*this)->value()->ShortPrint(&accumulator); |
| os << accumulator.ToCString().get(); |
| os << '>'; |
| break; |
| } |
| case PROPERTY_CELL_TYPE: { |
| PropertyCell cell = PropertyCell::cast(*this); |
| os << "<PropertyCell name="; |
| cell->name()->ShortPrint(os); |
| os << " value="; |
| HeapStringAllocator allocator; |
| StringStream accumulator(&allocator); |
| cell->value()->ShortPrint(&accumulator); |
| os << accumulator.ToCString().get(); |
| os << '>'; |
| break; |
| } |
| case CALL_HANDLER_INFO_TYPE: { |
| CallHandlerInfo info = CallHandlerInfo::cast(*this); |
| os << "<CallHandlerInfo "; |
| os << "callback= " << Brief(info->callback()); |
| os << ", js_callback= " << Brief(info->js_callback()); |
| os << ", data= " << Brief(info->data()); |
| if (info->IsSideEffectFreeCallHandlerInfo()) { |
| os << ", side_effect_free= true>"; |
| } else { |
| os << ", side_effect_free= false>"; |
| } |
| break; |
| } |
| default: |
| os << "<Other heap object (" << map()->instance_type() << ")>"; |
| break; |
| } |
| } |
| |
| void Struct::BriefPrintDetails(std::ostream& os) {} |
| |
| void Tuple2::BriefPrintDetails(std::ostream& os) { |
| os << " " << Brief(value1()) << ", " << Brief(value2()); |
| } |
| |
| void Tuple3::BriefPrintDetails(std::ostream& os) { |
| os << " " << Brief(value1()) << ", " << Brief(value2()) << ", " |
| << Brief(value3()); |
| } |
| |
| void ClassPositions::BriefPrintDetails(std::ostream& os) { |
| os << " " << start() << ", " << end(); |
| } |
| |
| void ArrayBoilerplateDescription::BriefPrintDetails(std::ostream& os) { |
| os << " " << elements_kind() << ", " << Brief(constant_elements()); |
| } |
| |
| void CallableTask::BriefPrintDetails(std::ostream& os) { |
| os << " callable=" << Brief(callable()); |
| } |
| |
| void HeapObject::Iterate(ObjectVisitor* v) { IterateFast<ObjectVisitor>(v); } |
| |
| |
| void HeapObject::IterateBody(ObjectVisitor* v) { |
| Map m = map(); |
| IterateBodyFast<ObjectVisitor>(m, SizeFromMap(m), v); |
| } |
| |
| void HeapObject::IterateBody(Map map, int object_size, ObjectVisitor* v) { |
| IterateBodyFast<ObjectVisitor>(map, object_size, v); |
| } |
| |
| |
| struct CallIsValidSlot { |
| template <typename BodyDescriptor> |
| static bool apply(Map map, HeapObject obj, int offset, int) { |
| return BodyDescriptor::IsValidSlot(map, obj, offset); |
| } |
| }; |
| |
| bool HeapObject::IsValidSlot(Map map, int offset) { |
| DCHECK_NE(0, offset); |
| return BodyDescriptorApply<CallIsValidSlot, bool>(map->instance_type(), map, |
| *this, offset, 0); |
| } |
| |
| int HeapObject::SizeFromMap(Map map) const { |
| int instance_size = map->instance_size(); |
| if (instance_size != kVariableSizeSentinel) return instance_size; |
| // Only inline the most frequent cases. |
| InstanceType instance_type = map->instance_type(); |
| if (IsInRange(instance_type, FIRST_FIXED_ARRAY_TYPE, LAST_FIXED_ARRAY_TYPE)) { |
| return FixedArray::SizeFor( |
| FixedArray::unchecked_cast(*this)->synchronized_length()); |
| } |
| if (IsInRange(instance_type, FIRST_CONTEXT_TYPE, LAST_CONTEXT_TYPE)) { |
| // Native context has fixed size. |
| DCHECK_NE(instance_type, NATIVE_CONTEXT_TYPE); |
| return Context::SizeFor(Context::unchecked_cast(*this)->length()); |
| } |
| if (instance_type == ONE_BYTE_STRING_TYPE || |
| instance_type == ONE_BYTE_INTERNALIZED_STRING_TYPE) { |
| // Strings may get concurrently truncated, hence we have to access its |
| // length synchronized. |
| return SeqOneByteString::SizeFor( |
| SeqOneByteString::unchecked_cast(*this)->synchronized_length()); |
| } |
| if (instance_type == BYTE_ARRAY_TYPE) { |
| return ByteArray::SizeFor( |
| ByteArray::unchecked_cast(*this)->synchronized_length()); |
| } |
| if (instance_type == BYTECODE_ARRAY_TYPE) { |
| return BytecodeArray::SizeFor( |
| BytecodeArray::unchecked_cast(*this)->synchronized_length()); |
| } |
| if (instance_type == FREE_SPACE_TYPE) { |
| return FreeSpace::unchecked_cast(*this)->relaxed_read_size(); |
| } |
| if (instance_type == STRING_TYPE || |
| instance_type == INTERNALIZED_STRING_TYPE) { |
| // Strings may get concurrently truncated, hence we have to access its |
| // length synchronized. |
| return SeqTwoByteString::SizeFor( |
| SeqTwoByteString::unchecked_cast(*this)->synchronized_length()); |
| } |
| if (instance_type == FIXED_DOUBLE_ARRAY_TYPE) { |
| return FixedDoubleArray::SizeFor( |
| FixedDoubleArray::unchecked_cast(*this)->synchronized_length()); |
| } |
| if (instance_type == FEEDBACK_METADATA_TYPE) { |
| return FeedbackMetadata::SizeFor( |
| FeedbackMetadata::unchecked_cast(*this)->synchronized_slot_count()); |
| } |
| if (instance_type == DESCRIPTOR_ARRAY_TYPE) { |
| return DescriptorArray::SizeFor( |
| DescriptorArray::unchecked_cast(*this)->number_of_all_descriptors()); |
| } |
| if (IsInRange(instance_type, FIRST_WEAK_FIXED_ARRAY_TYPE, |
| LAST_WEAK_FIXED_ARRAY_TYPE)) { |
| return WeakFixedArray::SizeFor( |
| WeakFixedArray::unchecked_cast(*this)->synchronized_length()); |
| } |
| if (instance_type == WEAK_ARRAY_LIST_TYPE) { |
| return WeakArrayList::SizeForCapacity( |
| WeakArrayList::unchecked_cast(*this)->synchronized_capacity()); |
| } |
| if (IsInRange(instance_type, FIRST_FIXED_TYPED_ARRAY_TYPE, |
| LAST_FIXED_TYPED_ARRAY_TYPE)) { |
| return FixedTypedArrayBase::unchecked_cast(*this)->TypedArraySize( |
| instance_type); |
| } |
| if (instance_type == SMALL_ORDERED_HASH_SET_TYPE) { |
| return SmallOrderedHashSet::SizeFor( |
| SmallOrderedHashSet::unchecked_cast(*this)->Capacity()); |
| } |
| if (instance_type == SMALL_ORDERED_HASH_MAP_TYPE) { |
| return SmallOrderedHashMap::SizeFor( |
| SmallOrderedHashMap::unchecked_cast(*this)->Capacity()); |
| } |
| if (instance_type == SMALL_ORDERED_NAME_DICTIONARY_TYPE) { |
| return SmallOrderedNameDictionary::SizeFor( |
| SmallOrderedNameDictionary::unchecked_cast(*this)->Capacity()); |
| } |
| if (instance_type == PROPERTY_ARRAY_TYPE) { |
| return PropertyArray::SizeFor( |
| PropertyArray::cast(*this)->synchronized_length()); |
| } |
| if (instance_type == FEEDBACK_VECTOR_TYPE) { |
| return FeedbackVector::SizeFor( |
| FeedbackVector::unchecked_cast(*this)->length()); |
| } |
| if (instance_type == BIGINT_TYPE) { |
| return BigInt::SizeFor(BigInt::unchecked_cast(*this)->length()); |
| } |
| if (instance_type == PREPARSE_DATA_TYPE) { |
| PreparseData data = PreparseData::unchecked_cast(*this); |
| return PreparseData::SizeFor(data->data_length(), data->children_length()); |
| } |
| if (instance_type == CODE_TYPE) { |
| return Code::unchecked_cast(*this)->CodeSize(); |
| } |
| DCHECK_EQ(instance_type, EMBEDDER_DATA_ARRAY_TYPE); |
| return EmbedderDataArray::SizeFor( |
| EmbedderDataArray::unchecked_cast(*this)->length()); |
| } |
| |
| bool HeapObject::NeedsRehashing() const { |
| switch (map()->instance_type()) { |
| case DESCRIPTOR_ARRAY_TYPE: |
| return DescriptorArray::cast(*this)->number_of_descriptors() > 1; |
| case TRANSITION_ARRAY_TYPE: |
| return TransitionArray::cast(*this)->number_of_entries() > 1; |
| case ORDERED_HASH_MAP_TYPE: |
| return OrderedHashMap::cast(*this)->NumberOfElements() > 0; |
| case ORDERED_HASH_SET_TYPE: |
| return OrderedHashSet::cast(*this)->NumberOfElements() > 0; |
| case NAME_DICTIONARY_TYPE: |
| case GLOBAL_DICTIONARY_TYPE: |
| case NUMBER_DICTIONARY_TYPE: |
| case SIMPLE_NUMBER_DICTIONARY_TYPE: |
| case STRING_TABLE_TYPE: |
| case HASH_TABLE_TYPE: |
| case SMALL_ORDERED_HASH_MAP_TYPE: |
| case SMALL_ORDERED_HASH_SET_TYPE: |
| case SMALL_ORDERED_NAME_DICTIONARY_TYPE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool HeapObject::CanBeRehashed() const { |
| DCHECK(NeedsRehashing()); |
| switch (map()->instance_type()) { |
| case ORDERED_HASH_MAP_TYPE: |
| case ORDERED_HASH_SET_TYPE: |
| case ORDERED_NAME_DICTIONARY_TYPE: |
| // TODO(yangguo): actually support rehashing OrderedHash{Map,Set}. |
| return false; |
| case NAME_DICTIONARY_TYPE: |
| case GLOBAL_DICTIONARY_TYPE: |
| case NUMBER_DICTIONARY_TYPE: |
| case SIMPLE_NUMBER_DICTIONARY_TYPE: |
| case STRING_TABLE_TYPE: |
| return true; |
| case DESCRIPTOR_ARRAY_TYPE: |
| return true; |
| case TRANSITION_ARRAY_TYPE: |
| return true; |
| case SMALL_ORDERED_HASH_MAP_TYPE: |
| return SmallOrderedHashMap::cast(*this)->NumberOfElements() == 0; |
| case SMALL_ORDERED_HASH_SET_TYPE: |
| return SmallOrderedHashMap::cast(*this)->NumberOfElements() == 0; |
| case SMALL_ORDERED_NAME_DICTIONARY_TYPE: |
| return SmallOrderedNameDictionary::cast(*this)->NumberOfElements() == 0; |
| default: |
| return false; |
| } |
| return false; |
| } |
| |
| void HeapObject::RehashBasedOnMap(ReadOnlyRoots roots) { |
| switch (map()->instance_type()) { |
| case HASH_TABLE_TYPE: |
| UNREACHABLE(); |
| break; |
| case NAME_DICTIONARY_TYPE: |
| NameDictionary::cast(*this)->Rehash(roots); |
| break; |
| case GLOBAL_DICTIONARY_TYPE: |
| GlobalDictionary::cast(*this)->Rehash(roots); |
| break; |
| case NUMBER_DICTIONARY_TYPE: |
| NumberDictionary::cast(*this)->Rehash(roots); |
| break; |
| case SIMPLE_NUMBER_DICTIONARY_TYPE: |
| SimpleNumberDictionary::cast(*this)->Rehash(roots); |
| break; |
| case STRING_TABLE_TYPE: |
| StringTable::cast(*this)->Rehash(roots); |
| break; |
| case DESCRIPTOR_ARRAY_TYPE: |
| DCHECK_LE(1, DescriptorArray::cast(*this)->number_of_descriptors()); |
| DescriptorArray::cast(*this)->Sort(); |
| break; |
| case TRANSITION_ARRAY_TYPE: |
| TransitionArray::cast(*this)->Sort(); |
| break; |
| case SMALL_ORDERED_HASH_MAP_TYPE: |
| DCHECK_EQ(0, SmallOrderedHashMap::cast(*this)->NumberOfElements()); |
| break; |
| case SMALL_ORDERED_HASH_SET_TYPE: |
| DCHECK_EQ(0, SmallOrderedHashSet::cast(*this)->NumberOfElements()); |
| break; |
| case SMALL_ORDERED_NAME_DICTIONARY_TYPE: |
| DCHECK_EQ(0, SmallOrderedNameDictionary::cast(*this)->NumberOfElements()); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| bool HeapObject::IsExternal(Isolate* isolate) const { |
| return map()->FindRootMap(isolate) == isolate->heap()->external_map(); |
| } |
| |
| void DescriptorArray::GeneralizeAllFields() { |
| int length = number_of_descriptors(); |
| for (int i = 0; i < length; i++) { |
| PropertyDetails details = GetDetails(i); |
| details = details.CopyWithRepresentation(Representation::Tagged()); |
| if (details.location() == kField) { |
| DCHECK_EQ(kData, details.kind()); |
| details = details.CopyWithConstness(PropertyConstness::kMutable); |
| SetValue(i, FieldType::Any()); |
| } |
| set(ToDetailsIndex(i), MaybeObject::FromObject(details.AsSmi())); |
| } |
| } |
| |
| MaybeHandle<Object> Object::SetProperty(Isolate* isolate, Handle<Object> object, |
| Handle<Name> name, Handle<Object> value, |
| StoreOrigin store_origin, |
| Maybe<ShouldThrow> should_throw) { |
| LookupIterator it(isolate, object, name); |
| MAYBE_RETURN_NULL(SetProperty(&it, value, store_origin, should_throw)); |
| return value; |
| } |
| |
| Maybe<bool> Object::SetPropertyInternal(LookupIterator* it, |
| Handle<Object> value, |
| Maybe<ShouldThrow> should_throw, |
| StoreOrigin store_origin, bool* found) { |
| it->UpdateProtector(); |
| DCHECK(it->IsFound()); |
| |
| // Make sure that the top context does not change when doing callbacks or |
| // interceptor calls. |
| AssertNoContextChange ncc(it->isolate()); |
| |
| do { |
| switch (it->state()) { |
| case LookupIterator::NOT_FOUND: |
| UNREACHABLE(); |
| |
| case LookupIterator::ACCESS_CHECK: |
| if (it->HasAccess()) break; |
| // Check whether it makes sense to reuse the lookup iterator. Here it |
| // might still call into setters up the prototype chain. |
| return JSObject::SetPropertyWithFailedAccessCheck(it, value, |
| should_throw); |
| |
| case LookupIterator::JSPROXY: { |
| Handle<Object> receiver = it->GetReceiver(); |
| // In case of global IC, the receiver is the global object. Replace by |
| // the global proxy. |
| if (receiver->IsJSGlobalObject()) { |
| receiver = handle(JSGlobalObject::cast(*receiver)->global_proxy(), |
| it->isolate()); |
| } |
| return JSProxy::SetProperty(it->GetHolder<JSProxy>(), it->GetName(), |
| value, receiver, should_throw); |
| } |
| |
| case LookupIterator::INTERCEPTOR: { |
| if (it->HolderIsReceiverOrHiddenPrototype()) { |
| Maybe<bool> result = |
| JSObject::SetPropertyWithInterceptor(it, should_throw, value); |
| if (result.IsNothing() || result.FromJust()) return result; |
| } else { |
| Maybe<PropertyAttributes> maybe_attributes = |
| JSObject::GetPropertyAttributesWithInterceptor(it); |
| if (maybe_attributes.IsNothing()) return Nothing<bool>(); |
| if ((maybe_attributes.FromJust() & READ_ONLY) != 0) { |
| return WriteToReadOnlyProperty(it, value, should_throw); |
| } |
| if (maybe_attributes.FromJust() == ABSENT) break; |
| *found = false; |
| return Nothing<bool>(); |
| } |
| break; |
| } |
| |
| case LookupIterator::ACCESSOR: { |
| if (it->IsReadOnly()) { |
| return WriteToReadOnlyProperty(it, value, should_throw); |
| } |
| Handle<Object> accessors = it->GetAccessors(); |
| if (accessors->IsAccessorInfo() && |
| !it->HolderIsReceiverOrHiddenPrototype() && |
| AccessorInfo::cast(*accessors)->is_special_data_property()) { |
| *found = false; |
| return Nothing<bool>(); |
| } |
| return SetPropertyWithAccessor(it, value, should_throw); |
| } |
| case LookupIterator::INTEGER_INDEXED_EXOTIC: { |
| // IntegerIndexedElementSet converts value to a Number/BigInt prior to |
| // the bounds check. The bounds check has already happened here, but |
| // perform the possibly effectful ToNumber (or ToBigInt) operation |
| // anyways. |
| auto holder = it->GetHolder<JSTypedArray>(); |
| Handle<Object> throwaway_value; |
| if (holder->type() == kExternalBigInt64Array || |
| holder->type() == kExternalBigUint64Array) { |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| it->isolate(), throwaway_value, |
| BigInt::FromObject(it->isolate(), value), Nothing<bool>()); |
| } else { |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| it->isolate(), throwaway_value, |
| Object::ToNumber(it->isolate(), value), Nothing<bool>()); |
| } |
| |
| // FIXME: Throw a TypeError if the holder is detached here |
| // (IntegerIndexedElementSpec step 5). |
| |
| // TODO(verwaest): Per spec, we should return false here (steps 6-9 |
| // in IntegerIndexedElementSpec), resulting in an exception being thrown |
| // on OOB accesses in strict code. Historically, v8 has not done made |
| // this change due to uncertainty about web compat. (v8:4901) |
| return Just(true); |
| } |
| |
| case LookupIterator::DATA: |
| if (it->IsReadOnly()) { |
| return WriteToReadOnlyProperty(it, value, should_throw); |
| } |
| if (it->HolderIsReceiverOrHiddenPrototype()) { |
| return SetDataProperty(it, value); |
| } |
| V8_FALLTHROUGH; |
| case LookupIterator::TRANSITION: |
| *found = false; |
| return Nothing<bool>(); |
| } |
| it->Next(); |
| } while (it->IsFound()); |
| |
| *found = false; |
| return Nothing<bool>(); |
| } |
| |
| Maybe<bool> Object::SetProperty(LookupIterator* it, Handle<Object> value, |
| StoreOrigin store_origin, |
| Maybe<ShouldThrow> should_throw) { |
| if (it->IsFound()) { |
| bool found = true; |
| Maybe<bool> result = |
| SetPropertyInternal(it, value, should_throw, store_origin, &found); |
| if (found) return result; |
| } |
| |
| // If the receiver is the JSGlobalObject, the store was contextual. In case |
| // the property did not exist yet on the global object itself, we have to |
| // throw a reference error in strict mode. In sloppy mode, we continue. |
| if (it->GetReceiver()->IsJSGlobalObject() && |
| (GetShouldThrow(it->isolate(), should_throw) == |
| ShouldThrow::kThrowOnError)) { |
| it->isolate()->Throw(*it->isolate()->factory()->NewReferenceError( |
| MessageTemplate::kNotDefined, it->name())); |
| return Nothing<bool>(); |
| } |
| |
| return AddDataProperty(it, value, NONE, should_throw, store_origin); |
| } |
| |
| Maybe<bool> Object::SetSuperProperty(LookupIterator* it, Handle<Object> value, |
| StoreOrigin store_origin, |
| Maybe<ShouldThrow> should_throw) { |
| Isolate* isolate = it->isolate(); |
| |
| if (it->IsFound()) { |
| bool found = true; |
| Maybe<bool> result = |
| SetPropertyInternal(it, value, should_throw, store_origin, &found); |
| if (found) return result; |
| } |
| |
| it->UpdateProtector(); |
| |
| // The property either doesn't exist on the holder or exists there as a data |
| // property. |
| |
| |
| if (!it->GetReceiver()->IsJSReceiver()) { |
| return WriteToReadOnlyProperty(it, value, should_throw); |
| } |
| Handle<JSReceiver> receiver = Handle<JSReceiver>::cast(it->GetReceiver()); |
| |
| LookupIterator::Configuration c = LookupIterator::OWN; |
| LookupIterator own_lookup = |
| it->IsElement() ? LookupIterator(isolate, receiver, it->index(), c) |
| : LookupIterator(isolate, receiver, it->name(), c); |
| |
| for (; own_lookup.IsFound(); own_lookup.Next()) { |
| switch (own_lookup.state()) { |
| case LookupIterator::ACCESS_CHECK: |
| if (!own_lookup.HasAccess()) { |
| return JSObject::SetPropertyWithFailedAccessCheck(&own_lookup, value, |
| should_throw); |
| } |
| break; |
| |
| case LookupIterator::ACCESSOR: |
| if (own_lookup.GetAccessors()->IsAccessorInfo()) { |
| if (own_lookup.IsReadOnly()) { |
| return WriteToReadOnlyProperty(&own_lookup, value, should_throw); |
| } |
| return Object::SetPropertyWithAccessor(&own_lookup, value, |
| should_throw); |
| } |
| V8_FALLTHROUGH; |
| case LookupIterator::INTEGER_INDEXED_EXOTIC: |
| return RedefineIncompatibleProperty(isolate, it->GetName(), value, |
| should_throw); |
| |
| case LookupIterator::DATA: { |
| if (own_lookup.IsReadOnly()) { |
| return WriteToReadOnlyProperty(&own_lookup, value, should_throw); |
| } |
| return SetDataProperty(&own_lookup, value); |
| } |
| |
| case LookupIterator::INTERCEPTOR: |
| case LookupIterator::JSPROXY: { |
| PropertyDescriptor desc; |
| Maybe<bool> owned = |
| JSReceiver::GetOwnPropertyDescriptor(&own_lookup, &desc); |
| MAYBE_RETURN(owned, Nothing<bool>()); |
| if (!owned.FromJust()) { |
| return JSReceiver::CreateDataProperty(&own_lookup, value, |
| should_throw); |
| } |
| if (PropertyDescriptor::IsAccessorDescriptor(&desc) || |
| !desc.writable()) { |
| return RedefineIncompatibleProperty(isolate, it->GetName(), value, |
| should_throw); |
| } |
| |
| PropertyDescriptor value_desc; |
| value_desc.set_value(value); |
| return JSReceiver::DefineOwnProperty(isolate, receiver, it->GetName(), |
| &value_desc, should_throw); |
| } |
| |
| case LookupIterator::NOT_FOUND: |
| case LookupIterator::TRANSITION: |
| UNREACHABLE(); |
| } |
| } |
| |
| return AddDataProperty(&own_lookup, value, NONE, should_throw, store_origin); |
| } |
| |
| Maybe<bool> Object::CannotCreateProperty(Isolate* isolate, |
| Handle<Object> receiver, |
| Handle<Object> name, |
| Handle<Object> value, |
| Maybe<ShouldThrow> should_throw) { |
| RETURN_FAILURE( |
| isolate, GetShouldThrow(isolate, should_throw), |
| NewTypeError(MessageTemplate::kStrictCannotCreateProperty, name, |
| Object::TypeOf(isolate, receiver), receiver)); |
| } |
| |
| Maybe<bool> Object::WriteToReadOnlyProperty( |
| LookupIterator* it, Handle<Object> value, |
| Maybe<ShouldThrow> maybe_should_throw) { |
| ShouldThrow should_throw = GetShouldThrow(it->isolate(), maybe_should_throw); |
| if (it->IsFound() && !it->HolderIsReceiver()) { |
| // "Override mistake" attempted, record a use count to track this per |
| // v8:8175 |
| v8::Isolate::UseCounterFeature feature = |
| should_throw == kThrowOnError |
| ? v8::Isolate::kAttemptOverrideReadOnlyOnPrototypeStrict |
| : v8::Isolate::kAttemptOverrideReadOnlyOnPrototypeSloppy; |
| it->isolate()->CountUsage(feature); |
| } |
| return WriteToReadOnlyProperty(it->isolate(), it->GetReceiver(), |
| it->GetName(), value, should_throw); |
| } |
| |
| Maybe<bool> Object::WriteToReadOnlyProperty(Isolate* isolate, |
| Handle<Object> receiver, |
| Handle<Object> name, |
| Handle<Object> value, |
| ShouldThrow should_throw) { |
| RETURN_FAILURE(isolate, should_throw, |
| NewTypeError(MessageTemplate::kStrictReadOnlyProperty, name, |
| Object::TypeOf(isolate, receiver), receiver)); |
| } |
| |
| Maybe<bool> Object::RedefineIncompatibleProperty( |
| Isolate* isolate, Handle<Object> name, Handle<Object> value, |
| Maybe<ShouldThrow> should_throw) { |
| RETURN_FAILURE(isolate, GetShouldThrow(isolate, should_throw), |
| NewTypeError(MessageTemplate::kRedefineDisallowed, name)); |
| } |
| |
| Maybe<bool> Object::SetDataProperty(LookupIterator* it, Handle<Object> value) { |
| DCHECK_IMPLIES(it->GetReceiver()->IsJSProxy(), |
| it->GetName()->IsPrivateName()); |
| DCHECK_IMPLIES(!it->IsElement() && it->GetName()->IsPrivateName(), |
| it->state() == LookupIterator::DATA); |
| Handle<JSReceiver> receiver = Handle<JSReceiver>::cast(it->GetReceiver()); |
| |
| // Store on the holder which may be hidden behind the receiver. |
| DCHECK(it->HolderIsReceiverOrHiddenPrototype()); |
| |
| Handle<Object> to_assign = value; |
| // Convert the incoming value to a number for storing into typed arrays. |
| if (it->IsElement() && receiver->IsJSObject() && |
| JSObject::cast(*receiver)->HasFixedTypedArrayElements()) { |
| ElementsKind elements_kind = JSObject::cast(*receiver)->GetElementsKind(); |
| if (elements_kind == BIGINT64_ELEMENTS || |
| elements_kind == BIGUINT64_ELEMENTS) { |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE(it->isolate(), to_assign, |
| BigInt::FromObject(it->isolate(), value), |
| Nothing<bool>()); |
| // We have to recheck the length. However, it can only change if the |
| // underlying buffer was detached, so just check that. |
| if (Handle<JSArrayBufferView>::cast(receiver)->WasDetached()) { |
| return Just(true); |
| // TODO(neis): According to the spec, this should throw a TypeError. |
| } |
| } else if (!value->IsNumber() && !value->IsUndefined(it->isolate())) { |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE(it->isolate(), to_assign, |
| Object::ToNumber(it->isolate(), value), |
| Nothing<bool>()); |
| // We have to recheck the length. However, it can only change if the |
| // underlying buffer was detached, so just check that. |
| if (Handle<JSArrayBufferView>::cast(receiver)->WasDetached()) { |
| return Just(true); |
| // TODO(neis): According to the spec, this should throw a TypeError. |
| } |
| } |
| } |
| |
| // Possibly migrate to the most up-to-date map that will be able to store |
| // |value| under it->name(). |
| it->PrepareForDataProperty(to_assign); |
| |
| // Write the property value. |
| it->WriteDataValue(to_assign, false); |
| |
| #if VERIFY_HEAP |
| if (FLAG_verify_heap) { |
| receiver->HeapObjectVerify(it->isolate()); |
| } |
| #endif |
| return Just(true); |
| } |
| |
| Maybe<bool> Object::AddDataProperty(LookupIterator* it, Handle<Object> value, |
| PropertyAttributes attributes, |
| Maybe<ShouldThrow> should_throw, |
| StoreOrigin store_origin) { |
| if (!it->GetReceiver()->IsJSReceiver()) { |
| return CannotCreateProperty(it->isolate(), it->GetReceiver(), it->GetName(), |
| value, should_throw); |
| } |
| |
| // Private symbols should be installed on JSProxy using |
| // JSProxy::SetPrivateSymbol. |
| if (it->GetReceiver()->IsJSProxy() && it->GetName()->IsPrivate() && |
| !it->GetName()->IsPrivateName()) { |
| RETURN_FAILURE(it->isolate(), GetShouldThrow(it->isolate(), should_throw), |
| NewTypeError(MessageTemplate::kProxyPrivate)); |
| } |
| |
| DCHECK_NE(LookupIterator::INTEGER_INDEXED_EXOTIC, it->state()); |
| |
| Handle<JSReceiver> receiver = it->GetStoreTarget<JSReceiver>(); |
| DCHECK_IMPLIES(receiver->IsJSProxy(), it->GetName()->IsPrivateName()); |
| DCHECK_IMPLIES(receiver->IsJSProxy(), |
| it->state() == LookupIterator::NOT_FOUND); |
| |
| // If the receiver is a JSGlobalProxy, store on the prototype (JSGlobalObject) |
| // instead. If the prototype is Null, the proxy is detached. |
| if (receiver->IsJSGlobalProxy()) return Just(true); |
| |
| Isolate* isolate = it->isolate(); |
| |
| if (it->ExtendingNonExtensible(receiver)) { |
| RETURN_FAILURE( |
| isolate, GetShouldThrow(it->isolate(), should_throw), |
| NewTypeError(MessageTemplate::kObjectNotExtensible, it->GetName())); |
| } |
| |
| if (it->IsElement()) { |
| if (receiver->IsJSArray()) { |
| Handle<JSArray> array = Handle<JSArray>::cast(receiver); |
| if (JSArray::WouldChangeReadOnlyLength(array, it->index())) { |
| RETURN_FAILURE(isolate, GetShouldThrow(it->isolate(), should_throw), |
| NewTypeError(MessageTemplate::kStrictReadOnlyProperty, |
| isolate->factory()->length_string(), |
| Object::TypeOf(isolate, array), array)); |
| } |
| |
| if (FLAG_trace_external_array_abuse && |
| array->HasFixedTypedArrayElements()) { |
| CheckArrayAbuse(array, "typed elements write", it->index(), true); |
| } |
| |
| if (FLAG_trace_js_array_abuse && !array->HasFixedTypedArrayElements()) { |
| CheckArrayAbuse(array, "elements write", it->index(), false); |
| } |
| } |
| |
| Handle<JSObject> receiver_obj = Handle<JSObject>::cast(receiver); |
| JSObject::AddDataElement(receiver_obj, it->index(), value, attributes); |
| JSObject::ValidateElements(*receiver_obj); |
| return Just(true); |
| } else { |
| it->UpdateProtector(); |
| // Migrate to the most up-to-date map that will be able to store |value| |
| // under it->name() with |attributes|. |
| it->PrepareTransitionToDataProperty(receiver, value, attributes, |
| store_origin); |
| DCHECK_EQ(LookupIterator::TRANSITION, it->state()); |
| it->ApplyTransitionToDataProperty(receiver); |
| |
| // Write the property value. |
| it->WriteDataValue(value, true); |
| |
| #if VERIFY_HEAP |
| if (FLAG_verify_heap) { |
| receiver->HeapObjectVerify(isolate); |
| } |
| #endif |
| } |
| |
| return Just(true); |
| } |
| |
| |
| template <class T> |
| static int AppendUniqueCallbacks(Isolate* isolate, |
| Handle<TemplateList> callbacks, |
| Handle<typename T::Array> array, |
| int valid_descriptors) { |
| int nof_callbacks = callbacks->length(); |
| |
| // Fill in new callback descriptors. Process the callbacks from |
| // back to front so that the last callback with a given name takes |
| // precedence over previously added callbacks with that name. |
| for (int i = nof_callbacks - 1; i >= 0; i--) { |
| Handle<AccessorInfo> entry(AccessorInfo::cast(callbacks->get(i)), isolate); |
| Handle<Name> key(Name::cast(entry->name()), isolate); |
| DCHECK(key->IsUniqueName()); |
| // Check if a descriptor with this name already exists before writing. |
| if (!T::Contains(key, entry, valid_descriptors, array)) { |
| T::Insert(key, entry, valid_descriptors, array); |
| valid_descriptors++; |
| } |
| } |
| |
| return valid_descriptors; |
| } |
| |
| struct FixedArrayAppender { |
| typedef FixedArray Array; |
| static bool Contains(Handle<Name> key, |
| Handle<AccessorInfo> entry, |
| int valid_descriptors, |
| Handle<FixedArray> array) { |
| for (int i = 0; i < valid_descriptors; i++) { |
| if (*key == AccessorInfo::cast(array->get(i))->name()) return true; |
| } |
| return false; |
| } |
| static void Insert(Handle<Name> key, |
| Handle<AccessorInfo> entry, |
| int valid_descriptors, |
| Handle<FixedArray> array) { |
| DisallowHeapAllocation no_gc; |
| array->set(valid_descriptors, *entry); |
| } |
| }; |
| |
| int AccessorInfo::AppendUnique(Isolate* isolate, Handle<Object> descriptors, |
| Handle<FixedArray> array, |
| int valid_descriptors) { |
| Handle<TemplateList> callbacks = Handle<TemplateList>::cast(descriptors); |
| DCHECK_GE(array->length(), callbacks->length() + valid_descriptors); |
| return AppendUniqueCallbacks<FixedArrayAppender>(isolate, callbacks, array, |
| valid_descriptors); |
| } |
| |
| |
| |
| |
| |
| void JSProxy::Revoke(Handle<JSProxy> proxy) { |
| Isolate* isolate = proxy->GetIsolate(); |
| // ES#sec-proxy-revocation-functions |
| if (!proxy->IsRevoked()) { |
| // 5. Set p.[[ProxyTarget]] to null. |
| proxy->set_target(ReadOnlyRoots(isolate).null_value()); |
| // 6. Set p.[[ProxyHandler]] to null. |
| proxy->set_handler(ReadOnlyRoots(isolate).null_value()); |
| } |
| DCHECK(proxy->IsRevoked()); |
| } |
| |
| // static |
| Maybe<bool> JSProxy::IsArray(Handle<JSProxy> proxy) { |
| Isolate* isolate = proxy->GetIsolate(); |
| Handle<JSReceiver> object = Handle<JSReceiver>::cast(proxy); |
| for (int i = 0; i < JSProxy::kMaxIterationLimit; i++) { |
| Handle<JSProxy> proxy = Handle<JSProxy>::cast(object); |
| if (proxy->IsRevoked()) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyRevoked, |
| isolate->factory()->NewStringFromAsciiChecked("IsArray"))); |
| return Nothing<bool>(); |
| } |
| object = handle(JSReceiver::cast(proxy->target()), isolate); |
| if (object->IsJSArray()) return Just(true); |
| if (!object->IsJSProxy()) return Just(false); |
| } |
| |
| // Too deep recursion, throw a RangeError. |
| isolate->StackOverflow(); |
| return Nothing<bool>(); |
| } |
| |
| Maybe<bool> JSProxy::HasProperty(Isolate* isolate, Handle<JSProxy> proxy, |
| Handle<Name> name) { |
| DCHECK(!name->IsPrivate()); |
| STACK_CHECK(isolate, Nothing<bool>()); |
| // 1. (Assert) |
| // 2. Let handler be the value of the [[ProxyHandler]] internal slot of O. |
| Handle<Object> handler(proxy->handler(), isolate); |
| // 3. If handler is null, throw a TypeError exception. |
| // 4. Assert: Type(handler) is Object. |
| if (proxy->IsRevoked()) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyRevoked, isolate->factory()->has_string())); |
| return Nothing<bool>(); |
| } |
| // 5. Let target be the value of the [[ProxyTarget]] internal slot of O. |
| Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate); |
| // 6. Let trap be ? GetMethod(handler, "has"). |
| Handle<Object> trap; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap, Object::GetMethod(Handle<JSReceiver>::cast(handler), |
| isolate->factory()->has_string()), |
| Nothing<bool>()); |
| // 7. If trap is undefined, then |
| if (trap->IsUndefined(isolate)) { |
| // 7a. Return target.[[HasProperty]](P). |
| return JSReceiver::HasProperty(target, name); |
| } |
| // 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, «target, P»)). |
| Handle<Object> trap_result_obj; |
| Handle<Object> args[] = {target, name}; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap_result_obj, |
| Execution::Call(isolate, trap, handler, arraysize(args), args), |
| Nothing<bool>()); |
| bool boolean_trap_result = trap_result_obj->BooleanValue(isolate); |
| // 9. If booleanTrapResult is false, then: |
| if (!boolean_trap_result) { |
| MAYBE_RETURN(JSProxy::CheckHasTrap(isolate, name, target), Nothing<bool>()); |
| } |
| // 10. Return booleanTrapResult. |
| return Just(boolean_trap_result); |
| } |
| |
| Maybe<bool> JSProxy::CheckHasTrap(Isolate* isolate, Handle<Name> name, |
| Handle<JSReceiver> target) { |
| // 9a. Let targetDesc be ? target.[[GetOwnProperty]](P). |
| PropertyDescriptor target_desc; |
| Maybe<bool> target_found = |
| JSReceiver::GetOwnPropertyDescriptor(isolate, target, name, &target_desc); |
| MAYBE_RETURN(target_found, Nothing<bool>()); |
| // 9b. If targetDesc is not undefined, then: |
| if (target_found.FromJust()) { |
| // 9b i. If targetDesc.[[Configurable]] is false, throw a TypeError |
| // exception. |
| if (!target_desc.configurable()) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyHasNonConfigurable, name)); |
| return Nothing<bool>(); |
| } |
| // 9b ii. Let extensibleTarget be ? IsExtensible(target). |
| Maybe<bool> extensible_target = JSReceiver::IsExtensible(target); |
| MAYBE_RETURN(extensible_target, Nothing<bool>()); |
| // 9b iii. If extensibleTarget is false, throw a TypeError exception. |
| if (!extensible_target.FromJust()) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyHasNonExtensible, name)); |
| return Nothing<bool>(); |
| } |
| } |
| return Just(true); |
| } |
| |
| Maybe<bool> JSProxy::SetProperty(Handle<JSProxy> proxy, Handle<Name> name, |
| Handle<Object> value, Handle<Object> receiver, |
| Maybe<ShouldThrow> should_throw) { |
| DCHECK(!name->IsPrivate()); |
| Isolate* isolate = proxy->GetIsolate(); |
| STACK_CHECK(isolate, Nothing<bool>()); |
| Factory* factory = isolate->factory(); |
| Handle<String> trap_name = factory->set_string(); |
| |
| if (proxy->IsRevoked()) { |
| isolate->Throw( |
| *factory->NewTypeError(MessageTemplate::kProxyRevoked, trap_name)); |
| return Nothing<bool>(); |
| } |
| Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate); |
| Handle<JSReceiver> handler(JSReceiver::cast(proxy->handler()), isolate); |
| |
| Handle<Object> trap; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap, Object::GetMethod(handler, trap_name), Nothing<bool>()); |
| if (trap->IsUndefined(isolate)) { |
| LookupIterator it = |
| LookupIterator::PropertyOrElement(isolate, receiver, name, target); |
| |
| return Object::SetSuperProperty(&it, value, StoreOrigin::kMaybeKeyed, |
| should_throw); |
| } |
| |
| Handle<Object> trap_result; |
| Handle<Object> args[] = {target, name, value, receiver}; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap_result, |
| Execution::Call(isolate, trap, handler, arraysize(args), args), |
| Nothing<bool>()); |
| if (!trap_result->BooleanValue(isolate)) { |
| RETURN_FAILURE(isolate, GetShouldThrow(isolate, should_throw), |
| NewTypeError(MessageTemplate::kProxyTrapReturnedFalsishFor, |
| trap_name, name)); |
| } |
| |
| MaybeHandle<Object> result = |
| JSProxy::CheckGetSetTrapResult(isolate, name, target, value, kSet); |
| |
| if (result.is_null()) { |
| return Nothing<bool>(); |
| } |
| return Just(true); |
| } |
| |
| Maybe<bool> JSProxy::DeletePropertyOrElement(Handle<JSProxy> proxy, |
| Handle<Name> name, |
| LanguageMode language_mode) { |
| DCHECK(!name->IsPrivate()); |
| ShouldThrow should_throw = |
| is_sloppy(language_mode) ? kDontThrow : kThrowOnError; |
| Isolate* isolate = proxy->GetIsolate(); |
| STACK_CHECK(isolate, Nothing<bool>()); |
| Factory* factory = isolate->factory(); |
| Handle<String> trap_name = factory->deleteProperty_string(); |
| |
| if (proxy->IsRevoked()) { |
| isolate->Throw( |
| *factory->NewTypeError(MessageTemplate::kProxyRevoked, trap_name)); |
| return Nothing<bool>(); |
| } |
| Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate); |
| Handle<JSReceiver> handler(JSReceiver::cast(proxy->handler()), isolate); |
| |
| Handle<Object> trap; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap, Object::GetMethod(handler, trap_name), Nothing<bool>()); |
| if (trap->IsUndefined(isolate)) { |
| return JSReceiver::DeletePropertyOrElement(target, name, language_mode); |
| } |
| |
| Handle<Object> trap_result; |
| Handle<Object> args[] = {target, name}; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap_result, |
| Execution::Call(isolate, trap, handler, arraysize(args), args), |
| Nothing<bool>()); |
| if (!trap_result->BooleanValue(isolate)) { |
| RETURN_FAILURE(isolate, should_throw, |
| NewTypeError(MessageTemplate::kProxyTrapReturnedFalsishFor, |
| trap_name, name)); |
| } |
| |
| // Enforce the invariant. |
| PropertyDescriptor target_desc; |
| Maybe<bool> owned = |
| JSReceiver::GetOwnPropertyDescriptor(isolate, target, name, &target_desc); |
| MAYBE_RETURN(owned, Nothing<bool>()); |
| if (owned.FromJust() && !target_desc.configurable()) { |
| isolate->Throw(*factory->NewTypeError( |
| MessageTemplate::kProxyDeletePropertyNonConfigurable, name)); |
| return Nothing<bool>(); |
| } |
| return Just(true); |
| } |
| |
| |
| // static |
| MaybeHandle<JSProxy> JSProxy::New(Isolate* isolate, Handle<Object> target, |
| Handle<Object> handler) { |
| if (!target->IsJSReceiver()) { |
| THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kProxyNonObject), |
| JSProxy); |
| } |
| if (target->IsJSProxy() && JSProxy::cast(*target)->IsRevoked()) { |
| THROW_NEW_ERROR(isolate, |
| NewTypeError(MessageTemplate::kProxyHandlerOrTargetRevoked), |
| JSProxy); |
| } |
| if (!handler->IsJSReceiver()) { |
| THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kProxyNonObject), |
| JSProxy); |
| } |
| if (handler->IsJSProxy() && JSProxy::cast(*handler)->IsRevoked()) { |
| THROW_NEW_ERROR(isolate, |
| NewTypeError(MessageTemplate::kProxyHandlerOrTargetRevoked), |
| JSProxy); |
| } |
| return isolate->factory()->NewJSProxy(Handle<JSReceiver>::cast(target), |
| Handle<JSReceiver>::cast(handler)); |
| } |
| |
| |
| // static |
| MaybeHandle<NativeContext> JSProxy::GetFunctionRealm(Handle<JSProxy> proxy) { |
| DCHECK(proxy->map()->is_constructor()); |
| if (proxy->IsRevoked()) { |
| THROW_NEW_ERROR(proxy->GetIsolate(), |
| NewTypeError(MessageTemplate::kProxyRevoked), |
| NativeContext); |
| } |
| Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), |
| proxy->GetIsolate()); |
| return JSReceiver::GetFunctionRealm(target); |
| } |
| |
| Maybe<PropertyAttributes> JSProxy::GetPropertyAttributes(LookupIterator* it) { |
| PropertyDescriptor desc; |
| Maybe<bool> found = JSProxy::GetOwnPropertyDescriptor( |
| it->isolate(), it->GetHolder<JSProxy>(), it->GetName(), &desc); |
| MAYBE_RETURN(found, Nothing<PropertyAttributes>()); |
| if (!found.FromJust()) return Just(ABSENT); |
| return Just(desc.ToAttributes()); |
| } |
| |
| // TODO(jkummerow): Consider unification with FastAsArrayLength() in |
| // accessors.cc. |
| bool PropertyKeyToArrayLength(Handle<Object> value, uint32_t* length) { |
| DCHECK(value->IsNumber() || value->IsName()); |
| if (value->ToArrayLength(length)) return true; |
| if (value->IsString()) return String::cast(*value)->AsArrayIndex(length); |
| return false; |
| } |
| |
| bool PropertyKeyToArrayIndex(Handle<Object> index_obj, uint32_t* output) { |
| return PropertyKeyToArrayLength(index_obj, output) && *output != kMaxUInt32; |
| } |
| |
| // ES6 9.4.2.1 |
| // static |
| Maybe<bool> JSArray::DefineOwnProperty(Isolate* isolate, Handle<JSArray> o, |
| Handle<Object> name, |
| PropertyDescriptor* desc, |
| Maybe<ShouldThrow> should_throw) { |
| // 1. Assert: IsPropertyKey(P) is true. ("P" is |name|.) |
| // 2. If P is "length", then: |
| // TODO(jkummerow): Check if we need slow string comparison. |
| if (*name == ReadOnlyRoots(isolate).length_string()) { |
| // 2a. Return ArraySetLength(A, Desc). |
| return ArraySetLength(isolate, o, desc, should_throw); |
| } |
| // 3. Else if P is an array index, then: |
| uint32_t index = 0; |
| if (PropertyKeyToArrayIndex(name, &index)) { |
| // 3a. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length"). |
| PropertyDescriptor old_len_desc; |
| Maybe<bool> success = GetOwnPropertyDescriptor( |
| isolate, o, isolate->factory()->length_string(), &old_len_desc); |
| // 3b. (Assert) |
| DCHECK(success.FromJust()); |
| USE(success); |
| // 3c. Let oldLen be oldLenDesc.[[Value]]. |
| uint32_t old_len = 0; |
| CHECK(old_len_desc.value()->ToArrayLength(&old_len)); |
| // 3d. Let index be ToUint32(P). |
| // (Already done above.) |
| // 3e. (Assert) |
| // 3f. If index >= oldLen and oldLenDesc.[[Writable]] is false, |
| // return false. |
| if (index >= old_len && old_len_desc.has_writable() && |
| !old_len_desc.writable()) { |
| RETURN_FAILURE(isolate, GetShouldThrow(isolate, should_throw), |
| NewTypeError(MessageTemplate::kDefineDisallowed, name)); |
| } |
| // 3g. Let succeeded be OrdinaryDefineOwnProperty(A, P, Desc). |
| Maybe<bool> succeeded = |
| OrdinaryDefineOwnProperty(isolate, o, name, desc, should_throw); |
| // 3h. Assert: succeeded is not an abrupt completion. |
| // In our case, if should_throw == kThrowOnError, it can be! |
| // 3i. If succeeded is false, return false. |
| if (succeeded.IsNothing() || !succeeded.FromJust()) return succeeded; |
| // 3j. If index >= oldLen, then: |
| if (index >= old_len) { |
| // 3j i. Set oldLenDesc.[[Value]] to index + 1. |
| old_len_desc.set_value(isolate->factory()->NewNumberFromUint(index + 1)); |
| // 3j ii. Let succeeded be |
| // OrdinaryDefineOwnProperty(A, "length", oldLenDesc). |
| succeeded = OrdinaryDefineOwnProperty(isolate, o, |
| isolate->factory()->length_string(), |
| &old_len_desc, should_throw); |
| // 3j iii. Assert: succeeded is true. |
| DCHECK(succeeded.FromJust()); |
| USE(succeeded); |
| } |
| // 3k. Return true. |
| return Just(true); |
| } |
| |
| // 4. Return OrdinaryDefineOwnProperty(A, P, Desc). |
| return OrdinaryDefineOwnProperty(isolate, o, name, desc, should_throw); |
| } |
| |
| // Part of ES6 9.4.2.4 ArraySetLength. |
| // static |
| bool JSArray::AnythingToArrayLength(Isolate* isolate, |
| Handle<Object> length_object, |
| uint32_t* output) { |
| // Fast path: check numbers and strings that can be converted directly |
| // and unobservably. |
| if (length_object->ToArrayLength(output)) return true; |
| if (length_object->IsString() && |
| Handle<String>::cast(length_object)->AsArrayIndex(output)) { |
| return true; |
| } |
| // Slow path: follow steps in ES6 9.4.2.4 "ArraySetLength". |
| // 3. Let newLen be ToUint32(Desc.[[Value]]). |
| Handle<Object> uint32_v; |
| if (!Object::ToUint32(isolate, length_object).ToHandle(&uint32_v)) { |
| // 4. ReturnIfAbrupt(newLen). |
| return false; |
| } |
| // 5. Let numberLen be ToNumber(Desc.[[Value]]). |
| Handle<Object> number_v; |
| if (!Object::ToNumber(isolate, length_object).ToHandle(&number_v)) { |
| // 6. ReturnIfAbrupt(newLen). |
| return false; |
| } |
| // 7. If newLen != numberLen, throw a RangeError exception. |
| if (uint32_v->Number() != number_v->Number()) { |
| Handle<Object> exception = |
| isolate->factory()->NewRangeError(MessageTemplate::kInvalidArrayLength); |
| isolate->Throw(*exception); |
| return false; |
| } |
| CHECK(uint32_v->ToArrayLength(output)); |
| return true; |
| } |
| |
| // ES6 9.4.2.4 |
| // static |
| Maybe<bool> JSArray::ArraySetLength(Isolate* isolate, Handle<JSArray> a, |
| PropertyDescriptor* desc, |
| Maybe<ShouldThrow> should_throw) { |
| // 1. If the [[Value]] field of Desc is absent, then |
| if (!desc->has_value()) { |
| // 1a. Return OrdinaryDefineOwnProperty(A, "length", Desc). |
| return OrdinaryDefineOwnProperty( |
| isolate, a, isolate->factory()->length_string(), desc, should_throw); |
| } |
| // 2. Let newLenDesc be a copy of Desc. |
| // (Actual copying is not necessary.) |
| PropertyDescriptor* new_len_desc = desc; |
| // 3. - 7. Convert Desc.[[Value]] to newLen. |
| uint32_t new_len = 0; |
| if (!AnythingToArrayLength(isolate, desc->value(), &new_len)) { |
| DCHECK(isolate->has_pending_exception()); |
| return Nothing<bool>(); |
| } |
| // 8. Set newLenDesc.[[Value]] to newLen. |
| // (Done below, if needed.) |
| // 9. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length"). |
| PropertyDescriptor old_len_desc; |
| Maybe<bool> success = GetOwnPropertyDescriptor( |
| isolate, a, isolate->factory()->length_string(), &old_len_desc); |
| // 10. (Assert) |
| DCHECK(success.FromJust()); |
| USE(success); |
| // 11. Let oldLen be oldLenDesc.[[Value]]. |
| uint32_t old_len = 0; |
| CHECK(old_len_desc.value()->ToArrayLength(&old_len)); |
| // 12. If newLen >= oldLen, then |
| if (new_len >= old_len) { |
| // 8. Set newLenDesc.[[Value]] to newLen. |
| // 12a. Return OrdinaryDefineOwnProperty(A, "length", newLenDesc). |
| new_len_desc->set_value(isolate->factory()->NewNumberFromUint(new_len)); |
| return OrdinaryDefineOwnProperty(isolate, a, |
| isolate->factory()->length_string(), |
| new_len_desc, should_throw); |
| } |
| // 13. If oldLenDesc.[[Writable]] is false, return false. |
| if (!old_len_desc.writable()) { |
| RETURN_FAILURE(isolate, GetShouldThrow(isolate, should_throw), |
| NewTypeError(MessageTemplate::kRedefineDisallowed, |
| isolate->factory()->length_string())); |
| } |
| // 14. If newLenDesc.[[Writable]] is absent or has the value true, |
| // let newWritable be true. |
| bool new_writable = false; |
| if (!new_len_desc->has_writable() || new_len_desc->writable()) { |
| new_writable = true; |
| } else { |
| // 15. Else, |
| // 15a. Need to defer setting the [[Writable]] attribute to false in case |
| // any elements cannot be deleted. |
| // 15b. Let newWritable be false. (It's initialized as "false" anyway.) |
| // 15c. Set newLenDesc.[[Writable]] to true. |
| // (Not needed.) |
| } |
| // Most of steps 16 through 19 is implemented by JSArray::SetLength. |
| JSArray::SetLength(a, new_len); |
| // Steps 19d-ii, 20. |
| if (!new_writable) { |
| PropertyDescriptor readonly; |
| readonly.set_writable(false); |
| Maybe<bool> success = OrdinaryDefineOwnProperty( |
| isolate, a, isolate->factory()->length_string(), &readonly, |
| should_throw); |
| DCHECK(success.FromJust()); |
| USE(success); |
| } |
| uint32_t actual_new_len = 0; |
| CHECK(a->length()->ToArrayLength(&actual_new_len)); |
| // Steps 19d-v, 21. Return false if there were non-deletable elements. |
| bool result = actual_new_len == new_len; |
| if (!result) { |
| RETURN_FAILURE( |
| isolate, GetShouldThrow(isolate, should_throw), |
| NewTypeError(MessageTemplate::kStrictDeleteProperty, |
| isolate->factory()->NewNumberFromUint(actual_new_len - 1), |
| a)); |
| } |
| return Just(result); |
| } |
| |
| // ES6 9.5.6 |
| // static |
| Maybe<bool> JSProxy::DefineOwnProperty(Isolate* isolate, Handle<JSProxy> proxy, |
| Handle<Object> key, |
| PropertyDescriptor* desc, |
| Maybe<ShouldThrow> should_throw) { |
| STACK_CHECK(isolate, Nothing<bool>()); |
| if (key->IsSymbol() && Handle<Symbol>::cast(key)->IsPrivate()) { |
| DCHECK(!Handle<Symbol>::cast(key)->IsPrivateName()); |
| return JSProxy::SetPrivateSymbol(isolate, proxy, Handle<Symbol>::cast(key), |
| desc, should_throw); |
| } |
| Handle<String> trap_name = isolate->factory()->defineProperty_string(); |
| // 1. Assert: IsPropertyKey(P) is true. |
| DCHECK(key->IsName() || key->IsNumber()); |
| // 2. Let handler be the value of the [[ProxyHandler]] internal slot of O. |
| Handle<Object> handler(proxy->handler(), isolate); |
| // 3. If handler is null, throw a TypeError exception. |
| // 4. Assert: Type(handler) is Object. |
| if (proxy->IsRevoked()) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyRevoked, trap_name)); |
| return Nothing<bool>(); |
| } |
| // 5. Let target be the value of the [[ProxyTarget]] internal slot of O. |
| Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate); |
| // 6. Let trap be ? GetMethod(handler, "defineProperty"). |
| Handle<Object> trap; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap, |
| Object::GetMethod(Handle<JSReceiver>::cast(handler), trap_name), |
| Nothing<bool>()); |
| // 7. If trap is undefined, then: |
| if (trap->IsUndefined(isolate)) { |
| // 7a. Return target.[[DefineOwnProperty]](P, Desc). |
| return JSReceiver::DefineOwnProperty(isolate, target, key, desc, |
| should_throw); |
| } |
| // 8. Let descObj be FromPropertyDescriptor(Desc). |
| Handle<Object> desc_obj = desc->ToObject(isolate); |
| // 9. Let booleanTrapResult be |
| // ToBoolean(? Call(trap, handler, «target, P, descObj»)). |
| Handle<Name> property_name = |
| key->IsName() |
| ? Handle<Name>::cast(key) |
| : Handle<Name>::cast(isolate->factory()->NumberToString(key)); |
| // Do not leak private property names. |
| DCHECK(!property_name->IsPrivate()); |
| Handle<Object> trap_result_obj; |
| Handle<Object> args[] = {target, property_name, desc_obj}; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap_result_obj, |
| Execution::Call(isolate, trap, handler, arraysize(args), args), |
| Nothing<bool>()); |
| // 10. If booleanTrapResult is false, return false. |
| if (!trap_result_obj->BooleanValue(isolate)) { |
| RETURN_FAILURE(isolate, GetShouldThrow(isolate, should_throw), |
| NewTypeError(MessageTemplate::kProxyTrapReturnedFalsishFor, |
| trap_name, property_name)); |
| } |
| // 11. Let targetDesc be ? target.[[GetOwnProperty]](P). |
| PropertyDescriptor target_desc; |
| Maybe<bool> target_found = |
| JSReceiver::GetOwnPropertyDescriptor(isolate, target, key, &target_desc); |
| MAYBE_RETURN(target_found, Nothing<bool>()); |
| // 12. Let extensibleTarget be ? IsExtensible(target). |
| Maybe<bool> maybe_extensible = JSReceiver::IsExtensible(target); |
| MAYBE_RETURN(maybe_extensible, Nothing<bool>()); |
| bool extensible_target = maybe_extensible.FromJust(); |
| // 13. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] |
| // is false, then: |
| // 13a. Let settingConfigFalse be true. |
| // 14. Else let settingConfigFalse be false. |
| bool setting_config_false = desc->has_configurable() && !desc->configurable(); |
| // 15. If targetDesc is undefined, then |
| if (!target_found.FromJust()) { |
| // 15a. If extensibleTarget is false, throw a TypeError exception. |
| if (!extensible_target) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyDefinePropertyNonExtensible, property_name)); |
| return Nothing<bool>(); |
| } |
| // 15b. If settingConfigFalse is true, throw a TypeError exception. |
| if (setting_config_false) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyDefinePropertyNonConfigurable, property_name)); |
| return Nothing<bool>(); |
| } |
| } else { |
| // 16. Else targetDesc is not undefined, |
| // 16a. If IsCompatiblePropertyDescriptor(extensibleTarget, Desc, |
| // targetDesc) is false, throw a TypeError exception. |
| Maybe<bool> valid = IsCompatiblePropertyDescriptor( |
| isolate, extensible_target, desc, &target_desc, property_name, |
| Just(kDontThrow)); |
| MAYBE_RETURN(valid, Nothing<bool>()); |
| if (!valid.FromJust()) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyDefinePropertyIncompatible, property_name)); |
| return Nothing<bool>(); |
| } |
| // 16b. If settingConfigFalse is true and targetDesc.[[Configurable]] is |
| // true, throw a TypeError exception. |
| if (setting_config_false && target_desc.configurable()) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyDefinePropertyNonConfigurable, property_name)); |
| return Nothing<bool>(); |
| } |
| } |
| // 17. Return true. |
| return Just(true); |
| } |
| |
| // static |
| Maybe<bool> JSProxy::SetPrivateSymbol(Isolate* isolate, Handle<JSProxy> proxy, |
| Handle<Symbol> private_name, |
| PropertyDescriptor* desc, |
| Maybe<ShouldThrow> should_throw) { |
| DCHECK(!private_name->IsPrivateName()); |
| // Despite the generic name, this can only add private data properties. |
| if (!PropertyDescriptor::IsDataDescriptor(desc) || |
| desc->ToAttributes() != DONT_ENUM) { |
| RETURN_FAILURE(isolate, GetShouldThrow(isolate, should_throw), |
| NewTypeError(MessageTemplate::kProxyPrivate)); |
| } |
| DCHECK(proxy->map()->is_dictionary_map()); |
| Handle<Object> value = |
| desc->has_value() |
| ? desc->value() |
| : Handle<Object>::cast(isolate->factory()->undefined_value()); |
| |
| LookupIterator it(proxy, private_name, proxy); |
| |
| if (it.IsFound()) { |
| DCHECK_EQ(LookupIterator::DATA, it.state()); |
| DCHECK_EQ(DONT_ENUM, it.property_attributes()); |
| it.WriteDataValue(value, false); |
| return Just(true); |
| } |
| |
| Handle<NameDictionary> dict(proxy->property_dictionary(), isolate); |
| PropertyDetails details(kData, DONT_ENUM, PropertyCellType::kNoCell); |
| Handle<NameDictionary> result = |
| NameDictionary::Add(isolate, dict, private_name, value, details); |
| if (!dict.is_identical_to(result)) proxy->SetProperties(*result); |
| return Just(true); |
| } |
| |
| // ES6 9.5.5 |
| // static |
| Maybe<bool> JSProxy::GetOwnPropertyDescriptor(Isolate* isolate, |
| Handle<JSProxy> proxy, |
| Handle<Name> name, |
| PropertyDescriptor* desc) { |
| DCHECK(!name->IsPrivate()); |
| STACK_CHECK(isolate, Nothing<bool>()); |
| |
| Handle<String> trap_name = |
| isolate->factory()->getOwnPropertyDescriptor_string(); |
| // 1. (Assert) |
| // 2. Let handler be the value of the [[ProxyHandler]] internal slot of O. |
| Handle<Object> handler(proxy->handler(), isolate); |
| // 3. If handler is null, throw a TypeError exception. |
| // 4. Assert: Type(handler) is Object. |
| if (proxy->IsRevoked()) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyRevoked, trap_name)); |
| return Nothing<bool>(); |
| } |
| // 5. Let target be the value of the [[ProxyTarget]] internal slot of O. |
| Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate); |
| // 6. Let trap be ? GetMethod(handler, "getOwnPropertyDescriptor"). |
| Handle<Object> trap; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap, |
| Object::GetMethod(Handle<JSReceiver>::cast(handler), trap_name), |
| Nothing<bool>()); |
| // 7. If trap is undefined, then |
| if (trap->IsUndefined(isolate)) { |
| // 7a. Return target.[[GetOwnProperty]](P). |
| return JSReceiver::GetOwnPropertyDescriptor(isolate, target, name, desc); |
| } |
| // 8. Let trapResultObj be ? Call(trap, handler, «target, P»). |
| Handle<Object> trap_result_obj; |
| Handle<Object> args[] = {target, name}; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap_result_obj, |
| Execution::Call(isolate, trap, handler, arraysize(args), args), |
| Nothing<bool>()); |
| // 9. If Type(trapResultObj) is neither Object nor Undefined, throw a |
| // TypeError exception. |
| if (!trap_result_obj->IsJSReceiver() && |
| !trap_result_obj->IsUndefined(isolate)) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyGetOwnPropertyDescriptorInvalid, name)); |
| return Nothing<bool>(); |
| } |
| // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). |
| PropertyDescriptor target_desc; |
| Maybe<bool> found = |
| JSReceiver::GetOwnPropertyDescriptor(isolate, target, name, &target_desc); |
| MAYBE_RETURN(found, Nothing<bool>()); |
| // 11. If trapResultObj is undefined, then |
| if (trap_result_obj->IsUndefined(isolate)) { |
| // 11a. If targetDesc is undefined, return undefined. |
| if (!found.FromJust()) return Just(false); |
| // 11b. If targetDesc.[[Configurable]] is false, throw a TypeError |
| // exception. |
| if (!target_desc.configurable()) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyGetOwnPropertyDescriptorUndefined, name)); |
| return Nothing<bool>(); |
| } |
| // 11c. Let extensibleTarget be ? IsExtensible(target). |
| Maybe<bool> extensible_target = JSReceiver::IsExtensible(target); |
| MAYBE_RETURN(extensible_target, Nothing<bool>()); |
| // 11d. (Assert) |
| // 11e. If extensibleTarget is false, throw a TypeError exception. |
| if (!extensible_target.FromJust()) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyGetOwnPropertyDescriptorNonExtensible, name)); |
| return Nothing<bool>(); |
| } |
| // 11f. Return undefined. |
| return Just(false); |
| } |
| // 12. Let extensibleTarget be ? IsExtensible(target). |
| Maybe<bool> extensible_target = JSReceiver::IsExtensible(target); |
| MAYBE_RETURN(extensible_target, Nothing<bool>()); |
| // 13. Let resultDesc be ? ToPropertyDescriptor(trapResultObj). |
| if (!PropertyDescriptor::ToPropertyDescriptor(isolate, trap_result_obj, |
| desc)) { |
| DCHECK(isolate->has_pending_exception()); |
| return Nothing<bool>(); |
| } |
| // 14. Call CompletePropertyDescriptor(resultDesc). |
| PropertyDescriptor::CompletePropertyDescriptor(isolate, desc); |
| // 15. Let valid be IsCompatiblePropertyDescriptor (extensibleTarget, |
| // resultDesc, targetDesc). |
| Maybe<bool> valid = IsCompatiblePropertyDescriptor( |
| isolate, extensible_target.FromJust(), desc, &target_desc, name, |
| Just(kDontThrow)); |
| MAYBE_RETURN(valid, Nothing<bool>()); |
| // 16. If valid is false, throw a TypeError exception. |
| if (!valid.FromJust()) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyGetOwnPropertyDescriptorIncompatible, name)); |
| return Nothing<bool>(); |
| } |
| // 17. If resultDesc.[[Configurable]] is false, then |
| if (!desc->configurable()) { |
| // 17a. If targetDesc is undefined or targetDesc.[[Configurable]] is true: |
| if (target_desc.is_empty() || target_desc.configurable()) { |
| // 17a i. Throw a TypeError exception. |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyGetOwnPropertyDescriptorNonConfigurable, |
| name)); |
| return Nothing<bool>(); |
| } |
| } |
| // 18. Return resultDesc. |
| return Just(true); |
| } |
| |
| Maybe<bool> JSProxy::PreventExtensions(Handle<JSProxy> proxy, |
| ShouldThrow should_throw) { |
| Isolate* isolate = proxy->GetIsolate(); |
| STACK_CHECK(isolate, Nothing<bool>()); |
| Factory* factory = isolate->factory(); |
| Handle<String> trap_name = factory->preventExtensions_string(); |
| |
| if (proxy->IsRevoked()) { |
| isolate->Throw( |
| *factory->NewTypeError(MessageTemplate::kProxyRevoked, trap_name)); |
| return Nothing<bool>(); |
| } |
| Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate); |
| Handle<JSReceiver> handler(JSReceiver::cast(proxy->handler()), isolate); |
| |
| Handle<Object> trap; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap, Object::GetMethod(handler, trap_name), Nothing<bool>()); |
| if (trap->IsUndefined(isolate)) { |
| return JSReceiver::PreventExtensions(target, should_throw); |
| } |
| |
| Handle<Object> trap_result; |
| Handle<Object> args[] = {target}; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap_result, |
| Execution::Call(isolate, trap, handler, arraysize(args), args), |
| Nothing<bool>()); |
| if (!trap_result->BooleanValue(isolate)) { |
| RETURN_FAILURE( |
| isolate, should_throw, |
| NewTypeError(MessageTemplate::kProxyTrapReturnedFalsish, trap_name)); |
| } |
| |
| // Enforce the invariant. |
| Maybe<bool> target_result = JSReceiver::IsExtensible(target); |
| MAYBE_RETURN(target_result, Nothing<bool>()); |
| if (target_result.FromJust()) { |
| isolate->Throw(*factory->NewTypeError( |
| MessageTemplate::kProxyPreventExtensionsExtensible)); |
| return Nothing<bool>(); |
| } |
| return Just(true); |
| } |
| |
| Maybe<bool> JSProxy::IsExtensible(Handle<JSProxy> proxy) { |
| Isolate* isolate = proxy->GetIsolate(); |
| STACK_CHECK(isolate, Nothing<bool>()); |
| Factory* factory = isolate->factory(); |
| Handle<String> trap_name = factory->isExtensible_string(); |
| |
| if (proxy->IsRevoked()) { |
| isolate->Throw( |
| *factory->NewTypeError(MessageTemplate::kProxyRevoked, trap_name)); |
| return Nothing<bool>(); |
| } |
| Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate); |
| Handle<JSReceiver> handler(JSReceiver::cast(proxy->handler()), isolate); |
| |
| Handle<Object> trap; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap, Object::GetMethod(handler, trap_name), Nothing<bool>()); |
| if (trap->IsUndefined(isolate)) { |
| return JSReceiver::IsExtensible(target); |
| } |
| |
| Handle<Object> trap_result; |
| Handle<Object> args[] = {target}; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap_result, |
| Execution::Call(isolate, trap, handler, arraysize(args), args), |
| Nothing<bool>()); |
| |
| // Enforce the invariant. |
| Maybe<bool> target_result = JSReceiver::IsExtensible(target); |
| MAYBE_RETURN(target_result, Nothing<bool>()); |
| if (target_result.FromJust() != trap_result->BooleanValue(isolate)) { |
| isolate->Throw( |
| *factory->NewTypeError(MessageTemplate::kProxyIsExtensibleInconsistent, |
| factory->ToBoolean(target_result.FromJust()))); |
| return Nothing<bool>(); |
| } |
| return target_result; |
| } |
| |
| Handle<DescriptorArray> DescriptorArray::CopyUpTo(Isolate* isolate, |
| Handle<DescriptorArray> desc, |
| int enumeration_index, |
| int slack) { |
| return DescriptorArray::CopyUpToAddAttributes(isolate, desc, |
| enumeration_index, NONE, slack); |
| } |
| |
| Handle<DescriptorArray> DescriptorArray::CopyUpToAddAttributes( |
| Isolate* isolate, Handle<DescriptorArray> desc, int enumeration_index, |
| PropertyAttributes attributes, int slack) { |
| if (enumeration_index + slack == 0) { |
| return isolate->factory()->empty_descriptor_array(); |
| } |
| |
| int size = enumeration_index; |
| |
| Handle<DescriptorArray> descriptors = |
| DescriptorArray::Allocate(isolate, size, slack); |
| |
| if (attributes != NONE) { |
| for (int i = 0; i < size; ++i) { |
| MaybeObject value_or_field_type = desc->GetValue(i); |
| Name key = desc->GetKey(i); |
| PropertyDetails details = desc->GetDetails(i); |
| // Bulk attribute changes never affect private properties. |
| if (!key->IsPrivate()) { |
| int mask = DONT_DELETE | DONT_ENUM; |
| // READ_ONLY is an invalid attribute for JS setters/getters. |
| HeapObject heap_object; |
| if (details.kind() != kAccessor || |
| !(value_or_field_type->GetHeapObjectIfStrong(&heap_object) && |
| heap_object->IsAccessorPair())) { |
| mask |= READ_ONLY; |
| } |
| details = details.CopyAddAttributes( |
| static_cast<PropertyAttributes>(attributes & mask)); |
| } |
| descriptors->Set(i, key, value_or_field_type, details); |
| } |
| } else { |
| for (int i = 0; i < size; ++i) { |
| descriptors->CopyFrom(i, *desc); |
| } |
| } |
| |
| if (desc->number_of_descriptors() != enumeration_index) descriptors->Sort(); |
| |
| return descriptors; |
| } |
| |
| // Create a new descriptor array with only enumerable, configurable, writeable |
| // data properties, but identical field locations. |
| Handle<DescriptorArray> DescriptorArray::CopyForFastObjectClone( |
| Isolate* isolate, Handle<DescriptorArray> src, int enumeration_index, |
| int slack) { |
| if (enumeration_index + slack == 0) { |
| return isolate->factory()->empty_descriptor_array(); |
| } |
| |
| int size = enumeration_index; |
| Handle<DescriptorArray> descriptors = |
| DescriptorArray::Allocate(isolate, size, slack); |
| |
| for (int i = 0; i < size; ++i) { |
| Name key = src->GetKey(i); |
| PropertyDetails details = src->GetDetails(i); |
| |
| DCHECK(!key->IsPrivateName()); |
| DCHECK(details.IsEnumerable()); |
| DCHECK_EQ(details.kind(), kData); |
| |
| // Ensure the ObjectClone property details are NONE, and that all source |
| // details did not contain DONT_ENUM. |
| PropertyDetails new_details(kData, NONE, details.location(), |
| details.constness(), details.representation(), |
| details.field_index()); |
| // Do not propagate the field type of normal object fields from the |
| // original descriptors since FieldType changes don't create new maps. |
| MaybeObject type = src->GetValue(i); |
| if (details.location() == PropertyLocation::kField) { |
| type = MaybeObject::FromObject(FieldType::Any()); |
| } |
| descriptors->Set(i, key, type, new_details); |
| } |
| |
| descriptors->Sort(); |
| |
| return descriptors; |
| } |
| |
| bool DescriptorArray::IsEqualUpTo(DescriptorArray desc, int nof_descriptors) { |
| for (int i = 0; i < nof_descriptors; i++) { |
| if (GetKey(i) != desc->GetKey(i) || GetValue(i) != desc->GetValue(i)) { |
| return false; |
| } |
| PropertyDetails details = GetDetails(i); |
| PropertyDetails other_details = desc->GetDetails(i); |
| if (details.kind() != other_details.kind() || |
| details.location() != other_details.location() || |
| !details.representation().Equals(other_details.representation())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| Handle<FixedArray> FixedArray::SetAndGrow(Isolate* isolate, |
| Handle<FixedArray> array, int index, |
| Handle<Object> value, |
| PretenureFlag pretenure) { |
| if (index < array->length()) { |
| array->set(index, *value); |
| return array; |
| } |
| int capacity = array->length(); |
| do { |
| capacity = JSObject::NewElementsCapacity(capacity); |
| } while (capacity <= index); |
| Handle<FixedArray> new_array = |
| isolate->factory()->NewUninitializedFixedArray(capacity, pretenure); |
| array->CopyTo(0, *new_array, 0, array->length()); |
| new_array->FillWithHoles(array->length(), new_array->length()); |
| new_array->set(index, *value); |
| return new_array; |
| } |
| |
| bool FixedArray::ContainsSortedNumbers() { |
| for (int i = 1; i < length(); ++i) { |
| Object a_obj = get(i - 1); |
| Object b_obj = get(i); |
| if (!a_obj->IsNumber() || !b_obj->IsNumber()) return false; |
| |
| uint32_t a = NumberToUint32(a_obj); |
| uint32_t b = NumberToUint32(b_obj); |
| |
| if (a > b) return false; |
| } |
| return true; |
| } |
| |
| Handle<FixedArray> FixedArray::ShrinkOrEmpty(Isolate* isolate, |
| Handle<FixedArray> array, |
| int new_length) { |
| if (new_length == 0) { |
| return array->GetReadOnlyRoots().empty_fixed_array_handle(); |
| } else { |
| array->Shrink(isolate, new_length); |
| return array; |
| } |
| } |
| |
| void FixedArray::Shrink(Isolate* isolate, int new_length) { |
| DCHECK(0 < new_length && new_length <= length()); |
| if (new_length < length()) { |
| isolate->heap()->RightTrimFixedArray(*this, length() - new_length); |
| } |
| } |
| |
| void FixedArray::CopyTo(int pos, FixedArray dest, int dest_pos, int len) const { |
| DisallowHeapAllocation no_gc; |
| // Return early if len == 0 so that we don't try to read the write barrier off |
| // a canonical read-only empty fixed array. |
| if (len == 0) return; |
| WriteBarrierMode mode = dest->GetWriteBarrierMode(no_gc); |
| for (int index = 0; index < len; index++) { |
| dest->set(dest_pos + index, get(pos + index), mode); |
| } |
| } |
| |
| |
| // static |
| Handle<ArrayList> ArrayList::Add(Isolate* isolate, Handle<ArrayList> array, |
| Handle<Object> obj) { |
| int length = array->Length(); |
| array = EnsureSpace(isolate, array, length + 1); |
| // Check that GC didn't remove elements from the array. |
| DCHECK_EQ(array->Length(), length); |
| array->Set(length, *obj); |
| array->SetLength(length + 1); |
| return array; |
| } |
| |
| // static |
| Handle<ArrayList> ArrayList::Add(Isolate* isolate, Handle<ArrayList> array, |
| Handle<Object> obj1, Handle<Object> obj2) { |
| int length = array->Length(); |
| array = EnsureSpace(isolate, array, length + 2); |
| // Check that GC didn't remove elements from the array. |
| DCHECK_EQ(array->Length(), length); |
| array->Set(length, *obj1); |
| array->Set(length + 1, *obj2); |
| array->SetLength(length + 2); |
| return array; |
| } |
| |
| // static |
| Handle<ArrayList> ArrayList::New(Isolate* isolate, int size) { |
| Handle<FixedArray> fixed_array = |
| isolate->factory()->NewFixedArray(size + kFirstIndex); |
| fixed_array->set_map_no_write_barrier( |
| ReadOnlyRoots(isolate).array_list_map()); |
| Handle<ArrayList> result = Handle<ArrayList>::cast(fixed_array); |
| result->SetLength(0); |
| return result; |
| } |
| |
| Handle<FixedArray> ArrayList::Elements(Isolate* isolate, |
| Handle<ArrayList> array) { |
| int length = array->Length(); |
| Handle<FixedArray> result = isolate->factory()->NewFixedArray(length); |
| // Do not copy the first entry, i.e., the length. |
| array->CopyTo(kFirstIndex, *result, 0, length); |
| return result; |
| } |
| |
| namespace { |
| |
| Handle<FixedArray> EnsureSpaceInFixedArray(Isolate* isolate, |
| Handle<FixedArray> array, |
| int length) { |
| int capacity = array->length(); |
| if (capacity < length) { |
| int new_capacity = length; |
| new_capacity = new_capacity + Max(new_capacity / 2, 2); |
| int grow_by = new_capacity - capacity; |
| array = isolate->factory()->CopyFixedArrayAndGrow(array, grow_by); |
| } |
| return array; |
| } |
| |
| } // namespace |
| |
| // static |
| Handle<ArrayList> ArrayList::EnsureSpace(Isolate* isolate, |
| Handle<ArrayList> array, int length) { |
| const bool empty = (array->length() == 0); |
| Handle<FixedArray> ret = |
| EnsureSpaceInFixedArray(isolate, array, kFirstIndex + length); |
| if (empty) { |
| ret->set_map_no_write_barrier(array->GetReadOnlyRoots().array_list_map()); |
| |
| Handle<ArrayList>::cast(ret)->SetLength(0); |
| } |
| return Handle<ArrayList>::cast(ret); |
| } |
| |
| // static |
| Handle<WeakArrayList> WeakArrayList::AddToEnd(Isolate* isolate, |
| Handle<WeakArrayList> array, |
| const MaybeObjectHandle& value) { |
| int length = array->length(); |
| array = EnsureSpace(isolate, array, length + 1); |
| // Reload length; GC might have removed elements from the array. |
| length = array->length(); |
| array->Set(length, *value); |
| array->set_length(length + 1); |
| return array; |
| } |
| |
| bool WeakArrayList::IsFull() { return length() == capacity(); } |
| |
| // static |
| Handle<WeakArrayList> WeakArrayList::EnsureSpace(Isolate* isolate, |
| Handle<WeakArrayList> array, |
| int length, |
| PretenureFlag pretenure) { |
| int capacity = array->capacity(); |
| if (capacity < length) { |
| int new_capacity = length; |
| new_capacity = new_capacity + Max(new_capacity / 2, 2); |
| int grow_by = new_capacity - capacity; |
| array = |
| isolate->factory()->CopyWeakArrayListAndGrow(array, grow_by, pretenure); |
| } |
| return array; |
| } |
| |
| int WeakArrayList::CountLiveWeakReferences() const { |
| int live_weak_references = 0; |
| for (int i = 0; i < length(); i++) { |
| if (Get(i)->IsWeak()) { |
| ++live_weak_references; |
| } |
| } |
| return live_weak_references; |
| } |
| |
| bool WeakArrayList::RemoveOne(const MaybeObjectHandle& value) { |
| if (length() == 0) return false; |
| // Optimize for the most recently added element to be removed again. |
| MaybeObject cleared_weak_ref = |
| HeapObjectReference::ClearedValue(GetIsolate()); |
| int last_index = length() - 1; |
| for (int i = last_index; i >= 0; --i) { |
| if (Get(i) == *value) { |
| // Move the last element into the this slot (or no-op, if this is the |
| // last slot). |
| Set(i, Get(last_index)); |
| Set(last_index, cleared_weak_ref); |
| set_length(last_index); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // static |
| Handle<WeakArrayList> PrototypeUsers::Add(Isolate* isolate, |
| Handle<WeakArrayList> array, |
| Handle<Map> value, |
| int* assigned_index) { |
| int length = array->length(); |
| if (length == 0) { |
| // Uninitialized WeakArrayList; need to initialize empty_slot_index. |
| array = WeakArrayList::EnsureSpace(isolate, array, kFirstIndex + 1); |
| set_empty_slot_index(*array, kNoEmptySlotsMarker); |
| array->Set(kFirstIndex, HeapObjectReference::Weak(*value)); |
| array->set_length(kFirstIndex + 1); |
| if (assigned_index != nullptr) *assigned_index = kFirstIndex; |
| return array; |
| } |
| |
| // If the array has unfilled space at the end, use it. |
| if (!array->IsFull()) { |
| array->Set(length, HeapObjectReference::Weak(*value)); |
| array->set_length(length + 1); |
| if (assigned_index != nullptr) *assigned_index = length; |
| return array; |
| } |
| |
| // If there are empty slots, use one of them. |
| int empty_slot = Smi::ToInt(empty_slot_index(*array)); |
| if (empty_slot != kNoEmptySlotsMarker) { |
| DCHECK_GE(empty_slot, kFirstIndex); |
| CHECK_LT(empty_slot, array->length()); |
| int next_empty_slot = array->Get(empty_slot).ToSmi().value(); |
| |
| array->Set(empty_slot, HeapObjectReference::Weak(*value)); |
| if (assigned_index != nullptr) *assigned_index = empty_slot; |
| |
| set_empty_slot_index(*array, next_empty_slot); |
| return array; |
| } else { |
| DCHECK_EQ(empty_slot, kNoEmptySlotsMarker); |
| } |
| |
| // Array full and no empty slots. Grow the array. |
| array = WeakArrayList::EnsureSpace(isolate, array, length + 1); |
| array->Set(length, HeapObjectReference::Weak(*value)); |
| array->set_length(length + 1); |
| if (assigned_index != nullptr) *assigned_index = length; |
| return array; |
| } |
| |
| WeakArrayList PrototypeUsers::Compact(Handle<WeakArrayList> array, Heap* heap, |
| CompactionCallback callback, |
| PretenureFlag pretenure) { |
| if (array->length() == 0) { |
| return *array; |
| } |
| int new_length = kFirstIndex + array->CountLiveWeakReferences(); |
| if (new_length == array->length()) { |
| return *array; |
| } |
| |
| Handle<WeakArrayList> new_array = WeakArrayList::EnsureSpace( |
| heap->isolate(), |
| handle(ReadOnlyRoots(heap).empty_weak_array_list(), heap->isolate()), |
| new_length, pretenure); |
| // Allocation might have caused GC and turned some of the elements into |
| // cleared weak heap objects. Count the number of live objects again. |
| int copy_to = kFirstIndex; |
| for (int i = kFirstIndex; i < array->length(); i++) { |
| MaybeObject element = array->Get(i); |
| HeapObject value; |
| if (element->GetHeapObjectIfWeak(&value)) { |
| callback(value, i, copy_to); |
| new_array->Set(copy_to++, element); |
| } else { |
| DCHECK(element->IsCleared() || element->IsSmi()); |
| } |
| } |
| new_array->set_length(copy_to); |
| set_empty_slot_index(*new_array, kNoEmptySlotsMarker); |
| return *new_array; |
| } |
| |
| Handle<RegExpMatchInfo> RegExpMatchInfo::ReserveCaptures( |
| Isolate* isolate, Handle<RegExpMatchInfo> match_info, int capture_count) { |
| DCHECK_GE(match_info->length(), kLastMatchOverhead); |
| const int required_length = kFirstCaptureIndex + capture_count; |
| return Handle<RegExpMatchInfo>::cast( |
| EnsureSpaceInFixedArray(isolate, match_info, required_length)); |
| } |
| |
| // static |
| Handle<FrameArray> FrameArray::AppendJSFrame(Handle<FrameArray> in, |
| Handle<Object> receiver, |
| Handle<JSFunction> function, |
| Handle<AbstractCode> code, |
| int offset, int flags, |
| Handle<FixedArray> parameters) { |
| const int frame_count = in->FrameCount(); |
| const int new_length = LengthFor(frame_count + 1); |
| Handle<FrameArray> array = |
| EnsureSpace(function->GetIsolate(), in, new_length); |
| array->SetReceiver(frame_count, *receiver); |
| array->SetFunction(frame_count, *function); |
| array->SetCode(frame_count, *code); |
| array->SetOffset(frame_count, Smi::FromInt(offset)); |
| array->SetFlags(frame_count, Smi::FromInt(flags)); |
| array->SetParameters(frame_count, *parameters); |
| array->set(kFrameCountIndex, Smi::FromInt(frame_count + 1)); |
| return array; |
| } |
| |
| // static |
| Handle<FrameArray> FrameArray::AppendWasmFrame( |
| Handle<FrameArray> in, Handle<WasmInstanceObject> wasm_instance, |
| int wasm_function_index, wasm::WasmCode* code, int offset, int flags) { |
| Isolate* isolate = wasm_instance->GetIsolate(); |
| const int frame_count = in->FrameCount(); |
| const int new_length = LengthFor(frame_count + 1); |
| Handle<FrameArray> array = EnsureSpace(isolate, in, new_length); |
| // The {code} will be {nullptr} for interpreted wasm frames. |
| Handle<Foreign> code_foreign = |
| isolate->factory()->NewForeign(reinterpret_cast<Address>(code)); |
| array->SetWasmInstance(frame_count, *wasm_instance); |
| array->SetWasmFunctionIndex(frame_count, Smi::FromInt(wasm_function_index)); |
| array->SetWasmCodeObject(frame_count, *code_foreign); |
| array->SetOffset(frame_count, Smi::FromInt(offset)); |
| array->SetFlags(frame_count, Smi::FromInt(flags)); |
| array->set(kFrameCountIndex, Smi::FromInt(frame_count + 1)); |
| return array; |
| } |
| |
| void FrameArray::ShrinkToFit(Isolate* isolate) { |
| Shrink(isolate, LengthFor(FrameCount())); |
| } |
| |
| // static |
| Handle<FrameArray> FrameArray::EnsureSpace(Isolate* isolate, |
| Handle<FrameArray> array, |
| int length) { |
| return Handle<FrameArray>::cast( |
| EnsureSpaceInFixedArray(isolate, array, length)); |
| } |
| |
| Handle<DescriptorArray> DescriptorArray::Allocate(Isolate* isolate, |
| int nof_descriptors, |
| int slack, |
| PretenureFlag pretenure) { |
| return nof_descriptors + slack == 0 |
| ? isolate->factory()->empty_descriptor_array() |
| : isolate->factory()->NewDescriptorArray(nof_descriptors, slack, |
| pretenure); |
| } |
| |
| void DescriptorArray::Initialize(EnumCache enum_cache, |
| HeapObject undefined_value, |
| int nof_descriptors, int slack) { |
| DCHECK_GE(nof_descriptors, 0); |
| DCHECK_GE(slack, 0); |
| DCHECK_LE(nof_descriptors + slack, kMaxNumberOfDescriptors); |
| set_number_of_all_descriptors(nof_descriptors + slack); |
| set_number_of_descriptors(nof_descriptors); |
| set_raw_number_of_marked_descriptors(0); |
| set_filler16bits(0); |
| set_enum_cache(enum_cache); |
| MemsetTagged(GetDescriptorSlot(0), undefined_value, |
| number_of_all_descriptors() * kEntrySize); |
| } |
| |
| void DescriptorArray::ClearEnumCache() { |
| set_enum_cache(GetReadOnlyRoots().empty_enum_cache()); |
| } |
| |
| void DescriptorArray::Replace(int index, Descriptor* descriptor) { |
| descriptor->SetSortedKeyIndex(GetSortedKeyIndex(index)); |
| Set(index, descriptor); |
| } |
| |
| // static |
| void DescriptorArray::InitializeOrChangeEnumCache( |
| Handle<DescriptorArray> descriptors, Isolate* isolate, |
| Handle<FixedArray> keys, Handle<FixedArray> indices) { |
| EnumCache enum_cache = descriptors->enum_cache(); |
| if (enum_cache == ReadOnlyRoots(isolate).empty_enum_cache()) { |
| enum_cache = *isolate->factory()->NewEnumCache(keys, indices); |
| descriptors->set_enum_cache(enum_cache); |
| } else { |
| enum_cache->set_keys(*keys); |
| enum_cache->set_indices(*indices); |
| } |
| } |
| |
| void DescriptorArray::CopyFrom(int index, DescriptorArray src) { |
| PropertyDetails details = src->GetDetails(index); |
| Set(index, src->GetKey(index), src->GetValue(index), details); |
| } |
| |
| void DescriptorArray::Sort() { |
| // In-place heap sort. |
| int len = number_of_descriptors(); |
| // Reset sorting since the descriptor array might contain invalid pointers. |
| for (int i = 0; i < len; ++i) SetSortedKey(i, i); |
| // Bottom-up max-heap construction. |
| // Index of the last node with children |
| const int max_parent_index = (len / 2) - 1; |
| for (int i = max_parent_index; i >= 0; --i) { |
| int parent_index = i; |
| const uint32_t parent_hash = GetSortedKey(i)->Hash(); |
| while (parent_index <= max_parent_index) { |
| int child_index = 2 * parent_index + 1; |
| uint32_t child_hash = GetSortedKey(child_index)->Hash(); |
| if (child_index + 1 < len) { |
| uint32_t right_child_hash = GetSortedKey(child_index + 1)->Hash(); |
| if (right_child_hash > child_hash) { |
| child_index++; |
| child_hash = right_child_hash; |
| } |
| } |
| if (child_hash <= parent_hash) break; |
| SwapSortedKeys(parent_index, child_index); |
| // Now element at child_index could be < its children. |
| parent_index = child_index; // parent_hash remains correct. |
| } |
| } |
| |
| // Extract elements and create sorted array. |
| for (int i = len - 1; i > 0; --i) { |
| // Put max element at the back of the array. |
| SwapSortedKeys(0, i); |
| // Shift down the new top element. |
| int parent_index = 0; |
| const uint32_t parent_hash = GetSortedKey(parent_index)->Hash(); |
| const int max_parent_index = (i / 2) - 1; |
| while (parent_index <= max_parent_index) { |
| int child_index = parent_index * 2 + 1; |
| uint32_t child_hash = GetSortedKey(child_index)->Hash(); |
| if (child_index + 1 < i) { |
| uint32_t right_child_hash = GetSortedKey(child_index + 1)->Hash(); |
| if (right_child_hash > child_hash) { |
| child_index++; |
| child_hash = right_child_hash; |
| } |
| } |
| if (child_hash <= parent_hash) break; |
| SwapSortedKeys(parent_index, child_index); |
| parent_index = child_index; |
| } |
| } |
| DCHECK(IsSortedNoDuplicates()); |
| } |
| |
| int16_t DescriptorArray::UpdateNumberOfMarkedDescriptors( |
| unsigned mark_compact_epoch, int16_t new_marked) { |
| STATIC_ASSERT(kMaxNumberOfDescriptors <= |
| NumberOfMarkedDescriptors::kMaxNumberOfMarkedDescriptors); |
| int16_t old_raw_marked = raw_number_of_marked_descriptors(); |
| int16_t old_marked = |
| NumberOfMarkedDescriptors::decode(mark_compact_epoch, old_raw_marked); |
| int16_t new_raw_marked = |
| NumberOfMarkedDescriptors::encode(mark_compact_epoch, new_marked); |
| while (old_marked < new_marked) { |
| int16_t actual_raw_marked = CompareAndSwapRawNumberOfMarkedDescriptors( |
| old_raw_marked, new_raw_marked); |
| if (actual_raw_marked == old_raw_marked) { |
| break; |
| } |
| old_raw_marked = actual_raw_marked; |
| old_marked = |
| NumberOfMarkedDescriptors::decode(mark_compact_epoch, old_raw_marked); |
| } |
| return old_marked; |
| } |
| |
| Handle<AccessorPair> AccessorPair::Copy(Isolate* isolate, |
| Handle<AccessorPair> pair) { |
| Handle<AccessorPair> copy = isolate->factory()->NewAccessorPair(); |
| copy->set_getter(pair->getter()); |
| copy->set_setter(pair->setter()); |
| return copy; |
| } |
| |
| Handle<Object> AccessorPair::GetComponent(Isolate* isolate, |
| Handle<AccessorPair> accessor_pair, |
| AccessorComponent component) { |
| Object accessor = accessor_pair->get(component); |
| if (accessor->IsFunctionTemplateInfo()) { |
| return ApiNatives::InstantiateFunction( |
| handle(FunctionTemplateInfo::cast(accessor), isolate)) |
| .ToHandleChecked(); |
| } |
| if (accessor->IsNull(isolate)) { |
| return isolate->factory()->undefined_value(); |
| } |
| return handle(accessor, isolate); |
| } |
| |
| #ifdef DEBUG |
| bool DescriptorArray::IsEqualTo(DescriptorArray other) { |
| if (number_of_all_descriptors() != other->number_of_all_descriptors()) { |
| return false; |
| } |
| for (int i = 0; i < number_of_all_descriptors(); ++i) { |
| if (get(i) != other->get(i)) return false; |
| } |
| return true; |
| } |
| #endif |
| |
| // static |
| MaybeHandle<String> Name::ToFunctionName(Isolate* isolate, Handle<Name> name) { |
| if (name->IsString()) return Handle<String>::cast(name); |
| // ES6 section 9.2.11 SetFunctionName, step 4. |
| Handle<Object> description(Handle<Symbol>::cast(name)->name(), isolate); |
| if (description->IsUndefined(isolate)) { |
| return isolate->factory()->empty_string(); |
| } |
| IncrementalStringBuilder builder(isolate); |
| builder.AppendCharacter('['); |
| builder.AppendString(Handle<String>::cast(description)); |
| builder.AppendCharacter(']'); |
| return builder.Finish(); |
| } |
| |
| // static |
| MaybeHandle<String> Name::ToFunctionName(Isolate* isolate, Handle<Name> name, |
| Handle<String> prefix) { |
| Handle<String> name_string; |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, name_string, |
| ToFunctionName(isolate, name), String); |
| IncrementalStringBuilder builder(isolate); |
| builder.AppendString(prefix); |
| builder.AppendCharacter(' '); |
| builder.AppendString(name_string); |
| return builder.Finish(); |
| } |
| |
| |
| void Relocatable::PostGarbageCollectionProcessing(Isolate* isolate) { |
| Relocatable* current = isolate->relocatable_top(); |
| while (current != nullptr) { |
| current->PostGarbageCollection(); |
| current = current->prev_; |
| } |
| } |
| |
| |
| // Reserve space for statics needing saving and restoring. |
| int Relocatable::ArchiveSpacePerThread() { |
| return sizeof(Relocatable*); // NOLINT |
| } |
| |
| |
| // Archive statics that are thread-local. |
| char* Relocatable::ArchiveState(Isolate* isolate, char* to) { |
| *reinterpret_cast<Relocatable**>(to) = isolate->relocatable_top(); |
| isolate->set_relocatable_top(nullptr); |
| return to + ArchiveSpacePerThread(); |
| } |
| |
| |
| // Restore statics that are thread-local. |
| char* Relocatable::RestoreState(Isolate* isolate, char* from) { |
| isolate->set_relocatable_top(*reinterpret_cast<Relocatable**>(from)); |
| return from + ArchiveSpacePerThread(); |
| } |
| |
| char* Relocatable::Iterate(RootVisitor* v, char* thread_storage) { |
| Relocatable* top = *reinterpret_cast<Relocatable**>(thread_storage); |
| Iterate(v, top); |
| return thread_storage + ArchiveSpacePerThread(); |
| } |
| |
| void Relocatable::Iterate(Isolate* isolate, RootVisitor* v) { |
| Iterate(v, isolate->relocatable_top()); |
| } |
| |
| void Relocatable::Iterate(RootVisitor* v, Relocatable* top) { |
| Relocatable* current = top; |
| while (current != nullptr) { |
| current->IterateInstance(v); |
| current = current->prev_; |
| } |
| } |
| |
| |
| |
| |
| |
| namespace { |
| |
| template <typename sinkchar> |
| void WriteFixedArrayToFlat(FixedArray fixed_array, int length, String separator, |
| sinkchar* sink, int sink_length) { |
| DisallowHeapAllocation no_allocation; |
| CHECK_GT(length, 0); |
| CHECK_LE(length, fixed_array->length()); |
| #ifdef DEBUG |
| sinkchar* sink_end = sink + sink_length; |
| #endif |
| |
| const int separator_length = separator->length(); |
| const bool use_one_byte_separator_fast_path = |
| separator_length == 1 && sizeof(sinkchar) == 1 && |
| StringShape(separator).IsSequentialOneByte(); |
| uint8_t separator_one_char; |
| if (use_one_byte_separator_fast_path) { |
| CHECK(StringShape(separator).IsSequentialOneByte()); |
| CHECK_EQ(separator->length(), 1); |
| separator_one_char = |
| SeqOneByteString::cast(separator)->GetChars(no_allocation)[0]; |
| } |
| |
| uint32_t num_separators = 0; |
| for (int i = 0; i < length; i++) { |
| Object element = fixed_array->get(i); |
| const bool element_is_separator_sequence = element->IsSmi(); |
| |
| // If element is a Smi, it represents the number of separators to write. |
| if (V8_UNLIKELY(element_is_separator_sequence)) { |
| CHECK(element->ToUint32(&num_separators)); |
| // Verify that Smis (number of separators) only occur when necessary: |
| // 1) at the beginning |
| // 2) at the end |
| // 3) when the number of separators > 1 |
| // - It is assumed that consecutive Strings will have one separator, |
| // so there is no need for a Smi. |
| DCHECK(i == 0 || i == length - 1 || num_separators > 1); |
| } |
| |
| // Write separator(s) if necessary. |
| if (num_separators > 0 && separator_length > 0) { |
| // TODO(pwong): Consider doubling strategy employed by runtime-strings.cc |
| // WriteRepeatToFlat(). |
| // Fast path for single character, single byte separators. |
| if (use_one_byte_separator_fast_path) { |
| DCHECK_LE(sink + num_separators, sink_end); |
| memset(sink, separator_one_char, num_separators); |
| DCHECK_EQ(separator_length, 1); |
| sink += num_separators; |
| } else { |
| for (uint32_t j = 0; j < num_separators; j++) { |
| DCHECK_LE(sink + separator_length, sink_end); |
| String::WriteToFlat(separator, sink, 0, separator_length); |
| sink += separator_length; |
| } |
| } |
| } |
| |
| if (V8_UNLIKELY(element_is_separator_sequence)) { |
| num_separators = 0; |
| } else { |
| DCHECK(element->IsString()); |
| String string = String::cast(element); |
| const int string_length = string->length(); |
| |
| DCHECK(string_length == 0 || sink < sink_end); |
| String::WriteToFlat(string, sink, 0, string_length); |
| sink += string_length; |
| |
| // Next string element, needs at least one separator preceding it. |
| num_separators = 1; |
| } |
| } |
| |
| // Verify we have written to the end of the sink. |
| DCHECK_EQ(sink, sink_end); |
| } |
| |
| } // namespace |
| |
| // static |
| Address JSArray::ArrayJoinConcatToSequentialString(Isolate* isolate, |
| Address raw_fixed_array, |
| intptr_t length, |
| Address raw_separator, |
| Address raw_dest) { |
| DisallowHeapAllocation no_allocation; |
| DisallowJavascriptExecution no_js(isolate); |
| FixedArray fixed_array = FixedArray::cast(Object(raw_fixed_array)); |
| String separator = String::cast(Object(raw_separator)); |
| String dest = String::cast(Object(raw_dest)); |
| DCHECK(fixed_array->IsFixedArray()); |
| DCHECK(StringShape(dest).IsSequentialOneByte() || |
| StringShape(dest).IsSequentialTwoByte()); |
| |
| if (StringShape(dest).IsSequentialOneByte()) { |
| WriteFixedArrayToFlat(fixed_array, static_cast<int>(length), separator, |
| SeqOneByteString::cast(dest)->GetChars(no_allocation), |
| dest->length()); |
| } else { |
| DCHECK(StringShape(dest).IsSequentialTwoByte()); |
| WriteFixedArrayToFlat(fixed_array, static_cast<int>(length), separator, |
| SeqTwoByteString::cast(dest)->GetChars(no_allocation), |
| dest->length()); |
| } |
| return dest->ptr(); |
| } |
| |
| |
| |
| |
| uint32_t StringHasher::MakeArrayIndexHash(uint32_t value, int length) { |
| // For array indexes mix the length into the hash as an array index could |
| // be zero. |
| DCHECK_GT(length, 0); |
| DCHECK_LE(length, String::kMaxArrayIndexSize); |
| DCHECK(TenToThe(String::kMaxCachedArrayIndexLength) < |
| (1 << String::kArrayIndexValueBits)); |
| |
| value <<= String::ArrayIndexValueBits::kShift; |
| value |= length << String::ArrayIndexLengthBits::kShift; |
| |
| DCHECK_EQ(value & String::kIsNotArrayIndexMask, 0); |
| DCHECK_EQ(length <= String::kMaxCachedArrayIndexLength, |
| Name::ContainsCachedArrayIndex(value)); |
| return value; |
| } |
| |
| |
| uint32_t StringHasher::GetHashField() { |
| if (length_ <= String::kMaxHashCalcLength) { |
| if (is_array_index_) { |
| return MakeArrayIndexHash(array_index_, length_); |
| } |
| return (GetHashCore(raw_running_hash_) << String::kHashShift) | |
| String::kIsNotArrayIndexMask; |
| } else { |
| return (length_ << String::kHashShift) | String::kIsNotArrayIndexMask; |
| } |
| } |
| |
| uint32_t StringHasher::ComputeUtf8Hash(Vector<const char> chars, uint64_t seed, |
| int* utf16_length_out) { |
| int vector_length = chars.length(); |
| // Handle some edge cases |
| if (vector_length <= 1) { |
| DCHECK(vector_length == 0 || |
| static_cast<uint8_t>(chars.start()[0]) <= |
| unibrow::Utf8::kMaxOneByteChar); |
| *utf16_length_out = vector_length; |
| return HashSequentialString(chars.start(), vector_length, seed); |
| } |
| |
| // Start with a fake length which won't affect computation. |
| // It will be updated later. |
| StringHasher hasher(String::kMaxArrayIndexSize, seed); |
| DCHECK(hasher.is_array_index_); |
| |
| unibrow::Utf8Iterator it = unibrow::Utf8Iterator(chars); |
| int utf16_length = 0; |
| bool is_index = true; |
| |
| while (utf16_length < String::kMaxHashCalcLength && !it.Done()) { |
| utf16_length++; |
| uint16_t c = *it; |
| ++it; |
| hasher.AddCharacter(c); |
| if (is_index) is_index = hasher.UpdateIndex(c); |
| } |
| |
| // Now that hashing is done, we just need to calculate utf16_length |
| while (!it.Done()) { |
| ++it; |
| utf16_length++; |
| } |
| |
| *utf16_length_out = utf16_length; |
| // Must set length here so that hash computation is correct. |
| hasher.length_ = utf16_length; |
| return hasher.GetHashField(); |
| } |
| |
| void IteratingStringHasher::VisitConsString(ConsString cons_string) { |
| // Run small ConsStrings through ConsStringIterator. |
| if (cons_string->length() < 64) { |
| ConsStringIterator iter(cons_string); |
| int offset; |
| for (String string = iter.Next(&offset); !string.is_null(); |
| string = iter.Next(&offset)) { |
| DCHECK_EQ(0, offset); |
| String::VisitFlat(this, string, 0); |
| } |
| return; |
| } |
| // Slow case. |
| const int max_length = String::kMaxHashCalcLength; |
| int length = std::min(cons_string->length(), max_length); |
| if (cons_string->HasOnlyOneByteChars()) { |
| uint8_t* buffer = new uint8_t[length]; |
| String::WriteToFlat(cons_string, buffer, 0, length); |
| AddCharacters(buffer, length); |
| delete[] buffer; |
| } else { |
| uint16_t* buffer = new uint16_t[length]; |
| String::WriteToFlat(cons_string, buffer, 0, length); |
| AddCharacters(buffer, length); |
| delete[] buffer; |
| } |
| } |
| |
| Handle<Object> CacheInitialJSArrayMaps(Handle<Context> native_context, |
| Handle<Map> initial_map) { |
| // Replace all of the cached initial array maps in the native context with |
| // the appropriate transitioned elements kind maps. |
| Handle<Map> current_map = initial_map; |
| ElementsKind kind = current_map->elements_kind(); |
| DCHECK_EQ(GetInitialFastElementsKind(), kind); |
| native_context->set(Context::ArrayMapIndex(kind), *current_map); |
| for (int i = GetSequenceIndexFromFastElementsKind(kind) + 1; |
| i < kFastElementsKindCount; ++i) { |
| Handle<Map> new_map; |
| ElementsKind next_kind = GetFastElementsKindFromSequenceIndex(i); |
| Map maybe_elements_transition = current_map->ElementsTransitionMap(); |
| if (!maybe_elements_transition.is_null()) { |
| new_map = handle(maybe_elements_transition, native_context->GetIsolate()); |
| } else { |
| new_map = |
| Map::CopyAsElementsKind(native_context->GetIsolate(), current_map, |
| next_kind, INSERT_TRANSITION); |
| } |
| DCHECK_EQ(next_kind, new_map->elements_kind()); |
| native_context->set(Context::ArrayMapIndex(next_kind), *new_map); |
| current_map = new_map; |
| } |
| return initial_map; |
| } |
| |
| STATIC_ASSERT(Oddball::kToNumberRawOffset == HeapNumber::kValueOffset); |
| |
| void Oddball::Initialize(Isolate* isolate, Handle<Oddball> oddball, |
| const char* to_string, Handle<Object> to_number, |
| const char* type_of, byte kind) { |
| Handle<String> internalized_to_string = |
| isolate->factory()->InternalizeUtf8String(to_string); |
| Handle<String> internalized_type_of = |
| isolate->factory()->InternalizeUtf8String(type_of); |
| if (to_number->IsHeapNumber()) { |
| oddball->set_to_number_raw_as_bits( |
| Handle<HeapNumber>::cast(to_number)->value_as_bits()); |
| } else { |
| oddball->set_to_number_raw(to_number->Number()); |
| } |
| oddball->set_to_number(*to_number); |
| oddball->set_to_string(*internalized_to_string); |
| oddball->set_type_of(*internalized_type_of); |
| oddball->set_kind(kind); |
| } |
| |
| int Script::GetEvalPosition() { |
| DisallowHeapAllocation no_gc; |
| DCHECK(compilation_type() == Script::COMPILATION_TYPE_EVAL); |
| int position = eval_from_position(); |
| if (position < 0) { |
| // Due to laziness, the position may not have been translated from code |
| // offset yet, which would be encoded as negative integer. In that case, |
| // translate and set the position. |
| if (!has_eval_from_shared()) { |
| position = 0; |
| } else { |
| SharedFunctionInfo shared = eval_from_shared(); |
| position = shared->abstract_code()->SourcePosition(-position); |
| } |
| DCHECK_GE(position, 0); |
| set_eval_from_position(position); |
| } |
| return position; |
| } |
| |
| void Script::InitLineEnds(Handle<Script> script) { |
| Isolate* isolate = script->GetIsolate(); |
| if (!script->line_ends()->IsUndefined(isolate)) return; |
| DCHECK(script->type() != Script::TYPE_WASM || |
| script->source_mapping_url()->IsString()); |
| |
| Object src_obj = script->source(); |
| if (!src_obj->IsString()) { |
| DCHECK(src_obj->IsUndefined(isolate)); |
| script->set_line_ends(ReadOnlyRoots(isolate).empty_fixed_array()); |
| } else { |
| DCHECK(src_obj->IsString()); |
| Handle<String> src(String::cast(src_obj), isolate); |
| Handle<FixedArray> array = String::CalculateLineEnds(isolate, src, true); |
| script->set_line_ends(*array); |
| } |
| |
| DCHECK(script->line_ends()->IsFixedArray()); |
| } |
| |
| bool Script::GetPositionInfo(Handle<Script> script, int position, |
| PositionInfo* info, OffsetFlag offset_flag) { |
| // For wasm, we do not create an artificial line_ends array, but do the |
| // translation directly. |
| if (script->type() != Script::TYPE_WASM) InitLineEnds(script); |
| return script->GetPositionInfo(position, info, offset_flag); |
| } |
| |
| bool Script::IsUserJavaScript() { return type() == Script::TYPE_NORMAL; } |
| |
| bool Script::ContainsAsmModule() { |
| DisallowHeapAllocation no_gc; |
| SharedFunctionInfo::ScriptIterator iter(this->GetIsolate(), *this); |
| for (SharedFunctionInfo info = iter.Next(); !info.is_null(); |
| info = iter.Next()) { |
| if (info->HasAsmWasmData()) return true; |
| } |
| return false; |
| } |
| |
| namespace { |
| bool GetPositionInfoSlow(const Script script, int position, |
| Script::PositionInfo* info) { |
| if (!script->source()->IsString()) return false; |
| if (position < 0) position = 0; |
| |
| String source_string = String::cast(script->source()); |
| int line = 0; |
| int line_start = 0; |
| int len = source_string->length(); |
| for (int pos = 0; pos <= len; ++pos) { |
| if (pos == len || source_string->Get(pos) == '\n') { |
| if (position <= pos) { |
| info->line = line; |
| info->column = position - line_start; |
| info->line_start = line_start; |
| info->line_end = pos; |
| return true; |
| } |
| line++; |
| line_start = pos + 1; |
| } |
| } |
| return false; |
| } |
| } // namespace |
| |
| #define SMI_VALUE(x) (Smi::ToInt(x)) |
| bool Script::GetPositionInfo(int position, PositionInfo* info, |
| OffsetFlag offset_flag) const { |
| DisallowHeapAllocation no_allocation; |
| |
| // For wasm, we do not rely on the line_ends array, but do the translation |
| // directly. |
| if (type() == Script::TYPE_WASM) { |
| DCHECK_LE(0, position); |
| return WasmModuleObject::cast(wasm_module_object()) |
| ->GetPositionInfo(static_cast<uint32_t>(position), info); |
| } |
| |
| if (line_ends()->IsUndefined()) { |
| // Slow mode: we do not have line_ends. We have to iterate through source. |
| if (!GetPositionInfoSlow(*this, position, info)) return false; |
| } else { |
| DCHECK(line_ends()->IsFixedArray()); |
| FixedArray ends = FixedArray::cast(line_ends()); |
| |
| const int ends_len = ends->length(); |
| if (ends_len == 0) return false; |
| |
| // Return early on invalid positions. Negative positions behave as if 0 was |
| // passed, and positions beyond the end of the script return as failure. |
| if (position < 0) { |
| position = 0; |
| } else if (position > SMI_VALUE(ends->get(ends_len - 1))) { |
| return false; |
| } |
| |
| // Determine line number by doing a binary search on the line ends array. |
| if (SMI_VALUE(ends->get(0)) >= position) { |
| info->line = 0; |
| info->line_start = 0; |
| info->column = position; |
| } else { |
| int left = 0; |
| int right = ends_len - 1; |
| |
| while (right > 0) { |
| DCHECK_LE(left, right); |
| const int mid = (left + right) / 2; |
| if (position > SMI_VALUE(ends->get(mid))) { |
| left = mid + 1; |
| } else if (position <= SMI_VALUE(ends->get(mid - 1))) { |
| right = mid - 1; |
| } else { |
| info->line = mid; |
| break; |
| } |
| } |
| DCHECK(SMI_VALUE(ends->get(info->line)) >= position && |
| SMI_VALUE(ends->get(info->line - 1)) < position); |
| info->line_start = SMI_VALUE(ends->get(info->line - 1)) + 1; |
| info->column = position - info->line_start; |
| } |
| |
| // Line end is position of the linebreak character. |
| info->line_end = SMI_VALUE(ends->get(info->line)); |
| if (info->line_end > 0) { |
| DCHECK(source()->IsString()); |
| String src = String::cast(source()); |
| if (src->length() >= info->line_end && |
| src->Get(info->line_end - 1) == '\r') { |
| info->line_end--; |
| } |
| } |
| } |
| |
| // Add offsets if requested. |
| if (offset_flag == WITH_OFFSET) { |
| if (info->line == 0) { |
| info->column += column_offset(); |
| } |
| info->line += line_offset(); |
| } |
| |
| return true; |
| } |
| #undef SMI_VALUE |
| |
| int Script::GetColumnNumber(Handle<Script> script, int code_pos) { |
| PositionInfo info; |
| GetPositionInfo(script, code_pos, &info, WITH_OFFSET); |
| return info.column; |
| } |
| |
| int Script::GetColumnNumber(int code_pos) const { |
| PositionInfo info; |
| GetPositionInfo(code_pos, &info, WITH_OFFSET); |
| return info.column; |
| } |
| |
| int Script::GetLineNumber(Handle<Script> script, int code_pos) { |
| PositionInfo info; |
| GetPositionInfo(script, code_pos, &info, WITH_OFFSET); |
| return info.line; |
| } |
| |
| int Script::GetLineNumber(int code_pos) const { |
| PositionInfo info; |
| GetPositionInfo(code_pos, &info, WITH_OFFSET); |
| return info.line; |
| } |
| |
| Object Script::GetNameOrSourceURL() { |
| // Keep in sync with ScriptNameOrSourceURL in messages.js. |
| if (!source_url()->IsUndefined()) return source_url(); |
| return name(); |
| } |
| |
| MaybeHandle<SharedFunctionInfo> Script::FindSharedFunctionInfo( |
| Isolate* isolate, const FunctionLiteral* fun) { |
| CHECK_NE(fun->function_literal_id(), kFunctionLiteralIdInvalid); |
| // If this check fails, the problem is most probably the function id |
| // renumbering done by AstFunctionLiteralIdReindexer; in particular, that |
| // AstTraversalVisitor doesn't recurse properly in the construct which |
| // triggers the mismatch. |
| CHECK_LT(fun->function_literal_id(), shared_function_infos()->length()); |
| MaybeObject shared = shared_function_infos()->Get(fun->function_literal_id()); |
| HeapObject heap_object; |
| if (!shared->GetHeapObject(&heap_object) || |
| heap_object->IsUndefined(isolate)) { |
| return MaybeHandle<SharedFunctionInfo>(); |
| } |
| return handle(SharedFunctionInfo::cast(heap_object), isolate); |
| } |
| |
| Script::Iterator::Iterator(Isolate* isolate) |
| : iterator_(isolate->heap()->script_list()) {} |
| |
| Script Script::Iterator::Next() { |
| Object o = iterator_.Next(); |
| if (o != Object()) { |
| return Script::cast(o); |
| } |
| return Script(); |
| } |
| |
| uint32_t SharedFunctionInfo::Hash() { |
| // Hash SharedFunctionInfo based on its start position and script id. Note: we |
| // don't use the function's literal id since getting that is slow for compiled |
| // funcitons. |
| int start_pos = StartPosition(); |
| int script_id = script()->IsScript() ? Script::cast(script())->id() : 0; |
| return static_cast<uint32_t>(base::hash_combine(start_pos, script_id)); |
| } |
| |
| Code SharedFunctionInfo::GetCode() const { |
| // ====== |
| // NOTE: This chain of checks MUST be kept in sync with the equivalent CSA |
| // GetSharedFunctionInfoCode method in code-stub-assembler.cc. |
| // ====== |
| |
| Isolate* isolate = GetIsolate(); |
| Object data = function_data(); |
| if (data->IsSmi()) { |
| // Holding a Smi means we are a builtin. |
| DCHECK(HasBuiltinId()); |
| return isolate->builtins()->builtin(builtin_id()); |
| } else if (data->IsBytecodeArray()) { |
| // Having a bytecode array means we are a compiled, interpreted function. |
| DCHECK(HasBytecodeArray()); |
| return isolate->builtins()->builtin(Builtins::kInterpreterEntryTrampoline); |
| } else if (data->IsAsmWasmData()) { |
| // Having AsmWasmData means we are an asm.js/wasm function. |
| DCHECK(HasAsmWasmData()); |
| return isolate->builtins()->builtin(Builtins::kInstantiateAsmJs); |
| } else if (data->IsUncompiledData()) { |
| // Having uncompiled data (with or without scope) means we need to compile. |
| DCHECK(HasUncompiledData()); |
| return isolate->builtins()->builtin(Builtins::kCompileLazy); |
| } else if (data->IsFunctionTemplateInfo()) { |
| // Having a function template info means we are an API function. |
| DCHECK(IsApiFunction()); |
| return isolate->builtins()->builtin(Builtins::kHandleApiCall); |
| } else if (data->IsWasmExportedFunctionData()) { |
| // Having a WasmExportedFunctionData means the code is in there. |
| DCHECK(HasWasmExportedFunctionData()); |
| return wasm_exported_function_data()->wrapper_code(); |
| } else if (data->IsInterpreterData()) { |
| Code code = InterpreterTrampoline(); |
| DCHECK(code->IsCode()); |
| DCHECK(code->is_interpreter_trampoline_builtin()); |
| return code; |
| } |
| UNREACHABLE(); |
| } |
| |
| WasmExportedFunctionData SharedFunctionInfo::wasm_exported_function_data() |
| const { |
| DCHECK(HasWasmExportedFunctionData()); |
| return WasmExportedFunctionData::cast(function_data()); |
| } |
| |
| SharedFunctionInfo::ScriptIterator::ScriptIterator(Isolate* isolate, |
| Script script) |
| : ScriptIterator(isolate, |
| handle(script->shared_function_infos(), isolate)) {} |
| |
| SharedFunctionInfo::ScriptIterator::ScriptIterator( |
| Isolate* isolate, Handle<WeakFixedArray> shared_function_infos) |
| : isolate_(isolate), |
| shared_function_infos_(shared_function_infos), |
| index_(0) {} |
| |
| SharedFunctionInfo SharedFunctionInfo::ScriptIterator::Next() { |
| while (index_ < shared_function_infos_->length()) { |
| MaybeObject raw = shared_function_infos_->Get(index_++); |
| HeapObject heap_object; |
| if (!raw->GetHeapObject(&heap_object) || |
| heap_object->IsUndefined(isolate_)) { |
| continue; |
| } |
| return SharedFunctionInfo::cast(heap_object); |
| } |
| return SharedFunctionInfo(); |
| } |
| |
| void SharedFunctionInfo::ScriptIterator::Reset(Script script) { |
| shared_function_infos_ = handle(script->shared_function_infos(), isolate_); |
| index_ = 0; |
| } |
| |
| SharedFunctionInfo::GlobalIterator::GlobalIterator(Isolate* isolate) |
| : script_iterator_(isolate), |
| noscript_sfi_iterator_(isolate->heap()->noscript_shared_function_infos()), |
| sfi_iterator_(isolate, script_iterator_.Next()) {} |
| |
| SharedFunctionInfo SharedFunctionInfo::GlobalIterator::Next() { |
| HeapObject next = noscript_sfi_iterator_.Next(); |
| if (!next.is_null()) return SharedFunctionInfo::cast(next); |
| for (;;) { |
| next = sfi_iterator_.Next(); |
| if (!next.is_null()) return SharedFunctionInfo::cast(next); |
| Script next_script = script_iterator_.Next(); |
| if (next_script.is_null()) return SharedFunctionInfo(); |
| sfi_iterator_.Reset(next_script); |
| } |
| } |
| |
| void SharedFunctionInfo::SetScript(Handle<SharedFunctionInfo> shared, |
| Handle<Object> script_object, |
| int function_literal_id, |
| bool reset_preparsed_scope_data) { |
| if (shared->script() == *script_object) return; |
| Isolate* isolate = shared->GetIsolate(); |
| |
| if (reset_preparsed_scope_data && |
| shared->HasUncompiledDataWithPreparseData()) { |
| shared->ClearPreparseData(); |
| } |
| |
| // Add shared function info to new script's list. If a collection occurs, |
| // the shared function info may be temporarily in two lists. |
| // This is okay because the gc-time processing of these lists can tolerate |
| // duplicates. |
| if (script_object->IsScript()) { |
| DCHECK(!shared->script()->IsScript()); |
| Handle<Script> script = Handle<Script>::cast(script_object); |
| Handle<WeakFixedArray> list = |
| handle(script->shared_function_infos(), isolate); |
| #ifdef DEBUG |
| DCHECK_LT(function_literal_id, list->length()); |
| MaybeObject maybe_object = list->Get(function_literal_id); |
| HeapObject heap_object; |
| if (maybe_object->GetHeapObjectIfWeak(&heap_object)) { |
| DCHECK_EQ(heap_object, *shared); |
| } |
| #endif |
| list->Set(function_literal_id, HeapObjectReference::Weak(*shared)); |
| |
| // Remove shared function info from root array. |
| WeakArrayList noscript_list = |
| isolate->heap()->noscript_shared_function_infos(); |
| CHECK(noscript_list->RemoveOne(MaybeObjectHandle::Weak(shared))); |
| } else { |
| DCHECK(shared->script()->IsScript()); |
| Handle<WeakArrayList> list = |
| isolate->factory()->noscript_shared_function_infos(); |
| |
| #ifdef DEBUG |
| if (FLAG_enable_slow_asserts) { |
| WeakArrayList::Iterator iterator(*list); |
| for (HeapObject next = iterator.Next(); !next.is_null(); |
| next = iterator.Next()) { |
| DCHECK_NE(next, *shared); |
| } |
| } |
| #endif // DEBUG |
| |
| list = |
| WeakArrayList::AddToEnd(isolate, list, MaybeObjectHandle::Weak(shared)); |
| |
| isolate->heap()->SetRootNoScriptSharedFunctionInfos(*list); |
| |
| // Remove shared function info from old script's list. |
| Script old_script = Script::cast(shared->script()); |
| |
| // Due to liveedit, it might happen that the old_script doesn't know |
| // about the SharedFunctionInfo, so we have to guard against that. |
| Handle<WeakFixedArray> infos(old_script->shared_function_infos(), isolate); |
| if (function_literal_id < infos->length()) { |
| MaybeObject raw = |
| old_script->shared_function_infos()->Get(function_literal_id); |
| HeapObject heap_object; |
| if (raw->GetHeapObjectIfWeak(&heap_object) && heap_object == *shared) { |
| old_script->shared_function_infos()->Set( |
| function_literal_id, HeapObjectReference::Strong( |
| ReadOnlyRoots(isolate).undefined_value())); |
| } |
| } |
| } |
| |
| // Finally set new script. |
| shared->set_script(*script_object); |
| } |
| |
| bool SharedFunctionInfo::HasBreakInfo() const { |
| if (!HasDebugInfo()) return false; |
| DebugInfo info = GetDebugInfo(); |
| bool has_break_info = info->HasBreakInfo(); |
| return has_break_info; |
| } |
| |
| bool SharedFunctionInfo::BreakAtEntry() const { |
| if (!HasDebugInfo()) return false; |
| DebugInfo info = GetDebugInfo(); |
| bool break_at_entry = info->BreakAtEntry(); |
| return break_at_entry; |
| } |
| |
| bool SharedFunctionInfo::HasCoverageInfo() const { |
| if (!HasDebugInfo()) return false; |
| DebugInfo info = GetDebugInfo(); |
| bool has_coverage_info = info->HasCoverageInfo(); |
| return has_coverage_info; |
| } |
| |
| CoverageInfo SharedFunctionInfo::GetCoverageInfo() const { |
| DCHECK(HasCoverageInfo()); |
| return CoverageInfo::cast(GetDebugInfo()->coverage_info()); |
| } |
| |
| String SharedFunctionInfo::DebugName() { |
| DisallowHeapAllocation no_gc; |
| String function_name = Name(); |
| if (function_name->length() > 0) return function_name; |
| return inferred_name(); |
| } |
| |
| bool SharedFunctionInfo::PassesFilter(const char* raw_filter) { |
| Vector<const char> filter = CStrVector(raw_filter); |
| std::unique_ptr<char[]> cstrname(DebugName()->ToCString()); |
| return v8::internal::PassesFilter(CStrVector(cstrname.get()), filter); |
| } |
| |
| bool SharedFunctionInfo::HasSourceCode() const { |
| Isolate* isolate = GetIsolate(); |
| return !script()->IsUndefined(isolate) && |
| !Script::cast(script())->source()->IsUndefined(isolate); |
| } |
| |
| void SharedFunctionInfo::DiscardCompiledMetadata( |
| Isolate* isolate, |
| std::function<void(HeapObject object, ObjectSlot slot, HeapObject target)> |
| gc_notify_updated_slot) { |
| DisallowHeapAllocation no_gc; |
| if (is_compiled()) { |
| HeapObject outer_scope_info; |
| if (scope_info()->HasOuterScopeInfo()) { |
| outer_scope_info = scope_info()->OuterScopeInfo(); |
| } else { |
| outer_scope_info = ReadOnlyRoots(isolate).the_hole_value(); |
| } |
| |
| // Raw setter to avoid validity checks, since we're performing the unusual |
| // task of decompiling. |
| set_raw_outer_scope_info_or_feedback_metadata(outer_scope_info); |
| gc_notify_updated_slot( |
| *this, |
| RawField(SharedFunctionInfo::kOuterScopeInfoOrFeedbackMetadataOffset), |
| outer_scope_info); |
| } else { |
| DCHECK(outer_scope_info()->IsScopeInfo() || |
| outer_scope_info()->IsTheHole()); |
| } |
| |
| // TODO(rmcilroy): Possibly discard ScopeInfo here as well. |
| } |
| |
| // static |
| void SharedFunctionInfo::DiscardCompiled( |
| Isolate* isolate, Handle<SharedFunctionInfo> shared_info) { |
| DCHECK(shared_info->CanDiscardCompiled()); |
| |
| Handle<String> inferred_name_val = |
| handle(shared_info->inferred_name(), isolate); |
| int start_position = shared_info->StartPosition(); |
| int end_position = shared_info->EndPosition(); |
| int function_literal_id = shared_info->FunctionLiteralId(isolate); |
| |
| shared_info->DiscardCompiledMetadata(isolate); |
| |
| // Replace compiled data with a new UncompiledData object. |
| if (shared_info->HasUncompiledDataWithPreparseData()) { |
| // If this is uncompiled data with a pre-parsed scope data, we can just |
| // clear out the scope data and keep the uncompiled data. |
| shared_info->ClearPreparseData(); |
| } else { |
| // Create a new UncompiledData, without pre-parsed scope, and update the |
| // function data to point to it. Use the raw function data setter to avoid |
| // validity checks, since we're performing the unusual task of decompiling. |
| Handle<UncompiledData> data = |
| isolate->factory()->NewUncompiledDataWithoutPreparseData( |
| inferred_name_val, start_position, end_position, |
| function_literal_id); |
| shared_info->set_function_data(*data); |
| } |
| } |
| |
| // static |
| Handle<Object> SharedFunctionInfo::GetSourceCode( |
| Handle<SharedFunctionInfo> shared) { |
| Isolate* isolate = shared->GetIsolate(); |
| if (!shared->HasSourceCode()) return isolate->factory()->undefined_value(); |
| Handle<String> source(String::cast(Script::cast(shared->script())->source()), |
| isolate); |
| return isolate->factory()->NewSubString(source, shared->StartPosition(), |
| shared->EndPosition()); |
| } |
| |
| // static |
| Handle<Object> SharedFunctionInfo::GetSourceCodeHarmony( |
| Handle<SharedFunctionInfo> shared) { |
| Isolate* isolate = shared->GetIsolate(); |
| if (!shared->HasSourceCode()) return isolate->factory()->undefined_value(); |
| Handle<String> script_source( |
| String::cast(Script::cast(shared->script())->source()), isolate); |
| int start_pos = shared->function_token_position(); |
| DCHECK_NE(start_pos, kNoSourcePosition); |
| Handle<String> source = isolate->factory()->NewSubString( |
| script_source, start_pos, shared->EndPosition()); |
| if (!shared->is_wrapped()) return source; |
| |
| DCHECK(!shared->name_should_print_as_anonymous()); |
| IncrementalStringBuilder builder(isolate); |
| builder.AppendCString("function "); |
| builder.AppendString(Handle<String>(shared->Name(), isolate)); |
| builder.AppendCString("("); |
| Handle<FixedArray> args(Script::cast(shared->script())->wrapped_arguments(), |
| isolate); |
| int argc = args->length(); |
| for (int i = 0; i < argc; i++) { |
| if (i > 0) builder.AppendCString(", "); |
| builder.AppendString(Handle<String>(String::cast(args->get(i)), isolate)); |
| } |
| builder.AppendCString(") {\n"); |
| builder.AppendString(source); |
| builder.AppendCString("\n}"); |
| return builder.Finish().ToHandleChecked(); |
| } |
| |
| namespace { |
| void TraceInlining(SharedFunctionInfo shared, const char* msg) { |
| if (FLAG_trace_turbo_inlining) { |
| StdoutStream os; |
| os << Brief(shared) << ": IsInlineable? " << msg << "\n"; |
| } |
| } |
| } // namespace |
| |
| bool SharedFunctionInfo::IsInlineable() { |
| if (!script()->IsScript()) { |
| TraceInlining(*this, "false (no Script associated with it)"); |
| return false; |
| } |
| |
| if (GetIsolate()->is_precise_binary_code_coverage() && |
| !has_reported_binary_coverage()) { |
| // We may miss invocations if this function is inlined. |
| TraceInlining(*this, "false (requires precise binary coverage)"); |
| return false; |
| } |
| |
| if (optimization_disabled()) { |
| TraceInlining(*this, "false (optimization disabled)"); |
| return false; |
| } |
| |
| // Built-in functions are handled by the JSCallReducer. |
| if (HasBuiltinFunctionId()) { |
| TraceInlining(*this, "false (is a builtin)"); |
| return false; |
| } |
| |
| if (!IsUserJavaScript()) { |
| TraceInlining(*this, "false (is not user code)"); |
| return false; |
| } |
| |
| // If there is no bytecode array, it is either not compiled or it is compiled |
| // with WebAssembly for the asm.js pipeline. In either case we don't want to |
| // inline. |
| if (!HasBytecodeArray()) { |
| TraceInlining(*this, "false (has no BytecodeArray)"); |
| return false; |
| } |
| |
| if (GetBytecodeArray()->length() > FLAG_max_inlined_bytecode_size) { |
| TraceInlining(*this, "false (length > FLAG_max_inlined_bytecode_size)"); |
| return false; |
| } |
| |
| if (HasBreakInfo()) { |
| TraceInlining(*this, "false (may contain break points)"); |
| return false; |
| } |
| |
| TraceInlining(*this, "true"); |
| return true; |
| } |
| |
| int SharedFunctionInfo::SourceSize() { return EndPosition() - StartPosition(); } |
| |
| int SharedFunctionInfo::FindIndexInScript(Isolate* isolate) const { |
| DisallowHeapAllocation no_gc; |
| |
| Object script_obj = script(); |
| if (!script_obj->IsScript()) return kFunctionLiteralIdInvalid; |
| |
| WeakFixedArray shared_info_list = |
| Script::cast(script_obj)->shared_function_infos(); |
| SharedFunctionInfo::ScriptIterator iterator( |
| isolate, |
| Handle<WeakFixedArray>(reinterpret_cast<Address*>(&shared_info_list))); |
| |
| for (SharedFunctionInfo shared = iterator.Next(); !shared.is_null(); |
| shared = iterator.Next()) { |
| if (shared == *this) { |
| return iterator.CurrentIndex(); |
| } |
| } |
| |
| return kFunctionLiteralIdInvalid; |
| } |
| |
| |
| // Output the source code without any allocation in the heap. |
| std::ostream& operator<<(std::ostream& os, const SourceCodeOf& v) { |
| const SharedFunctionInfo s = v.value; |
| // For some native functions there is no source. |
| if (!s->HasSourceCode()) return os << "<No Source>"; |
| |
| // Get the source for the script which this function came from. |
| // Don't use String::cast because we don't want more assertion errors while |
| // we are already creating a stack dump. |
| String script_source = |
| String::unchecked_cast(Script::cast(s->script())->source()); |
| |
| if (!script_source->LooksValid()) return os << "<Invalid Source>"; |
| |
| if (!s->is_toplevel()) { |
| os << "function "; |
| String name = s->Name(); |
| if (name->length() > 0) { |
| name->PrintUC16(os); |
| } |
| } |
| |
| int len = s->EndPosition() - s->StartPosition(); |
| if (len <= v.max_length || v.max_length < 0) { |
| script_source->PrintUC16(os, s->StartPosition(), s->EndPosition()); |
| return os; |
| } else { |
| script_source->PrintUC16(os, s->StartPosition(), |
| s->StartPosition() + v.max_length); |
| return os << "...\n"; |
| } |
| } |
| |
| |
| void SharedFunctionInfo::DisableOptimization(BailoutReason reason) { |
| DCHECK_NE(reason, BailoutReason::kNoReason); |
| |
| set_flags(DisabledOptimizationReasonBits::update(flags(), reason)); |
| // Code should be the lazy compilation stub or else interpreted. |
| DCHECK(abstract_code()->kind() == AbstractCode::INTERPRETED_FUNCTION || |
| abstract_code()->kind() == AbstractCode::BUILTIN); |
| PROFILE(GetIsolate(), CodeDisableOptEvent(abstract_code(), *this)); |
| if (FLAG_trace_opt) { |
| PrintF("[disabled optimization for "); |
| ShortPrint(); |
| PrintF(", reason: %s]\n", GetBailoutReason(reason)); |
| } |
| } |
| |
| void SharedFunctionInfo::InitFromFunctionLiteral( |
| Handle<SharedFunctionInfo> shared_info, FunctionLiteral* lit, |
| bool is_toplevel) { |
| Isolate* isolate = shared_info->GetIsolate(); |
| bool needs_position_info = true; |
| |
| // When adding fields here, make sure DeclarationScope::AnalyzePartially is |
| // updated accordingly. |
| shared_info->set_internal_formal_parameter_count(lit->parameter_count()); |
| shared_info->SetFunctionTokenPosition(lit->function_token_position(), |
| lit->start_position()); |
| if (shared_info->scope_info()->HasPositionInfo()) { |
| shared_info->scope_info()->SetPositionInfo(lit->start_position(), |
| lit->end_position()); |
| needs_position_info = false; |
| } |
| shared_info->set_is_declaration(lit->is_declaration()); |
| shared_info->set_is_named_expression(lit->is_named_expression()); |
| shared_info->set_is_anonymous_expression(lit->is_anonymous_expression()); |
| shared_info->set_allows_lazy_compilation(lit->AllowsLazyCompilation()); |
| shared_info->set_language_mode(lit->language_mode()); |
| shared_info->set_is_wrapped(lit->is_wrapped()); |
| // shared_info->set_kind(lit->kind()); |
| // FunctionKind must have already been set. |
| DCHECK(lit->kind() == shared_info->kind()); |
| shared_info->set_needs_home_object(lit->scope()->NeedsHomeObject()); |
| DCHECK_IMPLIES(lit->requires_instance_members_initializer(), |
| IsClassConstructor(lit->kind())); |
| shared_info->set_requires_instance_members_initializer( |
| lit->requires_instance_members_initializer()); |
| |
| shared_info->set_is_toplevel(is_toplevel); |
| DCHECK(shared_info->outer_scope_info()->IsTheHole()); |
| if (!is_toplevel) { |
| Scope* outer_scope = lit->scope()->GetOuterScopeWithContext(); |
| if (outer_scope) { |
| shared_info->set_outer_scope_info(*outer_scope->scope_info()); |
| } |
| } |
| |
| // For lazy parsed functions, the following flags will be inaccurate since we |
| // don't have the information yet. They're set later in |
| // SetSharedFunctionFlagsFromLiteral (compiler.cc), when the function is |
| // really parsed and compiled. |
| if (lit->ShouldEagerCompile()) { |
| shared_info->set_length(lit->function_length()); |
| shared_info->set_has_duplicate_parameters(lit->has_duplicate_parameters()); |
| shared_info->SetExpectedNofPropertiesFromEstimate(lit); |
| shared_info->set_is_safe_to_skip_arguments_adaptor( |
| lit->SafeToSkipArgumentsAdaptor()); |
| DCHECK_NULL(lit->produced_preparse_data()); |
| // If we're about to eager compile, we'll have the function literal |
| // available, so there's no need to wastefully allocate an uncompiled data. |
| // TODO(leszeks): This should be explicitly passed as a parameter, rather |
| // than relying on a property of the literal. |
| needs_position_info = false; |
| } else { |
| // Set an invalid length for lazy functions. This way we can set the correct |
| // value after compiling, but avoid overwriting values set manually by the |
| // bootstrapper. |
| shared_info->set_length(SharedFunctionInfo::kInvalidLength); |
| shared_info->set_is_safe_to_skip_arguments_adaptor(false); |
| ProducedPreparseData* scope_data = lit->produced_preparse_data(); |
| if (scope_data != nullptr) { |
| Handle<PreparseData> preparse_data = |
| scope_data->Serialize(shared_info->GetIsolate()); |
| Handle<UncompiledData> data = |
| isolate->factory()->NewUncompiledDataWithPreparseData( |
| lit->inferred_name(), lit->start_position(), lit->end_position(), |
| lit->function_literal_id(), preparse_data); |
| shared_info->set_uncompiled_data(*data); |
| needs_position_info = false; |
| } |
| } |
| if (needs_position_info) { |
| Handle<UncompiledData> data = |
| isolate->factory()->NewUncompiledDataWithoutPreparseData( |
| lit->inferred_name(), lit->start_position(), lit->end_position(), |
| lit->function_literal_id()); |
| shared_info->set_uncompiled_data(*data); |
| } |
| } |
| |
| void SharedFunctionInfo::SetExpectedNofPropertiesFromEstimate( |
| FunctionLiteral* literal) { |
| int estimate = literal->expected_property_count(); |
| |
| // If no properties are added in the constructor, they are more likely |
| // to be added later. |
| if (estimate == 0) estimate = 2; |
| |
| // Limit actual estimate to fit in a 8 bit field, we will never allocate |
| // more than this in any case. |
| STATIC_ASSERT(JSObject::kMaxInObjectProperties <= kMaxUInt8); |
| estimate = std::min(estimate, kMaxUInt8); |
| |
| set_expected_nof_properties(estimate); |
| } |
| |
| void SharedFunctionInfo::SetFunctionTokenPosition(int function_token_position, |
| int start_position) { |
| int offset; |
| if (function_token_position == kNoSourcePosition) { |
| offset = 0; |
| } else { |
| offset = start_position - function_token_position; |
| } |
| |
| if (offset > kMaximumFunctionTokenOffset) { |
| offset = kFunctionTokenOutOfRange; |
| } |
| set_raw_function_token_offset(offset); |
| } |
| |
| int SharedFunctionInfo::StartPosition() const { |
| Object maybe_scope_info = name_or_scope_info(); |
| if (maybe_scope_info->IsScopeInfo()) { |
| ScopeInfo info = ScopeInfo::cast(maybe_scope_info); |
| if (info->HasPositionInfo()) { |
| return info->StartPosition(); |
| } |
| } else if (HasUncompiledData()) { |
| // Works with or without scope. |
| return uncompiled_data()->start_position(); |
| } else if (IsApiFunction() || HasBuiltinId()) { |
| DCHECK_IMPLIES(HasBuiltinId(), builtin_id() != Builtins::kCompileLazy); |
| return 0; |
| } |
| return kNoSourcePosition; |
| } |
| |
| int SharedFunctionInfo::EndPosition() const { |
| Object maybe_scope_info = name_or_scope_info(); |
| if (maybe_scope_info->IsScopeInfo()) { |
| ScopeInfo info = ScopeInfo::cast(maybe_scope_info); |
| if (info->HasPositionInfo()) { |
| return info->EndPosition(); |
| } |
| } else if (HasUncompiledData()) { |
| // Works with or without scope. |
| return uncompiled_data()->end_position(); |
| } else if (IsApiFunction() || HasBuiltinId()) { |
| DCHECK_IMPLIES(HasBuiltinId(), builtin_id() != Builtins::kCompileLazy); |
| return 0; |
| } |
| return kNoSourcePosition; |
| } |
| |
| int SharedFunctionInfo::FunctionLiteralId(Isolate* isolate) const { |
| // Fast path for the common case when the SFI is uncompiled and so the |
| // function literal id is already in the uncompiled data. |
| if (HasUncompiledData() && uncompiled_data()->has_function_literal_id()) { |
| int id = uncompiled_data()->function_literal_id(); |
| // Make sure the id is what we should have found with the slow path. |
| DCHECK_EQ(id, FindIndexInScript(isolate)); |
| return id; |
| } |
| |
| // Otherwise, search for the function in the SFI's script's function list, |
| // and return its index in that list. |
| return FindIndexInScript(isolate); |
| } |
| |
| void SharedFunctionInfo::SetPosition(int start_position, int end_position) { |
| Object maybe_scope_info = name_or_scope_info(); |
| if (maybe_scope_info->IsScopeInfo()) { |
| ScopeInfo info = ScopeInfo::cast(maybe_scope_info); |
| if (info->HasPositionInfo()) { |
| info->SetPositionInfo(start_position, end_position); |
| } |
| } else if (HasUncompiledData()) { |
| if (HasUncompiledDataWithPreparseData()) { |
| // Clear out preparsed scope data, since the position setter invalidates |
| // any scope data. |
| ClearPreparseData(); |
| } |
| uncompiled_data()->set_start_position(start_position); |
| uncompiled_data()->set_end_position(end_position); |
| } else { |
| UNREACHABLE(); |
| } |
| } |
| |
| // static |
| void SharedFunctionInfo::EnsureSourcePositionsAvailable( |
| Isolate* isolate, Handle<SharedFunctionInfo> shared_info) { |
| if (FLAG_enable_lazy_source_positions && shared_info->HasBytecodeArray() && |
| !shared_info->GetBytecodeArray()->HasSourcePositionTable()) { |
| Compiler::CollectSourcePositions(isolate, shared_info); |
| } |
| } |
| |
| bool BytecodeArray::IsBytecodeEqual(const BytecodeArray other) const { |
| if (length() != other->length()) return false; |
| |
| for (int i = 0; i < length(); ++i) { |
| if (get(i) != other->get(i)) return false; |
| } |
| |
| return true; |
| } |
| |
| // static |
| void JSArray::Initialize(Handle<JSArray> array, int capacity, int length) { |
| DCHECK_GE(capacity, 0); |
| array->GetIsolate()->factory()->NewJSArrayStorage( |
| array, length, capacity, INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE); |
| } |
| |
| void JSArray::SetLength(Handle<JSArray> array, uint32_t new_length) { |
| // We should never end in here with a pixel or external array. |
| DCHECK(array->AllowsSetLength()); |
| if (array->SetLengthWouldNormalize(new_length)) { |
| JSObject::NormalizeElements(array); |
| } |
| array->GetElementsAccessor()->SetLength(array, new_length); |
| } |
| |
| // ES6: 9.5.2 [[SetPrototypeOf]] (V) |
| // static |
| Maybe<bool> JSProxy::SetPrototype(Handle<JSProxy> proxy, Handle<Object> value, |
| bool from_javascript, |
| ShouldThrow should_throw) { |
| Isolate* isolate = proxy->GetIsolate(); |
| STACK_CHECK(isolate, Nothing<bool>()); |
| Handle<Name> trap_name = isolate->factory()->setPrototypeOf_string(); |
| // 1. Assert: Either Type(V) is Object or Type(V) is Null. |
| DCHECK(value->IsJSReceiver() || value->IsNull(isolate)); |
| // 2. Let handler be the value of the [[ProxyHandler]] internal slot of O. |
| Handle<Object> handler(proxy->handler(), isolate); |
| // 3. If handler is null, throw a TypeError exception. |
| // 4. Assert: Type(handler) is Object. |
| if (proxy->IsRevoked()) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxyRevoked, trap_name)); |
| return Nothing<bool>(); |
| } |
| // 5. Let target be the value of the [[ProxyTarget]] internal slot. |
| Handle<JSReceiver> target(JSReceiver::cast(proxy->target()), isolate); |
| // 6. Let trap be ? GetMethod(handler, "getPrototypeOf"). |
| Handle<Object> trap; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap, |
| Object::GetMethod(Handle<JSReceiver>::cast(handler), trap_name), |
| Nothing<bool>()); |
| // 7. If trap is undefined, then return target.[[SetPrototypeOf]](). |
| if (trap->IsUndefined(isolate)) { |
| return JSReceiver::SetPrototype(target, value, from_javascript, |
| should_throw); |
| } |
| // 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, «target, V»)). |
| Handle<Object> argv[] = {target, value}; |
| Handle<Object> trap_result; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, trap_result, |
| Execution::Call(isolate, trap, handler, arraysize(argv), argv), |
| Nothing<bool>()); |
| bool bool_trap_result = trap_result->BooleanValue(isolate); |
| // 9. If booleanTrapResult is false, return false. |
| if (!bool_trap_result) { |
| RETURN_FAILURE( |
| isolate, should_throw, |
| NewTypeError(MessageTemplate::kProxyTrapReturnedFalsish, trap_name)); |
| } |
| // 10. Let extensibleTarget be ? IsExtensible(target). |
| Maybe<bool> is_extensible = JSReceiver::IsExtensible(target); |
| if (is_extensible.IsNothing()) return Nothing<bool>(); |
| // 11. If extensibleTarget is true, return true. |
| if (is_extensible.FromJust()) { |
| if (bool_trap_result) return Just(true); |
| RETURN_FAILURE( |
| isolate, should_throw, |
| NewTypeError(MessageTemplate::kProxyTrapReturnedFalsish, trap_name)); |
| } |
| // 12. Let targetProto be ? target.[[GetPrototypeOf]](). |
| Handle<Object> target_proto; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, target_proto, |
| JSReceiver::GetPrototype(isolate, target), |
| Nothing<bool>()); |
| // 13. If SameValue(V, targetProto) is false, throw a TypeError exception. |
| if (bool_trap_result && !value->SameValue(*target_proto)) { |
| isolate->Throw(*isolate->factory()->NewTypeError( |
| MessageTemplate::kProxySetPrototypeOfNonExtensible)); |
| return Nothing<bool>(); |
| } |
| // 14. Return true. |
| return Just(true); |
| } |
| |
| |
| |
| |
| |
| bool JSArray::SetLengthWouldNormalize(uint32_t new_length) { |
| if (!HasFastElements()) return false; |
| uint32_t capacity = static_cast<uint32_t>(elements()->length()); |
| uint32_t new_capacity; |
| return JSArray::SetLengthWouldNormalize(GetHeap(), new_length) && |
| ShouldConvertToSlowElements(*this, capacity, new_length - 1, |
| &new_capacity); |
| } |
| |
| |
| const double AllocationSite::kPretenureRatio = 0.85; |
| |
| |
| void AllocationSite::ResetPretenureDecision() { |
| set_pretenure_decision(kUndecided); |
| set_memento_found_count(0); |
| set_memento_create_count(0); |
| } |
| |
| PretenureFlag AllocationSite::GetPretenureMode() const { |
| PretenureDecision mode = pretenure_decision(); |
| // Zombie objects "decide" to be untenured. |
| return mode == kTenure ? TENURED : NOT_TENURED; |
| } |
| |
| bool AllocationSite::IsNested() { |
| DCHECK(FLAG_trace_track_allocation_sites); |
| Object current = boilerplate()->GetHeap()->allocation_sites_list(); |
| while (current->IsAllocationSite()) { |
| AllocationSite current_site = AllocationSite::cast(current); |
| if (current_site->nested_site() == *this) { |
| return true; |
| } |
| current = current_site->weak_next(); |
| } |
| return false; |
| } |
| |
| |
| bool AllocationSite::ShouldTrack(ElementsKind from, ElementsKind to) { |
| return IsSmiElementsKind(from) && |
| IsMoreGeneralElementsKindTransition(from, to); |
| } |
| |
| const char* AllocationSite::PretenureDecisionName(PretenureDecision decision) { |
| switch (decision) { |
| case kUndecided: return "undecided"; |
| case kDontTenure: return "don't tenure"; |
| case kMaybeTenure: return "maybe tenure"; |
| case kTenure: return "tenure"; |
| case kZombie: return "zombie"; |
| default: UNREACHABLE(); |
| } |
| return nullptr; |
| } |
| |
| |
| |
| bool JSArray::HasReadOnlyLength(Handle<JSArray> array) { |
| Map map = array->map(); |
| // Fast path: "length" is the first fast property of arrays. Since it's not |
| // configurable, it's guaranteed to be the first in the descriptor array. |
| if (!map->is_dictionary_map()) { |
| DCHECK(map->instance_descriptors()->GetKey(0) == |
| array->GetReadOnlyRoots().length_string()); |
| return map->instance_descriptors()->GetDetails(0).IsReadOnly(); |
| } |
| |
| Isolate* isolate = array->GetIsolate(); |
| LookupIterator it(array, isolate->factory()->length_string(), array, |
| LookupIterator::OWN_SKIP_INTERCEPTOR); |
| CHECK_EQ(LookupIterator::ACCESSOR, it.state()); |
| return it.IsReadOnly(); |
| } |
| |
| |
| bool JSArray::WouldChangeReadOnlyLength(Handle<JSArray> array, |
| uint32_t index) { |
| uint32_t length = 0; |
| CHECK(array->length()->ToArrayLength(&length)); |
| if (length <= index) return HasReadOnlyLength(array); |
| return false; |
| } |
| |
| |
| |
| // Certain compilers request function template instantiation when they |
| // see the definition of the other template functions in the |
| // class. This requires us to have the template functions put |
| // together, so even though this function belongs in objects-debug.cc, |
| // we keep it here instead to satisfy certain compilers. |
| #ifdef OBJECT_PRINT |
| template <typename Derived, typename Shape> |
| void Dictionary<Derived, Shape>::Print(std::ostream& os) { |
| DisallowHeapAllocation no_gc; |
| ReadOnlyRoots roots = this->GetReadOnlyRoots(); |
| Derived dictionary = Derived::cast(*this); |
| int capacity = dictionary->Capacity(); |
| for (int i = 0; i < capacity; i++) { |
| Object k = dictionary->KeyAt(i); |
| if (!dictionary->ToKey(roots, i, &k)) continue; |
| os << "\n "; |
| if (k->IsString()) { |
| String::cast(k)->StringPrint(os); |
| } else { |
| os << Brief(k); |
| } |
| os << ": " << Brief(dictionary->ValueAt(i)) << " "; |
| dictionary->DetailsAt(i).PrintAsSlowTo(os); |
| } |
| } |
| template <typename Derived, typename Shape> |
| void Dictionary<Derived, Shape>::Print() { |
| StdoutStream os; |
| Print(os); |
| os << std::endl; |
| } |
| #endif |
| |
| |
| |
| int FixedArrayBase::GetMaxLengthForNewSpaceAllocation(ElementsKind kind) { |
| return ((kMaxRegularHeapObjectSize - FixedArrayBase::kHeaderSize) >> |
| ElementsKindToShiftSize(kind)); |
| } |
| |
| bool FixedArrayBase::IsCowArray() const { |
| return map() == GetReadOnlyRoots().fixed_cow_array_map(); |
| } |
| |
| |
| const char* Symbol::PrivateSymbolToName() const { |
| ReadOnlyRoots roots = GetReadOnlyRoots(); |
| #define SYMBOL_CHECK_AND_PRINT(_, name) \ |
| if (*this == roots.name()) return #name; |
| PRIVATE_SYMBOL_LIST_GENERATOR(SYMBOL_CHECK_AND_PRINT, /* not used */) |
| #undef SYMBOL_CHECK_AND_PRINT |
| return "UNKNOWN"; |
| } |
| |
| |
| void Symbol::SymbolShortPrint(std::ostream& os) { |
| os << "<Symbol:"; |
| if (!name()->IsUndefined()) { |
| os << " "; |
| HeapStringAllocator allocator; |
| StringStream accumulator(&allocator); |
| String::cast(name())->StringShortPrint(&accumulator, false); |
| os << accumulator.ToCString().get(); |
| } else { |
| os << " (" << PrivateSymbolToName() << ")"; |
| } |
| os << ">"; |
| } |
| |
| |
| // StringSharedKeys are used as keys in the eval cache. |
| class StringSharedKey : public HashTableKey { |
| public: |
| // This tuple unambiguously identifies calls to eval() or |
| // CreateDynamicFunction() (such as through the Function() constructor). |
| // * source is the string passed into eval(). For dynamic functions, this is |
| // the effective source for the function, some of which is implicitly |
| // generated. |
| // * shared is the shared function info for the function containing the call |
| // to eval(). for dynamic functions, shared is the native context closure. |
| // * When positive, position is the position in the source where eval is |
| // called. When negative, position is the negation of the position in the |
| // dynamic function's effective source where the ')' ends the parameters. |
| StringSharedKey(Handle<String> source, Handle<SharedFunctionInfo> shared, |
| LanguageMode language_mode, int position) |
| : HashTableKey(CompilationCacheShape::StringSharedHash( |
| *source, *shared, language_mode, position)), |
| source_(source), |
| shared_(shared), |
| language_mode_(language_mode), |
| position_(position) {} |
| |
| bool IsMatch(Object other) override { |
| DisallowHeapAllocation no_allocation; |
| if (!other->IsFixedArray()) { |
| DCHECK(other->IsNumber()); |
| uint32_t other_hash = static_cast<uint32_t>(other->Number()); |
| return Hash() == other_hash; |
| } |
| FixedArray other_array = FixedArray::cast(other); |
| SharedFunctionInfo shared = SharedFunctionInfo::cast(other_array->get(0)); |
| if (shared != *shared_) return false; |
| int language_unchecked = Smi::ToInt(other_array->get(2)); |
| DCHECK(is_valid_language_mode(language_unchecked)); |
| LanguageMode language_mode = static_cast<LanguageMode>(language_unchecked); |
| if (language_mode != language_mode_) return false; |
| int position = Smi::ToInt(other_array->get(3)); |
| if (position != position_) return false; |
| String source = String::cast(other_array->get(1)); |
| return source->Equals(*source_); |
| } |
| |
| Handle<Object> AsHandle(Isolate* isolate) { |
| Handle<FixedArray> array = isolate->factory()->NewFixedArray(4); |
| array->set(0, *shared_); |
| array->set(1, *source_); |
| array->set(2, Smi::FromEnum(language_mode_)); |
| array->set(3, Smi::FromInt(position_)); |
| array->set_map(ReadOnlyRoots(isolate).fixed_cow_array_map()); |
| return array; |
| } |
| |
| private: |
| Handle<String> source_; |
| Handle<SharedFunctionInfo> shared_; |
| LanguageMode language_mode_; |
| int position_; |
| }; |
| |
| v8::Promise::PromiseState JSPromise::status() const { |
| int value = flags() & kStatusMask; |
| DCHECK(value == 0 || value == 1 || value == 2); |
| return static_cast<v8::Promise::PromiseState>(value); |
| } |
| |
| void JSPromise::set_status(Promise::PromiseState status) { |
| int value = flags() & ~kStatusMask; |
| set_flags(value | status); |
| } |
| |
| // static |
| const char* JSPromise::Status(v8::Promise::PromiseState status) { |
| switch (status) { |
| case v8::Promise::kFulfilled: |
| return "resolved"; |
| case v8::Promise::kPending: |
| return "pending"; |
| case v8::Promise::kRejected: |
| return "rejected"; |
| } |
| UNREACHABLE(); |
| } |
| |
| int JSPromise::async_task_id() const { |
| return AsyncTaskIdField::decode(flags()); |
| } |
| |
| void JSPromise::set_async_task_id(int id) { |
| set_flags(AsyncTaskIdField::update(flags(), id)); |
| } |
| |
| // static |
| Handle<Object> JSPromise::Fulfill(Handle<JSPromise> promise, |
| Handle<Object> value) { |
| Isolate* const isolate = promise->GetIsolate(); |
| |
| // 1. Assert: The value of promise.[[PromiseState]] is "pending". |
| CHECK_EQ(Promise::kPending, promise->status()); |
| |
| // 2. Let reactions be promise.[[PromiseFulfillReactions]]. |
| Handle<Object> reactions(promise->reactions(), isolate); |
| |
| // 3. Set promise.[[PromiseResult]] to value. |
| // 4. Set promise.[[PromiseFulfillReactions]] to undefined. |
| // 5. Set promise.[[PromiseRejectReactions]] to undefined. |
| promise->set_reactions_or_result(*value); |
| |
| // 6. Set promise.[[PromiseState]] to "fulfilled". |
| promise->set_status(Promise::kFulfilled); |
| |
| // 7. Return TriggerPromiseReactions(reactions, value). |
| return TriggerPromiseReactions(isolate, reactions, value, |
| PromiseReaction::kFulfill); |
| } |
| |
| // static |
| Handle<Object> JSPromise::Reject(Handle<JSPromise> promise, |
| Handle<Object> reason, bool debug_event) { |
| Isolate* const isolate = promise->GetIsolate(); |
| |
| if (debug_event) isolate->debug()->OnPromiseReject(promise, reason); |
| isolate->RunPromiseHook(PromiseHookType::kResolve, promise, |
| isolate->factory()->undefined_value()); |
| |
| // 1. Assert: The value of promise.[[PromiseState]] is "pending". |
| CHECK_EQ(Promise::kPending, promise->status()); |
| |
| // 2. Let reactions be promise.[[PromiseRejectReactions]]. |
| Handle<Object> reactions(promise->reactions(), isolate); |
| |
| // 3. Set promise.[[PromiseResult]] to reason. |
| // 4. Set promise.[[PromiseFulfillReactions]] to undefined. |
| // 5. Set promise.[[PromiseRejectReactions]] to undefined. |
| promise->set_reactions_or_result(*reason); |
| |
| // 6. Set promise.[[PromiseState]] to "rejected". |
| promise->set_status(Promise::kRejected); |
| |
| // 7. If promise.[[PromiseIsHandled]] is false, perform |
| // HostPromiseRejectionTracker(promise, "reject"). |
| if (!promise->has_handler()) { |
| isolate->ReportPromiseReject(promise, reason, kPromiseRejectWithNoHandler); |
| } |
| |
| // 8. Return TriggerPromiseReactions(reactions, reason). |
| return TriggerPromiseReactions(isolate, reactions, reason, |
| PromiseReaction::kReject); |
| } |
| |
| // static |
| MaybeHandle<Object> JSPromise::Resolve(Handle<JSPromise> promise, |
| Handle<Object> resolution) { |
| Isolate* const isolate = promise->GetIsolate(); |
| |
| isolate->RunPromiseHook(PromiseHookType::kResolve, promise, |
| isolate->factory()->undefined_value()); |
| |
| // 6. If SameValue(resolution, promise) is true, then |
| if (promise.is_identical_to(resolution)) { |
| // a. Let selfResolutionError be a newly created TypeError object. |
| Handle<Object> self_resolution_error = isolate->factory()->NewTypeError( |
| MessageTemplate::kPromiseCyclic, resolution); |
| // b. Return RejectPromise(promise, selfResolutionError). |
| return Reject(promise, self_resolution_error); |
| } |
| |
| // 7. If Type(resolution) is not Object, then |
| if (!resolution->IsJSReceiver()) { |
| // a. Return FulfillPromise(promise, resolution). |
| return Fulfill(promise, resolution); |
| } |
| |
| // 8. Let then be Get(resolution, "then"). |
| MaybeHandle<Object> then; |
| if (isolate->IsPromiseThenLookupChainIntact( |
| Handle<JSReceiver>::cast(resolution))) { |
| // We can skip the "then" lookup on {resolution} if its [[Prototype]] |
| // is the (initial) Promise.prototype and the Promise#then protector |
| // is intact, as that guards the lookup path for the "then" property |
| // on JSPromise instances which have the (initial) %PromisePrototype%. |
| then = isolate->promise_then(); |
| } else { |
| then = |
| JSReceiver::GetProperty(isolate, Handle<JSReceiver>::cast(resolution), |
| isolate->factory()->then_string()); |
| } |
| |
| // 9. If then is an abrupt completion, then |
| Handle<Object> then_action; |
| if (!then.ToHandle(&then_action)) { |
| // a. Return RejectPromise(promise, then.[[Value]]). |
| Handle<Object> reason(isolate->pending_exception(), isolate); |
| isolate->clear_pending_exception(); |
| return Reject(promise, reason, false); |
| } |
| |
| // 10. Let thenAction be then.[[Value]]. |
| // 11. If IsCallable(thenAction) is false, then |
| if (!then_action->IsCallable()) { |
| // a. Return FulfillPromise(promise, resolution). |
| return Fulfill(promise, resolution); |
| } |
| |
| // 12. Perform EnqueueJob("PromiseJobs", PromiseResolveThenableJob, |
| // «promise, resolution, thenAction»). |
| Handle<PromiseResolveThenableJobTask> task = |
| isolate->factory()->NewPromiseResolveThenableJobTask( |
| promise, Handle<JSReceiver>::cast(then_action), |
| Handle<JSReceiver>::cast(resolution), isolate->native_context()); |
| if (isolate->debug()->is_active() && resolution->IsJSPromise()) { |
| // Mark the dependency of the new {promise} on the {resolution}. |
| Object::SetProperty(isolate, resolution, |
| isolate->factory()->promise_handled_by_symbol(), |
| promise) |
| .Check(); |
| } |
| isolate->native_context()->microtask_queue()->EnqueueMicrotask(*task); |
| |
| // 13. Return undefined. |
| return isolate->factory()->undefined_value(); |
| } |
| |
| // static |
| Handle<Object> JSPromise::TriggerPromiseReactions(Isolate* isolate, |
| Handle<Object> reactions, |
| Handle<Object> argument, |
| PromiseReaction::Type type) { |
| CHECK(reactions->IsSmi() || reactions->IsPromiseReaction()); |
| |
| // We need to reverse the {reactions} here, since we record them |
| // on the JSPromise in the reverse order. |
| { |
| DisallowHeapAllocation no_gc; |
| Object current = *reactions; |
| Object reversed = Smi::kZero; |
| while (!current->IsSmi()) { |
| Object next = PromiseReaction::cast(current)->next(); |
| PromiseReaction::cast(current)->set_next(reversed); |
| reversed = current; |
| current = next; |
| } |
| reactions = handle(reversed, isolate); |
| } |
| |
| // Morph the {reactions} into PromiseReactionJobTasks |
| // and push them onto the microtask queue. |
| while (!reactions->IsSmi()) { |
| Handle<HeapObject> task = Handle<HeapObject>::cast(reactions); |
| Handle<PromiseReaction> reaction = Handle<PromiseReaction>::cast(task); |
| reactions = handle(reaction->next(), isolate); |
| |
| Handle<NativeContext> handler_context; |
| |
| STATIC_ASSERT(static_cast<int>(PromiseReaction::kSize) == |
| static_cast<int>(PromiseReactionJobTask::kSize)); |
| if (type == PromiseReaction::kFulfill) { |
| Handle<HeapObject> handler = handle(reaction->fulfill_handler(), isolate); |
| if (handler->IsJSReceiver()) { |
| JSReceiver::GetContextForMicrotask(Handle<JSReceiver>::cast(handler)) |
| .ToHandle(&handler_context); |
| } |
| if (handler_context.is_null()) |
| handler_context = isolate->native_context(); |
| |
| task->synchronized_set_map( |
| ReadOnlyRoots(isolate).promise_fulfill_reaction_job_task_map()); |
| Handle<PromiseFulfillReactionJobTask>::cast(task)->set_argument( |
| *argument); |
| Handle<PromiseFulfillReactionJobTask>::cast(task)->set_context( |
| *handler_context); |
| STATIC_ASSERT( |
| static_cast<int>(PromiseReaction::kFulfillHandlerOffset) == |
| static_cast<int>(PromiseFulfillReactionJobTask::kHandlerOffset)); |
| STATIC_ASSERT( |
| static_cast<int>(PromiseReaction::kPromiseOrCapabilityOffset) == |
| static_cast<int>( |
| PromiseFulfillReactionJobTask::kPromiseOrCapabilityOffset)); |
| } else { |
| DisallowHeapAllocation no_gc; |
| Handle<HeapObject> handler = handle(reaction->reject_handler(), isolate); |
| if (handler->IsJSReceiver()) { |
| JSReceiver::GetContextForMicrotask(Handle<JSReceiver>::cast(handler)) |
| .ToHandle(&handler_context); |
| } |
| if (handler_context.is_null()) |
| handler_context = isolate->native_context(); |
| task->synchronized_set_map( |
| ReadOnlyRoots(isolate).promise_reject_reaction_job_task_map()); |
| Handle<PromiseRejectReactionJobTask>::cast(task)->set_argument(*argument); |
| Handle<PromiseRejectReactionJobTask>::cast(task)->set_context( |
| *handler_context); |
| Handle<PromiseRejectReactionJobTask>::cast(task)->set_handler(*handler); |
| STATIC_ASSERT( |
| static_cast<int>(PromiseReaction::kPromiseOrCapabilityOffset) == |
| static_cast<int>( |
| PromiseRejectReactionJobTask::kPromiseOrCapabilityOffset)); |
| } |
| |
| handler_context->microtask_queue()->EnqueueMicrotask( |
| *Handle<PromiseReactionJobTask>::cast(task)); |
| } |
| |
| return isolate->factory()->undefined_value(); |
| } |
| |
| namespace { |
| |
| constexpr JSRegExp::Flag kCharFlagValues[] = { |
| JSRegExp::kGlobal, // g |
| JSRegExp::kInvalid, // h |
| JSRegExp::kIgnoreCase, // i |
| JSRegExp::kInvalid, // j |
| JSRegExp::kInvalid, // k |
| JSRegExp::kInvalid, // l |
| JSRegExp::kMultiline, // m |
| JSRegExp::kInvalid, // n |
| JSRegExp::kInvalid, // o |
| JSRegExp::kInvalid, // p |
| JSRegExp::kInvalid, // q |
| JSRegExp::kInvalid, // r |
| JSRegExp::kDotAll, // s |
| JSRegExp::kInvalid, // t |
| JSRegExp::kUnicode, // u |
| JSRegExp::kInvalid, // v |
| JSRegExp::kInvalid, // w |
| JSRegExp::kInvalid, // x |
| JSRegExp::kSticky, // y |
| }; |
| |
| constexpr JSRegExp::Flag CharToFlag(uc16 flag_char) { |
| return (flag_char < 'g' || flag_char > 'y') |
| ? JSRegExp::kInvalid |
| : kCharFlagValues[flag_char - 'g']; |
| } |
| |
| JSRegExp::Flags RegExpFlagsFromString(Isolate* isolate, Handle<String> flags, |
| bool* success) { |
| STATIC_ASSERT(CharToFlag('g') == JSRegExp::kGlobal); |
| STATIC_ASSERT(CharToFlag('i') == JSRegExp::kIgnoreCase); |
| STATIC_ASSERT(CharToFlag('m') == JSRegExp::kMultiline); |
| STATIC_ASSERT(CharToFlag('s') == JSRegExp::kDotAll); |
| STATIC_ASSERT(CharToFlag('u') == JSRegExp::kUnicode); |
| STATIC_ASSERT(CharToFlag('y') == JSRegExp::kSticky); |
| |
| int length = flags->length(); |
| if (length == 0) { |
| *success = true; |
| return JSRegExp::kNone; |
| } |
| // A longer flags string cannot be valid. |
| if (length > JSRegExp::FlagCount()) return JSRegExp::Flags(0); |
| // Initialize {value} to {kInvalid} to allow 2-in-1 duplicate/invalid check. |
| JSRegExp::Flags value = JSRegExp::kInvalid; |
| if (flags->IsSeqOneByteString()) { |
| DisallowHeapAllocation no_gc; |
| SeqOneByteString seq_flags = SeqOneByteString::cast(*flags); |
| for (int i = 0; i < length; i++) { |
| JSRegExp::Flag flag = CharToFlag(seq_flags.SeqOneByteStringGet(i)); |
| // Duplicate or invalid flag. |
| if (value & flag) return JSRegExp::Flags(0); |
| value |= flag; |
| } |
| } else { |
| flags = String::Flatten(isolate, flags); |
| DisallowHeapAllocation no_gc; |
| String::FlatContent flags_content = flags->GetFlatContent(no_gc); |
| for (int i = 0; i < length; i++) { |
| JSRegExp::Flag flag = CharToFlag(flags_content.Get(i)); |
| // Duplicate or invalid flag. |
| if (value & flag) return JSRegExp::Flags(0); |
| value |= flag; |
| } |
| } |
| *success = true; |
| // Drop the initially set {kInvalid} bit. |
| value ^= JSRegExp::kInvalid; |
| return value; |
| } |
| |
| } // namespace |
| |
| |
| // static |
| MaybeHandle<JSRegExp> JSRegExp::New(Isolate* isolate, Handle<String> pattern, |
| Flags flags) { |
| Handle<JSFunction> constructor = isolate->regexp_function(); |
| Handle<JSRegExp> regexp = |
| Handle<JSRegExp>::cast(isolate->factory()->NewJSObject(constructor)); |
| |
| return JSRegExp::Initialize(regexp, pattern, flags); |
| } |
| |
| |
| // static |
| Handle<JSRegExp> JSRegExp::Copy(Handle<JSRegExp> regexp) { |
| Isolate* const isolate = regexp->GetIsolate(); |
| return Handle<JSRegExp>::cast(isolate->factory()->CopyJSObject(regexp)); |
| } |
| |
| namespace { |
| |
| template <typename Char> |
| int CountRequiredEscapes(Handle<String> source) { |
| DisallowHeapAllocation no_gc; |
| int escapes = 0; |
| Vector<const Char> src = source->GetCharVector<Char>(no_gc); |
| for (int i = 0; i < src.length(); i++) { |
| const Char c = src[i]; |
| if (c == '\\') { |
| // Escape. Skip next character; |
| i++; |
| } else if (c == '/') { |
| // Not escaped forward-slash needs escape. |
| escapes++; |
| } else if (c == '\n') { |
| escapes++; |
| } else if (c == '\r') { |
| escapes++; |
| } else if (static_cast<int>(c) == 0x2028) { |
| escapes += std::strlen("\\u2028") - 1; |
| } else if (static_cast<int>(c) == 0x2029) { |
| escapes += std::strlen("\\u2029") - 1; |
| } else { |
| DCHECK(!unibrow::IsLineTerminator(static_cast<unibrow::uchar>(c))); |
| } |
| } |
| return escapes; |
| } |
| |
| template <typename Char> |
| void WriteStringToCharVector(Vector<Char> v, int* d, const char* string) { |
| int s = 0; |
| while (string[s] != '\0') v[(*d)++] = string[s++]; |
| } |
| |
| template <typename Char, typename StringType> |
| Handle<StringType> WriteEscapedRegExpSource(Handle<String> source, |
| Handle<StringType> result) { |
| DisallowHeapAllocation no_gc; |
| Vector<const Char> src = source->GetCharVector<Char>(no_gc); |
| Vector<Char> dst(result->GetChars(no_gc), result->length()); |
| int s = 0; |
| int d = 0; |
| // TODO(v8:1982): Fully implement |
| // https://tc39.github.io/ecma262/#sec-escaperegexppattern |
| while (s < src.length()) { |
| if (src[s] == '\\') { |
| // Escape. Copy this and next character. |
| dst[d++] = src[s++]; |
| if (s == src.length()) break; |
| } else if (src[s] == '/') { |
| // Not escaped forward-slash needs escape. |
| dst[d++] = '\\'; |
| } else if (src[s] == '\n') { |
| WriteStringToCharVector(dst, &d, "\\n"); |
| s++; |
| continue; |
| } else if (src[s] == '\r') { |
| WriteStringToCharVector(dst, &d, "\\r"); |
| s++; |
| continue; |
| } else if (static_cast<int>(src[s]) == 0x2028) { |
| WriteStringToCharVector(dst, &d, "\\u2028"); |
| s++; |
| continue; |
| } else if (static_cast<int>(src[s]) == 0x2029) { |
| WriteStringToCharVector(dst, &d, "\\u2029"); |
| s++; |
| continue; |
| } |
| dst[d++] = src[s++]; |
| } |
| DCHECK_EQ(result->length(), d); |
| return result; |
| } |
| |
| MaybeHandle<String> EscapeRegExpSource(Isolate* isolate, |
| Handle<String> source) { |
| DCHECK(source->IsFlat()); |
| if (source->length() == 0) return isolate->factory()->query_colon_string(); |
| bool one_byte = String::IsOneByteRepresentationUnderneath(*source); |
| int escapes = one_byte ? CountRequiredEscapes<uint8_t>(source) |
| : CountRequiredEscapes<uc16>(source); |
| if (escapes == 0) return source; |
| int length = source->length() + escapes; |
| if (one_byte) { |
| Handle<SeqOneByteString> result; |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, result, |
| isolate->factory()->NewRawOneByteString(length), |
| String); |
| return WriteEscapedRegExpSource<uint8_t>(source, result); |
| } else { |
| Handle<SeqTwoByteString> result; |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, result, |
| isolate->factory()->NewRawTwoByteString(length), |
| String); |
| return WriteEscapedRegExpSource<uc16>(source, result); |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| MaybeHandle<JSRegExp> JSRegExp::Initialize(Handle<JSRegExp> regexp, |
| Handle<String> source, |
| Handle<String> flags_string) { |
| Isolate* isolate = regexp->GetIsolate(); |
| bool success = false; |
| Flags flags = RegExpFlagsFromString(isolate, flags_string, &success); |
| if (!success) { |
| THROW_NEW_ERROR( |
| isolate, |
| NewSyntaxError(MessageTemplate::kInvalidRegExpFlags, flags_string), |
| JSRegExp); |
| } |
| return Initialize(regexp, source, flags); |
| } |
| |
| |
| // static |
| MaybeHandle<JSRegExp> JSRegExp::Initialize(Handle<JSRegExp> regexp, |
| Handle<String> source, Flags flags) { |
| Isolate* isolate = regexp->GetIsolate(); |
| Factory* factory = isolate->factory(); |
| // If source is the empty string we set it to "(?:)" instead as |
| // suggested by ECMA-262, 5th, section 15.10.4.1. |
| if (source->length() == 0) source = factory->query_colon_string(); |
| |
| source = String::Flatten(isolate, source); |
| |
| Handle<String> escaped_source; |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, escaped_source, |
| EscapeRegExpSource(isolate, source), JSRegExp); |
| |
| RETURN_ON_EXCEPTION( |
| isolate, RegExpImpl::Compile(isolate, regexp, source, flags), JSRegExp); |
| |
| regexp->set_source(*escaped_source); |
| regexp->set_flags(Smi::FromInt(flags)); |
| |
| Map map = regexp->map(); |
| Object constructor = map->GetConstructor(); |
| if (constructor->IsJSFunction() && |
| JSFunction::cast(constructor)->initial_map() == map) { |
| // If we still have the original map, set in-object properties directly. |
| regexp->InObjectPropertyAtPut(JSRegExp::kLastIndexFieldIndex, Smi::kZero, |
| SKIP_WRITE_BARRIER); |
| } else { |
| // Map has changed, so use generic, but slower, method. |
| RETURN_ON_EXCEPTION( |
| isolate, |
| Object::SetProperty(isolate, regexp, factory->lastIndex_string(), |
| Handle<Smi>(Smi::zero(), isolate)), |
| JSRegExp); |
| } |
| |
| return regexp; |
| } |
| |
| |
| // RegExpKey carries the source and flags of a regular expression as key. |
| class RegExpKey : public HashTableKey { |
| public: |
| RegExpKey(Handle<String> string, JSRegExp::Flags flags) |
| : HashTableKey( |
| CompilationCacheShape::RegExpHash(*string, Smi::FromInt(flags))), |
| string_(string), |
| flags_(Smi::FromInt(flags)) {} |
| |
| // Rather than storing the key in the hash table, a pointer to the |
| // stored value is stored where the key should be. IsMatch then |
| // compares the search key to the found object, rather than comparing |
| // a key to a key. |
| bool IsMatch(Object obj) override { |
| FixedArray val = FixedArray::cast(obj); |
| return string_->Equals(String::cast(val->get(JSRegExp::kSourceIndex))) |
| && (flags_ == val->get(JSRegExp::kFlagsIndex)); |
| } |
| |
| Handle<String> string_; |
| Smi flags_; |
| }; |
| |
| Handle<String> OneByteStringKey::AsHandle(Isolate* isolate) { |
| return isolate->factory()->NewOneByteInternalizedString(string_, HashField()); |
| } |
| |
| Handle<String> TwoByteStringKey::AsHandle(Isolate* isolate) { |
| return isolate->factory()->NewTwoByteInternalizedString(string_, HashField()); |
| } |
| |
| Handle<String> SeqOneByteSubStringKey::AsHandle(Isolate* isolate) { |
| return isolate->factory()->NewOneByteInternalizedSubString( |
| string_, from_, length_, HashField()); |
| } |
| |
| bool SeqOneByteSubStringKey::IsMatch(Object string) { |
| DisallowHeapAllocation no_gc; |
| Vector<const uint8_t> chars(string_->GetChars(no_gc) + from_, length_); |
| return String::cast(string)->IsOneByteEqualTo(chars); |
| } |
| |
| // InternalizedStringKey carries a string/internalized-string object as key. |
| class InternalizedStringKey : public StringTableKey { |
| public: |
| explicit InternalizedStringKey(Handle<String> string) |
| : StringTableKey(0), string_(string) { |
| DCHECK(!string->IsInternalizedString()); |
| DCHECK(string->IsFlat()); |
| // Make sure hash_field is computed. |
| string->Hash(); |
| set_hash_field(string->hash_field()); |
| } |
| |
| bool IsMatch(Object string) override { |
| return string_->SlowEquals(String::cast(string)); |
| } |
| |
| Handle<String> AsHandle(Isolate* isolate) override { |
| // Internalize the string if possible. |
| MaybeHandle<Map> maybe_map = |
| isolate->factory()->InternalizedStringMapForString(string_); |
| Handle<Map> map; |
| if (maybe_map.ToHandle(&map)) { |
| string_->set_map_no_write_barrier(*map); |
| DCHECK(string_->IsInternalizedString()); |
| return string_; |
| } |
| if (FLAG_thin_strings) { |
| // External strings get special treatment, to avoid copying their |
| // contents. |
| if (string_->IsExternalOneByteString()) { |
| return isolate->factory() |
| ->InternalizeExternalString<ExternalOneByteString>(string_); |
| } else if (string_->IsExternalTwoByteString()) { |
| return isolate->factory() |
| ->InternalizeExternalString<ExternalTwoByteString>(string_); |
| } |
| } |
| // Otherwise allocate a new internalized string. |
| return isolate->factory()->NewInternalizedStringImpl( |
| string_, string_->length(), string_->hash_field()); |
| } |
| |
| private: |
| Handle<String> string_; |
| }; |
| |
| template <typename Derived, typename Shape> |
| void HashTable<Derived, Shape>::IteratePrefix(ObjectVisitor* v) { |
| BodyDescriptorBase::IteratePointers(*this, 0, kElementsStartOffset, v); |
| } |
| |
| template <typename Derived, typename Shape> |
| void HashTable<Derived, Shape>::IterateElements(ObjectVisitor* v) { |
| BodyDescriptorBase::IteratePointers(*this, kElementsStartOffset, |
| SizeFor(length()), v); |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> HashTable<Derived, Shape>::New( |
| Isolate* isolate, int at_least_space_for, PretenureFlag pretenure, |
| MinimumCapacity capacity_option) { |
| DCHECK_LE(0, at_least_space_for); |
| DCHECK_IMPLIES(capacity_option == USE_CUSTOM_MINIMUM_CAPACITY, |
| base::bits::IsPowerOfTwo(at_least_space_for)); |
| |
| int capacity = (capacity_option == USE_CUSTOM_MINIMUM_CAPACITY) |
| ? at_least_space_for |
| : ComputeCapacity(at_least_space_for); |
| if (capacity > HashTable::kMaxCapacity) { |
| isolate->heap()->FatalProcessOutOfMemory("invalid table size"); |
| } |
| return NewInternal(isolate, capacity, pretenure); |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> HashTable<Derived, Shape>::NewInternal( |
| Isolate* isolate, int capacity, PretenureFlag pretenure) { |
| Factory* factory = isolate->factory(); |
| int length = EntryToIndex(capacity); |
| RootIndex map_root_index = Shape::GetMapRootIndex(); |
| Handle<FixedArray> array = |
| factory->NewFixedArrayWithMap(map_root_index, length, pretenure); |
| Handle<Derived> table = Handle<Derived>::cast(array); |
| |
| table->SetNumberOfElements(0); |
| table->SetNumberOfDeletedElements(0); |
| table->SetCapacity(capacity); |
| return table; |
| } |
| |
| template <typename Derived, typename Shape> |
| void HashTable<Derived, Shape>::Rehash(ReadOnlyRoots roots, Derived new_table) { |
| DisallowHeapAllocation no_gc; |
| WriteBarrierMode mode = new_table->GetWriteBarrierMode(no_gc); |
| |
| DCHECK_LT(NumberOfElements(), new_table->Capacity()); |
| |
| // Copy prefix to new array. |
| for (int i = kPrefixStartIndex; i < kElementsStartIndex; i++) { |
| new_table->set(i, get(i), mode); |
| } |
| |
| // Rehash the elements. |
| int capacity = this->Capacity(); |
| for (int i = 0; i < capacity; i++) { |
| uint32_t from_index = EntryToIndex(i); |
| Object k = this->get(from_index); |
| if (!Shape::IsLive(roots, k)) continue; |
| uint32_t hash = Shape::HashForObject(roots, k); |
| uint32_t insertion_index = |
| EntryToIndex(new_table->FindInsertionEntry(hash)); |
| for (int j = 0; j < Shape::kEntrySize; j++) { |
| new_table->set(insertion_index + j, get(from_index + j), mode); |
| } |
| } |
| new_table->SetNumberOfElements(NumberOfElements()); |
| new_table->SetNumberOfDeletedElements(0); |
| } |
| |
| template <typename Derived, typename Shape> |
| uint32_t HashTable<Derived, Shape>::EntryForProbe(ReadOnlyRoots roots, Object k, |
| int probe, |
| uint32_t expected) { |
| uint32_t hash = Shape::HashForObject(roots, k); |
| uint32_t capacity = this->Capacity(); |
| uint32_t entry = FirstProbe(hash, capacity); |
| for (int i = 1; i < probe; i++) { |
| if (entry == expected) return expected; |
| entry = NextProbe(entry, i, capacity); |
| } |
| return entry; |
| } |
| |
| template <typename Derived, typename Shape> |
| void HashTable<Derived, Shape>::Swap(uint32_t entry1, uint32_t entry2, |
| WriteBarrierMode mode) { |
| int index1 = EntryToIndex(entry1); |
| int index2 = EntryToIndex(entry2); |
| Object temp[Shape::kEntrySize]; |
| for (int j = 0; j < Shape::kEntrySize; j++) { |
| temp[j] = get(index1 + j); |
| } |
| for (int j = 0; j < Shape::kEntrySize; j++) { |
| set(index1 + j, get(index2 + j), mode); |
| } |
| for (int j = 0; j < Shape::kEntrySize; j++) { |
| set(index2 + j, temp[j], mode); |
| } |
| } |
| |
| template <typename Derived, typename Shape> |
| void HashTable<Derived, Shape>::Rehash(ReadOnlyRoots roots) { |
| DisallowHeapAllocation no_gc; |
| WriteBarrierMode mode = GetWriteBarrierMode(no_gc); |
| uint32_t capacity = Capacity(); |
| bool done = false; |
| for (int probe = 1; !done; probe++) { |
| // All elements at entries given by one of the first _probe_ probes |
| // are placed correctly. Other elements might need to be moved. |
| done = true; |
| for (uint32_t current = 0; current < capacity; current++) { |
| Object current_key = KeyAt(current); |
| if (!Shape::IsLive(roots, current_key)) continue; |
| uint32_t target = EntryForProbe(roots, current_key, probe, current); |
| if (current == target) continue; |
| Object target_key = KeyAt(target); |
| if (!Shape::IsLive(roots, target_key) || |
| EntryForProbe(roots, target_key, probe, target) != target) { |
| // Put the current element into the correct position. |
| Swap(current, target, mode); |
| // The other element will be processed on the next iteration. |
| current--; |
| } else { |
| // The place for the current element is occupied. Leave the element |
| // for the next probe. |
| done = false; |
| } |
| } |
| } |
| // Wipe deleted entries. |
| Object the_hole = roots.the_hole_value(); |
| Object undefined = roots.undefined_value(); |
| for (uint32_t current = 0; current < capacity; current++) { |
| if (KeyAt(current) == the_hole) { |
| set(EntryToIndex(current) + kEntryKeyIndex, undefined); |
| } |
| } |
| SetNumberOfDeletedElements(0); |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> HashTable<Derived, Shape>::EnsureCapacity( |
| Isolate* isolate, Handle<Derived> table, int n, PretenureFlag pretenure) { |
| if (table->HasSufficientCapacityToAdd(n)) return table; |
| |
| int capacity = table->Capacity(); |
| int new_nof = table->NumberOfElements() + n; |
| |
| const int kMinCapacityForPretenure = 256; |
| bool should_pretenure = |
| pretenure == TENURED || ((capacity > kMinCapacityForPretenure) && |
| !Heap::InYoungGeneration(*table)); |
| Handle<Derived> new_table = HashTable::New( |
| isolate, new_nof, should_pretenure ? TENURED : NOT_TENURED); |
| |
| table->Rehash(ReadOnlyRoots(isolate), *new_table); |
| return new_table; |
| } |
| |
| template bool |
| HashTable<NameDictionary, NameDictionaryShape>::HasSufficientCapacityToAdd(int); |
| |
| template <typename Derived, typename Shape> |
| bool HashTable<Derived, Shape>::HasSufficientCapacityToAdd( |
| int number_of_additional_elements) { |
| int capacity = Capacity(); |
| int nof = NumberOfElements() + number_of_additional_elements; |
| int nod = NumberOfDeletedElements(); |
| // Return true if: |
| // 50% is still free after adding number_of_additional_elements elements and |
| // at most 50% of the free elements are deleted elements. |
| if ((nof < capacity) && ((nod <= (capacity - nof) >> 1))) { |
| int needed_free = nof >> 1; |
| if (nof + needed_free <= capacity) return true; |
| } |
| return false; |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> HashTable<Derived, Shape>::Shrink(Isolate* isolate, |
| Handle<Derived> table, |
| int additionalCapacity) { |
| int capacity = table->Capacity(); |
| int nof = table->NumberOfElements(); |
| |
| // Shrink to fit the number of elements if only a quarter of the |
| // capacity is filled with elements. |
| if (nof > (capacity >> 2)) return table; |
| // Allocate a new dictionary with room for at least the current number of |
| // elements + {additionalCapacity}. The allocation method will make sure that |
| // there is extra room in the dictionary for additions. Don't go lower than |
| // room for {kMinShrinkCapacity} elements. |
| int at_least_room_for = nof + additionalCapacity; |
| int new_capacity = ComputeCapacity(at_least_room_for); |
| if (new_capacity < Derived::kMinShrinkCapacity) return table; |
| if (new_capacity == capacity) return table; |
| |
| const int kMinCapacityForPretenure = 256; |
| bool pretenure = (at_least_room_for > kMinCapacityForPretenure) && |
| !Heap::InYoungGeneration(*table); |
| Handle<Derived> new_table = |
| HashTable::New(isolate, new_capacity, pretenure ? TENURED : NOT_TENURED, |
| USE_CUSTOM_MINIMUM_CAPACITY); |
| |
| table->Rehash(ReadOnlyRoots(isolate), *new_table); |
| return new_table; |
| } |
| |
| template <typename Derived, typename Shape> |
| uint32_t HashTable<Derived, Shape>::FindInsertionEntry(uint32_t hash) { |
| uint32_t capacity = Capacity(); |
| uint32_t entry = FirstProbe(hash, capacity); |
| uint32_t count = 1; |
| // EnsureCapacity will guarantee the hash table is never full. |
| ReadOnlyRoots roots = GetReadOnlyRoots(); |
| while (true) { |
| if (!Shape::IsLive(roots, KeyAt(entry))) break; |
| entry = NextProbe(entry, count++, capacity); |
| } |
| return entry; |
| } |
| |
| |
| // This class is used for looking up two character strings in the string table. |
| // If we don't have a hit we don't want to waste much time so we unroll the |
| // string hash calculation loop here for speed. Doesn't work if the two |
| // characters form a decimal integer, since such strings have a different hash |
| // algorithm. |
| class TwoCharHashTableKey : public StringTableKey { |
| public: |
| TwoCharHashTableKey(uint16_t c1, uint16_t c2, uint64_t seed) |
| : StringTableKey(ComputeHashField(c1, c2, seed)), c1_(c1), c2_(c2) {} |
| |
| bool IsMatch(Object o) override { |
| String other = String::cast(o); |
| if (other->length() != 2) return false; |
| if (other->Get(0) != c1_) return false; |
| return other->Get(1) == c2_; |
| } |
| |
| Handle<String> AsHandle(Isolate* isolate) override { |
| // The TwoCharHashTableKey is only used for looking in the string |
| // table, not for adding to it. |
| UNREACHABLE(); |
| } |
| |
| private: |
| uint32_t ComputeHashField(uint16_t c1, uint16_t c2, uint64_t seed) { |
| // Char 1. |
| uint32_t hash = static_cast<uint32_t>(seed); |
| hash += c1; |
| hash += hash << 10; |
| hash ^= hash >> 6; |
| // Char 2. |
| hash += c2; |
| hash += hash << 10; |
| hash ^= hash >> 6; |
| // GetHash. |
| hash += hash << 3; |
| hash ^= hash >> 11; |
| hash += hash << 15; |
| if ((hash & String::kHashBitMask) == 0) hash = StringHasher::kZeroHash; |
| hash = (hash << String::kHashShift) | String::kIsNotArrayIndexMask; |
| #ifdef DEBUG |
| // If this assert fails then we failed to reproduce the two-character |
| // version of the string hashing algorithm above. One reason could be |
| // that we were passed two digits as characters, since the hash |
| // algorithm is different in that case. |
| uint16_t chars[2] = {c1, c2}; |
| uint32_t check_hash = StringHasher::HashSequentialString(chars, 2, seed); |
| DCHECK_EQ(hash, check_hash); |
| #endif |
| return hash; |
| } |
| |
| uint16_t c1_; |
| uint16_t c2_; |
| }; |
| |
| MaybeHandle<String> StringTable::LookupTwoCharsStringIfExists( |
| Isolate* isolate, |
| uint16_t c1, |
| uint16_t c2) { |
| TwoCharHashTableKey key(c1, c2, HashSeed(isolate)); |
| Handle<StringTable> string_table = isolate->factory()->string_table(); |
| int entry = string_table->FindEntry(isolate, &key); |
| if (entry == kNotFound) return MaybeHandle<String>(); |
| |
| Handle<String> result(String::cast(string_table->KeyAt(entry)), isolate); |
| DCHECK(StringShape(*result).IsInternalized()); |
| DCHECK_EQ(result->Hash(), key.Hash()); |
| return result; |
| } |
| |
| void StringTable::EnsureCapacityForDeserialization(Isolate* isolate, |
| int expected) { |
| Handle<StringTable> table = isolate->factory()->string_table(); |
| // We need a key instance for the virtual hash function. |
| table = StringTable::EnsureCapacity(isolate, table, expected); |
| isolate->heap()->SetRootStringTable(*table); |
| } |
| |
| namespace { |
| |
| template <class StringClass> |
| void MigrateExternalStringResource(Isolate* isolate, String from, String to) { |
| StringClass cast_from = StringClass::cast(from); |
| StringClass cast_to = StringClass::cast(to); |
| const typename StringClass::Resource* to_resource = cast_to->resource(); |
| if (to_resource == nullptr) { |
| // |to| is a just-created internalized copy of |from|. Migrate the resource. |
| cast_to->SetResource(isolate, cast_from->resource()); |
| // Zap |from|'s resource pointer to reflect the fact that |from| has |
| // relinquished ownership of its resource. |
| isolate->heap()->UpdateExternalString( |
| from, ExternalString::cast(from)->ExternalPayloadSize(), 0); |
| cast_from->SetResource(isolate, nullptr); |
| } else if (to_resource != cast_from->resource()) { |
| // |to| already existed and has its own resource. Finalize |from|. |
| isolate->heap()->FinalizeExternalString(from); |
| } |
| } |
| |
| void MakeStringThin(String string, String internalized, Isolate* isolate) { |
| DCHECK_NE(string, internalized); |
| DCHECK(internalized->IsInternalizedString()); |
| |
| if (string->IsExternalString()) { |
| if (internalized->IsExternalOneByteString()) { |
| MigrateExternalStringResource<ExternalOneByteString>(isolate, string, |
| internalized); |
| } else if (internalized->IsExternalTwoByteString()) { |
| MigrateExternalStringResource<ExternalTwoByteString>(isolate, string, |
| internalized); |
| } else { |
| // If the external string is duped into an existing non-external |
| // internalized string, free its resource (it's about to be rewritten |
| // into a ThinString below). |
| isolate->heap()->FinalizeExternalString(string); |
| } |
| } |
| |
| DisallowHeapAllocation no_gc; |
| int old_size = string->Size(); |
| isolate->heap()->NotifyObjectLayoutChange(string, old_size, no_gc); |
| bool one_byte = internalized->IsOneByteRepresentation(); |
| Handle<Map> map = one_byte ? isolate->factory()->thin_one_byte_string_map() |
| : isolate->factory()->thin_string_map(); |
| DCHECK_GE(old_size, ThinString::kSize); |
| string->synchronized_set_map(*map); |
| ThinString thin = ThinString::cast(string); |
| thin->set_actual(internalized); |
| Address thin_end = thin->address() + ThinString::kSize; |
| int size_delta = old_size - ThinString::kSize; |
| if (size_delta != 0) { |
| Heap* heap = isolate->heap(); |
| heap->CreateFillerObjectAt(thin_end, size_delta, ClearRecordedSlots::kNo); |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| Handle<String> StringTable::LookupString(Isolate* isolate, |
| Handle<String> string) { |
| string = String::Flatten(isolate, string); |
| if (string->IsInternalizedString()) return string; |
| |
| InternalizedStringKey key(string); |
| Handle<String> result = LookupKey(isolate, &key); |
| |
| if (FLAG_thin_strings) { |
| if (!string->IsInternalizedString()) { |
| MakeStringThin(*string, *result, isolate); |
| } |
| } else { // !FLAG_thin_strings |
| if (string->IsConsString()) { |
| Handle<ConsString> cons = Handle<ConsString>::cast(string); |
| cons->set_first(isolate, *result); |
| cons->set_second(isolate, ReadOnlyRoots(isolate).empty_string()); |
| } else if (string->IsSlicedString()) { |
| STATIC_ASSERT(static_cast<int>(ConsString::kSize) == |
| static_cast<int>(SlicedString::kSize)); |
| DisallowHeapAllocation no_gc; |
| bool one_byte = result->IsOneByteRepresentation(); |
| Handle<Map> map = one_byte |
| ? isolate->factory()->cons_one_byte_string_map() |
| : isolate->factory()->cons_string_map(); |
| string->set_map(*map); |
| Handle<ConsString> cons = Handle<ConsString>::cast(string); |
| cons->set_first(isolate, *result); |
| cons->set_second(isolate, ReadOnlyRoots(isolate).empty_string()); |
| } |
| } |
| return result; |
| } |
| |
| // static |
| Handle<String> StringTable::LookupKey(Isolate* isolate, StringTableKey* key) { |
| Handle<StringTable> table = isolate->factory()->string_table(); |
| int entry = table->FindEntry(isolate, key); |
| |
| // String already in table. |
| if (entry != kNotFound) { |
| return handle(String::cast(table->KeyAt(entry)), isolate); |
| } |
| |
| table = StringTable::CautiousShrink(isolate, table); |
| // Adding new string. Grow table if needed. |
| table = StringTable::EnsureCapacity(isolate, table, 1); |
| isolate->heap()->SetRootStringTable(*table); |
| |
| return AddKeyNoResize(isolate, key); |
| } |
| |
| Handle<String> StringTable::AddKeyNoResize(Isolate* isolate, |
| StringTableKey* key) { |
| Handle<StringTable> table = isolate->factory()->string_table(); |
| DCHECK(table->HasSufficientCapacityToAdd(1)); |
| // Create string object. |
| Handle<String> string = key->AsHandle(isolate); |
| // There must be no attempts to internalize strings that could throw |
| // InvalidStringLength error. |
| CHECK(!string.is_null()); |
| DCHECK(string->HasHashCode()); |
| DCHECK_EQ(table->FindEntry(isolate, key), kNotFound); |
| |
| // Add the new string and return it along with the string table. |
| int entry = table->FindInsertionEntry(key->Hash()); |
| table->set(EntryToIndex(entry), *string); |
| table->ElementAdded(); |
| |
| return Handle<String>::cast(string); |
| } |
| |
| Handle<StringTable> StringTable::CautiousShrink(Isolate* isolate, |
| Handle<StringTable> table) { |
| // Only shrink if the table is very empty to avoid performance penalty. |
| int capacity = table->Capacity(); |
| int nof = table->NumberOfElements(); |
| if (capacity <= StringTable::kMinCapacity) return table; |
| if (nof > (capacity / kMaxEmptyFactor)) return table; |
| // Keep capacity for at least half of the current nof elements. |
| int slack_capacity = nof >> 2; |
| return Shrink(isolate, table, slack_capacity); |
| } |
| |
| namespace { |
| |
| class StringTableNoAllocateKey : public StringTableKey { |
| public: |
| StringTableNoAllocateKey(String string, uint64_t seed) |
| : StringTableKey(0), string_(string) { |
| StringShape shape(string); |
| one_byte_ = shape.HasOnlyOneByteChars(); |
| DCHECK(!shape.IsInternalized()); |
| DCHECK(!shape.IsThin()); |
| int length = string->length(); |
| if (shape.IsCons() && length <= String::kMaxHashCalcLength) { |
| special_flattening_ = true; |
| uint32_t hash_field = 0; |
| if (one_byte_) { |
| if (V8_LIKELY(length <= |
| static_cast<int>(arraysize(one_byte_buffer_)))) { |
| one_byte_content_ = one_byte_buffer_; |
| } else { |
| one_byte_content_ = new uint8_t[length]; |
| } |
| String::WriteToFlat(string, one_byte_content_, 0, length); |
| hash_field = |
| StringHasher::HashSequentialString(one_byte_content_, length, seed); |
| } else { |
| if (V8_LIKELY(length <= |
| static_cast<int>(arraysize(two_byte_buffer_)))) { |
| two_byte_content_ = two_byte_buffer_; |
| } else { |
| two_byte_content_ = new uint16_t[length]; |
| } |
| String::WriteToFlat(string, two_byte_content_, 0, length); |
| hash_field = |
| StringHasher::HashSequentialString(two_byte_content_, length, seed); |
| } |
| string->set_hash_field(hash_field); |
| } else { |
| special_flattening_ = false; |
| one_byte_content_ = nullptr; |
| string->Hash(); |
| } |
| |
| DCHECK(string->HasHashCode()); |
| set_hash_field(string->hash_field()); |
| } |
| |
| ~StringTableNoAllocateKey() override { |
| if (one_byte_) { |
| if (one_byte_content_ != one_byte_buffer_) delete[] one_byte_content_; |
| } else { |
| if (two_byte_content_ != two_byte_buffer_) delete[] two_byte_content_; |
| } |
| } |
| |
| bool IsMatch(Object otherstring) override { |
| String other = String::cast(otherstring); |
| DCHECK(other->IsInternalizedString()); |
| DCHECK(other->IsFlat()); |
| if (Hash() != other->Hash()) return false; |
| int len = string_->length(); |
| if (len != other->length()) return false; |
| |
| DisallowHeapAllocation no_gc; |
| if (!special_flattening_) { |
| if (string_->Get(0) != other->Get(0)) return false; |
| if (string_->IsFlat()) { |
| StringShape shape1(string_); |
| StringShape shape2(other); |
| if (shape1.encoding_tag() == kOneByteStringTag && |
| shape2.encoding_tag() == kOneByteStringTag) { |
| String::FlatContent flat1 = string_->GetFlatContent(no_gc); |
| String::FlatContent flat2 = other->GetFlatContent(no_gc); |
| return CompareRawStringContents(flat1.ToOneByteVector().start(), |
| flat2.ToOneByteVector().start(), len); |
| } |
| if (shape1.encoding_tag() == kTwoByteStringTag && |
| shape2.encoding_tag() == kTwoByteStringTag) { |
| String::FlatContent flat1 = string_->GetFlatContent(no_gc); |
| String::FlatContent flat2 = other->GetFlatContent(no_gc); |
| return CompareRawStringContents(flat1.ToUC16Vector().start(), |
| flat2.ToUC16Vector().start(), len); |
| } |
| } |
| StringComparator comparator; |
| return comparator.Equals(string_, other); |
| } |
| |
| String::FlatContent flat_content = other->GetFlatContent(no_gc); |
| if (one_byte_) { |
| if (flat_content.IsOneByte()) { |
| return CompareRawStringContents( |
| one_byte_content_, flat_content.ToOneByteVector().start(), len); |
| } else { |
| DCHECK(flat_content.IsTwoByte()); |
| for (int i = 0; i < len; i++) { |
| if (flat_content.Get(i) != one_byte_content_[i]) return false; |
| } |
| return true; |
| } |
| } else { |
| if (flat_content.IsTwoByte()) { |
| return CompareRawStringContents( |
| two_byte_content_, flat_content.ToUC16Vector().start(), len); |
| } else { |
| DCHECK(flat_content.IsOneByte()); |
| for (int i = 0; i < len; i++) { |
| if (flat_content.Get(i) != two_byte_content_[i]) return false; |
| } |
| return true; |
| } |
| } |
| } |
| |
| V8_WARN_UNUSED_RESULT Handle<String> AsHandle(Isolate* isolate) override { |
| UNREACHABLE(); |
| } |
| |
| private: |
| String string_; |
| bool one_byte_; |
| bool special_flattening_; |
| union { |
| uint8_t* one_byte_content_; |
| uint16_t* two_byte_content_; |
| }; |
| union { |
| uint8_t one_byte_buffer_[256]; |
| uint16_t two_byte_buffer_[128]; |
| }; |
| }; |
| |
| } // namespace |
| |
| // static |
| Address StringTable::LookupStringIfExists_NoAllocate(Isolate* isolate, |
| Address raw_string) { |
| DisallowHeapAllocation no_gc; |
| String string = String::cast(Object(raw_string)); |
| Heap* heap = isolate->heap(); |
| StringTable table = heap->string_table(); |
| |
| StringTableNoAllocateKey key(string, HashSeed(isolate)); |
| |
| // String could be an array index. |
| uint32_t hash = string->hash_field(); |
| |
| // Valid array indices are >= 0, so they cannot be mixed up with any of |
| // the result sentinels, which are negative. |
| STATIC_ASSERT( |
| !String::ArrayIndexValueBits::is_valid(ResultSentinel::kUnsupported)); |
| STATIC_ASSERT( |
| !String::ArrayIndexValueBits::is_valid(ResultSentinel::kNotFound)); |
| |
| if (Name::ContainsCachedArrayIndex(hash)) { |
| return Smi::FromInt(String::ArrayIndexValueBits::decode(hash)).ptr(); |
| } |
| if ((hash & Name::kIsNotArrayIndexMask) == 0) { |
| // It is an indexed, but it's not cached. |
| return Smi::FromInt(ResultSentinel::kUnsupported).ptr(); |
| } |
| |
| DCHECK(!string->IsInternalizedString()); |
| int entry = table->FindEntry(ReadOnlyRoots(isolate), &key, key.Hash()); |
| if (entry != kNotFound) { |
| String internalized = String::cast(table->KeyAt(entry)); |
| if (FLAG_thin_strings) { |
| MakeStringThin(string, internalized, isolate); |
| } |
| return internalized.ptr(); |
| } |
| // A string that's not an array index, and not in the string table, |
| // cannot have been used as a property name before. |
| return Smi::FromInt(ResultSentinel::kNotFound).ptr(); |
| } |
| |
| String StringTable::ForwardStringIfExists(Isolate* isolate, StringTableKey* key, |
| String string) { |
| Handle<StringTable> table = isolate->factory()->string_table(); |
| int entry = table->FindEntry(isolate, key); |
| if (entry == kNotFound) return String(); |
| |
| String canonical = String::cast(table->KeyAt(entry)); |
| if (canonical != string) MakeStringThin(string, canonical, isolate); |
| return canonical; |
| } |
| |
| Handle<StringSet> StringSet::New(Isolate* isolate) { |
| return HashTable::New(isolate, 0); |
| } |
| |
| Handle<StringSet> StringSet::Add(Isolate* isolate, Handle<StringSet> stringset, |
| Handle<String> name) { |
| if (!stringset->Has(isolate, name)) { |
| stringset = EnsureCapacity(isolate, stringset, 1); |
| uint32_t hash = ShapeT::Hash(isolate, *name); |
| int entry = stringset->FindInsertionEntry(hash); |
| stringset->set(EntryToIndex(entry), *name); |
| stringset->ElementAdded(); |
| } |
| return stringset; |
| } |
| |
| bool StringSet::Has(Isolate* isolate, Handle<String> name) { |
| return FindEntry(isolate, *name) != kNotFound; |
| } |
| |
| Handle<ObjectHashSet> ObjectHashSet::Add(Isolate* isolate, |
| Handle<ObjectHashSet> set, |
| Handle<Object> key) { |
| int32_t hash = key->GetOrCreateHash(isolate)->value(); |
| if (!set->Has(isolate, key, hash)) { |
| set = EnsureCapacity(isolate, set, 1); |
| int entry = set->FindInsertionEntry(hash); |
| set->set(EntryToIndex(entry), *key); |
| set->ElementAdded(); |
| } |
| return set; |
| } |
| |
| namespace { |
| |
| const int kLiteralEntryLength = 2; |
| const int kLiteralInitialLength = 2; |
| const int kLiteralContextOffset = 0; |
| const int kLiteralLiteralsOffset = 1; |
| |
| int SearchLiteralsMapEntry(CompilationCacheTable cache, int cache_entry, |
| Context native_context) { |
| DisallowHeapAllocation no_gc; |
| DCHECK(native_context->IsNativeContext()); |
| Object obj = cache->get(cache_entry); |
| |
| // Check that there's no confusion between FixedArray and WeakFixedArray (the |
| // object used to be a FixedArray here). |
| DCHECK(!obj->IsFixedArray()); |
| if (obj->IsWeakFixedArray()) { |
| WeakFixedArray literals_map = WeakFixedArray::cast(obj); |
| int length = literals_map->length(); |
| for (int i = 0; i < length; i += kLiteralEntryLength) { |
| DCHECK(literals_map->Get(i + kLiteralContextOffset)->IsWeakOrCleared()); |
| if (literals_map->Get(i + kLiteralContextOffset) == |
| HeapObjectReference::Weak(native_context)) { |
| return i; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| void AddToFeedbackCellsMap(Handle<CompilationCacheTable> cache, int cache_entry, |
| Handle<Context> native_context, |
| Handle<FeedbackCell> feedback_cell) { |
| Isolate* isolate = native_context->GetIsolate(); |
| DCHECK(native_context->IsNativeContext()); |
| STATIC_ASSERT(kLiteralEntryLength == 2); |
| Handle<WeakFixedArray> new_literals_map; |
| int entry; |
| |
| Object obj = cache->get(cache_entry); |
| |
| // Check that there's no confusion between FixedArray and WeakFixedArray (the |
| // object used to be a FixedArray here). |
| DCHECK(!obj->IsFixedArray()); |
| if (!obj->IsWeakFixedArray() || WeakFixedArray::cast(obj)->length() == 0) { |
| new_literals_map = |
| isolate->factory()->NewWeakFixedArray(kLiteralInitialLength, TENURED); |
| entry = 0; |
| } else { |
| Handle<WeakFixedArray> old_literals_map(WeakFixedArray::cast(obj), isolate); |
| entry = SearchLiteralsMapEntry(*cache, cache_entry, *native_context); |
| if (entry >= 0) { |
| // Just set the code of the entry. |
| old_literals_map->Set(entry + kLiteralLiteralsOffset, |
| HeapObjectReference::Weak(*feedback_cell)); |
| return; |
| } |
| |
| // Can we reuse an entry? |
| DCHECK_LT(entry, 0); |
| int length = old_literals_map->length(); |
| for (int i = 0; i < length; i += kLiteralEntryLength) { |
| if (old_literals_map->Get(i + kLiteralContextOffset)->IsCleared()) { |
| new_literals_map = old_literals_map; |
| entry = i; |
| break; |
| } |
| } |
| |
| if (entry < 0) { |
| // Copy old optimized code map and append one new entry. |
| new_literals_map = isolate->factory()->CopyWeakFixedArrayAndGrow( |
| old_literals_map, kLiteralEntryLength, TENURED); |
| entry = old_literals_map->length(); |
| } |
| } |
| |
| new_literals_map->Set(entry + kLiteralContextOffset, |
| HeapObjectReference::Weak(*native_context)); |
| new_literals_map->Set(entry + kLiteralLiteralsOffset, |
| HeapObjectReference::Weak(*feedback_cell)); |
| |
| #ifdef DEBUG |
| for (int i = 0; i < new_literals_map->length(); i += kLiteralEntryLength) { |
| MaybeObject object = new_literals_map->Get(i + kLiteralContextOffset); |
| DCHECK(object->IsCleared() || |
| object->GetHeapObjectAssumeWeak()->IsNativeContext()); |
| object = new_literals_map->Get(i + kLiteralLiteralsOffset); |
| DCHECK(object->IsCleared() || |
| object->GetHeapObjectAssumeWeak()->IsFeedbackCell()); |
| } |
| #endif |
| |
| Object old_literals_map = cache->get(cache_entry); |
| if (old_literals_map != *new_literals_map) { |
| cache->set(cache_entry, *new_literals_map); |
| } |
| } |
| |
| FeedbackCell SearchLiteralsMap(CompilationCacheTable cache, int cache_entry, |
| Context native_context) { |
| FeedbackCell result; |
| int entry = SearchLiteralsMapEntry(cache, cache_entry, native_context); |
| if (entry >= 0) { |
| WeakFixedArray literals_map = WeakFixedArray::cast(cache->get(cache_entry)); |
| DCHECK_LE(entry + kLiteralEntryLength, literals_map->length()); |
| MaybeObject object = literals_map->Get(entry + kLiteralLiteralsOffset); |
| |
| if (!object->IsCleared()) { |
| result = FeedbackCell::cast(object->GetHeapObjectAssumeWeak()); |
| } |
| } |
| DCHECK(result.is_null() || result->IsFeedbackCell()); |
| return result; |
| } |
| |
| } // namespace |
| |
| MaybeHandle<SharedFunctionInfo> CompilationCacheTable::LookupScript( |
| Handle<CompilationCacheTable> table, Handle<String> src, |
| Handle<Context> native_context, LanguageMode language_mode) { |
| // We use the empty function SFI as part of the key. Although the |
| // empty_function is native context dependent, the SFI is de-duped on |
| // snapshot builds by the PartialSnapshotCache, and so this does not prevent |
| // reuse of scripts in the compilation cache across native contexts. |
| Handle<SharedFunctionInfo> shared(native_context->empty_function()->shared(), |
| native_context->GetIsolate()); |
| Isolate* isolate = native_context->GetIsolate(); |
| src = String::Flatten(isolate, src); |
| StringSharedKey key(src, shared, language_mode, kNoSourcePosition); |
| int entry = table->FindEntry(isolate, &key); |
| if (entry == kNotFound) return MaybeHandle<SharedFunctionInfo>(); |
| int index = EntryToIndex(entry); |
| if (!table->get(index)->IsFixedArray()) { |
| return MaybeHandle<SharedFunctionInfo>(); |
| } |
| Object obj = table->get(index + 1); |
| if (obj->IsSharedFunctionInfo()) { |
| return handle(SharedFunctionInfo::cast(obj), native_context->GetIsolate()); |
| } |
| return MaybeHandle<SharedFunctionInfo>(); |
| } |
| |
| InfoCellPair CompilationCacheTable::LookupEval( |
| Handle<CompilationCacheTable> table, Handle<String> src, |
| Handle<SharedFunctionInfo> outer_info, Handle<Context> native_context, |
| LanguageMode language_mode, int position) { |
| InfoCellPair empty_result; |
| Isolate* isolate = native_context->GetIsolate(); |
| src = String::Flatten(isolate, src); |
| StringSharedKey key(src, outer_info, language_mode, position); |
| int entry = table->FindEntry(isolate, &key); |
| if (entry == kNotFound) return empty_result; |
| int index = EntryToIndex(entry); |
| if (!table->get(index)->IsFixedArray()) return empty_result; |
| Object obj = table->get(EntryToIndex(entry) + 1); |
| if (obj->IsSharedFunctionInfo()) { |
| FeedbackCell feedback_cell = |
| SearchLiteralsMap(*table, EntryToIndex(entry) + 2, *native_context); |
| return InfoCellPair(SharedFunctionInfo::cast(obj), feedback_cell); |
| } |
| return empty_result; |
| } |
| |
| Handle<Object> CompilationCacheTable::LookupRegExp(Handle<String> src, |
| JSRegExp::Flags flags) { |
| Isolate* isolate = GetIsolate(); |
| DisallowHeapAllocation no_allocation; |
| RegExpKey key(src, flags); |
| int entry = FindEntry(isolate, &key); |
| if (entry == kNotFound) return isolate->factory()->undefined_value(); |
| return Handle<Object>(get(EntryToIndex(entry) + 1), isolate); |
| } |
| |
| Handle<CompilationCacheTable> CompilationCacheTable::PutScript( |
| Handle<CompilationCacheTable> cache, Handle<String> src, |
| Handle<Context> native_context, LanguageMode language_mode, |
| Handle<SharedFunctionInfo> value) { |
| Isolate* isolate = native_context->GetIsolate(); |
| // We use the empty function SFI as part of the key. Although the |
| // empty_function is native context dependent, the SFI is de-duped on |
| // snapshot builds by the PartialSnapshotCache, and so this does not prevent |
| // reuse of scripts in the compilation cache across native contexts. |
| Handle<SharedFunctionInfo> shared(native_context->empty_function()->shared(), |
| isolate); |
| src = String::Flatten(isolate, src); |
| StringSharedKey key(src, shared, language_mode, kNoSourcePosition); |
| Handle<Object> k = key.AsHandle(isolate); |
| cache = EnsureCapacity(isolate, cache, 1); |
| int entry = cache->FindInsertionEntry(key.Hash()); |
| cache->set(EntryToIndex(entry), *k); |
| cache->set(EntryToIndex(entry) + 1, *value); |
| cache->ElementAdded(); |
| return cache; |
| } |
| |
| Handle<CompilationCacheTable> CompilationCacheTable::PutEval( |
| Handle<CompilationCacheTable> cache, Handle<String> src, |
| Handle<SharedFunctionInfo> outer_info, Handle<SharedFunctionInfo> value, |
| Handle<Context> native_context, Handle<FeedbackCell> feedback_cell, |
| int position) { |
| Isolate* isolate = native_context->GetIsolate(); |
| src = String::Flatten(isolate, src); |
| StringSharedKey key(src, outer_info, value->language_mode(), position); |
| { |
| Handle<Object> k = key.AsHandle(isolate); |
| int entry = cache->FindEntry(isolate, &key); |
| if (entry != kNotFound) { |
| cache->set(EntryToIndex(entry), *k); |
| cache->set(EntryToIndex(entry) + 1, *value); |
| // AddToFeedbackCellsMap may allocate a new sub-array to live in the |
| // entry, but it won't change the cache array. Therefore EntryToIndex |
| // and entry remains correct. |
| AddToFeedbackCellsMap(cache, EntryToIndex(entry) + 2, native_context, |
| feedback_cell); |
| return cache; |
| } |
| } |
| |
| cache = EnsureCapacity(isolate, cache, 1); |
| int entry = cache->FindInsertionEntry(key.Hash()); |
| Handle<Object> k = |
| isolate->factory()->NewNumber(static_cast<double>(key.Hash())); |
| cache->set(EntryToIndex(entry), *k); |
| cache->set(EntryToIndex(entry) + 1, Smi::FromInt(kHashGenerations)); |
| cache->ElementAdded(); |
| return cache; |
| } |
| |
| Handle<CompilationCacheTable> CompilationCacheTable::PutRegExp( |
| Isolate* isolate, Handle<CompilationCacheTable> cache, Handle<String> src, |
| JSRegExp::Flags flags, Handle<FixedArray> value) { |
| RegExpKey key(src, flags); |
| cache = EnsureCapacity(isolate, cache, 1); |
| int entry = cache->FindInsertionEntry(key.Hash()); |
| // We store the value in the key slot, and compare the search key |
| // to the stored value with a custon IsMatch function during lookups. |
| cache->set(EntryToIndex(entry), *value); |
| cache->set(EntryToIndex(entry) + 1, *value); |
| cache->ElementAdded(); |
| return cache; |
| } |
| |
| |
| void CompilationCacheTable::Age() { |
| DisallowHeapAllocation no_allocation; |
| Object the_hole_value = GetReadOnlyRoots().the_hole_value(); |
| for (int entry = 0, size = Capacity(); entry < size; entry++) { |
| int entry_index = EntryToIndex(entry); |
| int value_index = entry_index + 1; |
| |
| if (get(entry_index)->IsNumber()) { |
| Smi count = Smi::cast(get(value_index)); |
| count = Smi::FromInt(count->value() - 1); |
| if (count->value() == 0) { |
| NoWriteBarrierSet(*this, entry_index, the_hole_value); |
| NoWriteBarrierSet(*this, value_index, the_hole_value); |
| ElementRemoved(); |
| } else { |
| NoWriteBarrierSet(*this, value_index, count); |
| } |
| } else if (get(entry_index)->IsFixedArray()) { |
| SharedFunctionInfo info = SharedFunctionInfo::cast(get(value_index)); |
| if (info->IsInterpreted() && info->GetBytecodeArray()->IsOld()) { |
| for (int i = 0; i < kEntrySize; i++) { |
| NoWriteBarrierSet(*this, entry_index + i, the_hole_value); |
| } |
| ElementRemoved(); |
| } |
| } |
| } |
| } |
| |
| void CompilationCacheTable::Remove(Object value) { |
| DisallowHeapAllocation no_allocation; |
| Object the_hole_value = GetReadOnlyRoots().the_hole_value(); |
| for (int entry = 0, size = Capacity(); entry < size; entry++) { |
| int entry_index = EntryToIndex(entry); |
| int value_index = entry_index + 1; |
| if (get(value_index) == value) { |
| for (int i = 0; i < kEntrySize; i++) { |
| NoWriteBarrierSet(*this, entry_index + i, the_hole_value); |
| } |
| ElementRemoved(); |
| } |
| } |
| return; |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> BaseNameDictionary<Derived, Shape>::New( |
| Isolate* isolate, int at_least_space_for, PretenureFlag pretenure, |
| MinimumCapacity capacity_option) { |
| DCHECK_LE(0, at_least_space_for); |
| Handle<Derived> dict = Dictionary<Derived, Shape>::New( |
| isolate, at_least_space_for, pretenure, capacity_option); |
| dict->SetHash(PropertyArray::kNoHashSentinel); |
| dict->SetNextEnumerationIndex(PropertyDetails::kInitialIndex); |
| return dict; |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> BaseNameDictionary<Derived, Shape>::EnsureCapacity( |
| Isolate* isolate, Handle<Derived> dictionary, int n) { |
| // Check whether there are enough enumeration indices to add n elements. |
| if (!PropertyDetails::IsValidIndex(dictionary->NextEnumerationIndex() + n)) { |
| // If not, we generate new indices for the properties. |
| int length = dictionary->NumberOfElements(); |
| |
| Handle<FixedArray> iteration_order = IterationIndices(isolate, dictionary); |
| DCHECK_EQ(length, iteration_order->length()); |
| |
| // Iterate over the dictionary using the enumeration order and update |
| // the dictionary with new enumeration indices. |
| for (int i = 0; i < length; i++) { |
| int index = Smi::ToInt(iteration_order->get(i)); |
| DCHECK(dictionary->IsKey(dictionary->GetReadOnlyRoots(), |
| dictionary->KeyAt(index))); |
| |
| int enum_index = PropertyDetails::kInitialIndex + i; |
| |
| PropertyDetails details = dictionary->DetailsAt(index); |
| PropertyDetails new_details = details.set_index(enum_index); |
| dictionary->DetailsAtPut(isolate, index, new_details); |
| } |
| |
| // Set the next enumeration index. |
| dictionary->SetNextEnumerationIndex(PropertyDetails::kInitialIndex + |
| length); |
| } |
| return HashTable<Derived, Shape>::EnsureCapacity(isolate, dictionary, n); |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> Dictionary<Derived, Shape>::DeleteEntry( |
| Isolate* isolate, Handle<Derived> dictionary, int entry) { |
| DCHECK(Shape::kEntrySize != 3 || |
| dictionary->DetailsAt(entry).IsConfigurable()); |
| dictionary->ClearEntry(isolate, entry); |
| dictionary->ElementRemoved(); |
| return Shrink(isolate, dictionary); |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> Dictionary<Derived, Shape>::AtPut(Isolate* isolate, |
| Handle<Derived> dictionary, |
| Key key, Handle<Object> value, |
| PropertyDetails details) { |
| int entry = dictionary->FindEntry(isolate, key); |
| |
| // If the entry is present set the value; |
| if (entry == Dictionary::kNotFound) { |
| return Derived::Add(isolate, dictionary, key, value, details); |
| } |
| |
| // We don't need to copy over the enumeration index. |
| dictionary->ValueAtPut(entry, *value); |
| if (Shape::kEntrySize == 3) dictionary->DetailsAtPut(isolate, entry, details); |
| return dictionary; |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> |
| BaseNameDictionary<Derived, Shape>::AddNoUpdateNextEnumerationIndex( |
| Isolate* isolate, Handle<Derived> dictionary, Key key, Handle<Object> value, |
| PropertyDetails details, int* entry_out) { |
| // Insert element at empty or deleted entry |
| return Dictionary<Derived, Shape>::Add(isolate, dictionary, key, value, |
| details, entry_out); |
| } |
| |
| // GCC workaround: Explicitly instantiate template method for NameDictionary |
| // to avoid "undefined reference" issues during linking. |
| template Handle<NameDictionary> |
| BaseNameDictionary<NameDictionary, NameDictionaryShape>:: |
| AddNoUpdateNextEnumerationIndex(Isolate* isolate, Handle<NameDictionary>, |
| Handle<Name>, Handle<Object>, |
| PropertyDetails, int*); |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> BaseNameDictionary<Derived, Shape>::Add( |
| Isolate* isolate, Handle<Derived> dictionary, Key key, Handle<Object> value, |
| PropertyDetails details, int* entry_out) { |
| // Insert element at empty or deleted entry |
| DCHECK_EQ(0, details.dictionary_index()); |
| // Assign an enumeration index to the property and update |
| // SetNextEnumerationIndex. |
| int index = dictionary->NextEnumerationIndex(); |
| details = details.set_index(index); |
| dictionary = AddNoUpdateNextEnumerationIndex(isolate, dictionary, key, value, |
| details, entry_out); |
| // Update enumeration index here in order to avoid potential modification of |
| // the canonical empty dictionary which lives in read only space. |
| dictionary->SetNextEnumerationIndex(index + 1); |
| return dictionary; |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> Dictionary<Derived, Shape>::Add(Isolate* isolate, |
| Handle<Derived> dictionary, |
| Key key, Handle<Object> value, |
| PropertyDetails details, |
| int* entry_out) { |
| uint32_t hash = Shape::Hash(isolate, key); |
| // Valdate key is absent. |
| SLOW_DCHECK((dictionary->FindEntry(isolate, key) == Dictionary::kNotFound)); |
| // Check whether the dictionary should be extended. |
| dictionary = Derived::EnsureCapacity(isolate, dictionary, 1); |
| |
| // Compute the key object. |
| Handle<Object> k = Shape::AsHandle(isolate, key); |
| |
| uint32_t entry = dictionary->FindInsertionEntry(hash); |
| dictionary->SetEntry(isolate, entry, *k, *value, details); |
| DCHECK(dictionary->KeyAt(entry)->IsNumber() || |
| Shape::Unwrap(dictionary->KeyAt(entry))->IsUniqueName()); |
| dictionary->ElementAdded(); |
| if (entry_out) *entry_out = entry; |
| return dictionary; |
| } |
| |
| // static |
| Handle<SimpleNumberDictionary> SimpleNumberDictionary::Set( |
| Isolate* isolate, Handle<SimpleNumberDictionary> dictionary, uint32_t key, |
| Handle<Object> value) { |
| return AtPut(isolate, dictionary, key, value, PropertyDetails::Empty()); |
| } |
| |
| bool NumberDictionary::HasComplexElements() { |
| if (!requires_slow_elements()) return false; |
| ReadOnlyRoots roots = GetReadOnlyRoots(); |
| int capacity = this->Capacity(); |
| for (int i = 0; i < capacity; i++) { |
| Object k; |
| if (!this->ToKey(roots, i, &k)) continue; |
| PropertyDetails details = this->DetailsAt(i); |
| if (details.kind() == kAccessor) return true; |
| PropertyAttributes attr = details.attributes(); |
| if (attr & ALL_ATTRIBUTES_MASK) return true; |
| } |
| return false; |
| } |
| |
| void NumberDictionary::UpdateMaxNumberKey(uint32_t key, |
| Handle<JSObject> dictionary_holder) { |
| DisallowHeapAllocation no_allocation; |
| // If the dictionary requires slow elements an element has already |
| // been added at a high index. |
| if (requires_slow_elements()) return; |
| // Check if this index is high enough that we should require slow |
| // elements. |
| if (key > kRequiresSlowElementsLimit) { |
| if (!dictionary_holder.is_null()) { |
| dictionary_holder->RequireSlowElements(*this); |
| } |
| set_requires_slow_elements(); |
| return; |
| } |
| // Update max key value. |
| Object max_index_object = get(kMaxNumberKeyIndex); |
| if (!max_index_object->IsSmi() || max_number_key() < key) { |
| FixedArray::set(kMaxNumberKeyIndex, |
| Smi::FromInt(key << kRequiresSlowElementsTagSize)); |
| } |
| } |
| |
| Handle<NumberDictionary> NumberDictionary::Set( |
| Isolate* isolate, Handle<NumberDictionary> dictionary, uint32_t key, |
| Handle<Object> value, Handle<JSObject> dictionary_holder, |
| PropertyDetails details) { |
| dictionary->UpdateMaxNumberKey(key, dictionary_holder); |
| return AtPut(isolate, dictionary, key, value, details); |
| } |
| |
| void NumberDictionary::CopyValuesTo(FixedArray elements) { |
| ReadOnlyRoots roots = GetReadOnlyRoots(); |
| int pos = 0; |
| int capacity = this->Capacity(); |
| DisallowHeapAllocation no_gc; |
| WriteBarrierMode mode = elements->GetWriteBarrierMode(no_gc); |
| for (int i = 0; i < capacity; i++) { |
| Object k; |
| if (this->ToKey(roots, i, &k)) { |
| elements->set(pos++, this->ValueAt(i), mode); |
| } |
| } |
| DCHECK_EQ(pos, elements->length()); |
| } |
| |
| template <typename Derived, typename Shape> |
| int Dictionary<Derived, Shape>::NumberOfEnumerableProperties() { |
| ReadOnlyRoots roots = this->GetReadOnlyRoots(); |
| int capacity = this->Capacity(); |
| int result = 0; |
| for (int i = 0; i < capacity; i++) { |
| Object k; |
| if (!this->ToKey(roots, i, &k)) continue; |
| if (k->FilterKey(ENUMERABLE_STRINGS)) continue; |
| PropertyDetails details = this->DetailsAt(i); |
| PropertyAttributes attr = details.attributes(); |
| if ((attr & ONLY_ENUMERABLE) == 0) result++; |
| } |
| return result; |
| } |
| |
| |
| template <typename Dictionary> |
| struct EnumIndexComparator { |
| explicit EnumIndexComparator(Dictionary dict) : dict(dict) {} |
| bool operator()(Tagged_t a, Tagged_t b) { |
| PropertyDetails da(dict->DetailsAt(Smi(static_cast<Address>(a)).value())); |
| PropertyDetails db(dict->DetailsAt(Smi(static_cast<Address>(b)).value())); |
| return da.dictionary_index() < db.dictionary_index(); |
| } |
| Dictionary dict; |
| }; |
| |
| template <typename Derived, typename Shape> |
| void BaseNameDictionary<Derived, Shape>::CopyEnumKeysTo( |
| Isolate* isolate, Handle<Derived> dictionary, Handle<FixedArray> storage, |
| KeyCollectionMode mode, KeyAccumulator* accumulator) { |
| DCHECK_IMPLIES(mode != KeyCollectionMode::kOwnOnly, accumulator != nullptr); |
| int length = storage->length(); |
| int capacity = dictionary->Capacity(); |
| int properties = 0; |
| ReadOnlyRoots roots(isolate); |
| for (int i = 0; i < capacity; i++) { |
| Object key; |
| if (!dictionary->ToKey(roots, i, &key)) continue; |
| bool is_shadowing_key = false; |
| if (key->IsSymbol()) continue; |
| PropertyDetails details = dictionary->DetailsAt(i); |
| if (details.IsDontEnum()) { |
| if (mode == KeyCollectionMode::kIncludePrototypes) { |
| is_shadowing_key = true; |
| } else { |
| continue; |
| } |
| } |
| if (is_shadowing_key) { |
| accumulator->AddShadowingKey(key); |
| continue; |
| } else { |
| storage->set(properties, Smi::FromInt(i)); |
| } |
| properties++; |
| if (mode == KeyCollectionMode::kOwnOnly && properties == length) break; |
| } |
| |
| CHECK_EQ(length, properties); |
| DisallowHeapAllocation no_gc; |
| Derived raw_dictionary = *dictionary; |
| FixedArray raw_storage = *storage; |
| EnumIndexComparator<Derived> cmp(raw_dictionary); |
| // Use AtomicSlot wrapper to ensure that std::sort uses atomic load and |
| // store operations that are safe for concurrent marking. |
| AtomicSlot start(storage->GetFirstElementAddress()); |
| std::sort(start, start + length, cmp); |
| for (int i = 0; i < length; i++) { |
| int index = Smi::ToInt(raw_storage->get(i)); |
| raw_storage->set(i, raw_dictionary->NameAt(index)); |
| } |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<FixedArray> BaseNameDictionary<Derived, Shape>::IterationIndices( |
| Isolate* isolate, Handle<Derived> dictionary) { |
| int capacity = dictionary->Capacity(); |
| int length = dictionary->NumberOfElements(); |
| Handle<FixedArray> array = isolate->factory()->NewFixedArray(length); |
| ReadOnlyRoots roots(isolate); |
| int array_size = 0; |
| { |
| DisallowHeapAllocation no_gc; |
| Derived raw_dictionary = *dictionary; |
| for (int i = 0; i < capacity; i++) { |
| Object k; |
| if (!raw_dictionary->ToKey(roots, i, &k)) continue; |
| array->set(array_size++, Smi::FromInt(i)); |
| } |
| |
| DCHECK_EQ(array_size, length); |
| |
| EnumIndexComparator<Derived> cmp(raw_dictionary); |
| // Use AtomicSlot wrapper to ensure that std::sort uses atomic load and |
| // store operations that are safe for concurrent marking. |
| AtomicSlot start(array->GetFirstElementAddress()); |
| std::sort(start, start + array_size, cmp); |
| } |
| return FixedArray::ShrinkOrEmpty(isolate, array, array_size); |
| } |
| |
| template <typename Derived, typename Shape> |
| void BaseNameDictionary<Derived, Shape>::CollectKeysTo( |
| Handle<Derived> dictionary, KeyAccumulator* keys) { |
| Isolate* isolate = keys->isolate(); |
| ReadOnlyRoots roots(isolate); |
| int capacity = dictionary->Capacity(); |
| Handle<FixedArray> array = |
| isolate->factory()->NewFixedArray(dictionary->NumberOfElements()); |
| int array_size = 0; |
| PropertyFilter filter = keys->filter(); |
| { |
| DisallowHeapAllocation no_gc; |
| Derived raw_dictionary = *dictionary; |
| for (int i = 0; i < capacity; i++) { |
| Object k; |
| if (!raw_dictionary->ToKey(roots, i, &k)) continue; |
| if (k->FilterKey(filter)) continue; |
| PropertyDetails details = raw_dictionary->DetailsAt(i); |
| if ((details.attributes() & filter) != 0) { |
| keys->AddShadowingKey(k); |
| continue; |
| } |
| if (filter & ONLY_ALL_CAN_READ) { |
| if (details.kind() != kAccessor) continue; |
| Object accessors = raw_dictionary->ValueAt(i); |
| if (!accessors->IsAccessorInfo()) continue; |
| if (!AccessorInfo::cast(accessors)->all_can_read()) continue; |
| } |
| array->set(array_size++, Smi::FromInt(i)); |
| } |
| |
| EnumIndexComparator<Derived> cmp(raw_dictionary); |
| // Use AtomicSlot wrapper to ensure that std::sort uses atomic load and |
| // store operations that are safe for concurrent marking. |
| AtomicSlot start(array->GetFirstElementAddress()); |
| std::sort(start, start + array_size, cmp); |
| } |
| |
| bool has_seen_symbol = false; |
| for (int i = 0; i < array_size; i++) { |
| int index = Smi::ToInt(array->get(i)); |
| Object key = dictionary->NameAt(index); |
| if (key->IsSymbol()) { |
| has_seen_symbol = true; |
| continue; |
| } |
| keys->AddKey(key, DO_NOT_CONVERT); |
| } |
| if (has_seen_symbol) { |
| for (int i = 0; i < array_size; i++) { |
| int index = Smi::ToInt(array->get(i)); |
| Object key = dictionary->NameAt(index); |
| if (!key->IsSymbol()) continue; |
| keys->AddKey(key, DO_NOT_CONVERT); |
| } |
| } |
| } |
| |
| // Backwards lookup (slow). |
| template <typename Derived, typename Shape> |
| Object Dictionary<Derived, Shape>::SlowReverseLookup(Object value) { |
| Derived dictionary = Derived::cast(*this); |
| ReadOnlyRoots roots = dictionary->GetReadOnlyRoots(); |
| int capacity = dictionary->Capacity(); |
| for (int i = 0; i < capacity; i++) { |
| Object k; |
| if (!dictionary->ToKey(roots, i, &k)) continue; |
| Object e = dictionary->ValueAt(i); |
| if (e == value) return k; |
| } |
| return roots.undefined_value(); |
| } |
| |
| template <typename Derived, typename Shape> |
| void ObjectHashTableBase<Derived, Shape>::FillEntriesWithHoles( |
| Handle<Derived> table) { |
| int length = table->length(); |
| for (int i = Derived::EntryToIndex(0); i < length; i++) { |
| table->set_the_hole(i); |
| } |
| } |
| |
| template <typename Derived, typename Shape> |
| Object ObjectHashTableBase<Derived, Shape>::Lookup(ReadOnlyRoots roots, |
| Handle<Object> key, |
| int32_t hash) { |
| DisallowHeapAllocation no_gc; |
| DCHECK(this->IsKey(roots, *key)); |
| |
| int entry = this->FindEntry(roots, key, hash); |
| if (entry == kNotFound) return roots.the_hole_value(); |
| return this->get(Derived::EntryToIndex(entry) + 1); |
| } |
| |
| template <typename Derived, typename Shape> |
| Object ObjectHashTableBase<Derived, Shape>::Lookup(Handle<Object> key) { |
| DisallowHeapAllocation no_gc; |
| |
| ReadOnlyRoots roots = this->GetReadOnlyRoots(); |
| DCHECK(this->IsKey(roots, *key)); |
| |
| // If the object does not have an identity hash, it was never used as a key. |
| Object hash = key->GetHash(); |
| if (hash->IsUndefined(roots)) { |
| return roots.the_hole_value(); |
| } |
| return Lookup(roots, key, Smi::ToInt(hash)); |
| } |
| |
| template <typename Derived, typename Shape> |
| Object ObjectHashTableBase<Derived, Shape>::Lookup(Handle<Object> key, |
| int32_t hash) { |
| return Lookup(this->GetReadOnlyRoots(), key, hash); |
| } |
| |
| template <typename Derived, typename Shape> |
| Object ObjectHashTableBase<Derived, Shape>::ValueAt(int entry) { |
| return this->get(EntryToValueIndex(entry)); |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> ObjectHashTableBase<Derived, Shape>::Put(Handle<Derived> table, |
| Handle<Object> key, |
| Handle<Object> value) { |
| Isolate* isolate = Heap::FromWritableHeapObject(*table)->isolate(); |
| DCHECK(table->IsKey(ReadOnlyRoots(isolate), *key)); |
| DCHECK(!value->IsTheHole(ReadOnlyRoots(isolate))); |
| |
| // Make sure the key object has an identity hash code. |
| int32_t hash = key->GetOrCreateHash(isolate)->value(); |
| |
| return ObjectHashTableBase<Derived, Shape>::Put(isolate, table, key, value, |
| hash); |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> ObjectHashTableBase<Derived, Shape>::Put(Isolate* isolate, |
| Handle<Derived> table, |
| Handle<Object> key, |
| Handle<Object> value, |
| int32_t hash) { |
| ReadOnlyRoots roots(isolate); |
| DCHECK(table->IsKey(roots, *key)); |
| DCHECK(!value->IsTheHole(roots)); |
| |
| int entry = table->FindEntry(roots, key, hash); |
| |
| // Key is already in table, just overwrite value. |
| if (entry != kNotFound) { |
| table->set(Derived::EntryToIndex(entry) + 1, *value); |
| return table; |
| } |
| |
| // Rehash if more than 33% of the entries are deleted entries. |
| // TODO(jochen): Consider to shrink the fixed array in place. |
| if ((table->NumberOfDeletedElements() << 1) > table->NumberOfElements()) { |
| table->Rehash(roots); |
| } |
| // If we're out of luck, we didn't get a GC recently, and so rehashing |
| // isn't enough to avoid a crash. |
| if (!table->HasSufficientCapacityToAdd(1)) { |
| int nof = table->NumberOfElements() + 1; |
| int capacity = ObjectHashTable::ComputeCapacity(nof * 2); |
| if (capacity > ObjectHashTable::kMaxCapacity) { |
| for (size_t i = 0; i < 2; ++i) { |
| isolate->heap()->CollectAllGarbage( |
| Heap::kNoGCFlags, GarbageCollectionReason::kFullHashtable); |
| } |
| table->Rehash(roots); |
| } |
| } |
| |
| // Check whether the hash table should be extended. |
| table = Derived::EnsureCapacity(isolate, table, 1); |
| table->AddEntry(table->FindInsertionEntry(hash), *key, *value); |
| return table; |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> ObjectHashTableBase<Derived, Shape>::Remove( |
| Isolate* isolate, Handle<Derived> table, Handle<Object> key, |
| bool* was_present) { |
| DCHECK(table->IsKey(table->GetReadOnlyRoots(), *key)); |
| |
| Object hash = key->GetHash(); |
| if (hash->IsUndefined()) { |
| *was_present = false; |
| return table; |
| } |
| |
| return Remove(isolate, table, key, was_present, Smi::ToInt(hash)); |
| } |
| |
| template <typename Derived, typename Shape> |
| Handle<Derived> ObjectHashTableBase<Derived, Shape>::Remove( |
| Isolate* isolate, Handle<Derived> table, Handle<Object> key, |
| bool* was_present, int32_t hash) { |
| ReadOnlyRoots roots = table->GetReadOnlyRoots(); |
| DCHECK(table->IsKey(roots, *key)); |
| |
| int entry = table->FindEntry(roots, key, hash); |
| if (entry == kNotFound) { |
| *was_present = false; |
| return table; |
| } |
| |
| *was_present = true; |
| table->RemoveEntry(entry); |
| return Derived::Shrink(isolate, table); |
| } |
| |
| template <typename Derived, typename Shape> |
| void ObjectHashTableBase<Derived, Shape>::AddEntry(int entry, Object key, |
| Object value) { |
| this->set(Derived::EntryToIndex(entry), key); |
| this->set(Derived::EntryToIndex(entry) + 1, value); |
| this->ElementAdded(); |
| } |
| |
| template <typename Derived, typename Shape> |
| void ObjectHashTableBase<Derived, Shape>::RemoveEntry(int entry) { |
| this->set_the_hole(Derived::EntryToIndex(entry)); |
| this->set_the_hole(Derived::EntryToIndex(entry) + 1); |
| this->ElementRemoved(); |
| } |
| |
| |
| void JSSet::Initialize(Handle<JSSet> set, Isolate* isolate) { |
| Handle<OrderedHashSet> table = isolate->factory()->NewOrderedHashSet(); |
| set->set_table(*table); |
| } |
| |
| void JSSet::Clear(Isolate* isolate, Handle<JSSet> set) { |
| Handle<OrderedHashSet> table(OrderedHashSet::cast(set->table()), isolate); |
| table = OrderedHashSet::Clear(isolate, table); |
| set->set_table(*table); |
| } |
| |
| |
| void JSMap::Initialize(Handle<JSMap> map, Isolate* isolate) { |
| Handle<OrderedHashMap> table = isolate->factory()->NewOrderedHashMap(); |
| map->set_table(*table); |
| } |
| |
| void JSMap::Clear(Isolate* isolate, Handle<JSMap> map) { |
| Handle<OrderedHashMap> table(OrderedHashMap::cast(map->table()), isolate); |
| table = OrderedHashMap::Clear(isolate, table); |
| map->set_table(*table); |
| } |
| |
| |
| void JSWeakCollection::Initialize(Handle<JSWeakCollection> weak_collection, |
| Isolate* isolate) { |
| Handle<EphemeronHashTable> table = EphemeronHashTable::New(isolate, 0); |
| weak_collection->set_table(*table); |
| } |
| |
| |
| void JSWeakCollection::Set(Handle<JSWeakCollection> weak_collection, |
| Handle<Object> key, Handle<Object> value, |
| int32_t hash) { |
| DCHECK(key->IsJSReceiver() || key->IsSymbol()); |
| Handle<EphemeronHashTable> table( |
| EphemeronHashTable::cast(weak_collection->table()), |
| weak_collection->GetIsolate()); |
| DCHECK(table->IsKey(weak_collection->GetReadOnlyRoots(), *key)); |
| Handle<EphemeronHashTable> new_table = EphemeronHashTable::Put( |
| weak_collection->GetIsolate(), table, key, value, hash); |
| weak_collection->set_table(*new_table); |
| if (*table != *new_table) { |
| // Zap the old table since we didn't record slots for its elements. |
| EphemeronHashTable::FillEntriesWithHoles(table); |
| } |
| } |
| |
| |
| bool JSWeakCollection::Delete(Handle<JSWeakCollection> weak_collection, |
| Handle<Object> key, int32_t hash) { |
| DCHECK(key->IsJSReceiver() || key->IsSymbol()); |
| Handle<EphemeronHashTable> table( |
| EphemeronHashTable::cast(weak_collection->table()), |
| weak_collection->GetIsolate()); |
| DCHECK(table->IsKey(weak_collection->GetReadOnlyRoots(), *key)); |
| bool was_present = false; |
| Handle<EphemeronHashTable> new_table = EphemeronHashTable::Remove( |
| weak_collection->GetIsolate(), table, key, &was_present, hash); |
| weak_collection->set_table(*new_table); |
| if (*table != *new_table) { |
| // Zap the old table since we didn't record slots for its elements. |
| EphemeronHashTable::FillEntriesWithHoles(table); |
| } |
| return was_present; |
| } |
| |
| Handle<JSArray> JSWeakCollection::GetEntries(Handle<JSWeakCollection> holder, |
| int max_entries) { |
| Isolate* isolate = holder->GetIsolate(); |
| Handle<EphemeronHashTable> table(EphemeronHashTable::cast(holder->table()), |
| isolate); |
| if (max_entries == 0 || max_entries > table->NumberOfElements()) { |
| max_entries = table->NumberOfElements(); |
| } |
| int values_per_entry = holder->IsJSWeakMap() ? 2 : 1; |
| Handle<FixedArray> entries = |
| isolate->factory()->NewFixedArray(max_entries * values_per_entry); |
| // Recompute max_values because GC could have removed elements from the table. |
| if (max_entries > table->NumberOfElements()) { |
| max_entries = table->NumberOfElements(); |
| } |
| |
| { |
| DisallowHeapAllocation no_gc; |
| ReadOnlyRoots roots = ReadOnlyRoots(isolate); |
| int count = 0; |
| for (int i = 0; |
| count / values_per_entry < max_entries && i < table->Capacity(); i++) { |
| Object key; |
| if (table->ToKey(roots, i, &key)) { |
| entries->set(count++, key); |
| if (values_per_entry > 1) { |
| Object value = table->Lookup(handle(key, isolate)); |
| entries->set(count++, value); |
| } |
| } |
| } |
| DCHECK_EQ(max_entries * values_per_entry, count); |
| } |
| return isolate->factory()->NewJSArrayWithElements(entries); |
| } |
| |
| |
| Handle<PropertyCell> PropertyCell::InvalidateEntry( |
| Isolate* isolate, Handle<GlobalDictionary> dictionary, int entry) { |
| // Swap with a copy. |
| Handle<PropertyCell> cell(dictionary->CellAt(entry), isolate); |
| Handle<Name> name(cell->name(), isolate); |
| Handle<PropertyCell> new_cell = isolate->factory()->NewPropertyCell(name); |
| new_cell->set_value(cell->value()); |
| dictionary->ValueAtPut(entry, *new_cell); |
| bool is_the_hole = cell->value()->IsTheHole(isolate); |
| // Cell is officially mutable henceforth. |
| PropertyDetails details = cell->property_details(); |
| details = details.set_cell_type(is_the_hole ? PropertyCellType::kUninitialized |
| : PropertyCellType::kMutable); |
| new_cell->set_property_details(details); |
| // Old cell is ready for invalidation. |
| if (is_the_hole) { |
| cell->set_value(ReadOnlyRoots(isolate).undefined_value()); |
| } else { |
| cell->set_value(ReadOnlyRoots(isolate).the_hole_value()); |
| } |
| details = details.set_cell_type(PropertyCellType::kInvalidated); |
| cell->set_property_details(details); |
| cell->dependent_code()->DeoptimizeDependentCodeGroup( |
| isolate, DependentCode::kPropertyCellChangedGroup); |
| return new_cell; |
| } |
| |
| |
| PropertyCellConstantType PropertyCell::GetConstantType() { |
| if (value()->IsSmi()) return PropertyCellConstantType::kSmi; |
| return PropertyCellConstantType::kStableMap; |
| } |
| |
| |
| static bool RemainsConstantType(Handle<PropertyCell> cell, |
| Handle<Object> value) { |
| // TODO(dcarney): double->smi and smi->double transition from kConstant |
| if (cell->value()->IsSmi() && value->IsSmi()) { |
| return true; |
| } else if (cell->value()->IsHeapObject() && value->IsHeapObject()) { |
| return HeapObject::cast(cell->value())->map() == |
| HeapObject::cast(*value)->map() && |
| HeapObject::cast(*value)->map()->is_stable(); |
| } |
| return false; |
| } |
| |
| PropertyCellType PropertyCell::UpdatedType(Isolate* isolate, |
| Handle<PropertyCell> cell, |
| Handle<Object> value, |
| PropertyDetails details) { |
| PropertyCellType type = details.cell_type(); |
| DCHECK(!value->IsTheHole(isolate)); |
| if (cell->value()->IsTheHole(isolate)) { |
| switch (type) { |
| // Only allow a cell to transition once into constant state. |
| case PropertyCellType::kUninitialized: |
| if (value->IsUndefined(isolate)) return PropertyCellType::kUndefined; |
| return PropertyCellType::kConstant; |
| case PropertyCellType::kInvalidated: |
| return PropertyCellType::kMutable; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| switch (type) { |
| case PropertyCellType::kUndefined: |
| return PropertyCellType::kConstant; |
| case PropertyCellType::kConstant: |
| if (*value == cell->value()) return PropertyCellType::kConstant; |
| V8_FALLTHROUGH; |
| case PropertyCellType::kConstantType: |
| if (RemainsConstantType(cell, value)) { |
| return PropertyCellType::kConstantType; |
| } |
| V8_FALLTHROUGH; |
| case PropertyCellType::kMutable: |
| return PropertyCellType::kMutable; |
| } |
| UNREACHABLE(); |
| } |
| |
| Handle<PropertyCell> PropertyCell::PrepareForValue( |
| Isolate* isolate, Handle<GlobalDictionary> dictionary, int entry, |
| Handle<Object> value, PropertyDetails details) { |
| DCHECK(!value->IsTheHole(isolate)); |
| Handle<PropertyCell> cell(dictionary->CellAt(entry), isolate); |
| const PropertyDetails original_details = cell->property_details(); |
| // Data accesses could be cached in ics or optimized code. |
| bool invalidate = |
| (original_details.kind() == kData && details.kind() == kAccessor) || |
| (!original_details.IsReadOnly() && details.IsReadOnly()); |
| int index; |
| PropertyCellType old_type = original_details.cell_type(); |
| // Preserve the enumeration index unless the property was deleted or never |
| // initialized. |
| if (cell->value()->IsTheHole(isolate)) { |
| index = dictionary->NextEnumerationIndex(); |
| dictionary->SetNextEnumerationIndex(index + 1); |
| } else { |
| index = original_details.dictionary_index(); |
| } |
| DCHECK_LT(0, index); |
| details = details.set_index(index); |
| |
| PropertyCellType new_type = |
| UpdatedType(isolate, cell, value, original_details); |
| if (invalidate) { |
| cell = PropertyCell::InvalidateEntry(isolate, dictionary, entry); |
| } |
| |
| // Install new property details. |
| details = details.set_cell_type(new_type); |
| cell->set_property_details(details); |
| |
| if (new_type == PropertyCellType::kConstant || |
| new_type == PropertyCellType::kConstantType) { |
| // Store the value now to ensure that the cell contains the constant or |
| // type information. Otherwise subsequent store operation will turn |
| // the cell to mutable. |
| cell->set_value(*value); |
| } |
| |
| // Deopt when transitioning from a constant type. |
| if (!invalidate && (old_type != new_type || |
| original_details.IsReadOnly() != details.IsReadOnly())) { |
| cell->dependent_code()->DeoptimizeDependentCodeGroup( |
| isolate, DependentCode::kPropertyCellChangedGroup); |
| } |
| return cell; |
| } |
| |
| |
| // static |
| void PropertyCell::SetValueWithInvalidation(Isolate* isolate, |
| Handle<PropertyCell> cell, |
| Handle<Object> new_value) { |
| if (cell->value() != *new_value) { |
| cell->set_value(*new_value); |
| cell->dependent_code()->DeoptimizeDependentCodeGroup( |
| isolate, DependentCode::kPropertyCellChangedGroup); |
| } |
| } |
| |
| int JSGeneratorObject::source_position() const { |
| CHECK(is_suspended()); |
| DCHECK(function()->shared()->HasBytecodeArray()); |
| DCHECK(function()->shared()->GetBytecodeArray()->HasSourcePositionTable()); |
| |
| int code_offset = Smi::ToInt(input_or_debug_pos()); |
| |
| // The stored bytecode offset is relative to a different base than what |
| // is used in the source position table, hence the subtraction. |
| code_offset -= BytecodeArray::kHeaderSize - kHeapObjectTag; |
| AbstractCode code = |
| AbstractCode::cast(function()->shared()->GetBytecodeArray()); |
| return code->SourcePosition(code_offset); |
| } |
| |
| // static |
| AccessCheckInfo AccessCheckInfo::Get(Isolate* isolate, |
| Handle<JSObject> receiver) { |
| DisallowHeapAllocation no_gc; |
| DCHECK(receiver->map()->is_access_check_needed()); |
| Object maybe_constructor = receiver->map()->GetConstructor(); |
| if (maybe_constructor->IsFunctionTemplateInfo()) { |
| Object data_obj = |
| FunctionTemplateInfo::cast(maybe_constructor)->GetAccessCheckInfo(); |
| if (data_obj->IsUndefined(isolate)) return AccessCheckInfo(); |
| return AccessCheckInfo::cast(data_obj); |
| } |
| // Might happen for a detached context. |
| if (!maybe_constructor->IsJSFunction()) return AccessCheckInfo(); |
| JSFunction constructor = JSFunction::cast(maybe_constructor); |
| // Might happen for the debug context. |
| if (!constructor->shared()->IsApiFunction()) return AccessCheckInfo(); |
| |
| Object data_obj = |
| constructor->shared()->get_api_func_data()->GetAccessCheckInfo(); |
| if (data_obj->IsUndefined(isolate)) return AccessCheckInfo(); |
| |
| return AccessCheckInfo::cast(data_obj); |
| } |
| |
| |
| MaybeHandle<Name> FunctionTemplateInfo::TryGetCachedPropertyName( |
| Isolate* isolate, Handle<Object> getter) { |
| if (getter->IsFunctionTemplateInfo()) { |
| Handle<FunctionTemplateInfo> fti = |
| Handle<FunctionTemplateInfo>::cast(getter); |
| // Check if the accessor uses a cached property. |
| if (!fti->cached_property_name()->IsTheHole(isolate)) { |
| return handle(Name::cast(fti->cached_property_name()), isolate); |
| } |
| } |
| return MaybeHandle<Name>(); |
| } |
| |
| Address Smi::LexicographicCompare(Isolate* isolate, Smi x, Smi y) { |
| DisallowHeapAllocation no_allocation; |
| DisallowJavascriptExecution no_js(isolate); |
| |
| int x_value = Smi::ToInt(x); |
| int y_value = Smi::ToInt(y); |
| |
| // If the integers are equal so are the string representations. |
| if (x_value == y_value) return Smi::FromInt(0).ptr(); |
| |
| // If one of the integers is zero the normal integer order is the |
| // same as the lexicographic order of the string representations. |
| if (x_value == 0 || y_value == 0) { |
| return Smi::FromInt(x_value < y_value ? -1 : 1).ptr(); |
| } |
| |
| // If only one of the integers is negative the negative number is |
| // smallest because the char code of '-' is less than the char code |
| // of any digit. Otherwise, we make both values positive. |
| |
| // Use unsigned values otherwise the logic is incorrect for -MIN_INT on |
| // architectures using 32-bit Smis. |
| uint32_t x_scaled = x_value; |
| uint32_t y_scaled = y_value; |
| if (x_value < 0) { |
| if (y_value >= 0) { |
| return Smi::FromInt(-1).ptr(); |
| } else { |
| y_scaled = base::NegateWithWraparound(y_value); |
| } |
| x_scaled = base::NegateWithWraparound(x_value); |
| } else if (y_value < 0) { |
| return Smi::FromInt(1).ptr(); |
| } |
| |
| // clang-format off |
| static const uint32_t kPowersOf10[] = { |
| 1, 10, 100, 1000, |
| 10 * 1000, 100 * 1000, 1000 * 1000, 10 * 1000 * 1000, |
| 100 * 1000 * 1000, 1000 * 1000 * 1000}; |
| // clang-format on |
| |
| // If the integers have the same number of decimal digits they can be |
| // compared directly as the numeric order is the same as the |
| // lexicographic order. If one integer has fewer digits, it is scaled |
| // by some power of 10 to have the same number of digits as the longer |
| // integer. If the scaled integers are equal it means the shorter |
| // integer comes first in the lexicographic order. |
| |
| // From http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 |
| int x_log2 = 31 - base::bits::CountLeadingZeros(x_scaled); |
| int x_log10 = ((x_log2 + 1) * 1233) >> 12; |
| x_log10 -= x_scaled < kPowersOf10[x_log10]; |
| |
| int y_log2 = 31 - base::bits::CountLeadingZeros(y_scaled); |
| int y_log10 = ((y_log2 + 1) * 1233) >> 12; |
| y_log10 -= y_scaled < kPowersOf10[y_log10]; |
| |
| int tie = 0; |
| |
| if (x_log10 < y_log10) { |
| // X has fewer digits. We would like to simply scale up X but that |
| // might overflow, e.g when comparing 9 with 1_000_000_000, 9 would |
| // be scaled up to 9_000_000_000. So we scale up by the next |
| // smallest power and scale down Y to drop one digit. It is OK to |
| // drop one digit from the longer integer since the final digit is |
| // past the length of the shorter integer. |
| x_scaled *= kPowersOf10[y_log10 - x_log10 - 1]; |
| y_scaled /= 10; |
| tie = -1; |
| } else if (y_log10 < x_log10) { |
| y_scaled *= kPowersOf10[x_log10 - y_log10 - 1]; |
| x_scaled /= 10; |
| tie = 1; |
| } |
| |
| if (x_scaled < y_scaled) return Smi::FromInt(-1).ptr(); |
| if (x_scaled > y_scaled) return Smi::FromInt(1).ptr(); |
| return Smi::FromInt(tie).ptr(); |
| } |
| |
| // Force instantiation of template instances class. |
| // Please note this list is compiler dependent. |
| // Keep this at the end of this file |
| |
| template class HashTable<StringTable, StringTableShape>; |
| |
| template class HashTable<CompilationCacheTable, CompilationCacheShape>; |
| |
| template class HashTable<ObjectHashTable, ObjectHashTableShape>; |
| |
| template class HashTable<EphemeronHashTable, EphemeronHashTableShape>; |
| |
| template class ObjectHashTableBase<ObjectHashTable, ObjectHashTableShape>; |
| |
| template class ObjectHashTableBase<EphemeronHashTable, EphemeronHashTableShape>; |
| |
| template class Dictionary<NameDictionary, NameDictionaryShape>; |
| |
| template class Dictionary<GlobalDictionary, GlobalDictionaryShape>; |
| |
| template class EXPORT_TEMPLATE_DEFINE( |
| V8_EXPORT_PRIVATE) HashTable<NumberDictionary, NumberDictionaryShape>; |
| |
| template class EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE) |
| Dictionary<NumberDictionary, NumberDictionaryShape>; |
| |
| template class EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE) |
| HashTable<SimpleNumberDictionary, SimpleNumberDictionaryShape>; |
| |
| template class EXPORT_TEMPLATE_DEFINE(V8_EXPORT_PRIVATE) |
| Dictionary<SimpleNumberDictionary, SimpleNumberDictionaryShape>; |
| |
| template Handle<NameDictionary> |
| BaseNameDictionary<NameDictionary, NameDictionaryShape>::New( |
| Isolate*, int n, PretenureFlag pretenure, MinimumCapacity capacity_option); |
| |
| template Handle<GlobalDictionary> |
| BaseNameDictionary<GlobalDictionary, GlobalDictionaryShape>::New( |
| Isolate*, int n, PretenureFlag pretenure, MinimumCapacity capacity_option); |
| |
| template Handle<NameDictionary> |
| HashTable<NameDictionary, NameDictionaryShape>::New(Isolate*, int, |
| PretenureFlag, |
| MinimumCapacity); |
| |
| template Handle<ObjectHashSet> |
| HashTable<ObjectHashSet, ObjectHashSetShape>::New(Isolate*, int n, |
| PretenureFlag, |
| MinimumCapacity); |
| |
| template Handle<NameDictionary> |
| HashTable<NameDictionary, NameDictionaryShape>::Shrink(Isolate* isolate, |
| Handle<NameDictionary>, |
| int additionalCapacity); |
| |
| template Handle<NameDictionary> |
| BaseNameDictionary<NameDictionary, NameDictionaryShape>::Add( |
| Isolate* isolate, Handle<NameDictionary>, Handle<Name>, Handle<Object>, |
| PropertyDetails, int*); |
| |
| template Handle<GlobalDictionary> |
| BaseNameDictionary<GlobalDictionary, GlobalDictionaryShape>::Add( |
| Isolate* isolate, Handle<GlobalDictionary>, Handle<Name>, Handle<Object>, |
| PropertyDetails, int*); |
| |
| template void HashTable<GlobalDictionary, GlobalDictionaryShape>::Rehash( |
| ReadOnlyRoots roots); |
| |
| template Handle<NameDictionary> |
| BaseNameDictionary<NameDictionary, NameDictionaryShape>::EnsureCapacity( |
| Isolate* isolate, Handle<NameDictionary>, int); |
| |
| template void |
| BaseNameDictionary<GlobalDictionary, GlobalDictionaryShape>::CopyEnumKeysTo( |
| Isolate* isolate, Handle<GlobalDictionary> dictionary, |
| Handle<FixedArray> storage, KeyCollectionMode mode, |
| KeyAccumulator* accumulator); |
| |
| template void |
| BaseNameDictionary<NameDictionary, NameDictionaryShape>::CopyEnumKeysTo( |
| Isolate* isolate, Handle<NameDictionary> dictionary, |
| Handle<FixedArray> storage, KeyCollectionMode mode, |
| KeyAccumulator* accumulator); |
| |
| template Handle<FixedArray> |
| BaseNameDictionary<GlobalDictionary, GlobalDictionaryShape>::IterationIndices( |
| Isolate* isolate, Handle<GlobalDictionary> dictionary); |
| template void |
| BaseNameDictionary<GlobalDictionary, GlobalDictionaryShape>::CollectKeysTo( |
| Handle<GlobalDictionary> dictionary, KeyAccumulator* keys); |
| |
| template Handle<FixedArray> |
| BaseNameDictionary<NameDictionary, NameDictionaryShape>::IterationIndices( |
| Isolate* isolate, Handle<NameDictionary> dictionary); |
| template void |
| BaseNameDictionary<NameDictionary, NameDictionaryShape>::CollectKeysTo( |
| Handle<NameDictionary> dictionary, KeyAccumulator* keys); |
| |
| void JSFinalizationGroup::Cleanup( |
| Handle<JSFinalizationGroup> finalization_group, Isolate* isolate) { |
| // It's possible that the cleared_cells list is empty, since |
| // FinalizationGroup.unregister() removed all its elements before this task |
| // ran. In that case, don't call the cleanup function. |
| if (!finalization_group->cleared_cells()->IsUndefined(isolate)) { |
| // Construct the iterator. |
| Handle<JSFinalizationGroupCleanupIterator> iterator; |
| { |
| Handle<Map> cleanup_iterator_map( |
| isolate->native_context() |
| ->js_finalization_group_cleanup_iterator_map(), |
| isolate); |
| iterator = Handle<JSFinalizationGroupCleanupIterator>::cast( |
| isolate->factory()->NewJSObjectFromMap( |
| cleanup_iterator_map, NOT_TENURED, |
| Handle<AllocationSite>::null())); |
| iterator->set_finalization_group(*finalization_group); |
| } |
| Handle<Object> cleanup(finalization_group->cleanup(), isolate); |
| |
| v8::TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate)); |
| v8::Local<v8::Value> result; |
| MaybeHandle<Object> exception; |
| Handle<Object> args[] = {iterator}; |
| bool has_pending_exception = !ToLocal<Value>( |
| Execution::TryCall( |
| isolate, cleanup, |
| handle(ReadOnlyRoots(isolate).undefined_value(), isolate), 1, args, |
| Execution::MessageHandling::kReport, &exception), |
| &result); |
| // TODO(marja): (spec): What if there's an exception? |
| USE(has_pending_exception); |
| |
| // TODO(marja): (spec): Should the iterator be invalidated after the |
| // function returns? |
| } |
| } |
| |
| MaybeHandle<FixedArray> JSReceiver::GetPrivateEntries( |
| Isolate* isolate, Handle<JSReceiver> receiver) { |
| PropertyFilter key_filter = static_cast<PropertyFilter>(PRIVATE_NAMES_ONLY); |
| |
| Handle<FixedArray> keys; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, keys, |
| KeyAccumulator::GetKeys(receiver, KeyCollectionMode::kOwnOnly, key_filter, |
| GetKeysConversion::kConvertToString), |
| MaybeHandle<FixedArray>()); |
| |
| Handle<FixedArray> entries = |
| isolate->factory()->NewFixedArray(keys->length() * 2); |
| int length = 0; |
| |
| for (int i = 0; i < keys->length(); ++i) { |
| Handle<Object> obj_key = handle(keys->get(i), isolate); |
| Handle<Symbol> key(Symbol::cast(*obj_key), isolate); |
| CHECK(key->is_private_name()); |
| Handle<Object> value; |
| ASSIGN_RETURN_ON_EXCEPTION_VALUE( |
| isolate, value, Object::GetProperty(isolate, receiver, key), |
| MaybeHandle<FixedArray>()); |
| |
| entries->set(length++, *key); |
| entries->set(length++, *value); |
| } |
| DCHECK_EQ(length, entries->length()); |
| return FixedArray::ShrinkOrEmpty(isolate, entries, length); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |