ART: Add null type to verifier
Add a null type to distinguish zero from null. This is an
infra-only change, as nothing will introduce null with this
commit, yet.
Bug: 22059710
Bug: 64683522
Bug: 69669661
Test: m test-art-host
Change-Id: Id2549a5aefefe9471d4bdbd2c8993395150947c6
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index c6ba2f7..978e51d 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -2237,7 +2237,7 @@
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-object not expected";
} else {
/* return_type is the *expected* return type, not register value */
- DCHECK(!return_type.IsZero());
+ DCHECK(!return_type.IsZeroOrNull());
DCHECK(!return_type.IsUninitializedReference());
const uint32_t vregA = inst->VRegA_11x();
const RegType& reg_type = work_line_->GetRegisterType(this, vregA);
@@ -2485,7 +2485,8 @@
case Instruction::ARRAY_LENGTH: {
const RegType& res_type = work_line_->GetRegisterType(this, inst->VRegB_12x());
if (res_type.IsReferenceTypes()) {
- if (!res_type.IsArrayTypes() && !res_type.IsZero()) { // ie not an array or null
+ if (!res_type.IsArrayTypes() && !res_type.IsZeroOrNull()) {
+ // ie not an array or null
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "array-length on non-array " << res_type;
} else {
work_line_->SetRegisterType<LockOp::kClear>(this,
@@ -2592,7 +2593,7 @@
/* Similar to the verification done for APUT */
const RegType& array_type = work_line_->GetRegisterType(this, inst->VRegA_31t());
/* array_type can be null if the reg type is Zero */
- if (!array_type.IsZero()) {
+ if (!array_type.IsZeroOrNull()) {
if (!array_type.IsArrayTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid fill-array-data with array type "
<< array_type;
@@ -2632,7 +2633,7 @@
const RegType& reg_type1 = work_line_->GetRegisterType(this, inst->VRegA_22t());
const RegType& reg_type2 = work_line_->GetRegisterType(this, inst->VRegB_22t());
bool mismatch = false;
- if (reg_type1.IsZero()) { // zero then integral or reference expected
+ if (reg_type1.IsZeroOrNull()) { // zero then integral or reference expected
mismatch = !reg_type2.IsReferenceTypes() && !reg_type2.IsIntegralTypes();
} else if (reg_type1.IsReferenceTypes()) { // both references?
mismatch = !reg_type2.IsReferenceTypes();
@@ -2717,7 +2718,7 @@
!cast_type.IsUnresolvedTypes() && !orig_type.IsUnresolvedTypes() &&
cast_type.HasClass() && // Could be conflict type, make sure it has a class.
!cast_type.GetClass()->IsInterface() &&
- (orig_type.IsZero() ||
+ (orig_type.IsZeroOrNull() ||
orig_type.IsStrictlyAssignableFrom(
cast_type.Merge(orig_type, ®_types_, this), this))) {
RegisterLine* update_line = RegisterLine::Create(code_item_accessor_.RegistersSize(),
@@ -3005,7 +3006,7 @@
break;
/* no null refs allowed (?) */
- if (this_type.IsZero()) {
+ if (this_type.IsZeroOrNull()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "unable to initialize null ref";
break;
}
@@ -3082,7 +3083,7 @@
* interface or Object (see comments in RegType::JoinClass).
*/
const RegType& this_type = work_line_->GetInvocationThis(this, inst);
- if (this_type.IsZero()) {
+ if (this_type.IsZeroOrNull()) {
/* null pointer always passes (and always fails at runtime) */
} else {
if (this_type.IsUninitializedTypes()) {
@@ -4081,7 +4082,7 @@
const RegType& adjusted_type = is_init
? GetRegTypeCache()->FromUninitialized(actual_arg_type)
: actual_arg_type;
- if (method_type != METHOD_INTERFACE && !adjusted_type.IsZero()) {
+ if (method_type != METHOD_INTERFACE && !adjusted_type.IsZeroOrNull()) {
const RegType* res_method_class;
// Miranda methods have the declaring interface as their declaring class, not the abstract
// class. It would be wrong to use this for the type check (interface type checks are
@@ -4454,7 +4455,7 @@
bool MethodVerifier::CheckSignaturePolymorphicReceiver(const Instruction* inst) {
const RegType& this_type = work_line_->GetInvocationThis(this, inst);
- if (this_type.IsZero()) {
+ if (this_type.IsZeroOrNull()) {
/* null pointer always passes (and always fails at run time) */
return true;
} else if (!this_type.IsNonZeroReferenceTypes()) {
@@ -4573,7 +4574,7 @@
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "'this' arg must be initialized";
return nullptr;
}
- if (!actual_arg_type.IsZero()) {
+ if (!actual_arg_type.IsZeroOrNull()) {
mirror::Class* klass = res_method->GetDeclaringClass();
std::string temp;
const RegType& res_method_class =
@@ -4689,7 +4690,7 @@
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Invalid reg type for array index (" << index_type << ")";
} else {
const RegType& array_type = work_line_->GetRegisterType(this, inst->VRegB_23x());
- if (array_type.IsZero()) {
+ if (array_type.IsZeroOrNull()) {
have_pending_runtime_throw_failure_ = true;
// Null array class; this code path will fail at runtime. Infer a merge-able type from the
// instruction type. TODO: have a proper notion of bottom here.
@@ -4804,7 +4805,7 @@
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Invalid reg type for array index (" << index_type << ")";
} else {
const RegType& array_type = work_line_->GetRegisterType(this, inst->VRegB_23x());
- if (array_type.IsZero()) {
+ if (array_type.IsZeroOrNull()) {
// Null array type; this code path will fail at runtime.
// Still check that the given value matches the instruction's type.
// Note: this is, as usual, complicated by the fact the the instruction isn't fully typed
@@ -4926,7 +4927,7 @@
DCHECK(self_->IsExceptionPending());
self_->ClearException();
return nullptr;
- } else if (obj_type.IsZero()) {
+ } else if (obj_type.IsZeroOrNull()) {
// Cannot infer and check type, however, access will cause null pointer exception.
// Fall through into a few last soft failure checks below.
} else if (!obj_type.IsReferenceTypes()) {
diff --git a/runtime/verifier/reg_type-inl.h b/runtime/verifier/reg_type-inl.h
index 631c6bd..f719782 100644
--- a/runtime/verifier/reg_type-inl.h
+++ b/runtime/verifier/reg_type-inl.h
@@ -29,6 +29,8 @@
namespace verifier {
inline bool RegType::CanAccess(const RegType& other) const {
+ DCHECK(IsReferenceTypes());
+ DCHECK(!IsNull());
if (Equals(other)) {
return true; // Trivial accessibility.
} else {
@@ -45,9 +47,13 @@
}
inline bool RegType::CanAccessMember(ObjPtr<mirror::Class> klass, uint32_t access_flags) const {
+ DCHECK(IsReferenceTypes());
if ((access_flags & kAccPublic) != 0) {
return true;
}
+ if (IsNull()) {
+ return true;
+ }
if (!IsUnresolvedTypes()) {
return GetClass()->CanAccessMember(klass, access_flags);
} else {
@@ -92,7 +98,7 @@
LOG(WARNING) << "RegType::AssignableFrom lhs is Conflict!";
return false;
case AssignmentType::kReference:
- if (rhs.IsZero()) {
+ if (rhs.IsZeroOrNull()) {
return true; // All reference types can be assigned null.
} else if (!rhs.IsReferenceTypes()) {
return false; // Expect rhs to be a reference type.
@@ -119,6 +125,7 @@
return result;
} else {
// Unresolved types are only assignable for null and equality.
+ // Null cannot be the left-hand side.
return false;
}
case AssignmentType::kNotAssignable:
@@ -199,6 +206,11 @@
return instance_;
}
+inline const NullType* NullType::GetInstance() {
+ DCHECK(instance_ != nullptr);
+ return instance_;
+}
+
inline void* RegType::operator new(size_t size, ScopedArenaAllocator* allocator) {
return allocator->Alloc(size, kArenaAllocMisc);
}
diff --git a/runtime/verifier/reg_type.cc b/runtime/verifier/reg_type.cc
index 8df2e0f..309c374 100644
--- a/runtime/verifier/reg_type.cc
+++ b/runtime/verifier/reg_type.cc
@@ -51,6 +51,7 @@
const DoubleLoType* DoubleLoType::instance_ = nullptr;
const DoubleHiType* DoubleHiType::instance_ = nullptr;
const IntegerType* IntegerType::instance_ = nullptr;
+const NullType* NullType::instance_ = nullptr;
PrimitiveType::PrimitiveType(mirror::Class* klass, const StringPiece& descriptor, uint16_t cache_id)
: RegType(klass, descriptor, cache_id) {
@@ -581,6 +582,10 @@
return a.IsConstantTypes() ? b : a;
}
+static const RegType& SelectNonConstant2(const RegType& a, const RegType& b) {
+ return a.IsConstantTypes() ? (b.IsZero() ? a : b) : a;
+}
+
const RegType& RegType::Merge(const RegType& incoming_type,
RegTypeCache* reg_types,
MethodVerifier* verifier) const {
@@ -695,8 +700,8 @@
// special. They may only ever be merged with themselves (must be taken care of by the
// caller of Merge(), see the DCHECK on entry). So mark any other merge as conflicting here.
return conflict;
- } else if (IsZero() || incoming_type.IsZero()) {
- return SelectNonConstant(*this, incoming_type); // 0 MERGE ref => ref
+ } else if (IsZeroOrNull() || incoming_type.IsZeroOrNull()) {
+ return SelectNonConstant2(*this, incoming_type); // 0 MERGE ref => ref
} else if (IsJavaLangObject() || incoming_type.IsJavaLangObject()) {
return reg_types->JavaLangObject(false); // Object MERGE ref => Object
} else if (IsUnresolvedTypes() || incoming_type.IsUnresolvedTypes()) {
@@ -965,6 +970,21 @@
return cmp1.CanAssignArray(cmp2, reg_types, class_loader, verifier, soft_error);
}
+const NullType* NullType::CreateInstance(mirror::Class* klass,
+ const StringPiece& descriptor,
+ uint16_t cache_id) {
+ CHECK(instance_ == nullptr);
+ instance_ = new NullType(klass, descriptor, cache_id);
+ return instance_;
+}
+
+void NullType::Destroy() {
+ if (NullType::instance_ != nullptr) {
+ delete instance_;
+ instance_ = nullptr;
+ }
+}
+
} // namespace verifier
} // namespace art
diff --git a/runtime/verifier/reg_type.h b/runtime/verifier/reg_type.h
index a2085a3..9055849 100644
--- a/runtime/verifier/reg_type.h
+++ b/runtime/verifier/reg_type.h
@@ -129,8 +129,12 @@
virtual bool IsConstantShort() const { return false; }
virtual bool IsOne() const { return false; }
virtual bool IsZero() const { return false; }
+ virtual bool IsNull() const { return false; }
bool IsReferenceTypes() const {
- return IsNonZeroReferenceTypes() || IsZero();
+ return IsNonZeroReferenceTypes() || IsZero() || IsNull();
+ }
+ bool IsZeroOrNull() const {
+ return IsZero() || IsNull();
}
virtual bool IsNonZeroReferenceTypes() const { return false; }
bool IsCategory1Types() const {
@@ -857,6 +861,46 @@
}
};
+// Special "null" type that captures the semantics of null / bottom.
+class NullType FINAL : public RegType {
+ public:
+ bool IsNull() const OVERRIDE {
+ return true;
+ }
+
+ // Get the singleton Null instance.
+ static const NullType* GetInstance() PURE;
+
+ // Create the singleton instance.
+ static const NullType* CreateInstance(mirror::Class* klass,
+ const StringPiece& descriptor,
+ uint16_t cache_id)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ static void Destroy();
+
+ std::string Dump() const OVERRIDE {
+ return "null";
+ }
+
+ AssignmentType GetAssignmentTypeImpl() const OVERRIDE {
+ return AssignmentType::kReference;
+ }
+
+ bool IsConstantTypes() const OVERRIDE {
+ return true;
+ }
+
+ private:
+ NullType(mirror::Class* klass, const StringPiece& descriptor, uint16_t cache_id)
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ : RegType(klass, descriptor, cache_id) {
+ CheckConstructorInvariants(this);
+ }
+
+ static const NullType* instance_;
+};
+
// Common parent of all uninitialized types. Uninitialized types are created by
// "new" dex
// instructions and must be passed to a constructor.
diff --git a/runtime/verifier/reg_type_cache-inl.h b/runtime/verifier/reg_type_cache-inl.h
index 197c976..61f34af 100644
--- a/runtime/verifier/reg_type_cache-inl.h
+++ b/runtime/verifier/reg_type_cache-inl.h
@@ -81,6 +81,9 @@
inline const ConflictType& RegTypeCache::Conflict() {
return *ConflictType::GetInstance();
}
+inline const NullType& RegTypeCache::Null() {
+ return *NullType::GetInstance();
+}
inline const ImpreciseConstType& RegTypeCache::ByteConstant() {
const ConstantType& result = FromCat1Const(std::numeric_limits<jbyte>::min(), false);
diff --git a/runtime/verifier/reg_type_cache.cc b/runtime/verifier/reg_type_cache.cc
index 4808381..a3f08c8 100644
--- a/runtime/verifier/reg_type_cache.cc
+++ b/runtime/verifier/reg_type_cache.cc
@@ -56,6 +56,7 @@
// Note: this must have the same order as CreatePrimitiveAndSmallConstantTypes.
entries_.push_back(UndefinedType::GetInstance());
entries_.push_back(ConflictType::GetInstance());
+ entries_.push_back(NullType::GetInstance());
entries_.push_back(BooleanType::GetInstance());
entries_.push_back(ByteType::GetInstance());
entries_.push_back(ShortType::GetInstance());
@@ -307,6 +308,7 @@
FloatType::Destroy();
DoubleLoType::Destroy();
DoubleHiType::Destroy();
+ NullType::Destroy();
for (int32_t value = kMinSmallConstant; value <= kMaxSmallConstant; ++value) {
const PreciseConstType* type = small_precise_constants_[value - kMinSmallConstant];
delete type;
@@ -354,6 +356,7 @@
};
create_primitive_type_instance(TypeHelper<UndefinedType>(""));
create_primitive_type_instance(TypeHelper<ConflictType>(""));
+ create_primitive_type_instance(TypeHelper<NullType>(""));
create_primitive_type_instance(TypeHelper<BooleanType>("Z"));
create_primitive_type_instance(TypeHelper<ByteType>("B"));
create_primitive_type_instance(TypeHelper<ShortType>("S"));
diff --git a/runtime/verifier/reg_type_cache.h b/runtime/verifier/reg_type_cache.h
index 054a2af..5277676 100644
--- a/runtime/verifier/reg_type_cache.h
+++ b/runtime/verifier/reg_type_cache.h
@@ -49,6 +49,7 @@
class LongHiType;
class LongLoType;
class MethodVerifier;
+class NullType;
class PreciseConstType;
class PreciseReferenceType;
class RegType;
@@ -123,6 +124,7 @@
const DoubleHiType& DoubleHi() REQUIRES_SHARED(Locks::mutator_lock_);
const UndefinedType& Undefined() REQUIRES_SHARED(Locks::mutator_lock_);
const ConflictType& Conflict();
+ const NullType& Null();
const PreciseReferenceType& JavaLangClass() REQUIRES_SHARED(Locks::mutator_lock_);
const PreciseReferenceType& JavaLangString() REQUIRES_SHARED(Locks::mutator_lock_);
@@ -180,7 +182,7 @@
kMinSmallConstant + 1];
static constexpr size_t kNumPrimitivesAndSmallConstants =
- 12 + (kMaxSmallConstant - kMinSmallConstant + 1);
+ 13 + (kMaxSmallConstant - kMinSmallConstant + 1);
// Have the well known global primitives been created?
static bool primitive_initialized_;
diff --git a/runtime/verifier/reg_type_test.cc b/runtime/verifier/reg_type_test.cc
index 6edeece..15a38f3 100644
--- a/runtime/verifier/reg_type_test.cc
+++ b/runtime/verifier/reg_type_test.cc
@@ -690,6 +690,8 @@
// | | | | | | |
// | #---------------#--------#---------#--------#---------#
// | |
+ // | null
+ // | |
// #--------------------------#----------------------------#
// |
// 0
@@ -707,6 +709,7 @@
const RegType& conflict = cache.Conflict();
const RegType& zero = cache.Zero();
+ const RegType& null = cache.Null();
const RegType& int_type = cache.Integer();
const RegType& obj = cache.JavaLangObject(false);
@@ -799,6 +802,7 @@
plain_nonobj_arr_classes.begin(),
plain_nonobj_arr_classes.end());
all_minus_uninit_conflict.push_back(&zero);
+ all_minus_uninit_conflict.push_back(&null);
all_minus_uninit_conflict.push_back(&obj);
std::vector<const RegType*> all_minus_uninit;
@@ -853,10 +857,12 @@
ADD_EDGE(int_type, conflict);
}
+ ADD_EDGE(zero, null);
+
// Unresolved.
{
- ADD_EDGE(zero, unresolved_a);
- ADD_EDGE(zero, unresolved_b);
+ ADD_EDGE(null, unresolved_a);
+ ADD_EDGE(null, unresolved_b);
ADD_EDGE(unresolved_a, unresolved_ab);
ADD_EDGE(unresolved_b, unresolved_ab);
@@ -887,7 +893,7 @@
// Classes.
{
- ADD_EDGE(zero, integer);
+ ADD_EDGE(null, integer);
ADD_EDGE(integer, number);
ADD_EDGE(number, obj);
}
@@ -902,10 +908,10 @@
ADD_EDGE(char_arr, obj);
ADD_EDGE(byte_arr, obj);
- ADD_EDGE(zero, integer_arr);
- ADD_EDGE(zero, number_arr_arr);
- ADD_EDGE(zero, char_arr);
- ADD_EDGE(zero, byte_arr);
+ ADD_EDGE(null, integer_arr);
+ ADD_EDGE(null, number_arr_arr);
+ ADD_EDGE(null, char_arr);
+ ADD_EDGE(null, byte_arr);
}
// Primitive.