2016-08-03 12:20:39 +00:00
|
|
|
// 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.
|
|
|
|
|
2017-09-11 11:40:20 +00:00
|
|
|
#include "src/api.h"
|
2016-09-01 12:01:33 +00:00
|
|
|
#include "src/factory.h"
|
|
|
|
#include "src/handles-inl.h"
|
|
|
|
#include "src/handles.h"
|
|
|
|
#include "src/isolate.h"
|
2017-01-09 13:43:28 +00:00
|
|
|
#include "src/objects-inl.h"
|
2016-09-01 12:01:33 +00:00
|
|
|
#include "src/objects.h"
|
2016-08-03 12:20:39 +00:00
|
|
|
#include "src/v8.h"
|
|
|
|
#include "test/cctest/cctest.h"
|
|
|
|
|
2017-08-11 11:22:28 +00:00
|
|
|
namespace v8 {
|
|
|
|
namespace internal {
|
2016-08-03 12:20:39 +00:00
|
|
|
|
|
|
|
static void CheckObject(Isolate* isolate, Handle<Object> obj,
|
|
|
|
const char* string) {
|
|
|
|
Object* print_string = *Object::NoSideEffectsToString(isolate, obj);
|
|
|
|
CHECK(String::cast(print_string)->IsUtf8EqualTo(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");
|
2017-11-17 20:53:16 +00:00
|
|
|
Handle<Object> smi_42 = handle(Smi::FromInt(42), isolate);
|
|
|
|
CheckObject(isolate, BigInt::FromNumber(isolate, smi_42).ToHandleChecked(),
|
|
|
|
"42");
|
2016-08-03 12:20:39 +00:00
|
|
|
CheckObject(isolate, factory->undefined_value(), "undefined");
|
|
|
|
CheckObject(isolate, factory->null_value(), "null");
|
|
|
|
|
|
|
|
CheckObject(isolate, factory->error_to_string(), "[object Error]");
|
2017-09-08 05:35:10 +00:00
|
|
|
CheckObject(isolate, factory->unscopables_symbol(),
|
|
|
|
"Symbol(Symbol.unscopables)");
|
2016-08-03 12:20:39 +00:00
|
|
|
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>");
|
|
|
|
}
|
2017-08-11 11:22:28 +00:00
|
|
|
|
2017-08-31 09:22:36 +00:00
|
|
|
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()->GetEnumCache(),
|
|
|
|
*factory->empty_enum_cache());
|
|
|
|
CHECK_EQ(b->map()->instance_descriptors()->GetEnumCache(),
|
|
|
|
*factory->empty_enum_cache());
|
|
|
|
CHECK_EQ(c->map()->instance_descriptors()->GetEnumCache(),
|
|
|
|
*factory->empty_enum_cache());
|
|
|
|
CHECK_EQ(cc->map()->instance_descriptors()->GetEnumCache(),
|
|
|
|
*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()->GetEnumCache(),
|
|
|
|
*factory->empty_enum_cache());
|
|
|
|
CHECK_EQ(b->map()->instance_descriptors()->GetEnumCache(),
|
|
|
|
*factory->empty_enum_cache());
|
|
|
|
CHECK_EQ(c->map()->instance_descriptors()->GetEnumCache(),
|
|
|
|
*factory->empty_enum_cache());
|
|
|
|
|
|
|
|
EnumCache* enum_cache = cc->map()->instance_descriptors()->GetEnumCache();
|
|
|
|
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()->GetEnumCache();
|
|
|
|
CHECK_NE(enum_cache, *factory->empty_enum_cache());
|
|
|
|
CHECK_NE(cc->map()->instance_descriptors()->GetEnumCache(),
|
|
|
|
*factory->empty_enum_cache());
|
|
|
|
CHECK_NE(cc->map()->instance_descriptors()->GetEnumCache(), enum_cache);
|
|
|
|
CHECK_EQ(a->map()->instance_descriptors()->GetEnumCache(), enum_cache);
|
|
|
|
CHECK_EQ(b->map()->instance_descriptors()->GetEnumCache(), enum_cache);
|
|
|
|
CHECK_EQ(c->map()->instance_descriptors()->GetEnumCache(), 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()->GetEnumCache());
|
|
|
|
Handle<FixedArray> previous_keys(previous_enum_cache->keys());
|
|
|
|
Handle<FixedArray> previous_indices(previous_enum_cache->indices());
|
|
|
|
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()->GetEnumCache();
|
|
|
|
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()->GetEnumCache(),
|
|
|
|
*factory->empty_enum_cache());
|
|
|
|
CHECK_NE(cc->map()->instance_descriptors()->GetEnumCache(), enum_cache);
|
|
|
|
CHECK_NE(cc->map()->instance_descriptors()->GetEnumCache(),
|
|
|
|
*previous_enum_cache);
|
|
|
|
CHECK_EQ(a->map()->instance_descriptors()->GetEnumCache(), enum_cache);
|
|
|
|
CHECK_EQ(b->map()->instance_descriptors()->GetEnumCache(), enum_cache);
|
|
|
|
CHECK_EQ(c->map()->instance_descriptors()->GetEnumCache(), 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()->GetEnumCache());
|
|
|
|
previous_keys = handle(previous_enum_cache->keys());
|
|
|
|
previous_indices = handle(previous_enum_cache->indices());
|
|
|
|
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()->GetEnumCache();
|
|
|
|
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()->GetEnumCache(),
|
|
|
|
*factory->empty_enum_cache());
|
|
|
|
CHECK_NE(cc->map()->instance_descriptors()->GetEnumCache(), enum_cache);
|
|
|
|
CHECK_NE(cc->map()->instance_descriptors()->GetEnumCache(),
|
|
|
|
*previous_enum_cache);
|
|
|
|
CHECK_EQ(a->map()->instance_descriptors()->GetEnumCache(), enum_cache);
|
|
|
|
CHECK_EQ(b->map()->instance_descriptors()->GetEnumCache(), enum_cache);
|
|
|
|
CHECK_EQ(c->map()->instance_descriptors()->GetEnumCache(), enum_cache);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-11 11:22:28 +00:00
|
|
|
} // namespace internal
|
|
|
|
} // namespace v8
|