[type profile] Collect return types.
Collect type information of return values. Use *one* feedback slot per function for all its return statements. For assignments, we currently use several slots per function, because not all assignments refer to the same variable. Instead of the variable names, pass the source location and print the function name. Add an integration test for --type-profile that checks for crashes. Remove type feedback for assignments for now as it convolutes the output. ************ Function with 2 return statements ******** function testFunction(param, flag) { // We want to test 2 different return positions in one function. if (flag) { var first_var = param; return first_var; } var second_var = param; return second_var; } testFunction({}); testFunction(123, true); testFunction('hello'); testFunction(undefined); ******************************************************* ************* Sample Output *************************** Function: testFunction 424: Object 374: number 424: string 424: undefined ******************************************************* Missing work: * Handle fall-off returns * Collect types for parameters * Remove duplicates from the list of collected types and use a common base class. BUG=v8:5935 Review-Url: https://codereview.chromium.org/2755973002 Cr-Commit-Position: refs/heads/master@{#43956}
This commit is contained in:
parent
99743ad460
commit
de04df7412
@ -33,6 +33,13 @@ class AstNumberingVisitor final : public AstVisitor<AstNumberingVisitor> {
|
||||
|
||||
bool Renumber(FunctionLiteral* node);
|
||||
|
||||
FeedbackSlot TypeProfileSlotForReturnValue() const {
|
||||
if (collect_type_profile_) {
|
||||
DCHECK(!type_profile_for_return_value_.IsInvalid());
|
||||
}
|
||||
return type_profile_for_return_value_;
|
||||
}
|
||||
|
||||
private:
|
||||
// AST node visitor interface.
|
||||
#define DEFINE_VISIT(type) void Visit##type(type* node);
|
||||
@ -104,6 +111,7 @@ class AstNumberingVisitor final : public AstVisitor<AstNumberingVisitor> {
|
||||
BailoutReason dont_optimize_reason_;
|
||||
HandlerTable::CatchPrediction catch_prediction_;
|
||||
bool collect_type_profile_;
|
||||
FeedbackSlot type_profile_for_return_value_;
|
||||
|
||||
DEFINE_AST_VISITOR_SUBCLASS_MEMBERS();
|
||||
DISALLOW_COPY_AND_ASSIGN(AstNumberingVisitor);
|
||||
@ -237,6 +245,8 @@ void AstNumberingVisitor::VisitReturnStatement(ReturnStatement* node) {
|
||||
IncrementNodeCount();
|
||||
Visit(node->expression());
|
||||
|
||||
node->SetTypeProfileSlot(TypeProfileSlotForReturnValue());
|
||||
|
||||
DCHECK(!node->is_async_return() ||
|
||||
properties_.flags() & AstProperties::kMustUseIgnitionTurbo);
|
||||
}
|
||||
@ -691,6 +701,11 @@ bool AstNumberingVisitor::Renumber(FunctionLiteral* node) {
|
||||
|
||||
LanguageModeScope language_mode_scope(this, node->language_mode());
|
||||
|
||||
if (collect_type_profile_) {
|
||||
type_profile_for_return_value_ =
|
||||
properties_.get_spec()->AddTypeProfileSlot();
|
||||
}
|
||||
|
||||
VisitDeclarations(scope->declarations());
|
||||
VisitStatements(node->body());
|
||||
|
||||
|
@ -904,6 +904,15 @@ class ReturnStatement final : public JumpStatement {
|
||||
Type type() const { return TypeField::decode(bit_field_); }
|
||||
bool is_async_return() const { return type() == kAsyncReturn; }
|
||||
|
||||
FeedbackSlot TypeProfileSlot() const {
|
||||
DCHECK(HasTypeProfileSlot());
|
||||
return type_profile_slot_;
|
||||
}
|
||||
|
||||
void SetTypeProfileSlot(FeedbackSlot slot) { type_profile_slot_ = slot; }
|
||||
|
||||
bool HasTypeProfileSlot() const { return !type_profile_slot_.IsInvalid(); }
|
||||
|
||||
private:
|
||||
friend class AstNodeFactory;
|
||||
|
||||
@ -912,6 +921,8 @@ class ReturnStatement final : public JumpStatement {
|
||||
bit_field_ |= TypeField::encode(type);
|
||||
}
|
||||
|
||||
FeedbackSlot type_profile_slot_;
|
||||
|
||||
Expression* expression_;
|
||||
|
||||
class TypeField
|
||||
|
@ -806,8 +806,8 @@ void BytecodeGraphBuilder::VisitStaDataPropertyInLiteral() {
|
||||
void BytecodeGraphBuilder::VisitCollectTypeProfile() {
|
||||
PrepareEagerCheckpoint();
|
||||
|
||||
Node* name =
|
||||
environment()->LookupRegister(bytecode_iterator().GetRegisterOperand(0));
|
||||
Node* position =
|
||||
jsgraph()->Constant(bytecode_iterator().GetImmediateOperand(0));
|
||||
Node* value = environment()->LookupAccumulator();
|
||||
Node* index = jsgraph()->Constant(bytecode_iterator().GetIndexOperand(1));
|
||||
|
||||
@ -815,7 +815,7 @@ void BytecodeGraphBuilder::VisitCollectTypeProfile() {
|
||||
|
||||
const Operator* op = javascript()->CallRuntime(Runtime::kCollectTypeProfile);
|
||||
|
||||
Node* node = NewNode(op, name, value, vector, index);
|
||||
Node* node = NewNode(op, position, value, vector, index);
|
||||
environment()->RecordAfterState(node, Environment::kAttachFrameState);
|
||||
}
|
||||
|
||||
|
@ -1049,7 +1049,7 @@ InlineCacheState CollectTypeProfileNexus::StateFromFeedback() const {
|
||||
return MONOMORPHIC;
|
||||
}
|
||||
|
||||
void CollectTypeProfileNexus::Collect(Handle<Name> type) {
|
||||
void CollectTypeProfileNexus::Collect(Handle<Name> type, int position) {
|
||||
Isolate* isolate = GetIsolate();
|
||||
|
||||
Object* const feedback = GetFeedback();
|
||||
@ -1060,9 +1060,13 @@ void CollectTypeProfileNexus::Collect(Handle<Name> type) {
|
||||
} else {
|
||||
types = Handle<ArrayList>(ArrayList::cast(feedback), isolate);
|
||||
}
|
||||
|
||||
Handle<Tuple2> entry = isolate->factory()->NewTuple2(
|
||||
type, Handle<Smi>(Smi::FromInt(position), isolate));
|
||||
|
||||
// TODO(franzih): Somehow sort this list. Either avoid duplicates
|
||||
// or use the common base type.
|
||||
SetFeedback(*ArrayList::Add(types, type));
|
||||
SetFeedback(*ArrayList::Add(types, entry));
|
||||
}
|
||||
|
||||
void CollectTypeProfileNexus::Print() const {
|
||||
@ -1078,8 +1082,13 @@ void CollectTypeProfileNexus::Print() const {
|
||||
list = Handle<ArrayList>(ArrayList::cast(feedback), isolate);
|
||||
|
||||
for (int i = 0; i < list->Length(); i++) {
|
||||
String* name = String::cast(list->Get(i));
|
||||
PrintF("%s\n", name->ToCString().get());
|
||||
Handle<Object> maybe_entry = Handle<Object>(list->Get(i), isolate);
|
||||
DCHECK(maybe_entry->IsTuple2());
|
||||
Handle<Tuple2> entry = Handle<Tuple2>::cast(maybe_entry);
|
||||
Handle<String> type =
|
||||
Handle<String>(String::cast(entry->value1()), isolate);
|
||||
int position = Smi::cast(entry->value2())->value();
|
||||
PrintF("%d: %s\n", position, type->ToCString().get());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -765,7 +765,7 @@ class CollectTypeProfileNexus : public FeedbackNexus {
|
||||
}
|
||||
|
||||
// Add a type to the list of types.
|
||||
void Collect(Handle<Name> type);
|
||||
void Collect(Handle<Name> type, int position);
|
||||
|
||||
// Dump the types to stdout.
|
||||
// TODO(franzih): pass this information to the debugger protocol instead of
|
||||
|
@ -640,9 +640,9 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::StoreDataPropertyInLiteral(
|
||||
}
|
||||
|
||||
BytecodeArrayBuilder& BytecodeArrayBuilder::CollectTypeProfile(
|
||||
Register name, int feedback_slot) {
|
||||
int position, int feedback_slot) {
|
||||
DCHECK(FLAG_type_profile);
|
||||
OutputCollectTypeProfile(name, feedback_slot);
|
||||
OutputCollectTypeProfile(position, feedback_slot);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -141,7 +141,7 @@ class V8_EXPORT_PRIVATE BytecodeArrayBuilder final
|
||||
// Collect type information for developer tools. The value for which we
|
||||
// record the type is stored in the accumulator.
|
||||
// TODO(franzih): Do not pass the name, instead use the source position.
|
||||
BytecodeArrayBuilder& CollectTypeProfile(Register name, int feedback_slot);
|
||||
BytecodeArrayBuilder& CollectTypeProfile(int position, int feedback_slot);
|
||||
|
||||
// Store a property named by a property name. The value to be stored should be
|
||||
// in the accumulator.
|
||||
|
@ -1093,6 +1093,12 @@ void BytecodeGenerator::VisitReturnStatement(ReturnStatement* stmt) {
|
||||
builder()->SetStatementPosition(stmt);
|
||||
VisitForAccumulatorValue(stmt->expression());
|
||||
|
||||
if (stmt->HasTypeProfileSlot()) {
|
||||
FeedbackSlot collect_type_feedback_slot = stmt->TypeProfileSlot();
|
||||
builder()->CollectTypeProfile(stmt->position(),
|
||||
feedback_index(collect_type_feedback_slot));
|
||||
}
|
||||
|
||||
if (stmt->is_async_return()) {
|
||||
execution_control()->AsyncReturnAccumulator();
|
||||
} else {
|
||||
@ -2211,34 +2217,18 @@ void BytecodeGenerator::VisitAssignment(Assignment* expr) {
|
||||
LhsKind assign_type = Property::GetAssignType(property);
|
||||
|
||||
// Evaluate LHS expression.
|
||||
Register lhs_name;
|
||||
if (expr->HasTypeProfileSlot()) {
|
||||
lhs_name = register_allocator()->NewRegister();
|
||||
}
|
||||
|
||||
switch (assign_type) {
|
||||
case VARIABLE:
|
||||
if (expr->HasTypeProfileSlot()) {
|
||||
builder()
|
||||
->LoadLiteral(expr->target()->AsVariableProxy()->var()->raw_name())
|
||||
.StoreAccumulatorInRegister(lhs_name);
|
||||
}
|
||||
// Nothing to do to evaluate variable assignment LHS.
|
||||
break;
|
||||
case NAMED_PROPERTY: {
|
||||
object = VisitForRegisterValue(property->obj());
|
||||
name = property->key()->AsLiteral()->AsRawPropertyName();
|
||||
if (expr->HasTypeProfileSlot()) {
|
||||
builder()->LoadLiteral(name).StoreAccumulatorInRegister(lhs_name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case KEYED_PROPERTY: {
|
||||
object = VisitForRegisterValue(property->obj());
|
||||
key = VisitForRegisterValue(property->key());
|
||||
if (expr->HasTypeProfileSlot()) {
|
||||
builder()->StoreAccumulatorInRegister(lhs_name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NAMED_SUPER_PROPERTY: {
|
||||
@ -2251,9 +2241,6 @@ void BytecodeGenerator::VisitAssignment(Assignment* expr) {
|
||||
builder()
|
||||
->LoadLiteral(property->key()->AsLiteral()->AsRawPropertyName())
|
||||
.StoreAccumulatorInRegister(super_property_args[2]);
|
||||
if (expr->HasTypeProfileSlot()) {
|
||||
builder()->StoreAccumulatorInRegister(lhs_name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case KEYED_SUPER_PROPERTY: {
|
||||
@ -2264,10 +2251,6 @@ void BytecodeGenerator::VisitAssignment(Assignment* expr) {
|
||||
VisitForRegisterValue(super_property->home_object(),
|
||||
super_property_args[1]);
|
||||
VisitForRegisterValue(property->key(), super_property_args[2]);
|
||||
if (expr->HasTypeProfileSlot()) {
|
||||
builder()->StoreAccumulatorInRegister(lhs_name);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2358,10 +2341,12 @@ void BytecodeGenerator::VisitAssignment(Assignment* expr) {
|
||||
}
|
||||
|
||||
// Value is in accumulator.
|
||||
if (expr->HasTypeProfileSlot()) {
|
||||
// TODO(franzih): Collect type profile once we can handle more than just
|
||||
// return statements.
|
||||
if (false && expr->HasTypeProfileSlot()) {
|
||||
FeedbackSlot collect_type_feedback_slot = expr->TypeProfileSlot();
|
||||
|
||||
builder()->CollectTypeProfile(lhs_name,
|
||||
builder()->CollectTypeProfile(expr->position(),
|
||||
feedback_index(collect_type_feedback_slot));
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ namespace interpreter {
|
||||
OperandType::kReg, OperandType::kIdx) \
|
||||
V(StaDataPropertyInLiteral, AccumulatorUse::kRead, OperandType::kReg, \
|
||||
OperandType::kReg, OperandType::kFlag8, OperandType::kIdx) \
|
||||
V(CollectTypeProfile, AccumulatorUse::kRead, OperandType::kReg, \
|
||||
V(CollectTypeProfile, AccumulatorUse::kRead, OperandType::kImm, \
|
||||
OperandType::kIdx) \
|
||||
\
|
||||
/* Binary Operators */ \
|
||||
|
@ -822,14 +822,14 @@ void InterpreterGenerator::DoStaDataPropertyInLiteral(
|
||||
|
||||
void InterpreterGenerator::DoCollectTypeProfile(
|
||||
InterpreterAssembler* assembler) {
|
||||
Node* name = __ LoadRegister(__ BytecodeOperandReg(0));
|
||||
Node* position = __ BytecodeOperandImmSmi(0);
|
||||
Node* value = __ GetAccumulator();
|
||||
Node* vector_index = __ SmiTag(__ BytecodeOperandIdx(1));
|
||||
|
||||
Node* feedback_vector = __ LoadFeedbackVector();
|
||||
Node* context = __ GetContext();
|
||||
|
||||
__ CallRuntime(Runtime::kCollectTypeProfile, context, name, value,
|
||||
__ CallRuntime(Runtime::kCollectTypeProfile, context, position, value,
|
||||
feedback_vector, vector_index);
|
||||
__ Dispatch();
|
||||
}
|
||||
|
@ -694,7 +694,7 @@ RUNTIME_FUNCTION(Runtime_DefineDataPropertyInLiteral) {
|
||||
RUNTIME_FUNCTION(Runtime_CollectTypeProfile) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(4, args.length());
|
||||
CONVERT_ARG_HANDLE_CHECKED(String, name, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Smi, position, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
|
||||
CONVERT_ARG_HANDLE_CHECKED(FeedbackVector, vector, 2);
|
||||
CONVERT_SMI_ARG_CHECKED(index, 3);
|
||||
@ -708,9 +708,9 @@ RUNTIME_FUNCTION(Runtime_CollectTypeProfile) {
|
||||
}
|
||||
|
||||
CollectTypeProfileNexus nexus(vector, vector->ToSlot(index));
|
||||
nexus.Collect(type);
|
||||
nexus.Collect(type, position->value());
|
||||
|
||||
return *name;
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
// Return property without being observable by accessors or interceptors.
|
||||
|
@ -4,25 +4,30 @@
|
||||
|
||||
// Flags: --type-profile --turbo --allow-natives-syntax
|
||||
|
||||
function test(param) {
|
||||
var my_var1 = param;
|
||||
var my_var2 = 17;
|
||||
function testFunction(param, flag) {
|
||||
// We want to test 2 different return positions in one function.
|
||||
if (flag) {
|
||||
var first_var = param;
|
||||
return first_var;
|
||||
}
|
||||
var second_var = param;
|
||||
return second_var;
|
||||
}
|
||||
|
||||
%PrintTypeProfile(test);
|
||||
%PrintTypeProfile(testFunction);
|
||||
|
||||
test({});
|
||||
test(123);
|
||||
test('hello');
|
||||
test(123);
|
||||
%PrintTypeProfile(test);
|
||||
testFunction({});
|
||||
testFunction(123, true);
|
||||
testFunction('hello');
|
||||
testFunction(123);
|
||||
%PrintTypeProfile(testFunction);
|
||||
|
||||
test(undefined);
|
||||
test('hello');
|
||||
test({x: 12});
|
||||
test({x: 12});
|
||||
testFunction(undefined);
|
||||
testFunction('hello', true);
|
||||
testFunction({x: 12}, true);
|
||||
testFunction({x: 12});
|
||||
|
||||
%PrintTypeProfile(test);
|
||||
%PrintTypeProfile(testFunction);
|
||||
|
||||
class MyClass {
|
||||
constructor() {}
|
||||
@ -37,4 +42,10 @@ testConstructorNames(new MyClass());
|
||||
testConstructorNames({});
|
||||
testConstructorNames(2);
|
||||
|
||||
function testReturnOfNonVariable() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
testReturnOfNonVariable();
|
||||
|
||||
throw "throw otherwise test fails with --stress-opt";
|
||||
|
@ -1,19 +1,19 @@
|
||||
Function: test
|
||||
Object
|
||||
number
|
||||
string
|
||||
number
|
||||
Function: testFunction
|
||||
424: Object
|
||||
374: number
|
||||
424: string
|
||||
424: number
|
||||
|
||||
Function: test
|
||||
Object
|
||||
number
|
||||
string
|
||||
number
|
||||
undefined
|
||||
string
|
||||
Object
|
||||
Object
|
||||
Function: testFunction
|
||||
424: Object
|
||||
374: number
|
||||
424: string
|
||||
424: number
|
||||
424: undefined
|
||||
374: string
|
||||
374: Object
|
||||
424: Object
|
||||
|
||||
*%(basename)s:40: throw otherwise test fails with --stress-opt
|
||||
*%(basename)s:51: throw otherwise test fails with --stress-opt
|
||||
throw "throw otherwise test fails with --stress-opt";
|
||||
^
|
||||
|
451
test/mjsunit/type-profile.js
Normal file
451
test/mjsunit/type-profile.js
Normal file
@ -0,0 +1,451 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Flags: --type-profile --turbo
|
||||
|
||||
// Copy of mjsunit/array-splice as integration test for type profiling.
|
||||
// It only tests that nothing crashes, no actual test for type profile output.
|
||||
|
||||
(function() {
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var array = new Array(10);
|
||||
var spliced = array.splice(1, 1, 'one', 'two');
|
||||
assertEquals(1, spliced.length);
|
||||
assertFalse(0 in spliced, "0 in spliced");
|
||||
|
||||
assertEquals(11, array.length);
|
||||
assertFalse(0 in array, "0 in array");
|
||||
assertTrue(1 in array);
|
||||
assertTrue(2 in array);
|
||||
assertFalse(3 in array, "3 in array");
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
// Check various variants of empty array's splicing.
|
||||
(function() {
|
||||
for (var i = 0; i < 7; i++) {
|
||||
assertEquals([], [].splice(0, 0));
|
||||
assertEquals([], [].splice(1, 0));
|
||||
assertEquals([], [].splice(0, 1));
|
||||
assertEquals([], [].splice(-1, 0));
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
// Check that even if result array is empty, receiver gets sliced.
|
||||
(function() {
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var a = [1, 2, 3];
|
||||
assertEquals([], a.splice(1, 0, 'a', 'b', 'c'));
|
||||
assertEquals([1, 'a', 'b', 'c', 2, 3], a);
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
// Check various forms of arguments omission.
|
||||
(function() {
|
||||
var array;
|
||||
for (var i = 0; i < 7; i++) {
|
||||
array = [1, 2, 3]
|
||||
assertEquals([], array.splice());
|
||||
assertEquals([1, 2, 3], array);
|
||||
|
||||
// SpiderMonkey, TraceMonkey and JSC treat the case where no delete count is
|
||||
// given differently from when an undefined delete count is given.
|
||||
// This does not follow ECMA-262, but we do the same for
|
||||
// compatibility.
|
||||
array = [1, 2, 3]
|
||||
assertEquals([1, 2, 3], array.splice(0));
|
||||
assertEquals([], array);
|
||||
|
||||
array = [1, 2, 3]
|
||||
assertEquals([1, 2, 3], array.splice(undefined));
|
||||
assertEquals([], array);
|
||||
|
||||
array = [1, 2, 3]
|
||||
assertEquals([1, 2, 3], array.splice("foobar"));
|
||||
assertEquals([], array);
|
||||
|
||||
array = [1, 2, 3]
|
||||
assertEquals([], array.splice(undefined, undefined));
|
||||
assertEquals([1, 2, 3], array);
|
||||
|
||||
array = [1, 2, 3]
|
||||
assertEquals([], array.splice("foobar", undefined));
|
||||
assertEquals([1, 2, 3], array);
|
||||
|
||||
array = [1, 2, 3]
|
||||
assertEquals([], array.splice(undefined, "foobar"));
|
||||
assertEquals([1, 2, 3], array);
|
||||
|
||||
array = [1, 2, 3]
|
||||
assertEquals([], array.splice("foobar", "foobar"));
|
||||
assertEquals([1, 2, 3], array);
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
// Check variants of negatives and positive indices.
|
||||
(function() {
|
||||
var array, spliced;
|
||||
for (var i = 0; i < 7; i++) {
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(-100);
|
||||
assertEquals([], array);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(-1e100);
|
||||
assertEquals([], array);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(-3);
|
||||
assertEquals([1, 2, 3, 4], array);
|
||||
assertEquals([5, 6, 7], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(-3.999999);
|
||||
assertEquals([1, 2, 3, 4], array);
|
||||
assertEquals([5, 6, 7], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(-3.000001);
|
||||
assertEquals([1, 2, 3, 4], array);
|
||||
assertEquals([5, 6, 7], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(4);
|
||||
assertEquals([1, 2, 3, 4], array);
|
||||
assertEquals([5, 6, 7], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(4.999999);
|
||||
assertEquals([1, 2, 3, 4], array);
|
||||
assertEquals([5, 6, 7], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(4.000001);
|
||||
assertEquals([1, 2, 3, 4], array);
|
||||
assertEquals([5, 6, 7], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(6);
|
||||
assertEquals([1, 2, 3, 4, 5, 6], array);
|
||||
assertEquals([7], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(7);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], array);
|
||||
assertEquals([], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(8);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], array);
|
||||
assertEquals([], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(100);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], array);
|
||||
assertEquals([], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(1e100);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], array);
|
||||
assertEquals([], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, -100);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], array);
|
||||
assertEquals([], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, -1e100);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], array);
|
||||
assertEquals([], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, -3);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], array);
|
||||
assertEquals([], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, -3.999999);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], array);
|
||||
assertEquals([], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, -3.000001);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], array);
|
||||
assertEquals([], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, 4);
|
||||
assertEquals([5, 6, 7], array);
|
||||
assertEquals([1, 2, 3, 4], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, 4.999999);
|
||||
assertEquals([5, 6, 7], array);
|
||||
assertEquals([1, 2, 3, 4], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, 4.000001);
|
||||
assertEquals([5, 6, 7], array);
|
||||
assertEquals([1, 2, 3, 4], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, 6);
|
||||
assertEquals([7], array);
|
||||
assertEquals([1, 2, 3, 4, 5, 6], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, 7);
|
||||
assertEquals([], array);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, 8);
|
||||
assertEquals([], array);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, 100);
|
||||
assertEquals([], array);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], spliced);
|
||||
|
||||
array = [1, 2, 3, 4, 5, 6, 7];
|
||||
spliced = array.splice(0, 1e100);
|
||||
assertEquals([], array);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7], spliced);
|
||||
|
||||
// Some exotic cases.
|
||||
obj = { toString: function() { throw 'Exception'; } };
|
||||
|
||||
// Throwing an exception in conversion:
|
||||
try {
|
||||
[1, 2, 3].splice(obj, 3);
|
||||
throw 'Should have thrown';
|
||||
} catch (e) {
|
||||
assertEquals('Exception', e);
|
||||
}
|
||||
|
||||
try {
|
||||
[1, 2, 3].splice(0, obj, 3);
|
||||
throw 'Should have thrown';
|
||||
} catch (e) {
|
||||
assertEquals('Exception', e);
|
||||
}
|
||||
|
||||
array = [1, 2, 3];
|
||||
array.splice(0, 3, obj);
|
||||
assertEquals(1, array.length);
|
||||
|
||||
// Custom conversion:
|
||||
array = [1, 2, 3];
|
||||
spliced = array.splice({valueOf: function() { return 1; }},
|
||||
{toString: function() { return 2; }},
|
||||
'one', 'two');
|
||||
assertEquals([2, 3], spliced);
|
||||
assertEquals([1, 'one', 'two'], array);
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
// Nasty: modify the array in ToInteger.
|
||||
(function() {
|
||||
var array = [];
|
||||
var spliced;
|
||||
|
||||
for (var i = 0; i < 13; i++) {
|
||||
bad_start = { valueOf: function() { array.push(2*i); return -1; } };
|
||||
bad_count = { valueOf: function() { array.push(2*i + 1); return 1; } };
|
||||
spliced = array.splice(bad_start, bad_count);
|
||||
// According to the spec (15.4.4.12), length is calculated before
|
||||
// performing ToInteger on arguments. However, v8 ignores elements
|
||||
// we add while converting, so we need corrective pushes.
|
||||
array.push(2*i); array.push(2*i + 1);
|
||||
if (i == 0) {
|
||||
assertEquals([], spliced); // Length was 0, nothing to get.
|
||||
assertEquals([0, 1], array);
|
||||
} else {
|
||||
// When we start splice, array is [0 .. 2*i - 1], so we get
|
||||
// as a result [2*i], this element is removed from the array,
|
||||
// but [2 * i, 2 * i + 1] are added.
|
||||
assertEquals([2 * i - 1], spliced);
|
||||
assertEquals(2 * i, array[i]);
|
||||
assertEquals(2 * i + 1, array[i + 1]);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// Check the behaviour when approaching maximal values for length.
|
||||
(function() {
|
||||
for (var i = 0; i < 7; i++) {
|
||||
try {
|
||||
new Array(Math.pow(2, 32) - 3).splice(-1, 0, 1, 2, 3, 4, 5);
|
||||
throw 'Should have thrown RangeError';
|
||||
} catch (e) {
|
||||
assertTrue(e instanceof RangeError);
|
||||
}
|
||||
|
||||
// Check smi boundary
|
||||
var bigNum = (1 << 30) - 3;
|
||||
var array = new Array(bigNum);
|
||||
array.splice(-1, 0, 1, 2, 3, 4, 5, 6, 7);
|
||||
assertEquals(bigNum + 7, array.length);
|
||||
}
|
||||
})();
|
||||
|
||||
(function() {
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var a = [7, 8, 9];
|
||||
a.splice(0, 0, 1, 2, 3, 4, 5, 6);
|
||||
assertEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], a);
|
||||
assertFalse(a.hasOwnProperty(10), "a.hasOwnProperty(10)");
|
||||
assertEquals(undefined, a[10]);
|
||||
}
|
||||
})();
|
||||
|
||||
(function testSpliceDeleteDouble() {
|
||||
var a = [1.1, 1.2, 1.3, 1.4];
|
||||
a.splice(2, 1)
|
||||
assertEquals([1.1, 1.2, 1.4], a);
|
||||
})();
|
||||
|
||||
// Past this point the ArrayProtector is invalidated since we modify the
|
||||
// Array.prototype.
|
||||
|
||||
// Check the case of JS builtin .splice()
|
||||
(function() {
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var array = [1, 2, 3, 4];
|
||||
Array.prototype[3] = 'foo'; // To force JS builtin.
|
||||
|
||||
var spliced = array.splice();
|
||||
|
||||
assertEquals([], spliced);
|
||||
assertEquals([1, 2, 3, 4], array);
|
||||
}
|
||||
})();
|
||||
|
||||
// Now check the case with array of holes and some elements on prototype.
|
||||
(function() {
|
||||
var len = 9;
|
||||
|
||||
var at3 = "@3";
|
||||
var at7 = "@7";
|
||||
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var array = new Array(len);
|
||||
var array_proto = [];
|
||||
array_proto[3] = at3;
|
||||
array_proto[7] = at7;
|
||||
array.__proto__ = array_proto;
|
||||
|
||||
var spliced = array.splice(2, 2, 'one', undefined, 'two');
|
||||
|
||||
// Second hole (at index 3) of array turns into
|
||||
// value of Array.prototype[3] while copying.
|
||||
assertEquals([, at3], spliced);
|
||||
assertEquals([, , 'one', undefined, 'two', , , at7, at7, ,], array);
|
||||
|
||||
// ... but array[3] and array[7] is actually a hole:
|
||||
assertTrue(delete array_proto[3]);
|
||||
assertEquals(undefined, array[3]);
|
||||
assertTrue(delete array_proto[7]);
|
||||
assertEquals(undefined, array[7]);
|
||||
|
||||
// and now check hasOwnProperty
|
||||
assertFalse(array.hasOwnProperty(0), "array.hasOwnProperty(0)");
|
||||
assertFalse(array.hasOwnProperty(1), "array.hasOwnProperty(1)");
|
||||
assertTrue(array.hasOwnProperty(2));
|
||||
assertTrue(array.hasOwnProperty(3));
|
||||
assertTrue(array.hasOwnProperty(4));
|
||||
assertFalse(array.hasOwnProperty(5), "array.hasOwnProperty(5)");
|
||||
assertFalse(array.hasOwnProperty(6), "array.hasOwnProperty(6)");
|
||||
assertFalse(array.hasOwnProperty(7), "array.hasOwnProperty(7)");
|
||||
assertTrue(array.hasOwnProperty(8));
|
||||
assertFalse(array.hasOwnProperty(9), "array.hasOwnProperty(9)");
|
||||
|
||||
// and now check couple of indices above length.
|
||||
assertFalse(array.hasOwnProperty(10), "array.hasOwnProperty(10)");
|
||||
assertFalse(array.hasOwnProperty(15), "array.hasOwnProperty(15)");
|
||||
assertFalse(array.hasOwnProperty(31), "array.hasOwnProperty(31)");
|
||||
assertFalse(array.hasOwnProperty(63), "array.hasOwnProperty(63)");
|
||||
assertFalse(array.hasOwnProperty(Math.pow(2, 32) - 2),
|
||||
"array.hasOwnProperty(Math.pow(2, 32) - 2)");
|
||||
}
|
||||
})();
|
||||
|
||||
// Now check the case with array of holes and some elements on prototype.
|
||||
(function() {
|
||||
var len = 9;
|
||||
|
||||
var at3 = "@3";
|
||||
var at7 = "@7";
|
||||
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var array = new Array(len);
|
||||
Array.prototype[3] = at3;
|
||||
Array.prototype[7] = at7;
|
||||
|
||||
var spliced = array.splice(2, 2, 'one', undefined, 'two');
|
||||
|
||||
// Second hole (at index 3) of array turns into
|
||||
// value of Array.prototype[3] while copying.
|
||||
assertEquals([, at3], spliced);
|
||||
assertEquals([, , 'one', undefined, 'two', , , at7, at7, ,], array);
|
||||
|
||||
// ... but array[3] and array[7] is actually a hole:
|
||||
assertTrue(delete Array.prototype[3]);
|
||||
assertEquals(undefined, array[3]);
|
||||
assertTrue(delete Array.prototype[7]);
|
||||
assertEquals(undefined, array[7]);
|
||||
|
||||
// and now check hasOwnProperty
|
||||
assertFalse(array.hasOwnProperty(0), "array.hasOwnProperty(0)");
|
||||
assertFalse(array.hasOwnProperty(1), "array.hasOwnProperty(1)");
|
||||
assertTrue(array.hasOwnProperty(2));
|
||||
assertTrue(array.hasOwnProperty(3));
|
||||
assertTrue(array.hasOwnProperty(4));
|
||||
assertFalse(array.hasOwnProperty(5), "array.hasOwnProperty(5)");
|
||||
assertFalse(array.hasOwnProperty(6), "array.hasOwnProperty(6)");
|
||||
assertFalse(array.hasOwnProperty(7), "array.hasOwnProperty(7)");
|
||||
assertTrue(array.hasOwnProperty(8));
|
||||
assertFalse(array.hasOwnProperty(9), "array.hasOwnProperty(9)");
|
||||
|
||||
// and now check couple of indices above length.
|
||||
assertFalse(array.hasOwnProperty(10), "array.hasOwnProperty(10)");
|
||||
assertFalse(array.hasOwnProperty(15), "array.hasOwnProperty(15)");
|
||||
assertFalse(array.hasOwnProperty(31), "array.hasOwnProperty(31)");
|
||||
assertFalse(array.hasOwnProperty(63), "array.hasOwnProperty(63)");
|
||||
assertFalse(array.hasOwnProperty(Math.pow(2, 32) - 2),
|
||||
"array.hasOwnProperty(Math.pow(2, 32) - 2)");
|
||||
}
|
||||
})();
|
Loading…
Reference in New Issue
Block a user