// 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 obj, const char* string) { Handle print_string = String::Flatten( isolate, Handle::cast(Object::NoSideEffectsToString(isolate, obj))); CHECK(print_string->IsOneByteEqualTo(CStrVector(string))); } static void CheckSmi(Isolate* isolate, int value, const char* string) { Handle handle(Smi::FromInt(value), isolate); CheckObject(isolate, handle, string); } static void CheckString(Isolate* isolate, const char* value, const char* string) { Handle handle(isolate->factory()->NewStringFromAsciiChecked(value)); CheckObject(isolate, handle, string); } static void CheckNumber(Isolate* isolate, double value, const char* string) { Handle 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 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()), "#"); } 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 a = Handle::cast(v8::Utils::OpenHandle( *env->Global()->Get(env.local(), v8_str("a")).ToLocalChecked())); Handle b = Handle::cast(v8::Utils::OpenHandle( *env->Global()->Get(env.local(), v8_str("b")).ToLocalChecked())); Handle c = Handle::cast(v8::Utils::OpenHandle( *env->Global()->Get(env.local(), v8_str("c")).ToLocalChecked())); Handle cc = Handle::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 previous_enum_cache( a->map().instance_descriptors().enum_cache(), a->GetIsolate()); Handle previous_keys(previous_enum_cache->keys(), a->GetIsolate()); Handle 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(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