diff options
| -rw-r--r-- | Android.bp | 1 | ||||
| -rw-r--r-- | build/Android.gtest.mk | 8 | ||||
| -rw-r--r-- | dex2oat/linker/oat_writer.cc | 14 | ||||
| -rw-r--r-- | runtime/dex/art_dex_file_loader.cc | 15 | ||||
| -rw-r--r-- | runtime/dex/art_dex_file_loader.h | 2 | ||||
| -rw-r--r-- | runtime/dex/dex_file.h | 11 | ||||
| -rw-r--r-- | runtime/dex/dex_file_loader.cc | 2 | ||||
| -rw-r--r-- | runtime/dex/dex_file_loader.h | 2 | ||||
| -rw-r--r-- | runtime/dex/dex_hidden_access_flags.h | 110 | ||||
| -rw-r--r-- | runtime/leb128.h | 2 | ||||
| -rw-r--r-- | runtime/modifiers.h | 8 | ||||
| -rw-r--r-- | runtime/parsed_options.cc | 3 | ||||
| -rw-r--r-- | runtime/runtime.cc | 4 | ||||
| -rw-r--r-- | runtime/runtime.h | 2 | ||||
| -rw-r--r-- | runtime/runtime_options.def | 1 | ||||
| -rw-r--r-- | test/HiddenApi/Main.java | 26 | ||||
| -rw-r--r-- | tools/hiddenapi/Android.bp | 62 | ||||
| -rw-r--r-- | tools/hiddenapi/README.md | 54 | ||||
| -rw-r--r-- | tools/hiddenapi/hiddenapi.cc | 408 | ||||
| -rw-r--r-- | tools/hiddenapi/hiddenapi_test.cc | 600 |
20 files changed, 1324 insertions, 11 deletions
diff --git a/Android.bp b/Android.bp index 197860694b..caf4f9a325 100644 --- a/Android.bp +++ b/Android.bp @@ -47,6 +47,7 @@ subdirs = [ "tools/breakpoint-logger", "tools/cpp-define-generator", "tools/dmtracedump", + "tools/hiddenapi", "tools/titrace", "tools/wrapagentproperties", ] diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 45f4e2d4d8..4f5df03c19 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -36,6 +36,7 @@ GTEST_DEX_DIRECTORIES := \ ForClassLoaderD \ ExceptionHandle \ GetMethodSignature \ + HiddenApi \ ImageLayoutA \ ImageLayoutB \ IMTA \ @@ -113,6 +114,7 @@ ART_GTEST_dexlayout_test_DEX_DEPS := ManyMethods ART_GTEST_dex2oat_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) ManyMethods Statics VerifierDeps ART_GTEST_dex2oat_image_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) Statics VerifierDeps ART_GTEST_exception_test_DEX_DEPS := ExceptionHandle +ART_GTEST_hiddenapi_test_DEX_DEPS := HiddenApi ART_GTEST_image_test_DEX_DEPS := ImageLayoutA ImageLayoutB DefaultMethods ART_GTEST_imtable_test_DEX_DEPS := IMTA IMTB ART_GTEST_instrumentation_test_DEX_DEPS := Instrumentation @@ -266,6 +268,11 @@ ART_GTEST_patchoat_test_TARGET_DEPS := \ ART_GTEST_profile_assistant_test_HOST_DEPS := profmand-host ART_GTEST_profile_assistant_test_TARGET_DEPS := profmand-target +ART_GTEST_hiddenapi_test_HOST_DEPS := \ + $(HOST_CORE_IMAGE_DEFAULT_64) \ + $(HOST_CORE_IMAGE_DEFAULT_32) \ + hiddenapid-host + # The path for which all the source files are relative, not actually the current directory. LOCAL_PATH := art @@ -279,6 +286,7 @@ ART_TEST_MODULES := \ art_dexlayout_tests \ art_dexlist_tests \ art_dexoptanalyzer_tests \ + art_hiddenapi_tests \ art_imgdiag_tests \ art_oatdump_tests \ art_patchoat_tests \ diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc index 28ded6ef85..44493283db 100644 --- a/dex2oat/linker/oat_writer.cc +++ b/dex2oat/linker/oat_writer.cc @@ -3434,6 +3434,11 @@ bool OatWriter::SeekToDexFile(OutputStream* out, File* file, OatDexFile* oat_dex } bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_file) { + // Open dex files and write them into `out`. + // Note that we only verify dex files which do not belong to the boot class path. + // This is because those have been processed by `hiddenapi` and would not pass + // some of the checks. No guarantees are lost, however, as `hiddenapi` verifies + // the dex files prior to processing. TimingLogger::ScopedTiming split("Dex Layout", timings_); std::string error_msg; std::string location(oat_dex_file->GetLocation()); @@ -3450,7 +3455,7 @@ bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_fil dex_file = dex_file_loader.Open(location, zip_entry->GetCrc32(), std::move(mem_map), - /* verify */ true, + /* verify */ !compiling_boot_image_, /* verify_checksum */ true, &error_msg); } else if (oat_dex_file->source_.IsRawFile()) { @@ -3460,8 +3465,11 @@ bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_fil PLOG(ERROR) << "Failed to dup dex file descriptor (" << raw_file->Fd() << ") at " << location; return false; } - dex_file = dex_file_loader.OpenDex( - dup_fd, location, /* verify */ true, /* verify_checksum */ true, &error_msg); + dex_file = dex_file_loader.OpenDex(dup_fd, location, + /* verify */ !compiling_boot_image_, + /* verify_checksum */ true, + /* mmap_shared */ false, + &error_msg); } else { // The source data is a vdex file. CHECK(oat_dex_file->source_.IsRawData()) diff --git a/runtime/dex/art_dex_file_loader.cc b/runtime/dex/art_dex_file_loader.cc index 282b282707..dee736ecff 100644 --- a/runtime/dex/art_dex_file_loader.cc +++ b/runtime/dex/art_dex_file_loader.cc @@ -127,8 +127,12 @@ bool ArtDexFileLoader::GetMultiDexChecksums(const char* filename, return true; } if (IsMagicValid(magic)) { - std::unique_ptr<const DexFile> dex_file( - OpenFile(fd.Release(), filename, false, false, error_msg)); + std::unique_ptr<const DexFile> dex_file(OpenFile(fd.Release(), + filename, + /* verify */ false, + /* verify_checksum */ false, + /* mmap_shared */ false, + error_msg)); if (dex_file == nullptr) { return false; } @@ -211,6 +215,7 @@ bool ArtDexFileLoader::Open(const char* filename, location, verify, verify_checksum, + /* mmap_shared */ false, error_msg)); if (dex_file.get() != nullptr) { dex_files->push_back(std::move(dex_file)); @@ -227,9 +232,10 @@ std::unique_ptr<const DexFile> ArtDexFileLoader::OpenDex(int fd, const std::string& location, bool verify, bool verify_checksum, + bool mmap_shared, std::string* error_msg) const { ScopedTrace trace("Open dex file " + std::string(location)); - return OpenFile(fd, location, verify, verify_checksum, error_msg); + return OpenFile(fd, location, verify, verify_checksum, mmap_shared, error_msg); } bool ArtDexFileLoader::OpenZip(int fd, @@ -253,6 +259,7 @@ std::unique_ptr<const DexFile> ArtDexFileLoader::OpenFile(int fd, const std::string& location, bool verify, bool verify_checksum, + bool mmap_shared, std::string* error_msg) const { ScopedTrace trace(std::string("Open dex file ") + std::string(location)); CHECK(!location.empty()); @@ -273,7 +280,7 @@ std::unique_ptr<const DexFile> ArtDexFileLoader::OpenFile(int fd, size_t length = sbuf.st_size; map.reset(MemMap::MapFile(length, PROT_READ, - MAP_PRIVATE, + mmap_shared ? MAP_SHARED : MAP_PRIVATE, fd, 0, /*low_4gb*/false, diff --git a/runtime/dex/art_dex_file_loader.h b/runtime/dex/art_dex_file_loader.h index a6191d9f54..8c12bf3137 100644 --- a/runtime/dex/art_dex_file_loader.h +++ b/runtime/dex/art_dex_file_loader.h @@ -83,6 +83,7 @@ class ArtDexFileLoader : public DexFileLoader { const std::string& location, bool verify, bool verify_checksum, + bool mmap_shared, std::string* error_msg) const OVERRIDE; // Opens dex files from within a .jar, .zip, or .apk file @@ -98,6 +99,7 @@ class ArtDexFileLoader : public DexFileLoader { const std::string& location, bool verify, bool verify_checksum, + bool mmap_shared, std::string* error_msg) const OVERRIDE; // Open all classesXXX.dex files from a zip archive. diff --git a/runtime/dex/dex_file.h b/runtime/dex/dex_file.h index 183d84e15d..695527605d 100644 --- a/runtime/dex/dex_file.h +++ b/runtime/dex/dex_file.h @@ -27,6 +27,7 @@ #include "base/macros.h" #include "base/value_object.h" #include "dex_file_types.h" +#include "dex_hidden_access_flags.h" #include "dex_instruction_iterator.h" #include "globals.h" #include "jni.h" @@ -1252,10 +1253,16 @@ class ClassDataItemIterator { } } uint32_t GetFieldAccessFlags() const { - return GetRawMemberAccessFlags() & kAccValidFieldFlags; + return GetMemberAccessFlags() & kAccValidFieldFlags; } uint32_t GetMethodAccessFlags() const { - return GetRawMemberAccessFlags() & kAccValidMethodFlags; + return GetMemberAccessFlags() & kAccValidMethodFlags; + } + uint32_t GetMemberAccessFlags() const { + return DexHiddenAccessFlags::RemoveHiddenFlags(GetRawMemberAccessFlags()); + } + DexHiddenAccessFlags::ApiList DecodeHiddenAccessFlags() const { + return DexHiddenAccessFlags::Decode(GetRawMemberAccessFlags()); } bool MemberIsNative() const { return GetRawMemberAccessFlags() & kAccNative; diff --git a/runtime/dex/dex_file_loader.cc b/runtime/dex/dex_file_loader.cc index 10aef56125..6ae1d73fdd 100644 --- a/runtime/dex/dex_file_loader.cc +++ b/runtime/dex/dex_file_loader.cc @@ -132,6 +132,7 @@ std::unique_ptr<const DexFile> DexFileLoader::OpenDex( const std::string& location ATTRIBUTE_UNUSED, bool verify ATTRIBUTE_UNUSED, bool verify_checksum ATTRIBUTE_UNUSED, + bool mmap_shared ATTRIBUTE_UNUSED, std::string* error_msg) const { *error_msg = "UNIMPLEMENTED"; return nullptr; @@ -153,6 +154,7 @@ std::unique_ptr<const DexFile> DexFileLoader::OpenFile( const std::string& location ATTRIBUTE_UNUSED, bool verify ATTRIBUTE_UNUSED, bool verify_checksum ATTRIBUTE_UNUSED, + bool mmap_shared ATTRIBUTE_UNUSED, std::string* error_msg) const { *error_msg = "UNIMPLEMENTED"; return nullptr; diff --git a/runtime/dex/dex_file_loader.h b/runtime/dex/dex_file_loader.h index 6f1afd636f..4e45fb03b8 100644 --- a/runtime/dex/dex_file_loader.h +++ b/runtime/dex/dex_file_loader.h @@ -97,6 +97,7 @@ class DexFileLoader { const std::string& location, bool verify, bool verify_checksum, + bool mmap_shared, std::string* error_msg) const; // Opens dex files from within a .jar, .zip, or .apk file @@ -182,6 +183,7 @@ class DexFileLoader { const std::string& location, bool verify, bool verify_checksum, + bool mmap_shared, std::string* error_msg) const; // Open all classesXXX.dex files from a zip archive. diff --git a/runtime/dex/dex_hidden_access_flags.h b/runtime/dex/dex_hidden_access_flags.h new file mode 100644 index 0000000000..16fae86b24 --- /dev/null +++ b/runtime/dex/dex_hidden_access_flags.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011 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_DEX_DEX_HIDDEN_ACCESS_FLAGS_H_ +#define ART_RUNTIME_DEX_DEX_HIDDEN_ACCESS_FLAGS_H_ + +#include "base/bit_utils.h" +#include "modifiers.h" + +namespace art { + +/* This class is used for encoding and decoding access flags of DexFile members + * from the boot class path. These access flags might contain additional two bits + * of information on whether the given class member should be hidden from apps. + * + * First bit is encoded as inversion of visibility flags (public/private/protected). + * At most one can be set for any given class member. If two or three are set, + * this is interpreted as the first bit being set and actual visibility flags + * being the complement of the encoded flags. + * + * Second bit is either encoded as bit 5 for fields and non-native methods, where + * it carries no other meaning. If a method is native, bit 9 is used. + * + * Bits were selected so that they never increase the length of unsigned LEB-128 + * encoding of the access flags. + */ +class DexHiddenAccessFlags { + public: + enum ApiList { + kWhitelist = 0, + kLightGreylist, + kDarkGreylist, + kBlacklist, + }; + + static ALWAYS_INLINE ApiList Decode(uint32_t access_flags) { + DexHiddenAccessFlags flags(access_flags); + uint32_t int_value = (flags.IsFirstBitSet() ? 1 : 0) + (flags.IsSecondBitSet() ? 2 : 0); + return static_cast<ApiList>(int_value); + } + + static ALWAYS_INLINE uint32_t RemoveHiddenFlags(uint32_t access_flags) { + DexHiddenAccessFlags flags(access_flags); + flags.SetFirstBit(false); + flags.SetSecondBit(false); + return flags.GetEncoding(); + } + + static ALWAYS_INLINE uint32_t Encode(uint32_t access_flags, ApiList value) { + DexHiddenAccessFlags flags(access_flags); + uint32_t int_value = static_cast<uint32_t>(value); + flags.SetFirstBit((int_value & 1) != 0); + flags.SetSecondBit((int_value & 2) != 0); + return flags.GetEncoding(); + } + + private: + explicit DexHiddenAccessFlags(uint32_t access_flags) : access_flags_(access_flags) {} + + ALWAYS_INLINE uint32_t GetSecondFlag() { + return ((access_flags_ & kAccNative) != 0) ? kAccDexHiddenBitNative : kAccDexHiddenBit; + } + + ALWAYS_INLINE bool IsFirstBitSet() { + static_assert(IsPowerOfTwo(0u), "Following statement checks if *at most* one bit is set"); + return !IsPowerOfTwo(access_flags_ & kAccVisibilityFlags); + } + + ALWAYS_INLINE void SetFirstBit(bool value) { + if (IsFirstBitSet() != value) { + access_flags_ ^= kAccVisibilityFlags; + } + } + + ALWAYS_INLINE bool IsSecondBitSet() { + return (access_flags_ & GetSecondFlag()) != 0; + } + + ALWAYS_INLINE void SetSecondBit(bool value) { + if (value) { + access_flags_ |= GetSecondFlag(); + } else { + access_flags_ &= ~GetSecondFlag(); + } + } + + ALWAYS_INLINE uint32_t GetEncoding() const { + return access_flags_; + } + + uint32_t access_flags_; +}; + +} // namespace art + + +#endif // ART_RUNTIME_DEX_DEX_HIDDEN_ACCESS_FLAGS_H_ diff --git a/runtime/leb128.h b/runtime/leb128.h index 2bfed7f539..9fb09d8fc2 100644 --- a/runtime/leb128.h +++ b/runtime/leb128.h @@ -241,7 +241,7 @@ static inline void EncodeUnsignedLeb128(Vector* dest, uint32_t value) { static inline void UpdateUnsignedLeb128(uint8_t* dest, uint32_t value) { const uint8_t* old_end = dest; uint32_t old_value = DecodeUnsignedLeb128(&old_end); - DCHECK_LE(value, old_value); + DCHECK_LE(UnsignedLeb128Size(value), UnsignedLeb128Size(old_value)); for (uint8_t* end = EncodeUnsignedLeb128(dest, value); end < old_end; end++) { // Use longer encoding than necessary to fill the allocated space. end[-1] |= 0x80; diff --git a/runtime/modifiers.h b/runtime/modifiers.h index d7d647b8fd..a72f9daad6 100644 --- a/runtime/modifiers.h +++ b/runtime/modifiers.h @@ -42,6 +42,12 @@ static constexpr uint32_t kAccEnum = 0x4000; // class, field, ic (1.5) static constexpr uint32_t kAccJavaFlagsMask = 0xffff; // bits set from Java sources (low 16) +// The following flags are used to insert hidden API access flags into boot +// class path dex files. They are decoded by DexFile::ClassDataItemIterator and +// removed from the access flags before used by the runtime. +static constexpr uint32_t kAccDexHiddenBit = 0x00000020; // field, method (not native) +static constexpr uint32_t kAccDexHiddenBitNative = 0x00000200; // method (native) + static constexpr uint32_t kAccConstructor = 0x00010000; // method (dex only) <(cl)init> static constexpr uint32_t kAccDeclaredSynchronized = 0x00020000; // method (dex only) static constexpr uint32_t kAccClassIsProxy = 0x00040000; // class (dex only) @@ -127,6 +133,8 @@ static constexpr uint32_t kAccValidClassFlags = kAccPublic | kAccFinal | kAccSup static constexpr uint32_t kAccValidInterfaceFlags = kAccPublic | kAccInterface | kAccAbstract | kAccSynthetic | kAccAnnotation; +static constexpr uint32_t kAccVisibilityFlags = kAccPublic | kAccPrivate | kAccProtected; + } // namespace art #endif // ART_RUNTIME_MODIFIERS_H_ diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index 3ac3d03e90..2f60162c77 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -327,6 +327,9 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .WithType<bool>() .WithValueMap({{"false", false}, {"true", true}}) .IntoKey(M::SlowDebug) + .Define("-Xtarget-sdk-version:_") + .WithType<int>() + .IntoKey(M::TargetSdkVersion) .Ignore({ "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_", diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 377e0a3fca..007d361976 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -250,7 +250,7 @@ Runtime::Runtime() preinitialization_transactions_(), verify_(verifier::VerifyMode::kNone), allow_dex_file_fallback_(true), - target_sdk_version_(0), + target_sdk_version_(kUnsetSdkVersion), implicit_null_checks_(false), implicit_so_checks_(false), implicit_suspend_checks_(false), @@ -1166,6 +1166,8 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { verify_ = runtime_options.GetOrDefault(Opt::Verify); allow_dex_file_fallback_ = !runtime_options.Exists(Opt::NoDexFileFallback); + target_sdk_version_ = runtime_options.GetOrDefault(Opt::TargetSdkVersion); + no_sig_chain_ = runtime_options.Exists(Opt::NoSigChain); force_native_bridge_ = runtime_options.Exists(Opt::ForceNativeBridge); diff --git a/runtime/runtime.h b/runtime/runtime.h index 3e055c3f84..6d2887cc42 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -709,6 +709,8 @@ class Runtime { return jdwp_provider_; } + static constexpr int32_t kUnsetSdkVersion = 0u; + private: static void InitPlatformSignalHandlers(); diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index 1dd3de5039..3996989920 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -118,6 +118,7 @@ RUNTIME_OPTIONS_KEY (std::vector<std::string>, \ ImageCompilerOptions) // -Ximage-compiler-option ... RUNTIME_OPTIONS_KEY (verifier::VerifyMode, \ Verify, verifier::VerifyMode::kEnable) +RUNTIME_OPTIONS_KEY (int, TargetSdkVersion, Runtime::kUnsetSdkVersion) RUNTIME_OPTIONS_KEY (std::string, NativeBridge) RUNTIME_OPTIONS_KEY (unsigned int, ZygoteMaxFailedBoots, 10) RUNTIME_OPTIONS_KEY (Unit, NoDexFileFallback) diff --git a/test/HiddenApi/Main.java b/test/HiddenApi/Main.java new file mode 100644 index 0000000000..187dd6e599 --- /dev/null +++ b/test/HiddenApi/Main.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. + */ + +class Main { + public int ifield; + private static Object sfield; + + void imethod(long x) {} + public static void smethod(Object x) {} + + public native void inmethod(char x); + protected native static void snmethod(Integer x); +} diff --git a/tools/hiddenapi/Android.bp b/tools/hiddenapi/Android.bp new file mode 100644 index 0000000000..ccab7f88b4 --- /dev/null +++ b/tools/hiddenapi/Android.bp @@ -0,0 +1,62 @@ +// +// 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. +// + +cc_defaults { + name: "hiddenapi-defaults", + host_supported: true, + device_supported: false, + defaults: ["art_defaults"], + srcs: [ + "hiddenapi.cc", + ], + + target: { + android: { + compile_multilib: "prefer32", + }, + }, + + shared_libs: [ + "libbase", + ], +} + +art_cc_binary { + name: "hiddenapi", + defaults: ["hiddenapi-defaults"], + shared_libs: [ + "libart", + ], +} + +art_cc_binary { + name: "hiddenapid", + defaults: [ + "art_debug_defaults", + "hiddenapi-defaults", + ], + shared_libs: [ + "libartd", + ], +} + +art_cc_test { + name: "art_hiddenapi_tests", + defaults: [ + "art_gtest_defaults", + ], + srcs: ["hiddenapi_test.cc"], +} diff --git a/tools/hiddenapi/README.md b/tools/hiddenapi/README.md new file mode 100644 index 0000000000..cad12126dd --- /dev/null +++ b/tools/hiddenapi/README.md @@ -0,0 +1,54 @@ +HiddenApi +========= + +This tool iterates over all class members inside given DEX files and modifies +their access flags if their signatures appear on one of two lists - greylist and +blacklist - provided as text file inputs. These access flags denote to the +runtime that the marked methods/fields should be treated as internal APIs with +access restricted only to platform code. Methods/fields not mentioned on the two +lists are assumed to be on a whitelist and left accessible by all code. + +API signatures +============== + +The methods/fields to be marked are specified in two text files (greylist, +blacklist) provided an input. Only one signature per line is allowed. + +Types are expected in their DEX format - class descriptors are to be provided in +"slash" form, e.g. "Ljava/lang/Object;", primitive types in their shorty form, +e.g. "I" for "int", and a "[" prefix denotes an array type. Lists of types do +not use any separators, e.g. "ILxyz;F" for "int, xyz, float". + +Methods are encoded as: + `class_descriptor->method_name(parameter_types)return_type` + +Fields are encoded as: + `class_descriptor->field_name:field_type` + +Bit encoding +============ + +Two bits of information are encoded in the DEX access flags. These are encoded +as unsigned LEB128 values in DEX and so as to not increase the size of the DEX, +different modifiers were chosen for different kinds of methods/fields. + +First bit is encoded as the inversion of visibility access flags (bits 2:0). +At most one of these flags can be set at any given time. Inverting these bits +therefore produces a value where at least two bits are set and there is never +any loss of information. + +Second bit is encoded differently for each given type of class member as there +is no single unused bit such that setting it would not increase the size of the +LEB128 encoding. The following bits are used: + + * bit 5 for fields as it carries no other meaning + * bit 5 for non-native methods, as `synchronized` can only be set on native + methods (the Java `synchronized` modifier is bit 17) + * bit 9 for native methods, as it carries no meaning and bit 8 (`native`) will + make the LEB128 encoding at least two bytes long + +Two following bit encoding is used to denote the membership of a method/field: + + * whitelist: `false`, `false` + * greylist: `true`, `false` + * blacklist: `true`, `true` diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc new file mode 100644 index 0000000000..fe72bb0231 --- /dev/null +++ b/tools/hiddenapi/hiddenapi.cc @@ -0,0 +1,408 @@ +/* + * 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. + */ + +#include <fstream> +#include <iostream> +#include <unordered_set> + +#include "android-base/stringprintf.h" +#include "android-base/strings.h" + +#include "base/unix_file/fd_file.h" +#include "dex/art_dex_file_loader.h" +#include "dex/dex_file-inl.h" +#include "dex/dex_hidden_access_flags.h" +#include "mem_map.h" +#include "os.h" + +namespace art { + +static int original_argc; +static char** original_argv; + +static std::string CommandLine() { + std::vector<std::string> command; + for (int i = 0; i < original_argc; ++i) { + command.push_back(original_argv[i]); + } + return android::base::Join(command, ' '); +} + +static void UsageErrorV(const char* fmt, va_list ap) { + std::string error; + android::base::StringAppendV(&error, fmt, ap); + LOG(ERROR) << error; +} + +static void UsageError(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + UsageErrorV(fmt, ap); + va_end(ap); +} + +NO_RETURN static void Usage(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + UsageErrorV(fmt, ap); + va_end(ap); + + UsageError("Command: %s", CommandLine().c_str()); + UsageError("Usage: hiddenapi [options]..."); + UsageError(""); + UsageError(" --dex=<filename>: specify dex file whose members' access flags are to be set."); + UsageError(" At least one --dex parameter must be specified."); + UsageError(""); + UsageError(" --light-greylist=<filename>:"); + UsageError(" --dark-greylist=<filename>:"); + UsageError(" --blacklist=<filename>: text files with signatures of methods/fields to be marked"); + UsageError(" greylisted/blacklisted respectively. At least one list must be provided."); + UsageError(""); + UsageError(" --print-hidden-api: dump a list of marked methods/fields to the standard output."); + UsageError(" There is no indication which API category they belong to."); + UsageError(""); + + exit(EXIT_FAILURE); +} + +class DexClass { + public: + DexClass(const DexFile& dex_file, uint32_t idx) + : dex_file_(dex_file), class_def_(dex_file.GetClassDef(idx)) {} + + const DexFile& GetDexFile() const { return dex_file_; } + + const dex::TypeIndex GetClassIndex() const { return class_def_.class_idx_; } + + const uint8_t* GetData() const { return dex_file_.GetClassData(class_def_); } + + const char* GetDescriptor() const { return dex_file_.GetClassDescriptor(class_def_); } + + private: + const DexFile& dex_file_; + const DexFile::ClassDef& class_def_; +}; + +class DexMember { + public: + DexMember(const DexClass& klass, const ClassDataItemIterator& it) + : klass_(klass), it_(it) { + DCHECK_EQ(it_.IsAtMethod() ? GetMethodId().class_idx_ : GetFieldId().class_idx_, + klass_.GetClassIndex()); + } + + // Sets hidden bits in access flags and writes them back into the DEX in memory. + // Note that this will not update the cached data of ClassDataItemIterator + // until it iterates over this item again and therefore will fail a CHECK if + // it is called multiple times on the same DexMember. + void SetHidden(DexHiddenAccessFlags::ApiList value) { + const uint32_t old_flags = it_.GetRawMemberAccessFlags(); + const uint32_t new_flags = DexHiddenAccessFlags::Encode(old_flags, value); + CHECK_EQ(UnsignedLeb128Size(new_flags), UnsignedLeb128Size(old_flags)); + + // Locate the LEB128-encoded access flags in class data. + // `ptr` initially points to the next ClassData item. We iterate backwards + // until we hit the terminating byte of the previous Leb128 value. + const uint8_t* ptr = it_.DataPointer(); + if (it_.IsAtMethod()) { + ptr = ReverseSearchUnsignedLeb128(ptr, it_.GetMethodCodeItemOffset()); + } + ptr = ReverseSearchUnsignedLeb128(ptr, old_flags); + + // Overwrite the access flags. + UpdateUnsignedLeb128(const_cast<uint8_t*>(ptr), new_flags); + } + + // Returns true if this member's API entry is in `list`. + bool IsOnApiList(const std::unordered_set<std::string>& list) const { + return list.find(GetApiEntry()) != list.end(); + } + + // Constructs a string with a unique signature of this class member. + std::string GetApiEntry() const { + std::stringstream ss; + ss << klass_.GetDescriptor() << "->"; + if (it_.IsAtMethod()) { + const DexFile::MethodId& mid = GetMethodId(); + ss << klass_.GetDexFile().GetMethodName(mid) + << klass_.GetDexFile().GetMethodSignature(mid).ToString(); + } else { + const DexFile::FieldId& fid = GetFieldId(); + ss << klass_.GetDexFile().GetFieldName(fid) << ":" + << klass_.GetDexFile().GetFieldTypeDescriptor(fid); + } + return ss.str(); + } + + private: + inline const DexFile::MethodId& GetMethodId() const { + DCHECK(it_.IsAtMethod()); + return klass_.GetDexFile().GetMethodId(it_.GetMemberIndex()); + } + + inline const DexFile::FieldId& GetFieldId() const { + DCHECK(!it_.IsAtMethod()); + return klass_.GetDexFile().GetFieldId(it_.GetMemberIndex()); + } + + static inline bool IsLeb128Terminator(const uint8_t* ptr) { + return *ptr <= 0x7f; + } + + // Returns the first byte of a Leb128 value assuming that: + // (1) `end_ptr` points to the first byte after the Leb128 value, and + // (2) there is another Leb128 value before this one. + // The function will fail after reading 5 bytes (the longest supported Leb128 + // encoding) to protect against situations when (2) is not satisfied. + // When a Leb128 value is discovered, it is decoded and CHECKed against `value`. + static const uint8_t* ReverseSearchUnsignedLeb128(const uint8_t* end_ptr, uint32_t expected) { + const uint8_t* ptr = end_ptr; + + // Move one byte back, check that this is the terminating byte. + ptr--; + CHECK(IsLeb128Terminator(ptr)); + + // Keep moving back while the previous byte is not a terminating byte. + // Fail after reading five bytes in case there isn't another Leb128 value + // before this one. + while (!IsLeb128Terminator(ptr - 1)) { + ptr--; + CHECK_LE((size_t) (end_ptr - ptr), 5u); + } + + // Check that the decoded value matches the `expected` value. + const uint8_t* tmp_ptr = ptr; + CHECK_EQ(DecodeUnsignedLeb128(&tmp_ptr), expected); + + return ptr; + } + + const DexClass& klass_; + const ClassDataItemIterator& it_; +}; + +class HiddenApi FINAL { + public: + HiddenApi() : print_hidden_api_(false) {} + + void ParseArgs(int argc, char** argv) { + original_argc = argc; + original_argv = argv; + + android::base::InitLogging(argv); + + // Skip over the command name. + argv++; + argc--; + + if (argc == 0) { + Usage("No arguments specified"); + } + + for (int i = 0; i < argc; ++i) { + const StringPiece option(argv[i]); + const bool log_options = false; + if (log_options) { + LOG(INFO) << "hiddenapi: option[" << i << "]=" << argv[i]; + } + if (option == "--print-hidden-api") { + print_hidden_api_ = true; + } else if (option.starts_with("--dex=")) { + dex_paths_.push_back(option.substr(strlen("--dex=")).ToString()); + } else if (option.starts_with("--light-greylist=")) { + light_greylist_path_ = option.substr(strlen("--light-greylist=")).ToString(); + } else if (option.starts_with("--dark-greylist=")) { + dark_greylist_path_ = option.substr(strlen("--dark-greylist=")).ToString(); + } else if (option.starts_with("--blacklist=")) { + blacklist_path_ = option.substr(strlen("--blacklist=")).ToString(); + } else { + Usage("Unknown argument '%s'", option.data()); + } + } + } + + bool ProcessDexFiles() { + if (dex_paths_.empty()) { + Usage("No DEX files specified"); + } + + if (light_greylist_path_.empty() && dark_greylist_path_.empty() && blacklist_path_.empty()) { + Usage("No API file specified"); + } + + if (!light_greylist_path_.empty() && !OpenApiFile(light_greylist_path_, &light_greylist_)) { + return false; + } + + if (!dark_greylist_path_.empty() && !OpenApiFile(dark_greylist_path_, &dark_greylist_)) { + return false; + } + + if (!blacklist_path_.empty() && !OpenApiFile(blacklist_path_, &blacklist_)) { + return false; + } + + MemMap::Init(); + if (!OpenDexFiles()) { + return false; + } + + DCHECK(!dex_files_.empty()); + for (auto& dex_file : dex_files_) { + CategorizeAllClasses(*dex_file.get()); + } + + UpdateDexChecksums(); + return true; + } + + private: + bool OpenApiFile(const std::string& path, std::unordered_set<std::string>* list) { + DCHECK(list->empty()); + DCHECK(!path.empty()); + + std::ifstream api_file(path, std::ifstream::in); + if (api_file.fail()) { + LOG(ERROR) << "Unable to open file '" << path << "' " << strerror(errno); + return false; + } + + for (std::string line; std::getline(api_file, line);) { + list->insert(line); + } + + api_file.close(); + return true; + } + + bool OpenDexFiles() { + ArtDexFileLoader dex_loader; + DCHECK(dex_files_.empty()); + + for (const std::string& filename : dex_paths_) { + std::string error_msg; + + File fd(filename.c_str(), O_RDWR, /* check_usage */ false); + if (fd.Fd() == -1) { + LOG(ERROR) << "Unable to open file '" << filename << "': " << strerror(errno); + return false; + } + + // Memory-map the dex file with MAP_SHARED flag so that changes in memory + // propagate to the underlying file. We run dex file verification as if + // the dex file was not in boot claass path to check basic assumptions, + // such as that at most one of public/private/protected flag is set. + // We do those checks here and skip them when loading the processed file + // into boot class path. + std::unique_ptr<const DexFile> dex_file(dex_loader.OpenDex(fd.Release(), + /* location */ filename, + /* verify */ true, + /* verify_checksum */ true, + /* mmap_shared */ true, + &error_msg)); + if (dex_file.get() == nullptr) { + LOG(ERROR) << "Open failed for '" << filename << "' " << error_msg; + return false; + } + + if (!dex_file->IsStandardDexFile()) { + LOG(ERROR) << "Expected a standard dex file '" << filename << "'"; + return false; + } + + // Change the protection of the memory mapping to read-write. + if (!dex_file->EnableWrite()) { + LOG(ERROR) << "Failed to enable write permission for '" << filename << "'"; + return false; + } + + dex_files_.push_back(std::move(dex_file)); + } + return true; + } + + void CategorizeAllClasses(const DexFile& dex_file) { + for (uint32_t class_idx = 0; class_idx < dex_file.NumClassDefs(); ++class_idx) { + DexClass klass(dex_file, class_idx); + const uint8_t* klass_data = klass.GetData(); + if (klass_data == nullptr) { + continue; + } + + for (ClassDataItemIterator it(klass.GetDexFile(), klass_data); it.HasNext(); it.Next()) { + DexMember member(klass, it); + + // Catagorize member and overwrite its access flags. + // Note that if a member appears on multiple API lists, it will be categorized + // as the strictest. + bool is_hidden = true; + if (member.IsOnApiList(blacklist_)) { + member.SetHidden(DexHiddenAccessFlags::kBlacklist); + } else if (member.IsOnApiList(dark_greylist_)) { + member.SetHidden(DexHiddenAccessFlags::kDarkGreylist); + } else if (member.IsOnApiList(light_greylist_)) { + member.SetHidden(DexHiddenAccessFlags::kLightGreylist); + } else { + member.SetHidden(DexHiddenAccessFlags::kWhitelist); + is_hidden = false; + } + + if (print_hidden_api_ && is_hidden) { + std::cout << member.GetApiEntry() << std::endl; + } + } + } + } + + void UpdateDexChecksums() { + for (auto& dex_file : dex_files_) { + // Obtain a writeable pointer to the dex header. + DexFile::Header* header = const_cast<DexFile::Header*>(&dex_file->GetHeader()); + // Recalculate checksum and overwrite the value in the header. + header->checksum_ = dex_file->CalculateChecksum(); + } + } + + // Print signatures of APIs which have been grey-/blacklisted. + bool print_hidden_api_; + + // Paths to DEX files which should be processed. + std::vector<std::string> dex_paths_; + + // Paths to text files which contain the lists of API members. + std::string light_greylist_path_; + std::string dark_greylist_path_; + std::string blacklist_path_; + + // Opened DEX files. Note that these are opened as `const` but eventually will be written into. + std::vector<std::unique_ptr<const DexFile>> dex_files_; + + // Signatures of DEX members loaded from `light_greylist_path_`, `dark_greylist_path_`, + // `blacklist_path_`. + std::unordered_set<std::string> light_greylist_; + std::unordered_set<std::string> dark_greylist_; + std::unordered_set<std::string> blacklist_; +}; + +} // namespace art + +int main(int argc, char** argv) { + art::HiddenApi hiddenapi; + + // Parse arguments. Argument mistakes will lead to exit(EXIT_FAILURE) in UsageError. + hiddenapi.ParseArgs(argc, argv); + return hiddenapi.ProcessDexFiles() ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tools/hiddenapi/hiddenapi_test.cc b/tools/hiddenapi/hiddenapi_test.cc new file mode 100644 index 0000000000..1f37f40f6e --- /dev/null +++ b/tools/hiddenapi/hiddenapi_test.cc @@ -0,0 +1,600 @@ +/* + * 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. + */ + +#include <fstream> + +#include "base/unix_file/fd_file.h" +#include "common_runtime_test.h" +#include "dex/art_dex_file_loader.h" +#include "dex/dex_file-inl.h" +#include "exec_utils.h" +#include "zip_archive.h" + +namespace art { + +class HiddenApiTest : public CommonRuntimeTest { + protected: + std::string GetHiddenApiCmd() { + std::string file_path = GetTestAndroidRoot(); + file_path += "/bin/hiddenapi"; + if (kIsDebugBuild) { + file_path += "d"; + } + if (!OS::FileExists(file_path.c_str())) { + LOG(FATAL) << "Could not find binary " << file_path; + UNREACHABLE(); + } + return file_path; + } + + std::unique_ptr<const DexFile> RunHiddenApi(const ScratchFile& light_greylist, + const ScratchFile& dark_greylist, + const ScratchFile& blacklist, + const std::vector<std::string>& extra_args, + ScratchFile* out_dex) { + std::string error; + ZipArchive* jar = ZipArchive::Open(GetTestDexFileName("HiddenApi").c_str(), &error); + if (jar == nullptr) { + LOG(FATAL) << "Could not open test file " << GetTestDexFileName("HiddenApi") << ": " << error; + UNREACHABLE(); + } + ZipEntry* jar_classes_dex = jar->Find("classes.dex", &error); + if (jar_classes_dex == nullptr) { + LOG(FATAL) << "Could not find classes.dex in test file " << GetTestDexFileName("HiddenApi") + << ": " << error; + UNREACHABLE(); + } else if (!jar_classes_dex->ExtractToFile(*out_dex->GetFile(), &error)) { + LOG(FATAL) << "Could not extract classes.dex from test file " + << GetTestDexFileName("HiddenApi") << ": " << error; + UNREACHABLE(); + } + + std::vector<std::string> argv_str; + argv_str.push_back(GetHiddenApiCmd()); + argv_str.insert(argv_str.end(), extra_args.begin(), extra_args.end()); + argv_str.push_back("--dex=" + out_dex->GetFilename()); + argv_str.push_back("--light-greylist=" + light_greylist.GetFilename()); + argv_str.push_back("--dark-greylist=" + dark_greylist.GetFilename()); + argv_str.push_back("--blacklist=" + blacklist.GetFilename()); + int return_code = ExecAndReturnCode(argv_str, &error); + if (return_code != 0) { + LOG(FATAL) << "HiddenApi binary exited with unexpected return code " << return_code; + } + return OpenDex(*out_dex); + } + + std::unique_ptr<const DexFile> OpenDex(const ScratchFile& file) { + ArtDexFileLoader dex_loader; + std::string error_msg; + + File fd(file.GetFilename(), O_RDONLY, /* check_usage */ false); + if (fd.Fd() == -1) { + LOG(FATAL) << "Unable to open file '" << file.GetFilename() << "': " << strerror(errno); + UNREACHABLE(); + } + + std::unique_ptr<const DexFile> dex_file(dex_loader.OpenDex( + fd.Release(), /* location */ file.GetFilename(), /* verify */ false, + /* verify_checksum */ true, /* mmap_shared */ false, &error_msg)); + if (dex_file.get() == nullptr) { + LOG(FATAL) << "Open failed for '" << file.GetFilename() << "' " << error_msg; + UNREACHABLE(); + } else if (!dex_file->IsStandardDexFile()) { + LOG(FATAL) << "Expected a standard dex file '" << file.GetFilename() << "'"; + UNREACHABLE(); + } + + return dex_file; + } + + std::ofstream OpenStream(const ScratchFile& file) { + std::ofstream ofs(file.GetFilename(), std::ofstream::out); + if (ofs.fail()) { + LOG(FATAL) << "Open failed for '" << file.GetFilename() << "' " << strerror(errno); + UNREACHABLE(); + } + return ofs; + } + + const DexFile::ClassDef& FindClass(const char* desc, const DexFile& dex_file) { + for (uint32_t i = 0; i < dex_file.NumClassDefs(); ++i) { + const DexFile::ClassDef& class_def = dex_file.GetClassDef(i); + if (strcmp(desc, dex_file.GetClassDescriptor(class_def)) == 0) { + return class_def; + } + } + LOG(FATAL) << "Could not find class " << desc; + UNREACHABLE(); + } + + DexHiddenAccessFlags::ApiList GetFieldHiddenFlags(const char* name, + uint32_t expected_visibility, + const DexFile::ClassDef& class_def, + const DexFile& dex_file) { + const uint8_t* class_data = dex_file.GetClassData(class_def); + if (class_data == nullptr) { + LOG(FATAL) << "Class " << dex_file.GetClassDescriptor(class_def) << " has no data"; + UNREACHABLE(); + } + + for (ClassDataItemIterator it(dex_file, class_data); it.HasNext(); it.Next()) { + if (it.IsAtMethod()) { + break; + } + const DexFile::FieldId& fid = dex_file.GetFieldId(it.GetMemberIndex()); + if (strcmp(name, dex_file.GetFieldName(fid)) == 0) { + uint32_t actual_visibility = it.GetFieldAccessFlags() & kAccVisibilityFlags; + if (actual_visibility != expected_visibility) { + LOG(FATAL) << "Field " << name << " in class " << dex_file.GetClassDescriptor(class_def) + << " does not have the expected visibility flags (" << expected_visibility + << " != " << actual_visibility << ")"; + UNREACHABLE(); + } + return it.DecodeHiddenAccessFlags(); + } + } + + LOG(FATAL) << "Could not find field " << name << " in class " + << dex_file.GetClassDescriptor(class_def); + UNREACHABLE(); + } + + DexHiddenAccessFlags::ApiList GetMethodHiddenFlags(const char* name, + uint32_t expected_visibility, + bool expected_native, + const DexFile::ClassDef& class_def, + const DexFile& dex_file) { + const uint8_t* class_data = dex_file.GetClassData(class_def); + if (class_data == nullptr) { + LOG(FATAL) << "Class " << dex_file.GetClassDescriptor(class_def) << " has no data"; + UNREACHABLE(); + } + + for (ClassDataItemIterator it(dex_file, class_data); it.HasNext(); it.Next()) { + if (!it.IsAtMethod()) { + continue; + } + const DexFile::MethodId& mid = dex_file.GetMethodId(it.GetMemberIndex()); + if (strcmp(name, dex_file.GetMethodName(mid)) == 0) { + if (expected_native != it.MemberIsNative()) { + LOG(FATAL) << "Expected native=" << expected_native << " for method " << name + << " in class " << dex_file.GetClassDescriptor(class_def); + UNREACHABLE(); + } + uint32_t actual_visibility = it.GetMethodAccessFlags() & kAccVisibilityFlags; + if (actual_visibility != expected_visibility) { + LOG(FATAL) << "Method " << name << " in class " << dex_file.GetClassDescriptor(class_def) + << " does not have the expected visibility flags (" << expected_visibility + << " != " << actual_visibility << ")"; + UNREACHABLE(); + } + return it.DecodeHiddenAccessFlags(); + } + } + + LOG(FATAL) << "Could not find method " << name << " in class " + << dex_file.GetClassDescriptor(class_def); + UNREACHABLE(); + } + + DexHiddenAccessFlags::ApiList GetIFieldHiddenFlags(const DexFile& dex_file) { + return GetFieldHiddenFlags("ifield", kAccPublic, FindClass("LMain;", dex_file), dex_file); + } + + DexHiddenAccessFlags::ApiList GetSFieldHiddenFlags(const DexFile& dex_file) { + return GetFieldHiddenFlags("sfield", kAccPrivate, FindClass("LMain;", dex_file), dex_file); + } + + DexHiddenAccessFlags::ApiList GetIMethodHiddenFlags(const DexFile& dex_file) { + return GetMethodHiddenFlags( + "imethod", 0, /* native */ false, FindClass("LMain;", dex_file), dex_file); + } + + DexHiddenAccessFlags::ApiList GetSMethodHiddenFlags(const DexFile& dex_file) { + return GetMethodHiddenFlags( + "smethod", kAccPublic, /* native */ false, FindClass("LMain;", dex_file), dex_file); + } + + DexHiddenAccessFlags::ApiList GetINMethodHiddenFlags(const DexFile& dex_file) { + return GetMethodHiddenFlags( + "inmethod", kAccPublic, /* native */ true, FindClass("LMain;", dex_file), dex_file); + } + + DexHiddenAccessFlags::ApiList GetSNMethodHiddenFlags(const DexFile& dex_file) { + return GetMethodHiddenFlags( + "snmethod", kAccProtected, /* native */ true, FindClass("LMain;", dex_file), dex_file); + } +}; + +TEST_F(HiddenApiTest, InstanceFieldNoMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kWhitelist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldLightGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:I" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kLightGreylist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldDarkGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:I" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldBlacklistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:I" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldTwoListsMatch1) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:I" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:I" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldTwoListsMatch2) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:I" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:I" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceFieldTwoListsMatch3) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->ifield:I" << std::endl; + OpenStream(dark_greylist) << "LMain;->ifield:I" << std::endl; + OpenStream(blacklist) << "LMain;->ifield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetIFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldNoMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kWhitelist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldLightGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kLightGreylist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldDarkGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldBlacklistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldTwoListsMatch1) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:LBadType1;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldTwoListsMatch2) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:LBadType2;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticFieldTwoListsMatch3) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + OpenStream(dark_greylist) << "LMain;->sfield:Ljava/lang/Object;" << std::endl; + OpenStream(blacklist) << "LMain;->sfield:LBadType3;" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetSFieldHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodNoMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kWhitelist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodLightGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(J)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kLightGreylist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodDarkGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(J)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodBlacklistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(J)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodTwoListsMatch1) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(J)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(J)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodTwoListsMatch2) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(J)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(J)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceMethodTwoListsMatch3) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->imethod(J)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->imethod(J)V" << std::endl; + OpenStream(blacklist) << "LMain;->imethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetIMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodNoMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kWhitelist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodLightGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kLightGreylist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodDarkGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodBlacklistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodTwoListsMatch1) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodTwoListsMatch2) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticMethodTwoListsMatch3) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->smethod(Ljava/lang/Object;)V" << std::endl; + OpenStream(blacklist) << "LMain;->smethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetSMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodNoMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kWhitelist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodLightGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(C)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kLightGreylist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodDarkGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(C)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodBlacklistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(C)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodTwoListsMatch1) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(C)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(C)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodTwoListsMatch2) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(C)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(C)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, InstanceNativeMethodTwoListsMatch3) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->inmethod(C)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->inmethod(C)V" << std::endl; + OpenStream(blacklist) << "LMain;->inmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetINMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodNoMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kWhitelist, GetSNMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodLightGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kLightGreylist, GetSNMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodDarkGreylistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetSNMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodBlacklistMatch) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSNMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodTwoListsMatch1) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(LBadType1;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSNMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodTwoListsMatch2) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(LBadType2;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kBlacklist, GetSNMethodHiddenFlags(*dex_file)); +} + +TEST_F(HiddenApiTest, StaticNativeMethodTwoListsMatch3) { + ScratchFile dex, light_greylist, dark_greylist, blacklist; + OpenStream(light_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + OpenStream(dark_greylist) << "LMain;->snmethod(Ljava/lang/Integer;)V" << std::endl; + OpenStream(blacklist) << "LMain;->snmethod(LBadType3;)V" << std::endl; + auto dex_file = RunHiddenApi(light_greylist, dark_greylist, blacklist, {}, &dex); + ASSERT_EQ(DexHiddenAccessFlags::kDarkGreylist, GetSNMethodHiddenFlags(*dex_file)); +} + +} // namespace art |