| // Copyright 2016 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/api/api-inl.h" |
| #include "src/common/globals.h" |
| #include "src/execution/isolate.h" |
| #include "src/handles/handles-inl.h" |
| #include "src/heap/factory.h" |
| #include "src/init/v8.h" |
| #include "src/objects/function-kind.h" |
| #include "src/objects/objects-inl.h" |
| #include "test/cctest/cctest.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| static void CheckObject(Isolate* isolate, Handle<Object> obj, |
| const char* string) { |
| Handle<String> print_string = String::Flatten( |
| isolate, |
| Handle<String>::cast(Object::NoSideEffectsToString(isolate, obj))); |
| CHECK(print_string->IsOneByteEqualTo(CStrVector(string))); |
| } |
| |
| static void CheckSmi(Isolate* isolate, int value, const char* string) { |
| Handle<Object> handle(Smi::FromInt(value), isolate); |
| CheckObject(isolate, handle, string); |
| } |
| |
| static void CheckString(Isolate* isolate, const char* value, |
| const char* string) { |
| Handle<String> handle(isolate->factory()->NewStringFromAsciiChecked(value)); |
| CheckObject(isolate, handle, string); |
| } |
| |
| static void CheckNumber(Isolate* isolate, double value, const char* string) { |
| Handle<Object> number = isolate->factory()->NewNumber(value); |
| CHECK(number->IsNumber()); |
| CheckObject(isolate, number, string); |
| } |
| |
| static void CheckBoolean(Isolate* isolate, bool value, const char* string) { |
| CheckObject(isolate, value ? isolate->factory()->true_value() |
| : isolate->factory()->false_value(), |
| string); |
| } |
| |
| TEST(NoSideEffectsToString) { |
| CcTest::InitializeVM(); |
| Isolate* isolate = CcTest::i_isolate(); |
| Factory* factory = isolate->factory(); |
| |
| HandleScope scope(isolate); |
| |
| CheckString(isolate, "fisk hest", "fisk hest"); |
| CheckNumber(isolate, 42.3, "42.3"); |
| CheckSmi(isolate, 42, "42"); |
| CheckBoolean(isolate, true, "true"); |
| CheckBoolean(isolate, false, "false"); |
| CheckBoolean(isolate, false, "false"); |
| Handle<Object> smi_42 = handle(Smi::FromInt(42), isolate); |
| CheckObject(isolate, BigInt::FromNumber(isolate, smi_42).ToHandleChecked(), |
| "42"); |
| CheckObject(isolate, factory->undefined_value(), "undefined"); |
| CheckObject(isolate, factory->null_value(), "null"); |
| |
| CheckObject(isolate, factory->error_to_string(), "[object Error]"); |
| CheckObject(isolate, factory->unscopables_symbol(), |
| "Symbol(Symbol.unscopables)"); |
| CheckObject(isolate, factory->NewError(isolate->error_function(), |
| factory->empty_string()), |
| "Error"); |
| CheckObject(isolate, factory->NewError( |
| isolate->error_function(), |
| factory->NewStringFromAsciiChecked("fisk hest")), |
| "Error: fisk hest"); |
| CheckObject(isolate, factory->NewJSObject(isolate->object_function()), |
| "#<Object>"); |
| } |
| |
| TEST(EnumCache) { |
| LocalContext env; |
| v8::Isolate* isolate = env->GetIsolate(); |
| i::Factory* factory = CcTest::i_isolate()->factory(); |
| v8::HandleScope scope(isolate); |
| |
| // Create a nice transition tree: |
| // (a) --> (b) --> (c) shared DescriptorArray 1 |
| // | |
| // +---> (cc) shared DescriptorArray 2 |
| CompileRun( |
| "function O(a) { this.a = 1 };" |
| |
| "a = new O();" |
| |
| "b = new O();" |
| "b.b = 2;" |
| |
| "c = new O();" |
| "c.b = 2;" |
| "c.c = 3;" |
| |
| "cc = new O();" |
| "cc.b = 2;" |
| "cc.cc = 4;"); |
| |
| Handle<JSObject> a = Handle<JSObject>::cast(v8::Utils::OpenHandle( |
| *env->Global()->Get(env.local(), v8_str("a")).ToLocalChecked())); |
| Handle<JSObject> b = Handle<JSObject>::cast(v8::Utils::OpenHandle( |
| *env->Global()->Get(env.local(), v8_str("b")).ToLocalChecked())); |
| Handle<JSObject> c = Handle<JSObject>::cast(v8::Utils::OpenHandle( |
| *env->Global()->Get(env.local(), v8_str("c")).ToLocalChecked())); |
| Handle<JSObject> cc = Handle<JSObject>::cast(v8::Utils::OpenHandle( |
| *env->Global()->Get(env.local(), v8_str("cc")).ToLocalChecked())); |
| |
| // Check the transition tree. |
| CHECK_EQ(a->map().instance_descriptors(), b->map().instance_descriptors()); |
| CHECK_EQ(b->map().instance_descriptors(), c->map().instance_descriptors()); |
| CHECK_NE(c->map().instance_descriptors(), cc->map().instance_descriptors()); |
| CHECK_NE(b->map().instance_descriptors(), cc->map().instance_descriptors()); |
| |
| // Check that the EnumLength is unset. |
| CHECK_EQ(a->map().EnumLength(), kInvalidEnumCacheSentinel); |
| CHECK_EQ(b->map().EnumLength(), kInvalidEnumCacheSentinel); |
| CHECK_EQ(c->map().EnumLength(), kInvalidEnumCacheSentinel); |
| CHECK_EQ(cc->map().EnumLength(), kInvalidEnumCacheSentinel); |
| |
| // Check that the EnumCache is empty. |
| CHECK_EQ(a->map().instance_descriptors().enum_cache(), |
| *factory->empty_enum_cache()); |
| CHECK_EQ(b->map().instance_descriptors().enum_cache(), |
| *factory->empty_enum_cache()); |
| CHECK_EQ(c->map().instance_descriptors().enum_cache(), |
| *factory->empty_enum_cache()); |
| CHECK_EQ(cc->map().instance_descriptors().enum_cache(), |
| *factory->empty_enum_cache()); |
| |
| // The EnumCache is shared on the DescriptorArray, creating it on {cc} has no |
| // effect on the other maps. |
| CompileRun("var s = 0; for (let key in cc) { s += cc[key] };"); |
| { |
| CHECK_EQ(a->map().EnumLength(), kInvalidEnumCacheSentinel); |
| CHECK_EQ(b->map().EnumLength(), kInvalidEnumCacheSentinel); |
| CHECK_EQ(c->map().EnumLength(), kInvalidEnumCacheSentinel); |
| CHECK_EQ(cc->map().EnumLength(), 3); |
| |
| CHECK_EQ(a->map().instance_descriptors().enum_cache(), |
| *factory->empty_enum_cache()); |
| CHECK_EQ(b->map().instance_descriptors().enum_cache(), |
| *factory->empty_enum_cache()); |
| CHECK_EQ(c->map().instance_descriptors().enum_cache(), |
| *factory->empty_enum_cache()); |
| |
| EnumCache enum_cache = cc->map().instance_descriptors().enum_cache(); |
| CHECK_NE(enum_cache, *factory->empty_enum_cache()); |
| CHECK_EQ(enum_cache.keys().length(), 3); |
| CHECK_EQ(enum_cache.indices().length(), 3); |
| } |
| |
| // Initializing the EnumCache for the the topmost map {a} will not create the |
| // cache for the other maps. |
| CompileRun("var s = 0; for (let key in a) { s += a[key] };"); |
| { |
| CHECK_EQ(a->map().EnumLength(), 1); |
| CHECK_EQ(b->map().EnumLength(), kInvalidEnumCacheSentinel); |
| CHECK_EQ(c->map().EnumLength(), kInvalidEnumCacheSentinel); |
| CHECK_EQ(cc->map().EnumLength(), 3); |
| |
| // The enum cache is shared on the descriptor array of maps {a}, {b} and |
| // {c} only. |
| EnumCache enum_cache = a->map().instance_descriptors().enum_cache(); |
| CHECK_NE(enum_cache, *factory->empty_enum_cache()); |
| CHECK_NE(cc->map().instance_descriptors().enum_cache(), |
| *factory->empty_enum_cache()); |
| CHECK_NE(cc->map().instance_descriptors().enum_cache(), enum_cache); |
| CHECK_EQ(a->map().instance_descriptors().enum_cache(), enum_cache); |
| CHECK_EQ(b->map().instance_descriptors().enum_cache(), enum_cache); |
| CHECK_EQ(c->map().instance_descriptors().enum_cache(), enum_cache); |
| |
| CHECK_EQ(enum_cache.keys().length(), 1); |
| CHECK_EQ(enum_cache.indices().length(), 1); |
| } |
| |
| // Creating the EnumCache for {c} will create a new EnumCache on the shared |
| // DescriptorArray. |
| Handle<EnumCache> previous_enum_cache( |
| a->map().instance_descriptors().enum_cache(), a->GetIsolate()); |
| Handle<FixedArray> previous_keys(previous_enum_cache->keys(), |
| a->GetIsolate()); |
| Handle<FixedArray> previous_indices(previous_enum_cache->indices(), |
| a->GetIsolate()); |
| CompileRun("var s = 0; for (let key in c) { s += c[key] };"); |
| { |
| CHECK_EQ(a->map().EnumLength(), 1); |
| CHECK_EQ(b->map().EnumLength(), kInvalidEnumCacheSentinel); |
| CHECK_EQ(c->map().EnumLength(), 3); |
| CHECK_EQ(cc->map().EnumLength(), 3); |
| |
| EnumCache enum_cache = c->map().instance_descriptors().enum_cache(); |
| CHECK_NE(enum_cache, *factory->empty_enum_cache()); |
| // The keys and indices caches are updated. |
| CHECK_EQ(enum_cache, *previous_enum_cache); |
| CHECK_NE(enum_cache.keys(), *previous_keys); |
| CHECK_NE(enum_cache.indices(), *previous_indices); |
| CHECK_EQ(previous_keys->length(), 1); |
| CHECK_EQ(previous_indices->length(), 1); |
| CHECK_EQ(enum_cache.keys().length(), 3); |
| CHECK_EQ(enum_cache.indices().length(), 3); |
| |
| // The enum cache is shared on the descriptor array of maps {a}, {b} and |
| // {c} only. |
| CHECK_NE(cc->map().instance_descriptors().enum_cache(), |
| *factory->empty_enum_cache()); |
| CHECK_NE(cc->map().instance_descriptors().enum_cache(), enum_cache); |
| CHECK_NE(cc->map().instance_descriptors().enum_cache(), |
| *previous_enum_cache); |
| CHECK_EQ(a->map().instance_descriptors().enum_cache(), enum_cache); |
| CHECK_EQ(b->map().instance_descriptors().enum_cache(), enum_cache); |
| CHECK_EQ(c->map().instance_descriptors().enum_cache(), enum_cache); |
| } |
| |
| // {b} can reuse the existing EnumCache, hence we only need to set the correct |
| // EnumLength on the map without modifying the cache itself. |
| previous_enum_cache = |
| handle(a->map().instance_descriptors().enum_cache(), a->GetIsolate()); |
| previous_keys = handle(previous_enum_cache->keys(), a->GetIsolate()); |
| previous_indices = handle(previous_enum_cache->indices(), a->GetIsolate()); |
| CompileRun("var s = 0; for (let key in b) { s += b[key] };"); |
| { |
| CHECK_EQ(a->map().EnumLength(), 1); |
| CHECK_EQ(b->map().EnumLength(), 2); |
| CHECK_EQ(c->map().EnumLength(), 3); |
| CHECK_EQ(cc->map().EnumLength(), 3); |
| |
| EnumCache enum_cache = c->map().instance_descriptors().enum_cache(); |
| CHECK_NE(enum_cache, *factory->empty_enum_cache()); |
| // The keys and indices caches are not updated. |
| CHECK_EQ(enum_cache, *previous_enum_cache); |
| CHECK_EQ(enum_cache.keys(), *previous_keys); |
| CHECK_EQ(enum_cache.indices(), *previous_indices); |
| CHECK_EQ(enum_cache.keys().length(), 3); |
| CHECK_EQ(enum_cache.indices().length(), 3); |
| |
| // The enum cache is shared on the descriptor array of maps {a}, {b} and |
| // {c} only. |
| CHECK_NE(cc->map().instance_descriptors().enum_cache(), |
| *factory->empty_enum_cache()); |
| CHECK_NE(cc->map().instance_descriptors().enum_cache(), enum_cache); |
| CHECK_NE(cc->map().instance_descriptors().enum_cache(), |
| *previous_enum_cache); |
| CHECK_EQ(a->map().instance_descriptors().enum_cache(), enum_cache); |
| CHECK_EQ(b->map().instance_descriptors().enum_cache(), enum_cache); |
| CHECK_EQ(c->map().instance_descriptors().enum_cache(), enum_cache); |
| } |
| } |
| |
| #define TEST_FUNCTION_KIND(Name) \ |
| TEST(Name) { \ |
| for (int i = 0; i < FunctionKind::kLastFunctionKind; i++) { \ |
| FunctionKind kind = static_cast<FunctionKind>(i); \ |
| CHECK_EQ(FunctionKind##Name(kind), Name(kind)); \ |
| } \ |
| } |
| |
| bool FunctionKindIsArrowFunction(FunctionKind kind) { |
| switch (kind) { |
| case FunctionKind::kArrowFunction: |
| case FunctionKind::kAsyncArrowFunction: |
| return true; |
| default: |
| return false; |
| } |
| } |
| TEST_FUNCTION_KIND(IsArrowFunction) |
| |
| bool FunctionKindIsAsyncGeneratorFunction(FunctionKind kind) { |
| switch (kind) { |
| case FunctionKind::kAsyncConciseGeneratorMethod: |
| case FunctionKind::kAsyncGeneratorFunction: |
| return true; |
| default: |
| return false; |
| } |
| } |
| TEST_FUNCTION_KIND(IsAsyncGeneratorFunction) |
| |
| bool FunctionKindIsGeneratorFunction(FunctionKind kind) { |
| switch (kind) { |
| case FunctionKind::kConciseGeneratorMethod: |
| case FunctionKind::kAsyncConciseGeneratorMethod: |
| case FunctionKind::kGeneratorFunction: |
| case FunctionKind::kAsyncGeneratorFunction: |
| return true; |
| default: |
| return false; |
| } |
| } |
| TEST_FUNCTION_KIND(IsGeneratorFunction) |
| |
| bool FunctionKindIsAsyncFunction(FunctionKind kind) { |
| switch (kind) { |
| case FunctionKind::kAsyncFunction: |
| case FunctionKind::kAsyncArrowFunction: |
| case FunctionKind::kAsyncConciseMethod: |
| case FunctionKind::kAsyncConciseGeneratorMethod: |
| case FunctionKind::kAsyncGeneratorFunction: |
| return true; |
| default: |
| return false; |
| } |
| } |
| TEST_FUNCTION_KIND(IsAsyncFunction) |
| |
| bool FunctionKindIsConciseMethod(FunctionKind kind) { |
| switch (kind) { |
| case FunctionKind::kConciseMethod: |
| case FunctionKind::kConciseGeneratorMethod: |
| case FunctionKind::kAsyncConciseMethod: |
| case FunctionKind::kAsyncConciseGeneratorMethod: |
| case FunctionKind::kClassMembersInitializerFunction: |
| return true; |
| default: |
| return false; |
| } |
| } |
| TEST_FUNCTION_KIND(IsConciseMethod) |
| |
| bool FunctionKindIsAccessorFunction(FunctionKind kind) { |
| switch (kind) { |
| case FunctionKind::kGetterFunction: |
| case FunctionKind::kSetterFunction: |
| return true; |
| default: |
| return false; |
| } |
| } |
| TEST_FUNCTION_KIND(IsAccessorFunction) |
| |
| bool FunctionKindIsDefaultConstructor(FunctionKind kind) { |
| switch (kind) { |
| case FunctionKind::kDefaultBaseConstructor: |
| case FunctionKind::kDefaultDerivedConstructor: |
| return true; |
| default: |
| return false; |
| } |
| } |
| TEST_FUNCTION_KIND(IsDefaultConstructor) |
| |
| bool FunctionKindIsBaseConstructor(FunctionKind kind) { |
| switch (kind) { |
| case FunctionKind::kBaseConstructor: |
| case FunctionKind::kDefaultBaseConstructor: |
| return true; |
| default: |
| return false; |
| } |
| } |
| TEST_FUNCTION_KIND(IsBaseConstructor) |
| |
| bool FunctionKindIsDerivedConstructor(FunctionKind kind) { |
| switch (kind) { |
| case FunctionKind::kDefaultDerivedConstructor: |
| case FunctionKind::kDerivedConstructor: |
| return true; |
| default: |
| return false; |
| } |
| } |
| TEST_FUNCTION_KIND(IsDerivedConstructor) |
| |
| bool FunctionKindIsClassConstructor(FunctionKind kind) { |
| switch (kind) { |
| case FunctionKind::kBaseConstructor: |
| case FunctionKind::kDefaultBaseConstructor: |
| case FunctionKind::kDefaultDerivedConstructor: |
| case FunctionKind::kDerivedConstructor: |
| return true; |
| default: |
| return false; |
| } |
| } |
| TEST_FUNCTION_KIND(IsClassConstructor) |
| |
| bool FunctionKindIsConstructable(FunctionKind kind) { |
| switch (kind) { |
| case FunctionKind::kGetterFunction: |
| case FunctionKind::kSetterFunction: |
| case FunctionKind::kArrowFunction: |
| case FunctionKind::kAsyncArrowFunction: |
| case FunctionKind::kAsyncFunction: |
| case FunctionKind::kAsyncConciseMethod: |
| case FunctionKind::kAsyncConciseGeneratorMethod: |
| case FunctionKind::kAsyncGeneratorFunction: |
| case FunctionKind::kGeneratorFunction: |
| case FunctionKind::kConciseGeneratorMethod: |
| case FunctionKind::kConciseMethod: |
| case FunctionKind::kClassMembersInitializerFunction: |
| return false; |
| default: |
| return true; |
| } |
| } |
| TEST_FUNCTION_KIND(IsConstructable) |
| |
| bool FunctionKindIsStrictFunctionWithoutPrototype(FunctionKind kind) { |
| return IsArrowFunction(kind) || IsConciseMethod(kind) || |
| IsAccessorFunction(kind); |
| } |
| TEST_FUNCTION_KIND(IsStrictFunctionWithoutPrototype) |
| |
| #undef TEST_FUNCTION_KIND |
| |
| } // namespace internal |
| } // namespace v8 |