[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:
franzih 2017-03-20 12:51:20 -07:00 committed by Commit bot
parent 99743ad460
commit de04df7412
14 changed files with 553 additions and 71 deletions

View File

@ -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());

View File

@ -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

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -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.

View File

@ -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));
}
}

View File

@ -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 */ \

View File

@ -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();
}

View File

@ -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.

View File

@ -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";

View File

@ -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";
^

View 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)");
}
})();