diff --git a/runtime/lib/object.cc b/runtime/lib/object.cc index fb33534f55c3..02f25920070a 100644 --- a/runtime/lib/object.cc +++ b/runtime/lib/object.cc @@ -88,7 +88,18 @@ DEFINE_NATIVE_ENTRY(Object_runtimeType, 0, 1) { return Type::Double(); } else if (instance.IsType() || instance.IsFunctionType()) { return Type::DartTypeType(); + } else if (IsArrayClassId(instance.GetClassId())) { + const auto& cls = Class::Handle( + zone, thread->isolate_group()->object_store()->list_class()); + const auto& type_arguments = + TypeArguments::Handle(zone, instance.GetTypeArguments()); + const auto& type = Type::Handle( + zone, + Type::New(cls, type_arguments, Nullability::kNonNullable, Heap::kNew)); + type.SetIsFinalized(); + return type.Canonicalize(thread, nullptr); } + return instance.GetType(Heap::kNew); } @@ -101,14 +112,18 @@ static bool HaveSameRuntimeTypeHelper(Zone* zone, if (left_cid != right_cid) { if (IsIntegerClassId(left_cid)) { return IsIntegerClassId(right_cid); - } - if (IsStringClassId(left_cid)) { + } else if (IsStringClassId(left_cid)) { return IsStringClassId(right_cid); - } - if (IsTypeClassId(left_cid)) { + } else if (IsTypeClassId(left_cid)) { return IsTypeClassId(right_cid); + } else if (IsArrayClassId(left_cid)) { + if (!IsArrayClassId(right_cid)) { + return false; + } + // Still need to check type arguments. + } else { + return false; } - return false; } if (left_cid == kClosureCid) { diff --git a/runtime/tests/vm/dart/have_same_runtime_type_test.dart b/runtime/tests/vm/dart/have_same_runtime_type_test.dart new file mode 100644 index 000000000000..b7ade2aba34b --- /dev/null +++ b/runtime/tests/vm/dart/have_same_runtime_type_test.dart @@ -0,0 +1,32 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Test for corner cases of 'a.runtimeType == b.runtimeType' pattern +// which is recognized and optimized in AOT mode. + +import "package:expect/expect.dart"; + +@pragma('vm:never-inline') +Object getType(Object obj) => obj.runtimeType; + +@pragma('vm:never-inline') +void test(bool expected, Object a, Object b) { + bool result1 = getType(a) == getType(b); + bool result2 = a.runtimeType == b.runtimeType; + Expect.equals(expected, result1); + Expect.equals(expected, result2); +} + +typedef Func = void Function(); + +void main() { + test(true, 0x7fffffffffffffff, int.parse('42')); + test(true, 'hi', String.fromCharCode(1114111)); + test(false, 'hi', 1); + test(true, List, Func); + test(true, [1], const [2]); + test(true, const [], List.filled(1, '')); + test(true, []..add('hi'), List.filled(2, '')); + test(false, [], []); +} diff --git a/runtime/tests/vm/dart_2/have_same_runtime_type_test.dart b/runtime/tests/vm/dart_2/have_same_runtime_type_test.dart new file mode 100644 index 000000000000..119e40ff4bec --- /dev/null +++ b/runtime/tests/vm/dart_2/have_same_runtime_type_test.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Test for corner cases of 'a.runtimeType == b.runtimeType' pattern +// which is recognized and optimized in AOT mode. + +// @dart = 2.9 + +import "package:expect/expect.dart"; + +@pragma('vm:never-inline') +Object getType(Object obj) => obj.runtimeType; + +@pragma('vm:never-inline') +void test(bool expected, Object a, Object b) { + bool result1 = getType(a) == getType(b); + bool result2 = a.runtimeType == b.runtimeType; + Expect.equals(expected, result1); + Expect.equals(expected, result2); +} + +typedef Func = void Function(); + +void main() { + test(true, 0x7fffffffffffffff, int.parse('42')); + test(true, 'hi', String.fromCharCode(1114111)); + test(false, 'hi', 1); + test(true, List, Func); + test(true, [1], const [2]); + test(true, const [], List.filled(1, '')); + test(true, []..add('hi'), List.filled(2, '')); + test(false, [], []); +} diff --git a/runtime/vm/class_id.h b/runtime/vm/class_id.h index ac06355c4a84..5ca709a90b33 100644 --- a/runtime/vm/class_id.h +++ b/runtime/vm/class_id.h @@ -78,7 +78,6 @@ typedef uint16_t ClassIdTagType; V(Mint) \ V(Double) \ V(Bool) \ - V(GrowableObjectArray) \ V(Float32x4) \ V(Int32x4) \ V(Float64x2) \ @@ -108,10 +107,14 @@ typedef uint16_t ClassIdTagType; // TODO(http://dartbug.com/45908): Add ImmutableLinkedHashSet. #define CLASS_LIST_SETS(V) V(LinkedHashSet) -#define CLASS_LIST_ARRAYS(V) \ +#define CLASS_LIST_FIXED_LENGTH_ARRAYS(V) \ V(Array) \ V(ImmutableArray) +#define CLASS_LIST_ARRAYS(V) \ + CLASS_LIST_FIXED_LENGTH_ARRAYS(V) \ + V(GrowableObjectArray) + #define CLASS_LIST_STRINGS(V) \ V(String) \ V(OneByteString) \ @@ -182,6 +185,7 @@ typedef uint16_t ClassIdTagType; V(LinkedHashMap) \ V(LinkedHashSet) \ V(Array) \ + V(GrowableObjectArray) \ V(String) #define CLASS_LIST_NO_OBJECT(V) \ @@ -327,11 +331,15 @@ inline bool IsExternalStringClassId(intptr_t index) { index == kExternalTwoByteStringCid); } +inline bool IsArrayClassId(intptr_t index) { + COMPILE_ASSERT(kImmutableArrayCid == kArrayCid + 1); + COMPILE_ASSERT(kGrowableObjectArrayCid == kArrayCid + 2); + return (index >= kArrayCid && index <= kGrowableObjectArrayCid); +} + inline bool IsBuiltinListClassId(intptr_t index) { // Make sure this function is updated when new builtin List types are added. - COMPILE_ASSERT(kImmutableArrayCid == kArrayCid + 1); - return ((index >= kArrayCid && index <= kImmutableArrayCid) || - (index == kGrowableObjectArrayCid) || IsTypedDataBaseClassId(index) || + return (IsArrayClassId(index) || IsTypedDataBaseClassId(index) || (index == kByteBufferCid)); } diff --git a/runtime/vm/compiler/asm_intrinsifier_arm.cc b/runtime/vm/compiler/asm_intrinsifier_arm.cc index 375c454710fd..e7f2ee15cf62 100644 --- a/runtime/vm/compiler/asm_intrinsifier_arm.cc +++ b/runtime/vm/compiler/asm_intrinsifier_arm.cc @@ -1198,6 +1198,14 @@ static void JumpIfNotString(Assembler* assembler, kIfNotInRange, target); } +static void JumpIfNotList(Assembler* assembler, + Register cid, + Register tmp, + Label* target) { + RangeCheck(assembler, cid, tmp, kArrayCid, kGrowableObjectArrayCid, + kIfNotInRange, target); +} + static void JumpIfType(Assembler* assembler, Register cid, Register tmp, @@ -1284,7 +1292,7 @@ static void EquivalentClassIds(Assembler* assembler, Register scratch, bool testing_instance_cids) { Label different_cids, equal_cids_but_generic, not_integer, - not_integer_or_string; + not_integer_or_string, not_integer_or_string_or_list; // Check if left hand side is a closure. Closures are handled in the runtime. __ CompareImmediate(cid1, kClosureCid); @@ -1310,7 +1318,8 @@ static void EquivalentClassIds(Assembler* assembler, __ b(equal); // Class ids are different. Check if we are comparing two string types (with - // different representations) or two integer types or two type types. + // different representations), two integer types, two list types or two type + // types. __ Bind(&different_cids); __ CompareImmediate(cid1, kNumPredefinedCids); __ b(not_equal, HI); @@ -1335,9 +1344,20 @@ static void EquivalentClassIds(Assembler* assembler, if (testing_instance_cids) { __ Bind(¬_integer_or_string); + // Check if both are List types. + JumpIfNotList(assembler, cid1, scratch, ¬_integer_or_string_or_list); + + // First type is a List. Check if the second is a List too. + JumpIfNotList(assembler, cid2, scratch, not_equal); + ASSERT(compiler::target::Array::type_arguments_offset() == + compiler::target::GrowableObjectArray::type_arguments_offset()); + __ LoadImmediate(scratch, compiler::target::Array::type_arguments_offset()); + __ b(&equal_cids_but_generic); + + __ Bind(¬_integer_or_string_or_list); // Check if the first type is a Type. If it is not then types are not // equivalent because they have different class ids and they are not String - // or integer or Type. + // or integer or List or Type. JumpIfNotType(assembler, cid1, scratch, not_equal); // First type is a Type. Check if the second is a Type too. diff --git a/runtime/vm/compiler/asm_intrinsifier_arm64.cc b/runtime/vm/compiler/asm_intrinsifier_arm64.cc index 36cb205d1ffc..1e0134aeb189 100644 --- a/runtime/vm/compiler/asm_intrinsifier_arm64.cc +++ b/runtime/vm/compiler/asm_intrinsifier_arm64.cc @@ -1342,6 +1342,14 @@ static void JumpIfNotString(Assembler* assembler, kIfNotInRange, target); } +static void JumpIfNotList(Assembler* assembler, + Register cid, + Register tmp, + Label* target) { + RangeCheck(assembler, cid, tmp, kArrayCid, kGrowableObjectArrayCid, + kIfNotInRange, target); +} + static void JumpIfType(Assembler* assembler, Register cid, Register tmp, @@ -1432,7 +1440,7 @@ static void EquivalentClassIds(Assembler* assembler, Register scratch, bool testing_instance_cids) { Label different_cids, equal_cids_but_generic, not_integer, - not_integer_or_string; + not_integer_or_string, not_integer_or_string_or_list; // Check if left hand side is a closure. Closures are handled in the runtime. __ CompareImmediate(cid1, kClosureCid); @@ -1458,7 +1466,8 @@ static void EquivalentClassIds(Assembler* assembler, __ b(equal); // Class ids are different. Check if we are comparing two string types (with - // different representations) or two integer types or two type types. + // different representations), two integer types, two list types or two type + // types. __ Bind(&different_cids); __ CompareImmediate(cid1, kNumPredefinedCids); __ b(not_equal, HI); @@ -1483,9 +1492,20 @@ static void EquivalentClassIds(Assembler* assembler, if (testing_instance_cids) { __ Bind(¬_integer_or_string); + // Check if both are List types. + JumpIfNotList(assembler, cid1, scratch, ¬_integer_or_string_or_list); + + // First type is a List. Check if the second is a List too. + JumpIfNotList(assembler, cid2, scratch, not_equal); + ASSERT(compiler::target::Array::type_arguments_offset() == + compiler::target::GrowableObjectArray::type_arguments_offset()); + __ LoadImmediate(scratch, compiler::target::Array::type_arguments_offset()); + __ b(&equal_cids_but_generic); + + __ Bind(¬_integer_or_string_or_list); // Check if the first type is a Type. If it is not then types are not // equivalent because they have different class ids and they are not String - // or integer or Type. + // or integer or List or Type. JumpIfNotType(assembler, cid1, scratch, not_equal); // First type is a Type. Check if the second is a Type too. diff --git a/runtime/vm/compiler/asm_intrinsifier_ia32.cc b/runtime/vm/compiler/asm_intrinsifier_ia32.cc index a17f7a834825..a6f1c9daae0c 100644 --- a/runtime/vm/compiler/asm_intrinsifier_ia32.cc +++ b/runtime/vm/compiler/asm_intrinsifier_ia32.cc @@ -1281,6 +1281,11 @@ static void JumpIfNotString(Assembler* assembler, Register cid, Label* target) { kIfNotInRange, target); } +static void JumpIfNotList(Assembler* assembler, Register cid, Label* target) { + RangeCheck(assembler, cid, kArrayCid, kGrowableObjectArrayCid, kIfNotInRange, + target); +} + static void JumpIfType(Assembler* assembler, Register cid, Label* target) { RangeCheck(assembler, cid, kTypeCid, kFunctionTypeCid, kIfInRange, target); } @@ -1370,7 +1375,7 @@ static void EquivalentClassIds(Assembler* assembler, Register scratch, bool testing_instance_cids) { Label different_cids, equal_cids_but_generic, not_integer, - not_integer_or_string; + not_integer_or_string, not_integer_or_string_or_list; // Check if left hand side is a closure. Closures are handled in the runtime. __ cmpl(cid1, Immediate(kClosureCid)); @@ -1392,11 +1397,12 @@ static void EquivalentClassIds(Assembler* assembler, scratch, target::Class::host_type_arguments_field_offset_in_words_offset())); __ cmpl(scratch, Immediate(target::Class::kNoTypeArguments)); - __ j(NOT_EQUAL, &equal_cids_but_generic, Assembler::kNearJump); + __ j(NOT_EQUAL, &equal_cids_but_generic); __ jmp(equal); // Class ids are different. Check if we are comparing two string types (with - // different representations) or two integer types or two type types. + // different representations), two integer types, two list types or two type + // types. __ Bind(&different_cids); __ cmpl(cid1, Immediate(kNumPredefinedCids)); __ j(ABOVE_EQUAL, not_equal); @@ -1406,25 +1412,42 @@ static void EquivalentClassIds(Assembler* assembler, JumpIfNotInteger(assembler, scratch, ¬_integer); // First type is an integer. Check if the second is an integer too. - JumpIfInteger(assembler, cid2, equal); + __ movl(scratch, cid2); + JumpIfInteger(assembler, scratch, equal); // Integer types are only equivalent to other integer types. __ jmp(not_equal); __ Bind(¬_integer); // Check if both are String types. - JumpIfNotString(assembler, cid1, + __ movl(scratch, cid1); + JumpIfNotString(assembler, scratch, testing_instance_cids ? ¬_integer_or_string : not_equal); // First type is a String. Check if the second is a String too. - JumpIfString(assembler, cid2, equal); + __ movl(scratch, cid2); + JumpIfString(assembler, scratch, equal); // String types are only equivalent to other String types. __ jmp(not_equal); if (testing_instance_cids) { __ Bind(¬_integer_or_string); + // Check if both are List types. + __ movl(scratch, cid1); + JumpIfNotList(assembler, scratch, ¬_integer_or_string_or_list); + + // First type is a List. Check if the second is a List too. + __ movl(scratch, cid2); + JumpIfNotList(assembler, scratch, not_equal); + ASSERT(compiler::target::Array::type_arguments_offset() == + compiler::target::GrowableObjectArray::type_arguments_offset()); + __ movl(scratch, + Immediate(compiler::target::Array::type_arguments_offset())); + __ jmp(&equal_cids_but_generic, Assembler::kNearJump); + + __ Bind(¬_integer_or_string_or_list); // Check if the first type is a Type. If it is not then types are not // equivalent because they have different class ids and they are not String - // or integer or Type. + // or integer or List or Type. JumpIfNotType(assembler, cid1, not_equal); // First type is a Type. Check if the second is a Type too. diff --git a/runtime/vm/compiler/asm_intrinsifier_x64.cc b/runtime/vm/compiler/asm_intrinsifier_x64.cc index b7109e88fa6c..3196c49d1c42 100644 --- a/runtime/vm/compiler/asm_intrinsifier_x64.cc +++ b/runtime/vm/compiler/asm_intrinsifier_x64.cc @@ -1184,6 +1184,11 @@ static void JumpIfNotString(Assembler* assembler, Register cid, Label* target) { kIfNotInRange, target); } +static void JumpIfNotList(Assembler* assembler, Register cid, Label* target) { + RangeCheck(assembler, cid, kArrayCid, kGrowableObjectArrayCid, kIfNotInRange, + target); +} + static void JumpIfType(Assembler* assembler, Register cid, Label* target) { RangeCheck(assembler, cid, kTypeCid, kFunctionTypeCid, kIfInRange, target); } @@ -1275,7 +1280,7 @@ static void EquivalentClassIds(Assembler* assembler, Register scratch, bool testing_instance_cids) { Label different_cids, equal_cids_but_generic, not_integer, - not_integer_or_string; + not_integer_or_string, not_integer_or_string_or_list; // Check if left hand side is a closure. Closures are handled in the runtime. __ cmpq(cid1, Immediate(kClosureCid)); @@ -1297,11 +1302,12 @@ static void EquivalentClassIds(Assembler* assembler, scratch, target::Class::host_type_arguments_field_offset_in_words_offset())); __ cmpl(scratch, Immediate(target::Class::kNoTypeArguments)); - __ j(NOT_EQUAL, &equal_cids_but_generic, Assembler::kNearJump); + __ j(NOT_EQUAL, &equal_cids_but_generic); __ jmp(equal); // Class ids are different. Check if we are comparing two string types (with - // different representations) or two integer types or two type types. + // different representations), two integer types, two list types or two type + // types. __ Bind(&different_cids); __ cmpq(cid1, Immediate(kNumPredefinedCids)); __ j(ABOVE_EQUAL, not_equal); @@ -1311,25 +1317,42 @@ static void EquivalentClassIds(Assembler* assembler, JumpIfNotInteger(assembler, scratch, ¬_integer); // First type is an integer. Check if the second is an integer too. - JumpIfInteger(assembler, cid2, equal); + __ movq(scratch, cid2); + JumpIfInteger(assembler, scratch, equal); // Integer types are only equivalent to other integer types. __ jmp(not_equal); __ Bind(¬_integer); // Check if both are String types. - JumpIfNotString(assembler, cid1, + __ movq(scratch, cid1); + JumpIfNotString(assembler, scratch, testing_instance_cids ? ¬_integer_or_string : not_equal); // First type is a String. Check if the second is a String too. - JumpIfString(assembler, cid2, equal); + __ movq(scratch, cid2); + JumpIfString(assembler, scratch, equal); // String types are only equivalent to other String types. __ jmp(not_equal); if (testing_instance_cids) { __ Bind(¬_integer_or_string); + // Check if both are List types. + __ movq(scratch, cid1); + JumpIfNotList(assembler, scratch, ¬_integer_or_string_or_list); + + // First type is a List. Check if the second is a List too. + __ movq(scratch, cid2); + JumpIfNotList(assembler, scratch, not_equal); + ASSERT(compiler::target::Array::type_arguments_offset() == + compiler::target::GrowableObjectArray::type_arguments_offset()); + __ movq(scratch, + Immediate(compiler::target::Array::type_arguments_offset())); + __ jmp(&equal_cids_but_generic, Assembler::kNearJump); + + __ Bind(¬_integer_or_string_or_list); // Check if the first type is a Type. If it is not then types are not // equivalent because they have different class ids and they are not String - // or integer or Type. + // or integer or List or Type. JumpIfNotType(assembler, cid1, not_equal); // First type is a Type. Check if the second is a Type too. diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc index 4473e8188ad5..5ce53950eb1e 100644 --- a/runtime/vm/compiler/backend/flow_graph_compiler.cc +++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc @@ -1531,13 +1531,11 @@ void FlowGraphCompiler::GenerateListTypeCheck( Register class_id_reg, compiler::Label* is_instance_lbl) { assembler()->Comment("ListTypeCheck"); - compiler::Label unknown; - GrowableArray args; - args.Add(kArrayCid); - args.Add(kGrowableObjectArrayCid); - args.Add(kImmutableArrayCid); - CheckClassIds(class_id_reg, args, is_instance_lbl, &unknown); - assembler()->Bind(&unknown); + COMPILE_ASSERT((kImmutableArrayCid == kArrayCid + 1) && + (kGrowableObjectArrayCid == kArrayCid + 2)); + CidRangeVector ranges; + ranges.Add({kArrayCid, kGrowableObjectArrayCid}); + GenerateCidRangesCheck(assembler(), class_id_reg, ranges, is_instance_lbl); } void FlowGraphCompiler::EmitComment(Instruction* instr) { diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 5c3adc45c129..9b7ff28a54e7 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -607,6 +607,7 @@ void Object::InitVtables() { builtin_vtables_[k##clazz##Cid] = fake_handle.vtable(); \ } CLASS_LIST_NO_OBJECT_NOR_STRING_NOR_ARRAY_NOR_MAP(INIT_VTABLE) + INIT_VTABLE(GrowableObjectArray) #undef INIT_VTABLE #define INIT_VTABLE(clazz) \ @@ -630,7 +631,7 @@ void Object::InitVtables() { Array fake_handle; \ builtin_vtables_[k##clazz##Cid] = fake_handle.vtable(); \ } - CLASS_LIST_ARRAYS(INIT_VTABLE) + CLASS_LIST_FIXED_LENGTH_ARRAYS(INIT_VTABLE) #undef INIT_VTABLE #define INIT_VTABLE(clazz) \ diff --git a/runtime/vm/object_graph_copy.cc b/runtime/vm/object_graph_copy.cc index 76d65b65572b..1a54883aeced 100644 --- a/runtime/vm/object_graph_copy.cc +++ b/runtime/vm/object_graph_copy.cc @@ -1009,6 +1009,7 @@ class ObjectCopy : public Base { CLASS_LIST_NO_OBJECT_NOR_STRING_NOR_ARRAY_NOR_MAP(COPY_TO) COPY_TO(Array) + COPY_TO(GrowableObjectArray) COPY_TO(LinkedHashMap) COPY_TO(LinkedHashSet) #undef COPY_TO diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc index 6112fa740341..4985582031aa 100644 --- a/runtime/vm/service.cc +++ b/runtime/vm/service.cc @@ -5208,7 +5208,6 @@ static void GetDefaultClassesAliases(Thread* thread, JSONStream* js) { { JSONArray internals(&map, "List"); CLASS_LIST_ARRAYS(DEFINE_ADD_VALUE_F_CID) - DEFINE_ADD_VALUE_F_CID(GrowableObjectArray) DEFINE_ADD_VALUE_F_CID(ByteBuffer) } { diff --git a/tests/lib/mirrors/regress_b196606044_test.dart b/tests/lib/mirrors/regress_b196606044_test.dart new file mode 100644 index 000000000000..e6565198a9cf --- /dev/null +++ b/tests/lib/mirrors/regress_b196606044_test.dart @@ -0,0 +1,23 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Regression test for b/196606044. +// +// Verifies that instance.type.instanceMembers contains 'isNotEmpty' +// member for a List literal. + +import 'package:expect/expect.dart'; +import 'dart:mirrors'; + +dynamic object = [1, 2, 3]; +String name = 'isNotEmpty'; + +main() { + var instance = reflect(object); + var member = instance.type.instanceMembers[new Symbol(name)]; + Expect.isNotNull(member); + var invocation = instance.getField(member!.simpleName); + Expect.isNotNull(invocation); + Expect.equals(true, invocation.reflectee); +} diff --git a/tests/lib_2/mirrors/regress_b196606044_test.dart b/tests/lib_2/mirrors/regress_b196606044_test.dart new file mode 100644 index 000000000000..12df1a27d0c8 --- /dev/null +++ b/tests/lib_2/mirrors/regress_b196606044_test.dart @@ -0,0 +1,25 @@ +// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// @dart = 2.9 + +// Regression test for b/196606044. +// +// Verifies that instance.type.instanceMembers contains 'isNotEmpty' +// member for a List literal. + +import 'package:expect/expect.dart'; +import 'dart:mirrors'; + +dynamic object = [1, 2, 3]; +String name = 'isNotEmpty'; + +main() { + var instance = reflect(object); + var member = instance.type.instanceMembers[new Symbol(name)]; + Expect.isNotNull(member); + var invocation = instance.getField(member.simpleName); + Expect.isNotNull(invocation); + Expect.equals(true, invocation.reflectee); +}