| /* |
| * 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 "hidden_api_finder.h" |
| |
| #include "dex/class_accessor-inl.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 "hidden_api.h" |
| #include "resolver.h" |
| #include "veridex.h" |
| |
| #include <iostream> |
| |
| namespace art { |
| |
| void HiddenApiFinder::CheckMethod(uint32_t method_id, |
| VeridexResolver* resolver, |
| MethodReference ref) { |
| // Note: we always query whether a method is in boot, as the app |
| // might define blacklisted APIs (which won't be used at runtime). |
| const auto& name = HiddenApi::GetApiMethodName(resolver->GetDexFile(), method_id); |
| method_locations_[name].push_back(ref); |
| } |
| |
| void HiddenApiFinder::CheckField(uint32_t field_id, |
| VeridexResolver* resolver, |
| MethodReference ref) { |
| // Note: we always query whether a field is in a boot, as the app |
| // might define blacklisted APIs (which won't be used at runtime). |
| const auto& name = HiddenApi::GetApiFieldName(resolver->GetDexFile(), field_id); |
| field_locations_[name].push_back(ref); |
| } |
| |
| void HiddenApiFinder::CollectAccesses(VeridexResolver* resolver, |
| const ClassFilter& class_filter) { |
| const DexFile& dex_file = resolver->GetDexFile(); |
| // Look at all types referenced in this dex file. Any of these |
| // types can lead to being used through reflection. |
| for (uint32_t i = 0; i < dex_file.NumTypeIds(); ++i) { |
| std::string name(dex_file.StringByTypeIdx(dex::TypeIndex(i))); |
| classes_.insert(name); |
| } |
| // Note: we collect strings constants only referenced in code items as the string table |
| // contains other kind of strings (eg types). |
| for (ClassAccessor accessor : dex_file.GetClasses()) { |
| if (class_filter.Matches(accessor.GetDescriptor())) { |
| for (const ClassAccessor::Method& method : accessor.GetMethods()) { |
| for (const DexInstructionPcPair& inst : method.GetInstructions()) { |
| switch (inst->Opcode()) { |
| case Instruction::CONST_STRING: { |
| dex::StringIndex string_index(inst->VRegB_21c()); |
| const auto& name = std::string(dex_file.StringDataByIdx(string_index)); |
| // Cheap filtering on the string literal. We know it cannot be a field/method/class |
| // if it contains a space. |
| if (name.find(' ') == std::string::npos) { |
| // Class names at the Java level are of the form x.y.z, but the list encodes |
| // them of the form Lx/y/z;. Inner classes have '$' for both Java level class |
| // names in strings, and hidden API lists. |
| std::string str = HiddenApi::ToInternalName(name); |
| // Note: we can query the lists directly, as HiddenApi added classes that own |
| // private methods and fields in them. |
| // We don't add class names to the `strings_` set as we know method/field names |
| // don't have '.' or '/'. All hidden API class names have a '/'. |
| if (hidden_api_.IsInBoot(str)) { |
| classes_.insert(str); |
| } else if (hidden_api_.IsInBoot(name)) { |
| // Could be something passed to JNI. |
| classes_.insert(name); |
| } else { |
| // We only keep track of the location for strings, as these will be the |
| // field/method names the user is interested in. |
| strings_.insert(name); |
| reflection_locations_[name].push_back(method.GetReference()); |
| } |
| } |
| break; |
| } |
| case Instruction::INVOKE_DIRECT: |
| case Instruction::INVOKE_INTERFACE: |
| case Instruction::INVOKE_STATIC: |
| case Instruction::INVOKE_SUPER: |
| case Instruction::INVOKE_VIRTUAL: { |
| CheckMethod(inst->VRegB_35c(), resolver, method.GetReference()); |
| 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: { |
| CheckMethod(inst->VRegB_3rc(), resolver, method.GetReference()); |
| 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: { |
| CheckField(inst->VRegC_22c(), resolver, method.GetReference()); |
| 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: { |
| CheckField(inst->VRegC_22c(), resolver, method.GetReference()); |
| 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: { |
| CheckField(inst->VRegB_21c(), resolver, method.GetReference()); |
| 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: { |
| CheckField(inst->VRegB_21c(), resolver, method.GetReference()); |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| void HiddenApiFinder::Run(const std::vector<std::unique_ptr<VeridexResolver>>& resolvers, |
| const ClassFilter& class_filter) { |
| for (const std::unique_ptr<VeridexResolver>& resolver : resolvers) { |
| CollectAccesses(resolver.get(), class_filter); |
| } |
| } |
| |
| void HiddenApiFinder::Dump(std::ostream& os, |
| HiddenApiStats* stats, |
| bool dump_reflection) { |
| // Dump methods from hidden APIs linked against. |
| for (const std::pair<const std::string, |
| std::vector<MethodReference>>& pair : method_locations_) { |
| const auto& name = pair.first; |
| if (hidden_api_.GetSignatureSource(name) != SignatureSource::APP && |
| hidden_api_.ShouldReport(name)) { |
| stats->linking_count++; |
| hiddenapi::ApiList api_list = hidden_api_.GetApiList(pair.first); |
| stats->api_counts[api_list.GetIntValue()]++; |
| os << "#" << ++stats->count << ": Linking " << api_list << " " << pair.first << " use(s):"; |
| os << std::endl; |
| HiddenApiFinder::DumpReferences(os, pair.second); |
| os << std::endl; |
| } |
| } |
| |
| // Dump fields from hidden APIs linked against. |
| for (const std::pair<const std::string, |
| std::vector<MethodReference>>& pair : field_locations_) { |
| const auto& name = pair.first; |
| if (hidden_api_.GetSignatureSource(name) != SignatureSource::APP && |
| hidden_api_.ShouldReport(name)) { |
| stats->linking_count++; |
| hiddenapi::ApiList api_list = hidden_api_.GetApiList(pair.first); |
| stats->api_counts[api_list.GetIntValue()]++; |
| // Note: There is a test depending on this output format, |
| // so please be careful when you modify the format. b/123662832 |
| os << "#" << ++stats->count << ": Linking " << api_list << " " << pair.first << " use(s):"; |
| os << std::endl; |
| HiddenApiFinder::DumpReferences(os, pair.second); |
| os << 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; |
| if (hidden_api_.GetSignatureSource(full_name) != SignatureSource::APP && |
| hidden_api_.ShouldReport(full_name)) { |
| hiddenapi::ApiList api_list = hidden_api_.GetApiList(full_name); |
| stats->api_counts[api_list.GetIntValue()]++; |
| stats->reflection_count++; |
| // Note: There is a test depending on this output format, |
| // so please be careful when you modify the format. b/123662832 |
| os << "#" << ++stats->count << ": Reflection " << api_list << " " << full_name |
| << " potential use(s):"; |
| os << std::endl; |
| HiddenApiFinder::DumpReferences(os, reflection_locations_[name]); |
| os << std::endl; |
| } |
| } |
| } |
| } |
| } |
| |
| void HiddenApiFinder::DumpReferences(std::ostream& os, |
| const std::vector<MethodReference>& references) { |
| static const char* kPrefix = " "; |
| |
| // Count number of occurrences of each reference, to make the output clearer. |
| std::map<std::string, size_t> counts; |
| for (const MethodReference& ref : references) { |
| std::string ref_string = HiddenApi::GetApiMethodName(ref); |
| if (!counts.count(ref_string)) { |
| counts[ref_string] = 0; |
| } |
| counts[ref_string]++; |
| } |
| |
| for (const std::pair<const std::string, size_t>& pair : counts) { |
| os << kPrefix << pair.first; |
| if (pair.second > 1) { |
| os << " (" << pair.second << " occurrences)"; |
| } |
| os << std::endl; |
| } |
| } |
| |
| } // namespace art |