diff options
41 files changed, 1875 insertions, 489 deletions
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 44c9816991..760147e62e 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -454,7 +454,9 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(" The image writer will group them together."); UsageError(""); UsageError(" --compact-dex-level=none|fast: None avoids generating compact dex, fast"); - UsageError(" generates compact dex with low compile time."); + UsageError(" generates compact dex with low compile time. If speed-profile is specified as"); + UsageError(" the compiler filter and the profile is not empty, the default compact dex"); + UsageError(" level is always used."); UsageError(""); UsageError(" --deduplicate-code=true|false: enable|disable code deduplication. Deduplicated"); UsageError(" code will have an arbitrary symbol tagged with [DEDUPED]."); diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc index 089aa80ad4..c635d59cd7 100644 --- a/dex2oat/linker/oat_writer.cc +++ b/dex2oat/linker/oat_writer.cc @@ -440,6 +440,11 @@ OatWriter::OatWriter(bool compiling_boot_image, absolute_patch_locations_(), profile_compilation_info_(info), compact_dex_level_(compact_dex_level) { + // If we have a profile, always use at least the default compact dex level. The reason behind + // this is that CompactDex conversion is not more expensive than normal dexlayout. + if (info != nullptr && compact_dex_level_ == CompactDexLevel::kCompactDexLevelNone) { + compact_dex_level_ = kDefaultCompactDexLevel; + } } static bool ValidateDexFileHeader(const uint8_t* raw_header, const char* location) { diff --git a/oatdump/oatdump_test.cc b/oatdump/oatdump_test.cc index 18cb2fde88..b4eddb91f9 100644 --- a/oatdump/oatdump_test.cc +++ b/oatdump/oatdump_test.cc @@ -72,6 +72,8 @@ TEST_F(OatDumpTest, TestSymbolizeStatic) { } TEST_F(OatDumpTest, TestExportDex) { + // Test is failing on target, b/77469384. + TEST_DISABLED_FOR_TARGET(); std::string error_msg; ASSERT_TRUE(Exec(kDynamic, kModeOat, {"--export-dex-to=" + tmp_dir_}, kListOnly, &error_msg)) << error_msg; diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc index e55eec4c30..261fe3e810 100644 --- a/openjdkjvmti/ti_class.cc +++ b/openjdkjvmti/ti_class.cc @@ -41,6 +41,7 @@ #include "base/macros.h" #include "base/utils.h" #include "class_linker.h" +#include "class_loader_utils.h" #include "class_table-inl.h" #include "common_throws.h" #include "dex/art_dex_file_loader.h" @@ -942,23 +943,12 @@ jvmtiError ClassUtil::GetClassLoaderClassDescriptors(jvmtiEnv* env, art::Handle<art::mirror::ClassLoader> class_loader( hs.NewHandle(soa.Decode<art::mirror::ClassLoader>(loader))); std::vector<const art::DexFile*> dex_files; - ClassLoaderHelper::VisitDexFileObjects( - self, + art::VisitClassLoaderDexFiles( + soa, class_loader, - [&] (art::ObjPtr<art::mirror::Object> dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_) { - art::StackHandleScope<2> hs(self); - art::Handle<art::mirror::Object> h_dex_file(hs.NewHandle(dex_file)); - art::Handle<art::mirror::LongArray> cookie( - hs.NewHandle(ClassLoaderHelper::GetDexFileCookie(h_dex_file))); - size_t num_elements = cookie->GetLength(); - // We need to skip over the oat_file that's the first element. The other elements are all - // dex files. - for (size_t i = 1; i < num_elements; i++) { - dex_files.push_back( - reinterpret_cast<const art::DexFile*>(static_cast<uintptr_t>(cookie->Get(i)))); - } - // Iterate over all dex files. - return true; + [&](const art::DexFile* dex_file) { + dex_files.push_back(dex_file); + return true; // Continue with other dex files. }); // We hold the loader so the dex files won't go away until after this call at worst. return CopyClassDescriptors(env, dex_files, count_ptr, classes); diff --git a/runtime/Android.bp b/runtime/Android.bp index c0f1c366b8..08025824c3 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -46,6 +46,7 @@ cc_defaults { "class_table.cc", "common_throws.cc", "compiler_filter.cc", + "debug_print.cc", "debugger.cc", "dex/art_dex_file_loader.cc", "dex/dex_file_annotations.cc", diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 879301c4a0..d98e0b2c54 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -53,6 +53,7 @@ #include "class_loader_utils.h" #include "class_table-inl.h" #include "compiler_callbacks.h" +#include "debug_print.h" #include "debugger.h" #include "dex/descriptors_names.h" #include "dex/dex_file-inl.h" @@ -1124,12 +1125,8 @@ static bool FlattenPathClassLoader(ObjPtr<mirror::ClassLoader> class_loader, DCHECK(out_dex_file_names != nullptr); DCHECK(error_msg != nullptr); ScopedObjectAccessUnchecked soa(Thread::Current()); - ArtField* const dex_path_list_field = - jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList); - ArtField* const dex_elements_field = - jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements); - CHECK(dex_path_list_field != nullptr); - CHECK(dex_elements_field != nullptr); + StackHandleScope<1> hs(soa.Self()); + Handle<mirror::ClassLoader> handle(hs.NewHandle(class_loader)); while (!ClassLinker::IsBootClassLoader(soa, class_loader)) { if (soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader) != class_loader->GetClass()) { @@ -1138,32 +1135,29 @@ static bool FlattenPathClassLoader(ObjPtr<mirror::ClassLoader> class_loader, // Unsupported class loader. return false; } - ObjPtr<mirror::Object> dex_path_list = dex_path_list_field->GetObject(class_loader); - if (dex_path_list != nullptr) { - // DexPathList has an array dexElements of Elements[] which each contain a dex file. - ObjPtr<mirror::Object> dex_elements_obj = dex_elements_field->GetObject(dex_path_list); - // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look - // at the mCookie which is a DexFile vector. - if (dex_elements_obj != nullptr) { - ObjPtr<mirror::ObjectArray<mirror::Object>> dex_elements = - dex_elements_obj->AsObjectArray<mirror::Object>(); - // Reverse order since we insert the parent at the front. - for (int32_t i = dex_elements->GetLength() - 1; i >= 0; --i) { - ObjPtr<mirror::Object> element = dex_elements->GetWithoutChecks(i); - if (element == nullptr) { - *error_msg = StringPrintf("Null dex element at index %d", i); - return false; - } - ObjPtr<mirror::String> name; - if (!GetDexPathListElementName(element, &name)) { - *error_msg = StringPrintf("Invalid dex path list element at index %d", i); - return false; - } - if (name != nullptr) { - out_dex_file_names->push_front(name.Ptr()); - } - } + // Get element names. Sets error to true on failure. + auto add_element_names = [&](ObjPtr<mirror::Object> element, bool* error) + REQUIRES_SHARED(Locks::mutator_lock_) { + if (element == nullptr) { + *error_msg = "Null dex element"; + *error = true; // Null element is a critical error. + return false; // Had an error, stop the visit. } + ObjPtr<mirror::String> name; + if (!GetDexPathListElementName(element, &name)) { + *error_msg = "Invalid dex path list element"; + *error = false; // Invalid element is not a critical error. + return false; // Stop the visit. + } + if (name != nullptr) { + out_dex_file_names->push_front(name.Ptr()); + } + return true; // Continue with the next Element. + }; + bool error = VisitClassLoaderDexElements(soa, handle, add_element_names, /* error */ false); + if (error) { + // An error occurred during DexPathList Element visiting. + return false; } class_loader = class_loader->GetParent(); } @@ -2473,71 +2467,33 @@ ObjPtr<mirror::Class> ClassLinker::FindClassInBaseDexClassLoaderClassPath( const char* descriptor, size_t hash, Handle<mirror::ClassLoader> class_loader) { - CHECK(IsPathOrDexClassLoader(soa, class_loader) || IsDelegateLastClassLoader(soa, class_loader)) + DCHECK(IsPathOrDexClassLoader(soa, class_loader) || IsDelegateLastClassLoader(soa, class_loader)) << "Unexpected class loader for descriptor " << descriptor; - Thread* self = soa.Self(); - ArtField* const cookie_field = - jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie); - ArtField* const dex_file_field = - jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile); - ObjPtr<mirror::Object> dex_path_list = - jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList)-> - GetObject(class_loader.Get()); - if (dex_path_list != nullptr && dex_file_field != nullptr && cookie_field != nullptr) { - // DexPathList has an array dexElements of Elements[] which each contain a dex file. - ObjPtr<mirror::Object> dex_elements_obj = - jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)-> - GetObject(dex_path_list); - // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look - // at the mCookie which is a DexFile vector. - if (dex_elements_obj != nullptr) { - StackHandleScope<1> hs(self); - Handle<mirror::ObjectArray<mirror::Object>> dex_elements = - hs.NewHandle(dex_elements_obj->AsObjectArray<mirror::Object>()); - for (int32_t i = 0; i < dex_elements->GetLength(); ++i) { - ObjPtr<mirror::Object> element = dex_elements->GetWithoutChecks(i); - if (element == nullptr) { - // Should never happen, fall back to java code to throw a NPE. - break; - } - ObjPtr<mirror::Object> dex_file = dex_file_field->GetObject(element); - if (dex_file != nullptr) { - ObjPtr<mirror::LongArray> long_array = cookie_field->GetObject(dex_file)->AsLongArray(); - if (long_array == nullptr) { - // This should never happen so log a warning. - LOG(WARNING) << "Null DexFile::mCookie for " << descriptor; - break; - } - int32_t long_array_size = long_array->GetLength(); - // First element is the oat file. - for (int32_t j = kDexFileIndexStart; j < long_array_size; ++j) { - const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>(static_cast<uintptr_t>( - long_array->GetWithoutChecks(j))); - const DexFile::ClassDef* dex_class_def = - OatDexFile::FindClassDef(*cp_dex_file, descriptor, hash); - if (dex_class_def != nullptr) { - ObjPtr<mirror::Class> klass = DefineClass(self, - descriptor, - hash, - class_loader, - *cp_dex_file, - *dex_class_def); - if (klass == nullptr) { - CHECK(self->IsExceptionPending()) << descriptor; - self->ClearException(); - // TODO: Is it really right to break here, and not check the other dex files? - return nullptr; - } - return klass; - } - } - } - } - } - self->AssertNoPendingException(); - } - return nullptr; + ObjPtr<mirror::Class> ret; + auto define_class = [&](const DexFile* cp_dex_file) REQUIRES_SHARED(Locks::mutator_lock_) { + const DexFile::ClassDef* dex_class_def = + OatDexFile::FindClassDef(*cp_dex_file, descriptor, hash); + if (dex_class_def != nullptr) { + ObjPtr<mirror::Class> klass = DefineClass(soa.Self(), + descriptor, + hash, + class_loader, + *cp_dex_file, + *dex_class_def); + if (klass == nullptr) { + CHECK(soa.Self()->IsExceptionPending()) << descriptor; + soa.Self()->ClearException(); + // TODO: Is it really right to break here, and not check the other dex files? + } + ret = klass; + return false; // Found a Class (or error == nullptr), stop visit. + } + return true; // Continue with the next DexFile. + }; + + VisitClassLoaderDexFiles(soa, class_loader, define_class); + return ret; } mirror::Class* ClassLinker::FindClass(Thread* self, @@ -7878,106 +7834,6 @@ ObjPtr<mirror::Class> ClassLinker::DoResolveType(dex::TypeIndex type_idx, return resolved; } -std::string DescribeSpace(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) { - std::ostringstream oss; - gc::Heap* heap = Runtime::Current()->GetHeap(); - gc::space::ContinuousSpace* cs = heap->FindContinuousSpaceFromAddress(klass.Ptr()); - if (cs != nullptr) { - if (cs->IsImageSpace()) { - oss << "image;" << cs->GetName() << ";" << cs->AsImageSpace()->GetImageFilename(); - } else { - oss << "continuous;" << cs->GetName(); - } - } else { - gc::space::DiscontinuousSpace* ds = - heap->FindDiscontinuousSpaceFromObject(klass, /* fail_ok */ true); - if (ds != nullptr) { - oss << "discontinuous;" << ds->GetName(); - } else { - oss << "invalid"; - } - } - return oss.str(); -} - -std::string DescribeLoaders(ObjPtr<mirror::ClassLoader> loader, const char* class_descriptor) - REQUIRES_SHARED(Locks::mutator_lock_) { - std::ostringstream oss; - uint32_t hash = ComputeModifiedUtf8Hash(class_descriptor); - ObjPtr<mirror::Class> path_class_loader = - WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_PathClassLoader); - ObjPtr<mirror::Class> dex_class_loader = - WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_DexClassLoader); - ObjPtr<mirror::Class> delegate_last_class_loader = - WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_DelegateLastClassLoader); - - // Print the class loader chain. - bool found_class = false; - const char* loader_separator = ""; - if (loader == nullptr) { - oss << "BootClassLoader"; // This would be unexpected. - } - for (; loader != nullptr; loader = loader->GetParent()) { - oss << loader_separator << loader->GetClass()->PrettyDescriptor(); - loader_separator = ";"; - // If we didn't find the interface yet, try to find it in the current class loader. - if (!found_class) { - ClassTable* table = Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(loader); - ObjPtr<mirror::Class> klass = - (table != nullptr) ? table->Lookup(class_descriptor, hash) : nullptr; - if (klass != nullptr) { - found_class = true; - oss << "[hit:" << DescribeSpace(klass) << "]"; - } - } - - // For PathClassLoader, DexClassLoader or DelegateLastClassLoader - // also dump the dex file locations. - if (loader->GetClass() == path_class_loader || - loader->GetClass() == dex_class_loader || - loader->GetClass() == delegate_last_class_loader) { - ArtField* const cookie_field = - jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie); - ArtField* const dex_file_field = - jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile); - ObjPtr<mirror::Object> dex_path_list = - jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList)-> - GetObject(loader); - if (dex_path_list != nullptr && dex_file_field != nullptr && cookie_field != nullptr) { - ObjPtr<mirror::Object> dex_elements_obj = - jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)-> - GetObject(dex_path_list); - if (dex_elements_obj != nullptr) { - ObjPtr<mirror::ObjectArray<mirror::Object>> dex_elements = - dex_elements_obj->AsObjectArray<mirror::Object>(); - oss << "("; - const char* path_separator = ""; - for (int32_t i = 0; i != dex_elements->GetLength(); ++i) { - ObjPtr<mirror::Object> element = dex_elements->GetWithoutChecks(i); - ObjPtr<mirror::Object> dex_file = - (element != nullptr) ? dex_file_field->GetObject(element) : nullptr; - ObjPtr<mirror::LongArray> long_array = - (dex_file != nullptr) ? cookie_field->GetObject(dex_file)->AsLongArray() : nullptr; - if (long_array != nullptr) { - int32_t long_array_size = long_array->GetLength(); - // First element is the oat file. - for (int32_t j = kDexFileIndexStart; j < long_array_size; ++j) { - const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>( - static_cast<uintptr_t>(long_array->GetWithoutChecks(j))); - oss << path_separator << cp_dex_file->GetLocation(); - path_separator = ":"; - } - } - } - oss << ")"; - } - } - } - } - - return oss.str(); -} - ArtMethod* ClassLinker::FindResolvedMethod(ObjPtr<mirror::Class> klass, ObjPtr<mirror::DexCache> dex_cache, ObjPtr<mirror::ClassLoader> class_loader, @@ -7993,8 +7849,8 @@ ArtMethod* ClassLinker::FindResolvedMethod(ObjPtr<mirror::Class> klass, } DCHECK(resolved == nullptr || resolved->GetDeclaringClassUnchecked() != nullptr); if (resolved != nullptr && - hiddenapi::ShouldBlockAccessToMember( - resolved, class_loader, dex_cache, hiddenapi::kLinking)) { + hiddenapi::GetMemberAction( + resolved, class_loader, dex_cache, hiddenapi::kLinking) == hiddenapi::kDeny) { resolved = nullptr; } if (resolved != nullptr) { @@ -8017,6 +7873,40 @@ ArtMethod* ClassLinker::FindResolvedMethod(ObjPtr<mirror::Class> klass, return resolved; } +// Returns true if `method` is either null or hidden. +// Does not print any warnings if it is hidden. +static bool CheckNoSuchMethod(ArtMethod* method, + ObjPtr<mirror::DexCache> dex_cache, + ObjPtr<mirror::ClassLoader> class_loader) + REQUIRES_SHARED(Locks::mutator_lock_) { + return method == nullptr || + hiddenapi::GetMemberAction(method, + class_loader, + dex_cache, + hiddenapi::kNone) // do not print warnings + == hiddenapi::kDeny; +} + +ArtMethod* ClassLinker::FindIncompatibleMethod(ObjPtr<mirror::Class> klass, + ObjPtr<mirror::DexCache> dex_cache, + ObjPtr<mirror::ClassLoader> class_loader, + uint32_t method_idx) { + if (klass->IsInterface()) { + ArtMethod* method = klass->FindClassMethod(dex_cache, method_idx, image_pointer_size_); + return CheckNoSuchMethod(method, dex_cache, class_loader) ? nullptr : method; + } else { + // If there was an interface method with the same signature, we would have + // found it in the "copied" methods. Only DCHECK that the interface method + // really does not exist. + if (kIsDebugBuild) { + ArtMethod* method = + klass->FindInterfaceMethod(dex_cache, method_idx, image_pointer_size_); + DCHECK(CheckNoSuchMethod(method, dex_cache, class_loader)); + } + return nullptr; + } +} + template <ClassLinker::ResolveMode kResolveMode> ArtMethod* ClassLinker::ResolveMethod(uint32_t method_idx, Handle<mirror::DexCache> dex_cache, @@ -8092,13 +7982,7 @@ ArtMethod* ClassLinker::ResolveMethod(uint32_t method_idx, // If we had a method, or if we can find one with another lookup type, // it's an incompatible-class-change error. if (resolved == nullptr) { - if (klass->IsInterface()) { - resolved = klass->FindClassMethod(dex_cache.Get(), method_idx, pointer_size); - } else { - // If there was an interface method with the same signature, - // we would have found it also in the "copied" methods. - DCHECK(klass->FindInterfaceMethod(dex_cache.Get(), method_idx, pointer_size) == nullptr); - } + resolved = FindIncompatibleMethod(klass, dex_cache.Get(), class_loader.Get(), method_idx); } if (resolved != nullptr) { ThrowIncompatibleClassChangeError(type, resolved->GetInvokeType(), resolved, referrer); @@ -8136,8 +8020,8 @@ ArtMethod* ClassLinker::ResolveMethodWithoutInvokeType(uint32_t method_idx, resolved = klass->FindClassMethod(dex_cache.Get(), method_idx, image_pointer_size_); } if (resolved != nullptr && - hiddenapi::ShouldBlockAccessToMember( - resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking)) { + hiddenapi::GetMemberAction( + resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking) == hiddenapi::kDeny) { resolved = nullptr; } return resolved; @@ -8216,8 +8100,8 @@ ArtField* ClassLinker::ResolveField(uint32_t field_idx, } if (resolved == nullptr || - hiddenapi::ShouldBlockAccessToMember( - resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking)) { + hiddenapi::GetMemberAction( + resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking) == hiddenapi::kDeny) { const char* name = dex_file.GetFieldName(field_id); const char* type = dex_file.GetFieldTypeDescriptor(field_id); ThrowNoSuchFieldError(is_static ? "static " : "instance ", klass, type, name); @@ -8250,8 +8134,8 @@ ArtField* ClassLinker::ResolveFieldJLS(uint32_t field_idx, StringPiece type(dex_file.GetFieldTypeDescriptor(field_id)); resolved = mirror::Class::FindField(self, klass, name, type); if (resolved != nullptr && - hiddenapi::ShouldBlockAccessToMember( - resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking)) { + hiddenapi::GetMemberAction( + resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking) == hiddenapi::kDeny) { resolved = nullptr; } if (resolved != nullptr) { diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 2471f146aa..c46e8271e5 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -321,6 +321,15 @@ class ClassLinker { uint32_t method_idx) REQUIRES_SHARED(Locks::mutator_lock_); + // Find a method using the wrong lookup mechanism. If `klass` is an interface, + // search for a class method. If it is a class, search for an interface method. + // This is useful when throwing IncompatibleClassChangeError. + ArtMethod* FindIncompatibleMethod(ObjPtr<mirror::Class> klass, + ObjPtr<mirror::DexCache> dex_cache, + ObjPtr<mirror::ClassLoader> class_loader, + uint32_t method_idx) + REQUIRES_SHARED(Locks::mutator_lock_); + // Resolve a method with a given ID from the DexFile associated with the given DexCache // and ClassLoader, storing the result in DexCache. The ClassLinker and ClassLoader are // used as in ResolveType. What is unique is the method type argument which is used to diff --git a/runtime/class_loader_utils.h b/runtime/class_loader_utils.h index d160a511de..1439f11636 100644 --- a/runtime/class_loader_utils.h +++ b/runtime/class_loader_utils.h @@ -17,8 +17,12 @@ #ifndef ART_RUNTIME_CLASS_LOADER_UTILS_H_ #define ART_RUNTIME_CLASS_LOADER_UTILS_H_ +#include "art_field-inl.h" +#include "base/mutex.h" #include "handle_scope.h" +#include "jni_internal.h" #include "mirror/class_loader.h" +#include "native/dalvik_system_DexFile.h" #include "scoped_thread_state_change-inl.h" #include "well_known_classes.h" @@ -26,7 +30,7 @@ namespace art { // Returns true if the given class loader is either a PathClassLoader or a DexClassLoader. // (they both have the same behaviour with respect to class lockup order) -static bool IsPathOrDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa, +inline bool IsPathOrDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa, Handle<mirror::ClassLoader> class_loader) REQUIRES_SHARED(Locks::mutator_lock_) { mirror::Class* class_loader_class = class_loader->GetClass(); @@ -37,7 +41,7 @@ static bool IsPathOrDexClassLoader(ScopedObjectAccessAlreadyRunnable& soa, soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader)); } -static bool IsDelegateLastClassLoader(ScopedObjectAccessAlreadyRunnable& soa, +inline bool IsDelegateLastClassLoader(ScopedObjectAccessAlreadyRunnable& soa, Handle<mirror::ClassLoader> class_loader) REQUIRES_SHARED(Locks::mutator_lock_) { mirror::Class* class_loader_class = class_loader->GetClass(); @@ -45,6 +49,114 @@ static bool IsDelegateLastClassLoader(ScopedObjectAccessAlreadyRunnable& soa, soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DelegateLastClassLoader); } +// Visit the DexPathList$Element instances in the given classloader with the given visitor. +// Constraints on the visitor: +// * The visitor should return true to continue visiting more Elements. +// * The last argument of the visitor is an out argument of RetType. It will be returned +// when the visitor ends the visit (by returning false). +// This function assumes that the given classloader is a subclass of BaseDexClassLoader! +template <typename Visitor, typename RetType> +inline RetType VisitClassLoaderDexElements(ScopedObjectAccessAlreadyRunnable& soa, + Handle<mirror::ClassLoader> class_loader, + Visitor fn, + RetType defaultReturn) + REQUIRES_SHARED(Locks::mutator_lock_) { + Thread* self = soa.Self(); + ObjPtr<mirror::Object> dex_path_list = + jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList)-> + GetObject(class_loader.Get()); + if (dex_path_list != nullptr) { + // DexPathList has an array dexElements of Elements[] which each contain a dex file. + ObjPtr<mirror::Object> dex_elements_obj = + jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)-> + GetObject(dex_path_list); + // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look + // at the mCookie which is a DexFile vector. + if (dex_elements_obj != nullptr) { + StackHandleScope<1> hs(self); + Handle<mirror::ObjectArray<mirror::Object>> dex_elements = + hs.NewHandle(dex_elements_obj->AsObjectArray<mirror::Object>()); + for (int32_t i = 0; i < dex_elements->GetLength(); ++i) { + ObjPtr<mirror::Object> element = dex_elements->GetWithoutChecks(i); + if (element == nullptr) { + // Should never happen, fail. + break; + } + RetType ret_value; + if (!fn(element, &ret_value)) { + return ret_value; + } + } + } + self->AssertNoPendingException(); + } + return defaultReturn; +} + +// Visit the DexFiles in the given classloader with the given visitor. +// Constraints on the visitor: +// * The visitor should return true to continue visiting more DexFiles. +// * The last argument of the visitor is an out argument of RetType. It will be returned +// when the visitor ends the visit (by returning false). +// This function assumes that the given classloader is a subclass of BaseDexClassLoader! +template <typename Visitor, typename RetType> +inline RetType VisitClassLoaderDexFiles(ScopedObjectAccessAlreadyRunnable& soa, + Handle<mirror::ClassLoader> class_loader, + Visitor fn, + RetType defaultReturn) + REQUIRES_SHARED(Locks::mutator_lock_) { + ArtField* const cookie_field = + jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie); + ArtField* const dex_file_field = + jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile); + if (dex_file_field == nullptr || cookie_field == nullptr) { + return defaultReturn; + } + auto visit_dex_files = [&](ObjPtr<mirror::Object> element, RetType* ret) + REQUIRES_SHARED(Locks::mutator_lock_) { + ObjPtr<mirror::Object> dex_file = dex_file_field->GetObject(element); + if (dex_file != nullptr) { + ObjPtr<mirror::LongArray> long_array = cookie_field->GetObject(dex_file)->AsLongArray(); + if (long_array == nullptr) { + // This should never happen so log a warning. + LOG(WARNING) << "Null DexFile::mCookie"; + *ret = defaultReturn; + return true; + } + int32_t long_array_size = long_array->GetLength(); + // First element is the oat file. + for (int32_t j = kDexFileIndexStart; j < long_array_size; ++j) { + const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>(static_cast<uintptr_t>( + long_array->GetWithoutChecks(j))); + RetType ret_value; + if (!fn(cp_dex_file, /* out */ &ret_value)) { + *ret = ret_value; + return false; + } + } + } + return true; + }; + + return VisitClassLoaderDexElements(soa, class_loader, visit_dex_files, defaultReturn); +} + +// Simplified version of the above, w/o out argument. +template <typename Visitor> +inline void VisitClassLoaderDexFiles(ScopedObjectAccessAlreadyRunnable& soa, + Handle<mirror::ClassLoader> class_loader, + Visitor fn) + REQUIRES_SHARED(Locks::mutator_lock_) { + auto helper = [&fn](const art::DexFile* dex_file, void** ATTRIBUTE_UNUSED) + REQUIRES_SHARED(Locks::mutator_lock_) { + return fn(dex_file); + }; + VisitClassLoaderDexFiles<decltype(helper), void*>(soa, + class_loader, + helper, + /* default */ nullptr); +} + } // namespace art #endif // ART_RUNTIME_CLASS_LOADER_UTILS_H_ diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index add300bdf3..05159e253d 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -36,6 +36,7 @@ #include "base/stl_util.h" #include "base/unix_file/fd_file.h" #include "class_linker.h" +#include "class_loader_utils.h" #include "compiler_callbacks.h" #include "dex/art_dex_file_loader.h" #include "dex/dex_file-inl.h" @@ -543,58 +544,23 @@ std::vector<const DexFile*> CommonRuntimeTestImpl::GetDexFiles(jobject jclass_lo std::vector<const DexFile*> CommonRuntimeTestImpl::GetDexFiles( ScopedObjectAccess& soa, Handle<mirror::ClassLoader> class_loader) { - std::vector<const DexFile*> ret; - DCHECK( (class_loader->GetClass() == soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader)) || (class_loader->GetClass() == soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DelegateLastClassLoader))); - // The class loader is a PathClassLoader which inherits from BaseDexClassLoader. - // We need to get the DexPathList and loop through it. - ArtField* cookie_field = jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie); - ArtField* dex_file_field = - jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile); - ObjPtr<mirror::Object> dex_path_list = - jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList)-> - GetObject(class_loader.Get()); - if (dex_path_list != nullptr && dex_file_field!= nullptr && cookie_field != nullptr) { - // DexPathList has an array dexElements of Elements[] which each contain a dex file. - ObjPtr<mirror::Object> dex_elements_obj = - jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)-> - GetObject(dex_path_list); - // Loop through each dalvik.system.DexPathList$Element's dalvik.system.DexFile and look - // at the mCookie which is a DexFile vector. - if (dex_elements_obj != nullptr) { - StackHandleScope<1> hs(soa.Self()); - Handle<mirror::ObjectArray<mirror::Object>> dex_elements = - hs.NewHandle(dex_elements_obj->AsObjectArray<mirror::Object>()); - for (int32_t i = 0; i < dex_elements->GetLength(); ++i) { - ObjPtr<mirror::Object> element = dex_elements->GetWithoutChecks(i); - if (element == nullptr) { - // Should never happen, fall back to java code to throw a NPE. - break; - } - ObjPtr<mirror::Object> dex_file = dex_file_field->GetObject(element); - if (dex_file != nullptr) { - ObjPtr<mirror::LongArray> long_array = cookie_field->GetObject(dex_file)->AsLongArray(); - DCHECK(long_array != nullptr); - int32_t long_array_size = long_array->GetLength(); - for (int32_t j = kDexFileIndexStart; j < long_array_size; ++j) { - const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>(static_cast<uintptr_t>( - long_array->GetWithoutChecks(j))); - if (cp_dex_file == nullptr) { - LOG(WARNING) << "Null DexFile"; - continue; - } - ret.push_back(cp_dex_file); - } - } - } - } - } - + std::vector<const DexFile*> ret; + VisitClassLoaderDexFiles(soa, + class_loader, + [&](const DexFile* cp_dex_file) { + if (cp_dex_file == nullptr) { + LOG(WARNING) << "Null DexFile"; + } else { + ret.push_back(cp_dex_file); + } + return true; + }); return ret; } diff --git a/runtime/debug_print.cc b/runtime/debug_print.cc new file mode 100644 index 0000000000..44a183669d --- /dev/null +++ b/runtime/debug_print.cc @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sstream> + +#include "debug_print.h" + +#include "class_linker.h" +#include "class_table.h" +#include "class_loader_utils.h" +#include "dex/utf.h" +#include "gc/heap.h" +#include "gc/space/space-inl.h" +#include "mirror/class.h" +#include "mirror/class_loader.h" +#include "runtime.h" +#include "scoped_thread_state_change-inl.h" +#include "thread-current-inl.h" +#include "well_known_classes.h" + +namespace art { + +std::string DescribeSpace(ObjPtr<mirror::Class> klass) { + std::ostringstream oss; + gc::Heap* heap = Runtime::Current()->GetHeap(); + gc::space::ContinuousSpace* cs = + heap->FindContinuousSpaceFromObject(klass.Ptr(), /* fail_ok */ true); + if (cs != nullptr) { + if (cs->IsImageSpace()) { + oss << "image;" << cs->GetName() << ";" << cs->AsImageSpace()->GetImageFilename(); + } else { + oss << "continuous;" << cs->GetName(); + } + } else { + gc::space::DiscontinuousSpace* ds = + heap->FindDiscontinuousSpaceFromObject(klass, /* fail_ok */ true); + if (ds != nullptr) { + oss << "discontinuous;" << ds->GetName(); + } else { + oss << "invalid"; + } + } + return oss.str(); +} + +std::string DescribeLoaders(ObjPtr<mirror::ClassLoader> loader, const char* class_descriptor) { + std::ostringstream oss; + uint32_t hash = ComputeModifiedUtf8Hash(class_descriptor); + ObjPtr<mirror::Class> path_class_loader = + WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_PathClassLoader); + ObjPtr<mirror::Class> dex_class_loader = + WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_DexClassLoader); + ObjPtr<mirror::Class> delegate_last_class_loader = + WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_DelegateLastClassLoader); + + // Print the class loader chain. + bool found_class = false; + const char* loader_separator = ""; + if (loader == nullptr) { + oss << "BootClassLoader"; // This would be unexpected. + } + for (; loader != nullptr; loader = loader->GetParent()) { + oss << loader_separator << loader->GetClass()->PrettyDescriptor(); + loader_separator = ";"; + // If we didn't find the class yet, try to find it in the current class loader. + if (!found_class) { + ClassTable* table = Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(loader); + ObjPtr<mirror::Class> klass = + (table != nullptr) ? table->Lookup(class_descriptor, hash) : nullptr; + if (klass != nullptr) { + found_class = true; + oss << "[hit:" << DescribeSpace(klass) << "]"; + } + } + + // For PathClassLoader, DexClassLoader or DelegateLastClassLoader + // also dump the dex file locations. + if (loader->GetClass() == path_class_loader || + loader->GetClass() == dex_class_loader || + loader->GetClass() == delegate_last_class_loader) { + oss << "("; + ScopedObjectAccessUnchecked soa(Thread::Current()); + StackHandleScope<1> hs(soa.Self()); + Handle<mirror::ClassLoader> handle(hs.NewHandle(loader)); + const char* path_separator = ""; + VisitClassLoaderDexFiles(soa, + handle, + [&](const DexFile* dex_file) { + oss << path_separator << dex_file->GetLocation(); + path_separator = ":"; + return true; // Continue with the next DexFile. + }); + oss << ")"; + } + } + + return oss.str(); +} + +} // namespace art diff --git a/runtime/debug_print.h b/runtime/debug_print.h new file mode 100644 index 0000000000..479c36a02f --- /dev/null +++ b/runtime/debug_print.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_DEBUG_PRINT_H_ +#define ART_RUNTIME_DEBUG_PRINT_H_ + +#include "base/mutex.h" +#include "mirror/object.h" + +// Helper functions for printing extra information for certain hard to diagnose bugs. + +namespace art { + +std::string DescribeSpace(ObjPtr<mirror::Class> klass) + REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR; +std::string DescribeLoaders(ObjPtr<mirror::ClassLoader> loader, const char* class_descriptor) + REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR; + +} // namespace art + +#endif // ART_RUNTIME_DEBUG_PRINT_H_ diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 2eb3ab923c..66cec7113e 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -19,6 +19,7 @@ #include "base/enums.h" #include "callee_save_frame.h" #include "common_throws.h" +#include "debug_print.h" #include "debugger.h" #include "dex/dex_file-inl.h" #include "dex/dex_file_types.h" @@ -1187,9 +1188,24 @@ static std::string DumpInstruction(ArtMethod* method, uint32_t dex_pc) } } +static void DumpB74410240ClassData(ObjPtr<mirror::Class> klass) + REQUIRES_SHARED(Locks::mutator_lock_) { + std::string storage; + const char* descriptor = klass->GetDescriptor(&storage); + LOG(FATAL_WITHOUT_ABORT) << " " << DescribeLoaders(klass->GetClassLoader(), descriptor); + const OatDexFile* oat_dex_file = klass->GetDexFile().GetOatDexFile(); + if (oat_dex_file != nullptr) { + const OatFile* oat_file = oat_dex_file->GetOatFile(); + const char* dex2oat_cmdline = + oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kDex2OatCmdLineKey); + LOG(FATAL_WITHOUT_ABORT) << " OatFile: " << oat_file->GetLocation() + << "; " << (dex2oat_cmdline != nullptr ? dex2oat_cmdline : "<not recorded>"); + } +} + static void DumpB74410240DebugData(ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_) { // Mimick the search for the caller and dump some data while doing so. - LOG(FATAL_WITHOUT_ABORT) << "Dumping debugging data for b/74410240."; + LOG(FATAL_WITHOUT_ABORT) << "Dumping debugging data, please attach a bugreport to b/74410240."; constexpr CalleeSaveType type = CalleeSaveType::kSaveRefsAndArgs; CHECK_EQ(*sp, Runtime::Current()->GetCalleeSaveMethod(type)); @@ -1227,6 +1243,7 @@ static void DumpB74410240DebugData(ArtMethod** sp) REQUIRES_SHARED(Locks::mutato << " dex pc: " << dex_pc << " dex file: " << outer_method->GetDexFile()->GetLocation() << " class table: " << class_linker->ClassTableForClassLoader(outer_method->GetClassLoader()); + DumpB74410240ClassData(outer_method->GetDeclaringClass()); LOG(FATAL_WITHOUT_ABORT) << " instruction: " << DumpInstruction(outer_method, dex_pc); ArtMethod* caller = outer_method; @@ -1260,7 +1277,8 @@ static void DumpB74410240DebugData(ArtMethod** sp) REQUIRES_SHARED(Locks::mutato << " dex pc: " << dex_pc << " dex file: " << caller->GetDexFile()->GetLocation() << " class table: " - << class_linker->ClassTableForClassLoader(outer_method->GetClassLoader()); + << class_linker->ClassTableForClassLoader(caller->GetClassLoader()); + DumpB74410240ClassData(caller->GetDeclaringClass()); LOG(FATAL_WITHOUT_ABORT) << " instruction: " << DumpInstruction(caller, dex_pc); } } diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc index f0b36a090a..02b4f5349d 100644 --- a/runtime/hidden_api.cc +++ b/runtime/hidden_api.cc @@ -16,13 +16,20 @@ #include "hidden_api.h" +#include <nativehelper/scoped_local_ref.h> + #include "base/dumpable.h" +#include "thread-current-inl.h" +#include "well_known_classes.h" namespace art { namespace hiddenapi { static inline std::ostream& operator<<(std::ostream& os, AccessMethod value) { switch (value) { + case kNone: + LOG(FATAL) << "Internal access to hidden API should not be logged"; + UNREACHABLE(); case kReflection: os << "reflection"; break; @@ -42,12 +49,11 @@ static constexpr bool EnumsEqual(EnforcementPolicy policy, HiddenApiAccessFlags: // GetMemberAction-related static_asserts. static_assert( - EnumsEqual(EnforcementPolicy::kAllLists, HiddenApiAccessFlags::kLightGreylist) && EnumsEqual(EnforcementPolicy::kDarkGreyAndBlackList, HiddenApiAccessFlags::kDarkGreylist) && EnumsEqual(EnforcementPolicy::kBlacklistOnly, HiddenApiAccessFlags::kBlacklist), "Mismatch between EnforcementPolicy and ApiList enums"); static_assert( - EnforcementPolicy::kAllLists < EnforcementPolicy::kDarkGreyAndBlackList && + EnforcementPolicy::kJustWarn < EnforcementPolicy::kDarkGreyAndBlackList && EnforcementPolicy::kDarkGreyAndBlackList < EnforcementPolicy::kBlacklistOnly, "EnforcementPolicy values ordering not correct"); @@ -111,62 +117,104 @@ void MemberSignature::WarnAboutAccess(AccessMethod access_method, } template<typename T> -bool ShouldBlockAccessToMemberImpl(T* member, Action action, AccessMethod access_method) { +Action GetMemberActionImpl(T* member, Action action, AccessMethod access_method) { + DCHECK_NE(action, kAllow); + // Get the signature, we need it later. MemberSignature member_signature(member); Runtime* runtime = Runtime::Current(); - if (action == kDeny) { - // If we were about to deny, check for an exemption first. - // Exempted APIs are treated as light grey list. + // Check for an exemption first. Exempted APIs are treated as white list. + // We only do this if we're about to deny, or if the app is debuggable. This is because: + // - we only print a warning for light greylist violations for debuggable apps + // - for non-debuggable apps, there is no distinction between light grey & whitelisted APIs. + // - we want to avoid the overhead of checking for exemptions for light greylisted APIs whenever + // possible. + if (action == kDeny || runtime->IsJavaDebuggable()) { if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) { - action = kAllowButWarn; + action = kAllow; // Avoid re-examining the exemption list next time. - // Note this results in the warning below showing "light greylist", which - // seems like what one would expect. Exemptions effectively add new members to - // the light greylist. + // Note this results in no warning for the member, which seems like what one would expect. + // Exemptions effectively adds new members to the whitelist. member->SetAccessFlags(HiddenApiAccessFlags::EncodeForRuntime( - member->GetAccessFlags(), HiddenApiAccessFlags::kLightGreylist)); + member->GetAccessFlags(), HiddenApiAccessFlags::kWhitelist)); + return kAllow; } - } - // Print a log message with information about this class member access. - // We do this regardless of whether we block the access or not. - member_signature.WarnAboutAccess(access_method, - HiddenApiAccessFlags::DecodeFromRuntime(member->GetAccessFlags())); + if (access_method != kNone) { + // Print a log message with information about this class member access. + // We do this if we're about to block access, or the app is debuggable. + member_signature.WarnAboutAccess(access_method, + HiddenApiAccessFlags::DecodeFromRuntime(member->GetAccessFlags())); + } + } if (action == kDeny) { // Block access - return true; + return action; } // Allow access to this member but print a warning. DCHECK(action == kAllowButWarn || action == kAllowButWarnAndToast); - // Depending on a runtime flag, we might move the member into whitelist and - // skip the warning the next time the member is accessed. - if (runtime->ShouldDedupeHiddenApiWarnings()) { - member->SetAccessFlags(HiddenApiAccessFlags::EncodeForRuntime( - member->GetAccessFlags(), HiddenApiAccessFlags::kWhitelist)); - } + if (access_method != kNone) { + // Depending on a runtime flag, we might move the member into whitelist and + // skip the warning the next time the member is accessed. + if (runtime->ShouldDedupeHiddenApiWarnings()) { + member->SetAccessFlags(HiddenApiAccessFlags::EncodeForRuntime( + member->GetAccessFlags(), HiddenApiAccessFlags::kWhitelist)); + } - // If this action requires a UI warning, set the appropriate flag. - if (action == kAllowButWarnAndToast || runtime->ShouldAlwaysSetHiddenApiWarningFlag()) { - runtime->SetPendingHiddenApiWarning(true); + // If this action requires a UI warning, set the appropriate flag. + if (action == kAllowButWarnAndToast || runtime->ShouldAlwaysSetHiddenApiWarningFlag()) { + runtime->SetPendingHiddenApiWarning(true); + } } - return false; + return action; } // Need to instantiate this. -template bool ShouldBlockAccessToMemberImpl<ArtField>(ArtField* member, - Action action, - AccessMethod access_method); -template bool ShouldBlockAccessToMemberImpl<ArtMethod>(ArtMethod* member, - Action action, - AccessMethod access_method); - +template Action GetMemberActionImpl<ArtField>(ArtField* member, + Action action, + AccessMethod access_method); +template Action GetMemberActionImpl<ArtMethod>(ArtMethod* member, + Action action, + AccessMethod access_method); } // namespace detail + +template<typename T> +void NotifyHiddenApiListener(T* member) { + Runtime* runtime = Runtime::Current(); + if (!runtime->IsAotCompiler()) { + ScopedObjectAccessUnchecked soa(Thread::Current()); + + ScopedLocalRef<jobject> consumer_object(soa.Env(), + soa.Env()->GetStaticObjectField( + WellKnownClasses::dalvik_system_VMRuntime, + WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer)); + // If the consumer is non-null, we call back to it to let it know that we + // have encountered an API that's in one of our lists. + if (consumer_object != nullptr) { + detail::MemberSignature member_signature(member); + std::ostringstream member_signature_str; + member_signature.Dump(member_signature_str); + + ScopedLocalRef<jobject> signature_str( + soa.Env(), + soa.Env()->NewStringUTF(member_signature_str.str().c_str())); + + // Call through to Consumer.accept(String memberSignature); + soa.Env()->CallVoidMethod(consumer_object.get(), + WellKnownClasses::java_util_function_Consumer_accept, + signature_str.get()); + } + } +} + +template void NotifyHiddenApiListener<ArtMethod>(ArtMethod* member); +template void NotifyHiddenApiListener<ArtField>(ArtField* member); + } // namespace hiddenapi } // namespace art diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h index cc6c146f00..d2c71a7d35 100644 --- a/runtime/hidden_api.h +++ b/runtime/hidden_api.h @@ -33,7 +33,7 @@ namespace hiddenapi { // frameworks/base/core/java/android/content/pm/ApplicationInfo.java enum class EnforcementPolicy { kNoChecks = 0, - kAllLists = 1, // ban anything but whitelist + kJustWarn = 1, // keep checks enabled, but allow everything (enables logging) kDarkGreyAndBlackList = 2, // ban dark grey & blacklist kBlacklistOnly = 3, // ban blacklist violations only kMax = kBlacklistOnly, @@ -53,12 +53,13 @@ enum Action { }; enum AccessMethod { + kNone, // internal test that does not correspond to an actual access by app kReflection, kJNI, kLinking, }; -inline Action GetMemberAction(uint32_t access_flags) { +inline Action GetActionFromAccessFlags(uint32_t access_flags) { EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy(); if (policy == EnforcementPolicy::kNoChecks) { // Exit early. Nothing to enforce. @@ -69,6 +70,11 @@ inline Action GetMemberAction(uint32_t access_flags) { if (api_list == HiddenApiAccessFlags::kWhitelist) { return kAllow; } + // if policy is "just warn", always warn. We returned above for whitelist APIs. + if (policy == EnforcementPolicy::kJustWarn) { + return kAllowButWarn; + } + DCHECK(policy >= EnforcementPolicy::kDarkGreyAndBlackList); // The logic below relies on equality of values in the enums EnforcementPolicy and // HiddenApiAccessFlags::ApiList, and their ordering. Assertions are in hidden_api.cc. if (static_cast<int>(policy) > static_cast<int>(api_list)) { @@ -108,9 +114,7 @@ class MemberSignature { }; template<typename T> -bool ShouldBlockAccessToMemberImpl(T* member, - Action action, - AccessMethod access_method) +Action GetMemberActionImpl(T* member, Action action, AccessMethod access_method) REQUIRES_SHARED(Locks::mutator_lock_); // Returns true if the caller is either loaded by the boot strap class loader or comes from @@ -138,28 +142,28 @@ inline bool IsCallerInPlatformDex(ObjPtr<mirror::ClassLoader> caller_class_loade // return true if the caller is located in the platform. // This function might print warnings into the log if the member is hidden. template<typename T> -inline bool ShouldBlockAccessToMember(T* member, - Thread* self, - std::function<bool(Thread*)> fn_caller_in_platform, - AccessMethod access_method) +inline Action GetMemberAction(T* member, + Thread* self, + std::function<bool(Thread*)> fn_caller_in_platform, + AccessMethod access_method) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(member != nullptr); - Action action = GetMemberAction(member->GetAccessFlags()); + Action action = GetActionFromAccessFlags(member->GetAccessFlags()); if (action == kAllow) { // Nothing to do. - return false; + return action; } // Member is hidden. Invoke `fn_caller_in_platform` and find the origin of the access. // This can be *very* expensive. Save it for last. if (fn_caller_in_platform(self)) { // Caller in the platform. Exit. - return false; + return kAllow; } // Member is hidden and caller is not in the platform. - return detail::ShouldBlockAccessToMemberImpl(member, action, access_method); + return detail::GetMemberActionImpl(member, action, access_method); } inline bool IsCallerInPlatformDex(ObjPtr<mirror::Class> caller) @@ -172,18 +176,26 @@ inline bool IsCallerInPlatformDex(ObjPtr<mirror::Class> caller) // `caller_class_loader`. // This function might print warnings into the log if the member is hidden. template<typename T> -inline bool ShouldBlockAccessToMember(T* member, - ObjPtr<mirror::ClassLoader> caller_class_loader, - ObjPtr<mirror::DexCache> caller_dex_cache, - AccessMethod access_method) +inline Action GetMemberAction(T* member, + ObjPtr<mirror::ClassLoader> caller_class_loader, + ObjPtr<mirror::DexCache> caller_dex_cache, + AccessMethod access_method) REQUIRES_SHARED(Locks::mutator_lock_) { bool caller_in_platform = detail::IsCallerInPlatformDex(caller_class_loader, caller_dex_cache); - return ShouldBlockAccessToMember(member, - /* thread */ nullptr, - [caller_in_platform] (Thread*) { return caller_in_platform; }, - access_method); + return GetMemberAction(member, + /* thread */ nullptr, + [caller_in_platform] (Thread*) { return caller_in_platform; }, + access_method); } +// Calls back into managed code to notify VMRuntime.nonSdkApiUsageConsumer that +// |member| was accessed. This is usually called when an API is on the black, +// dark grey or light grey lists. Given that the callback can execute arbitrary +// code, a call to this method can result in thread suspension. +template<typename T> void NotifyHiddenApiListener(T* member) + REQUIRES_SHARED(Locks::mutator_lock_); + + } // namespace hiddenapi } // namespace art diff --git a/runtime/hidden_api_test.cc b/runtime/hidden_api_test.cc index 5a31dd4972..65d6363bfd 100644 --- a/runtime/hidden_api_test.cc +++ b/runtime/hidden_api_test.cc @@ -22,6 +22,7 @@ namespace art { using hiddenapi::detail::MemberSignature; +using hiddenapi::GetActionFromAccessFlags; class HiddenApiTest : public CommonRuntimeTest { protected: @@ -84,6 +85,39 @@ class HiddenApiTest : public CommonRuntimeTest { ArtMethod* class3_method1_i_; }; +TEST_F(HiddenApiTest, CheckGetActionFromRuntimeFlags) { + uint32_t whitelist = HiddenApiAccessFlags::EncodeForRuntime(0, HiddenApiAccessFlags::kWhitelist); + uint32_t lightgreylist = + HiddenApiAccessFlags::EncodeForRuntime(0, HiddenApiAccessFlags::kLightGreylist); + uint32_t darkgreylist = + HiddenApiAccessFlags::EncodeForRuntime(0, HiddenApiAccessFlags::kDarkGreylist); + uint32_t blacklist = HiddenApiAccessFlags::EncodeForRuntime(0, HiddenApiAccessFlags::kBlacklist); + + runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kNoChecks); + ASSERT_EQ(GetActionFromAccessFlags(whitelist), hiddenapi::kAllow); + ASSERT_EQ(GetActionFromAccessFlags(lightgreylist), hiddenapi::kAllow); + ASSERT_EQ(GetActionFromAccessFlags(darkgreylist), hiddenapi::kAllow); + ASSERT_EQ(GetActionFromAccessFlags(blacklist), hiddenapi::kAllow); + + runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kJustWarn); + ASSERT_EQ(GetActionFromAccessFlags(whitelist), hiddenapi::kAllow); + ASSERT_EQ(GetActionFromAccessFlags(lightgreylist), hiddenapi::kAllowButWarn); + ASSERT_EQ(GetActionFromAccessFlags(darkgreylist), hiddenapi::kAllowButWarn); + ASSERT_EQ(GetActionFromAccessFlags(blacklist), hiddenapi::kAllowButWarn); + + runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kDarkGreyAndBlackList); + ASSERT_EQ(GetActionFromAccessFlags(whitelist), hiddenapi::kAllow); + ASSERT_EQ(GetActionFromAccessFlags(lightgreylist), hiddenapi::kAllowButWarn); + ASSERT_EQ(GetActionFromAccessFlags(darkgreylist), hiddenapi::kDeny); + ASSERT_EQ(GetActionFromAccessFlags(blacklist), hiddenapi::kDeny); + + runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kBlacklistOnly); + ASSERT_EQ(GetActionFromAccessFlags(whitelist), hiddenapi::kAllow); + ASSERT_EQ(GetActionFromAccessFlags(lightgreylist), hiddenapi::kAllowButWarn); + ASSERT_EQ(GetActionFromAccessFlags(darkgreylist), hiddenapi::kAllowButWarnAndToast); + ASSERT_EQ(GetActionFromAccessFlags(blacklist), hiddenapi::kDeny); +} + TEST_F(HiddenApiTest, CheckMembersRead) { ASSERT_NE(nullptr, class1_field1_); ASSERT_NE(nullptr, class1_field12_); diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc index 4a2dd3bc3f..6e920363e8 100644 --- a/runtime/interpreter/unstarted_runtime.cc +++ b/runtime/interpreter/unstarted_runtime.cc @@ -181,11 +181,13 @@ static mirror::String* GetClassName(Thread* self, ShadowFrame* shadow_frame, siz template<typename T> static ALWAYS_INLINE bool ShouldBlockAccessToMember(T* member, ShadowFrame* frame) REQUIRES_SHARED(Locks::mutator_lock_) { - return hiddenapi::ShouldBlockAccessToMember( + // All uses in this file are from reflection + constexpr hiddenapi::AccessMethod access_method = hiddenapi::kReflection; + return hiddenapi::GetMemberAction( member, frame->GetMethod()->GetDeclaringClass()->GetClassLoader(), frame->GetMethod()->GetDeclaringClass()->GetDexCache(), - hiddenapi::kReflection); // all uses in this file are from reflection + access_method) == hiddenapi::kDeny; } void UnstartedRuntime::UnstartedClassForNameCommon(Thread* self, diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc index f309581735..9dbcded867 100644 --- a/runtime/jni_internal.cc +++ b/runtime/jni_internal.cc @@ -87,8 +87,13 @@ static bool IsCallerInPlatformDex(Thread* self) REQUIRES_SHARED(Locks::mutator_l template<typename T> ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { - return hiddenapi::ShouldBlockAccessToMember( + hiddenapi::Action action = hiddenapi::GetMemberAction( member, self, IsCallerInPlatformDex, hiddenapi::kJNI); + if (action != hiddenapi::kAllow) { + hiddenapi::NotifyHiddenApiListener(member); + } + + return action == hiddenapi::kDeny; } // Helpers to call instrumentation functions for fields. These take jobjects so we don't need to set diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc index ad05856eaf..a8b203bff2 100644 --- a/runtime/native/java_lang_Class.cc +++ b/runtime/native/java_lang_Class.cc @@ -98,8 +98,13 @@ ALWAYS_INLINE static bool ShouldEnforceHiddenApi(Thread* self) template<typename T> ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { - return hiddenapi::ShouldBlockAccessToMember( + hiddenapi::Action action = hiddenapi::GetMemberAction( member, self, IsCallerInPlatformDex, hiddenapi::kReflection); + if (action != hiddenapi::kAllow) { + hiddenapi::NotifyHiddenApiListener(member); + } + + return action == hiddenapi::kDeny; } // Returns true if a class member should be discoverable with reflection given @@ -113,7 +118,8 @@ ALWAYS_INLINE static bool IsDiscoverable(bool public_only, return false; } - if (enforce_hidden_api && hiddenapi::GetMemberAction(access_flags) == hiddenapi::kDeny) { + if (enforce_hidden_api && + hiddenapi::GetActionFromAccessFlags(access_flags) == hiddenapi::kDeny) { return false; } @@ -433,12 +439,14 @@ static jobject Class_getPublicFieldRecursive(JNIEnv* env, jobject javaThis, jstr return nullptr; } - mirror::Field* field = GetPublicFieldRecursive( - soa.Self(), DecodeClass(soa, javaThis), name_string); - if (field == nullptr || ShouldBlockAccessToMember(field->GetArtField(), soa.Self())) { + StackHandleScope<1> hs(soa.Self()); + Handle<mirror::Field> field = hs.NewHandle(GetPublicFieldRecursive( + soa.Self(), DecodeClass(soa, javaThis), name_string)); + if (field.Get() == nullptr || + ShouldBlockAccessToMember(field->GetArtField(), soa.Self())) { return nullptr; } - return soa.AddLocalReference<jobject>(field); + return soa.AddLocalReference<jobject>(field.Get()); } static jobject Class_getDeclaredField(JNIEnv* env, jobject javaThis, jstring name) { @@ -477,15 +485,17 @@ static jobject Class_getDeclaredConstructorInternal( ScopedFastNativeObjectAccess soa(env); DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize); DCHECK(!Runtime::Current()->IsActiveTransaction()); - ObjPtr<mirror::Constructor> result = + + StackHandleScope<1> hs(soa.Self()); + Handle<mirror::Constructor> result = hs.NewHandle( mirror::Class::GetDeclaredConstructorInternal<kRuntimePointerSize, false>( soa.Self(), DecodeClass(soa, javaThis), - soa.Decode<mirror::ObjectArray<mirror::Class>>(args)); + soa.Decode<mirror::ObjectArray<mirror::Class>>(args))); if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) { return nullptr; } - return soa.AddLocalReference<jobject>(result); + return soa.AddLocalReference<jobject>(result.Get()); } static ALWAYS_INLINE inline bool MethodMatchesConstructor( @@ -535,18 +545,19 @@ static jobjectArray Class_getDeclaredConstructorsInternal( static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis, jstring name, jobjectArray args) { ScopedFastNativeObjectAccess soa(env); + StackHandleScope<1> hs(soa.Self()); DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize); DCHECK(!Runtime::Current()->IsActiveTransaction()); - ObjPtr<mirror::Method> result = + Handle<mirror::Method> result = hs.NewHandle( mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize, false>( soa.Self(), DecodeClass(soa, javaThis), soa.Decode<mirror::String>(name), - soa.Decode<mirror::ObjectArray<mirror::Class>>(args)); + soa.Decode<mirror::ObjectArray<mirror::Class>>(args))); if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) { return nullptr; } - return soa.AddLocalReference<jobject>(result); + return soa.AddLocalReference<jobject>(result.Get()); } static jobjectArray Class_getDeclaredMethodsUnchecked(JNIEnv* env, jobject javaThis, diff --git a/runtime/runtime.h b/runtime/runtime.h index 03f17bc04a..c14593749e 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -1004,7 +1004,8 @@ class Runtime { // Whether access checks on hidden API should be performed. hiddenapi::EnforcementPolicy hidden_api_policy_; - // List of signature prefixes of methods that have been removed from the blacklist + // List of signature prefixes of methods that have been removed from the blacklist, and treated + // as if whitelisted. std::vector<std::string> hidden_api_exemptions_; // Whether the application has used an API which is not restricted but we diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index 0206d2c1e5..dfb98b6083 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -3801,16 +3801,8 @@ ArtMethod* MethodVerifier::ResolveMethodAndCheckAccess( must_fail = true; // Try to find the method also with the other type for better error reporting below // but do not store such bogus lookup result in the DexCache or VerifierDeps. - if (klass->IsInterface()) { - // NB This is normally not really allowed but we want to get any static or private object - // methods for error message purposes. This will never be returned. - // TODO We might want to change the verifier to not require this. - res_method = klass->FindClassMethod(dex_cache_.Get(), dex_method_idx, pointer_size); - } else { - // If there was an interface method with the same signature, - // we would have found it also in the "copied" methods. - DCHECK(klass->FindInterfaceMethod(dex_cache_.Get(), dex_method_idx, pointer_size) == nullptr); - } + res_method = class_linker->FindIncompatibleMethod( + klass, dex_cache_.Get(), class_loader_.Get(), dex_method_idx); } if (res_method == nullptr) { diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc index bf36ccf0fa..742e713774 100644 --- a/runtime/well_known_classes.cc +++ b/runtime/well_known_classes.cc @@ -77,6 +77,7 @@ jclass WellKnownClasses::java_nio_ByteBuffer; jclass WellKnownClasses::java_nio_DirectByteBuffer; jclass WellKnownClasses::java_util_ArrayList; jclass WellKnownClasses::java_util_Collections; +jclass WellKnownClasses::java_util_function_Consumer; jclass WellKnownClasses::libcore_reflect_AnnotationFactory; jclass WellKnownClasses::libcore_reflect_AnnotationMember; jclass WellKnownClasses::libcore_util_EmptyArray; @@ -115,6 +116,7 @@ jmethodID WellKnownClasses::java_lang_Thread_run; jmethodID WellKnownClasses::java_lang_ThreadGroup_add; jmethodID WellKnownClasses::java_lang_ThreadGroup_removeThread; jmethodID WellKnownClasses::java_nio_DirectByteBuffer_init; +jmethodID WellKnownClasses::java_util_function_Consumer_accept; jmethodID WellKnownClasses::libcore_reflect_AnnotationFactory_createAnnotation; jmethodID WellKnownClasses::libcore_reflect_AnnotationMember_init; jmethodID WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_broadcast; @@ -125,6 +127,7 @@ jfieldID WellKnownClasses::dalvik_system_DexFile_fileName; jfieldID WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList; jfieldID WellKnownClasses::dalvik_system_DexPathList_dexElements; jfieldID WellKnownClasses::dalvik_system_DexPathList__Element_dexFile; +jfieldID WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer; jfieldID WellKnownClasses::java_lang_Thread_daemon; jfieldID WellKnownClasses::java_lang_Thread_group; jfieldID WellKnownClasses::java_lang_Thread_lock; @@ -349,6 +352,7 @@ void WellKnownClasses::Init(JNIEnv* env) { java_nio_DirectByteBuffer = CacheClass(env, "java/nio/DirectByteBuffer"); java_util_ArrayList = CacheClass(env, "java/util/ArrayList"); java_util_Collections = CacheClass(env, "java/util/Collections"); + java_util_function_Consumer = CacheClass(env, "java/util/function/Consumer"); libcore_reflect_AnnotationFactory = CacheClass(env, "libcore/reflect/AnnotationFactory"); libcore_reflect_AnnotationMember = CacheClass(env, "libcore/reflect/AnnotationMember"); libcore_util_EmptyArray = CacheClass(env, "libcore/util/EmptyArray"); @@ -379,6 +383,7 @@ void WellKnownClasses::Init(JNIEnv* env) { java_lang_ThreadGroup_add = CacheMethod(env, java_lang_ThreadGroup, false, "add", "(Ljava/lang/Thread;)V"); java_lang_ThreadGroup_removeThread = CacheMethod(env, java_lang_ThreadGroup, false, "threadTerminated", "(Ljava/lang/Thread;)V"); java_nio_DirectByteBuffer_init = CacheMethod(env, java_nio_DirectByteBuffer, false, "<init>", "(JI)V"); + java_util_function_Consumer_accept = CacheMethod(env, java_util_function_Consumer, false, "accept", "(Ljava/lang/Object;)V"); libcore_reflect_AnnotationFactory_createAnnotation = CacheMethod(env, libcore_reflect_AnnotationFactory, true, "createAnnotation", "(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)Ljava/lang/annotation/Annotation;"); libcore_reflect_AnnotationMember_init = CacheMethod(env, libcore_reflect_AnnotationMember, false, "<init>", "(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/reflect/Method;)V"); org_apache_harmony_dalvik_ddmc_DdmServer_broadcast = CacheMethod(env, org_apache_harmony_dalvik_ddmc_DdmServer, true, "broadcast", "(I)V"); @@ -389,6 +394,7 @@ void WellKnownClasses::Init(JNIEnv* env) { dalvik_system_DexFile_fileName = CacheField(env, dalvik_system_DexFile, false, "mFileName", "Ljava/lang/String;"); dalvik_system_DexPathList_dexElements = CacheField(env, dalvik_system_DexPathList, false, "dexElements", "[Ldalvik/system/DexPathList$Element;"); dalvik_system_DexPathList__Element_dexFile = CacheField(env, dalvik_system_DexPathList__Element, false, "dexFile", "Ldalvik/system/DexFile;"); + dalvik_system_VMRuntime_nonSdkApiUsageConsumer = CacheField(env, dalvik_system_VMRuntime, true, "nonSdkApiUsageConsumer", "Ljava/util/function/Consumer;"); java_lang_Thread_daemon = CacheField(env, java_lang_Thread, false, "daemon", "Z"); java_lang_Thread_group = CacheField(env, java_lang_Thread, false, "group", "Ljava/lang/ThreadGroup;"); java_lang_Thread_lock = CacheField(env, java_lang_Thread, false, "lock", "Ljava/lang/Object;"); diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h index d5d7033132..7b1a2943d2 100644 --- a/runtime/well_known_classes.h +++ b/runtime/well_known_classes.h @@ -85,6 +85,7 @@ struct WellKnownClasses { static jclass java_lang_Throwable; static jclass java_util_ArrayList; static jclass java_util_Collections; + static jclass java_util_function_Consumer; static jclass java_nio_ByteBuffer; static jclass java_nio_DirectByteBuffer; static jclass libcore_reflect_AnnotationFactory; @@ -125,6 +126,7 @@ struct WellKnownClasses { static jmethodID java_lang_ThreadGroup_add; static jmethodID java_lang_ThreadGroup_removeThread; static jmethodID java_nio_DirectByteBuffer_init; + static jmethodID java_util_function_Consumer_accept; static jmethodID libcore_reflect_AnnotationFactory_createAnnotation; static jmethodID libcore_reflect_AnnotationMember_init; static jmethodID org_apache_harmony_dalvik_ddmc_DdmServer_broadcast; @@ -135,6 +137,7 @@ struct WellKnownClasses { static jfieldID dalvik_system_DexFile_fileName; static jfieldID dalvik_system_DexPathList_dexElements; static jfieldID dalvik_system_DexPathList__Element_dexFile; + static jfieldID dalvik_system_VMRuntime_nonSdkApiUsageConsumer; static jfieldID java_lang_reflect_Executable_artMethod; static jfieldID java_lang_reflect_Proxy_h; static jfieldID java_lang_Thread_daemon; diff --git a/test/674-hiddenapi/build b/test/674-hiddenapi/build index 9012e8fd13..330a6def29 100644 --- a/test/674-hiddenapi/build +++ b/test/674-hiddenapi/build @@ -16,15 +16,6 @@ set -e -# Special build logic to handle src-ex .java files which have code that only builds on RI. -custom_build_logic() { - [[ -d ignore.src-ex ]] && mv ignore.src-ex src-ex - # src-ex uses code that can only build on RI. - ${JAVAC} -source 1.8 -target 1.8 -sourcepath src-ex -sourcepath src -d classes-ex $(find src-ex -name '*.java') - # remove src-ex so that default-build doesn't try to build it. - [[ -d src-ex ]] && mv src-ex ignore.src-ex -} - # Build the jars twice. First with applying hiddenapi, creating a boot jar, then # a second time without to create a normal jar. We need to do this because we # want to load the jar once as an app module and once as a member of the boot @@ -33,7 +24,6 @@ custom_build_logic() { # class path dex files, so the boot jar loads fine in the latter case. export USE_HIDDENAPI=true -custom_build_logic ./default-build "$@" # Move the jar file into the resource folder to be bundled with the test. @@ -45,5 +35,4 @@ mv ${TEST_NAME}.jar res/boot.jar rm -rf classes* export USE_HIDDENAPI=false -custom_build_logic ./default-build "$@" diff --git a/test/674-hiddenapi/src-ex/ChildClass.java b/test/674-hiddenapi/src-ex/ChildClass.java index 582e907ca3..71c92fba64 100644 --- a/test/674-hiddenapi/src-ex/ChildClass.java +++ b/test/674-hiddenapi/src-ex/ChildClass.java @@ -14,6 +14,7 @@ * limitations under the License. */ +import dalvik.system.VMRuntime; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -21,11 +22,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVisitor; +import java.util.function.Consumer; public class ChildClass { enum PrimitiveType { @@ -129,6 +126,7 @@ public class ChildClass { checkLinking("LinkFieldGet" + suffix, /*takesParameter*/ false, expected); checkLinking("LinkFieldSet" + suffix, /*takesParameter*/ true, expected); checkLinking("LinkMethod" + suffix, /*takesParameter*/ false, expected); + checkLinking("LinkMethodInterface" + suffix, /*takesParameter*/ false, expected); } // Check whether Class.newInstance succeeds. @@ -136,6 +134,47 @@ public class ChildClass { } } + static final class RecordingConsumer implements Consumer<String> { + public String recordedValue = null; + + @Override + public void accept(String value) { + recordedValue = value; + } + } + + private static void checkMemberCallback(Class<?> klass, String name, + boolean isPublic, boolean isField) { + try { + RecordingConsumer consumer = new RecordingConsumer(); + VMRuntime.setNonSdkApiUsageConsumer(consumer); + try { + if (isPublic) { + if (isField) { + klass.getField(name); + } else { + klass.getMethod(name); + } + } else { + if (isField) { + klass.getDeclaredField(name); + } else { + klass.getDeclaredMethod(name); + } + } + } catch (NoSuchFieldException|NoSuchMethodException ignored) { + // We're not concerned whether an exception is thrown or not - we're + // only interested in whether the callback is invoked. + } + + if (consumer.recordedValue == null || !consumer.recordedValue.contains(name)) { + throw new RuntimeException("No callback for member: " + name); + } + } finally { + VMRuntime.setNonSdkApiUsageConsumer(null); + } + } + private static void checkField(Class<?> klass, String name, boolean isStatic, Visibility visibility, Behaviour behaviour) throws Exception { @@ -174,48 +213,52 @@ public class ChildClass { // Finish here if we could not discover the field. - if (!canDiscover) { - return; - } + if (canDiscover) { + // Test that modifiers are unaffected. - // Test that modifiers are unaffected. + if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) { + throwModifiersException(klass, name, true); + } - if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) { - throwModifiersException(klass, name, true); - } + // Test getters and setters when meaningful. - // Test getters and setters when meaningful. + clearWarning(); + if (!Reflection.canGetField(klass, name)) { + throwAccessException(klass, name, true, "Field.getInt()"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, true, "Field.getInt()", setsWarning); + } - clearWarning(); - if (!Reflection.canGetField(klass, name)) { - throwAccessException(klass, name, true, "Field.getInt()"); - } - if (hasPendingWarning() != setsWarning) { - throwWarningException(klass, name, true, "Field.getInt()", setsWarning); - } + clearWarning(); + if (!Reflection.canSetField(klass, name)) { + throwAccessException(klass, name, true, "Field.setInt()"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, true, "Field.setInt()", setsWarning); + } - clearWarning(); - if (!Reflection.canSetField(klass, name)) { - throwAccessException(klass, name, true, "Field.setInt()"); - } - if (hasPendingWarning() != setsWarning) { - throwWarningException(klass, name, true, "Field.setInt()", setsWarning); - } + clearWarning(); + if (!JNI.canGetField(klass, name, isStatic)) { + throwAccessException(klass, name, true, "getIntField"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, true, "getIntField", setsWarning); + } - clearWarning(); - if (!JNI.canGetField(klass, name, isStatic)) { - throwAccessException(klass, name, true, "getIntField"); - } - if (hasPendingWarning() != setsWarning) { - throwWarningException(klass, name, true, "getIntField", setsWarning); + clearWarning(); + if (!JNI.canSetField(klass, name, isStatic)) { + throwAccessException(klass, name, true, "setIntField"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, true, "setIntField", setsWarning); + } } + // Test that callbacks are invoked correctly. clearWarning(); - if (!JNI.canSetField(klass, name, isStatic)) { - throwAccessException(klass, name, true, "setIntField"); - } - if (hasPendingWarning() != setsWarning) { - throwWarningException(klass, name, true, "setIntField", setsWarning); + if (setsWarning || !canDiscover) { + checkMemberCallback(klass, name, isPublic, true /* isField */); } } @@ -257,42 +300,46 @@ public class ChildClass { // Finish here if we could not discover the field. - if (!canDiscover) { - return; - } + if (canDiscover) { + // Test that modifiers are unaffected. - // Test that modifiers are unaffected. + if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) { + throwModifiersException(klass, name, false); + } - if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) { - throwModifiersException(klass, name, false); - } + // Test whether we can invoke the method. This skips non-static interface methods. - // Test whether we can invoke the method. This skips non-static interface methods. + if (!klass.isInterface() || isStatic) { + clearWarning(); + if (!Reflection.canInvokeMethod(klass, name)) { + throwAccessException(klass, name, false, "invoke()"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, false, "invoke()", setsWarning); + } - if (!klass.isInterface() || isStatic) { - clearWarning(); - if (!Reflection.canInvokeMethod(klass, name)) { - throwAccessException(klass, name, false, "invoke()"); - } - if (hasPendingWarning() != setsWarning) { - throwWarningException(klass, name, false, "invoke()", setsWarning); - } + clearWarning(); + if (!JNI.canInvokeMethodA(klass, name, isStatic)) { + throwAccessException(klass, name, false, "CallMethodA"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, false, "CallMethodA()", setsWarning); + } - clearWarning(); - if (!JNI.canInvokeMethodA(klass, name, isStatic)) { - throwAccessException(klass, name, false, "CallMethodA"); - } - if (hasPendingWarning() != setsWarning) { - throwWarningException(klass, name, false, "CallMethodA()", setsWarning); + clearWarning(); + if (!JNI.canInvokeMethodV(klass, name, isStatic)) { + throwAccessException(klass, name, false, "CallMethodV"); + } + if (hasPendingWarning() != setsWarning) { + throwWarningException(klass, name, false, "CallMethodV()", setsWarning); + } } + } - clearWarning(); - if (!JNI.canInvokeMethodV(klass, name, isStatic)) { - throwAccessException(klass, name, false, "CallMethodV"); - } - if (hasPendingWarning() != setsWarning) { - throwWarningException(klass, name, false, "CallMethodV()", setsWarning); - } + // Test that callbacks are invoked correctly. + clearWarning(); + if (setsWarning || !canDiscover) { + checkMemberCallback(klass, name, isPublic, false /* isField */); } } diff --git a/test/674-hiddenapi/src-ex/Linking.java b/test/674-hiddenapi/src-ex/Linking.java index a89b92b2b9..0fa0b1912f 100644 --- a/test/674-hiddenapi/src-ex/Linking.java +++ b/test/674-hiddenapi/src-ex/Linking.java @@ -174,6 +174,32 @@ class LinkMethodBlacklist { } } +// INVOKE INSTANCE INTERFACE METHOD + +class LinkMethodInterfaceWhitelist { + public static int access() { + return DummyClass.getInterfaceInstance().methodPublicWhitelist(); + } +} + +class LinkMethodInterfaceLightGreylist { + public static int access() { + return DummyClass.getInterfaceInstance().methodPublicLightGreylist(); + } +} + +class LinkMethodInterfaceDarkGreylist { + public static int access() { + return DummyClass.getInterfaceInstance().methodPublicDarkGreylist(); + } +} + +class LinkMethodInterfaceBlacklist { + public static int access() { + return DummyClass.getInterfaceInstance().methodPublicBlacklist(); + } +} + // INVOKE STATIC METHOD class LinkMethodStaticWhitelist { @@ -199,3 +225,29 @@ class LinkMethodStaticBlacklist { return ParentClass.methodPublicStaticBlacklist(); } } + +// INVOKE INTERFACE STATIC METHOD + +class LinkMethodInterfaceStaticWhitelist { + public static int access() { + return ParentInterface.methodPublicStaticWhitelist(); + } +} + +class LinkMethodInterfaceStaticLightGreylist { + public static int access() { + return ParentInterface.methodPublicStaticLightGreylist(); + } +} + +class LinkMethodInterfaceStaticDarkGreylist { + public static int access() { + return ParentInterface.methodPublicStaticDarkGreylist(); + } +} + +class LinkMethodInterfaceStaticBlacklist { + public static int access() { + return ParentInterface.methodPublicStaticBlacklist(); + } +} diff --git a/test/674-hiddenapi/src/DummyClass.java b/test/674-hiddenapi/src/DummyClass.java new file mode 100644 index 0000000000..51281a2666 --- /dev/null +++ b/test/674-hiddenapi/src/DummyClass.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class DummyClass implements ParentInterface { + public int methodPublicWhitelist() { return 1; } + public int methodPublicLightGreylist() { return 2; } + public int methodPublicDarkGreylist() { return 3; } + public int methodPublicBlacklist() { return 4; } + + public static ParentInterface getInterfaceInstance() { + return new DummyClass(); + } +} diff --git a/test/674-hiddenapi/src/ParentInterface.java b/test/674-hiddenapi/src/ParentInterface.java index e36fe0e6b2..f79ac9d661 100644 --- a/test/674-hiddenapi/src/ParentInterface.java +++ b/test/674-hiddenapi/src/ParentInterface.java @@ -23,9 +23,9 @@ public interface ParentInterface { // INSTANCE METHOD int methodPublicWhitelist(); - int methodPublicBlacklist(); int methodPublicLightGreylist(); int methodPublicDarkGreylist(); + int methodPublicBlacklist(); // STATIC METHOD static int methodPublicStaticWhitelist() { return 21; } diff --git a/tools/veridex/Android.bp b/tools/veridex/Android.bp index cbf62d9e9c..570960c4da 100644 --- a/tools/veridex/Android.bp +++ b/tools/veridex/Android.bp @@ -16,8 +16,10 @@ cc_binary { name: "veridex", host_supported: true, srcs: [ + "flow_analysis.cc", "hidden_api.cc", "hidden_api_finder.cc", + "precise_hidden_api_finder.cc", "resolver.cc", "veridex.cc", ], diff --git a/tools/veridex/README.md b/tools/veridex/README.md index 0f91b08771..f85a51bb48 100644 --- a/tools/veridex/README.md +++ b/tools/veridex/README.md @@ -11,4 +11,4 @@ To build it: > make appcompat To run it: -> ./art/tools/veridex/appcompat.sh test.apk +> ./art/tools/veridex/appcompat.sh --dex-file=test.apk diff --git a/tools/veridex/appcompat.sh b/tools/veridex/appcompat.sh index f75aa4f0d0..31a8654b58 100755 --- a/tools/veridex/appcompat.sh +++ b/tools/veridex/appcompat.sh @@ -48,4 +48,4 @@ ${ANDROID_HOST_OUT}/bin/veridex \ --blacklist=${PACKAGING}/hiddenapi-blacklist.txt \ --light-greylist=${PACKAGING}/hiddenapi-light-greylist.txt \ --dark-greylist=${PACKAGING}/hiddenapi-dark-greylist.txt \ - --dex-file=$1 + $@ diff --git a/tools/veridex/flow_analysis.cc b/tools/veridex/flow_analysis.cc new file mode 100644 index 0000000000..abd0b9b28c --- /dev/null +++ b/tools/veridex/flow_analysis.cc @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "flow_analysis.h" + +#include "dex/bytecode_utils.h" +#include "dex/code_item_accessors-inl.h" +#include "dex/dex_instruction-inl.h" +#include "dex/dex_file-inl.h" +#include "dex/dex_file_exception_helpers.h" +#include "resolver.h" +#include "veridex.h" + +namespace art { + + +void VeriFlowAnalysis::SetAsBranchTarget(uint32_t dex_pc) { + if (dex_registers_[dex_pc] == nullptr) { + dex_registers_[dex_pc].reset( + new std::vector<RegisterValue>(code_item_accessor_.RegistersSize())); + } +} + +bool VeriFlowAnalysis::IsBranchTarget(uint32_t dex_pc) { + return dex_registers_[dex_pc] != nullptr; +} + +bool VeriFlowAnalysis::MergeRegisterValues(uint32_t dex_pc) { + // TODO: Do the merging. Right now, just return that we should continue + // the iteration if the instruction has not been visited. + return !instruction_infos_[dex_pc].has_been_visited; +} + +void VeriFlowAnalysis::SetVisited(uint32_t dex_pc) { + instruction_infos_[dex_pc].has_been_visited = true; +} + +void VeriFlowAnalysis::FindBranches() { + SetAsBranchTarget(0); + + if (code_item_accessor_.TriesSize() != 0) { + // TODO: We need to mark the range of dex pcs as flowing in the handlers. + /* + for (const DexFile::TryItem& try_item : code_item_accessor_.TryItems()) { + uint32_t dex_pc_start = try_item.start_addr_; + uint32_t dex_pc_end = dex_pc_start + try_item.insn_count_; + } + */ + + // Create branch targets for exception handlers. + const uint8_t* handlers_ptr = code_item_accessor_.GetCatchHandlerData(); + uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_ptr); + for (uint32_t idx = 0; idx < handlers_size; ++idx) { + CatchHandlerIterator iterator(handlers_ptr); + for (; iterator.HasNext(); iterator.Next()) { + SetAsBranchTarget(iterator.GetHandlerAddress()); + } + handlers_ptr = iterator.EndDataPointer(); + } + } + + // Iterate over all instructions and find branching instructions. + for (const DexInstructionPcPair& pair : code_item_accessor_) { + const uint32_t dex_pc = pair.DexPc(); + const Instruction& instruction = pair.Inst(); + + if (instruction.IsBranch()) { + SetAsBranchTarget(dex_pc + instruction.GetTargetOffset()); + } else if (instruction.IsSwitch()) { + DexSwitchTable table(instruction, dex_pc); + for (DexSwitchTableIterator s_it(table); !s_it.Done(); s_it.Advance()) { + SetAsBranchTarget(dex_pc + s_it.CurrentTargetOffset()); + if (table.ShouldBuildDecisionTree() && !s_it.IsLast()) { + SetAsBranchTarget(s_it.GetDexPcForCurrentIndex()); + } + } + } + } +} + +void VeriFlowAnalysis::UpdateRegister(uint32_t dex_register, + RegisterSource kind, + VeriClass* cls, + uint32_t source_id) { + current_registers_[dex_register] = RegisterValue( + kind, DexFileReference(&resolver_->GetDexFile(), source_id), cls); +} + +void VeriFlowAnalysis::UpdateRegister(uint32_t dex_register, const RegisterValue& value) { + current_registers_[dex_register] = value; +} + +void VeriFlowAnalysis::UpdateRegister(uint32_t dex_register, const VeriClass* cls) { + current_registers_[dex_register] = + RegisterValue(RegisterSource::kNone, DexFileReference(nullptr, 0), cls); +} + +const RegisterValue& VeriFlowAnalysis::GetRegister(uint32_t dex_register) { + return current_registers_[dex_register]; +} + +RegisterValue VeriFlowAnalysis::GetReturnType(uint32_t method_index) { + const DexFile& dex_file = resolver_->GetDexFile(); + const DexFile::MethodId& method_id = dex_file.GetMethodId(method_index); + const DexFile::ProtoId& proto_id = dex_file.GetMethodPrototype(method_id); + VeriClass* cls = resolver_->GetVeriClass(proto_id.return_type_idx_); + return RegisterValue(RegisterSource::kMethod, DexFileReference(&dex_file, method_index), cls); +} + +RegisterValue VeriFlowAnalysis::GetFieldType(uint32_t field_index) { + const DexFile& dex_file = resolver_->GetDexFile(); + const DexFile::FieldId& field_id = dex_file.GetFieldId(field_index); + VeriClass* cls = resolver_->GetVeriClass(field_id.type_idx_); + return RegisterValue(RegisterSource::kField, DexFileReference(&dex_file, field_index), cls); +} + +void VeriFlowAnalysis::AnalyzeCode() { + std::vector<uint32_t> work_list; + work_list.push_back(0); + // Iterate over the code. + // When visiting unconditional branches (goto), move to that instruction. + // When visiting conditional branches, move to one destination, and put the other + // in the worklist. + while (!work_list.empty()) { + uint32_t dex_pc = work_list.back(); + work_list.pop_back(); + CHECK(IsBranchTarget(dex_pc)); + current_registers_ = *dex_registers_[dex_pc].get(); + while (true) { + const uint16_t* insns = code_item_accessor_.Insns() + dex_pc; + const Instruction& inst = *Instruction::At(insns); + ProcessDexInstruction(inst); + SetVisited(dex_pc); + + int opcode_flags = Instruction::FlagsOf(inst.Opcode()); + if ((opcode_flags & Instruction::kContinue) != 0) { + if ((opcode_flags & Instruction::kBranch) != 0) { + uint32_t branch_dex_pc = dex_pc + inst.GetTargetOffset(); + if (MergeRegisterValues(branch_dex_pc)) { + work_list.push_back(branch_dex_pc); + } + } + dex_pc += inst.SizeInCodeUnits(); + } else if ((opcode_flags & Instruction::kBranch) != 0) { + dex_pc += inst.GetTargetOffset(); + DCHECK(IsBranchTarget(dex_pc)); + } else { + break; + } + + if (IsBranchTarget(dex_pc)) { + if (MergeRegisterValues(dex_pc)) { + current_registers_ = *dex_registers_[dex_pc].get(); + } else { + break; + } + } + } + } +} + +void VeriFlowAnalysis::ProcessDexInstruction(const Instruction& instruction) { + switch (instruction.Opcode()) { + case Instruction::CONST_4: + case Instruction::CONST_16: + case Instruction::CONST: + case Instruction::CONST_HIGH16: { + int32_t register_index = instruction.VRegA(); + UpdateRegister(register_index, VeriClass::integer_); + break; + } + + case Instruction::CONST_WIDE_16: + case Instruction::CONST_WIDE_32: + case Instruction::CONST_WIDE: + case Instruction::CONST_WIDE_HIGH16: { + int32_t register_index = instruction.VRegA(); + UpdateRegister(register_index, VeriClass::long_); + break; + } + + case Instruction::MOVE: + case Instruction::MOVE_FROM16: + case Instruction::MOVE_16: { + UpdateRegister(instruction.VRegA(), GetRegister(instruction.VRegB())); + break; + } + + case Instruction::MOVE_WIDE: + case Instruction::MOVE_WIDE_FROM16: + case Instruction::MOVE_WIDE_16: { + UpdateRegister(instruction.VRegA(), GetRegister(instruction.VRegB())); + break; + } + + case Instruction::MOVE_OBJECT: + case Instruction::MOVE_OBJECT_16: + case Instruction::MOVE_OBJECT_FROM16: { + UpdateRegister(instruction.VRegA(), GetRegister(instruction.VRegB())); + break; + } + case Instruction::CONST_CLASS: { + UpdateRegister(instruction.VRegA_21c(), + RegisterSource::kClass, + VeriClass::class_, + instruction.VRegB_21c()); + break; + } + case Instruction::CONST_STRING: { + UpdateRegister(instruction.VRegA_21c(), + RegisterSource::kString, + VeriClass::string_, + instruction.VRegB_21c()); + break; + } + + case Instruction::CONST_STRING_JUMBO: { + UpdateRegister(instruction.VRegA_31c(), + RegisterSource::kString, + VeriClass::string_, + instruction.VRegB_31c()); + break; + } + case Instruction::INVOKE_DIRECT: + case Instruction::INVOKE_INTERFACE: + case Instruction::INVOKE_STATIC: + case Instruction::INVOKE_SUPER: + case Instruction::INVOKE_VIRTUAL: { + VeriMethod method = resolver_->GetMethod(instruction.VRegB_35c()); + uint32_t args[5]; + instruction.GetVarArgs(args); + if (method == VeriClass::forName_) { + RegisterValue value = GetRegister(args[0]); + last_result_ = RegisterValue( + value.GetSource(), value.GetDexFileReference(), VeriClass::class_); + } else if (IsGetField(method)) { + RegisterValue cls = GetRegister(args[0]); + RegisterValue name = GetRegister(args[1]); + field_uses_.push_back(std::make_pair(cls, name)); + last_result_ = GetReturnType(instruction.VRegB_35c()); + } else if (IsGetMethod(method)) { + RegisterValue cls = GetRegister(args[0]); + RegisterValue name = GetRegister(args[1]); + method_uses_.push_back(std::make_pair(cls, name)); + last_result_ = GetReturnType(instruction.VRegB_35c()); + } else if (method == VeriClass::getClass_) { + RegisterValue obj = GetRegister(args[0]); + last_result_ = RegisterValue( + obj.GetSource(), obj.GetDexFileReference(), VeriClass::class_); + } else { + last_result_ = GetReturnType(instruction.VRegB_35c()); + } + break; + } + + case Instruction::INVOKE_DIRECT_RANGE: + case Instruction::INVOKE_INTERFACE_RANGE: + case Instruction::INVOKE_STATIC_RANGE: + case Instruction::INVOKE_SUPER_RANGE: + case Instruction::INVOKE_VIRTUAL_RANGE: { + last_result_ = GetReturnType(instruction.VRegB_3rc()); + break; + } + + case Instruction::MOVE_RESULT: + case Instruction::MOVE_RESULT_WIDE: + case Instruction::MOVE_RESULT_OBJECT: { + UpdateRegister(instruction.VRegA(), last_result_); + break; + } + case Instruction::RETURN_VOID: + case Instruction::RETURN_OBJECT: + case Instruction::RETURN_WIDE: + case Instruction::RETURN: { + break; + } + #define IF_XX(cond) \ + case Instruction::IF_##cond: break; \ + case Instruction::IF_##cond##Z: break + + IF_XX(EQ); + IF_XX(NE); + IF_XX(LT); + IF_XX(LE); + IF_XX(GT); + IF_XX(GE); + + case Instruction::GOTO: + case Instruction::GOTO_16: + case Instruction::GOTO_32: { + break; + } + case Instruction::INVOKE_POLYMORPHIC: { + // TODO + break; + } + + case Instruction::INVOKE_POLYMORPHIC_RANGE: { + // TODO + break; + } + + case Instruction::NEG_INT: + case Instruction::NEG_LONG: + case Instruction::NEG_FLOAT: + case Instruction::NEG_DOUBLE: + case Instruction::NOT_INT: + case Instruction::NOT_LONG: { + UpdateRegister(instruction.VRegA(), VeriClass::integer_); + break; + } + + case Instruction::INT_TO_LONG: + case Instruction::INT_TO_FLOAT: + case Instruction::INT_TO_DOUBLE: + case Instruction::LONG_TO_INT: + case Instruction::LONG_TO_FLOAT: + case Instruction::LONG_TO_DOUBLE: + case Instruction::FLOAT_TO_INT: + case Instruction::FLOAT_TO_LONG: + case Instruction::FLOAT_TO_DOUBLE: + case Instruction::DOUBLE_TO_INT: + case Instruction::DOUBLE_TO_LONG: + case Instruction::DOUBLE_TO_FLOAT: + case Instruction::INT_TO_BYTE: + case Instruction::INT_TO_SHORT: + case Instruction::INT_TO_CHAR: { + UpdateRegister(instruction.VRegA(), VeriClass::integer_); + break; + } + + case Instruction::ADD_INT: + case Instruction::ADD_LONG: + case Instruction::ADD_DOUBLE: + case Instruction::ADD_FLOAT: + case Instruction::SUB_INT: + case Instruction::SUB_LONG: + case Instruction::SUB_FLOAT: + case Instruction::SUB_DOUBLE: + case Instruction::MUL_INT: + case Instruction::MUL_LONG: + case Instruction::MUL_FLOAT: + case Instruction::MUL_DOUBLE: + case Instruction::DIV_INT: + case Instruction::DIV_LONG: + case Instruction::DIV_FLOAT: + case Instruction::DIV_DOUBLE: + case Instruction::REM_INT: + case Instruction::REM_LONG: + case Instruction::REM_FLOAT: + case Instruction::REM_DOUBLE: + case Instruction::AND_INT: + case Instruction::AND_LONG: + case Instruction::SHL_INT: + case Instruction::SHL_LONG: + case Instruction::SHR_INT: + case Instruction::SHR_LONG: + case Instruction::USHR_INT: + case Instruction::USHR_LONG: + case Instruction::OR_INT: + case Instruction::OR_LONG: + case Instruction::XOR_INT: + case Instruction::XOR_LONG: { + UpdateRegister(instruction.VRegA(), VeriClass::integer_); + break; + } + + case Instruction::ADD_INT_2ADDR: + case Instruction::ADD_LONG_2ADDR: + case Instruction::ADD_DOUBLE_2ADDR: + case Instruction::ADD_FLOAT_2ADDR: + case Instruction::SUB_INT_2ADDR: + case Instruction::SUB_LONG_2ADDR: + case Instruction::SUB_FLOAT_2ADDR: + case Instruction::SUB_DOUBLE_2ADDR: + case Instruction::MUL_INT_2ADDR: + case Instruction::MUL_LONG_2ADDR: + case Instruction::MUL_FLOAT_2ADDR: + case Instruction::MUL_DOUBLE_2ADDR: + case Instruction::DIV_INT_2ADDR: + case Instruction::DIV_LONG_2ADDR: + case Instruction::REM_INT_2ADDR: + case Instruction::REM_LONG_2ADDR: + case Instruction::REM_FLOAT_2ADDR: + case Instruction::REM_DOUBLE_2ADDR: + case Instruction::SHL_INT_2ADDR: + case Instruction::SHL_LONG_2ADDR: + case Instruction::SHR_INT_2ADDR: + case Instruction::SHR_LONG_2ADDR: + case Instruction::USHR_INT_2ADDR: + case Instruction::USHR_LONG_2ADDR: + case Instruction::DIV_FLOAT_2ADDR: + case Instruction::DIV_DOUBLE_2ADDR: + case Instruction::AND_INT_2ADDR: + case Instruction::AND_LONG_2ADDR: + case Instruction::OR_INT_2ADDR: + case Instruction::OR_LONG_2ADDR: + case Instruction::XOR_INT_2ADDR: + case Instruction::XOR_LONG_2ADDR: { + UpdateRegister(instruction.VRegA(), VeriClass::integer_); + break; + } + + case Instruction::ADD_INT_LIT16: + case Instruction::AND_INT_LIT16: + case Instruction::OR_INT_LIT16: + case Instruction::XOR_INT_LIT16: + case Instruction::RSUB_INT: + case Instruction::MUL_INT_LIT16: + case Instruction::DIV_INT_LIT16: + case Instruction::REM_INT_LIT16: { + UpdateRegister(instruction.VRegA(), VeriClass::integer_); + break; + } + + case Instruction::ADD_INT_LIT8: + case Instruction::AND_INT_LIT8: + case Instruction::OR_INT_LIT8: + case Instruction::XOR_INT_LIT8: + case Instruction::RSUB_INT_LIT8: + case Instruction::MUL_INT_LIT8: + case Instruction::DIV_INT_LIT8: + case Instruction::REM_INT_LIT8: + case Instruction::SHL_INT_LIT8: + case Instruction::SHR_INT_LIT8: { + case Instruction::USHR_INT_LIT8: { + UpdateRegister(instruction.VRegA(), VeriClass::integer_); + break; + } + + case Instruction::NEW_INSTANCE: { + VeriClass* cls = resolver_->GetVeriClass(dex::TypeIndex(instruction.VRegB_21c())); + UpdateRegister(instruction.VRegA(), cls); + break; + } + + case Instruction::NEW_ARRAY: { + dex::TypeIndex type_index(instruction.VRegC_22c()); + VeriClass* cls = resolver_->GetVeriClass(type_index); + UpdateRegister(instruction.VRegA_22c(), cls); + break; + } + + case Instruction::FILLED_NEW_ARRAY: { + dex::TypeIndex type_index(instruction.VRegB_35c()); + VeriClass* cls = resolver_->GetVeriClass(type_index); + UpdateRegister(instruction.VRegA_22c(), cls); + break; + } + + case Instruction::FILLED_NEW_ARRAY_RANGE: { + dex::TypeIndex type_index(instruction.VRegB_3rc()); + uint32_t register_index = instruction.VRegC_3rc(); + VeriClass* cls = resolver_->GetVeriClass(type_index); + UpdateRegister(register_index, cls); + break; + } + + case Instruction::FILL_ARRAY_DATA: { + break; + } + + case Instruction::CMP_LONG: + case Instruction::CMPG_FLOAT: + case Instruction::CMPG_DOUBLE: + case Instruction::CMPL_FLOAT: + case Instruction::CMPL_DOUBLE: + UpdateRegister(instruction.VRegA(), VeriClass::integer_); + break; + } + + case Instruction::NOP: + break; + + case Instruction::IGET: + case Instruction::IGET_WIDE: + case Instruction::IGET_OBJECT: + case Instruction::IGET_BOOLEAN: + case Instruction::IGET_BYTE: + case Instruction::IGET_CHAR: + case Instruction::IGET_SHORT: { + UpdateRegister(instruction.VRegA_22c(), GetFieldType(instruction.VRegC_22c())); + break; + } + + case Instruction::IPUT: + case Instruction::IPUT_WIDE: + case Instruction::IPUT_OBJECT: + case Instruction::IPUT_BOOLEAN: + case Instruction::IPUT_BYTE: + case Instruction::IPUT_CHAR: + case Instruction::IPUT_SHORT: { + break; + } + + case Instruction::SGET: + case Instruction::SGET_WIDE: + case Instruction::SGET_OBJECT: + case Instruction::SGET_BOOLEAN: + case Instruction::SGET_BYTE: + case Instruction::SGET_CHAR: + case Instruction::SGET_SHORT: { + UpdateRegister(instruction.VRegA_22c(), GetFieldType(instruction.VRegC_22c())); + break; + } + + case Instruction::SPUT: + case Instruction::SPUT_WIDE: + case Instruction::SPUT_OBJECT: + case Instruction::SPUT_BOOLEAN: + case Instruction::SPUT_BYTE: + case Instruction::SPUT_CHAR: + case Instruction::SPUT_SHORT: { + break; + } + +#define ARRAY_XX(kind, anticipated_type) \ + case Instruction::AGET##kind: { \ + UpdateRegister(instruction.VRegA_23x(), anticipated_type); \ + break; \ + } \ + case Instruction::APUT##kind: { \ + break; \ + } + + ARRAY_XX(, VeriClass::integer_); + ARRAY_XX(_WIDE, VeriClass::long_); + ARRAY_XX(_BOOLEAN, VeriClass::boolean_); + ARRAY_XX(_BYTE, VeriClass::byte_); + ARRAY_XX(_CHAR, VeriClass::char_); + ARRAY_XX(_SHORT, VeriClass::short_); + + case Instruction::AGET_OBJECT: { + // TODO: take the component type. + UpdateRegister(instruction.VRegA_23x(), VeriClass::object_); + break; + } + + case Instruction::APUT_OBJECT: { + break; + } + + case Instruction::ARRAY_LENGTH: { + UpdateRegister(instruction.VRegA_12x(), VeriClass::integer_); + break; + } + + case Instruction::MOVE_EXCEPTION: { + UpdateRegister(instruction.VRegA_11x(), VeriClass::throwable_); + break; + } + + case Instruction::THROW: { + break; + } + + case Instruction::INSTANCE_OF: { + uint8_t destination = instruction.VRegA_22c(); + UpdateRegister(destination, VeriClass::boolean_); + break; + } + + case Instruction::CHECK_CAST: { + uint8_t reference = instruction.VRegA_21c(); + dex::TypeIndex type_index(instruction.VRegB_21c()); + UpdateRegister(reference, resolver_->GetVeriClass(type_index)); + break; + } + + case Instruction::MONITOR_ENTER: + case Instruction::MONITOR_EXIT: { + break; + } + + case Instruction::SPARSE_SWITCH: + case Instruction::PACKED_SWITCH: + break; + + default: + break; + } +} + +void VeriFlowAnalysis::Run() { + FindBranches(); + AnalyzeCode(); +} + +} // namespace art diff --git a/tools/veridex/flow_analysis.h b/tools/veridex/flow_analysis.h new file mode 100644 index 0000000000..c065fb8c24 --- /dev/null +++ b/tools/veridex/flow_analysis.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_TOOLS_VERIDEX_FLOW_ANALYSIS_H_ +#define ART_TOOLS_VERIDEX_FLOW_ANALYSIS_H_ + +#include "dex/code_item_accessors.h" +#include "dex/dex_file_reference.h" +#include "dex/method_reference.h" +#include "veridex.h" + +namespace art { + +class VeridexClass; +class VeridexResolver; + +/** + * The source where a dex register comes from. + */ +enum class RegisterSource { + kParameter, + kField, + kMethod, + kClass, + kString, + kNone +}; + +/** + * Abstract representation of a dex register value. + */ +class RegisterValue { + public: + RegisterValue() : source_(RegisterSource::kNone), reference_(nullptr, 0), type_(nullptr) {} + RegisterValue(RegisterSource source, DexFileReference reference, const VeriClass* type) + : source_(source), reference_(reference), type_(type) {} + + RegisterSource GetSource() const { return source_; } + DexFileReference GetDexFileReference() const { return reference_; } + const VeriClass* GetType() const { return type_; } + + const char* ToString() const { + switch (source_) { + case RegisterSource::kString: + return reference_.dex_file->StringDataByIdx(dex::StringIndex(reference_.index)); + case RegisterSource::kClass: + return reference_.dex_file->StringByTypeIdx(dex::TypeIndex(reference_.index)); + default: + return "<unknown>"; + } + } + + private: + RegisterSource source_; + DexFileReference reference_; + const VeriClass* type_; +}; + +struct InstructionInfo { + bool has_been_visited; +}; + +class VeriFlowAnalysis { + public: + VeriFlowAnalysis(VeridexResolver* resolver, + const CodeItemDataAccessor& code_item_accessor) + : resolver_(resolver), + code_item_accessor_(code_item_accessor), + dex_registers_(code_item_accessor.InsnsSizeInCodeUnits()), + instruction_infos_(code_item_accessor.InsnsSizeInCodeUnits()) {} + + void Run(); + + const std::vector<std::pair<RegisterValue, RegisterValue>>& GetFieldUses() const { + return field_uses_; + } + + const std::vector<std::pair<RegisterValue, RegisterValue>>& GetMethodUses() const { + return method_uses_; + } + + private: + // Find all branches in the code. + void FindBranches(); + + // Analyze all non-deead instructions in the code. + void AnalyzeCode(); + + // Set the instruction at the given pc as a branch target. + void SetAsBranchTarget(uint32_t dex_pc); + + // Whether the instruction at the given pc is a branch target. + bool IsBranchTarget(uint32_t dex_pc); + + // Merge the register values at the given pc with `current_registers`. + // Return whether the register values have changed, and the instruction needs + // to be visited again. + bool MergeRegisterValues(uint32_t dex_pc); + + void UpdateRegister( + uint32_t dex_register, RegisterSource kind, VeriClass* cls, uint32_t source_id); + void UpdateRegister(uint32_t dex_register, const RegisterValue& value); + void UpdateRegister(uint32_t dex_register, const VeriClass* cls); + const RegisterValue& GetRegister(uint32_t dex_register); + void ProcessDexInstruction(const Instruction& inst); + void SetVisited(uint32_t dex_pc); + RegisterValue GetReturnType(uint32_t method_index); + RegisterValue GetFieldType(uint32_t field_index); + + VeridexResolver* resolver_; + const CodeItemDataAccessor& code_item_accessor_; + + // Vector of register values for all branch targets. + std::vector<std::unique_ptr<std::vector<RegisterValue>>> dex_registers_; + + // The current values of dex registers. + std::vector<RegisterValue> current_registers_; + + // Information on each instruction useful for the analysis. + std::vector<InstructionInfo> instruction_infos_; + + // The value of invoke instructions, to be fetched when visiting move-result. + RegisterValue last_result_; + + // List of reflection field uses found. + std::vector<std::pair<RegisterValue, RegisterValue>> field_uses_; + + // List of reflection method uses found. + std::vector<std::pair<RegisterValue, RegisterValue>> method_uses_; +}; + +} // namespace art + +#endif // ART_TOOLS_VERIDEX_FLOW_ANALYSIS_H_ diff --git a/tools/veridex/hidden_api.h b/tools/veridex/hidden_api.h index 5893b8ae33..4c67768a00 100644 --- a/tools/veridex/hidden_api.h +++ b/tools/veridex/hidden_api.h @@ -18,6 +18,7 @@ #define ART_TOOLS_VERIDEX_HIDDEN_API_H_ #include "dex/hidden_api_access_flags.h" +#include "dex/method_reference.h" #include <ostream> #include <set> @@ -58,6 +59,10 @@ class HiddenApi { static std::string GetApiFieldName(const DexFile& dex_file, uint32_t field_index); + static std::string GetApiMethodName(MethodReference ref) { + return HiddenApi::GetApiMethodName(*ref.dex_file, ref.index); + } + private: static bool IsInList(const std::string& name, const std::set<std::string>& list) { return list.find(name) != list.end(); @@ -70,6 +75,13 @@ class HiddenApi { std::set<std::string> dark_greylist_; }; +struct HiddenApiStats { + uint32_t count = 0; + uint32_t reflection_count = 0; + uint32_t linking_count = 0; + uint32_t api_counts[4] = { 0, 0, 0, 0 }; +}; + } // namespace art #endif // ART_TOOLS_VERIDEX_HIDDEN_API_H_ diff --git a/tools/veridex/hidden_api_finder.cc b/tools/veridex/hidden_api_finder.cc index 4885e02769..b9be618ed7 100644 --- a/tools/veridex/hidden_api_finder.cc +++ b/tools/veridex/hidden_api_finder.cc @@ -193,32 +193,26 @@ void HiddenApiFinder::CollectAccesses(VeridexResolver* resolver) { } } -static std::string GetApiMethodName(MethodReference ref) { - return HiddenApi::GetApiMethodName(*ref.dex_file, ref.index); -} - void HiddenApiFinder::Run(const std::vector<std::unique_ptr<VeridexResolver>>& resolvers) { for (const std::unique_ptr<VeridexResolver>& resolver : resolvers) { CollectAccesses(resolver.get()); } - - Dump(std::cout); } -void HiddenApiFinder::Dump(std::ostream& os) { +void HiddenApiFinder::Dump(std::ostream& os, + HiddenApiStats* stats, + bool dump_reflection) { static const char* kPrefix = " "; - uint32_t count = 0; - uint32_t linking_count = method_locations_.size() + field_locations_.size(); - uint32_t api_counts[4] = {0, 0, 0, 0}; + stats->linking_count = method_locations_.size() + field_locations_.size(); // Dump methods from hidden APIs linked against. for (const std::pair<std::string, std::vector<MethodReference>>& pair : method_locations_) { HiddenApiAccessFlags::ApiList api_list = hidden_api_.GetApiList(pair.first); - api_counts[api_list]++; - os << "#" << ++count << ": Linking " << api_list << " " << pair.first << " use(s):"; + stats->api_counts[api_list]++; + os << "#" << ++stats->count << ": Linking " << api_list << " " << pair.first << " use(s):"; os << std::endl; for (const MethodReference& ref : pair.second) { - os << kPrefix << GetApiMethodName(ref) << std::endl; + os << kPrefix << HiddenApi::GetApiMethodName(ref) << std::endl; } os << std::endl; } @@ -226,42 +220,35 @@ void HiddenApiFinder::Dump(std::ostream& os) { // Dump fields from hidden APIs linked against. for (const std::pair<std::string, std::vector<MethodReference>>& pair : field_locations_) { HiddenApiAccessFlags::ApiList api_list = hidden_api_.GetApiList(pair.first); - api_counts[api_list]++; - os << "#" << ++count << ": Linking " << api_list << " " << pair.first << " use(s):"; + stats->api_counts[api_list]++; + os << "#" << ++stats->count << ": Linking " << api_list << " " << pair.first << " use(s):"; os << std::endl; for (const MethodReference& ref : pair.second) { - os << kPrefix << GetApiMethodName(ref) << std::endl; + os << kPrefix << HiddenApi::GetApiMethodName(ref) << std::endl; } os << std::endl; } - // Dump potential reflection uses. - for (const std::string& cls : classes_) { - for (const std::string& name : strings_) { - std::string full_name = cls + "->" + name; - HiddenApiAccessFlags::ApiList api_list = hidden_api_.GetApiList(full_name); - api_counts[api_list]++; - if (api_list != HiddenApiAccessFlags::kWhitelist) { - os << "#" << ++count << ": Reflection " << api_list << " " << full_name - << " potential use(s):"; - os << std::endl; - for (const MethodReference& ref : reflection_locations_[name]) { - os << kPrefix << GetApiMethodName(ref) << std::endl; + if (dump_reflection) { + // Dump potential reflection uses. + for (const std::string& cls : classes_) { + for (const std::string& name : strings_) { + std::string full_name = cls + "->" + name; + HiddenApiAccessFlags::ApiList api_list = hidden_api_.GetApiList(full_name); + stats->api_counts[api_list]++; + if (api_list != HiddenApiAccessFlags::kWhitelist) { + stats->reflection_count++; + os << "#" << ++stats->count << ": Reflection " << api_list << " " << full_name + << " potential use(s):"; + os << std::endl; + for (const MethodReference& ref : reflection_locations_[name]) { + os << kPrefix << HiddenApi::GetApiMethodName(ref) << std::endl; + } + os << std::endl; } - os << std::endl; } } } - - os << count << " hidden API(s) used: " - << linking_count << " linked against, " - << count - linking_count << " potentially through reflection" << std::endl; - os << kPrefix << api_counts[HiddenApiAccessFlags::kBlacklist] - << " in blacklist" << std::endl; - os << kPrefix << api_counts[HiddenApiAccessFlags::kDarkGreylist] - << " in dark greylist" << std::endl; - os << kPrefix << api_counts[HiddenApiAccessFlags::kLightGreylist] - << " in light greylist" << std::endl; } } // namespace art diff --git a/tools/veridex/hidden_api_finder.h b/tools/veridex/hidden_api_finder.h index 243079c187..f7d3dc832d 100644 --- a/tools/veridex/hidden_api_finder.h +++ b/tools/veridex/hidden_api_finder.h @@ -27,6 +27,7 @@ namespace art { class HiddenApi; +struct HiddenApiStats; class VeridexResolver; /** @@ -40,11 +41,12 @@ class HiddenApiFinder { // hidden API uses. void Run(const std::vector<std::unique_ptr<VeridexResolver>>& app_resolvers); + void Dump(std::ostream& os, HiddenApiStats* stats, bool dump_reflection); + private: void CollectAccesses(VeridexResolver* resolver); void CheckMethod(uint32_t method_idx, VeridexResolver* resolver, MethodReference ref); void CheckField(uint32_t field_idx, VeridexResolver* resolver, MethodReference ref); - void Dump(std::ostream& os); const HiddenApi& hidden_api_; std::set<std::string> classes_; diff --git a/tools/veridex/precise_hidden_api_finder.cc b/tools/veridex/precise_hidden_api_finder.cc new file mode 100644 index 0000000000..2092af3d75 --- /dev/null +++ b/tools/veridex/precise_hidden_api_finder.cc @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "precise_hidden_api_finder.h" + +#include "dex/code_item_accessors-inl.h" +#include "dex/dex_instruction-inl.h" +#include "dex/dex_file.h" +#include "dex/method_reference.h" +#include "flow_analysis.h" +#include "hidden_api.h" +#include "resolver.h" +#include "veridex.h" + +#include <iostream> + +namespace art { + +void PreciseHiddenApiFinder::Run(const std::vector<std::unique_ptr<VeridexResolver>>& resolvers) { + for (const std::unique_ptr<VeridexResolver>& resolver : resolvers) { + const DexFile& dex_file = resolver->GetDexFile(); + size_t class_def_count = dex_file.NumClassDefs(); + for (size_t class_def_index = 0; class_def_index < class_def_count; ++class_def_index) { + const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_index); + const uint8_t* class_data = dex_file.GetClassData(class_def); + if (class_data == nullptr) { + // Empty class. + continue; + } + ClassDataItemIterator it(dex_file, class_data); + it.SkipAllFields(); + for (; it.HasNextMethod(); it.Next()) { + const DexFile::CodeItem* code_item = it.GetMethodCodeItem(); + if (code_item == nullptr) { + continue; + } + CodeItemDataAccessor code_item_accessor(dex_file, code_item); + VeriFlowAnalysis ana(resolver.get(), code_item_accessor); + ana.Run(); + if (!ana.GetFieldUses().empty()) { + field_uses_[MethodReference(&dex_file, it.GetMemberIndex())] = ana.GetFieldUses(); + } + if (!ana.GetMethodUses().empty()) { + method_uses_[MethodReference(&dex_file, it.GetMemberIndex())] = ana.GetMethodUses(); + } + } + } + } +} + +void PreciseHiddenApiFinder::Dump(std::ostream& os, HiddenApiStats* stats) { + static const char* kPrefix = " "; + for (auto kinds : { field_uses_, method_uses_ }) { + for (auto it : kinds) { + MethodReference ref = it.first; + for (const std::pair<RegisterValue, RegisterValue>& info : it.second) { + if ((info.first.GetSource() == RegisterSource::kClass || + info.first.GetSource() == RegisterSource::kString) && + info.second.GetSource() == RegisterSource::kString) { + std::string cls(info.first.ToString()); + std::string name(info.second.ToString()); + std::string full_name = cls + "->" + name; + HiddenApiAccessFlags::ApiList api_list = hidden_api_.GetApiList(full_name); + stats->api_counts[api_list]++; + if (api_list != HiddenApiAccessFlags::kWhitelist) { + ++stats->reflection_count; + os << "#" << ++stats->count << ": Reflection " << api_list << " " << full_name + << " use:"; + os << std::endl; + os << kPrefix << HiddenApi::GetApiMethodName(ref) << std::endl; + os << std::endl; + } + } + } + } + } +} + +} // namespace art diff --git a/tools/veridex/precise_hidden_api_finder.h b/tools/veridex/precise_hidden_api_finder.h new file mode 100644 index 0000000000..22744a6f1f --- /dev/null +++ b/tools/veridex/precise_hidden_api_finder.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_TOOLS_VERIDEX_PRECISE_HIDDEN_API_FINDER_H_ +#define ART_TOOLS_VERIDEX_PRECISE_HIDDEN_API_FINDER_H_ + +#include "dex/method_reference.h" +#include "flow_analysis.h" + +#include <iostream> +#include <map> +#include <set> +#include <string> + +namespace art { + +class HiddenApi; +struct HiddenApiStats; +class VeridexResolver; + +/** + * Reports known uses of hidden APIs from reflection. + */ +class PreciseHiddenApiFinder { + public: + explicit PreciseHiddenApiFinder(const HiddenApi& hidden_api) : hidden_api_(hidden_api) {} + + // Iterate over the dex files associated with the passed resolvers to report + // hidden API uses. + void Run(const std::vector<std::unique_ptr<VeridexResolver>>& app_resolvers); + + void Dump(std::ostream& os, HiddenApiStats* stats); + + private: + const HiddenApi& hidden_api_; + std::map<MethodReference, std::vector<std::pair<RegisterValue, RegisterValue>>> field_uses_; + std::map<MethodReference, std::vector<std::pair<RegisterValue, RegisterValue>>> method_uses_; +}; + +} // namespace art + +#endif // ART_TOOLS_VERIDEX_PRECISE_HIDDEN_API_FINDER_H_ diff --git a/tools/veridex/resolver.cc b/tools/veridex/resolver.cc index 13dda5c199..9113039b04 100644 --- a/tools/veridex/resolver.cc +++ b/tools/veridex/resolver.cc @@ -59,6 +59,14 @@ void VeridexResolver::Run() { static bool HasSameNameAndSignature(const DexFile& dex_file, const DexFile::MethodId& method_id, const char* method_name, + const char* type) { + return strcmp(method_name, dex_file.GetMethodName(method_id)) == 0 && + strcmp(type, dex_file.GetMethodSignature(method_id).ToString().c_str()) == 0; +} + +static bool HasSameNameAndSignature(const DexFile& dex_file, + const DexFile::MethodId& method_id, + const char* method_name, const Signature& signature) { return strcmp(method_name, dex_file.GetMethodName(method_id)) == 0 && dex_file.GetMethodSignature(method_id) == signature; @@ -241,6 +249,34 @@ VeriField VeridexResolver::LookupFieldIn(const VeriClass& kls, return nullptr; } +VeriMethod VeridexResolver::LookupDeclaredMethodIn(const VeriClass& kls, + const char* method_name, + const char* type) const { + if (kls.IsPrimitive()) { + return nullptr; + } + if (kls.IsArray()) { + return nullptr; + } + VeridexResolver* resolver = GetResolverOf(kls); + const DexFile& other_dex_file = resolver->dex_file_; + const uint8_t* class_data = other_dex_file.GetClassData(*kls.GetClassDef()); + if (class_data != nullptr) { + ClassDataItemIterator it(other_dex_file, class_data); + it.SkipAllFields(); + for (; it.HasNextMethod(); it.Next()) { + const DexFile::MethodId& other_method_id = other_dex_file.GetMethodId(it.GetMemberIndex()); + if (HasSameNameAndSignature(other_dex_file, + other_method_id, + method_name, + type)) { + return it.DataPointer(); + } + } + } + return nullptr; +} + VeriMethod VeridexResolver::GetMethod(uint32_t method_index) { VeriMethod method_info = method_infos_[method_index]; if (method_info == nullptr) { diff --git a/tools/veridex/resolver.h b/tools/veridex/resolver.h index 06c8aa70c5..52b15fe0ed 100644 --- a/tools/veridex/resolver.h +++ b/tools/veridex/resolver.h @@ -66,6 +66,11 @@ class VeridexResolver { const char* field_name, const char* field_type); + // Lookup a method declared in `kls`. + VeriMethod LookupDeclaredMethodIn(const VeriClass& kls, + const char* method_name, + const char* signature) const; + // Resolve all type_id/method_id/field_id. void ResolveAll(); diff --git a/tools/veridex/veridex.cc b/tools/veridex/veridex.cc index 16e9f0e55b..6e72faaf57 100644 --- a/tools/veridex/veridex.cc +++ b/tools/veridex/veridex.cc @@ -22,6 +22,7 @@ #include "dex/dex_file_loader.h" #include "hidden_api.h" #include "hidden_api_finder.h" +#include "precise_hidden_api_finder.h" #include "resolver.h" #include <sstream> @@ -47,8 +48,18 @@ VeriClass* VeriClass::float_ = &f_; VeriClass* VeriClass::double_ = &d_; VeriClass* VeriClass::long_ = &j_; VeriClass* VeriClass::void_ = &v_; + // Will be set after boot classpath has been resolved. VeriClass* VeriClass::object_ = nullptr; +VeriClass* VeriClass::class_ = nullptr; +VeriClass* VeriClass::string_ = nullptr; +VeriClass* VeriClass::throwable_ = nullptr; +VeriMethod VeriClass::forName_ = nullptr; +VeriMethod VeriClass::getField_ = nullptr; +VeriMethod VeriClass::getDeclaredField_ = nullptr; +VeriMethod VeriClass::getMethod_ = nullptr; +VeriMethod VeriClass::getDeclaredMethod_ = nullptr; +VeriMethod VeriClass::getClass_ = nullptr; struct VeridexOptions { const char* dex_file = nullptr; @@ -56,6 +67,7 @@ struct VeridexOptions { const char* blacklist = nullptr; const char* light_greylist = nullptr; const char* dark_greylist = nullptr; + bool precise = true; }; static const char* Substr(const char* str, int index) { @@ -76,6 +88,7 @@ static void ParseArgs(VeridexOptions* options, int argc, char** argv) { static const char* kBlacklistOption = "--blacklist="; static const char* kDarkGreylistOption = "--dark-greylist="; static const char* kLightGreylistOption = "--light-greylist="; + static const char* kImprecise = "--imprecise"; for (int i = 0; i < argc; ++i) { if (StartsWith(argv[i], kDexFileOption)) { @@ -88,6 +101,8 @@ static void ParseArgs(VeridexOptions* options, int argc, char** argv) { options->dark_greylist = Substr(argv[i], strlen(kDarkGreylistOption)); } else if (StartsWith(argv[i], kLightGreylistOption)) { options->light_greylist = Substr(argv[i], strlen(kLightGreylistOption)); + } else if (strcmp(argv[i], kImprecise) == 0) { + options->precise = false; } } } @@ -157,21 +172,70 @@ class Veridex { std::vector<std::unique_ptr<VeridexResolver>> boot_resolvers; Resolve(boot_dex_files, resolver_map, type_map, &boot_resolvers); - // Now that boot classpath has been resolved, fill j.l.Object. + // Now that boot classpath has been resolved, fill classes and reflection + // methods. VeriClass::object_ = type_map["Ljava/lang/Object;"]; + VeriClass::class_ = type_map["Ljava/lang/Class;"]; + VeriClass::string_ = type_map["Ljava/lang/String;"]; + VeriClass::throwable_ = type_map["Ljava/lang/Throwable;"]; + VeriClass::forName_ = boot_resolvers[0]->LookupDeclaredMethodIn( + *VeriClass::class_, "forName", "(Ljava/lang/String;)Ljava/lang/Class;"); + VeriClass::getField_ = boot_resolvers[0]->LookupDeclaredMethodIn( + *VeriClass::class_, "getField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;"); + VeriClass::getDeclaredField_ = boot_resolvers[0]->LookupDeclaredMethodIn( + *VeriClass::class_, "getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;"); + VeriClass::getMethod_ = boot_resolvers[0]->LookupDeclaredMethodIn( + *VeriClass::class_, + "getMethod", + "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"); + VeriClass::getDeclaredMethod_ = boot_resolvers[0]->LookupDeclaredMethodIn( + *VeriClass::class_, + "getDeclaredMethod", + "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"); + VeriClass::getClass_ = boot_resolvers[0]->LookupDeclaredMethodIn( + *VeriClass::object_, "getClass", "()Ljava/lang/Class;"); std::vector<std::unique_ptr<VeridexResolver>> app_resolvers; Resolve(app_dex_files, resolver_map, type_map, &app_resolvers); // Find and log uses of hidden APIs. HiddenApi hidden_api(options.blacklist, options.dark_greylist, options.light_greylist); + HiddenApiStats stats; + HiddenApiFinder api_finder(hidden_api); api_finder.Run(app_resolvers); + api_finder.Dump(std::cout, &stats, !options.precise); + + if (options.precise) { + PreciseHiddenApiFinder precise_api_finder(hidden_api); + precise_api_finder.Run(app_resolvers); + precise_api_finder.Dump(std::cout, &stats); + } + + DumpSummaryStats(std::cout, stats); + + if (options.precise) { + std::cout << "To run an analysis that can give more reflection accesses, " << std::endl + << "but could include false positives, pass the --imprecise flag. " << std::endl; + } return 0; } private: + static void DumpSummaryStats(std::ostream& os, const HiddenApiStats& stats) { + static const char* kPrefix = " "; + os << stats.count << " hidden API(s) used: " + << stats.linking_count << " linked against, " + << stats.reflection_count << " through reflection" << std::endl; + os << kPrefix << stats.api_counts[HiddenApiAccessFlags::kBlacklist] + << " in blacklist" << std::endl; + os << kPrefix << stats.api_counts[HiddenApiAccessFlags::kDarkGreylist] + << " in dark greylist" << std::endl; + os << kPrefix << stats.api_counts[HiddenApiAccessFlags::kLightGreylist] + << " in light greylist" << std::endl; + } + static bool Load(const std::string& filename, std::string& content, std::vector<std::unique_ptr<const DexFile>>* dex_files, diff --git a/tools/veridex/veridex.h b/tools/veridex/veridex.h index 0c928ab166..75e4845293 100644 --- a/tools/veridex/veridex.h +++ b/tools/veridex/veridex.h @@ -25,6 +25,18 @@ namespace art { /** + * Abstraction for fields defined in dex files. Currently, that's a pointer into their + * `encoded_field` description. + */ +using VeriField = const uint8_t*; + +/** + * Abstraction for methods defined in dex files. Currently, that's a pointer into their + * `encoded_method` description. + */ +using VeriMethod = const uint8_t*; + +/** * Abstraction for classes defined, or implicitly defined (for arrays and primitives) * in dex files. */ @@ -52,6 +64,9 @@ class VeriClass { const DexFile::ClassDef* GetClassDef() const { return class_def_; } static VeriClass* object_; + static VeriClass* class_; + static VeriClass* string_; + static VeriClass* throwable_; static VeriClass* boolean_; static VeriClass* byte_; static VeriClass* char_; @@ -62,23 +77,26 @@ class VeriClass { static VeriClass* long_; static VeriClass* void_; + static VeriMethod forName_; + static VeriMethod getField_; + static VeriMethod getDeclaredField_; + static VeriMethod getMethod_; + static VeriMethod getDeclaredMethod_; + static VeriMethod getClass_; + private: Primitive::Type kind_; uint8_t dimensions_; const DexFile::ClassDef* class_def_; }; -/** - * Abstraction for fields defined in dex files. Currently, that's a pointer into their - * `encoded_field` description. - */ -using VeriField = const uint8_t*; +inline bool IsGetMethod(VeriMethod method) { + return method == VeriClass::getMethod_ || method == VeriClass::getDeclaredMethod_; +} -/** - * Abstraction for methods defined in dex files. Currently, that's a pointer into their - * `encoded_method` description. - */ -using VeriMethod = const uint8_t*; +inline bool IsGetField(VeriMethod method) { + return method == VeriClass::getField_ || method == VeriClass::getDeclaredField_; +} /** * Map from name to VeriClass to quickly lookup classes. |