diff options
Diffstat (limited to 'tools')
297 files changed, 19332 insertions, 6605 deletions
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index fecc7b3cbf37..d02fd83df6af 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -1028,7 +1028,6 @@ int doDump(Bundle* bundle) // These permissions are required by services implementing services // the system binds to (IME, Accessibility, PrintServices, etc.) bool hasBindDeviceAdminPermission = false; - bool hasBindInputMethodPermission = false; bool hasBindAccessibilityServicePermission = false; bool hasBindPrintServicePermission = false; bool hasBindNfcServicePermission = false; @@ -1757,7 +1756,6 @@ int doDump(Bundle* bundle) hasMetaHostPaymentCategory = false; hasMetaOffHostPaymentCategory = false; hasBindDeviceAdminPermission = false; - hasBindInputMethodPermission = false; hasBindAccessibilityServicePermission = false; hasBindPrintServicePermission = false; hasBindNfcServicePermission = false; @@ -1871,9 +1869,7 @@ int doDump(Bundle* bundle) String8 permission = AaptXml::getAttribute(tree, PERMISSION_ATTR, &error); if (error == "") { - if (permission == "android.permission.BIND_INPUT_METHOD") { - hasBindInputMethodPermission = true; - } else if (permission == + if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") { hasBindAccessibilityServicePermission = true; } else if (permission == diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index b9de11b0026b..4e597fb3b30a 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -2970,14 +2970,6 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& } e->setNameIndex(keyStrings.add(e->getName(), true)); - // If this entry has no values for other configs, - // and is the default config, then it is special. Otherwise - // we want to add it with the config info. - ConfigDescription* valueConfig = NULL; - if (N != 1 || config == nullConfig) { - valueConfig = &config; - } - status_t err = e->prepareFlatten(&valueStrings, this, &configTypeName, &config); if (err != NO_ERROR) { @@ -3751,15 +3743,15 @@ ssize_t ResourceTable::Entry::flatten(Bundle* /* bundle */, const sp<AaptFile>& size_t amt = 0; ResTable_entry header; memset(&header, 0, sizeof(header)); - header.size = htods(sizeof(header)); + header.full.size = htods(sizeof(header)); const type ty = mType; if (ty == TYPE_BAG) { - header.flags |= htods(header.FLAG_COMPLEX); + header.full.flags |= htods(header.FLAG_COMPLEX); } if (isPublic) { - header.flags |= htods(header.FLAG_PUBLIC); + header.full.flags |= htods(header.FLAG_PUBLIC); } - header.key.index = htodl(mNameIndex); + header.full.key.index = htodl(mNameIndex); if (ty != TYPE_BAG) { status_t err = data->writeData(&header, sizeof(header)); if (err != NO_ERROR) { diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h index a146466402f6..e2c161482857 100644 --- a/tools/aapt/SdkConstants.h +++ b/tools/aapt/SdkConstants.h @@ -49,6 +49,7 @@ enum { SDK_S = 31, SDK_S_V2 = 32, SDK_TIRAMISU = 33, + SDK_UPSIDE_DOWN_CAKE = 34, SDK_CUR_DEVELOPMENT = 10000, }; diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index bfb32854a374..0d6dc3522d24 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -24,6 +24,7 @@ package { } toolSources = [ + "cmd/ApkInfo.cpp", "cmd/Command.cpp", "cmd/Compile.cpp", "cmd/Convert.cpp", @@ -36,6 +37,7 @@ toolSources = [ cc_defaults { name: "aapt2_defaults", + cpp_std: "gnu++2b", cflags: [ "-Wall", "-Werror", @@ -48,6 +50,7 @@ cc_defaults { ], target: { windows: { + compile_multilib: "64", enabled: true, cflags: ["-Wno-maybe-uninitialized"], ldflags: ["-static"], @@ -103,6 +106,7 @@ cc_library_host_static { "format/Container.cpp", "format/binary/BinaryResourceParser.cpp", "format/binary/ResChunkPullParser.cpp", + "format/binary/ResEntryWriter.cpp", "format/binary/TableFlattener.cpp", "format/binary/XmlFlattener.cpp", "format/proto/ProtoDeserialize.cpp", @@ -128,14 +132,13 @@ cc_library_host_static { "optimize/MultiApkGenerator.cpp", "optimize/ResourceDeduper.cpp", "optimize/ResourceFilter.cpp", - "optimize/ResourcePathShortener.cpp", + "optimize/Obfuscator.cpp", "optimize/VersionCollapser.cpp", "process/SymbolTable.cpp", "split/TableSplitter.cpp", "text/Printer.cpp", "text/Unicode.cpp", "text/Utf8Iterator.cpp", - "util/BigBuffer.cpp", "util/Files.cpp", "util/Util.cpp", "Debug.cpp", @@ -152,14 +155,15 @@ cc_library_host_static { "ResourceUtils.cpp", "ResourceValues.cpp", "SdkConstants.cpp", - "StringPool.cpp", "trace/TraceBuffer.cpp", "xml/XmlActionExecutor.cpp", "xml/XmlDom.cpp", "xml/XmlPullParser.cpp", "xml/XmlUtil.cpp", + "ApkInfo.proto", "Configuration.proto", "Resources.proto", + "ResourceMetadata.proto", "ResourcesInternal.proto", "ValueTransformer.cpp", ], @@ -190,6 +194,7 @@ cc_test_host { "integration-tests/CompileTest/**/*", "integration-tests/CommandTests/**/*", "integration-tests/ConvertTest/**/*", + "integration-tests/DumpTest/**/*", ], } @@ -216,6 +221,7 @@ genrule { srcs: [ "Configuration.proto", "ResourcesInternal.proto", + "ResourceMetadata.proto", "Resources.proto", ], out: ["aapt2-protos.zip"], diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 7b94e718fd0e..34a1b112d880 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -15,7 +15,7 @@ $(aapt2_results): .KATI_IMPLICIT_OUTPUTS := $(aapt2_results)-nocache $(aapt2_results): $(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests -$(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests --gtest_output=xml:$@ > /dev/null 2>&1 -$(call declare-0p-target,$(aapt2_results)) +$(call declare-1p-target,$(aapt2_results)) aapt2_results := diff --git a/tools/aapt2/ApkInfo.proto b/tools/aapt2/ApkInfo.proto new file mode 100644 index 000000000000..b5ff71fa5935 --- /dev/null +++ b/tools/aapt2/ApkInfo.proto @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2022 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. + */ + +syntax = "proto3"; + +import "frameworks/base/tools/aapt2/Resources.proto"; + +package aapt.pb; + +option java_package = "com.android.aapt"; + +// Top level message representing data extracted from the APK for 'apkinfo' +// command. +message ApkInfo { + message XmlFile { + string path = 1; + XmlNode root = 2; + } + + Badging badging = 1; + ResourceTable resource_table = 2; + repeated XmlFile xml_files = 3; +} + +// Data extracted from the manifest of the APK. +message Badging { + PackageInfo package = 1; + Application application = 2; + UsesSdk uses_sdk = 3; + // Previously: UsesConfiguration uses_configuration = 4; + reserved 4; + SupportsScreen supports_screen = 5; + SupportsInput supports_input = 6; + LaunchableActivity launchable_activity = 7; + LeanbackLaunchableActivity leanback_launchable_activity = 8; + StaticLibrary static_library = 9; + SdkLibrary sdk_library = 10; + Overlay overlay = 11; + PackageVerifier package_verifier = 12; + CompatibleScreens compatible_screens = 13; + Architectures architectures = 14; + SupportsGlTexture supports_gl_texture = 15; + Components components = 16; + + repeated string locales = 17; + repeated int32 densities = 18; + + repeated UsesPackage uses_packages = 51; + repeated UsesConfiguration uses_configurations = 52; + repeated FeatureGroup feature_groups = 53; + repeated UsesPermission uses_permissions = 54; + repeated Permission permissions = 55; + repeated UsesLibrary uses_libraries = 56; + repeated UsesStaticLibrary uses_static_libraries = 57; + repeated UsesSdkLibrary uses_sdk_libraries = 58; + repeated UsesNativeLibrary uses_native_libraries = 59; + + repeated Metadata metadata = 62; + repeated Property properties = 63; +} + +// Information extracted about package from <manifest> and +// <original-package> tags. +message PackageInfo { + enum InstallLocation { + DEFAULT_INSTALL_LOCATION = 0; + AUTO = 1; + INTERNAL_ONLY = 2; + PREFER_EXTERNAL = 3; + } + + string package = 1; + int32 version_code = 2; + string version_name = 3; + + string split = 4; + + string platform_version_name = 5; + string platform_version_code = 6; + + int32 compile_sdk_version = 7; + string compile_sdk_version_codename = 8; + + InstallLocation install_location = 9; + + string original_package = 10; +} + +// Information extracted from <application> element. +message Application { + string label = 1; + string icon = 2; + string banner = 3; + + bool test_only = 4; + bool game = 5; + bool debuggable = 6; + + map<string, string> locale_labels = 8; + map<int32, string> density_icons = 9; +} + +// Components defined in the APK. +message Components { + bool main = 1; + bool other_activities = 2; + bool other_receivers = 3; + bool other_services = 4; + + repeated string provided_components = 5; +} + +// Application's min and target SDKs. +message UsesSdk { + oneof min_sdk { + int32 min_sdk_version = 2; + string min_sdk_version_name = 3; + } + int32 max_sdk_version = 4; + oneof target_sdk { + int32 target_sdk_version = 5; + string target_sdk_version_name = 6; + } +} + +message UsesConfiguration { + int32 req_touch_screen = 1; + int32 req_keyboard_type = 2; + int32 req_hard_keyboard = 3; + int32 req_navigation = 4; + int32 req_five_way_nav = 5; +} + +// Screens supported by this application. +message SupportsScreen { + enum ScreenType { + UNSPECIFIED_SCREEN_TYPE = 0; + SMALL = 1; + NORMAL = 2; + LARGE = 3; + XLARGE = 4; + } + repeated ScreenType screens = 1; + bool supports_any_densities = 2; + int32 requires_smallest_width_dp = 3; + int32 compatible_width_limit_dp = 4; + int32 largest_width_limit_dp = 5; +} + +// Inputs supported by this application. +message SupportsInput { + repeated string inputs = 1; +} + +// Information about used features which is extracted from <uses-permission> +// elements or implied from permissions. +message Feature { + message ImpliedData { + bool from_sdk_23_permission = 1; + repeated string reasons = 2; + } + + string name = 1; + bool required = 2; + int32 version = 3; + + ImpliedData implied_data = 4; +} + +message FeatureGroup { + string label = 1; + int32 open_gles_version = 2; + repeated Feature features = 3; +} + +// Information about permission requested by the application. +message UsesPermission { + message PermissionFlags { + bool never_for_location = 1; + } + + string name = 1; + int32 max_sdk_version = 2; + bool required = 3; + bool implied = 4; + bool sdk23_and_above = 5; + + repeated string required_features = 6; + repeated string required_not_features = 7; + + PermissionFlags permission_flags = 8; +} + +// Permission defined by the application. +message Permission { + string name = 1; +} + +// Data extracted about launchable activity. Launchable activity is an entry +// point on phone and tablet devices. +message LaunchableActivity { + string name = 1; + string icon = 2; + string label = 3; +} + +// Data extracted about leanback launchable activity. Leanback launchable +// activity is an entry point on TV devices. +message LeanbackLaunchableActivity { + string name = 1; + string icon = 2; + string label = 3; + string banner = 4; +} + +// Library used by the application. +message UsesLibrary { + string name = 1; + bool required = 2; +} + +// Static library this APK declares. +message StaticLibrary { + string name = 1; + int32 version = 2; + int32 version_major = 3; +} + +// Static library used by the application. +message UsesStaticLibrary { + string name = 1; + int32 version = 2; + int32 version_major = 3; + repeated string certificates = 4; +} + +// SDK library this APK declares. +message SdkLibrary { + string name = 1; + int32 version_major = 2; +} + +// SDK library used by the application. +message UsesSdkLibrary { + string name = 1; + int32 version_major = 2; + repeated string certificates = 3; +} + +// Native library used by the application. +message UsesNativeLibrary { + string name = 1; + bool required = 2; +} + +// Information extracted from <meta-data> elements defined across +// AndroidManifest.xml. +message Metadata { + string name = 1; + oneof value { + string value_string = 2; + int32 value_int = 3; + } + oneof resource { + string resource_string = 4; + int32 resource_int = 5; + } +} + +// Information about overlay that is declared in the APK. +message Overlay { + string target_package = 1; + int32 priority = 2; + bool static = 3; + string required_property_name = 4; + string required_property_value = 5; +} + +// Data extracted from <package-verifier> element. +message PackageVerifier { + string name = 1; + string public_key = 2; +} + +// External packages used by the application +message UsesPackage { + string name = 1; + string package_type = 2; + int32 version = 3; + int32 version_major = 4; + repeated string certificates = 5; +} + +// Open GL textures format supported by the current application. +message SupportsGlTexture { + repeated string name = 1; +} + +// Screens compatible with the application. +message CompatibleScreens { + message Screen { + int32 size = 1; + int32 density = 2; + } + + repeated Screen screens = 1; +} + +// Architectures supported by the application. +message Architectures { + repeated string architectures = 1; + repeated string alt_architectures = 2; +} + +// Information extracted from <property> elements defined across +// AndroidManifest.xml. +message Property { + string name = 1; + oneof value { + string value_string = 2; + int32 value_int = 3; + } + oneof resource { + string resource_string = 4; + int32 resource_int = 5; + } +}
\ No newline at end of file diff --git a/tools/aapt2/Configuration.proto b/tools/aapt2/Configuration.proto index 8a4644c9a219..48838445f8c1 100644 --- a/tools/aapt2/Configuration.proto +++ b/tools/aapt2/Configuration.proto @@ -120,6 +120,13 @@ message Configuration { NAVIGATION_WHEEL = 4; } + enum GrammaticalGender { + GRAM_GENDER_USET = 0; + GRAM_GENDER_NEUTER = 1; + GRAM_GENDER_FEMININE = 2; + GRAM_GENDER_MASCULINE = 3; + } + // // Axis/dimensions that are understood by the runtime. // @@ -198,6 +205,9 @@ message Configuration { // The minimum SDK version of the device. uint32 sdk_version = 24; + // Grammatical gender. + GrammaticalGender grammatical_gender = 26; + // // Build-time only dimensions. // diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index f47d66ea5e87..df878899fa28 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -17,6 +17,7 @@ #include "Debug.h" #include <androidfw/TypeWrappers.h> +#include <androidfw/Util.h> #include <format/binary/ResChunkPullParser.h> #include <algorithm> @@ -32,6 +33,7 @@ #include "ValueVisitor.h" #include "android-base/logging.h" #include "android-base/stringprintf.h" +#include "androidfw/ResourceTypes.h" #include "idmap2/Policies.h" #include "text/Printer.h" #include "util/Util.h" @@ -273,7 +275,7 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& printer->Indent(); for (const auto& type : package.types) { printer->Print("type "); - printer->Print(to_string(type.type)); + printer->Print(type.named_type.to_string()); if (type.id) { printer->Print(StringPrintf(" id=%02x", type.id.value())); } @@ -287,7 +289,7 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& printer->Print(" "); // Write the name without the package (this is obvious and too verbose). - printer->Print(to_string(type.type)); + printer->Print(type.named_type.to_string()); printer->Print("/"); printer->Print(entry.name); @@ -514,7 +516,8 @@ class XmlPrinter : public xml::ConstVisitor { } void Visit(const xml::Text* text) override { - printer_->Println(StringPrintf("T: '%s'", text->text.c_str())); + printer_->Println( + StringPrintf("T: '%s'", android::ResTable::normalizeForOutput(text->text.c_str()).c_str())); } private: @@ -547,7 +550,7 @@ void Debug::DumpOverlayable(const ResourceTable& table, text::Printer* printer) const auto policy_subsection = StringPrintf(R"(policies="%s")", android::idmap2::policy::PoliciesToDebugString(overlayable_item.policies).c_str()); const auto value = - StringPrintf("%s/%s", to_string(type->type).data(), entry->name.c_str()); + StringPrintf("%s/%s", type->named_type.to_string().data(), entry->name.c_str()); items.push_back(DumpOverlayableEntry{overlayable_section, policy_subsection, value}); } } @@ -592,12 +595,12 @@ using namespace android; class ChunkPrinter { public: - ChunkPrinter(const void* data, size_t len, Printer* printer, IDiagnostics* diag) + ChunkPrinter(const void* data, size_t len, Printer* printer, android::IDiagnostics* diag) : data_(data), data_len_(len), printer_(printer), diag_(diag) { } void PrintChunkHeader(const ResChunk_header* chunk) { - switch (util::DeviceToHost16(chunk->type)) { + switch (android::util::DeviceToHost16(chunk->type)) { case RES_STRING_POOL_TYPE: printer_->Print("[RES_STRING_POOL_TYPE]"); break; @@ -620,13 +623,14 @@ class ChunkPrinter { break; } - printer_->Print(StringPrintf(" chunkSize: %u", util::DeviceToHost32(chunk->size))); - printer_->Print(StringPrintf(" headerSize: %u", util::DeviceToHost32(chunk->headerSize))); + printer_->Print(StringPrintf(" chunkSize: %u", android::util::DeviceToHost32(chunk->size))); + printer_->Print( + StringPrintf(" headerSize: %u", android::util::DeviceToHost32(chunk->headerSize))); } bool PrintTable(const ResTable_header* chunk) { printer_->Print( - StringPrintf(" Package count: %u\n", util::DeviceToHost32(chunk->packageCount))); + StringPrintf(" Package count: %u\n", android::util::DeviceToHost32(chunk->packageCount))); // Print the chunks contained within the table printer_->Indent(); @@ -639,9 +643,10 @@ class ChunkPrinter { void PrintResValue(const Res_value* value, const ConfigDescription& config, const ResourceType* type) { printer_->Print("[Res_value]"); - printer_->Print(StringPrintf(" size: %u", util::DeviceToHost32(value->size))); - printer_->Print(StringPrintf(" dataType: 0x%02x", util::DeviceToHost32(value->dataType))); - printer_->Print(StringPrintf(" data: 0x%08x", util::DeviceToHost32(value->data))); + printer_->Print(StringPrintf(" size: %u", android::util::DeviceToHost32(value->size))); + printer_->Print( + StringPrintf(" dataType: 0x%02x", android::util::DeviceToHost32(value->dataType))); + printer_->Print(StringPrintf(" data: 0x%08x", android::util::DeviceToHost32(value->data))); if (type) { auto item = @@ -655,19 +660,23 @@ class ChunkPrinter { } bool PrintTableType(const ResTable_type* chunk) { - printer_->Print(StringPrintf(" id: 0x%02x", util::DeviceToHost32(chunk->id))); + printer_->Print(StringPrintf(" id: 0x%02x", android::util::DeviceToHost32(chunk->id))); printer_->Print(StringPrintf( - " name: %s", util::GetString(type_pool_, util::DeviceToHost32(chunk->id) - 1).c_str())); - printer_->Print(StringPrintf(" flags: 0x%02x", util::DeviceToHost32(chunk->flags))); - printer_->Print(StringPrintf(" entryCount: %u", util::DeviceToHost32(chunk->entryCount))); - printer_->Print(StringPrintf(" entryStart: %u", util::DeviceToHost32(chunk->entriesStart))); + " name: %s", + android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1) + .c_str())); + printer_->Print(StringPrintf(" flags: 0x%02x", android::util::DeviceToHost32(chunk->flags))); + printer_->Print( + StringPrintf(" entryCount: %u", android::util::DeviceToHost32(chunk->entryCount))); + printer_->Print( + StringPrintf(" entryStart: %u", android::util::DeviceToHost32(chunk->entriesStart))); ConfigDescription config; config.copyFromDtoH(chunk->config); printer_->Print(StringPrintf(" config: %s\n", config.to_string().c_str())); - const ResourceType* type = - ParseResourceType(util::GetString(type_pool_, util::DeviceToHost32(chunk->id) - 1)); + const ResourceType* type = ParseResourceType( + android::util::GetString(type_pool_, android::util::DeviceToHost32(chunk->id) - 1)); printer_->Indent(); @@ -678,40 +687,46 @@ class ChunkPrinter { continue; } - printer_->Print((entry->flags & ResTable_entry::FLAG_COMPLEX) ? "[ResTable_map_entry]" - : "[ResTable_entry]"); + if (entry->is_complex()) { + printer_->Print("[ResTable_map_entry]"); + } else if (entry->is_compact()) { + printer_->Print("[ResTable_entry_compact]"); + } else { + printer_->Print("[ResTable_entry]"); + } + printer_->Print(StringPrintf(" id: 0x%04x", it.index())); printer_->Print(StringPrintf( - " name: %s", util::GetString(key_pool_, util::DeviceToHost32(entry->key.index)).c_str())); - printer_->Print(StringPrintf(" keyIndex: %u", util::DeviceToHost32(entry->key.index))); - printer_->Print(StringPrintf(" size: %u", util::DeviceToHost32(entry->size))); - printer_->Print(StringPrintf(" flags: 0x%04x", util::DeviceToHost32(entry->flags))); + " name: %s", android::util::GetString(key_pool_, entry->key()).c_str())); + printer_->Print(StringPrintf(" keyIndex: %u", entry->key())); + printer_->Print(StringPrintf(" size: %zu", entry->size())); + printer_->Print(StringPrintf(" flags: 0x%04x", entry->flags())); printer_->Indent(); - if (entry->flags & ResTable_entry::FLAG_COMPLEX) { - auto map_entry = (const ResTable_map_entry*)entry; - printer_->Print(StringPrintf(" count: 0x%04x", util::DeviceToHost32(map_entry->count))); - printer_->Print( - StringPrintf(" parent: 0x%08x\n", util::DeviceToHost32(map_entry->parent.ident))); + if (auto map_entry = entry->map_entry()) { + uint32_t map_entry_count = android::util::DeviceToHost32(map_entry->count); + printer_->Print(StringPrintf(" count: 0x%04x", map_entry_count)); + printer_->Print(StringPrintf(" parent: 0x%08x\n", + android::util::DeviceToHost32(map_entry->parent.ident))); // Print the name and value mappings - auto maps = - (const ResTable_map*)((const uint8_t*)entry + util::DeviceToHost32(entry->size)); - for (size_t i = 0, count = util::DeviceToHost32(map_entry->count); i < count; i++) { + auto maps = (const ResTable_map*)((const uint8_t*)entry + entry->size()); + for (size_t i = 0; i < map_entry_count; i++) { PrintResValue(&(maps[i].value), config, type); printer_->Print(StringPrintf( " name: %s name-id:%d\n", - util::GetString(key_pool_, util::DeviceToHost32(maps[i].name.ident)).c_str(), - util::DeviceToHost32(maps[i].name.ident))); + android::util::GetString(key_pool_, android::util::DeviceToHost32(maps[i].name.ident)) + .c_str(), + android::util::DeviceToHost32(maps[i].name.ident))); } } else { printer_->Print("\n"); // Print the value of the entry - auto value = (const Res_value*)((const uint8_t*)entry + util::DeviceToHost32(entry->size)); - PrintResValue(value, config, type); + Res_value value = entry->value(); + PrintResValue(&value, config, type); } printer_->Undent(); @@ -735,33 +750,37 @@ class ChunkPrinter { return; } - pool->setTo(chunk, - util::DeviceToHost32((reinterpret_cast<const ResChunk_header*>(chunk))->size)); + pool->setTo(chunk, android::util::DeviceToHost32( + (reinterpret_cast<const ResChunk_header*>(chunk))->size)); printer_->Print("\n"); for (size_t i = 0; i < pool->size(); i++) { - printer_->Print(StringPrintf("#%zd : %s\n", i, util::GetString(*pool, i).c_str())); + printer_->Print(StringPrintf("#%zd : %s\n", i, android::util::GetString(*pool, i).c_str())); } } bool PrintPackage(const ResTable_package* chunk) { - printer_->Print(StringPrintf(" id: 0x%02x", util::DeviceToHost32(chunk->id))); + printer_->Print(StringPrintf(" id: 0x%02x", android::util::DeviceToHost32(chunk->id))); size_t len = strnlen16((const char16_t*)chunk->name, std::size(chunk->name)); std::u16string package_name(len, u'\0'); package_name.resize(len); for (size_t i = 0; i < len; i++) { - package_name[i] = util::DeviceToHost16(chunk->name[i]); + package_name[i] = android::util::DeviceToHost16(chunk->name[i]); } printer_->Print(StringPrintf("name: %s", String8(package_name.c_str()).c_str())); - printer_->Print(StringPrintf(" typeStrings: %u", util::DeviceToHost32(chunk->typeStrings))); printer_->Print( - StringPrintf(" lastPublicType: %u", util::DeviceToHost32(chunk->lastPublicType))); - printer_->Print(StringPrintf(" keyStrings: %u", util::DeviceToHost32(chunk->keyStrings))); - printer_->Print(StringPrintf(" lastPublicKey: %u", util::DeviceToHost32(chunk->lastPublicKey))); - printer_->Print(StringPrintf(" typeIdOffset: %u\n", util::DeviceToHost32(chunk->typeIdOffset))); + StringPrintf(" typeStrings: %u", android::util::DeviceToHost32(chunk->typeStrings))); + printer_->Print( + StringPrintf(" lastPublicType: %u", android::util::DeviceToHost32(chunk->lastPublicType))); + printer_->Print( + StringPrintf(" keyStrings: %u", android::util::DeviceToHost32(chunk->keyStrings))); + printer_->Print( + StringPrintf(" lastPublicKey: %u", android::util::DeviceToHost32(chunk->lastPublicKey))); + printer_->Print( + StringPrintf(" typeIdOffset: %u\n", android::util::DeviceToHost32(chunk->typeIdOffset))); // Print the chunks contained within the table printer_->Indent(); @@ -776,7 +795,7 @@ class ChunkPrinter { auto chunk = parser.chunk(); PrintChunkHeader(chunk); - switch (util::DeviceToHost16(chunk->type)) { + switch (android::util::DeviceToHost16(chunk->type)) { case RES_STRING_POOL_TYPE: PrintStringPool(reinterpret_cast<const ResStringPool_header*>(chunk)); break; @@ -802,7 +821,7 @@ class ChunkPrinter { } if (parser.event() == ResChunkPullParser::Event::kBadDocument) { - diag_->Error(DiagMessage(source_) << "corrupt resource table: " << parser.error()); + diag_->Error(android::DiagMessage(source_) << "corrupt resource table: " << parser.error()); return false; } @@ -815,11 +834,11 @@ class ChunkPrinter { } private: - const Source source_; + const android::Source source_; const void* data_; const size_t data_len_; Printer* printer_; - IDiagnostics* diag_; + android::IDiagnostics* diag_; // The standard value string pool for resource values. ResStringPool value_pool_; @@ -832,12 +851,13 @@ class ChunkPrinter { // in this table. ResStringPool key_pool_; - StringPool out_pool_; + android::StringPool out_pool_; }; } // namespace -void Debug::DumpChunks(const void* data, size_t len, Printer* printer, IDiagnostics* diag) { +void Debug::DumpChunks(const void* data, size_t len, Printer* printer, + android::IDiagnostics* diag) { ChunkPrinter chunk_printer(data, len, printer, diag); chunk_printer.Print(); } diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h index 4da92044cf2a..8015249e7d36 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -40,7 +40,8 @@ struct Debug { static void DumpXml(const xml::XmlResource& doc, text::Printer* printer); static void DumpResStringPool(const android::ResStringPool* pool, text::Printer* printer); static void DumpOverlayable(const ResourceTable& table, text::Printer* printer); - static void DumpChunks(const void* data, size_t len, text::Printer* printer, IDiagnostics* diag); + static void DumpChunks(const void* data, size_t len, text::Printer* printer, + android::IDiagnostics* diag); }; } // namespace aapt diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h index 30deb5555b21..c89db725e6f2 100644 --- a/tools/aapt2/Diagnostics.h +++ b/tools/aapt2/Diagnostics.h @@ -13,86 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -#ifndef AAPT_DIAGNOSTICS_H -#define AAPT_DIAGNOSTICS_H +#ifndef AAPT_DIAGNOSTICS_H_ +#define AAPT_DIAGNOSTICS_H_ #include <iostream> #include <sstream> #include <string> #include "android-base/macros.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/Source.h" #include "androidfw/StringPiece.h" - -#include "Source.h" #include "util/Util.h" namespace aapt { - -struct DiagMessageActual { - Source source; - std::string message; -}; - -struct DiagMessage { - public: - DiagMessage() = default; - - explicit DiagMessage(const android::StringPiece& src) : source_(src) {} - - explicit DiagMessage(const Source& src) : source_(src) {} - - explicit DiagMessage(size_t line) : source_(Source().WithLine(line)) {} - - template <typename T> - DiagMessage& operator<<(const T& value) { - message_ << value; - return *this; - } - - DiagMessageActual Build() const { - return DiagMessageActual{source_, message_.str()}; - } - - private: - Source source_; - std::stringstream message_; -}; - -template <> -inline DiagMessage& DiagMessage::operator<<(const ::std::u16string& value) { - message_ << android::StringPiece16(value); - return *this; -} - -struct IDiagnostics { - virtual ~IDiagnostics() = default; - - enum class Level { Note, Warn, Error }; - - virtual void Log(Level level, DiagMessageActual& actualMsg) = 0; - - virtual void Error(const DiagMessage& message) { - DiagMessageActual actual = message.Build(); - Log(Level::Error, actual); - } - - virtual void Warn(const DiagMessage& message) { - DiagMessageActual actual = message.Build(); - Log(Level::Warn, actual); - } - - virtual void Note(const DiagMessage& message) { - DiagMessageActual actual = message.Build(); - Log(Level::Note, actual); - } -}; - -class StdErrDiagnostics : public IDiagnostics { +class StdErrDiagnostics : public android::IDiagnostics { public: StdErrDiagnostics() = default; - void Log(Level level, DiagMessageActual& actual_msg) override { + void Log(Level level, android::DiagMessageActual& actual_msg) override { const char* tag; switch (level) { @@ -125,31 +64,6 @@ class StdErrDiagnostics : public IDiagnostics { DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics); }; -class SourcePathDiagnostics : public IDiagnostics { - public: - SourcePathDiagnostics(const Source& src, IDiagnostics* diag) - : source_(src), diag_(diag) {} - - void Log(Level level, DiagMessageActual& actual_msg) override { - actual_msg.source.path = source_.path; - diag_->Log(level, actual_msg); - if (level == Level::Error) { - error = true; - } - } - - bool HadError() { - return error; - } - - private: - Source source_; - IDiagnostics* diag_; - bool error = false; - - DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics); -}; - } // namespace aapt -#endif /* AAPT_DIAGNOSTICS_H */ +#endif /* AAPT_DIAGNOSTICS_H_ */ diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index 830bc5fa36aa..6b1fd9ff11eb 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -72,12 +72,13 @@ static ApkFormat DetermineApkFormat(io::IFileCollection* apk) { } } -std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, IDiagnostics* diag) { - Source source(path); +std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(StringPiece path, + android::IDiagnostics* diag) { + android::Source source(path); std::string error; std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error); if (apk == nullptr) { - diag->Error(DiagMessage(path) << "failed opening zip: " << error); + diag->Error(android::DiagMessage(path) << "failed opening zip: " << error); return {}; } @@ -88,13 +89,14 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, I case ApkFormat::kProto: return LoadProtoApkFromFileCollection(source, std::move(apk), diag); default: - diag->Error(DiagMessage(path) << "could not identify format of APK"); + diag->Error(android::DiagMessage(path) << "could not identify format of APK"); return {}; } } std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection( - const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) { + const android::Source& source, unique_ptr<io::IFileCollection> collection, + android::IDiagnostics* diag) { std::unique_ptr<ResourceTable> table; io::IFile* table_file = collection->FindFile(kProtoResourceTablePath); @@ -102,20 +104,20 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection( pb::ResourceTable pb_table; std::unique_ptr<io::InputStream> in = table_file->OpenInputStream(); if (in == nullptr) { - diag->Error(DiagMessage(source) << "failed to open " << kProtoResourceTablePath); + diag->Error(android::DiagMessage(source) << "failed to open " << kProtoResourceTablePath); return {}; } io::ProtoInputStreamReader proto_reader(in.get()); if (!proto_reader.ReadMessage(&pb_table)) { - diag->Error(DiagMessage(source) << "failed to read " << kProtoResourceTablePath); + diag->Error(android::DiagMessage(source) << "failed to read " << kProtoResourceTablePath); return {}; } std::string error; table = util::make_unique<ResourceTable>(ResourceTable::Validation::kDisabled); if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) { - diag->Error(DiagMessage(source) + diag->Error(android::DiagMessage(source) << "failed to deserialize " << kProtoResourceTablePath << ": " << error); return {}; } @@ -123,27 +125,27 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection( io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath); if (manifest_file == nullptr) { - diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath); + diag->Error(android::DiagMessage(source) << "failed to find " << kAndroidManifestPath); return {}; } std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream(); if (manifest_in == nullptr) { - diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath); + diag->Error(android::DiagMessage(source) << "failed to open " << kAndroidManifestPath); return {}; } pb::XmlNode pb_node; io::ProtoInputStreamReader proto_reader(manifest_in.get()); if (!proto_reader.ReadMessage(&pb_node)) { - diag->Error(DiagMessage(source) << "failed to read proto " << kAndroidManifestPath); + diag->Error(android::DiagMessage(source) << "failed to read proto " << kAndroidManifestPath); return {}; } std::string error; std::unique_ptr<xml::XmlResource> manifest = DeserializeXmlResourceFromPb(pb_node, &error); if (manifest == nullptr) { - diag->Error(DiagMessage(source) + diag->Error(android::DiagMessage(source) << "failed to deserialize proto " << kAndroidManifestPath << ": " << error); return {}; } @@ -152,7 +154,8 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection( } std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection( - const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) { + const android::Source& source, unique_ptr<io::IFileCollection> collection, + android::IDiagnostics* diag) { std::unique_ptr<ResourceTable> table; io::IFile* table_file = collection->FindFile(kApkResourceTablePath); @@ -160,7 +163,7 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection( table = util::make_unique<ResourceTable>(ResourceTable::Validation::kDisabled); std::unique_ptr<io::IData> data = table_file->OpenAsData(); if (data == nullptr) { - diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath); + diag->Error(android::DiagMessage(source) << "failed to open " << kApkResourceTablePath); return {}; } BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(), @@ -172,13 +175,13 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection( io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath); if (manifest_file == nullptr) { - diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath); + diag->Error(android::DiagMessage(source) << "failed to find " << kAndroidManifestPath); return {}; } std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData(); if (manifest_data == nullptr) { - diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath); + diag->Error(android::DiagMessage(source) << "failed to open " << kAndroidManifestPath); return {}; } @@ -186,7 +189,7 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection( std::unique_ptr<xml::XmlResource> manifest = xml::Inflate(manifest_data->data(), manifest_data->size(), &error); if (manifest == nullptr) { - diag->Error(DiagMessage(source) + diag->Error(android::DiagMessage(source) << "failed to parse binary " << kAndroidManifestPath << ": " << error); return {}; } @@ -235,7 +238,7 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table // Skip resources that are not referenced if requested. if (is_resource && referenced_resources.find(output_path) == referenced_resources.end()) { if (context->IsVerbose()) { - context->GetDiagnostics()->Note(DiagMessage() + context->GetDiagnostics()->Note(android::DiagMessage() << "Removing resource '" << path << "' from APK."); } continue; @@ -243,14 +246,15 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table if (!filters->Keep(path)) { if (context->IsVerbose()) { - context->GetDiagnostics()->Note(DiagMessage() << "Filtered '" << path << "' from APK."); + context->GetDiagnostics()->Note(android::DiagMessage() + << "Filtered '" << path << "' from APK."); } continue; } // The resource table needs to be re-serialized since it might have changed. if (format_ == ApkFormat::kBinary && path == kApkResourceTablePath) { - BigBuffer buffer(4096); + android::BigBuffer buffer(4096); // TODO(adamlesinski): How to determine if there were sparse entries (and if to encode // with sparse entries) b/35389232. TableFlattener flattener(options, &buffer); @@ -282,12 +286,12 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table return false; } } else if (manifest != nullptr && path == "AndroidManifest.xml") { - BigBuffer buffer(8192); + android::BigBuffer buffer(8192); XmlFlattenerOptions xml_flattener_options; xml_flattener_options.use_utf16 = true; XmlFlattener xml_flattener(&buffer, xml_flattener_options); if (!xml_flattener.Consume(context, manifest)) { - context->GetDiagnostics()->Error(DiagMessage(path) << "flattening failed"); + context->GetDiagnostics()->Error(android::DiagMessage(path) << "flattening failed"); return false; } @@ -308,10 +312,10 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table } std::unique_ptr<xml::XmlResource> LoadedApk::LoadXml(const std::string& file_path, - IDiagnostics* diag) const { + android::IDiagnostics* diag) const { io::IFile* file = apk_->FindFile(file_path); if (file == nullptr) { - diag->Error(DiagMessage() << "failed to find file"); + diag->Error(android::DiagMessage() << "failed to find file"); return nullptr; } @@ -319,34 +323,34 @@ std::unique_ptr<xml::XmlResource> LoadedApk::LoadXml(const std::string& file_pat if (format_ == ApkFormat::kProto) { std::unique_ptr<io::InputStream> in = file->OpenInputStream(); if (!in) { - diag->Error(DiagMessage() << "failed to open file"); + diag->Error(android::DiagMessage() << "failed to open file"); return nullptr; } pb::XmlNode pb_node; io::ProtoInputStreamReader proto_reader(in.get()); if (!proto_reader.ReadMessage(&pb_node)) { - diag->Error(DiagMessage() << "failed to parse file as proto XML"); + diag->Error(android::DiagMessage() << "failed to parse file as proto XML"); return nullptr; } std::string err; doc = DeserializeXmlResourceFromPb(pb_node, &err); if (!doc) { - diag->Error(DiagMessage() << "failed to deserialize proto XML: " << err); + diag->Error(android::DiagMessage() << "failed to deserialize proto XML: " << err); return nullptr; } } else if (format_ == ApkFormat::kBinary) { std::unique_ptr<io::IData> data = file->OpenAsData(); if (!data) { - diag->Error(DiagMessage() << "failed to open file"); + diag->Error(android::DiagMessage() << "failed to open file"); return nullptr; } std::string err; doc = xml::Inflate(data->data(), data->size(), &err); if (!doc) { - diag->Error(DiagMessage() << "failed to parse file as binary XML: " << err); + diag->Error(android::DiagMessage() << "failed to parse file as binary XML: " << err); return nullptr; } } diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h index 5b6f45ebb38d..4cd7eae0a5e2 100644 --- a/tools/aapt2/LoadedApk.h +++ b/tools/aapt2/LoadedApk.h @@ -45,18 +45,20 @@ class LoadedApk { virtual ~LoadedApk() = default; // Loads both binary and proto APKs from disk. - static std::unique_ptr<LoadedApk> LoadApkFromPath(const ::android::StringPiece& path, - IDiagnostics* diag); + static std::unique_ptr<LoadedApk> LoadApkFromPath(android::StringPiece path, + android::IDiagnostics* diag); // Loads a proto APK from the given file collection. static std::unique_ptr<LoadedApk> LoadProtoApkFromFileCollection( - const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag); + const android::Source& source, std::unique_ptr<io::IFileCollection> collection, + android::IDiagnostics* diag); // Loads a binary APK from the given file collection. static std::unique_ptr<LoadedApk> LoadBinaryApkFromFileCollection( - const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag); + const android::Source& source, std::unique_ptr<io::IFileCollection> collection, + android::IDiagnostics* diag); - LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk, + LoadedApk(const android::Source& source, std::unique_ptr<io::IFileCollection> apk, std::unique_ptr<ResourceTable> table, std::unique_ptr<xml::XmlResource> manifest, const ApkFormat& format) : source_(source), @@ -82,7 +84,7 @@ class LoadedApk { return table_.get(); } - const Source& GetSource() { + const android::Source& GetSource() { return source_; } @@ -111,12 +113,13 @@ class LoadedApk { IArchiveWriter* writer, xml::XmlResource* manifest = nullptr); /** Loads the file as an xml document. */ - std::unique_ptr<xml::XmlResource> LoadXml(const std::string& file_path, IDiagnostics* diag) const; + std::unique_ptr<xml::XmlResource> LoadXml(const std::string& file_path, + android::IDiagnostics* diag) const; private: DISALLOW_COPY_AND_ASSIGN(LoadedApk); - Source source_; + android::Source source_; std::unique_ptr<io::IFileCollection> apk_; std::unique_ptr<ResourceTable> table_; std::unique_ptr<xml::XmlResource> manifest_; diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index b249c6c128e1..a0b4dab9b8e5 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -24,11 +24,12 @@ #include <iostream> #include <vector> +#include "Diagnostics.h" #include "android-base/stringprintf.h" #include "android-base/utf8.h" +#include "androidfw/IDiagnostics.h" #include "androidfw/StringPiece.h" - -#include "Diagnostics.h" +#include "cmd/ApkInfo.h" #include "cmd/Command.h" #include "cmd/Compile.h" #include "cmd/Convert.h" @@ -63,7 +64,7 @@ class VersionCommand : public Command { /** The main entry point of AAPT. */ class MainCommand : public Command { public: - explicit MainCommand(text::Printer* printer, IDiagnostics* diagnostics) + explicit MainCommand(text::Printer* printer, android::IDiagnostics* diagnostics) : Command("aapt2"), diagnostics_(diagnostics) { AddOptionalSubcommand(util::make_unique<CompileCommand>(diagnostics)); AddOptionalSubcommand(util::make_unique<LinkCommand>(diagnostics)); @@ -72,13 +73,14 @@ class MainCommand : public Command { AddOptionalSubcommand(util::make_unique<OptimizeCommand>()); AddOptionalSubcommand(util::make_unique<ConvertCommand>()); AddOptionalSubcommand(util::make_unique<VersionCommand>()); + AddOptionalSubcommand(util::make_unique<ApkInfoCommand>(diagnostics)); } int Action(const std::vector<std::string>& args) override { if (args.size() == 0) { - diagnostics_->Error(DiagMessage() << "no subcommand specified"); + diagnostics_->Error(android::DiagMessage() << "no subcommand specified"); } else { - diagnostics_->Error(DiagMessage() << "unknown subcommand '" << args[0] << "'"); + diagnostics_->Error(android::DiagMessage() << "unknown subcommand '" << args[0] << "'"); } Usage(&std::cerr); @@ -86,7 +88,7 @@ class MainCommand : public Command { } private: - IDiagnostics* diagnostics_; + android::IDiagnostics* diagnostics_; }; /* @@ -97,7 +99,7 @@ class MainCommand : public Command { */ class DaemonCommand : public Command { public: - explicit DaemonCommand(io::FileOutputStream* out, IDiagnostics* diagnostics) + explicit DaemonCommand(io::FileOutputStream* out, android::IDiagnostics* diagnostics) : Command("daemon", "m"), out_(out), diagnostics_(diagnostics) { SetDescription("Runs aapt in daemon mode. Each subsequent line is a single parameter to the\n" "command. The end of an invocation is signaled by providing an empty line."); @@ -146,7 +148,7 @@ class DaemonCommand : public Command { private: io::FileOutputStream* out_; - IDiagnostics* diagnostics_; + android::IDiagnostics* diagnostics_; std::optional<std::string> trace_folder_; }; diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h index 0b4905253d20..0b08c3276cb5 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -36,7 +36,7 @@ struct NameManglerPolicy { * We must know which references to mangle, and which to keep (android vs. * com.android.support). */ - std::set<std::string> packages_to_mangle; + std::set<std::string, std::less<>> packages_to_mangle; }; class NameMangler { @@ -54,7 +54,7 @@ class NameMangler { mangled_entry_name); } - bool ShouldMangle(const std::string& package) const { + bool ShouldMangle(std::string_view package) const { if (package.empty() || policy_.target_package_name == package) { return false; } @@ -68,8 +68,8 @@ class NameMangler { * The mangled name should contain symbols that are illegal to define in XML, * so that there will never be name mangling collisions. */ - static std::string MangleEntry(const std::string& package, const std::string& name) { - return package + "$" + name; + static std::string MangleEntry(std::string_view package, std::string_view name) { + return (std::string(package) += '$') += name; } /** diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index 0bb330e26e6f..cfcb2bb4f99d 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -138,11 +138,11 @@ ResourceNamedTypeRef ResourceNamedTypeWithDefaultName(ResourceType t) { return {to_string(t), t}; } -std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s) { - auto colon = std::find(s.begin(), s.end(), ':'); +std::optional<ResourceNamedTypeRef> ParseResourceNamedType(android::StringPiece s) { + auto dot = std::find(s.begin(), s.end(), '.'); const ResourceType* parsedType; - if (colon != s.end() && colon != std::prev(s.end())) { - parsedType = ParseResourceType(s.substr(s.begin(), colon)); + if (dot != s.end() && dot != std::prev(s.end())) { + parsedType = ParseResourceType(android::StringPiece(s.begin(), dot - s.begin())); } else { parsedType = ParseResourceType(s); } @@ -152,7 +152,7 @@ std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::String return ResourceNamedTypeRef(s, *parsedType); } -const ResourceType* ParseResourceType(const StringPiece& str) { +const ResourceType* ParseResourceType(StringPiece str) { auto iter = sResourceTypeMap.find(str); if (iter == std::end(sResourceTypeMap)) { return nullptr; diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index b41d8514230b..7ba3277d2093 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -25,8 +25,8 @@ #include <tuple> #include <vector> -#include "Source.h" #include "androidfw/ConfigDescription.h" +#include "androidfw/Source.h" #include "androidfw/StringPiece.h" #include "utils/JenkinsHash.h" @@ -74,7 +74,7 @@ android::StringPiece to_string(ResourceType type); /** * Returns a pointer to a valid ResourceType, or nullptr if the string was invalid. */ -const ResourceType* ParseResourceType(const android::StringPiece& str); +const ResourceType* ParseResourceType(android::StringPiece str); /** * Pair of type name as in ResourceTable and actual resource type. @@ -87,7 +87,7 @@ struct ResourceNamedType { ResourceType type = ResourceType::kRaw; ResourceNamedType() = default; - ResourceNamedType(const android::StringPiece& n, ResourceType t); + ResourceNamedType(android::StringPiece n, ResourceType t); int compare(const ResourceNamedType& other) const; @@ -108,19 +108,19 @@ struct ResourceNamedTypeRef { ResourceNamedTypeRef(const ResourceNamedTypeRef&) = default; ResourceNamedTypeRef(ResourceNamedTypeRef&&) = default; ResourceNamedTypeRef(const ResourceNamedType& rhs); // NOLINT(google-explicit-constructor) - ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t); + ResourceNamedTypeRef(android::StringPiece n, ResourceType t); ResourceNamedTypeRef& operator=(const ResourceNamedTypeRef& rhs) = default; ResourceNamedTypeRef& operator=(ResourceNamedTypeRef&& rhs) = default; ResourceNamedTypeRef& operator=(const ResourceNamedType& rhs); ResourceNamedType ToResourceNamedType() const; - std::string to_string() const; + std::string_view to_string() const; }; ResourceNamedTypeRef ResourceNamedTypeWithDefaultName(ResourceType t); -std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s); +std::optional<ResourceNamedTypeRef> ParseResourceNamedType(android::StringPiece s); /** * A resource's name. This can uniquely identify @@ -132,9 +132,8 @@ struct ResourceName { std::string entry; ResourceName() = default; - ResourceName(const android::StringPiece& p, const ResourceNamedTypeRef& t, - const android::StringPiece& e); - ResourceName(const android::StringPiece& p, ResourceType t, const android::StringPiece& e); + ResourceName(android::StringPiece p, const ResourceNamedTypeRef& t, android::StringPiece e); + ResourceName(android::StringPiece p, ResourceType t, android::StringPiece e); int compare(const ResourceName& other) const; @@ -157,9 +156,8 @@ struct ResourceNameRef { ResourceNameRef(const ResourceNameRef&) = default; ResourceNameRef(ResourceNameRef&&) = default; ResourceNameRef(const ResourceName& rhs); // NOLINT(google-explicit-constructor) - ResourceNameRef(const android::StringPiece& p, const ResourceNamedTypeRef& t, - const android::StringPiece& e); - ResourceNameRef(const android::StringPiece& p, ResourceType t, const android::StringPiece& e); + ResourceNameRef(android::StringPiece p, const ResourceNamedTypeRef& t, android::StringPiece e); + ResourceNameRef(android::StringPiece p, ResourceType t, android::StringPiece e); ResourceNameRef& operator=(const ResourceNameRef& rhs) = default; ResourceNameRef& operator=(ResourceNameRef&& rhs) = default; ResourceNameRef& operator=(const ResourceName& rhs); @@ -228,7 +226,7 @@ struct ResourceFile { Type type; // Source - Source source; + android::Source source; // Exported symbols std::vector<SourcedResourceName> exported_symbols; @@ -346,8 +344,8 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) // // ResourceNamedType implementation. // -inline ResourceNamedType::ResourceNamedType(const android::StringPiece& n, ResourceType t) - : name(n.to_string()), type(t) { +inline ResourceNamedType::ResourceNamedType(android::StringPiece n, ResourceType t) + : name(n), type(t) { } inline int ResourceNamedType::compare(const ResourceNamedType& other) const { @@ -380,7 +378,7 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNamedType& // // ResourceNamedTypeRef implementation. // -inline ResourceNamedTypeRef::ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t) +inline ResourceNamedTypeRef::ResourceNamedTypeRef(android::StringPiece n, ResourceType t) : name(n), type(t) { } @@ -398,8 +396,8 @@ inline ResourceNamedType ResourceNamedTypeRef::ToResourceNamedType() const { return ResourceNamedType(name, type); } -inline std::string ResourceNamedTypeRef::to_string() const { - return name.to_string(); +inline std::string_view ResourceNamedTypeRef::to_string() const { + return name; } inline bool operator<(const ResourceNamedTypeRef& lhs, const ResourceNamedTypeRef& rhs) { @@ -422,13 +420,12 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNamedTypeRe // ResourceName implementation. // -inline ResourceName::ResourceName(const android::StringPiece& p, const ResourceNamedTypeRef& t, - const android::StringPiece& e) - : package(p.to_string()), type(t.ToResourceNamedType()), entry(e.to_string()) { +inline ResourceName::ResourceName(android::StringPiece p, const ResourceNamedTypeRef& t, + android::StringPiece e) + : package(p), type(t.ToResourceNamedType()), entry(e) { } -inline ResourceName::ResourceName(const android::StringPiece& p, ResourceType t, - const android::StringPiece& e) +inline ResourceName::ResourceName(android::StringPiece p, ResourceType t, android::StringPiece e) : ResourceName(p, ResourceNamedTypeWithDefaultName(t), e) { } @@ -471,14 +468,13 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs) : package(rhs.package), type(rhs.type), entry(rhs.entry) {} -inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p, - const ResourceNamedTypeRef& t, - const android::StringPiece& e) +inline ResourceNameRef::ResourceNameRef(android::StringPiece p, const ResourceNamedTypeRef& t, + android::StringPiece e) : package(p), type(t), entry(e) { } -inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p, ResourceType t, - const android::StringPiece& e) +inline ResourceNameRef::ResourceNameRef(android::StringPiece p, ResourceType t, + android::StringPiece e) : ResourceNameRef(p, ResourceNamedTypeWithDefaultName(t), e) { } diff --git a/tools/aapt2/ResourceMetadata.proto b/tools/aapt2/ResourceMetadata.proto new file mode 100644 index 000000000000..8eca54c4da5e --- /dev/null +++ b/tools/aapt2/ResourceMetadata.proto @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 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. + */ + +syntax = "proto3"; + +package aapt.pb; + +option java_package = "com.android.aapt"; +option java_multiple_files = true; + +message ResourceMappings { + ShortenedPathsMap shortened_paths = 1; + CollapsedNamesMap collapsed_names = 2; +} + +// Metadata relating to "aapt2 optimize --shorten-resource-paths" +message ShortenedPathsMap { + // Maps shorted paths (e.g. "res/foo.xml") to their original names (e.g. + // "res/xml/file_with_long_name.xml"). + message ResourcePathMapping { + string shortened_path = 1; + string original_path = 2; + } + repeated ResourcePathMapping resource_paths = 1; +} + +// Metadata relating to "aapt2 optimize --collapse-resource-names" +message CollapsedNamesMap { + // Maps resource IDs (e.g. 0x7f123456) to their original names (e.g. + // "package:type/entry"). + message ResourceNameMapping { + uint32 id = 1; + string name = 2; + } + repeated ResourceNameMapping resource_names = 1; +} diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 8d35eeec2a93..fa9a98f136cb 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -50,11 +50,11 @@ constexpr const char* kStagingPublicGroupFinalTag = "staging-public-group-final" constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2"; // Returns true if the element is <skip> or <eat-comment> and can be safely ignored. -static bool ShouldIgnoreElement(const StringPiece& ns, const StringPiece& name) { +static bool ShouldIgnoreElement(StringPiece ns, StringPiece name) { return ns.empty() && (name == "skip" || name == "eat-comment"); } -static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) { +static uint32_t ParseFormatTypeNoEnumsOrFlags(StringPiece piece) { if (piece == "reference") { return android::ResTable_map::TYPE_REFERENCE; } else if (piece == "string") { @@ -75,7 +75,7 @@ static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) { return 0; } -static uint32_t ParseFormatType(const StringPiece& piece) { +static uint32_t ParseFormatType(StringPiece piece) { if (piece == "enum") { return android::ResTable_map::TYPE_ENUM; } else if (piece == "flags") { @@ -84,9 +84,9 @@ static uint32_t ParseFormatType(const StringPiece& piece) { return ParseFormatTypeNoEnumsOrFlags(piece); } -static uint32_t ParseFormatAttribute(const StringPiece& str) { +static uint32_t ParseFormatAttribute(StringPiece str) { uint32_t mask = 0; - for (const StringPiece& part : util::Tokenize(str, '|')) { + for (StringPiece part : util::Tokenize(str, '|')) { StringPiece trimmed_part = util::TrimWhitespace(part); uint32_t type = ParseFormatType(trimmed_part); if (type == 0) { @@ -102,7 +102,7 @@ struct ParsedResource { ResourceName name; ConfigDescription config; std::string product; - Source source; + android::Source source; ResourceId id; Visibility::Level visibility_level = Visibility::Level::kUndefined; @@ -117,11 +117,12 @@ struct ParsedResource { }; // Recursively adds resources to the ResourceTable. -static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) { +static bool AddResourcesToTable(ResourceTable* table, android::IDiagnostics* diag, + ParsedResource* res) { StringPiece trimmed_comment = util::TrimWhitespace(res->comment); if (trimmed_comment.size() != res->comment.size()) { // Only if there was a change do we re-assign. - res->comment = trimmed_comment.to_string(); + res->comment = std::string(trimmed_comment); } NewResourceBuilder res_builder(res->name); @@ -175,15 +176,11 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed // Convenient aliases for more readable function calls. enum { kAllowRawString = true, kNoRawString = false }; -ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, - const Source& source, - const ConfigDescription& config, +ResourceParser::ResourceParser(android::IDiagnostics* diag, ResourceTable* table, + const android::Source& source, const ConfigDescription& config, const ResourceParserOptions& options) - : diag_(diag), - table_(table), - source_(source), - config_(config), - options_(options) {} + : diag_(diag), table_(table), source_(source), config_(config), options_(options) { +} // Base class Node for representing the various Spans and UntranslatableSections of an XML string. // This will be used to traverse and flatten the XML string into a single std::string, with all @@ -245,7 +242,7 @@ class UntranslatableNode : public Node { // Build a string from XML that converts nested elements into Span objects. bool ResourceParser::FlattenXmlSubtree( - xml::XmlPullParser* parser, std::string* out_raw_string, StyleString* out_style_string, + xml::XmlPullParser* parser, std::string* out_raw_string, android::StyleString* out_style_string, std::vector<UntranslatableSection>* out_untranslatable_sections) { std::string raw_string; std::string current_text; @@ -308,7 +305,7 @@ bool ResourceParser::FlattenXmlSubtree( // Check that an 'untranslatable' tag is not already being processed. Nested // <xliff:g> tags are illegal. if (untranslatable_start_depth) { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "illegal nested XLIFF 'g' tag"); return false; } else { @@ -323,7 +320,7 @@ bool ResourceParser::FlattenXmlSubtree( } } else { // Besides XLIFF, any other namespaced tag is unsupported and ignored. - diag_->Warn(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Warn(android::DiagMessage(source_.WithLine(parser->line_number())) << "ignoring element '" << parser->element_name() << "' with unknown namespace '" << parser->element_namespace() << "'"); node_stack.push_back(node_stack.back()->AddChild(util::make_unique<Node>())); @@ -365,7 +362,7 @@ bool ResourceParser::FlattenXmlSubtree( // Trim leading whitespace. StringPiece trimmed = util::TrimLeadingWhitespace(first_segment->data); if (trimmed.size() != first_segment->data.size()) { - first_segment->data = trimmed.to_string(); + first_segment->data = std::string(trimmed); } } @@ -373,7 +370,7 @@ bool ResourceParser::FlattenXmlSubtree( // Trim trailing whitespace. StringPiece trimmed = util::TrimTrailingWhitespace(last_segment->data); if (trimmed.size() != last_segment->data.size()) { - last_segment->data = trimmed.to_string(); + last_segment->data = std::string(trimmed); } } } @@ -383,7 +380,8 @@ bool ResourceParser::FlattenXmlSubtree( StringBuilder builder; root.Build(&builder); if (!builder) { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) << builder.GetError()); + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) + << builder.GetError()); return false; } @@ -405,7 +403,7 @@ bool ResourceParser::Parse(xml::XmlPullParser* parser) { } if (!parser->element_namespace().empty() || parser->element_name() != "resources") { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "root element must be <resources>"); return false; } @@ -415,7 +413,7 @@ bool ResourceParser::Parse(xml::XmlPullParser* parser) { }; if (parser->event() == xml::XmlPullParser::Event::kBadDocument) { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "xml parser error: " << parser->error()); return false; } @@ -437,7 +435,7 @@ bool ResourceParser::ParseResources(xml::XmlPullParser* parser) { if (event == xml::XmlPullParser::Event::kText) { if (!util::TrimWhitespace(parser->text()).empty()) { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "plain text not allowed here"); error = true; } @@ -468,7 +466,7 @@ bool ResourceParser::ParseResources(xml::XmlPullParser* parser) { // Extract the product name if it exists. if (std::optional<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) { - parsed_resource.product = maybe_product.value().to_string(); + parsed_resource.product = std::string(maybe_product.value()); } // Parse the resource regardless of product. @@ -486,8 +484,9 @@ bool ResourceParser::ParseResources(xml::XmlPullParser* parser) { for (const ResourceName& stripped_resource : stripped_resources) { if (!table_->FindResource(stripped_resource)) { // Failed to find the resource. - diag_->Error(DiagMessage(source_) << "resource '" << stripped_resource - << "' was filtered out but no product variant remains"); + diag_->Error(android::DiagMessage(source_) + << "resource '" << stripped_resource + << "' was filtered out but no product variant remains"); error = true; } } @@ -560,9 +559,9 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, // Items have their type encoded in the type attribute. if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { - resource_type = maybe_type.value().to_string(); + resource_type = std::string(maybe_type.value()); } else { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "<item> must have a 'type' attribute"); return false; } @@ -573,9 +572,8 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, // overridden. resource_format = ParseFormatTypeNoEnumsOrFlags(maybe_format.value()); if (!resource_format) { - diag_->Error(DiagMessage(out_resource->source) - << "'" << maybe_format.value() - << "' is an invalid format"); + diag_->Error(android::DiagMessage(out_resource->source) + << "'" << maybe_format.value() << "' is an invalid format"); return false; } } @@ -584,9 +582,9 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, // Bags have their type encoded in the type attribute. if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { - resource_type = maybe_type.value().to_string(); + resource_type = std::string(maybe_type.value()); } else { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "<bag> must have a 'type' attribute"); return false; } @@ -598,15 +596,14 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, if (resource_type == "id") { if (!maybe_name) { - diag_->Error(DiagMessage(out_resource->source) - << "<" << parser->element_name() - << "> missing 'name' attribute"); + diag_->Error(android::DiagMessage(out_resource->source) + << "<" << parser->element_name() << "> missing 'name' attribute"); return false; } out_resource->name.type = ResourceNamedTypeWithDefaultName(ResourceType::kId).ToResourceNamedType(); - out_resource->name.entry = maybe_name.value().to_string(); + out_resource->name.entry = std::string(maybe_name.value()); // Ids either represent a unique resource id or reference another resource id auto item = ParseItem(parser, out_resource, resource_format); @@ -626,9 +623,9 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, out_resource->value = util::make_unique<Id>(); } else if (!ref || ref->name.value().type.type != ResourceType::kId) { // If an inner element exists, the inner element must be a reference to another resource id - diag_->Error(DiagMessage(out_resource->source) - << "<" << parser->element_name() - << "> inner element must either be a resource reference or empty"); + diag_->Error(android::DiagMessage(out_resource->source) + << "<" << parser->element_name() + << "> inner element must either be a resource reference or empty"); return false; } } @@ -636,14 +633,14 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, return true; } else if (resource_type == "macro") { if (!maybe_name) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "<" << parser->element_name() << "> missing 'name' attribute"); return false; } out_resource->name.type = ResourceNamedTypeWithDefaultName(ResourceType::kMacro).ToResourceNamedType(); - out_resource->name.entry = maybe_name.value().to_string(); + out_resource->name.entry = std::string(maybe_name.value()); return ParseMacro(parser, out_resource); } @@ -653,14 +650,14 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, // This is an item, record its type and format and start parsing. if (!maybe_name) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "<" << parser->element_name() << "> missing 'name' attribute"); return false; } out_resource->name.type = ResourceNamedTypeWithDefaultName(item_iter->second.type).ToResourceNamedType(); - out_resource->name.entry = maybe_name.value().to_string(); + out_resource->name.entry = std::string(maybe_name.value()); // Only use the implied format of the type when there is no explicit format. if (resource_format == 0u) { @@ -682,12 +679,12 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag && resource_type != kStagingPublicGroupFinalTag && resource_type != "overlayable") { if (!maybe_name) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "<" << parser->element_name() << "> missing 'name' attribute"); return false; } - out_resource->name.entry = maybe_name.value().to_string(); + out_resource->name.entry = std::string(maybe_name.value()); } // Call the associated parse method. The type will be filled in by the @@ -705,17 +702,16 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(resource_type); if (parsed_type) { if (!maybe_name) { - diag_->Error(DiagMessage(out_resource->source) - << "<" << parser->element_name() - << "> missing 'name' attribute"); + diag_->Error(android::DiagMessage(out_resource->source) + << "<" << parser->element_name() << "> missing 'name' attribute"); return false; } out_resource->name.type = parsed_type->ToResourceNamedType(); - out_resource->name.entry = maybe_name.value().to_string(); + out_resource->name.entry = std::string(maybe_name.value()); out_resource->value = ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString); if (!out_resource->value) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "invalid value for type '" << *parsed_type << "'. Expected a reference"); return false; } @@ -724,8 +720,8 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, } // If the resource type was not recognized, write the error and return false. - diag_->Error(DiagMessage(out_resource->source) - << "unknown resource type '" << resource_type << "'"); + diag_->Error(android::DiagMessage(out_resource->source) + << "unknown resource type '" << resource_type << "'"); return false; } @@ -738,8 +734,8 @@ bool ResourceParser::ParseItem(xml::XmlPullParser* parser, out_resource->value = ParseXml(parser, format, kNoRawString); if (!out_resource->value) { - diag_->Error(DiagMessage(out_resource->source) << "invalid " - << out_resource->name.type); + diag_->Error(android::DiagMessage(out_resource->source) + << "invalid " << out_resource->name.type); return false; } return true; @@ -750,7 +746,7 @@ std::optional<FlattenedXmlSubTree> ResourceParser::CreateFlattenSubTree( const size_t begin_xml_line = parser->line_number(); std::string raw_value; - StyleString style_string; + android::StyleString style_string; std::vector<UntranslatableSection> untranslatable_sections; if (!FlattenXmlSubtree(parser, &raw_value, &style_string, &untranslatable_sections)) { return {}; @@ -783,13 +779,13 @@ std::unique_ptr<Item> ResourceParser::ParseXml(const FlattenedXmlSubTree& xmlsub const uint32_t type_mask, const bool allow_raw_value, ResourceTable& table, const android::ConfigDescription& config, - IDiagnostics& diag) { + android::IDiagnostics& diag) { if (!xmlsub_tree.style_string.spans.empty()) { // This can only be a StyledString. std::unique_ptr<StyledString> styled_string = util::make_unique<StyledString>(table.string_pool.MakeRef( xmlsub_tree.style_string, - StringPool::Context(StringPool::Context::kNormalPriority, config))); + android::StringPool::Context(android::StringPool::Context::kNormalPriority, config))); styled_string->untranslatable_sections = xmlsub_tree.untranslatable_sections; return std::move(styled_string); } @@ -817,8 +813,8 @@ std::unique_ptr<Item> ResourceParser::ParseXml(const FlattenedXmlSubTree& xmlsub // Try making a regular string. if (type_mask & android::ResTable_map::TYPE_STRING) { // Use the trimmed, escaped string. - std::unique_ptr<String> string = util::make_unique<String>( - table.string_pool.MakeRef(xmlsub_tree.style_string.str, StringPool::Context(config))); + std::unique_ptr<String> string = util::make_unique<String>(table.string_pool.MakeRef( + xmlsub_tree.style_string.str, android::StringPool::Context(config))); string->untranslatable_sections = xmlsub_tree.untranslatable_sections; return std::move(string); } @@ -826,7 +822,7 @@ std::unique_ptr<Item> ResourceParser::ParseXml(const FlattenedXmlSubTree& xmlsub if (allow_raw_value) { // We can't parse this so return a RawString if we are allowed. return util::make_unique<RawString>(table.string_pool.MakeRef( - util::TrimWhitespace(xmlsub_tree.raw_value), StringPool::Context(config))); + util::TrimWhitespace(xmlsub_tree.raw_value), android::StringPool::Context(config))); } else if (util::TrimWhitespace(xmlsub_tree.raw_value).empty()) { // If the text is empty, and the value is not allowed to be a string, encode it as a @null. return ResourceUtils::MakeNull(); @@ -840,7 +836,7 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser, if (std::optional<StringPiece> formatted_attr = xml::FindAttribute(parser, "formatted")) { std::optional<bool> maybe_formatted = ResourceUtils::ParseBool(formatted_attr.value()); if (!maybe_formatted) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "invalid value for 'formatted'. Must be a boolean"); return false; } @@ -851,7 +847,7 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser, if (std::optional<StringPiece> translatable_attr = xml::FindAttribute(parser, "translatable")) { std::optional<bool> maybe_translatable = ResourceUtils::ParseBool(translatable_attr.value()); if (!maybe_translatable) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "invalid value for 'translatable'. Must be a boolean"); return false; } @@ -861,7 +857,7 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser, out_resource->value = ParseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString); if (!out_resource->value) { - diag_->Error(DiagMessage(out_resource->source) << "not a valid string"); + diag_->Error(android::DiagMessage(out_resource->source) << "not a valid string"); return false; } @@ -870,7 +866,7 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser, if (formatted && translatable) { if (!util::VerifyJavaStringFormat(*string_value->value)) { - DiagMessage msg(out_resource->source); + android::DiagMessage msg(out_resource->source); msg << "multiple substitutions specified in non-positional format; " "did you mean to add the formatted=\"false\" attribute?"; if (options_.error_on_positional_arguments) { @@ -895,7 +891,7 @@ bool ResourceParser::ParseMacro(xml::XmlPullParser* parser, ParsedResource* out_ } if (out_resource->config != ConfigDescription::DefaultConfig()) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "<macro> tags cannot be declared in configurations other than the default " "configuration'"); return false; @@ -919,28 +915,27 @@ bool ResourceParser::ParseMacro(xml::XmlPullParser* parser, ParsedResource* out_ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (options_.visibility) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "<public> tag not allowed with --visibility flag"); return false; } if (out_resource->config != ConfigDescription::DefaultConfig()) { - diag_->Warn(DiagMessage(out_resource->source) + diag_->Warn(android::DiagMessage(out_resource->source) << "ignoring configuration '" << out_resource->config << "' for <public> tag"); } std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "<public> must have a 'type' attribute"); return false; } std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(maybe_type.value()); if (!parsed_type) { - diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '" - << maybe_type.value() - << "' in <public>"); + diag_->Error(android::DiagMessage(out_resource->source) + << "invalid resource type '" << maybe_type.value() << "' in <public>"); return false; } @@ -949,7 +944,7 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out if (std::optional<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "id")) { std::optional<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value()); if (!maybe_id) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "invalid resource ID '" << maybe_id_str.value() << "' in <public>"); return false; } @@ -967,37 +962,39 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out template <typename Func> bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, - const char* tag_name, IDiagnostics* diag, Func&& func) { + const char* tag_name, android::IDiagnostics* diag, Func&& func) { if (out_resource->config != ConfigDescription::DefaultConfig()) { - diag->Warn(DiagMessage(out_resource->source) + diag->Warn(android::DiagMessage(out_resource->source) << "ignoring configuration '" << out_resource->config << "' for <" << tag_name << "> tag"); } std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { - diag->Error(DiagMessage(out_resource->source) + diag->Error(android::DiagMessage(out_resource->source) << "<" << tag_name << "> must have a 'type' attribute"); return false; } - std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(maybe_type.value()); - if (!parsed_type) { - diag->Error(DiagMessage(out_resource->source) + std::optional<ResourceNamedTypeRef> maybe_parsed_type = + ParseResourceNamedType(maybe_type.value()); + if (!maybe_parsed_type) { + diag->Error(android::DiagMessage(out_resource->source) << "invalid resource type '" << maybe_type.value() << "' in <" << tag_name << ">"); return false; } + auto parsed_type = maybe_parsed_type->ToResourceNamedType(); std::optional<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "first-id"); if (!maybe_id_str) { - diag->Error(DiagMessage(out_resource->source) + diag->Error(android::DiagMessage(out_resource->source) << "<" << tag_name << "> must have a 'first-id' attribute"); return false; } std::optional<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value()); if (!maybe_id) { - diag->Error(DiagMessage(out_resource->source) + diag->Error(android::DiagMessage(out_resource->source) << "invalid resource ID '" << maybe_id_str.value() << "' in <" << tag_name << ">"); return false; } @@ -1008,32 +1005,34 @@ bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resou const size_t depth = parser->depth(); while (xml::XmlPullParser::NextChildNode(parser, depth)) { if (parser->event() == xml::XmlPullParser::Event::kComment) { - comment = util::TrimWhitespace(parser->comment()).to_string(); + comment = std::string(util::TrimWhitespace(parser->comment())); continue; } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) { // Skip text. continue; } - const Source item_source = out_resource->source.WithLine(parser->line_number()); + const android::Source item_source = out_resource->source.WithLine(parser->line_number()); const std::string& element_namespace = parser->element_namespace(); const std::string& element_name = parser->element_name(); if (element_namespace.empty() && element_name == "public") { auto maybe_name = xml::FindNonEmptyAttribute(parser, "name"); if (!maybe_name) { - diag->Error(DiagMessage(item_source) << "<public> must have a 'name' attribute"); + diag->Error(android::DiagMessage(item_source) << "<public> must have a 'name' attribute"); error = true; continue; } if (xml::FindNonEmptyAttribute(parser, "id")) { - diag->Error(DiagMessage(item_source) << "'id' is ignored within <" << tag_name << ">"); + diag->Error(android::DiagMessage(item_source) + << "'id' is ignored within <" << tag_name << ">"); error = true; continue; } if (xml::FindNonEmptyAttribute(parser, "type")) { - diag->Error(DiagMessage(item_source) << "'type' is ignored within <" << tag_name << ">"); + diag->Error(android::DiagMessage(item_source) + << "'type' is ignored within <" << tag_name << ">"); error = true; continue; } @@ -1046,7 +1045,7 @@ bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resou } ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{ - .name = ResourceName{{}, *parsed_type, maybe_name.value().to_string()}, + .name = ResourceName{{}, parsed_type, std::string(maybe_name.value())}, .source = item_source, .comment = std::move(comment), }); @@ -1057,7 +1056,7 @@ bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resou next_id.id++; } else if (!ShouldIgnoreElement(element_namespace, element_name)) { - diag->Error(DiagMessage(item_source) << ":" << element_name << ">"); + diag->Error(android::DiagMessage(item_source) << ":" << element_name << ">"); error = true; } } @@ -1084,7 +1083,7 @@ bool ResourceParser::ParseStagingPublicGroupFinal(xml::XmlPullParser* parser, bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (options_.visibility) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "<" << kPublicGroupTag << "> tag not allowed with --visibility flag"); return false; } @@ -1100,15 +1099,14 @@ bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource) { std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { - diag_->Error(DiagMessage(out_resource->source) - << "<" << parser->element_name() - << "> must have a 'type' attribute"); + diag_->Error(android::DiagMessage(out_resource->source) + << "<" << parser->element_name() << "> must have a 'type' attribute"); return false; } std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(maybe_type.value()); if (!parsed_type) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "invalid resource type '" << maybe_type.value() << "' in <" << parser->element_name() << ">"); return false; @@ -1120,12 +1118,12 @@ bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser, bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (options_.visibility) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "<java-symbol> and <symbol> tags not allowed with --visibility flag"); return false; } if (out_resource->config != ConfigDescription::DefaultConfig()) { - diag_->Warn(DiagMessage(out_resource->source) + diag_->Warn(android::DiagMessage(out_resource->source) << "ignoring configuration '" << out_resource->config << "' for <" << parser->element_name() << "> tag"); } @@ -1140,15 +1138,14 @@ bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource) { if (out_resource->config != ConfigDescription::DefaultConfig()) { - diag_->Warn(DiagMessage(out_resource->source) - << "ignoring configuration '" << out_resource->config - << "' for <overlayable> tag"); + diag_->Warn(android::DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config << "' for <overlayable> tag"); } std::optional<StringPiece> overlayable_name = xml::FindNonEmptyAttribute(parser, "name"); if (!overlayable_name) { - diag_->Error(DiagMessage(out_resource->source) - << "<overlayable> tag must have a 'name' attribute"); + diag_->Error(android::DiagMessage(out_resource->source) + << "<overlayable> tag must have a 'name' attribute"); return false; } @@ -1156,7 +1153,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource android::base::StringPrintf("%s://", Overlayable::kActorScheme); std::optional<StringPiece> overlayable_actor = xml::FindNonEmptyAttribute(parser, "actor"); if (overlayable_actor && !util::StartsWith(overlayable_actor.value(), kActorUriScheme)) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "specified <overlayable> tag 'actor' attribute must use the scheme '" << Overlayable::kActorScheme << "'"); return false; @@ -1190,13 +1187,13 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource continue; } - const Source element_source = source_.WithLine(parser->line_number()); + const android::Source element_source = source_.WithLine(parser->line_number()); const std::string& element_name = parser->element_name(); const std::string& element_namespace = parser->element_namespace(); if (element_namespace.empty() && element_name == "item") { if (current_policies == PolicyFlags::NONE) { - diag_->Error(DiagMessage(element_source) - << "<item> within an <overlayable> must be inside a <policy> block"); + diag_->Error(android::DiagMessage(element_source) + << "<item> within an <overlayable> must be inside a <policy> block"); error = true; continue; } @@ -1204,7 +1201,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource // Items specify the name and type of resource that should be overlayable std::optional<StringPiece> item_name = xml::FindNonEmptyAttribute(parser, "name"); if (!item_name) { - diag_->Error(DiagMessage(element_source) + diag_->Error(android::DiagMessage(element_source) << "<item> within an <overlayable> must have a 'name' attribute"); error = true; continue; @@ -1212,7 +1209,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource std::optional<StringPiece> item_type = xml::FindNonEmptyAttribute(parser, "type"); if (!item_type) { - diag_->Error(DiagMessage(element_source) + diag_->Error(android::DiagMessage(element_source) << "<item> within an <overlayable> must have a 'type' attribute"); error = true; continue; @@ -1220,7 +1217,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource std::optional<ResourceNamedTypeRef> type = ParseResourceNamedType(item_type.value()); if (!type) { - diag_->Error(DiagMessage(element_source) + diag_->Error(android::DiagMessage(element_source) << "invalid resource type '" << item_type.value() << "' in <item> within an <overlayable>"); error = true; @@ -1234,21 +1231,22 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource ParsedResource child_resource{}; child_resource.name.type = type->ToResourceNamedType(); - child_resource.name.entry = item_name.value().to_string(); + child_resource.name.entry = std::string(item_name.value()); child_resource.overlayable_item = overlayable_item; out_resource->child_resources.push_back(std::move(child_resource)); } else if (element_namespace.empty() && element_name == "policy") { if (current_policies != PolicyFlags::NONE) { // If the policy list is not empty, then we are currently inside a policy element - diag_->Error(DiagMessage(element_source) << "<policy> blocks cannot be recursively nested"); + diag_->Error(android::DiagMessage(element_source) + << "<policy> blocks cannot be recursively nested"); error = true; break; } else if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { // Parse the polices separated by vertical bar characters to allow for specifying multiple // policies. Items within the policy tag will have the specified policy. - for (const StringPiece& part : util::Tokenize(maybe_type.value(), '|')) { + for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) { StringPiece trimmed_part = util::TrimWhitespace(part); const auto policy = std::find_if(kPolicyStringToFlag.begin(), kPolicyStringToFlag.end(), @@ -1256,7 +1254,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource return trimmed_part == it.first; }); if (policy == kPolicyStringToFlag.end()) { - diag_->Error(DiagMessage(element_source) + diag_->Error(android::DiagMessage(element_source) << "<policy> has unsupported type '" << trimmed_part << "'"); error = true; continue; @@ -1265,14 +1263,15 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource current_policies |= policy->second; } } else { - diag_->Error(DiagMessage(element_source) + diag_->Error(android::DiagMessage(element_source) << "<policy> must have a 'type' attribute"); error = true; continue; } } else if (!ShouldIgnoreElement(element_namespace, element_name)) { - diag_->Error(DiagMessage(element_source) << "invalid element <" << element_name << "> " - << " in <overlayable>"); + diag_->Error(android::DiagMessage(element_source) + << "invalid element <" << element_name << "> " + << " in <overlayable>"); error = true; break; } @@ -1304,9 +1303,9 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, // Attributes only end up in default configuration. if (out_resource->config != ConfigDescription::DefaultConfig()) { - diag_->Warn(DiagMessage(out_resource->source) - << "ignoring configuration '" << out_resource->config - << "' for attribute " << out_resource->name); + diag_->Warn(android::DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config << "' for attribute " + << out_resource->name); out_resource->config = ConfigDescription::DefaultConfig(); } @@ -1316,7 +1315,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, if (maybe_format) { type_mask = ParseFormatAttribute(maybe_format.value()); if (type_mask == 0) { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "invalid attribute format '" << maybe_format.value() << "'"); return false; } @@ -1327,7 +1326,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, if (std::optional<StringPiece> maybe_min_str = xml::FindAttribute(parser, "min")) { StringPiece min_str = util::TrimWhitespace(maybe_min_str.value()); if (!min_str.empty()) { - std::u16string min_str16 = util::Utf8ToUtf16(min_str); + std::u16string min_str16 = android::util::Utf8ToUtf16(min_str); android::Res_value value; if (android::ResTable::stringToInt(min_str16.data(), min_str16.size(), &value)) { maybe_min = static_cast<int32_t>(value.data); @@ -1335,7 +1334,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, } if (!maybe_min) { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "invalid 'min' value '" << min_str << "'"); return false; } @@ -1344,7 +1343,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, if (std::optional<StringPiece> maybe_max_str = xml::FindAttribute(parser, "max")) { StringPiece max_str = util::TrimWhitespace(maybe_max_str.value()); if (!max_str.empty()) { - std::u16string max_str16 = util::Utf8ToUtf16(max_str); + std::u16string max_str16 = android::util::Utf8ToUtf16(max_str); android::Res_value value; if (android::ResTable::stringToInt(max_str16.data(), max_str16.size(), &value)) { maybe_max = static_cast<int32_t>(value.data); @@ -1352,7 +1351,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, } if (!maybe_max) { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "invalid 'max' value '" << max_str << "'"); return false; } @@ -1360,7 +1359,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, if ((maybe_min || maybe_max) && (type_mask & android::ResTable_map::TYPE_INTEGER) == 0) { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "'min' and 'max' can only be used when format='integer'"); return false; } @@ -1378,20 +1377,20 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, const size_t depth = parser->depth(); while (xml::XmlPullParser::NextChildNode(parser, depth)) { if (parser->event() == xml::XmlPullParser::Event::kComment) { - comment = util::TrimWhitespace(parser->comment()).to_string(); + comment = std::string(util::TrimWhitespace(parser->comment())); continue; } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) { // Skip text. continue; } - const Source item_source = source_.WithLine(parser->line_number()); + const android::Source item_source = source_.WithLine(parser->line_number()); const std::string& element_namespace = parser->element_namespace(); const std::string& element_name = parser->element_name(); if (element_namespace.empty() && (element_name == "flag" || element_name == "enum")) { if (element_name == "enum") { if (type_mask & android::ResTable_map::TYPE_FLAGS) { - diag_->Error(DiagMessage(item_source) + diag_->Error(android::DiagMessage(item_source) << "can not define an <enum>; already defined a <flag>"); error = true; continue; @@ -1400,7 +1399,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, } else if (element_name == "flag") { if (type_mask & android::ResTable_map::TYPE_ENUM) { - diag_->Error(DiagMessage(item_source) + diag_->Error(android::DiagMessage(item_source) << "can not define a <flag>; already defined an <enum>"); error = true; continue; @@ -1425,11 +1424,10 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, auto insert_result = items.insert(std::move(symbol)); if (!insert_result.second) { const Attribute::Symbol& existing_symbol = *insert_result.first; - diag_->Error(DiagMessage(item_source) - << "duplicate symbol '" - << existing_symbol.symbol.name.value().entry << "'"); + diag_->Error(android::DiagMessage(item_source) + << "duplicate symbol '" << existing_symbol.symbol.name.value().entry << "'"); - diag_->Note(DiagMessage(existing_symbol.symbol.GetSource()) + diag_->Note(android::DiagMessage(existing_symbol.symbol.GetSource()) << "first defined here"); error = true; } @@ -1437,7 +1435,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, error = true; } } else if (!ShouldIgnoreElement(element_namespace, element_name)) { - diag_->Error(DiagMessage(item_source) << ":" << element_name << ">"); + diag_->Error(android::DiagMessage(item_source) << ":" << element_name << ">"); error = true; } @@ -1459,29 +1457,28 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, } std::optional<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(xml::XmlPullParser* parser, - const StringPiece& tag) { - const Source source = source_.WithLine(parser->line_number()); + StringPiece tag) { + const android::Source source = source_.WithLine(parser->line_number()); std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); if (!maybe_name) { - diag_->Error(DiagMessage(source) << "no attribute 'name' found for tag <" - << tag << ">"); + diag_->Error(android::DiagMessage(source) + << "no attribute 'name' found for tag <" << tag << ">"); return {}; } std::optional<StringPiece> maybe_value = xml::FindNonEmptyAttribute(parser, "value"); if (!maybe_value) { - diag_->Error(DiagMessage(source) << "no attribute 'value' found for tag <" - << tag << ">"); + diag_->Error(android::DiagMessage(source) + << "no attribute 'value' found for tag <" << tag << ">"); return {}; } - std::u16string value16 = util::Utf8ToUtf16(maybe_value.value()); + std::u16string value16 = android::util::Utf8ToUtf16(maybe_value.value()); android::Res_value val; if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) { - diag_->Error(DiagMessage(source) << "invalid value '" << maybe_value.value() - << "' for <" << tag - << ">; must be an integer"); + diag_->Error(android::DiagMessage(source) << "invalid value '" << maybe_value.value() + << "' for <" << tag << ">; must be an integer"); return {}; } @@ -1492,17 +1489,18 @@ std::optional<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(xml::XmlPul } bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) { - const Source source = source_.WithLine(parser->line_number()); + const android::Source source = source_.WithLine(parser->line_number()); std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); if (!maybe_name) { - diag_->Error(DiagMessage(source) << "<item> must have a 'name' attribute"); + diag_->Error(android::DiagMessage(source) << "<item> must have a 'name' attribute"); return false; } std::optional<Reference> maybe_key = ResourceUtils::ParseXmlAttributeName(maybe_name.value()); if (!maybe_key) { - diag_->Error(DiagMessage(source) << "invalid attribute name '" << maybe_name.value() << "'"); + diag_->Error(android::DiagMessage(source) + << "invalid attribute name '" << maybe_name.value() << "'"); return false; } @@ -1511,7 +1509,7 @@ bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) { std::unique_ptr<Item> value = ParseXml(parser, 0, kAllowRawString); if (!value) { - diag_->Error(DiagMessage(source) << "could not parse style item"); + diag_->Error(android::DiagMessage(source) << "could not parse style item"); return false; } @@ -1532,7 +1530,7 @@ bool ResourceParser::ParseStyle(const ResourceType type, xml::XmlPullParser* par std::string err_str; style->parent = ResourceUtils::ParseStyleParentReference(maybe_parent.value(), &err_str); if (!style->parent) { - diag_->Error(DiagMessage(out_resource->source) << err_str); + diag_->Error(android::DiagMessage(out_resource->source) << err_str); return false; } @@ -1566,7 +1564,7 @@ bool ResourceParser::ParseStyle(const ResourceType type, xml::XmlPullParser* par error |= !ParseStyleItem(parser, style.get()); } else if (!ShouldIgnoreElement(element_namespace, element_name)) { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << ":" << element_name << ">"); error = true; } @@ -1585,7 +1583,7 @@ bool ResourceParser::ParseArray(xml::XmlPullParser* parser, ParsedResource* out_ if (std::optional<StringPiece> format_attr = xml::FindNonEmptyAttribute(parser, "format")) { resource_format = ParseFormatTypeNoEnumsOrFlags(format_attr.value()); if (resource_format == 0u) { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "'" << format_attr.value() << "' is an invalid format"); return false; } @@ -1613,7 +1611,7 @@ bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser, if (std::optional<StringPiece> translatable_attr = xml::FindAttribute(parser, "translatable")) { std::optional<bool> maybe_translatable = ResourceUtils::ParseBool(translatable_attr.value()); if (!maybe_translatable) { - diag_->Error(DiagMessage(out_resource->source) + diag_->Error(android::DiagMessage(out_resource->source) << "invalid value for 'translatable'. Must be a boolean"); return false; } @@ -1629,13 +1627,13 @@ bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser, continue; } - const Source item_source = source_.WithLine(parser->line_number()); + const android::Source item_source = source_.WithLine(parser->line_number()); const std::string& element_namespace = parser->element_namespace(); const std::string& element_name = parser->element_name(); if (element_namespace.empty() && element_name == "item") { std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString); if (!item) { - diag_->Error(DiagMessage(item_source) << "could not parse array item"); + diag_->Error(android::DiagMessage(item_source) << "could not parse array item"); error = true; continue; } @@ -1643,9 +1641,8 @@ bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser, array->elements.emplace_back(std::move(item)); } else if (!ShouldIgnoreElement(element_namespace, element_name)) { - diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) - << "unknown tag <" << element_namespace << ":" - << element_name << ">"); + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) + << "unknown tag <" << element_namespace << ":" << element_name << ">"); error = true; } } @@ -1673,15 +1670,14 @@ bool ResourceParser::ParsePlural(xml::XmlPullParser* parser, continue; } - const Source item_source = source_.WithLine(parser->line_number()); + const android::Source item_source = source_.WithLine(parser->line_number()); const std::string& element_namespace = parser->element_namespace(); const std::string& element_name = parser->element_name(); if (element_namespace.empty() && element_name == "item") { std::optional<StringPiece> maybe_quantity = xml::FindNonEmptyAttribute(parser, "quantity"); if (!maybe_quantity) { - diag_->Error(DiagMessage(item_source) - << "<item> in <plurals> requires attribute " - << "'quantity'"); + diag_->Error(android::DiagMessage(item_source) << "<item> in <plurals> requires attribute " + << "'quantity'"); error = true; continue; } @@ -1702,16 +1698,16 @@ bool ResourceParser::ParsePlural(xml::XmlPullParser* parser, } else if (trimmed_quantity == "other") { index = Plural::Other; } else { - diag_->Error(DiagMessage(item_source) - << "<item> in <plural> has invalid value '" - << trimmed_quantity << "' for attribute 'quantity'"); + diag_->Error(android::DiagMessage(item_source) + << "<item> in <plural> has invalid value '" << trimmed_quantity + << "' for attribute 'quantity'"); error = true; continue; } if (plural->values[index]) { - diag_->Error(DiagMessage(item_source) << "duplicate quantity '" - << trimmed_quantity << "'"); + diag_->Error(android::DiagMessage(item_source) + << "duplicate quantity '" << trimmed_quantity << "'"); error = true; continue; } @@ -1725,9 +1721,8 @@ bool ResourceParser::ParsePlural(xml::XmlPullParser* parser, plural->values[index]->SetSource(item_source); } else if (!ShouldIgnoreElement(element_namespace, element_name)) { - diag_->Error(DiagMessage(item_source) << "unknown tag <" - << element_namespace << ":" - << element_name << ">"); + diag_->Error(android::DiagMessage(item_source) + << "unknown tag <" << element_namespace << ":" << element_name << ">"); error = true; } } @@ -1756,9 +1751,9 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, // Declare-styleable only ends up in default config; if (out_resource->config != ConfigDescription::DefaultConfig()) { - diag_->Warn(DiagMessage(out_resource->source) - << "ignoring configuration '" << out_resource->config - << "' for styleable " << out_resource->name.entry); + diag_->Warn(android::DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config << "' for styleable " + << out_resource->name.entry); out_resource->config = ConfigDescription::DefaultConfig(); } @@ -1769,20 +1764,21 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, const size_t depth = parser->depth(); while (xml::XmlPullParser::NextChildNode(parser, depth)) { if (parser->event() == xml::XmlPullParser::Event::kComment) { - comment = util::TrimWhitespace(parser->comment()).to_string(); + comment = std::string(util::TrimWhitespace(parser->comment())); continue; } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) { // Ignore text. continue; } - const Source item_source = source_.WithLine(parser->line_number()); + const android::Source item_source = source_.WithLine(parser->line_number()); const std::string& element_namespace = parser->element_namespace(); const std::string& element_name = parser->element_name(); if (element_namespace.empty() && element_name == "attr") { std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); if (!maybe_name) { - diag_->Error(DiagMessage(item_source) << "<attr> tag must have a 'name' attribute"); + diag_->Error(android::DiagMessage(item_source) + << "<attr> tag must have a 'name' attribute"); error = true; continue; } @@ -1792,8 +1788,8 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, // Eg. <attr name="android:text" /> std::optional<Reference> maybe_ref = ResourceUtils::ParseXmlAttributeName(maybe_name.value()); if (!maybe_ref) { - diag_->Error(DiagMessage(item_source) << "<attr> tag has invalid name '" - << maybe_name.value() << "'"); + diag_->Error(android::DiagMessage(item_source) + << "<attr> tag has invalid name '" << maybe_name.value() << "'"); error = true; continue; } @@ -1831,9 +1827,8 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, } } else if (!ShouldIgnoreElement(element_namespace, element_name)) { - diag_->Error(DiagMessage(item_source) << "unknown tag <" - << element_namespace << ":" - << element_name << ">"); + diag_->Error(android::DiagMessage(item_source) + << "unknown tag <" << element_namespace << ":" << element_name << ">"); error = true; } diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 548f5f9531fd..012a056dccf3 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -20,14 +20,13 @@ #include <memory> #include <optional> +#include "ResourceTable.h" +#include "ResourceValues.h" #include "android-base/macros.h" #include "androidfw/ConfigDescription.h" +#include "androidfw/IDiagnostics.h" #include "androidfw/StringPiece.h" - -#include "Diagnostics.h" -#include "ResourceTable.h" -#include "ResourceValues.h" -#include "StringPool.h" +#include "androidfw/StringPool.h" #include "xml/XmlPullParser.h" namespace aapt { @@ -59,10 +58,10 @@ struct ResourceParserOptions { struct FlattenedXmlSubTree { std::string raw_value; - StyleString style_string; + android::StyleString style_string; std::vector<UntranslatableSection> untranslatable_sections; xml::IPackageDeclStack* namespace_resolver; - Source source; + android::Source source; }; /* @@ -70,7 +69,7 @@ struct FlattenedXmlSubTree { */ class ResourceParser { public: - ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, + ResourceParser(android::IDiagnostics* diag, ResourceTable* table, const android::Source& source, const android::ConfigDescription& config, const ResourceParserOptions& options = {}); bool Parse(xml::XmlPullParser* parser); @@ -78,7 +77,7 @@ class ResourceParser { static std::unique_ptr<Item> ParseXml(const FlattenedXmlSubTree& xmlsub_tree, uint32_t type_mask, bool allow_raw_value, ResourceTable& table, const android::ConfigDescription& config, - IDiagnostics& diag); + android::IDiagnostics& diag); private: DISALLOW_COPY_AND_ASSIGN(ResourceParser); @@ -93,7 +92,7 @@ class ResourceParser { // `out_untranslatable_sections` contains the sections of the string that should not be // translated. bool FlattenXmlSubtree(xml::XmlPullParser* parser, std::string* out_raw_string, - StyleString* out_style_string, + android::StyleString* out_style_string, std::vector<UntranslatableSection>* out_untranslatable_sections); /* @@ -123,7 +122,7 @@ class ResourceParser { bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak); std::optional<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser, - const android::StringPiece& tag); + android::StringPiece tag); bool ParseStyle(ResourceType type, xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseStyleItem(xml::XmlPullParser* parser, Style* style); bool ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource); @@ -133,9 +132,9 @@ class ResourceParser { bool ParseArrayImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, uint32_t typeMask); bool ParsePlural(xml::XmlPullParser* parser, ParsedResource* out_resource); - IDiagnostics* diag_; + android::IDiagnostics* diag_; ResourceTable* table_; - Source source_; + android::Source source_; android::ConfigDescription config_; ResourceParserOptions options_; }; diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 556ffa221db5..b59b16574c42 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -50,7 +50,7 @@ constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?> TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); ResourceTable table; - ResourceParser parser(context->GetDiagnostics(), &table, Source{"test"}, {}); + ResourceParser parser(context->GetDiagnostics(), &table, android::Source{"test"}, {}); std::string input = kXmlPreamble; input += R"(<attr name="foo"/>)"; @@ -65,13 +65,13 @@ class ResourceParserTest : public ::testing::Test { context_ = test::ContextBuilder().Build(); } - ::testing::AssertionResult TestParse(const StringPiece& str) { + ::testing::AssertionResult TestParse(StringPiece str) { return TestParse(str, ConfigDescription{}); } - ::testing::AssertionResult TestParse(const StringPiece& str, const ConfigDescription& config) { + ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) { ResourceParserOptions parserOptions; - ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"}, config, + ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config, parserOptions); std::string input = kXmlPreamble; @@ -711,7 +711,7 @@ TEST_F(ResourceParserTest, ParseDeclareStyleablePreservingVisibility) { </declare-styleable> <public type="styleable" name="bar" /> </resources>)"); - ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"}, + ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, ConfigDescription::DefaultConfig(), ResourceParserOptions{.preserve_visibility_of_styleables = true}); diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 98cce268e213..a3b0b45df5c3 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -43,26 +43,27 @@ namespace aapt { const char* Overlayable::kActorScheme = "overlay"; namespace { -bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) { - return lhs->type < rhs; +bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, + const ResourceNamedTypeRef& rhs) { + return lhs->named_type < rhs; } template <typename T> -bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) { +bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, StringPiece rhs) { return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0; } template <typename T> -bool greater_than_struct_with_name(const StringPiece& lhs, const std::unique_ptr<T>& rhs) { +bool greater_than_struct_with_name(StringPiece lhs, const std::unique_ptr<T>& rhs) { return rhs->name.compare(0, rhs->name.size(), lhs.data(), lhs.size()) > 0; } template <typename T> struct NameEqualRange { - bool operator()(const std::unique_ptr<T>& lhs, const StringPiece& rhs) const { + bool operator()(const std::unique_ptr<T>& lhs, StringPiece rhs) const { return less_than_struct_with_name<T>(lhs, rhs); } - bool operator()(const StringPiece& lhs, const std::unique_ptr<T>& rhs) const { + bool operator()(StringPiece lhs, const std::unique_ptr<T>& rhs) const { return greater_than_struct_with_name<T>(lhs, rhs); } }; @@ -77,7 +78,7 @@ bool less_than_struct_with_name_and_id(const T& lhs, } template <typename T, typename Func, typename Elements> -T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) { +T* FindElementsRunAction(android::StringPiece name, Elements& entries, Func action) { const auto iter = std::lower_bound(entries.begin(), entries.end(), name, less_than_struct_with_name<T>); const bool found = iter != entries.end() && name == (*iter)->name; @@ -86,7 +87,7 @@ T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Fu struct ConfigKey { const ConfigDescription* config; - const StringPiece& product; + StringPiece product; }; template <typename T> @@ -103,47 +104,53 @@ bool lt_config_key_ref(const T& lhs, const ConfigKey& rhs) { ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) { } -ResourceTablePackage* ResourceTable::FindPackage(const android::StringPiece& name) const { +ResourceTablePackage* ResourceTable::FindPackage(android::StringPiece name) const { return FindElementsRunAction<ResourceTablePackage>( name, packages, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; }); } -ResourceTablePackage* ResourceTable::FindOrCreatePackage(const android::StringPiece& name) { +ResourceTablePackage* ResourceTable::FindOrCreatePackage(android::StringPiece name) { return FindElementsRunAction<ResourceTablePackage>(name, packages, [&](bool found, auto& iter) { return found ? iter->get() : packages.emplace(iter, new ResourceTablePackage(name))->get(); }); } template <typename Func, typename Elements> -static ResourceTableType* FindTypeRunAction(ResourceType type, Elements& entries, Func action) { +static ResourceTableType* FindTypeRunAction(const ResourceNamedTypeRef& type, Elements& entries, + Func action) { const auto iter = std::lower_bound(entries.begin(), entries.end(), type, less_than_type); - const bool found = iter != entries.end() && type == (*iter)->type; + const bool found = iter != entries.end() && type == (*iter)->named_type; return action(found, iter); } -ResourceTableType* ResourceTablePackage::FindType(ResourceType type) const { +ResourceTableType* ResourceTablePackage::FindTypeWithDefaultName(const ResourceType type) const { + auto named_type = ResourceNamedTypeWithDefaultName(type); + return FindType(named_type); +} + +ResourceTableType* ResourceTablePackage::FindType(const ResourceNamedTypeRef& type) const { return FindTypeRunAction(type, types, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; }); } -ResourceTableType* ResourceTablePackage::FindOrCreateType(ResourceType type) { +ResourceTableType* ResourceTablePackage::FindOrCreateType(const ResourceNamedTypeRef& type) { return FindTypeRunAction(type, types, [&](bool found, auto& iter) { return found ? iter->get() : types.emplace(iter, new ResourceTableType(type))->get(); }); } -ResourceEntry* ResourceTableType::CreateEntry(const android::StringPiece& name) { +ResourceEntry* ResourceTableType::CreateEntry(android::StringPiece name) { return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) { return entries.emplace(iter, new ResourceEntry(name))->get(); }); } -ResourceEntry* ResourceTableType::FindEntry(const android::StringPiece& name) const { +ResourceEntry* ResourceTableType::FindEntry(android::StringPiece name) const { return FindElementsRunAction<ResourceEntry>( name, entries, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; }); } -ResourceEntry* ResourceTableType::FindOrCreateEntry(const android::StringPiece& name) { +ResourceEntry* ResourceTableType::FindOrCreateEntry(android::StringPiece name) { return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) { return found ? iter->get() : entries.emplace(iter, new ResourceEntry(name))->get(); }); @@ -176,7 +183,7 @@ const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescrip } ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config, - const StringPiece& product) { + StringPiece product) { auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product}, lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>); if (iter != values.end()) { @@ -329,7 +336,7 @@ struct PackageViewComparer { struct TypeViewComparer { bool operator()(const ResourceTableTypeView& lhs, const ResourceTableTypeView& rhs) { - return lhs.id != rhs.id ? lhs.id < rhs.id : lhs.type < rhs.type; + return lhs.id != rhs.id ? lhs.id < rhs.id : lhs.named_type < rhs.named_type; } }; @@ -355,7 +362,8 @@ void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePacka id ? id.value().package_id() : std::optional<uint8_t>{}}; auto view_package = package_inserter.Insert(table.packages, std::move(new_package)); - ResourceTableTypeView new_type{type->type, id ? id.value().type_id() : std::optional<uint8_t>{}}; + ResourceTableTypeView new_type{type->named_type, + id ? id.value().type_id() : std::optional<uint8_t>{}}; auto view_type = type_inserter.Insert(view_package->types, std::move(new_type)); if (visibility.level == Visibility::Level::kPublic) { @@ -420,13 +428,14 @@ ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptio // we can reuse those packages for other types that need to be extracted from this package. // `start_index` is the index of the first newly created package that can be reused. const size_t start_index = new_packages.size(); - std::map<ResourceType, size_t> type_new_package_index; + std::map<ResourceNamedType, size_t> type_new_package_index; for (auto type_it = package.types.begin(); type_it != package.types.end();) { auto& type = *type_it; - auto type_index_iter = type_new_package_index.find(type.type); + auto type_index_iter = type_new_package_index.find(type.named_type); if (type_index_iter == type_new_package_index.end()) { // First occurrence of the resource type in this package. Keep it in this package. - type_new_package_index.insert(type_index_iter, std::make_pair(type.type, start_index)); + type_new_package_index.insert(type_index_iter, + std::make_pair(type.named_type, start_index)); ++type_it; continue; } @@ -440,7 +449,7 @@ ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptio // Move the type into a new package auto& other_package = new_packages[index]; - type_new_package_index[type.type] = index + 1; + type_new_package_index[type.named_type] = index + 1; type_inserter.Insert(other_package.types, std::move(type)); type_it = package.types.erase(type_it); } @@ -455,25 +464,26 @@ ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptio return view; } -bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) { +bool ResourceTable::AddResource(NewResource&& res, android::IDiagnostics* diag) { CHECK(diag != nullptr) << "Diagnostic pointer is null"; const bool validate = validation_ == Validation::kEnabled; - const Source source = res.value ? res.value->GetSource() : Source{}; + const android::Source source = res.value ? res.value->GetSource() : android::Source{}; if (validate && !res.allow_mangled && !IsValidResourceEntryName(res.name.entry)) { - diag->Error(DiagMessage(source) + diag->Error(android::DiagMessage(source) << "resource '" << res.name << "' has invalid entry name '" << res.name.entry); return false; } if (res.id.has_value() && !res.id->first.is_valid()) { - diag->Error(DiagMessage(source) << "trying to add resource '" << res.name << "' with ID " - << res.id->first << " but that ID is invalid"); + diag->Error(android::DiagMessage(source) + << "trying to add resource '" << res.name << "' with ID " << res.id->first + << " but that ID is invalid"); return false; } auto package = FindOrCreatePackage(res.name.package); - auto type = package->FindOrCreateType(res.name.type.type); + auto type = package->FindOrCreateType(res.name.type); auto entry_it = std::equal_range(type->entries.begin(), type->entries.end(), res.name.entry, NameEqualRange<ResourceEntry>{}); const size_t entry_count = std::distance(entry_it.first, entry_it.second); @@ -504,7 +514,7 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) { if (res.id.has_value()) { if (entry->id && entry->id.value() != res.id->first) { if (res.id->second != OnIdConflict::CREATE_ENTRY) { - diag->Error(DiagMessage(source) + diag->Error(android::DiagMessage(source) << "trying to add resource '" << res.name << "' with ID " << res.id->first << " but resource already has ID " << entry->id.value()); return false; @@ -532,9 +542,9 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) { if (res.overlayable.has_value()) { if (entry->overlayable_item) { - diag->Error(DiagMessage(res.overlayable->source) + diag->Error(android::DiagMessage(res.overlayable->source) << "duplicate overlayable declaration for resource '" << res.name << "'"); - diag->Error(DiagMessage(entry->overlayable_item.value().source) + diag->Error(android::DiagMessage(entry->overlayable_item.value().source) << "previous declaration here"); return false; } @@ -572,9 +582,10 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) { break; case CollisionResult::kConflict: - diag->Error(DiagMessage(source) << "duplicate value for resource '" << res.name << "' " - << "with config '" << res.config << "'"); - diag->Error(DiagMessage(source) << "resource previously defined here"); + diag->Error(android::DiagMessage(source) + << "duplicate value for resource '" << res.name << "' " + << "with config '" << res.config << "'"); + diag->Error(android::DiagMessage(source) << "resource previously defined here"); return false; case CollisionResult::kKeepOriginal: @@ -593,7 +604,7 @@ std::optional<ResourceTable::SearchResult> ResourceTable::FindResource( return {}; } - ResourceTableType* type = package->FindType(name.type.type); + ResourceTableType* type = package->FindType(name.type); if (type == nullptr) { return {}; } @@ -612,7 +623,7 @@ std::optional<ResourceTable::SearchResult> ResourceTable::FindResource(const Res return {}; } - ResourceTableType* type = package->FindType(name.type.type); + ResourceTableType* type = package->FindType(name.type); if (type == nullptr) { return {}; } @@ -633,7 +644,7 @@ bool ResourceTable::RemoveResource(const ResourceNameRef& name, ResourceId id) c return {}; } - ResourceTableType* type = package->FindType(name.type.type); + ResourceTableType* type = package->FindType(name.type); if (type == nullptr) { return {}; } @@ -655,7 +666,7 @@ std::unique_ptr<ResourceTable> ResourceTable::Clone() const { for (const auto& pkg : packages) { ResourceTablePackage* new_pkg = new_table->FindOrCreatePackage(pkg->name); for (const auto& type : pkg->types) { - ResourceTableType* new_type = new_pkg->FindOrCreateType(type->type); + ResourceTableType* new_type = new_pkg->FindOrCreateType(type->named_type); new_type->visibility_level = type->visibility_level; for (const auto& entry : type->entries) { diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 2e17659b0679..bb286a8abdaa 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -17,17 +17,6 @@ #ifndef AAPT_RESOURCE_TABLE_H #define AAPT_RESOURCE_TABLE_H -#include "Diagnostics.h" -#include "Resource.h" -#include "ResourceValues.h" -#include "Source.h" -#include "StringPool.h" -#include "io/File.h" - -#include "android-base/macros.h" -#include "androidfw/ConfigDescription.h" -#include "androidfw/StringPiece.h" - #include <functional> #include <map> #include <memory> @@ -36,6 +25,16 @@ #include <unordered_map> #include <vector> +#include "Resource.h" +#include "ResourceValues.h" +#include "android-base/macros.h" +#include "androidfw/ConfigDescription.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/Source.h" +#include "androidfw/StringPiece.h" +#include "androidfw/StringPool.h" +#include "io/File.h" + using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; namespace aapt { @@ -49,7 +48,7 @@ struct Visibility { }; Level level = Level::kUndefined; - Source source; + android::Source source; std::string comment; // Indicates that the resource id may change across builds and that the public R.java identifier @@ -60,28 +59,28 @@ struct Visibility { // Represents <add-resource> in an overlay. struct AllowNew { - Source source; + android::Source source; std::string comment; }; // Represents the staged resource id of a finalized resource. struct StagedId { ResourceId id; - Source source; + android::Source source; }; struct Overlayable { Overlayable() = default; - Overlayable(const android::StringPiece& name, const android::StringPiece& actor) - : name(name.to_string()), actor(actor.to_string()) {} - Overlayable(const android::StringPiece& name, const android::StringPiece& actor, - const Source& source) - : name(name.to_string()), actor(actor.to_string()), source(source ){} + Overlayable(android::StringPiece name, android::StringPiece actor) : name(name), actor(actor) { + } + Overlayable(android::StringPiece name, android::StringPiece actor, const android::Source& source) + : name(name), actor(actor), source(source) { + } static const char* kActorScheme; std::string name; std::string actor; - Source source; + android::Source source; }; // Represents a declaration that a resource is overlayable at runtime. @@ -91,7 +90,7 @@ struct OverlayableItem { std::shared_ptr<Overlayable> overlayable; PolicyFlags policies = PolicyFlags::NONE; std::string comment; - Source source; + android::Source source; }; class ResourceConfigValue { @@ -105,8 +104,9 @@ class ResourceConfigValue { // The actual Value. std::unique_ptr<Value> value; - ResourceConfigValue(const android::ConfigDescription& config, const android::StringPiece& product) - : config(config), product(product.to_string()) {} + ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product) + : config(config), product(product) { + } private: DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue); @@ -136,7 +136,8 @@ class ResourceEntry { // The resource's values for each configuration. std::vector<std::unique_ptr<ResourceConfigValue>> values; - explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {} + explicit ResourceEntry(android::StringPiece name) : name(name) { + } ResourceConfigValue* FindValue(const android::ConfigDescription& config, android::StringPiece product = {}); @@ -144,7 +145,7 @@ class ResourceEntry { android::StringPiece product = {}) const; ResourceConfigValue* FindOrCreateValue(const android::ConfigDescription& config, - const android::StringPiece& product); + android::StringPiece product); std::vector<ResourceConfigValue*> FindAllValues(const android::ConfigDescription& config); template <typename Func> @@ -168,7 +169,7 @@ class ResourceEntry { class ResourceTableType { public: // The logical type of resource (string, drawable, layout, etc.). - const ResourceType type; + const ResourceNamedType named_type; // Whether this type is public (and must maintain the same type ID across builds). Visibility::Level visibility_level = Visibility::Level::kUndefined; @@ -176,11 +177,13 @@ class ResourceTableType { // List of resources for this type. std::vector<std::unique_ptr<ResourceEntry>> entries; - explicit ResourceTableType(const ResourceType type) : type(type) {} + explicit ResourceTableType(const ResourceNamedTypeRef& type) + : named_type(type.ToResourceNamedType()) { + } - ResourceEntry* CreateEntry(const android::StringPiece& name); - ResourceEntry* FindEntry(const android::StringPiece& name) const; - ResourceEntry* FindOrCreateEntry(const android::StringPiece& name); + ResourceEntry* CreateEntry(android::StringPiece name); + ResourceEntry* FindEntry(android::StringPiece name) const; + ResourceEntry* FindOrCreateEntry(android::StringPiece name); private: DISALLOW_COPY_AND_ASSIGN(ResourceTableType); @@ -192,12 +195,13 @@ class ResourceTablePackage { std::vector<std::unique_ptr<ResourceTableType>> types; - explicit ResourceTablePackage(const android::StringPiece& name) : name(name.to_string()) { + explicit ResourceTablePackage(android::StringPiece name) : name(name) { } ResourceTablePackage() = default; - ResourceTableType* FindType(ResourceType type) const; - ResourceTableType* FindOrCreateType(ResourceType type); + ResourceTableType* FindTypeWithDefaultName(const ResourceType type) const; + ResourceTableType* FindType(const ResourceNamedTypeRef& type) const; + ResourceTableType* FindOrCreateType(const ResourceNamedTypeRef& type); private: DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage); @@ -217,7 +221,7 @@ struct ResourceTableEntryView { }; struct ResourceTableTypeView { - ResourceType type; + ResourceNamedType named_type; std::optional<uint8_t> id; Visibility::Level visibility_level = Visibility::Level::kUndefined; @@ -297,7 +301,7 @@ class ResourceTable { ResourceTable() = default; explicit ResourceTable(Validation validation); - bool AddResource(NewResource&& res, IDiagnostics* diag); + bool AddResource(NewResource&& res, android::IDiagnostics* diag); // Retrieves a sorted a view of the packages, types, and entries sorted in ascending resource id // order. @@ -316,8 +320,8 @@ class ResourceTable { // Returns the package struct with the given name, or nullptr if such a package does not // exist. The empty string is a valid package and typically is used to represent the // 'current' package before it is known to the ResourceTable. - ResourceTablePackage* FindPackage(const android::StringPiece& name) const; - ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name); + ResourceTablePackage* FindPackage(android::StringPiece name) const; + ResourceTablePackage* FindOrCreatePackage(android::StringPiece name); std::unique_ptr<ResourceTable> Clone() const; @@ -330,7 +334,7 @@ class ResourceTable { // When `string_pool` references are destroyed (as they will be when `packages` is destroyed), // they decrement a refCount, which would cause invalid memory access if the pool was already // destroyed. - StringPool string_pool; + android::StringPool string_pool; // The list of packages in this table, sorted alphabetically by package name and increasing // package ID (missing ID being the lowest). diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index de73d2c203e4..54b98d13aa0a 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -15,15 +15,16 @@ */ #include "ResourceTable.h" -#include "Diagnostics.h" -#include "ResourceValues.h" -#include "test/Test.h" -#include "util/Util.h" #include <algorithm> #include <ostream> #include <string> +#include "ResourceValues.h" +#include "androidfw/IDiagnostics.h" +#include "test/Test.h" +#include "util/Util.h" + using ::android::ConfigDescription; using ::android::StringPiece; using ::testing::Eq; @@ -186,7 +187,7 @@ static StringPiece LevelToString(Visibility::Level level) { static ::testing::AssertionResult VisibilityOfResource(const ResourceTable& table, const ResourceNameRef& name, Visibility::Level level, - const StringPiece& comment) { + StringPiece comment) { std::optional<ResourceTable::SearchResult> result = table.FindResource(name); if (!result) { return ::testing::AssertionFailure() << "no resource '" << name << "' found in table"; @@ -263,13 +264,13 @@ TEST(ResourceTableTest, SetAllowNew) { TEST(ResourceTableTest, SetOverlayable) { ResourceTable table; - auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme", - Source("res/values/overlayable.xml", 40)); + auto overlayable = std::make_shared<Overlayable>( + "Name", "overlay://theme", android::Source("res/values/overlayable.xml", 40)); OverlayableItem overlayable_item(overlayable); overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION; overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION; overlayable_item.comment = "comment"; - overlayable_item.source = Source("res/values/overlayable.xml", 42); + overlayable_item.source = android::Source("res/values/overlayable.xml", 42); const ResourceName name = test::ParseNameOrDie("android:string/foo"); ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item).Build(), diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 23f6c88aad91..5a118a902963 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -40,6 +40,24 @@ using ::android::base::StringPrintf; namespace aapt { namespace ResourceUtils { +static std::optional<ResourceNamedType> ToResourceNamedType(const char16_t* type16, + const char* type, size_t type_len) { + std::optional<ResourceNamedTypeRef> parsed_type; + std::string converted; + if (type16) { + converted = android::util::Utf16ToUtf8(StringPiece16(type16, type_len)); + parsed_type = ParseResourceNamedType(converted); + } else if (type) { + parsed_type = ParseResourceNamedType(StringPiece(type, type_len)); + } else { + return {}; + } + if (!parsed_type) { + return {}; + } + return parsed_type->ToResourceNamedType(); +} + std::optional<ResourceName> ToResourceName(const android::ResTable::resource_name& name_in) { // TODO: Remove this when ResTable and AssetManager(1) are removed from AAPT2 ResourceName name_out; @@ -47,27 +65,17 @@ std::optional<ResourceName> ToResourceName(const android::ResTable::resource_nam return {}; } - name_out.package = - util::Utf16ToUtf8(StringPiece16(name_in.package, name_in.packageLen)); - - std::optional<ResourceNamedTypeRef> type; - if (name_in.type) { - type = ParseResourceNamedType(util::Utf16ToUtf8(StringPiece16(name_in.type, name_in.typeLen))); - } else if (name_in.type8) { - type = ParseResourceNamedType(StringPiece(name_in.type8, name_in.typeLen)); - } else { - return {}; - } + name_out.package = android::util::Utf16ToUtf8(StringPiece16(name_in.package, name_in.packageLen)); + std::optional<ResourceNamedType> type = + ToResourceNamedType(name_in.type, name_in.name8, name_in.typeLen); if (!type) { return {}; } - - name_out.type = type->ToResourceNamedType(); + name_out.type = *type; if (name_in.name) { - name_out.entry = - util::Utf16ToUtf8(StringPiece16(name_in.name, name_in.nameLen)); + name_out.entry = android::util::Utf16ToUtf8(StringPiece16(name_in.name, name_in.nameLen)); } else if (name_in.name8) { name_out.entry.assign(name_in.name8, name_in.nameLen); } else { @@ -84,25 +92,15 @@ std::optional<ResourceName> ToResourceName(const android::AssetManager2::Resourc name_out.package = std::string(name_in.package, name_in.package_len); - std::optional<ResourceNamedTypeRef> type; - if (name_in.type16) { - type = - ParseResourceNamedType(util::Utf16ToUtf8(StringPiece16(name_in.type16, name_in.type_len))); - } else if (name_in.type) { - type = ParseResourceNamedType(StringPiece(name_in.type, name_in.type_len)); - } else { - return {}; - } - + std::optional<ResourceNamedType> type = + ToResourceNamedType(name_in.type16, name_in.type, name_in.type_len); if (!type) { return {}; } - - name_out.type = type->ToResourceNamedType(); + name_out.type = *type; if (name_in.entry16) { - name_out.entry = - util::Utf16ToUtf8(StringPiece16(name_in.entry16, name_in.entry_len)); + name_out.entry = android::util::Utf16ToUtf8(StringPiece16(name_in.entry16, name_in.entry_len)); } else if (name_in.entry) { name_out.entry = std::string(name_in.entry, name_in.entry_len); } else { @@ -111,8 +109,7 @@ std::optional<ResourceName> ToResourceName(const android::AssetManager2::Resourc return name_out; } -bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref, - bool* out_private) { +bool ParseResourceName(StringPiece str, ResourceNameRef* out_ref, bool* out_private) { if (str.empty()) { return false; } @@ -153,8 +150,8 @@ bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref, return true; } -bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref, - bool* out_create, bool* out_private) { +bool ParseReference(StringPiece str, ResourceNameRef* out_ref, bool* out_create, + bool* out_private) { StringPiece trimmed_str(util::TrimWhitespace(str)); if (trimmed_str.empty()) { return false; @@ -200,11 +197,11 @@ bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref, return false; } -bool IsReference(const StringPiece& str) { +bool IsReference(StringPiece str) { return ParseReference(str, nullptr, nullptr, nullptr); } -bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) { +bool ParseAttributeReference(StringPiece str, ResourceNameRef* out_ref) { StringPiece trimmed_str(util::TrimWhitespace(str)); if (trimmed_str.empty()) { return false; @@ -237,7 +234,7 @@ bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) { return false; } -bool IsAttributeReference(const StringPiece& str) { +bool IsAttributeReference(StringPiece str) { return ParseAttributeReference(str, nullptr); } @@ -249,7 +246,7 @@ bool IsAttributeReference(const StringPiece& str) { * <[*]package>:[style/]<entry> * [[*]package:style/]<entry> */ -std::optional<Reference> ParseStyleParentReference(const StringPiece& str, std::string* out_error) { +std::optional<Reference> ParseStyleParentReference(StringPiece str, std::string* out_error) { if (str.empty()) { return {}; } @@ -298,7 +295,7 @@ std::optional<Reference> ParseStyleParentReference(const StringPiece& str, std:: return result; } -std::optional<Reference> ParseXmlAttributeName(const StringPiece& str) { +std::optional<Reference> ParseXmlAttributeName(StringPiece str) { StringPiece trimmed_str = util::TrimWhitespace(str); const char* start = trimmed_str.data(); const char* const end = start + trimmed_str.size(); @@ -327,8 +324,7 @@ std::optional<Reference> ParseXmlAttributeName(const StringPiece& str) { return std::optional<Reference>(std::move(ref)); } -std::unique_ptr<Reference> TryParseReference(const StringPiece& str, - bool* out_create) { +std::unique_ptr<Reference> TryParseReference(StringPiece str, bool* out_create) { ResourceNameRef ref; bool private_ref = false; if (ParseReference(str, &ref, out_create, &private_ref)) { @@ -346,7 +342,7 @@ std::unique_ptr<Reference> TryParseReference(const StringPiece& str, return {}; } -std::unique_ptr<Item> TryParseNullOrEmpty(const StringPiece& str) { +std::unique_ptr<Item> TryParseNullOrEmpty(StringPiece str) { const StringPiece trimmed_str(util::TrimWhitespace(str)); if (trimmed_str == "@null") { return MakeNull(); @@ -367,8 +363,7 @@ std::unique_ptr<BinaryPrimitive> MakeEmpty() { android::Res_value::DATA_NULL_EMPTY); } -std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, - const StringPiece& str) { +std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, StringPiece str) { StringPiece trimmed_str(util::TrimWhitespace(str)); for (const Attribute::Symbol& symbol : enum_attr->symbols) { // Enum symbols are stored as @package:id/symbol resources, @@ -384,8 +379,7 @@ std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, return {}; } -std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr, - const StringPiece& str) { +std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr, StringPiece str) { android::Res_value flags = {}; flags.dataType = android::Res_value::TYPE_INT_HEX; flags.data = 0u; @@ -395,7 +389,7 @@ std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr, return util::make_unique<BinaryPrimitive>(flags); } - for (const StringPiece& part : util::Tokenize(str, '|')) { + for (StringPiece part : util::Tokenize(str, '|')) { StringPiece trimmed_part = util::TrimWhitespace(part); bool flag_set = false; @@ -431,7 +425,7 @@ static uint32_t ParseHex(char c, bool* out_error) { } } -std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) { +std::unique_ptr<BinaryPrimitive> TryParseColor(StringPiece str) { StringPiece color_str(util::TrimWhitespace(str)); const char* start = color_str.data(); const size_t len = color_str.size(); @@ -486,7 +480,7 @@ std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) { : util::make_unique<BinaryPrimitive>(value); } -std::optional<bool> ParseBool(const StringPiece& str) { +std::optional<bool> ParseBool(StringPiece str) { StringPiece trimmed_str(util::TrimWhitespace(str)); if (trimmed_str == "true" || trimmed_str == "TRUE" || trimmed_str == "True") { return std::optional<bool>(true); @@ -497,8 +491,8 @@ std::optional<bool> ParseBool(const StringPiece& str) { return {}; } -std::optional<uint32_t> ParseInt(const StringPiece& str) { - std::u16string str16 = util::Utf8ToUtf16(str); +std::optional<uint32_t> ParseInt(StringPiece str) { + std::u16string str16 = android::util::Utf8ToUtf16(str); android::Res_value value; if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { return value.data; @@ -506,10 +500,10 @@ std::optional<uint32_t> ParseInt(const StringPiece& str) { return {}; } -std::optional<ResourceId> ParseResourceId(const StringPiece& str) { +std::optional<ResourceId> ParseResourceId(StringPiece str) { StringPiece trimmed_str(util::TrimWhitespace(str)); - std::u16string str16 = util::Utf8ToUtf16(trimmed_str); + std::u16string str16 = android::util::Utf8ToUtf16(trimmed_str); android::Res_value value; if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { if (value.dataType == android::Res_value::TYPE_INT_HEX) { @@ -522,10 +516,10 @@ std::optional<ResourceId> ParseResourceId(const StringPiece& str) { return {}; } -std::optional<int> ParseSdkVersion(const StringPiece& str) { +std::optional<int> ParseSdkVersion(StringPiece str) { StringPiece trimmed_str(util::TrimWhitespace(str)); - std::u16string str16 = util::Utf8ToUtf16(trimmed_str); + std::u16string str16 = android::util::Utf8ToUtf16(trimmed_str); android::Res_value value; if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { return static_cast<int>(value.data); @@ -541,14 +535,14 @@ std::optional<int> ParseSdkVersion(const StringPiece& str) { const StringPiece::const_iterator begin = std::begin(trimmed_str); const StringPiece::const_iterator end = std::end(trimmed_str); const StringPiece::const_iterator codename_end = std::find(begin, end, '.'); - entry = GetDevelopmentSdkCodeNameVersion(trimmed_str.substr(begin, codename_end)); + entry = GetDevelopmentSdkCodeNameVersion(StringPiece(begin, codename_end - begin)); if (entry) { return entry.value(); } return {}; } -std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str) { +std::unique_ptr<BinaryPrimitive> TryParseBool(StringPiece str) { if (std::optional<bool> maybe_result = ParseBool(str)) { const uint32_t data = maybe_result.value() ? 0xffffffffu : 0u; return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_BOOLEAN, data); @@ -561,8 +555,8 @@ std::unique_ptr<BinaryPrimitive> MakeBool(bool val) { val ? 0xffffffffu : 0u); } -std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) { - std::u16string str16 = util::Utf8ToUtf16(util::TrimWhitespace(str)); +std::unique_ptr<BinaryPrimitive> TryParseInt(StringPiece str) { + std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(str)); android::Res_value value; if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { return {}; @@ -574,8 +568,8 @@ std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t val) { return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, val); } -std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str) { - std::u16string str16 = util::Utf8ToUtf16(util::TrimWhitespace(str)); +std::unique_ptr<BinaryPrimitive> TryParseFloat(StringPiece str) { + std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(str)); android::Res_value value; if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) { return {}; @@ -625,7 +619,7 @@ uint32_t AndroidTypeToAttributeTypeMask(uint16_t type) { } std::unique_ptr<Item> TryParseItemForAttribute( - const StringPiece& value, uint32_t type_mask, + StringPiece value, uint32_t type_mask, const std::function<bool(const ResourceName&)>& on_create_reference) { using android::ResTable_map; @@ -689,7 +683,7 @@ std::unique_ptr<Item> TryParseItemForAttribute( * allows. */ std::unique_ptr<Item> TryParseItemForAttribute( - const StringPiece& str, const Attribute* attr, + StringPiece str, const Attribute* attr, const std::function<bool(const ResourceName&)>& on_create_reference) { using android::ResTable_map; @@ -737,7 +731,7 @@ std::string BuildResourceFileName(const ResourceFile& res_file, const NameMangle std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const ConfigDescription& config, const android::ResStringPool& src_pool, const android::Res_value& res_value, - StringPool* dst_pool) { + android::StringPool* dst_pool) { if (type == ResourceType::kId) { if (res_value.dataType != android::Res_value::TYPE_REFERENCE && res_value.dataType != android::Res_value::TYPE_DYNAMIC_REFERENCE) { @@ -748,30 +742,32 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config // fall through to regular reference deserialization logic } - const uint32_t data = util::DeviceToHost32(res_value.data); + const uint32_t data = android::util::DeviceToHost32(res_value.data); switch (res_value.dataType) { case android::Res_value::TYPE_STRING: { - const std::string str = util::GetString(src_pool, data); + const std::string str = android::util::GetString(src_pool, data); auto spans_result = src_pool.styleAt(data); // Check if the string has a valid style associated with it. if (spans_result.has_value() && (*spans_result)->name.index != android::ResStringPool_span::END) { const android::ResStringPool_span* spans = spans_result->unsafe_ptr(); - StyleString style_str = {str}; + android::StyleString style_str = {str}; while (spans->name.index != android::ResStringPool_span::END) { - style_str.spans.push_back(Span{util::GetString(src_pool, spans->name.index), - spans->firstChar, spans->lastChar}); + style_str.spans.push_back( + android::Span{android::util::GetString(src_pool, spans->name.index), spans->firstChar, + spans->lastChar}); spans++; } return util::make_unique<StyledString>(dst_pool->MakeRef( - style_str, StringPool::Context(StringPool::Context::kNormalPriority, config))); + style_str, + android::StringPool::Context(android::StringPool::Context::kNormalPriority, config))); } else { if (type != ResourceType::kString && util::StartsWith(str, "res/")) { // This must be a FileReference. - std::unique_ptr<FileReference> file_ref = - util::make_unique<FileReference>(dst_pool->MakeRef( - str, StringPool::Context(StringPool::Context::kHighPriority, config))); + std::unique_ptr<FileReference> file_ref = util::make_unique<FileReference>( + dst_pool->MakeRef(str, android::StringPool::Context( + android::StringPool::Context::kHighPriority, config))); if (type == ResourceType::kRaw) { file_ref->type = ResourceFile::Type::kUnknown; } else if (util::EndsWith(*file_ref->path, ".xml")) { @@ -783,7 +779,8 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config } // There are no styles associated with this string, so treat it as a simple string. - return util::make_unique<String>(dst_pool->MakeRef(str, StringPool::Context(config))); + return util::make_unique<String>( + dst_pool->MakeRef(str, android::StringPool::Context(config))); } } break; @@ -950,7 +947,7 @@ StringBuilder::SpanHandle StringBuilder::StartSpan(const std::string& name) { // When we start a span, all state associated with whitespace truncation and quotation is ended. ResetTextState(); - Span span; + android::Span span; span.name = name; span.first_char = span.last_char = utf16_len_; xml_string_.spans.push_back(std::move(span)); diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index fe450a834dfa..f30f4acfec7a 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -20,15 +20,14 @@ #include <functional> #include <memory> +#include "NameMangler.h" +#include "Resource.h" +#include "ResourceValues.h" #include "androidfw/AssetManager2.h" #include "androidfw/ConfigDescription.h" #include "androidfw/ResourceTypes.h" #include "androidfw/StringPiece.h" - -#include "NameMangler.h" -#include "Resource.h" -#include "ResourceValues.h" -#include "StringPool.h" +#include "androidfw/StringPool.h" namespace aapt { namespace ResourceUtils { @@ -39,7 +38,7 @@ namespace ResourceUtils { * `out_resource` set to the parsed resource name and `out_private` set to true * if a '*' prefix was present. */ -bool ParseResourceName(const android::StringPiece& str, ResourceNameRef* out_resource, +bool ParseResourceName(android::StringPiece str, ResourceNameRef* out_resource, bool* out_private = nullptr); /* @@ -50,27 +49,27 @@ bool ParseResourceName(const android::StringPiece& str, ResourceNameRef* out_res * If '+' was present in the reference, `out_create` is set to true. * If '*' was present in the reference, `out_private` is set to true. */ -bool ParseReference(const android::StringPiece& str, ResourceNameRef* out_reference, +bool ParseReference(android::StringPiece str, ResourceNameRef* out_reference, bool* out_create = nullptr, bool* out_private = nullptr); /* * Returns true if the string is in the form of a resource reference * (@[+][package:]type/name). */ -bool IsReference(const android::StringPiece& str); +bool IsReference(android::StringPiece str); /* * Returns true if the string was parsed as an attribute reference * (?[package:][type/]name), * with `out_reference` set to the parsed reference. */ -bool ParseAttributeReference(const android::StringPiece& str, ResourceNameRef* out_reference); +bool ParseAttributeReference(android::StringPiece str, ResourceNameRef* out_reference); /** * Returns true if the string is in the form of an attribute * reference(?[package:][type/]name). */ -bool IsAttributeReference(const android::StringPiece& str); +bool IsAttributeReference(android::StringPiece str); /** * Convert an android::ResTable::resource_name to an aapt::ResourceName struct. @@ -86,22 +85,22 @@ std::optional<ResourceName> ToResourceName(const android::AssetManager2::Resourc * Returns a boolean value if the string is equal to TRUE, true, True, FALSE, * false, or False. */ -std::optional<bool> ParseBool(const android::StringPiece& str); +std::optional<bool> ParseBool(android::StringPiece str); /** * Returns a uint32_t if the string is an integer. */ -std::optional<uint32_t> ParseInt(const android::StringPiece& str); +std::optional<uint32_t> ParseInt(android::StringPiece str); /** * Returns an ID if it the string represented a valid ID. */ -std::optional<ResourceId> ParseResourceId(const android::StringPiece& str); +std::optional<ResourceId> ParseResourceId(android::StringPiece str); /** * Parses an SDK version, which can be an integer, or a letter from A-Z. */ -std::optional<int> ParseSdkVersion(const android::StringPiece& str); +std::optional<int> ParseSdkVersion(android::StringPiece str); /* * Returns a Reference, or None Maybe instance if the string `str` was parsed as @@ -114,7 +113,7 @@ std::optional<int> ParseSdkVersion(const android::StringPiece& str); * ?[package:]style/<entry> or * <package>:[style/]<entry> */ -std::optional<Reference> ParseStyleParentReference(const android::StringPiece& str, +std::optional<Reference> ParseStyleParentReference(android::StringPiece str, std::string* out_error); /* @@ -124,7 +123,7 @@ std::optional<Reference> ParseStyleParentReference(const android::StringPiece& s * * package:entry */ -std::optional<Reference> ParseXmlAttributeName(const android::StringPiece& str); +std::optional<Reference> ParseXmlAttributeName(android::StringPiece str); /* * Returns a Reference object if the string was parsed as a resource or @@ -133,14 +132,13 @@ std::optional<Reference> ParseXmlAttributeName(const android::StringPiece& str); * if * the '+' was present in the string. */ -std::unique_ptr<Reference> TryParseReference(const android::StringPiece& str, - bool* out_create = nullptr); +std::unique_ptr<Reference> TryParseReference(android::StringPiece str, bool* out_create = nullptr); /* * Returns a BinaryPrimitve object representing @null or @empty if the string * was parsed as one. */ -std::unique_ptr<Item> TryParseNullOrEmpty(const android::StringPiece& str); +std::unique_ptr<Item> TryParseNullOrEmpty(android::StringPiece str); // Returns a Reference representing @null. // Due to runtime compatibility issues, this is encoded as a reference with ID 0. @@ -155,13 +153,13 @@ std::unique_ptr<BinaryPrimitive> MakeEmpty(); * Returns a BinaryPrimitve object representing a color if the string was parsed * as one. */ -std::unique_ptr<BinaryPrimitive> TryParseColor(const android::StringPiece& str); +std::unique_ptr<BinaryPrimitive> TryParseColor(android::StringPiece str); /* * Returns a BinaryPrimitve object representing a boolean if the string was * parsed as one. */ -std::unique_ptr<BinaryPrimitive> TryParseBool(const android::StringPiece& str); +std::unique_ptr<BinaryPrimitive> TryParseBool(android::StringPiece str); // Returns a boolean BinaryPrimitive. std::unique_ptr<BinaryPrimitive> MakeBool(bool val); @@ -170,7 +168,7 @@ std::unique_ptr<BinaryPrimitive> MakeBool(bool val); * Returns a BinaryPrimitve object representing an integer if the string was * parsed as one. */ -std::unique_ptr<BinaryPrimitive> TryParseInt(const android::StringPiece& str); +std::unique_ptr<BinaryPrimitive> TryParseInt(android::StringPiece str); // Returns an integer BinaryPrimitive. std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t value); @@ -179,21 +177,21 @@ std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t value); * Returns a BinaryPrimitve object representing a floating point number * (float, dimension, etc) if the string was parsed as one. */ -std::unique_ptr<BinaryPrimitive> TryParseFloat(const android::StringPiece& str); +std::unique_ptr<BinaryPrimitive> TryParseFloat(android::StringPiece str); /* * Returns a BinaryPrimitve object representing an enum symbol if the string was * parsed as one. */ std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, - const android::StringPiece& str); + android::StringPiece str); /* * Returns a BinaryPrimitve object representing a flag symbol if the string was * parsed as one. */ std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr, - const android::StringPiece& str); + android::StringPiece str); /* * Try to convert a string to an Item for the given attribute. The attribute * will @@ -202,11 +200,11 @@ std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr, * reference to an ID that must be created (@+id/foo). */ std::unique_ptr<Item> TryParseItemForAttribute( - const android::StringPiece& value, const Attribute* attr, + android::StringPiece value, const Attribute* attr, const std::function<bool(const ResourceName&)>& on_create_reference = {}); std::unique_ptr<Item> TryParseItemForAttribute( - const android::StringPiece& value, uint32_t type_mask, + android::StringPiece value, uint32_t type_mask, const std::function<bool(const ResourceName&)>& on_create_reference = {}); uint32_t AndroidTypeToAttributeTypeMask(uint16_t type); @@ -230,14 +228,14 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const android::ConfigDescription& config, const android::ResStringPool& src_pool, const android::Res_value& res_value, - StringPool* dst_pool); + android::StringPool* dst_pool); // A string flattened from an XML hierarchy, which maintains tags and untranslatable sections // in parallel data structures. struct FlattenedXmlString { std::string text; std::vector<UntranslatableSection> untranslatable_sections; - std::vector<Span> spans; + std::vector<android::Span> spans; }; // Flattens an XML hierarchy into a FlattenedXmlString, formatting the text, escaping characters, diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index 1aaa34deee79..568871a4d66e 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -111,7 +111,7 @@ TEST(ResourceUtilsTest, ParsePrivateReference) { TEST(ResourceUtilsTest, ParseBinaryDynamicReference) { android::Res_value value = {}; - value.data = util::HostToDevice32(0x01); + value.data = android::util::HostToDevice32(0x01); value.dataType = android::Res_value::TYPE_DYNAMIC_REFERENCE; std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(ResourceType::kId, android::ConfigDescription(), diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index b796eb07f076..a5754e0d168f 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -75,7 +75,8 @@ void BaseItem<Derived>::Accept(ConstValueVisitor* visitor) const { visitor->Visit(static_cast<const Derived*>(this)); } -RawString::RawString(const StringPool::Ref& ref) : value(ref) {} +RawString::RawString(const android::StringPool::Ref& ref) : value(ref) { +} bool RawString::Equals(const Value* value) const { const RawString* other = ValueCast<RawString>(value); @@ -87,7 +88,7 @@ bool RawString::Equals(const Value* value) const { bool RawString::Flatten(android::Res_value* out_value) const { out_value->dataType = android::Res_value::TYPE_STRING; - out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index())); + out_value->data = android::util::HostToDevice32(static_cast<uint32_t>(value.index())); return true; } @@ -136,7 +137,7 @@ bool Reference::Flatten(android::Res_value* out_value) const { out_value->dataType = android::Res_value::TYPE_ATTRIBUTE; } } - out_value->data = util::HostToDevice32(resid.id); + out_value->data = android::util::HostToDevice32(resid.id); return true; } @@ -205,7 +206,7 @@ void Reference::PrettyPrint(Printer* printer) const { PrettyPrintReferenceImpl(*this, true /*print_package*/, printer); } -void Reference::PrettyPrint(const StringPiece& package, Printer* printer) const { +void Reference::PrettyPrint(StringPiece package, Printer* printer) const { const bool print_package = name ? package != name.value().package : true; PrettyPrintReferenceImpl(*this, print_package, printer); } @@ -216,7 +217,7 @@ bool Id::Equals(const Value* value) const { bool Id::Flatten(android::Res_value* out) const { out->dataType = android::Res_value::TYPE_INT_BOOLEAN; - out->data = util::HostToDevice32(0); + out->data = android::util::HostToDevice32(0); return true; } @@ -224,7 +225,7 @@ void Id::Print(std::ostream* out) const { *out << "(id)"; } -String::String(const StringPool::Ref& ref) : value(ref) { +String::String(const android::StringPool::Ref& ref) : value(ref) { } bool String::Equals(const Value* value) const { @@ -258,7 +259,7 @@ bool String::Flatten(android::Res_value* out_value) const { } out_value->dataType = android::Res_value::TYPE_STRING; - out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index())); + out_value->data = android::util::HostToDevice32(static_cast<uint32_t>(value.index())); return true; } @@ -272,7 +273,7 @@ void String::PrettyPrint(Printer* printer) const { printer->Print("\""); } -StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) { +StyledString::StyledString(const android::StringPool::StyleRef& ref) : value(ref) { } bool StyledString::Equals(const Value* value) const { @@ -305,18 +306,18 @@ bool StyledString::Flatten(android::Res_value* out_value) const { } out_value->dataType = android::Res_value::TYPE_STRING; - out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index())); + out_value->data = android::util::HostToDevice32(static_cast<uint32_t>(value.index())); return true; } void StyledString::Print(std::ostream* out) const { *out << "(styled string) \"" << value->value << "\""; - for (const StringPool::Span& span : value->spans) { + for (const android::StringPool::Span& span : value->spans) { *out << " " << *span.name << ":" << span.first_char << "," << span.last_char; } } -FileReference::FileReference(const StringPool::Ref& _path) : path(_path) { +FileReference::FileReference(const android::StringPool::Ref& _path) : path(_path) { } bool FileReference::Equals(const Value* value) const { @@ -333,7 +334,7 @@ bool FileReference::Flatten(android::Res_value* out_value) const { } out_value->dataType = android::Res_value::TYPE_STRING; - out_value->data = util::HostToDevice32(static_cast<uint32_t>(path.index())); + out_value->data = android::util::HostToDevice32(static_cast<uint32_t>(path.index())); return true; } @@ -373,7 +374,7 @@ bool BinaryPrimitive::Equals(const Value* value) const { bool BinaryPrimitive::Flatten(::android::Res_value* out_value) const { out_value->dataType = value.dataType; - out_value->data = util::HostToDevice32(value.data); + out_value->data = android::util::HostToDevice32(value.data); return true; } @@ -678,7 +679,7 @@ void Attribute::Print(std::ostream* out) const { } static void BuildAttributeMismatchMessage(const Attribute& attr, const Item& value, - DiagMessage* out_msg) { + android::DiagMessage* out_msg) { *out_msg << "expected"; if (attr.type_mask & android::ResTable_map::TYPE_BOOLEAN) { *out_msg << " boolean"; @@ -723,7 +724,7 @@ static void BuildAttributeMismatchMessage(const Attribute& attr, const Item& val *out_msg << " but got " << value; } -bool Attribute::Matches(const Item& item, DiagMessage* out_msg) const { +bool Attribute::Matches(const Item& item, android::DiagMessage* out_msg) const { constexpr const uint32_t TYPE_ENUM = android::ResTable_map::TYPE_ENUM; constexpr const uint32_t TYPE_FLAGS = android::ResTable_map::TYPE_FLAGS; constexpr const uint32_t TYPE_INTEGER = android::ResTable_map::TYPE_INTEGER; @@ -732,7 +733,7 @@ bool Attribute::Matches(const Item& item, DiagMessage* out_msg) const { android::Res_value val = {}; item.Flatten(&val); - const uint32_t flattened_data = util::DeviceToHost32(val.data); + const uint32_t flattened_data = android::util::DeviceToHost32(val.data); // Always allow references. const uint32_t actual_type = ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType); @@ -872,7 +873,7 @@ void Style::Print(std::ostream* out) const { *out << " [" << util::Joiner(entries, ", ") << "]"; } -Style::Entry CloneEntry(const Style::Entry& entry, StringPool* pool) { +Style::Entry CloneEntry(const Style::Entry& entry, android::StringPool* pool) { Style::Entry cloned_entry{entry.key}; if (entry.value != nullptr) { CloningValueTransformer cloner(pool); @@ -881,7 +882,7 @@ Style::Entry CloneEntry(const Style::Entry& entry, StringPool* pool) { return cloned_entry; } -void Style::MergeWith(Style* other, StringPool* pool) { +void Style::MergeWith(Style* other, android::StringPool* pool) { if (other->parent) { parent = other->parent; } @@ -1077,7 +1078,7 @@ std::unique_ptr<T> CopyValueFields(std::unique_ptr<T> new_value, const T* value) return new_value; } -CloningValueTransformer::CloningValueTransformer(StringPool* new_pool) +CloningValueTransformer::CloningValueTransformer(android::StringPool* new_pool) : ValueTransformer(new_pool) { } diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 1694d6b6fe4a..6f9dccbd3bcc 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -22,13 +22,12 @@ #include <ostream> #include <vector> -#include "androidfw/ResourceTypes.h" -#include "androidfw/StringPiece.h" - -#include "Diagnostics.h" #include "Resource.h" -#include "StringPool.h" #include "ValueTransformer.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/ResourceTypes.h" +#include "androidfw/StringPiece.h" +#include "androidfw/StringPool.h" #include "io/File.h" #include "text/Printer.h" @@ -67,15 +66,15 @@ class Value { } // Returns the source where this value was defined. - const Source& GetSource() const { + const android::Source& GetSource() const { return source_; } - void SetSource(const Source& source) { + void SetSource(const android::Source& source) { source_ = source; } - void SetSource(Source&& source) { + void SetSource(android::Source&& source) { source_ = std::move(source); } @@ -84,8 +83,8 @@ class Value { return comment_; } - void SetComment(const android::StringPiece& str) { - comment_ = str.to_string(); + void SetComment(android::StringPiece str) { + comment_.assign(str); } void SetComment(std::string&& str) { @@ -113,7 +112,7 @@ class Value { friend std::ostream& operator<<(std::ostream& out, const Value& value); protected: - Source source_; + android::Source source_; std::string comment_; bool weak_ = false; bool translatable_ = true; @@ -177,7 +176,7 @@ struct Reference : public TransformableItem<Reference, BaseItem<Reference>> { void PrettyPrint(text::Printer* printer) const override; // Prints the reference without a package name if the package name matches the one given. - void PrettyPrint(const android::StringPiece& package, text::Printer* printer) const; + void PrettyPrint(android::StringPiece package, text::Printer* printer) const; }; bool operator<(const Reference&, const Reference&); @@ -197,9 +196,9 @@ struct Id : public TransformableItem<Id, BaseItem<Id>> { // A raw, unprocessed string. This may contain quotations, escape sequences, and whitespace. // This shall *NOT* end up in the final resource table. struct RawString : public TransformableItem<RawString, BaseItem<RawString>> { - StringPool::Ref value; + android::StringPool::Ref value; - explicit RawString(const StringPool::Ref& ref); + explicit RawString(const android::StringPool::Ref& ref); bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out_value) const override; @@ -225,14 +224,14 @@ inline bool operator!=(const UntranslatableSection& a, const UntranslatableSecti } struct String : public TransformableItem<String, BaseItem<String>> { - StringPool::Ref value; + android::StringPool::Ref value; // Sections of the string to NOT translate. Mainly used // for pseudolocalization. This data is NOT persisted // in any format. std::vector<UntranslatableSection> untranslatable_sections; - explicit String(const StringPool::Ref& ref); + explicit String(const android::StringPool::Ref& ref); bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out_value) const override; @@ -241,14 +240,14 @@ struct String : public TransformableItem<String, BaseItem<String>> { }; struct StyledString : public TransformableItem<StyledString, BaseItem<StyledString>> { - StringPool::StyleRef value; + android::StringPool::StyleRef value; // Sections of the string to NOT translate. Mainly used // for pseudolocalization. This data is NOT persisted // in any format. std::vector<UntranslatableSection> untranslatable_sections; - explicit StyledString(const StringPool::StyleRef& ref); + explicit StyledString(const android::StringPool::StyleRef& ref); bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out_value) const override; @@ -256,7 +255,7 @@ struct StyledString : public TransformableItem<StyledString, BaseItem<StyledStri }; struct FileReference : public TransformableItem<FileReference, BaseItem<FileReference>> { - StringPool::Ref path; + android::StringPool::Ref path; // A handle to the file object from which this file can be read. // This field is NOT persisted in any format. It is transient. @@ -267,7 +266,7 @@ struct FileReference : public TransformableItem<FileReference, BaseItem<FileRefe ResourceFile::Type type = ResourceFile::Type::kUnknown; FileReference() = default; - explicit FileReference(const StringPool::Ref& path); + explicit FileReference(const android::StringPool::Ref& path); bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out_value) const override; @@ -315,7 +314,7 @@ struct Attribute : public TransformableValue<Attribute, BaseValue<Attribute>> { static std::string MaskString(uint32_t type_mask); void Print(std::ostream* out) const override; - bool Matches(const Item& item, DiagMessage* out_msg = nullptr) const; + bool Matches(const Item& item, android::DiagMessage* out_msg = nullptr) const; }; struct Style : public TransformableValue<Style, BaseValue<Style>> { @@ -338,7 +337,7 @@ struct Style : public TransformableValue<Style, BaseValue<Style>> { // Merges `style` into this Style. All identical attributes of `style` take precedence, including // the parent, if there is one. - void MergeWith(Style* style, StringPool* pool); + void MergeWith(Style* style, android::StringPool* pool); }; struct Array : public TransformableValue<Array, BaseValue<Array>> { @@ -367,7 +366,7 @@ struct Styleable : public TransformableValue<Styleable, BaseValue<Styleable>> { struct Macro : public TransformableValue<Macro, BaseValue<Macro>> { std::string raw_value; - StyleString style_string; + android::StyleString style_string; std::vector<UntranslatableSection> untranslatable_sections; struct Namespace { @@ -399,7 +398,7 @@ typename std::enable_if<std::is_base_of<Value, T>::value, std::ostream&>::type o } struct CloningValueTransformer : public ValueTransformer { - explicit CloningValueTransformer(StringPool* new_pool); + explicit CloningValueTransformer(android::StringPool* new_pool); std::unique_ptr<Reference> TransformDerived(const Reference* value) override; std::unique_ptr<Id> TransformDerived(const Id* value) override; diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp index c75a4b99e138..d788e3fd5fc7 100644 --- a/tools/aapt2/ResourceValues_test.cpp +++ b/tools/aapt2/ResourceValues_test.cpp @@ -37,7 +37,7 @@ constexpr const uint32_t TYPE_STRING = android::ResTable_map::TYPE_STRING; } // namespace TEST(ResourceValuesTest, PluralEquals) { - StringPool pool; + android::StringPool pool; Plural a; a.values[Plural::One] = util::make_unique<String>(pool.MakeRef("one")); @@ -56,7 +56,7 @@ TEST(ResourceValuesTest, PluralEquals) { } TEST(ResourceValuesTest, PluralClone) { - StringPool pool; + android::StringPool pool; Plural a; a.values[Plural::One] = util::make_unique<String>(pool.MakeRef("one")); @@ -68,7 +68,7 @@ TEST(ResourceValuesTest, PluralClone) { } TEST(ResourceValuesTest, ArrayEquals) { - StringPool pool; + android::StringPool pool; Array a; a.elements.push_back(util::make_unique<String>(pool.MakeRef("one"))); @@ -92,7 +92,7 @@ TEST(ResourceValuesTest, ArrayEquals) { } TEST(ResourceValuesTest, ArrayClone) { - StringPool pool; + android::StringPool pool; Array a; a.elements.push_back(util::make_unique<String>(pool.MakeRef("one"))); @@ -104,7 +104,7 @@ TEST(ResourceValuesTest, ArrayClone) { } TEST(ResourceValuesTest, StyleEquals) { - StringPool pool; + android::StringPool pool; std::unique_ptr<Style> a = test::StyleBuilder() .SetParent("android:style/Parent") @@ -168,10 +168,10 @@ TEST(ResourceValuesTest, StyleClone) { } TEST(ResourcesValuesTest, StringClones) { - StringPool pool_a; - StringPool pool_b; + android::StringPool pool_a; + android::StringPool pool_b; - String str_a(pool_a.MakeRef("hello", StringPool::Context(test::ParseConfigOrDie("en")))); + String str_a(pool_a.MakeRef("hello", android::StringPool::Context(test::ParseConfigOrDie("en")))); ASSERT_THAT(pool_a, SizeIs(1u)); EXPECT_THAT(pool_a.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en"))); @@ -185,8 +185,8 @@ TEST(ResourcesValuesTest, StringClones) { } TEST(ResourceValuesTest, StyleMerges) { - StringPool pool_a; - StringPool pool_b; + android::StringPool pool_a; + android::StringPool pool_b; std::unique_ptr<Style> a = test::StyleBuilder() @@ -204,7 +204,7 @@ TEST(ResourceValuesTest, StyleMerges) { a->MergeWith(b.get(), &pool_a); - StringPool pool; + android::StringPool pool; std::unique_ptr<Style> expected = test::StyleBuilder() .SetParent("android:style/OverlayParent") diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp index 2c55d1d548db..01d3c84e05ba 100644 --- a/tools/aapt2/Resource_test.cpp +++ b/tools/aapt2/Resource_test.cpp @@ -135,13 +135,13 @@ TEST(ResourceTypeTest, ParseResourceNamedType) { type = ParseResourceNamedType("layout"); EXPECT_THAT(type, Optional(Eq(ResourceNamedType("layout", ResourceType::kLayout)))); - type = ParseResourceNamedType("layout:2"); - EXPECT_THAT(type, Optional(Eq(ResourceNamedType("layout:2", ResourceType::kLayout)))); + type = ParseResourceNamedType("layout.2"); + EXPECT_THAT(type, Optional(Eq(ResourceNamedType("layout.2", ResourceType::kLayout)))); - type = ParseResourceNamedType("layout:another"); - EXPECT_THAT(type, Optional(Eq(ResourceNamedType("layout:another", ResourceType::kLayout)))); + type = ParseResourceNamedType("layout.another"); + EXPECT_THAT(type, Optional(Eq(ResourceNamedType("layout.another", ResourceType::kLayout)))); - type = ParseResourceNamedType("layout:"); + type = ParseResourceNamedType("layout."); EXPECT_THAT(type, Eq(std::nullopt)); type = ParseResourceNamedType("layout2"); diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 95b794964068..1d7fd1d17dcd 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -46,6 +46,13 @@ message ToolFingerprint { string version = 2; } +// References to non local resources +message DynamicRefTable { + PackageId package_id = 1; + string package_name = 2; +} + + // Top level message representing a resource table. message ResourceTable { // The string pool containing source paths referenced throughout the resource table. This does @@ -60,6 +67,8 @@ message ResourceTable { // The version fingerprints of the tools that built the resource table. repeated ToolFingerprint tool_fingerprint = 4; + + repeated DynamicRefTable dynamic_ref_table = 5; } // A package ID in the range [0x00, 0xff]. @@ -636,4 +645,4 @@ message StyleString { message UntranslatableSection { uint64 start_index = 1; uint64 end_index = 2; -}
\ No newline at end of file +} diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index 8ea43abff895..a7c5479b56fd 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -27,7 +27,7 @@ namespace aapt { static ApiVersion sDevelopmentSdkLevel = 10000; static const auto sDevelopmentSdkCodeNames = - std::unordered_set<StringPiece>({"Q", "R", "S", "Sv2", "Tiramisu"}); + std::unordered_set<StringPiece>({"Q", "R", "S", "Sv2", "Tiramisu", "UpsideDownCake"}); static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = { {0x021c, 1}, @@ -77,7 +77,7 @@ ApiVersion FindAttributeSdkLevel(const ResourceId& id) { return iter->second; } -std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const StringPiece& code_name) { +std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(StringPiece code_name) { return (sDevelopmentSdkCodeNames.find(code_name) == sDevelopmentSdkCodeNames.end()) ? std::optional<ApiVersion>() : sDevelopmentSdkLevel; diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index 0bd61c04f2b2..e47704ea2725 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -59,11 +59,12 @@ enum : ApiVersion { SDK_S = 31, SDK_S_V2 = 32, SDK_TIRAMISU = 33, + SDK_UPSIDE_DOWN_CAKE = 34, SDK_CUR_DEVELOPMENT = 10000, }; ApiVersion FindAttributeSdkLevel(const ResourceId& id); -std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const android::StringPiece& code_name); +std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(android::StringPiece code_name); } // namespace aapt diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h deleted file mode 100644 index 4f9369a5d517..000000000000 --- a/tools/aapt2/Source.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2015 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 AAPT_SOURCE_H -#define AAPT_SOURCE_H - -#include <optional> -#include <ostream> -#include <string> - -#include "android-base/stringprintf.h" -#include "androidfw/StringPiece.h" - -namespace aapt { - -// Represents a file on disk. Used for logging and showing errors. -struct Source { - std::string path; - std::optional<size_t> line; - std::optional<std::string> archive; - - Source() = default; - - inline Source(const android::StringPiece& path) : path(path.to_string()) { // NOLINT(implicit) - } - - inline Source(const android::StringPiece& path, const android::StringPiece& archive) - : path(path.to_string()), archive(archive.to_string()) {} - - inline Source(const android::StringPiece& path, size_t line) - : path(path.to_string()), line(line) {} - - inline Source WithLine(size_t line) const { - return Source(path, line); - } - - std::string to_string() const { - std::string s = path; - if (archive) { - s = ::android::base::StringPrintf("%s@%s", archive.value().c_str(), s.c_str()); - } - if (line) { - s = ::android::base::StringPrintf("%s:%zd", s.c_str(), line.value()); - } - return s; - } -}; - -// -// Implementations -// - -inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) { - return out << source.to_string(); -} - -inline bool operator==(const Source& lhs, const Source& rhs) { - return lhs.path == rhs.path && lhs.line == rhs.line; -} - -inline bool operator<(const Source& lhs, const Source& rhs) { - int cmp = lhs.path.compare(rhs.path); - if (cmp < 0) return true; - if (cmp > 0) return false; - if (lhs.line) { - if (rhs.line) { - return lhs.line.value() < rhs.line.value(); - } - return false; - } - return bool(rhs.line); -} - -} // namespace aapt - -#endif // AAPT_SOURCE_H diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp deleted file mode 100644 index 8eabd3225d87..000000000000 --- a/tools/aapt2/StringPool.cpp +++ /dev/null @@ -1,511 +0,0 @@ -/* - * Copyright (C) 2015 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 "StringPool.h" - -#include <algorithm> -#include <memory> -#include <string> - -#include "android-base/logging.h" -#include "androidfw/ResourceTypes.h" -#include "androidfw/StringPiece.h" - -#include "util/BigBuffer.h" -#include "util/Util.h" - -using ::android::StringPiece; - -namespace aapt { - -StringPool::Ref::Ref() : entry_(nullptr) {} - -StringPool::Ref::Ref(const StringPool::Ref& rhs) : entry_(rhs.entry_) { - if (entry_ != nullptr) { - entry_->ref_++; - } -} - -StringPool::Ref::Ref(StringPool::Entry* entry) : entry_(entry) { - if (entry_ != nullptr) { - entry_->ref_++; - } -} - -StringPool::Ref::~Ref() { - if (entry_ != nullptr) { - entry_->ref_--; - } -} - -StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) { - if (rhs.entry_ != nullptr) { - rhs.entry_->ref_++; - } - - if (entry_ != nullptr) { - entry_->ref_--; - } - entry_ = rhs.entry_; - return *this; -} - -bool StringPool::Ref::operator==(const Ref& rhs) const { - return entry_->value == rhs.entry_->value; -} - -bool StringPool::Ref::operator!=(const Ref& rhs) const { - return entry_->value != rhs.entry_->value; -} - -const std::string* StringPool::Ref::operator->() const { - return &entry_->value; -} - -const std::string& StringPool::Ref::operator*() const { - return entry_->value; -} - -size_t StringPool::Ref::index() const { - // Account for the styles, which *always* come first. - return entry_->pool_->styles_.size() + entry_->index_; -} - -const StringPool::Context& StringPool::Ref::GetContext() const { - return entry_->context; -} - -StringPool::StyleRef::StyleRef() : entry_(nullptr) {} - -StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) - : entry_(rhs.entry_) { - if (entry_ != nullptr) { - entry_->ref_++; - } -} - -StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : entry_(entry) { - if (entry_ != nullptr) { - entry_->ref_++; - } -} - -StringPool::StyleRef::~StyleRef() { - if (entry_ != nullptr) { - entry_->ref_--; - } -} - -StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) { - if (rhs.entry_ != nullptr) { - rhs.entry_->ref_++; - } - - if (entry_ != nullptr) { - entry_->ref_--; - } - entry_ = rhs.entry_; - return *this; -} - -bool StringPool::StyleRef::operator==(const StyleRef& rhs) const { - if (entry_->value != rhs.entry_->value) { - return false; - } - - if (entry_->spans.size() != rhs.entry_->spans.size()) { - return false; - } - - auto rhs_iter = rhs.entry_->spans.begin(); - for (const Span& span : entry_->spans) { - const Span& rhs_span = *rhs_iter; - if (span.first_char != rhs_span.first_char || span.last_char != rhs_span.last_char || - span.name != rhs_span.name) { - return false; - } - } - return true; -} - -bool StringPool::StyleRef::operator!=(const StyleRef& rhs) const { - return !operator==(rhs); -} - -const StringPool::StyleEntry* StringPool::StyleRef::operator->() const { - return entry_; -} - -const StringPool::StyleEntry& StringPool::StyleRef::operator*() const { - return *entry_; -} - -size_t StringPool::StyleRef::index() const { - return entry_->index_; -} - -const StringPool::Context& StringPool::StyleRef::GetContext() const { - return entry_->context; -} - -StringPool::Ref StringPool::MakeRef(const StringPiece& str) { - return MakeRefImpl(str, Context{}, true); -} - -StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context) { - return MakeRefImpl(str, context, true); -} - -StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context, - bool unique) { - if (unique) { - auto range = indexed_strings_.equal_range(str); - for (auto iter = range.first; iter != range.second; ++iter) { - if (context.priority == iter->second->context.priority) { - return Ref(iter->second); - } - } - } - - std::unique_ptr<Entry> entry(new Entry()); - entry->value = str.to_string(); - entry->context = context; - entry->index_ = strings_.size(); - entry->ref_ = 0; - entry->pool_ = this; - - Entry* borrow = entry.get(); - strings_.emplace_back(std::move(entry)); - indexed_strings_.insert(std::make_pair(StringPiece(borrow->value), borrow)); - return Ref(borrow); -} - -StringPool::Ref StringPool::MakeRef(const Ref& ref) { - if (ref.entry_->pool_ == this) { - return ref; - } - return MakeRef(ref.entry_->value, ref.entry_->context); -} - -StringPool::StyleRef StringPool::MakeRef(const StyleString& str) { - return MakeRef(str, Context{}); -} - -StringPool::StyleRef StringPool::MakeRef(const StyleString& str, const Context& context) { - std::unique_ptr<StyleEntry> entry(new StyleEntry()); - entry->value = str.str; - entry->context = context; - entry->index_ = styles_.size(); - entry->ref_ = 0; - for (const aapt::Span& span : str.spans) { - entry->spans.emplace_back(Span{MakeRef(span.name), span.first_char, span.last_char}); - } - - StyleEntry* borrow = entry.get(); - styles_.emplace_back(std::move(entry)); - return StyleRef(borrow); -} - -StringPool::StyleRef StringPool::MakeRef(const StyleRef& ref) { - std::unique_ptr<StyleEntry> entry(new StyleEntry()); - entry->value = ref.entry_->value; - entry->context = ref.entry_->context; - entry->index_ = styles_.size(); - entry->ref_ = 0; - for (const Span& span : ref.entry_->spans) { - entry->spans.emplace_back(Span{MakeRef(*span.name), span.first_char, span.last_char}); - } - - StyleEntry* borrow = entry.get(); - styles_.emplace_back(std::move(entry)); - return StyleRef(borrow); -} - -void StringPool::ReAssignIndices() { - // Assign the style indices. - const size_t style_len = styles_.size(); - for (size_t index = 0; index < style_len; index++) { - styles_[index]->index_ = index; - } - - // Assign the string indices. - const size_t string_len = strings_.size(); - for (size_t index = 0; index < string_len; index++) { - strings_[index]->index_ = index; - } -} - -void StringPool::Merge(StringPool&& pool) { - // First, change the owning pool for the incoming strings. - for (std::unique_ptr<Entry>& entry : pool.strings_) { - entry->pool_ = this; - } - - // Now move the styles, strings, and indices over. - std::move(pool.styles_.begin(), pool.styles_.end(), std::back_inserter(styles_)); - pool.styles_.clear(); - std::move(pool.strings_.begin(), pool.strings_.end(), std::back_inserter(strings_)); - pool.strings_.clear(); - indexed_strings_.insert(pool.indexed_strings_.begin(), pool.indexed_strings_.end()); - pool.indexed_strings_.clear(); - - ReAssignIndices(); -} - -void StringPool::HintWillAdd(size_t string_count, size_t style_count) { - strings_.reserve(strings_.size() + string_count); - styles_.reserve(styles_.size() + style_count); -} - -void StringPool::Prune() { - const auto iter_end = indexed_strings_.end(); - auto index_iter = indexed_strings_.begin(); - while (index_iter != iter_end) { - if (index_iter->second->ref_ <= 0) { - index_iter = indexed_strings_.erase(index_iter); - } else { - ++index_iter; - } - } - - auto end_iter2 = - std::remove_if(strings_.begin(), strings_.end(), - [](const std::unique_ptr<Entry>& entry) -> bool { return entry->ref_ <= 0; }); - auto end_iter3 = std::remove_if( - styles_.begin(), styles_.end(), - [](const std::unique_ptr<StyleEntry>& entry) -> bool { return entry->ref_ <= 0; }); - - // Remove the entries at the end or else we'll be accessing a deleted string from the StyleEntry. - strings_.erase(end_iter2, strings_.end()); - styles_.erase(end_iter3, styles_.end()); - - ReAssignIndices(); -} - -template <typename E> -static void SortEntries( - std::vector<std::unique_ptr<E>>& entries, - const std::function<int(const StringPool::Context&, const StringPool::Context&)>& cmp) { - using UEntry = std::unique_ptr<E>; - - if (cmp != nullptr) { - std::sort(entries.begin(), entries.end(), [&cmp](const UEntry& a, const UEntry& b) -> bool { - int r = cmp(a->context, b->context); - if (r == 0) { - r = a->value.compare(b->value); - } - return r < 0; - }); - } else { - std::sort(entries.begin(), entries.end(), - [](const UEntry& a, const UEntry& b) -> bool { return a->value < b->value; }); - } -} - -void StringPool::Sort(const std::function<int(const Context&, const Context&)>& cmp) { - SortEntries(styles_, cmp); - SortEntries(strings_, cmp); - ReAssignIndices(); -} - -template <typename T> -static T* EncodeLength(T* data, size_t length) { - static_assert(std::is_integral<T>::value, "wat."); - - constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); - constexpr size_t kMaxSize = kMask - 1; - if (length > kMaxSize) { - *data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8))); - } - *data++ = length; - return data; -} - -/** - * Returns the maximum possible string length that can be successfully encoded - * using 2 units of the specified T. - * EncodeLengthMax<char> -> maximum unit length of 0x7FFF - * EncodeLengthMax<char16_t> -> maximum unit length of 0x7FFFFFFF - **/ -template <typename T> -static size_t EncodeLengthMax() { - static_assert(std::is_integral<T>::value, "wat."); - - constexpr size_t kMask = 1 << ((sizeof(T) * 8 * 2) - 1); - constexpr size_t max = kMask - 1; - return max; -} - -/** - * Returns the number of units (1 or 2) needed to encode the string length - * before writing the string. - */ -template <typename T> -static size_t EncodedLengthUnits(size_t length) { - static_assert(std::is_integral<T>::value, "wat."); - - constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); - constexpr size_t kMaxSize = kMask - 1; - return length > kMaxSize ? 2 : 1; -} - -const std::string kStringTooLarge = "STRING_TOO_LARGE"; - -static bool EncodeString(const std::string& str, const bool utf8, BigBuffer* out, - IDiagnostics* diag) { - if (utf8) { - const std::string& encoded = util::Utf8ToModifiedUtf8(str); - const ssize_t utf16_length = utf8_to_utf16_length( - reinterpret_cast<const uint8_t*>(encoded.data()), encoded.size()); - CHECK(utf16_length >= 0); - - // Make sure the lengths to be encoded do not exceed the maximum length that - // can be encoded using chars - if ((((size_t)encoded.size()) > EncodeLengthMax<char>()) - || (((size_t)utf16_length) > EncodeLengthMax<char>())) { - - diag->Error(DiagMessage() << "string too large to encode using UTF-8 " - << "written instead as '" << kStringTooLarge << "'"); - - EncodeString(kStringTooLarge, utf8, out, diag); - return false; - } - - const size_t total_size = EncodedLengthUnits<char>(utf16_length) - + EncodedLengthUnits<char>(encoded.size()) + encoded.size() + 1; - - char* data = out->NextBlock<char>(total_size); - - // First encode the UTF16 string length. - data = EncodeLength(data, utf16_length); - - // Now encode the size of the real UTF8 string. - data = EncodeLength(data, encoded.size()); - strncpy(data, encoded.data(), encoded.size()); - - } else { - const std::u16string encoded = util::Utf8ToUtf16(str); - const ssize_t utf16_length = encoded.size(); - - // Make sure the length to be encoded does not exceed the maximum possible - // length that can be encoded - if (((size_t)utf16_length) > EncodeLengthMax<char16_t>()) { - diag->Error(DiagMessage() << "string too large to encode using UTF-16 " - << "written instead as '" << kStringTooLarge << "'"); - - EncodeString(kStringTooLarge, utf8, out, diag); - return false; - } - - // Total number of 16-bit words to write. - const size_t total_size = EncodedLengthUnits<char16_t>(utf16_length) - + encoded.size() + 1; - - char16_t* data = out->NextBlock<char16_t>(total_size); - - // Encode the actual UTF16 string length. - data = EncodeLength(data, utf16_length); - const size_t byte_length = encoded.size() * sizeof(char16_t); - - // NOTE: For some reason, strncpy16(data, entry->value.data(), - // entry->value.size()) truncates the string. - memcpy(data, encoded.data(), byte_length); - - // The null-terminating character is already here due to the block of data - // being set to 0s on allocation. - } - - return true; -} - -bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8, - IDiagnostics* diag) { - bool no_error = true; - const size_t start_index = out->size(); - android::ResStringPool_header* header = out->NextBlock<android::ResStringPool_header>(); - header->header.type = util::HostToDevice16(android::RES_STRING_POOL_TYPE); - header->header.headerSize = util::HostToDevice16(sizeof(*header)); - header->stringCount = util::HostToDevice32(pool.size()); - header->styleCount = util::HostToDevice32(pool.styles_.size()); - if (utf8) { - header->flags |= android::ResStringPool_header::UTF8_FLAG; - } - - uint32_t* indices = pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr; - uint32_t* style_indices = - pool.styles_.size() != 0 ? out->NextBlock<uint32_t>(pool.styles_.size()) : nullptr; - - const size_t before_strings_index = out->size(); - header->stringsStart = before_strings_index - start_index; - - // Styles always come first. - for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) { - *indices++ = out->size() - before_strings_index; - no_error = EncodeString(entry->value, utf8, out, diag) && no_error; - } - - for (const std::unique_ptr<Entry>& entry : pool.strings_) { - *indices++ = out->size() - before_strings_index; - no_error = EncodeString(entry->value, utf8, out, diag) && no_error; - } - - out->Align4(); - - if (style_indices != nullptr) { - const size_t before_styles_index = out->size(); - header->stylesStart = util::HostToDevice32(before_styles_index - start_index); - - for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) { - *style_indices++ = out->size() - before_styles_index; - - if (!entry->spans.empty()) { - android::ResStringPool_span* span = - out->NextBlock<android::ResStringPool_span>(entry->spans.size()); - for (const Span& s : entry->spans) { - span->name.index = util::HostToDevice32(s.name.index()); - span->firstChar = util::HostToDevice32(s.first_char); - span->lastChar = util::HostToDevice32(s.last_char); - span++; - } - } - - uint32_t* spanEnd = out->NextBlock<uint32_t>(); - *spanEnd = android::ResStringPool_span::END; - } - - // The error checking code in the platform looks for an entire - // ResStringPool_span structure worth of 0xFFFFFFFF at the end - // of the style block, so fill in the remaining 2 32bit words - // with 0xFFFFFFFF. - const size_t padding_length = sizeof(android::ResStringPool_span) - - sizeof(android::ResStringPool_span::name); - uint8_t* padding = out->NextBlock<uint8_t>(padding_length); - memset(padding, 0xff, padding_length); - out->Align4(); - } - header->header.size = util::HostToDevice32(out->size() - start_index); - return no_error; -} - -bool StringPool::FlattenUtf8(BigBuffer* out, const StringPool& pool, IDiagnostics* diag) { - return Flatten(out, pool, true, diag); -} - -bool StringPool::FlattenUtf16(BigBuffer* out, const StringPool& pool, IDiagnostics* diag) { - return Flatten(out, pool, false, diag); -} - -} // namespace aapt diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h deleted file mode 100644 index 3457e0b41859..000000000000 --- a/tools/aapt2/StringPool.h +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2015 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 AAPT_STRING_POOL_H -#define AAPT_STRING_POOL_H - -#include <functional> -#include <memory> -#include <string> -#include <unordered_map> -#include <vector> - -#include "android-base/macros.h" -#include "androidfw/ConfigDescription.h" -#include "androidfw/StringPiece.h" - -#include "Diagnostics.h" -#include "util/BigBuffer.h" - -namespace aapt { - -struct Span { - std::string name; - uint32_t first_char; - uint32_t last_char; - - bool operator==(const Span& right) const { - return name == right.name && first_char == right.first_char && last_char == right.last_char; - } -}; - -struct StyleString { - std::string str; - std::vector<Span> spans; -}; - -// A StringPool for storing the value of String and StyledString resources. -// Styles and Strings are stored separately, since the runtime variant of this -// class -- ResStringPool -- requires that styled strings *always* appear first, since their -// style data is stored as an array indexed by the same indices as the main string pool array. -// Otherwise, the style data array would have to be sparse and take up more space. -class StringPool { - public: - using size_type = size_t; - - class Context { - public: - enum : uint32_t { - kHighPriority = 1u, - kNormalPriority = 0x7fffffffu, - kLowPriority = 0xffffffffu, - }; - uint32_t priority = kNormalPriority; - android::ConfigDescription config; - - Context() = default; - Context(uint32_t p, const android::ConfigDescription& c) : priority(p), config(c) {} - explicit Context(uint32_t p) : priority(p) {} - explicit Context(const android::ConfigDescription& c) : priority(kNormalPriority), config(c) { - } - }; - - class Entry; - - class Ref { - public: - Ref(); - Ref(const Ref&); - ~Ref(); - - Ref& operator=(const Ref& rhs); - bool operator==(const Ref& rhs) const; - bool operator!=(const Ref& rhs) const; - const std::string* operator->() const; - const std::string& operator*() const; - - size_t index() const; - const Context& GetContext() const; - - private: - friend class StringPool; - - explicit Ref(Entry* entry); - - Entry* entry_; - }; - - class StyleEntry; - - class StyleRef { - public: - StyleRef(); - StyleRef(const StyleRef&); - ~StyleRef(); - - StyleRef& operator=(const StyleRef& rhs); - bool operator==(const StyleRef& rhs) const; - bool operator!=(const StyleRef& rhs) const; - const StyleEntry* operator->() const; - const StyleEntry& operator*() const; - - size_t index() const; - const Context& GetContext() const; - - private: - friend class StringPool; - - explicit StyleRef(StyleEntry* entry); - - StyleEntry* entry_; - }; - - class Entry { - public: - std::string value; - Context context; - - private: - friend class StringPool; - friend class Ref; - - size_t index_; - int ref_; - const StringPool* pool_; - }; - - struct Span { - Ref name; - uint32_t first_char; - uint32_t last_char; - }; - - class StyleEntry { - public: - std::string value; - Context context; - std::vector<Span> spans; - - private: - friend class StringPool; - friend class StyleRef; - - size_t index_; - int ref_; - }; - - static bool FlattenUtf8(BigBuffer* out, const StringPool& pool, IDiagnostics* diag); - static bool FlattenUtf16(BigBuffer* out, const StringPool& pool, IDiagnostics* diag); - - StringPool() = default; - StringPool(StringPool&&) = default; - StringPool& operator=(StringPool&&) = default; - - // Adds a string to the pool, unless it already exists. Returns a reference to the string in the - // pool. - Ref MakeRef(const android::StringPiece& str); - - // Adds a string to the pool, unless it already exists, with a context object that can be used - // when sorting the string pool. Returns a reference to the string in the pool. - Ref MakeRef(const android::StringPiece& str, const Context& context); - - // Adds a string from another string pool. Returns a reference to the string in the string pool. - Ref MakeRef(const Ref& ref); - - // Adds a style to the string pool and returns a reference to it. - StyleRef MakeRef(const StyleString& str); - - // Adds a style to the string pool with a context object that can be used when sorting the string - // pool. Returns a reference to the style in the string pool. - StyleRef MakeRef(const StyleString& str, const Context& context); - - // Adds a style from another string pool. Returns a reference to the style in the string pool. - StyleRef MakeRef(const StyleRef& ref); - - // Moves pool into this one without coalescing strings. When this function returns, pool will be - // empty. - void Merge(StringPool&& pool); - - inline const std::vector<std::unique_ptr<Entry>>& strings() const { - return strings_; - } - - // Returns the number of strings in the table. - inline size_t size() const { - return styles_.size() + strings_.size(); - } - - // Reserves space for strings and styles as an optimization. - void HintWillAdd(size_t string_count, size_t style_count); - - // Sorts the strings according to their Context using some comparison function. - // Equal Contexts are further sorted by string value, lexicographically. - // If no comparison function is provided, values are only sorted lexicographically. - void Sort(const std::function<int(const Context&, const Context&)>& cmp = nullptr); - - // Removes any strings that have no references. - void Prune(); - - private: - DISALLOW_COPY_AND_ASSIGN(StringPool); - - static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag); - - Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique); - void ReAssignIndices(); - - std::vector<std::unique_ptr<Entry>> strings_; - std::vector<std::unique_ptr<StyleEntry>> styles_; - std::unordered_multimap<android::StringPiece, Entry*> indexed_strings_; -}; - -} // namespace aapt - -#endif // AAPT_STRING_POOL_H diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp deleted file mode 100644 index 6e5200bca44c..000000000000 --- a/tools/aapt2/StringPool_test.cpp +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright (C) 2015 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 "StringPool.h" - -#include <string> - -#include "androidfw/StringPiece.h" - -#include "Diagnostics.h" -#include "test/Test.h" -#include "util/Util.h" - -using ::android::StringPiece; -using ::android::StringPiece16; -using ::testing::Eq; -using ::testing::Ne; -using ::testing::NotNull; -using ::testing::Pointee; - -namespace aapt { - -TEST(StringPoolTest, InsertOneString) { - StringPool pool; - - StringPool::Ref ref = pool.MakeRef("wut"); - EXPECT_THAT(*ref, Eq("wut")); -} - -TEST(StringPoolTest, InsertTwoUniqueStrings) { - StringPool pool; - - StringPool::Ref ref_a = pool.MakeRef("wut"); - StringPool::Ref ref_b = pool.MakeRef("hey"); - - EXPECT_THAT(*ref_a, Eq("wut")); - EXPECT_THAT(*ref_b, Eq("hey")); -} - -TEST(StringPoolTest, DoNotInsertNewDuplicateString) { - StringPool pool; - - StringPool::Ref ref_a = pool.MakeRef("wut"); - StringPool::Ref ref_b = pool.MakeRef("wut"); - - EXPECT_THAT(*ref_a, Eq("wut")); - EXPECT_THAT(*ref_b, Eq("wut")); - EXPECT_THAT(pool.size(), Eq(1u)); -} - -TEST(StringPoolTest, DoNotDedupeSameStringDifferentPriority) { - StringPool pool; - - StringPool::Ref ref_a = pool.MakeRef("wut", StringPool::Context(0x81010001)); - StringPool::Ref ref_b = pool.MakeRef("wut", StringPool::Context(0x81010002)); - - EXPECT_THAT(*ref_a, Eq("wut")); - EXPECT_THAT(*ref_b, Eq("wut")); - EXPECT_THAT(pool.size(), Eq(2u)); -} - -TEST(StringPoolTest, MaintainInsertionOrderIndex) { - StringPool pool; - - StringPool::Ref ref_a = pool.MakeRef("z"); - StringPool::Ref ref_b = pool.MakeRef("a"); - StringPool::Ref ref_c = pool.MakeRef("m"); - - EXPECT_THAT(ref_a.index(), Eq(0u)); - EXPECT_THAT(ref_b.index(), Eq(1u)); - EXPECT_THAT(ref_c.index(), Eq(2u)); -} - -TEST(StringPoolTest, PruneStringsWithNoReferences) { - StringPool pool; - - StringPool::Ref ref_a = pool.MakeRef("foo"); - - { - StringPool::Ref ref_b = pool.MakeRef("wut"); - EXPECT_THAT(*ref_b, Eq("wut")); - EXPECT_THAT(pool.size(), Eq(2u)); - pool.Prune(); - EXPECT_THAT(pool.size(), Eq(2u)); - } - EXPECT_THAT(pool.size(), Eq(2u)); - - { - StringPool::Ref ref_c = pool.MakeRef("bar"); - EXPECT_THAT(pool.size(), Eq(3u)); - - pool.Prune(); - EXPECT_THAT(pool.size(), Eq(2u)); - } - EXPECT_THAT(pool.size(), Eq(2u)); - - pool.Prune(); - EXPECT_THAT(pool.size(), Eq(1u)); -} - -TEST(StringPoolTest, SortAndMaintainIndexesInStringReferences) { - StringPool pool; - - StringPool::Ref ref_a = pool.MakeRef("z"); - StringPool::Ref ref_b = pool.MakeRef("a"); - StringPool::Ref ref_c = pool.MakeRef("m"); - - EXPECT_THAT(*ref_a, Eq("z")); - EXPECT_THAT(ref_a.index(), Eq(0u)); - - EXPECT_THAT(*ref_b, Eq("a")); - EXPECT_THAT(ref_b.index(), Eq(1u)); - - EXPECT_THAT(*ref_c, Eq("m")); - EXPECT_THAT(ref_c.index(), Eq(2u)); - - pool.Sort(); - - EXPECT_THAT(*ref_a, Eq("z")); - EXPECT_THAT(ref_a.index(), Eq(2u)); - - EXPECT_THAT(*ref_b, Eq("a")); - EXPECT_THAT(ref_b.index(), Eq(0u)); - - EXPECT_THAT(*ref_c, Eq("m")); - EXPECT_THAT(ref_c.index(), Eq(1u)); -} - -TEST(StringPoolTest, SortAndStillDedupe) { - StringPool pool; - - StringPool::Ref ref_a = pool.MakeRef("z"); - StringPool::Ref ref_b = pool.MakeRef("a"); - StringPool::Ref ref_c = pool.MakeRef("m"); - - pool.Sort(); - - StringPool::Ref ref_d = pool.MakeRef("z"); - StringPool::Ref ref_e = pool.MakeRef("a"); - StringPool::Ref ref_f = pool.MakeRef("m"); - - EXPECT_THAT(ref_d.index(), Eq(ref_a.index())); - EXPECT_THAT(ref_e.index(), Eq(ref_b.index())); - EXPECT_THAT(ref_f.index(), Eq(ref_c.index())); -} - -TEST(StringPoolTest, AddStyles) { - StringPool pool; - - StringPool::StyleRef ref = pool.MakeRef(StyleString{{"android"}, {Span{{"b"}, 2, 6}}}); - EXPECT_THAT(ref.index(), Eq(0u)); - EXPECT_THAT(ref->value, Eq("android")); - ASSERT_THAT(ref->spans.size(), Eq(1u)); - - const StringPool::Span& span = ref->spans.front(); - EXPECT_THAT(*span.name, Eq("b")); - EXPECT_THAT(span.first_char, Eq(2u)); - EXPECT_THAT(span.last_char, Eq(6u)); -} - -TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) { - StringPool pool; - - StringPool::Ref ref = pool.MakeRef("android"); - - StyleString str{{"android"}}; - StringPool::StyleRef style_ref = pool.MakeRef(StyleString{{"android"}}); - - EXPECT_THAT(ref.index(), Ne(style_ref.index())); -} - -TEST(StringPoolTest, StylesAndStringsAreSeparateAfterSorting) { - StringPool pool; - - StringPool::StyleRef ref_a = pool.MakeRef(StyleString{{"beta"}}); - StringPool::Ref ref_b = pool.MakeRef("alpha"); - StringPool::StyleRef ref_c = pool.MakeRef(StyleString{{"alpha"}}); - - EXPECT_THAT(ref_b.index(), Ne(ref_c.index())); - - pool.Sort(); - - EXPECT_THAT(ref_c.index(), Eq(0u)); - EXPECT_THAT(ref_a.index(), Eq(1u)); - EXPECT_THAT(ref_b.index(), Eq(2u)); -} - -TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { - using namespace android; // For NO_ERROR on Windows. - StdErrDiagnostics diag; - - StringPool pool; - BigBuffer buffer(1024); - StringPool::FlattenUtf8(&buffer, pool, &diag); - - std::unique_ptr<uint8_t[]> data = util::Copy(buffer); - ResStringPool test; - ASSERT_THAT(test.setTo(data.get(), buffer.size()), Eq(NO_ERROR)); -} - -TEST(StringPoolTest, FlattenOddCharactersUtf16) { - using namespace android; // For NO_ERROR on Windows. - StdErrDiagnostics diag; - - StringPool pool; - pool.MakeRef("\u093f"); - BigBuffer buffer(1024); - StringPool::FlattenUtf16(&buffer, pool, &diag); - - std::unique_ptr<uint8_t[]> data = util::Copy(buffer); - ResStringPool test; - ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); - auto str = test.stringAt(0); - ASSERT_TRUE(str.has_value()); - EXPECT_THAT(str->size(), Eq(1u)); - EXPECT_THAT(str->data(), Pointee(Eq(u'\u093f'))); - EXPECT_THAT(str->data()[1], Eq(0u)); -} - -constexpr const char* sLongString = - "バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑" - "え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限" - "します。メール、SMSや、同期を使 " - "用するその他のアプリは、起動しても更新されないことがあります。バッテリーセ" - "ーバーは端末の充電中は自動的にOFFになります。"; - -TEST(StringPoolTest, Flatten) { - using namespace android; // For NO_ERROR on Windows. - StdErrDiagnostics diag; - - StringPool pool; - - StringPool::Ref ref_a = pool.MakeRef("hello"); - StringPool::Ref ref_b = pool.MakeRef("goodbye"); - StringPool::Ref ref_c = pool.MakeRef(sLongString); - StringPool::Ref ref_d = pool.MakeRef(""); - StringPool::StyleRef ref_e = - pool.MakeRef(StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}}); - - // Styles are always first. - EXPECT_THAT(ref_e.index(), Eq(0u)); - - EXPECT_THAT(ref_a.index(), Eq(1u)); - EXPECT_THAT(ref_b.index(), Eq(2u)); - EXPECT_THAT(ref_c.index(), Eq(3u)); - EXPECT_THAT(ref_d.index(), Eq(4u)); - - BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)}; - StringPool::FlattenUtf8(&buffers[0], pool, &diag); - StringPool::FlattenUtf16(&buffers[1], pool, &diag); - - // Test both UTF-8 and UTF-16 buffers. - for (const BigBuffer& buffer : buffers) { - std::unique_ptr<uint8_t[]> data = util::Copy(buffer); - - ResStringPool test; - ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); - - EXPECT_THAT(util::GetString(test, 1), Eq("hello")); - EXPECT_THAT(util::GetString16(test, 1), Eq(u"hello")); - - EXPECT_THAT(util::GetString(test, 2), Eq("goodbye")); - EXPECT_THAT(util::GetString16(test, 2), Eq(u"goodbye")); - - EXPECT_THAT(util::GetString(test, 3), Eq(sLongString)); - EXPECT_THAT(util::GetString16(test, 3), Eq(util::Utf8ToUtf16(sLongString))); - - EXPECT_TRUE(test.stringAt(4).has_value() || test.string8At(4).has_value()); - - EXPECT_THAT(util::GetString(test, 0), Eq("style")); - EXPECT_THAT(util::GetString16(test, 0), Eq(u"style")); - - auto span_result = test.styleAt(0); - ASSERT_TRUE(span_result.has_value()); - - const ResStringPool_span* span = span_result->unsafe_ptr(); - EXPECT_THAT(util::GetString(test, span->name.index), Eq("b")); - EXPECT_THAT(util::GetString16(test, span->name.index), Eq(u"b")); - EXPECT_THAT(span->firstChar, Eq(0u)); - EXPECT_THAT(span->lastChar, Eq(1u)); - span++; - - ASSERT_THAT(span->name.index, Ne(ResStringPool_span::END)); - EXPECT_THAT(util::GetString(test, span->name.index), Eq("i")); - EXPECT_THAT(util::GetString16(test, span->name.index), Eq(u"i")); - EXPECT_THAT(span->firstChar, Eq(2u)); - EXPECT_THAT(span->lastChar, Eq(3u)); - span++; - - EXPECT_THAT(span->name.index, Eq(ResStringPool_span::END)); - } -} - -TEST(StringPoolTest, ModifiedUTF8) { - using namespace android; // For NO_ERROR on Windows. - StdErrDiagnostics diag; - StringPool pool; - StringPool::Ref ref_a = pool.MakeRef("\xF0\x90\x90\x80"); // 𐐀 (U+10400) - StringPool::Ref ref_b = pool.MakeRef("foo \xF0\x90\x90\xB7 bar"); // 𐐷 (U+10437) - StringPool::Ref ref_c = pool.MakeRef("\xF0\x90\x90\x80\xF0\x90\x90\xB7"); - - BigBuffer buffer(1024); - StringPool::FlattenUtf8(&buffer, pool, &diag); - std::unique_ptr<uint8_t[]> data = util::Copy(buffer); - - // Check that the codepoints are encoded using two three-byte surrogate pairs - ResStringPool test; - ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); - auto str = test.string8At(0); - ASSERT_TRUE(str.has_value()); - EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80")); - - str = test.string8At(1); - ASSERT_TRUE(str.has_value()); - EXPECT_THAT(str->to_string(), Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar")); - - str = test.string8At(2); - ASSERT_TRUE(str.has_value()); - EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7")); - - // Check that retrieving the strings returns the original UTF-8 character bytes - EXPECT_THAT(util::GetString(test, 0), Eq("\xF0\x90\x90\x80")); - EXPECT_THAT(util::GetString(test, 1), Eq("foo \xF0\x90\x90\xB7 bar")); - EXPECT_THAT(util::GetString(test, 2), Eq("\xF0\x90\x90\x80\xF0\x90\x90\xB7")); -} - -TEST(StringPoolTest, MaxEncodingLength) { - StdErrDiagnostics diag; - using namespace android; // For NO_ERROR on Windows. - ResStringPool test; - - StringPool pool; - pool.MakeRef("aaaaaaaaaa"); - BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)}; - - // Make sure a UTF-8 string under the maximum length does not produce an error - EXPECT_THAT(StringPool::FlattenUtf8(&buffers[0], pool, &diag), Eq(true)); - std::unique_ptr<uint8_t[]> data = util::Copy(buffers[0]); - test.setTo(data.get(), buffers[0].size()); - EXPECT_THAT(util::GetString(test, 0), Eq("aaaaaaaaaa")); - - // Make sure a UTF-16 string under the maximum length does not produce an error - EXPECT_THAT(StringPool::FlattenUtf16(&buffers[1], pool, &diag), Eq(true)); - data = util::Copy(buffers[1]); - test.setTo(data.get(), buffers[1].size()); - EXPECT_THAT(util::GetString16(test, 0), Eq(u"aaaaaaaaaa")); - - StringPool pool2; - std::string longStr(50000, 'a'); - pool2.MakeRef("this fits1"); - pool2.MakeRef(longStr); - pool2.MakeRef("this fits2"); - BigBuffer buffers2[2] = {BigBuffer(1024), BigBuffer(1024)}; - - // Make sure a string that exceeds the maximum length of UTF-8 produces an - // error and writes a shorter error string instead - EXPECT_THAT(StringPool::FlattenUtf8(&buffers2[0], pool2, &diag), Eq(false)); - data = util::Copy(buffers2[0]); - test.setTo(data.get(), buffers2[0].size()); - EXPECT_THAT(util::GetString(test, 0), "this fits1"); - EXPECT_THAT(util::GetString(test, 1), "STRING_TOO_LARGE"); - EXPECT_THAT(util::GetString(test, 2), "this fits2"); - - // Make sure a string that a string that exceeds the maximum length of UTF-8 - // but not UTF-16 does not error for UTF-16 - StringPool pool3; - std::u16string longStr16(50000, 'a'); - pool3.MakeRef(longStr); - EXPECT_THAT(StringPool::FlattenUtf16(&buffers2[1], pool3, &diag), Eq(true)); - data = util::Copy(buffers2[1]); - test.setTo(data.get(), buffers2[1].size()); - EXPECT_THAT(util::GetString16(test, 0), Eq(longStr16)); -} - -} // namespace aapt diff --git a/tools/aapt2/ValueTransformer.h b/tools/aapt2/ValueTransformer.h index 6fc4a191b04b..68242659dc73 100644 --- a/tools/aapt2/ValueTransformer.h +++ b/tools/aapt2/ValueTransformer.h @@ -19,7 +19,7 @@ #include <memory> -#include "StringPool.h" +#include "androidfw/StringPool.h" namespace aapt { @@ -82,7 +82,7 @@ struct Macro; struct ValueTransformer { // `new_pool` is the new StringPool that newly created Values should use for string storing string // values. - explicit ValueTransformer(StringPool* new_pool); + explicit ValueTransformer(android::StringPool* new_pool); virtual ~ValueTransformer() = default; AAPT_TRANSFORM_ITEM(Id); @@ -101,7 +101,7 @@ struct ValueTransformer { AAPT_TRANSFORM_VALUE(Macro); protected: - StringPool* const pool_; + android::StringPool* const pool_; }; #undef AAPT_TRANSFORM_VALUE @@ -127,4 +127,4 @@ struct TransformableItem : public TransformableValue<Derived, Base> { // Implementation #include "ValueTransformer_inline.h" -#endif // AAPT_VALUE_TRANSFORMER_H
\ No newline at end of file +#endif // AAPT_VALUE_TRANSFORMER_H diff --git a/tools/aapt2/ValueTransformer_inline.h b/tools/aapt2/ValueTransformer_inline.h index c6c07c0fd6f9..4f8eadca54e3 100644 --- a/tools/aapt2/ValueTransformer_inline.h +++ b/tools/aapt2/ValueTransformer_inline.h @@ -19,7 +19,7 @@ namespace aapt { -inline ValueTransformer::ValueTransformer(StringPool* new_pool) : pool_(new_pool) { +inline ValueTransformer::ValueTransformer(android::StringPool* new_pool) : pool_(new_pool) { } template <typename Derived, typename Base> @@ -44,4 +44,4 @@ Item* TransformableItem<Derived, Base>::TransformItemImpl(ValueTransformer& tran } // namespace aapt -#endif // AAPT_VALUE_TRANSFORMER_IMPL_H
\ No newline at end of file +#endif // AAPT_VALUE_TRANSFORMER_IMPL_H diff --git a/tools/aapt2/cmd/ApkInfo.cpp b/tools/aapt2/cmd/ApkInfo.cpp new file mode 100644 index 000000000000..3c0831c7ec0d --- /dev/null +++ b/tools/aapt2/cmd/ApkInfo.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 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 "ApkInfo.h" + +#include <fcntl.h> + +#include <iostream> +#include <memory> + +#include "LoadedApk.h" +#include "android-base/file.h" // for O_BINARY +#include "android-base/utf8.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/StringPiece.h" +#include "dump/DumpManifest.h" +#include "format/proto/ProtoSerialize.h" + +using ::android::StringPiece; + +namespace aapt { + +int ExportApkInfo(LoadedApk* apk, bool include_resource_table, + const std::unordered_set<std::string>& xml_resources, pb::ApkInfo* out_apk_info, + android::IDiagnostics* diag) { + auto result = DumpBadgingProto(apk, out_apk_info->mutable_badging(), diag); + if (result != 0) { + return result; + } + + if (include_resource_table) { + SerializeTableToPb(*apk->GetResourceTable(), out_apk_info->mutable_resource_table(), diag); + } + + for (auto& xml_resource : xml_resources) { + auto xml = apk->LoadXml(xml_resource, diag); + if (xml) { + auto out_xml = out_apk_info->add_xml_files(); + out_xml->set_path(xml_resource); + SerializeXmlResourceToPb(*xml, out_xml->mutable_root(), + {/* remove_empty_text_nodes= */ true}); + } + } + + return 0; +} + +int ApkInfoCommand::Action(const std::vector<std::string>& args) { + if (args.size() != 1) { + std::cerr << "must supply a single APK\n"; + Usage(&std::cerr); + return 1; + } + StringPiece path = args[0]; + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, diag_); + if (!apk) { + return 1; + } + + pb::ApkInfo out_apk_info; + int result = + ExportApkInfo(apk.get(), include_resource_table_, xml_resources_, &out_apk_info, diag_); + if (result != 0) { + diag_->Error(android::DiagMessage() << "Failed to serialize ApkInfo into proto."); + return result; + } + + int mode = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY; + int outfd = ::android::base::utf8::open(output_path_.c_str(), mode, 0666); + if (outfd == -1) { + diag_->Error(android::DiagMessage() << "Failed to open output file."); + return 1; + } + + bool is_serialized = out_apk_info.SerializeToFileDescriptor(outfd); + close(outfd); + + return is_serialized ? 0 : 1; +} + +} // namespace aapt
\ No newline at end of file diff --git a/tools/aapt2/cmd/ApkInfo.h b/tools/aapt2/cmd/ApkInfo.h new file mode 100644 index 000000000000..bb92a8579bc0 --- /dev/null +++ b/tools/aapt2/cmd/ApkInfo.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 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 AAPT2_APKINFO_H +#define AAPT2_APKINFO_H + +#include "Command.h" +#include "androidfw/IDiagnostics.h" + +namespace aapt { + +class ApkInfoCommand : public Command { + public: + explicit ApkInfoCommand(android::IDiagnostics* diag) : Command("apkinfo"), diag_(diag) { + SetDescription("Dump information about an APK in binary proto format."); + AddRequiredFlag("-o", "Output path", &output_path_, Command::kPath); + AddOptionalSwitch("--include-resource-table", "Include the resource table data into output.", + &include_resource_table_); + AddOptionalFlagList("--include-xml", + "Include an XML file content into output. Multiple XML files might be " + "requested during single invocation.", + &xml_resources_); + } + + int Action(const std::vector<std::string>& args) override; + + private: + android::IDiagnostics* diag_; + std::string output_path_; + bool include_resource_table_ = false; + std::unordered_set<std::string> xml_resources_; +}; + +} // namespace aapt + +#endif // AAPT2_APKINFO_H diff --git a/tools/aapt2/cmd/ApkInfo_test.cpp b/tools/aapt2/cmd/ApkInfo_test.cpp new file mode 100644 index 000000000000..97d4abebfe6a --- /dev/null +++ b/tools/aapt2/cmd/ApkInfo_test.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 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 "ApkInfo.h" + +#include "ApkInfo.pb.h" +#include "LoadedApk.h" +#include "android-base/unique_fd.h" +#include "io/StringStream.h" +#include "test/Test.h" + +using testing::Eq; +using testing::Ne; + +namespace aapt { + +using ApkInfoTest = CommandTestFixture; + +void AssertProducedAndExpectedInfo(const std::string& produced_path, + const std::string& expected_path) { + android::base::unique_fd fd(open(produced_path.c_str(), O_RDONLY)); + ASSERT_NE(fd.get(), -1); + + pb::ApkInfo produced_apk_info; + produced_apk_info.ParseFromFileDescriptor(fd.get()); + + std::string expected; + ::android::base::ReadFileToString(expected_path, &expected); + + EXPECT_EQ(produced_apk_info.DebugString(), expected); +} + +static android::NoOpDiagnostics noop_diag; + +TEST_F(ApkInfoTest, ApkInfoWithBadging) { + auto apk_path = file::BuildPath( + {android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", "components.apk"}); + auto out_info_path = GetTestPath("apk_info.pb"); + + ApkInfoCommand command(&noop_diag); + command.Execute({"-o", out_info_path, apk_path}, &std::cerr); + + auto expected_path = + file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", + "components_expected_proto.txt"}); + AssertProducedAndExpectedInfo(out_info_path, expected_path); +} + +TEST_F(ApkInfoTest, FullApkInfo) { + auto apk_path = file::BuildPath( + {android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", "components.apk"}); + auto out_info_path = GetTestPath("apk_info.pb"); + + ApkInfoCommand command(&noop_diag); + command.Execute({"-o", out_info_path, "--include-resource-table", "--include-xml", + "AndroidManifest.xml", "--include-xml", "res/oy.xml", apk_path}, + &std::cerr); + + auto expected_path = + file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", + "components_full_proto.txt"}); + AssertProducedAndExpectedInfo(out_info_path, expected_path); +} + +} // namespace aapt diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp index b1452fad0e8f..514651e92c27 100644 --- a/tools/aapt2/cmd/Command.cpp +++ b/tools/aapt2/cmd/Command.cpp @@ -33,7 +33,7 @@ using android::StringPiece; namespace aapt { -std::string GetSafePath(const StringPiece& arg) { +std::string GetSafePath(StringPiece arg) { #ifdef _WIN32 // If the path exceeds the maximum path length for Windows, encode the path using the // extended-length prefix @@ -47,63 +47,62 @@ std::string GetSafePath(const StringPiece& arg) { return path8; #else - return arg.to_string(); + return std::string(arg); #endif } -void Command::AddRequiredFlag(const StringPiece& name, const StringPiece& description, - std::string* value, uint32_t flags) { - auto func = [value, flags](const StringPiece& arg) -> bool { - *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string(); +void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::string* value, + uint32_t flags) { + auto func = [value, flags](StringPiece arg) -> bool { + *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg); return true; }; flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func)); } -void Command::AddRequiredFlagList(const StringPiece& name, const StringPiece& description, +void Command::AddRequiredFlagList(StringPiece name, StringPiece description, std::vector<std::string>* value, uint32_t flags) { - auto func = [value, flags](const StringPiece& arg) -> bool { - value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string()); + auto func = [value, flags](StringPiece arg) -> bool { + value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg)); return true; }; flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func)); } -void Command::AddOptionalFlag(const StringPiece& name, const StringPiece& description, +void Command::AddOptionalFlag(StringPiece name, StringPiece description, std::optional<std::string>* value, uint32_t flags) { - auto func = [value, flags](const StringPiece& arg) -> bool { - *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string(); + auto func = [value, flags](StringPiece arg) -> bool { + *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg); return true; }; flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); } -void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description, +void Command::AddOptionalFlagList(StringPiece name, StringPiece description, std::vector<std::string>* value, uint32_t flags) { - auto func = [value, flags](const StringPiece& arg) -> bool { - value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string()); + auto func = [value, flags](StringPiece arg) -> bool { + value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg)); return true; }; flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); } -void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description, +void Command::AddOptionalFlagList(StringPiece name, StringPiece description, std::unordered_set<std::string>* value) { - auto func = [value](const StringPiece& arg) -> bool { - value->insert(arg.to_string()); + auto func = [value](StringPiece arg) -> bool { + value->emplace(arg); return true; }; flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); } -void Command::AddOptionalSwitch(const StringPiece& name, const StringPiece& description, - bool* value) { - auto func = [value](const StringPiece& arg) -> bool { +void Command::AddOptionalSwitch(StringPiece name, StringPiece description, bool* value) { + auto func = [value](StringPiece arg) -> bool { *value = true; return true; }; @@ -120,8 +119,8 @@ void Command::AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool } } -void Command::SetDescription(const StringPiece& description) { - description_ = description.to_string(); +void Command::SetDescription(StringPiece description) { + description_ = std::string(description); } void Command::Usage(std::ostream* out) { @@ -183,7 +182,7 @@ int Command::Execute(const std::vector<StringPiece>& args, std::ostream* out_err std::vector<std::string> file_args; for (size_t i = 0; i < args.size(); i++) { - const StringPiece& arg = args[i]; + StringPiece arg = args[i]; if (*(arg.data()) != '-') { // Continue parsing as the subcommand if the first argument matches one of the subcommands if (i == 0) { diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h index 8678cda59856..1416e980ed19 100644 --- a/tools/aapt2/cmd/Command.h +++ b/tools/aapt2/cmd/Command.h @@ -30,13 +30,10 @@ namespace aapt { class Command { public: - explicit Command(const android::StringPiece& name) - : name_(name.to_string()), full_subcommand_name_(name.to_string()){}; + explicit Command(android::StringPiece name) : name_(name), full_subcommand_name_(name){}; - explicit Command(const android::StringPiece& name, const android::StringPiece& short_name) - : name_(name.to_string()), - short_name_(short_name.to_string()), - full_subcommand_name_(name.to_string()){}; + explicit Command(android::StringPiece name, android::StringPiece short_name) + : name_(name), short_name_(short_name), full_subcommand_name_(name){}; Command(Command&&) = default; Command& operator=(Command&&) = default; @@ -52,30 +49,26 @@ class Command { kPath = 1 << 0, }; - void AddRequiredFlag(const android::StringPiece& name, const android::StringPiece& description, + void AddRequiredFlag(android::StringPiece name, android::StringPiece description, std::string* value, uint32_t flags = 0); - void AddRequiredFlagList(const android::StringPiece& name, - const android::StringPiece& description, std::vector<std::string>* value, - uint32_t flags = 0); + void AddRequiredFlagList(android::StringPiece name, android::StringPiece description, + std::vector<std::string>* value, uint32_t flags = 0); - void AddOptionalFlag(const android::StringPiece& name, const android::StringPiece& description, + void AddOptionalFlag(android::StringPiece name, android::StringPiece description, std::optional<std::string>* value, uint32_t flags = 0); - void AddOptionalFlagList(const android::StringPiece& name, - const android::StringPiece& description, std::vector<std::string>* value, - uint32_t flags = 0); + void AddOptionalFlagList(android::StringPiece name, android::StringPiece description, + std::vector<std::string>* value, uint32_t flags = 0); - void AddOptionalFlagList(const android::StringPiece& name, - const android::StringPiece& description, + void AddOptionalFlagList(android::StringPiece name, android::StringPiece description, std::unordered_set<std::string>* value); - void AddOptionalSwitch(const android::StringPiece& name, const android::StringPiece& description, - bool* value); + void AddOptionalSwitch(android::StringPiece name, android::StringPiece description, bool* value); void AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool experimental = false); - void SetDescription(const android::StringPiece& name); + void SetDescription(android::StringPiece name); // Prints the help menu of the command. void Usage(std::ostream* out); @@ -90,17 +83,21 @@ class Command { private: struct Flag { - explicit Flag(const android::StringPiece& name, const android::StringPiece& description, + explicit Flag(android::StringPiece name, android::StringPiece description, const bool is_required, const size_t num_args, - std::function<bool(const android::StringPiece& value)>&& action) - : name(name.to_string()), description(description.to_string()), is_required(is_required), - num_args(num_args), action(std::move(action)) {} + std::function<bool(android::StringPiece value)>&& action) + : name(name), + description(description), + is_required(is_required), + num_args(num_args), + action(std::move(action)) { + } const std::string name; const std::string description; const bool is_required; const size_t num_args; - const std::function<bool(const android::StringPiece& value)> action; + const std::function<bool(android::StringPiece value)> action; bool found = false; }; diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index fe560180bd48..03f9715fb265 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -17,19 +17,17 @@ #include "Compile.h" #include <dirent.h> + #include <string> +#include "ResourceParser.h" +#include "ResourceTable.h" #include "android-base/errors.h" #include "android-base/file.h" #include "android-base/utf8.h" #include "androidfw/ConfigDescription.h" +#include "androidfw/IDiagnostics.h" #include "androidfw/StringPiece.h" -#include "google/protobuf/io/coded_stream.h" -#include "google/protobuf/io/zero_copy_stream_impl_lite.h" - -#include "Diagnostics.h" -#include "ResourceParser.h" -#include "ResourceTable.h" #include "cmd/Util.h" #include "compile/IdAssigner.h" #include "compile/InlineXmlFormatParser.h" @@ -39,6 +37,8 @@ #include "format/Archive.h" #include "format/Container.h" #include "format/proto/ProtoSerialize.h" +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/io/zero_copy_stream_impl_lite.h" #include "io/BigBufferStream.h" #include "io/FileStream.h" #include "io/FileSystem.h" @@ -61,7 +61,7 @@ using ::google::protobuf::io::CopyingOutputStreamAdaptor; namespace aapt { struct ResourcePathData { - Source source; + android::Source source; std::string resource_dir; std::string name; std::string extension; @@ -122,12 +122,15 @@ static std::optional<ResourcePathData> ExtractResourcePathData(const std::string } } - const Source res_path = options.source_path - ? StringPiece(options.source_path.value()) - : StringPiece(path); + const android::Source res_path = + options.source_path ? StringPiece(options.source_path.value()) : StringPiece(path); - return ResourcePathData{res_path, dir_str.to_string(), name.to_string(), - extension.to_string(), config_str.to_string(), config}; + return ResourcePathData{res_path, + std::string(dir_str), + std::string(name), + std::string(extension), + std::string(config_str), + config}; } static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) { @@ -154,8 +157,8 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, { auto fin = file->OpenInputStream(); if (fin->HadError()) { - context->GetDiagnostics()->Error(DiagMessage(path_data.source) - << "failed to open file: " << fin->GetError()); + context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) + << "failed to open file: " << fin->GetError()); return false; } @@ -191,7 +194,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, // Create the file/zip entry. if (!writer->StartEntry(output_path, 0)) { - context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open"); + context->GetDiagnostics()->Error(android::DiagMessage(output_path) << "failed to open"); return false; } @@ -204,13 +207,13 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, pb::ResourceTable pb_table; SerializeTableToPb(table, &pb_table, context->GetDiagnostics()); if (!container_writer.AddResTableEntry(pb_table)) { - context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write"); + context->GetDiagnostics()->Error(android::DiagMessage(output_path) << "failed to write"); return false; } } if (!writer->FinishEntry()) { - context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry"); + context->GetDiagnostics()->Error(android::DiagMessage(output_path) << "failed to finish entry"); return false; } @@ -218,7 +221,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, io::FileOutputStream fout_text(options.generate_text_symbols_path.value()); if (fout_text.HadError()) { - context->GetDiagnostics()->Error(DiagMessage() + context->GetDiagnostics()->Error(android::DiagMessage() << "failed writing to'" << options.generate_text_symbols_path.value() << "': " << fout_text.GetError()); @@ -243,9 +246,9 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, r_txt_printer.Print("private "); } - if (type->type != ResourceType::kStyleable) { + if (type->named_type.type != ResourceType::kStyleable) { r_txt_printer.Print("int "); - r_txt_printer.Print(to_string(type->type)); + r_txt_printer.Print(type->named_type.to_string()); r_txt_printer.Print(" "); r_txt_printer.Println(entry->name); } else { @@ -280,13 +283,13 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, return true; } -static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file, +static bool WriteHeaderAndDataToWriter(StringPiece output_path, const ResourceFile& file, io::KnownSizeInputStream* in, IArchiveWriter* writer, - IDiagnostics* diag) { + android::IDiagnostics* diag) { TRACE_CALL(); // Start the entry so we can write the header. if (!writer->StartEntry(output_path, 0)) { - diag->Error(DiagMessage(output_path) << "failed to open file"); + diag->Error(android::DiagMessage(output_path) << "failed to open file"); return false; } @@ -300,20 +303,20 @@ static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const Res SerializeCompiledFileToPb(file, &pb_compiled_file); if (!container_writer.AddResFileEntry(pb_compiled_file, in)) { - diag->Error(DiagMessage(output_path) << "failed to write entry data"); + diag->Error(android::DiagMessage(output_path) << "failed to write entry data"); return false; } } if (!writer->FinishEntry()) { - diag->Error(DiagMessage(output_path) << "failed to finish writing data"); + diag->Error(android::DiagMessage(output_path) << "failed to finish writing data"); return false; } return true; } -static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres, - ContainerWriter* container_writer, IDiagnostics* diag) { +static bool FlattenXmlToOutStream(StringPiece output_path, const xml::XmlResource& xmlres, + ContainerWriter* container_writer, android::IDiagnostics* diag) { pb::internal::CompiledFile pb_compiled_file; SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file); @@ -324,7 +327,7 @@ static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::Xml io::StringInputStream serialized_in(serialized_xml); if (!container_writer->AddResFileEntry(pb_compiled_file, &serialized_in)) { - diag->Error(DiagMessage(output_path) << "failed to write entry data"); + diag->Error(android::DiagMessage(output_path) << "failed to write entry data"); return false; } return true; @@ -334,12 +337,12 @@ static bool IsValidFile(IAaptContext* context, const std::string& input_path) { const file::FileType file_type = file::GetFileType(input_path); if (file_type != file::FileType::kRegular && file_type != file::FileType::kSymlink) { if (file_type == file::FileType::kDirectory) { - context->GetDiagnostics()->Error(DiagMessage(input_path) + context->GetDiagnostics()->Error(android::DiagMessage(input_path) << "resource file cannot be a directory"); } else if (file_type == file::FileType::kNonExistant) { - context->GetDiagnostics()->Error(DiagMessage(input_path) << "file not found"); + context->GetDiagnostics()->Error(android::DiagMessage(input_path) << "file not found"); } else { - context->GetDiagnostics()->Error(DiagMessage(input_path) + context->GetDiagnostics()->Error(android::DiagMessage(input_path) << "not a valid resource file"); } return false; @@ -352,14 +355,14 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, const std::string& output_path) { TRACE_CALL(); if (context->IsVerbose()) { - context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML"); + context->GetDiagnostics()->Note(android::DiagMessage(path_data.source) << "compiling XML"); } std::unique_ptr<xml::XmlResource> xmlres; { auto fin = file->OpenInputStream(); if (fin->HadError()) { - context->GetDiagnostics()->Error(DiagMessage(path_data.source) + context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << "failed to open file: " << fin->GetError()); return false; } @@ -389,7 +392,7 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, // Start the entry so we can write the header. if (!writer->StartEntry(output_path, 0)) { - context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open file"); + context->GetDiagnostics()->Error(android::DiagMessage(output_path) << "failed to open file"); return false; } @@ -416,7 +419,8 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, } if (!writer->FinishEntry()) { - context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data"); + context->GetDiagnostics()->Error(android::DiagMessage(output_path) + << "failed to finish writing data"); return false; } @@ -424,7 +428,7 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, io::FileOutputStream fout_text(options.generate_text_symbols_path.value()); if (fout_text.HadError()) { - context->GetDiagnostics()->Error(DiagMessage() + context->GetDiagnostics()->Error(android::DiagMessage() << "failed writing to'" << options.generate_text_symbols_path.value() << "': " << fout_text.GetError()); @@ -452,10 +456,10 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, const std::string& output_path) { TRACE_CALL(); if (context->IsVerbose()) { - context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling PNG"); + context->GetDiagnostics()->Note(android::DiagMessage(path_data.source) << "compiling PNG"); } - BigBuffer buffer(4096); + android::BigBuffer buffer(4096); ResourceFile res_file; res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name); res_file.config = path_data.config; @@ -465,11 +469,12 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, { auto data = file->OpenAsData(); if (!data) { - context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to open file "); + context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) + << "failed to open file "); return false; } - BigBuffer crunched_png_buffer(4096); + android::BigBuffer crunched_png_buffer(4096); io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer); // Ensure that we only keep the chunks we care about if we end up @@ -486,7 +491,7 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, std::string err; nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err); if (!nine_patch) { - context->GetDiagnostics()->Error(DiagMessage() << err); + context->GetDiagnostics()->Error(android::DiagMessage() << err); return false; } @@ -503,8 +508,8 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, } if (context->IsVerbose()) { - context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "9-patch: " - << *nine_patch); + context->GetDiagnostics()->Note(android::DiagMessage(path_data.source) + << "9-patch: " << *nine_patch); } } @@ -522,13 +527,13 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, // The re-encoded PNG is larger than the original, and there is // no mandatory transformation. Use the original. if (context->IsVerbose()) { - context->GetDiagnostics()->Note(DiagMessage(path_data.source) + context->GetDiagnostics()->Note(android::DiagMessage(path_data.source) << "original PNG is smaller than crunched PNG" << ", using original"); } png_chunk_filter.Rewind(); - BigBuffer filtered_png_buffer(4096); + android::BigBuffer filtered_png_buffer(4096); io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer); io::Copy(&filtered_png_buffer_out, &png_chunk_filter); buffer.AppendBuffer(std::move(filtered_png_buffer)); @@ -537,14 +542,14 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, if (context->IsVerbose()) { // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes. // This will help catch exotic cases where the new code may generate larger PNGs. - std::stringstream legacy_stream(content.to_string()); - BigBuffer legacy_buffer(4096); + std::stringstream legacy_stream{std::string(content)}; + android::BigBuffer legacy_buffer(4096); Png png(context->GetDiagnostics()); if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) { return false; } - context->GetDiagnostics()->Note(DiagMessage(path_data.source) + context->GetDiagnostics()->Note(android::DiagMessage(path_data.source) << "legacy=" << legacy_buffer.size() << " new=" << buffer.size()); } @@ -560,7 +565,7 @@ static bool CompileFile(IAaptContext* context, const CompileOptions& options, const std::string& output_path) { TRACE_CALL(); if (context->IsVerbose()) { - context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling file"); + context->GetDiagnostics()->Note(android::DiagMessage(path_data.source) << "compiling file"); } ResourceFile res_file; @@ -571,7 +576,8 @@ static bool CompileFile(IAaptContext* context, const CompileOptions& options, auto data = file->OpenAsData(); if (!data) { - context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to open file "); + context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) + << "failed to open file "); return false; } @@ -581,7 +587,7 @@ static bool CompileFile(IAaptContext* context, const CompileOptions& options, class CompileContext : public IAaptContext { public: - explicit CompileContext(IDiagnostics* diagnostics) : diagnostics_(diagnostics) { + explicit CompileContext(android::IDiagnostics* diagnostics) : diagnostics_(diagnostics) { } PackageType GetPackageType() override { @@ -597,7 +603,7 @@ class CompileContext : public IAaptContext { return verbose_; } - IDiagnostics* GetDiagnostics() override { + android::IDiagnostics* GetDiagnostics() override { return diagnostics_; } @@ -633,7 +639,7 @@ class CompileContext : public IAaptContext { private: DISALLOW_COPY_AND_ASSIGN(CompileContext); - IDiagnostics* diagnostics_; + android::IDiagnostics* diagnostics_; bool verbose_ = false; }; @@ -665,7 +671,7 @@ int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* path, inputs->GetDirSeparator(), &err_str, options)) { path_data = maybe_path_data.value(); } else { - context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << err_str); + context->GetDiagnostics()->Error(android::DiagMessage(file->GetSource()) << err_str); error = true; continue; } @@ -688,8 +694,8 @@ int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* } } } else { - context->GetDiagnostics()->Error(DiagMessage() - << "invalid file path '" << path_data.source << "'"); + context->GetDiagnostics()->Error(android::DiagMessage() + << "invalid file path '" << path_data.source << "'"); error = true; continue; } @@ -699,15 +705,16 @@ int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* if (compile_func != &CompileFile && !options.legacy_mode && std::count(path_data.name.begin(), path_data.name.end(), '.') != 0) { error = true; - context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) - << "file name cannot contain '.' other than for" - << " specifying the extension"); + context->GetDiagnostics()->Error(android::DiagMessage(file->GetSource()) + << "file name cannot contain '.' other than for" + << " specifying the extension"); continue; } const std::string out_path = BuildIntermediateContainerFilename(path_data); if (!compile_func(context, options, path_data, file, output_writer, out_path)) { - context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "file failed to compile"); + context->GetDiagnostics()->Error(android::DiagMessage(file->GetSource()) + << "file failed to compile"); error = true; } } @@ -728,9 +735,10 @@ int CompileCommand::Action(const std::vector<std::string>& args) { } else if (visibility_.value() == "default") { options_.visibility = Visibility::Level::kUndefined; } else { - context.GetDiagnostics()->Error( - DiagMessage() << "Unrecognized visibility level passes to --visibility: '" - << visibility_.value() << "'. Accepted levels: public, private, default"); + context.GetDiagnostics()->Error(android::DiagMessage() + << "Unrecognized visibility level passes to --visibility: '" + << visibility_.value() + << "'. Accepted levels: public, private, default"); return 1; } } @@ -739,17 +747,17 @@ int CompileCommand::Action(const std::vector<std::string>& args) { // Collect the resources files to compile if (options_.res_dir && options_.res_zip) { - context.GetDiagnostics()->Error(DiagMessage() - << "only one of --dir and --zip can be specified"); + context.GetDiagnostics()->Error(android::DiagMessage() + << "only one of --dir and --zip can be specified"); return 1; } else if ((options_.res_dir || options_.res_zip) && options_.source_path && args.size() > 1) { - context.GetDiagnostics()->Error(DiagMessage(kPath) - << "Cannot use an overriding source path with multiple files."); - return 1; + context.GetDiagnostics()->Error(android::DiagMessage(kPath) + << "Cannot use an overriding source path with multiple files."); + return 1; } else if (options_.res_dir) { if (!args.empty()) { - context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified"); + context.GetDiagnostics()->Error(android::DiagMessage() << "files given but --dir specified"); Usage(&std::cerr); return 1; } @@ -758,12 +766,12 @@ int CompileCommand::Action(const std::vector<std::string>& args) { std::string err; file_collection = io::FileCollection::Create(options_.res_dir.value(), &err); if (!file_collection) { - context.GetDiagnostics()->Error(DiagMessage(options_.res_dir.value()) << err); + context.GetDiagnostics()->Error(android::DiagMessage(options_.res_dir.value()) << err); return 1; } } else if (options_.res_zip) { if (!args.empty()) { - context.GetDiagnostics()->Error(DiagMessage() << "files given but --zip specified"); + context.GetDiagnostics()->Error(android::DiagMessage() << "files given but --zip specified"); Usage(&std::cerr); return 1; } @@ -772,7 +780,7 @@ int CompileCommand::Action(const std::vector<std::string>& args) { std::string err; file_collection = io::ZipFileCollection::Create(options_.res_zip.value(), &err); if (!file_collection) { - context.GetDiagnostics()->Error(DiagMessage(options_.res_zip.value()) << err); + context.GetDiagnostics()->Error(android::DiagMessage(options_.res_zip.value()) << err); return 1; } } else { diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h index bd2e3d72a551..14a730a1b1a0 100644 --- a/tools/aapt2/cmd/Compile.h +++ b/tools/aapt2/cmd/Compile.h @@ -17,15 +17,15 @@ #ifndef AAPT2_COMPILE_H #define AAPT2_COMPILE_H -#include <optional> - #include <androidfw/StringPiece.h> -#include "format/Archive.h" -#include "process/IResourceTableConsumer.h" +#include <optional> + #include "Command.h" -#include "Diagnostics.h" #include "ResourceTable.h" +#include "androidfw/IDiagnostics.h" +#include "format/Archive.h" +#include "process/IResourceTableConsumer.h" namespace aapt { @@ -47,8 +47,8 @@ struct CompileOptions { /** Parses flags and compiles resources to be used in linking. */ class CompileCommand : public Command { public: - explicit CompileCommand(IDiagnostics* diagnostic) : Command("compile", "c"), - diagnostic_(diagnostic) { + explicit CompileCommand(android::IDiagnostics* diagnostic) + : Command("compile", "c"), diagnostic_(diagnostic) { SetDescription("Compiles resources to be linked into an apk."); AddRequiredFlag("-o", "Output path", &options_.output_path, Command::kPath); AddOptionalFlag("--dir", "Directory to scan for resources", &options_.res_dir, Command::kPath); @@ -81,7 +81,7 @@ class CompileCommand : public Command { int Action(const std::vector<std::string>& args) override; private: - IDiagnostics* diagnostic_; + android::IDiagnostics* diagnostic_; CompileOptions options_; std::optional<std::string> visibility_; std::optional<std::string> trace_folder_; diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index fbfbf68b30bb..3464a7662c60 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -219,7 +219,7 @@ static void AssertTranslations(CommandTestFixture *ctf, std::string file_name, ASSERT_NE(table, nullptr); table->string_pool.Sort(); - const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings = + const std::vector<std::unique_ptr<android::StringPool::Entry>>& pool_strings = table->string_pool.strings(); // The actual / expected vectors have the same size @@ -316,7 +316,7 @@ TEST_F(CompilerTest, RelativePathTest) { std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, &diag); ResourceTable* resource_table = apk.get()->GetResourceTable(); - const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings = + const std::vector<std::unique_ptr<android::StringPool::Entry>>& pool_strings = resource_table->string_pool.strings(); ASSERT_EQ(pool_strings.size(), 2); diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp index 3b097e09e09d..7381a85f4339 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -18,11 +18,13 @@ #include <vector> -#include "android-base/macros.h" -#include "androidfw/StringPiece.h" - +#include "Diagnostics.h" #include "LoadedApk.h" #include "ValueVisitor.h" +#include "android-base/file.h" +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "androidfw/StringPiece.h" #include "cmd/Util.h" #include "format/binary/TableFlattener.h" #include "format/binary/XmlFlattener.h" @@ -43,8 +45,9 @@ namespace aapt { class IApkSerializer { public: - IApkSerializer(IAaptContext* context, const Source& source) : context_(context), - source_(source) {} + IApkSerializer(IAaptContext* context, const android::Source& source) + : context_(context), source_(source) { + } virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, IArchiveWriter* writer, uint32_t compression_flags) = 0; @@ -55,21 +58,22 @@ class IApkSerializer { protected: IAaptContext* context_; - Source source_; + android::Source source_; }; class BinaryApkSerializer : public IApkSerializer { public: - BinaryApkSerializer(IAaptContext* context, const Source& source, + BinaryApkSerializer(IAaptContext* context, const android::Source& source, const TableFlattenerOptions& table_flattener_options, const XmlFlattenerOptions& xml_flattener_options) : IApkSerializer(context, source), table_flattener_options_(table_flattener_options), - xml_flattener_options_(xml_flattener_options) {} + xml_flattener_options_(xml_flattener_options) { + } bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, IArchiveWriter* writer, uint32_t compression_flags) override { - BigBuffer buffer(4096); + android::BigBuffer buffer(4096); xml_flattener_options_.use_utf16 = utf16; XmlFlattener flattener(&buffer, xml_flattener_options_); if (!flattener.Consume(context_, xml)) { @@ -81,7 +85,7 @@ class BinaryApkSerializer : public IApkSerializer { } bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override { - BigBuffer buffer(4096); + android::BigBuffer buffer(4096); TableFlattener table_flattener(table_flattener_options_, &buffer); if (!table_flattener.Consume(context_, table)) { return false; @@ -96,7 +100,7 @@ class BinaryApkSerializer : public IApkSerializer { if (file->type == ResourceFile::Type::kProtoXml) { unique_ptr<io::InputStream> in = file->file->OpenInputStream(); if (in == nullptr) { - context_->GetDiagnostics()->Error(DiagMessage(source_) + context_->GetDiagnostics()->Error(android::DiagMessage(source_) << "failed to open file " << *file->path); return false; } @@ -104,7 +108,7 @@ class BinaryApkSerializer : public IApkSerializer { pb::XmlNode pb_node; io::ProtoInputStreamReader proto_reader(in.get()); if (!proto_reader.ReadMessage(&pb_node)) { - context_->GetDiagnostics()->Error(DiagMessage(source_) + context_->GetDiagnostics()->Error(android::DiagMessage(source_) << "failed to parse proto XML " << *file->path); return false; } @@ -112,15 +116,15 @@ class BinaryApkSerializer : public IApkSerializer { std::string error; unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error); if (xml == nullptr) { - context_->GetDiagnostics()->Error(DiagMessage(source_) - << "failed to deserialize proto XML " - << *file->path << ": " << error); + context_->GetDiagnostics()->Error(android::DiagMessage(source_) + << "failed to deserialize proto XML " << *file->path + << ": " << error); return false; } if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer, file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) { - context_->GetDiagnostics()->Error(DiagMessage(source_) + context_->GetDiagnostics()->Error(android::DiagMessage(source_) << "failed to serialize to binary XML: " << *file->path); return false; } @@ -128,7 +132,7 @@ class BinaryApkSerializer : public IApkSerializer { file->type = ResourceFile::Type::kBinaryXml; } else { if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) { - context_->GetDiagnostics()->Error(DiagMessage(source_) + context_->GetDiagnostics()->Error(android::DiagMessage(source_) << "failed to copy file " << *file->path); return false; } @@ -146,8 +150,9 @@ class BinaryApkSerializer : public IApkSerializer { class ProtoApkSerializer : public IApkSerializer { public: - ProtoApkSerializer(IAaptContext* context, const Source& source) - : IApkSerializer(context, source) {} + ProtoApkSerializer(IAaptContext* context, const android::Source& source) + : IApkSerializer(context, source) { + } bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, IArchiveWriter* writer, uint32_t compression_flags) override { @@ -167,7 +172,7 @@ class ProtoApkSerializer : public IApkSerializer { if (file->type == ResourceFile::Type::kBinaryXml) { std::unique_ptr<io::IData> data = file->file->OpenAsData(); if (!data) { - context_->GetDiagnostics()->Error(DiagMessage(source_) + context_->GetDiagnostics()->Error(android::DiagMessage(source_) << "failed to open file " << *file->path); return false; } @@ -175,14 +180,14 @@ class ProtoApkSerializer : public IApkSerializer { std::string error; std::unique_ptr<xml::XmlResource> xml = xml::Inflate(data->data(), data->size(), &error); if (xml == nullptr) { - context_->GetDiagnostics()->Error(DiagMessage(source_) << "failed to parse binary XML: " - << error); + context_->GetDiagnostics()->Error(android::DiagMessage(source_) + << "failed to parse binary XML: " << error); return false; } if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer, file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) { - context_->GetDiagnostics()->Error(DiagMessage(source_) + context_->GetDiagnostics()->Error(android::DiagMessage(source_) << "failed to serialize to proto XML: " << *file->path); return false; } @@ -190,7 +195,7 @@ class ProtoApkSerializer : public IApkSerializer { file->type = ResourceFile::Type::kProtoXml; } else { if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) { - context_->GetDiagnostics()->Error(DiagMessage(source_) + context_->GetDiagnostics()->Error(android::DiagMessage(source_) << "failed to copy file " << *file->path); return false; } @@ -216,7 +221,7 @@ class Context : public IAaptContext { return &symbols_; } - IDiagnostics* GetDiagnostics() override { + android::IDiagnostics* GetDiagnostics() override { return &diag_; } @@ -240,7 +245,7 @@ class Context : public IAaptContext { } int GetMinSdkVersion() override { - return 0u; + return min_sdk_; } const std::set<std::string>& GetSplitNameDependencies() override { @@ -251,6 +256,7 @@ class Context : public IAaptContext { bool verbose_ = false; std::string package_; + int32_t min_sdk_ = 0; private: DISALLOW_COPY_AND_ASSIGN(Context); @@ -270,7 +276,7 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer } else if (output_format == ApkFormat::kProto) { serializer.reset(new ProtoApkSerializer(context, apk->GetSource())); } else { - context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource()) << "Cannot convert APK to unknown format"); return 1; } @@ -279,7 +285,7 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, output_writer, (manifest != nullptr && manifest->WasCompressed()) ? ArchiveEntry::kCompress : 0u)) { - context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource()) << "failed to serialize AndroidManifest.xml"); return 1; } @@ -298,7 +304,7 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer FileReference* file = ValueCast<FileReference>(config_value->value.get()); if (file != nullptr) { if (file->file == nullptr) { - context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource()) << "no file associated with " << *file); return 1; } @@ -306,7 +312,7 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer // Only serialize if we haven't seen this file before if (files_written.insert(*file->path).second) { if (!serializer->SerializeFile(file, output_writer)) { - context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource()) << "failed to serialize file " << *file->path); return 1; } @@ -319,7 +325,7 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer // Converted resource table if (!serializer->SerializeTable(converted_table, output_writer)) { - context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource()) << "failed to serialize the resource table"); return 1; } @@ -340,8 +346,8 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer } if (!io::CopyFileToArchivePreserveCompression(context, file, path, output_writer)) { - context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) - << "failed to copy file " << path); + context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource()) + << "failed to copy file " << path); return 1; } } @@ -349,6 +355,28 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer return 0; } +bool ExtractResourceConfig(const std::string& path, IAaptContext* context, + TableFlattenerOptions& out_options) { + std::string content; + if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) { + context->GetDiagnostics()->Error(android::DiagMessage(path) << "failed reading config file"); + return false; + } + std::unordered_set<ResourceName> resources_exclude_list; + bool result = ParseResourceConfig(content, context, resources_exclude_list, + out_options.name_collapse_exemptions, + out_options.path_shorten_exemptions); + if (!result) { + return false; + } + if (!resources_exclude_list.empty()) { + context->GetDiagnostics()->Error(android::DiagMessage(path) + << "Unsupported '#remove' directive in resource config."); + return false; + } + return true; +} + const char* ConvertCommand::kOutputFormatProto = "proto"; const char* ConvertCommand::kOutputFormatBinary = "binary"; @@ -360,10 +388,10 @@ int ConvertCommand::Action(const std::vector<std::string>& args) { } Context context; - const StringPiece& path = args[0]; + StringPiece path = args[0]; unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics()); if (apk == nullptr) { - context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK"); + context.GetDiagnostics()->Error(android::DiagMessage(path) << "failed to load APK"); return 1; } @@ -373,6 +401,7 @@ int ConvertCommand::Action(const std::vector<std::string>& args) { } context.package_ = app_info.value().package; + context.min_sdk_ = app_info.value().min_sdk_version.value_or(0); unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path_); if (writer == nullptr) { @@ -385,10 +414,22 @@ int ConvertCommand::Action(const std::vector<std::string>& args) { } else if (output_format_.value() == ConvertCommand::kOutputFormatProto) { format = ApkFormat::kProto; } else { - context.GetDiagnostics()->Error(DiagMessage(path) << "Invalid value for flag --output-format: " - << output_format_.value()); + context.GetDiagnostics()->Error(android::DiagMessage(path) + << "Invalid value for flag --output-format: " + << output_format_.value()); return 1; } + if (enable_sparse_encoding_) { + table_flattener_options_.sparse_entries = SparseEntriesMode::Enabled; + } + if (force_sparse_encoding_) { + table_flattener_options_.sparse_entries = SparseEntriesMode::Forced; + } + if (resources_config_path_) { + if (!ExtractResourceConfig(*resources_config_path_, &context, table_flattener_options_)) { + return 1; + } + } return Convert(&context, apk.get(), writer.get(), format, table_flattener_options_, xml_flattener_options_); diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h index 2cdb0c87310f..15fe11fd91cc 100644 --- a/tools/aapt2/cmd/Convert.h +++ b/tools/aapt2/cmd/Convert.h @@ -34,14 +34,41 @@ class ConvertCommand : public Command { AddOptionalFlag("--output-format", android::base::StringPrintf("Format of the output. " "Accepted values are '%s' and '%s'. When not set, defaults to '%s'.", kOutputFormatProto, kOutputFormatBinary, kOutputFormatBinary), &output_format_); - AddOptionalSwitch("--enable-sparse-encoding", + AddOptionalSwitch( + "--enable-sparse-encoding", "Enables encoding sparse entries using a binary search tree.\n" - "This decreases APK size at the cost of resource retrieval performance.", - &table_flattener_options_.use_sparse_entries); + "This decreases APK size at the cost of resource retrieval performance.\n" + "Only applies sparse encoding to Android O+ resources or all resources if minSdk of " + "the APK is O+", + &enable_sparse_encoding_); + AddOptionalSwitch("--force-sparse-encoding", + "Enables encoding sparse entries using a binary search tree.\n" + "This decreases APK size at the cost of resource retrieval performance.\n" + "Applies sparse encoding to all resources regardless of minSdk.", + &force_sparse_encoding_); AddOptionalSwitch("--keep-raw-values", android::base::StringPrintf("Preserve raw attribute values in xml files when using the" " '%s' output format", kOutputFormatBinary), &xml_flattener_options_.keep_raw_values); + AddOptionalFlag("--resources-config-path", + "Path to the resources.cfg file containing the list of resources and \n" + "directives to each resource. \n" + "Format: type/resource_name#[directive][,directive]", + &resources_config_path_); + AddOptionalSwitch( + "--collapse-resource-names", + "Collapses resource names to a single value in the key string pool. Resources can \n" + "be exempted using the \"no_collapse\" directive in a file specified by " + "--resources-config-path.", + &table_flattener_options_.collapse_key_stringpool); + AddOptionalSwitch( + "--deduplicate-entry-values", + "Whether to deduplicate pairs of resource entry and value for simple resources.\n" + "This is recommended to be used together with '--collapse-resource-names' flag or for\n" + "APKs where resource names are manually collapsed. For such APKs this flag allows to\n" + "store the same resource value only once in resource table which decreases APK size.\n" + "Has no effect on APKs where resource names are kept.", + &table_flattener_options_.deduplicate_entry_values); AddOptionalSwitch("-v", "Enables verbose logging", &verbose_); } @@ -56,6 +83,9 @@ class ConvertCommand : public Command { std::string output_path_; std::optional<std::string> output_format_; bool verbose_ = false; + bool enable_sparse_encoding_ = false; + bool force_sparse_encoding_ = false; + std::optional<std::string> resources_config_path_; }; int Convert(IAaptContext* context, LoadedApk* input, IArchiveWriter* output_writer, diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp index f35237ec25e3..2c9388b7d711 100644 --- a/tools/aapt2/cmd/Convert_test.cpp +++ b/tools/aapt2/cmd/Convert_test.cpp @@ -17,13 +17,18 @@ #include "Convert.h" #include "LoadedApk.h" +#include "test/Common.h" #include "test/Test.h" #include "ziparchive/zip_archive.h" +using testing::AnyOfArray; using testing::Eq; using testing::Ne; +using testing::Not; +using testing::SizeIs; namespace aapt { +using namespace aapt::test; using ConvertTest = CommandTestFixture; @@ -101,7 +106,8 @@ TEST_F(ConvertTest, KeepRawXmlStrings) { // Check that the raw string index has been set to the correct string pool entry int32_t raw_index = tree.getAttributeValueStringID(0); ASSERT_THAT(raw_index, Ne(-1)); - EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007")); + EXPECT_THAT(android::util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), + Eq("007")); } TEST_F(ConvertTest, DuplicateEntriesWrittenOnce) { @@ -144,4 +150,76 @@ TEST_F(ConvertTest, DuplicateEntriesWrittenOnce) { EXPECT_THAT(count, Eq(1)); } +TEST_F(ConvertTest, ConvertWithResourceNameCollapsing) { + StdErrDiagnostics diag; + const std::string compiled_files_dir = GetTestPath("compiled"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), + R"(<resources> + <string name="first">string</string> + <string name="second">string</string> + <string name="third">another string</string> + + <bool name="bool1">true</bool> + <bool name="bool2">true</bool> + <bool name="bool3">true</bool> + + <integer name="int1">10</integer> + <integer name="int2">10</integer> + </resources>)", + compiled_files_dir, &diag)); + std::string resource_config_path = GetTestPath("resource-config"); + WriteFile(resource_config_path, "integer/int1#no_collapse\ninteger/int2#no_collapse"); + + const std::string proto_apk = GetTestPath("proto.apk"); + std::vector<std::string> link_args = { + "--proto-format", "--manifest", GetDefaultManifest(kDefaultPackageName), "-o", proto_apk, + }; + ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag)); + + const std::string binary_apk = GetTestPath("binary.apk"); + std::vector<android::StringPiece> convert_args = {"-o", + binary_apk, + "--output-format", + "binary", + "--collapse-resource-names", + "--deduplicate-entry-values", + "--resources-config-path", + resource_config_path, + proto_apk}; + ASSERT_THAT(ConvertCommand().Execute(convert_args, &std::cerr), Eq(0)); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(binary_apk, &diag); + for (const auto& package : apk->GetResourceTable()->packages) { + for (const auto& type : package->types) { + switch (type->named_type.type) { + case ResourceType::kBool: + EXPECT_THAT(type->entries, SizeIs(3)); + for (const auto& entry : type->entries) { + auto value = ValueCast<BinaryPrimitive>(entry->FindValue({})->value.get())->value; + EXPECT_THAT(value.data, Eq(0xffffffffu)); + } + break; + case ResourceType::kString: + EXPECT_THAT(type->entries, SizeIs(3)); + for (const auto& entry : type->entries) { + auto value = ValueCast<String>(entry->FindValue({})->value.get())->value; + EXPECT_THAT(entry->name, Not(AnyOfArray({"first", "second", "third"}))); + EXPECT_THAT(*value, AnyOfArray({"string", "another string"})); + } + break; + case ResourceType::kInteger: + EXPECT_THAT(type->entries, SizeIs(2)); + for (const auto& entry : type->entries) { + auto value = ValueCast<BinaryPrimitive>(entry->FindValue({})->value.get())->value; + EXPECT_THAT(entry->name, AnyOfArray({"int1", "int2"})); + EXPECT_THAT(value.data, Eq(10)); + } + break; + default: + break; + } + } + } +} + } // namespace aapt diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp index d9e8c921dbc5..5bfc73233bfe 100644 --- a/tools/aapt2/cmd/Diff.cpp +++ b/tools/aapt2/cmd/Diff.cpp @@ -16,10 +16,10 @@ #include "Diff.h" -#include "android-base/macros.h" - +#include "Diagnostics.h" #include "LoadedApk.h" #include "ValueVisitor.h" +#include "android-base/macros.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" @@ -45,7 +45,7 @@ class DiffContext : public IAaptContext { return 0x0; } - IDiagnostics* GetDiagnostics() override { + android::IDiagnostics* GetDiagnostics() override { return &diagnostics_; } @@ -78,7 +78,7 @@ class DiffContext : public IAaptContext { SymbolTable symbol_table_; }; -static void EmitDiffLine(const Source& source, const StringPiece& message) { +static void EmitDiffLine(const android::Source& source, StringPiece message) { std::cerr << source << ": " << message << "\n"; } @@ -105,7 +105,7 @@ static bool EmitResourceConfigValueDiff( Value* value_b = config_value_b->value.get(); if (!value_a->Equals(value_b)) { std::stringstream str_stream; - str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name + str_stream << "value " << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name << " config=" << config_value_a->config << " does not match:\n"; value_a->Print(&str_stream); str_stream << "\n vs \n"; @@ -128,7 +128,7 @@ static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a, auto config_value_b = entry_b.FindValue(config_value_a->config); if (!config_value_b) { std::stringstream str_stream; - str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name + str_stream << "missing " << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name << " config=" << config_value_a->config; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; @@ -143,7 +143,7 @@ static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a, auto config_value_a = entry_a.FindValue(config_value_b->config); if (!config_value_a) { std::stringstream str_stream; - str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b.name + str_stream << "new config " << pkg_b.name << ":" << type_b.named_type << "/" << entry_b.name << " config=" << config_value_b->config; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; @@ -164,13 +164,15 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a, if (entry_b_iter == type_b.entries.end()) { // Type A contains a type that type B does not have. std::stringstream str_stream; - str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a_iter->name; + str_stream << "missing " << pkg_a.name << ":" << type_a.named_type << "/" + << entry_a_iter->name; EmitDiffLine(apk_a->GetSource(), str_stream.str()); diff = true; } else if (entry_a_iter == type_a.entries.end()) { // Type B contains a type that type A does not have. std::stringstream str_stream; - str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/" << entry_b_iter->name; + str_stream << "new entry " << pkg_b.name << ":" << type_b.named_type << "/" + << entry_b_iter->name; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; } else { @@ -178,7 +180,7 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a, const auto& entry_b = *entry_b_iter; if (IsSymbolVisibilityDifferent(entry_a.visibility, entry_b.visibility)) { std::stringstream str_stream; - str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name + str_stream << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name << " has different visibility ("; if (entry_b.visibility.staged_api) { str_stream << "STAGED "; @@ -203,7 +205,7 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a, } else if (IsIdDiff(entry_a.visibility.level, entry_a.id, entry_b.visibility.level, entry_b.id)) { std::stringstream str_stream; - str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name + str_stream << pkg_a.name << ":" << type_a.named_type << "/" << entry_a.name << " has different public ID ("; if (entry_b.id) { str_stream << "0x" << std::hex << entry_b.id.value(); @@ -243,13 +245,13 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a, if (type_b_iter == pkg_b.types.end()) { // Type A contains a type that type B does not have. std::stringstream str_stream; - str_stream << "missing " << pkg_a.name << ":" << type_a_iter->type; + str_stream << "missing " << pkg_a.name << ":" << type_a_iter->named_type; EmitDiffLine(apk_a->GetSource(), str_stream.str()); diff = true; } else if (type_a_iter == pkg_a.types.end()) { // Type B contains a type that type A does not have. std::stringstream str_stream; - str_stream << "new type " << pkg_b.name << ":" << type_b_iter->type; + str_stream << "new type " << pkg_b.name << ":" << type_b_iter->named_type; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; } else { @@ -257,7 +259,7 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a, const auto& type_b = *type_b_iter; if (type_a.visibility_level != type_b.visibility_level) { std::stringstream str_stream; - str_stream << pkg_a.name << ":" << type_a.type << " has different visibility ("; + str_stream << pkg_a.name << ":" << type_a.named_type << " has different visibility ("; if (type_b.visibility_level == Visibility::Level::kPublic) { str_stream << "PUBLIC"; } else { @@ -274,7 +276,7 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a, diff = true; } else if (IsIdDiff(type_a.visibility_level, type_a.id, type_b.visibility_level, type_b.id)) { std::stringstream str_stream; - str_stream << pkg_a.name << ":" << type_a.type << " has different public ID ("; + str_stream << pkg_a.name << ":" << type_a.named_type << " has different public ID ("; if (type_b.id) { str_stream << "0x" << std::hex << type_b.id.value(); } else { @@ -383,7 +385,7 @@ int DiffCommand::Action(const std::vector<std::string>& args) { return 1; } - IDiagnostics* diag = context.GetDiagnostics(); + android::IDiagnostics* diag = context.GetDiagnostics(); std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(args[0], diag); std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(args[1], diag); if (!apk_a || !apk_b) { diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp index bcce3e5a800f..71b08022f688 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -57,8 +57,8 @@ static const char* ResourceFileTypeToString(const ResourceFile::Type& type) { return "UNKNOWN"; } -static void DumpCompiledFile(const ResourceFile& file, const Source& source, off64_t offset, - size_t len, Printer* printer) { +static void DumpCompiledFile(const ResourceFile& file, const android::Source& source, + off64_t offset, size_t len, Printer* printer) { printer->Print("Resource: "); printer->Println(file.name.to_string()); @@ -83,7 +83,7 @@ class DumpContext : public IAaptContext { return PackageType::kApp; } - IDiagnostics* GetDiagnostics() override { + android::IDiagnostics* GetDiagnostics() override { return &diagnostics_; } @@ -138,7 +138,7 @@ int DumpAPCCommand::Action(const std::vector<std::string>& args) { print_options.show_values = !no_values_; if (args.size() < 1) { - diag_->Error(DiagMessage() << "No dump container specified"); + diag_->Error(android::DiagMessage() << "No dump container specified"); return 1; } @@ -146,7 +146,7 @@ int DumpAPCCommand::Action(const std::vector<std::string>& args) { for (auto container : args) { io::FileInputStream input(container); if (input.HadError()) { - context.GetDiagnostics()->Error(DiagMessage(container) + context.GetDiagnostics()->Error(android::DiagMessage(container) << "failed to open file: " << input.GetError()); error = true; continue; @@ -155,7 +155,7 @@ int DumpAPCCommand::Action(const std::vector<std::string>& args) { // Try as a compiled file. ContainerReader reader(&input); if (reader.HadError()) { - context.GetDiagnostics()->Error(DiagMessage(container) + context.GetDiagnostics()->Error(android::DiagMessage(container) << "failed to read container: " << reader.GetError()); error = true; continue; @@ -170,7 +170,7 @@ int DumpAPCCommand::Action(const std::vector<std::string>& args) { pb::ResourceTable pb_table; if (!entry->GetResTable(&pb_table)) { - context.GetDiagnostics()->Error(DiagMessage(container) + context.GetDiagnostics()->Error(android::DiagMessage(container) << "failed to parse proto table: " << entry->GetError()); error = true; continue; @@ -179,7 +179,7 @@ int DumpAPCCommand::Action(const std::vector<std::string>& args) { ResourceTable table; error.clear(); if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &error)) { - context.GetDiagnostics()->Error(DiagMessage(container) + context.GetDiagnostics()->Error(android::DiagMessage(container) << "failed to parse table: " << error); error = true; continue; @@ -194,7 +194,7 @@ int DumpAPCCommand::Action(const std::vector<std::string>& args) { off64_t offset; size_t length; if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &length)) { - context.GetDiagnostics()->Error(DiagMessage(container) + context.GetDiagnostics()->Error(android::DiagMessage(container) << "failed to parse compiled proto file: " << entry->GetError()); error = true; @@ -203,14 +203,14 @@ int DumpAPCCommand::Action(const std::vector<std::string>& args) { ResourceFile file; if (!DeserializeCompiledFileFromPb(pb_compiled_file, &file, &error)) { - context.GetDiagnostics()->Warn(DiagMessage(container) + context.GetDiagnostics()->Warn(android::DiagMessage(container) << "failed to parse compiled file: " << error); error = true; continue; } printer_->Indent(); - DumpCompiledFile(file, Source(container), offset, length, printer_); + DumpCompiledFile(file, android::Source(container), offset, length, printer_); printer_->Undent(); } } @@ -228,7 +228,7 @@ int DumpBadgerCommand::Action(const std::vector<std::string>& args) { int DumpConfigsCommand::Dump(LoadedApk* apk) { ResourceTable* table = apk->GetResourceTable(); if (!table) { - GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table"); + GetDiagnostics()->Error(android::DiagMessage() << "Failed to retrieve resource table"); return 1; } @@ -268,13 +268,13 @@ int DumpPackageNameCommand::Dump(LoadedApk* apk) { int DumpStringsCommand::Dump(LoadedApk* apk) { ResourceTable* table = apk->GetResourceTable(); if (!table) { - GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table"); + GetDiagnostics()->Error(android::DiagMessage() << "Failed to retrieve resource table"); return 1; } // Load the run-time xml string pool using the flattened data - BigBuffer buffer(4096); - StringPool::FlattenUtf8(&buffer, table->string_pool, GetDiagnostics()); + android::BigBuffer buffer(4096); + android::StringPool::FlattenUtf8(&buffer, table->string_pool, GetDiagnostics()); auto data = buffer.to_string(); android::ResStringPool pool(data.data(), data.size(), false); Debug::DumpResStringPool(&pool, GetPrinter()); @@ -291,14 +291,14 @@ int DumpStyleParentCommand::Dump(LoadedApk* apk) { const auto table = apk->GetResourceTable(); if (!table) { - GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table"); + GetDiagnostics()->Error(android::DiagMessage() << "Failed to retrieve resource table"); return 1; } std::optional<ResourceTable::SearchResult> target = table->FindResource(target_style); if (!target) { - GetDiagnostics()->Error( - DiagMessage() << "Target style \"" << target_style.entry << "\" does not exist"); + GetDiagnostics()->Error(android::DiagMessage() + << "Target style \"" << target_style.entry << "\" does not exist"); return 1; } @@ -315,7 +315,7 @@ int DumpTableCommand::Dump(LoadedApk* apk) { ResourceTable* table = apk->GetResourceTable(); if (!table) { - GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table"); + GetDiagnostics()->Error(android::DiagMessage() << "Failed to retrieve resource table"); return 1; } @@ -340,7 +340,7 @@ int DumpXmlStringsCommand::Dump(LoadedApk* apk) { } // Flatten the xml document to get a binary representation of the proto xml file - BigBuffer buffer(4096); + android::BigBuffer buffer(4096); XmlFlattenerOptions options = {}; options.keep_raw_values = true; XmlFlattener flattener(&buffer, options); @@ -356,7 +356,7 @@ int DumpXmlStringsCommand::Dump(LoadedApk* apk) { } else if (apk->GetApkFormat() == ApkFormat::kBinary) { io::IFile* file = apk->GetFileCollection()->FindFile(xml_file); if (!file) { - GetDiagnostics()->Error(DiagMessage(xml_file) + GetDiagnostics()->Error(android::DiagMessage(xml_file) << "File '" << xml_file << "' not found in APK"); error = true; continue; @@ -364,7 +364,7 @@ int DumpXmlStringsCommand::Dump(LoadedApk* apk) { std::unique_ptr<io::IData> data = file->OpenAsData(); if (!data) { - GetDiagnostics()->Error(DiagMessage() << "Failed to open " << xml_file); + GetDiagnostics()->Error(android::DiagMessage() << "Failed to open " << xml_file); error = true; continue; } @@ -372,7 +372,7 @@ int DumpXmlStringsCommand::Dump(LoadedApk* apk) { // Load the run-time xml tree from the file data tree.setTo(data->data(), data->size(), /** copyData */ true); } else { - GetDiagnostics()->Error(DiagMessage(apk->GetSource()) << "Unknown APK format"); + GetDiagnostics()->Error(android::DiagMessage(apk->GetSource()) << "Unknown APK format"); error = true; continue; } @@ -396,7 +396,7 @@ int DumpXmlTreeCommand::Dump(LoadedApk* apk) { int DumpOverlayableCommand::Dump(LoadedApk* apk) { ResourceTable* table = apk->GetResourceTable(); if (!table) { - GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table"); + GetDiagnostics()->Error(android::DiagMessage() << "Failed to retrieve resource table"); return 1; } @@ -563,13 +563,13 @@ const char DumpBadgerCommand::kBadgerData[2925] = { int DumpChunks::Dump(LoadedApk* apk) { auto file = apk->GetFileCollection()->FindFile("resources.arsc"); if (!file) { - GetDiagnostics()->Error(DiagMessage() << "Failed to find resources.arsc in APK"); + GetDiagnostics()->Error(android::DiagMessage() << "Failed to find resources.arsc in APK"); return 1; } auto data = file->OpenAsData(); if (!data) { - GetDiagnostics()->Error(DiagMessage() << "Failed to open resources.arsc "); + GetDiagnostics()->Error(android::DiagMessage() << "Failed to open resources.arsc "); return 1; } diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h index ec320ecd2a32..76d33d7aa3b2 100644 --- a/tools/aapt2/cmd/Dump.h +++ b/tools/aapt2/cmd/Dump.h @@ -33,29 +33,30 @@ namespace aapt { **/ class DumpApkCommand : public Command { public: - explicit DumpApkCommand(const std::string&& name, text::Printer* printer, IDiagnostics* diag) + explicit DumpApkCommand(const std::string&& name, text::Printer* printer, + android::IDiagnostics* diag) : Command(name), printer_(printer), diag_(diag) { - SetDescription("Dump information about an APK or APC."); + SetDescription("Dump information about an APK or APC."); } text::Printer* GetPrinter() { return printer_; } - IDiagnostics* GetDiagnostics() { + android::IDiagnostics* GetDiagnostics() { return diag_; } std::optional<std::string> GetPackageName(LoadedApk* apk) { xml::Element* manifest_el = apk->GetManifest()->root.get(); if (!manifest_el) { - GetDiagnostics()->Error(DiagMessage() << "No AndroidManifest."); + GetDiagnostics()->Error(android::DiagMessage() << "No AndroidManifest."); return {}; } xml::Attribute* attr = manifest_el->FindAttribute({}, "package"); if (!attr) { - GetDiagnostics()->Error(DiagMessage() << "No package name."); + GetDiagnostics()->Error(android::DiagMessage() << "No package name."); return {}; } return attr->value; @@ -66,7 +67,7 @@ class DumpApkCommand : public Command { int Action(const std::vector<std::string>& args) final { if (args.size() < 1) { - diag_->Error(DiagMessage() << "No dump apk specified."); + diag_->Error(android::DiagMessage() << "No dump apk specified."); return 1; } @@ -86,13 +87,13 @@ class DumpApkCommand : public Command { private: text::Printer* printer_; - IDiagnostics* diag_; + android::IDiagnostics* diag_; }; /** Command that prints contents of files generated from the compilation stage. */ class DumpAPCCommand : public Command { public: - explicit DumpAPCCommand(text::Printer* printer, IDiagnostics* diag) + explicit DumpAPCCommand(text::Printer* printer, android::IDiagnostics* diag) : Command("apc"), printer_(printer), diag_(diag) { SetDescription("Print the contents of the AAPT2 Container (APC) generated fom compilation."); AddOptionalSwitch("--no-values", "Suppresses output of values when displaying resource tables.", @@ -104,7 +105,7 @@ class DumpAPCCommand : public Command { private: text::Printer* printer_; - IDiagnostics* diag_; + android::IDiagnostics* diag_; bool no_values_ = false; bool verbose_ = false; }; @@ -124,13 +125,21 @@ class DumpBadgerCommand : public Command { class DumpBadgingCommand : public DumpApkCommand { public: - explicit DumpBadgingCommand(text::Printer* printer, IDiagnostics* diag) + explicit DumpBadgingCommand(text::Printer* printer, android::IDiagnostics* diag) : DumpApkCommand("badging", printer, diag) { SetDescription("Print information extracted from the manifest of the APK."); AddOptionalSwitch("--include-meta-data", "Include meta-data information.", &options_.include_meta_data); } + void SetIncludeMetaData(bool value) { + options_.include_meta_data = value; + } + + void SetOnlyPermissions(bool value) { + options_.only_permissions = value; + } + int Dump(LoadedApk* apk) override { return DumpManifest(apk, options_, GetPrinter(), GetDiagnostics()); } @@ -141,7 +150,7 @@ class DumpBadgingCommand : public DumpApkCommand { class DumpConfigsCommand : public DumpApkCommand { public: - explicit DumpConfigsCommand(text::Printer* printer, IDiagnostics* diag) + explicit DumpConfigsCommand(text::Printer* printer, android::IDiagnostics* diag) : DumpApkCommand("configurations", printer, diag) { SetDescription("Print every configuration used by a resource in the APK."); } @@ -151,7 +160,7 @@ class DumpConfigsCommand : public DumpApkCommand { class DumpPackageNameCommand : public DumpApkCommand { public: - explicit DumpPackageNameCommand(text::Printer* printer, IDiagnostics* diag) + explicit DumpPackageNameCommand(text::Printer* printer, android::IDiagnostics* diag) : DumpApkCommand("packagename", printer, diag) { SetDescription("Print the package name of the APK."); } @@ -161,7 +170,7 @@ class DumpPackageNameCommand : public DumpApkCommand { class DumpPermissionsCommand : public DumpApkCommand { public: - explicit DumpPermissionsCommand(text::Printer* printer, IDiagnostics* diag) + explicit DumpPermissionsCommand(text::Printer* printer, android::IDiagnostics* diag) : DumpApkCommand("permissions", printer, diag) { SetDescription("Print the permissions extracted from the manifest of the APK."); } @@ -175,7 +184,7 @@ class DumpPermissionsCommand : public DumpApkCommand { class DumpStringsCommand : public DumpApkCommand { public: - explicit DumpStringsCommand(text::Printer* printer, IDiagnostics* diag) + explicit DumpStringsCommand(text::Printer* printer, android::IDiagnostics* diag) : DumpApkCommand("strings", printer, diag) { SetDescription("Print the contents of the resource table string pool in the APK."); } @@ -186,7 +195,7 @@ class DumpStringsCommand : public DumpApkCommand { /** Prints the graph of parents of a style in an APK. */ class DumpStyleParentCommand : public DumpApkCommand { public: - explicit DumpStyleParentCommand(text::Printer* printer, IDiagnostics* diag) + explicit DumpStyleParentCommand(text::Printer* printer, android::IDiagnostics* diag) : DumpApkCommand("styleparents", printer, diag) { SetDescription("Print the parents of a style in an APK."); AddRequiredFlag("--style", "The name of the style to print", &style_); @@ -200,7 +209,7 @@ class DumpStyleParentCommand : public DumpApkCommand { class DumpTableCommand : public DumpApkCommand { public: - explicit DumpTableCommand(text::Printer* printer, IDiagnostics* diag) + explicit DumpTableCommand(text::Printer* printer, android::IDiagnostics* diag) : DumpApkCommand("resources", printer, diag) { SetDescription("Print the contents of the resource table from the APK."); AddOptionalSwitch("--no-values", "Suppresses output of values when displaying resource tables.", @@ -217,7 +226,7 @@ class DumpTableCommand : public DumpApkCommand { class DumpXmlStringsCommand : public DumpApkCommand { public: - explicit DumpXmlStringsCommand(text::Printer* printer, IDiagnostics* diag) + explicit DumpXmlStringsCommand(text::Printer* printer, android::IDiagnostics* diag) : DumpApkCommand("xmlstrings", printer, diag) { SetDescription("Print the string pool of a compiled xml in an APK."); AddRequiredFlagList("--file", "A compiled xml file to print", &files_); @@ -231,7 +240,8 @@ class DumpXmlStringsCommand : public DumpApkCommand { class DumpChunks : public DumpApkCommand { public: - DumpChunks(text::Printer* printer, IDiagnostics* diag) : DumpApkCommand("chunks", printer, diag) { + DumpChunks(text::Printer* printer, android::IDiagnostics* diag) + : DumpApkCommand("chunks", printer, diag) { SetDescription("Print the chunk information of the compiled resources.arsc in the APK."); } @@ -241,7 +251,7 @@ class DumpChunks : public DumpApkCommand { /** Prints the tree of a compiled xml in an APK. */ class DumpXmlTreeCommand : public DumpApkCommand { public: - explicit DumpXmlTreeCommand(text::Printer* printer, IDiagnostics* diag) + explicit DumpXmlTreeCommand(text::Printer* printer, android::IDiagnostics* diag) : DumpApkCommand("xmltree", printer, diag) { SetDescription("Print the tree of a compiled xml in an APK."); AddRequiredFlagList("--file", "A compiled xml file to print", &files_); @@ -255,7 +265,7 @@ class DumpXmlTreeCommand : public DumpApkCommand { class DumpOverlayableCommand : public DumpApkCommand { public: - explicit DumpOverlayableCommand(text::Printer* printer, IDiagnostics* diag) + explicit DumpOverlayableCommand(text::Printer* printer, android::IDiagnostics* diag) : DumpApkCommand("overlayable", printer, diag) { SetDescription("Print the <overlayable> resources of an APK."); } @@ -266,7 +276,7 @@ class DumpOverlayableCommand : public DumpApkCommand { /** The default dump command. Performs no action because a subcommand is required. */ class DumpCommand : public Command { public: - explicit DumpCommand(text::Printer* printer, IDiagnostics* diag) + explicit DumpCommand(text::Printer* printer, android::IDiagnostics* diag) : Command("dump", "d"), diag_(diag) { AddOptionalSubcommand(util::make_unique<DumpAPCCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpBadgingCommand>(printer, diag_)); @@ -285,16 +295,16 @@ class DumpCommand : public Command { int Action(const std::vector<std::string>& args) override { if (args.size() == 0) { - diag_->Error(DiagMessage() << "no subcommand specified"); + diag_->Error(android::DiagMessage() << "no subcommand specified"); } else { - diag_->Error(DiagMessage() << "unknown subcommand '" << args[0] << "'"); + diag_->Error(android::DiagMessage() << "unknown subcommand '" << args[0] << "'"); } Usage(&std::cerr); return 1; } private: - IDiagnostics* diag_; + android::IDiagnostics* diag_; }; } // namespace aapt diff --git a/tools/aapt2/cmd/Dump_test.cpp b/tools/aapt2/cmd/Dump_test.cpp new file mode 100644 index 000000000000..df35ebbc064d --- /dev/null +++ b/tools/aapt2/cmd/Dump_test.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2022 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 "Dump.h" + +#include "LoadedApk.h" +#include "io/StringStream.h" +#include "test/Test.h" +#include "text/Printer.h" + +using ::aapt::io::StringOutputStream; +using ::aapt::text::Printer; +using testing::Eq; +using testing::Ne; + +namespace aapt { + +using DumpTest = CommandTestFixture; + +static android::NoOpDiagnostics noop_diag; + +void DumpBadgingToString(LoadedApk* loaded_apk, std::string* output, bool include_meta_data = false, + bool only_permissions = false) { + StringOutputStream output_stream(output); + Printer printer(&output_stream); + + DumpBadgingCommand command(&printer, &noop_diag); + command.SetIncludeMetaData(include_meta_data); + command.SetOnlyPermissions(only_permissions); + ASSERT_EQ(command.Dump(loaded_apk), 0); + output_stream.Flush(); +} + +TEST_F(DumpTest, DumpBadging) { + auto apk_path = file::BuildPath( + {android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", "minimal.apk"}); + auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag); + + std::string output; + DumpBadgingToString(loaded_apk.get(), &output); + + std::string expected; + auto expected_path = file::BuildPath({android::base::GetExecutableDirectory(), + "integration-tests", "DumpTest", "minimal_expected.txt"}); + ::android::base::ReadFileToString(expected_path, &expected); + ASSERT_EQ(output, expected); +} + +TEST_F(DumpTest, DumpBadgingMultipleUsesSdkTakesLatest) { + auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", + "DumpTest", "multiple_uses_sdk.apk"}); + auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag); + + std::string output; + DumpBadgingToString(loaded_apk.get(), &output); + + std::string expected; + auto expected_path = + file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", + "multiple_uses_sdk_expected.txt"}); + ::android::base::ReadFileToString(expected_path, &expected); + ASSERT_EQ(output, expected); +} + +TEST_F(DumpTest, DumpBadgingAllComponents) { + auto apk_path = file::BuildPath( + {android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", "components.apk"}); + auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag); + + std::string output; + DumpBadgingToString(loaded_apk.get(), &output, /* include_meta_data= */ true); + + std::string expected; + auto expected_path = + file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", + "components_expected.txt"}); + ::android::base::ReadFileToString(expected_path, &expected); + ASSERT_EQ(output, expected); +} + +TEST_F(DumpTest, DumpBadgingPermissionsOnly) { + auto apk_path = file::BuildPath( + {android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", "components.apk"}); + auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag); + + std::string output; + DumpBadgingToString(loaded_apk.get(), &output, /* include_meta_data= */ false, + /* only_permissions= */ true); + + std::string expected; + auto expected_path = + file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", + "components_permissions_expected.txt"}); + ::android::base::ReadFileToString(expected_path, &expected); + ASSERT_EQ(output, expected); +} + +TEST_F(DumpTest, DumpBadgingApkBuiltWithAaptAndTagsInWrongPlace) { + auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", + "DumpTest", "built_with_aapt.apk"}); + auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag); + + std::string output; + DumpBadgingToString(loaded_apk.get(), &output, /* include_meta_data= */ false, + /* only_permissions= */ false); + + std::string expected; + auto expected_path = + file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", + "built_with_aapt_expected.txt"}); + ::android::base::ReadFileToString(expected_path, &expected); + ASSERT_EQ(output, expected); +} + +} // namespace aapt diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 790f2b34c58b..97404fc69af2 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -17,20 +17,13 @@ #include "Link.h" #include <sys/stat.h> -#include <cinttypes> #include <algorithm> +#include <cinttypes> #include <queue> #include <unordered_map> #include <vector> -#include "android-base/errors.h" -#include "android-base/expected.h" -#include "android-base/file.h" -#include "android-base/stringprintf.h" -#include "androidfw/Locale.h" -#include "androidfw/StringPiece.h" - #include "AppInfo.h" #include "Debug.h" #include "LoadedApk.h" @@ -38,6 +31,13 @@ #include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" +#include "android-base/errors.h" +#include "android-base/expected.h" +#include "android-base/file.h" +#include "android-base/stringprintf.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/Locale.h" +#include "androidfw/StringPiece.h" #include "cmd/Util.h" #include "compile/IdAssigner.h" #include "compile/XmlIdCollector.h" @@ -98,7 +98,7 @@ constexpr uint8_t kAndroidPackageId = 0x01; class LinkContext : public IAaptContext { public: - explicit LinkContext(IDiagnostics* diagnostics) + explicit LinkContext(android::IDiagnostics* diagnostics) : diagnostics_(diagnostics), name_mangler_({}), symbols_(&name_mangler_) { } @@ -110,7 +110,7 @@ class LinkContext : public IAaptContext { package_type_ = type; } - IDiagnostics* GetDiagnostics() override { + android::IDiagnostics* GetDiagnostics() override { return diagnostics_; } @@ -126,8 +126,8 @@ class LinkContext : public IAaptContext { return compilation_package_; } - void SetCompilationPackage(const StringPiece& package_name) { - compilation_package_ = package_name.to_string(); + void SetCompilationPackage(StringPiece package_name) { + compilation_package_ = std::string(package_name); } uint8_t GetPackageId() override { @@ -170,7 +170,7 @@ class LinkContext : public IAaptContext { DISALLOW_COPY_AND_ASSIGN(LinkContext); PackageType package_type_ = PackageType::kApp; - IDiagnostics* diagnostics_; + android::IDiagnostics* diagnostics_; NameMangler name_mangler_; std::string compilation_package_; uint8_t package_id_ = 0x0; @@ -216,14 +216,16 @@ class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate { // Check that this doesn't overlap another resource. if (DefaultSymbolTableDelegate::FindById(rewritten_id, sources) != nullptr) { // The ID overlaps, so log a message (since this is a weird failure) and fail. - context_->GetDiagnostics()->Error(DiagMessage() << "Failed to rewrite " << name - << " for pre-O feature split support"); + context_->GetDiagnostics()->Error(android::DiagMessage() + << "Failed to rewrite " << name + << " for pre-O feature split support"); return {}; } if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() << "rewriting " << name << " (" << *id - << ") -> (" << rewritten_id << ")"); + context_->GetDiagnostics()->Note(android::DiagMessage() + << "rewriting " << name << " (" << *id << ") -> (" + << rewritten_id << ")"); } *id = rewritten_id; @@ -238,19 +240,19 @@ class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate { IAaptContext* context_; }; -static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, - const StringPiece& path, bool keep_raw_values, bool utf16, - OutputFormat format, IArchiveWriter* writer) { +static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, StringPiece path, + bool keep_raw_values, bool utf16, OutputFormat format, + IArchiveWriter* writer) { TRACE_CALL(); if (context->IsVerbose()) { - context->GetDiagnostics()->Note(DiagMessage(path) << "writing to archive (keep_raw_values=" - << (keep_raw_values ? "true" : "false") - << ")"); + context->GetDiagnostics()->Note(android::DiagMessage(path) + << "writing to archive (keep_raw_values=" + << (keep_raw_values ? "true" : "false") << ")"); } switch (format) { case OutputFormat::kApk: { - BigBuffer buffer(1024); + android::BigBuffer buffer(1024); XmlFlattenerOptions options = {}; options.keep_raw_values = keep_raw_values; options.use_utf16 = utf16; @@ -260,8 +262,8 @@ static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, } io::BigBufferInputStream input_stream(&buffer); - return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(), - ArchiveEntry::kCompress, writer); + return io::CopyInputStreamToArchive(context, &input_stream, path, ArchiveEntry::kCompress, + writer); } break; case OutputFormat::kProto: { @@ -270,22 +272,22 @@ static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, SerializeXmlOptions options; options.remove_empty_text_nodes = (path == kAndroidManifestPath); SerializeXmlResourceToPb(xml_res, &pb_node); - return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress, - writer); + return io::CopyProtoToArchive(context, &pb_node, path, ArchiveEntry::kCompress, writer); } break; } return false; } // Inflates an XML file from the source path. -static std::unique_ptr<xml::XmlResource> LoadXml(const std::string& path, IDiagnostics* diag) { +static std::unique_ptr<xml::XmlResource> LoadXml(const std::string& path, + android::IDiagnostics* diag) { TRACE_CALL(); FileInputStream fin(path); if (fin.HadError()) { - diag->Error(DiagMessage(path) << "failed to load XML file: " << fin.GetError()); + diag->Error(android::DiagMessage(path) << "failed to load XML file: " << fin.GetError()); return {}; } - return xml::Inflate(&fin, diag, Source(path)); + return xml::Inflate(&fin, diag, android::Source(path)); } struct ResourceFileFlattenerOptions { @@ -326,13 +328,13 @@ struct R { }; template <typename T> -uint32_t GetCompressionFlags(const StringPiece& str, T options) { +uint32_t GetCompressionFlags(StringPiece str, T options) { if (options.do_not_compress_anything) { return 0; } - if (options.regex_to_not_compress - && std::regex_search(str.to_string(), options.regex_to_not_compress.value())) { + if (options.regex_to_not_compress && + std::regex_search(str.begin(), str.end(), options.regex_to_not_compress.value())) { return 0; } @@ -451,10 +453,10 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer ResourceTable* table, FileOperation* file_op) { TRACE_CALL(); xml::XmlResource* doc = file_op->xml_to_flatten.get(); - const Source& src = doc->file.source; + const android::Source& src = doc->file.source; if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() + context_->GetDiagnostics()->Note(android::DiagMessage() << "linking " << src.path << " (" << doc->file.name << ")"); } @@ -545,7 +547,7 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv io::IFile* file = file_ref->file; if (!file) { - context_->GetDiagnostics()->Error(DiagMessage(file_ref->GetSource()) + context_->GetDiagnostics()->Error(android::DiagMessage(file_ref->GetSource()) << "file not found"); return false; } @@ -556,12 +558,12 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv file_op.config = config_value->config; file_op.file_to_copy = file; - if (type->type != ResourceType::kRaw && + if (type->named_type.type != ResourceType::kRaw && (file_ref->type == ResourceFile::Type::kBinaryXml || file_ref->type == ResourceFile::Type::kProtoXml)) { std::unique_ptr<io::IData> data = file->OpenAsData(); if (!data) { - context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + context_->GetDiagnostics()->Error(android::DiagMessage(file->GetSource()) << "failed to open file"); return false; } @@ -569,7 +571,7 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv if (file_ref->type == ResourceFile::Type::kProtoXml) { pb::XmlNode pb_xml_node; if (!pb_xml_node.ParseFromArray(data->data(), static_cast<int>(data->size()))) { - context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + context_->GetDiagnostics()->Error(android::DiagMessage(file->GetSource()) << "failed to parse proto XML"); return false; } @@ -577,7 +579,7 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv std::string error; file_op.xml_to_flatten = DeserializeXmlResourceFromPb(pb_xml_node, &error); if (file_op.xml_to_flatten == nullptr) { - context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + context_->GetDiagnostics()->Error(android::DiagMessage(file->GetSource()) << "failed to deserialize proto XML: " << error); return false; } @@ -585,7 +587,7 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv std::string error_str; file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(), &error_str); if (file_op.xml_to_flatten == nullptr) { - context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + context_->GetDiagnostics()->Error(android::DiagMessage(file->GetSource()) << "failed to parse binary XML: " << error_str); return false; } @@ -596,7 +598,8 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv file_op.xml_to_flatten->file.config = config_value->config; file_op.xml_to_flatten->file.source = file_ref->GetSource(); - file_op.xml_to_flatten->file.name = ResourceName(pkg->name, type->type, entry->name); + file_op.xml_to_flatten->file.name = + ResourceName(pkg->name, type->named_type, entry->name); } // NOTE(adamlesinski): Explicitly construct a StringPiece here, or @@ -620,10 +623,10 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv if (drawable_entry != kDrawableVersions.end()) { if (drawable_entry->second > context_->GetMinSdkVersion() && drawable_entry->second > config.sdkVersion) { - context_->GetDiagnostics()->Error(DiagMessage(file_op.xml_to_flatten->file.source) - << "<" << drawable_entry->first << "> elements " - << "require a sdk version of at least " - << (int16_t) drawable_entry->second); + context_->GetDiagnostics()->Error( + android::DiagMessage(file_op.xml_to_flatten->file.source) + << "<" << drawable_entry->first << "> elements " + << "require a sdk version of at least " << (int16_t)drawable_entry->second); error = true; continue; } @@ -641,7 +644,7 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv if (doc->file.config != file_op.config) { // Only add the new versioned configurations. if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage(doc->file.source) + context_->GetDiagnostics()->Note(android::DiagMessage(doc->file.source) << "auto-versioning resource from config '" << config << "' -> '" << doc->file.config << "'"); } @@ -679,12 +682,12 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv return !error; } -static bool WriteStableIdMapToPath(IDiagnostics* diag, +static bool WriteStableIdMapToPath(android::IDiagnostics* diag, const std::unordered_map<ResourceName, ResourceId>& id_map, const std::string& id_map_path) { io::FileOutputStream fout(id_map_path); if (fout.HadError()) { - diag->Error(DiagMessage(id_map_path) << "failed to open: " << fout.GetError()); + diag->Error(android::DiagMessage(id_map_path) << "failed to open: " << fout.GetError()); return false; } @@ -699,17 +702,17 @@ static bool WriteStableIdMapToPath(IDiagnostics* diag, fout.Flush(); if (fout.HadError()) { - diag->Error(DiagMessage(id_map_path) << "failed writing to file: " << fout.GetError()); + diag->Error(android::DiagMessage(id_map_path) << "failed writing to file: " << fout.GetError()); return false; } return true; } -static bool LoadStableIdMap(IDiagnostics* diag, const std::string& path, +static bool LoadStableIdMap(android::IDiagnostics* diag, const std::string& path, std::unordered_map<ResourceName, ResourceId>* out_id_map) { std::string content; if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) { - diag->Error(DiagMessage(path) << "failed reading stable ID file"); + diag->Error(android::DiagMessage(path) << "failed reading stable ID file"); return false; } @@ -724,7 +727,7 @@ static bool LoadStableIdMap(IDiagnostics* diag, const std::string& path, auto iter = std::find(line.begin(), line.end(), '='); if (iter == line.end()) { - diag->Error(DiagMessage(Source(path, line_no)) << "missing '='"); + diag->Error(android::DiagMessage(android::Source(path, line_no)) << "missing '='"); return false; } @@ -732,8 +735,8 @@ static bool LoadStableIdMap(IDiagnostics* diag, const std::string& path, StringPiece res_name_str = util::TrimWhitespace(line.substr(0, std::distance(line.begin(), iter))); if (!ResourceUtils::ParseResourceName(res_name_str, &name)) { - diag->Error(DiagMessage(Source(path, line_no)) << "invalid resource name '" << res_name_str - << "'"); + diag->Error(android::DiagMessage(android::Source(path, line_no)) + << "invalid resource name '" << res_name_str << "'"); return false; } @@ -743,8 +746,8 @@ static bool LoadStableIdMap(IDiagnostics* diag, const std::string& path, std::optional<ResourceId> maybe_id = ResourceUtils::ParseResourceId(res_id_str); if (!maybe_id) { - diag->Error(DiagMessage(Source(path, line_no)) << "invalid resource ID '" << res_id_str - << "'"); + diag->Error(android::DiagMessage(android::Source(path, line_no)) + << "invalid resource ID '" << res_id_str << "'"); return false; } @@ -834,20 +837,21 @@ class Linker { auto asset_source = util::make_unique<AssetManagerSymbolSource>(); for (const std::string& path : options_.include_paths) { if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() << "including " << path); + context_->GetDiagnostics()->Note(android::DiagMessage() << "including " << path); } std::string error; auto zip_collection = io::ZipFileCollection::Create(path, &error); if (zip_collection == nullptr) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to open APK: " << error); + context_->GetDiagnostics()->Error(android::DiagMessage() + << "failed to open APK: " << error); return false; } if (zip_collection->FindFile(kProtoResourceTablePath) != nullptr) { // Load this as a static library include. std::unique_ptr<LoadedApk> static_apk = LoadedApk::LoadProtoApkFromFileCollection( - Source(path), std::move(zip_collection), context_->GetDiagnostics()); + android::Source(path), std::move(zip_collection), context_->GetDiagnostics()); if (static_apk == nullptr) { return false; } @@ -856,7 +860,8 @@ class Linker { // Can't include static libraries when not building a static library (they have no IDs // assigned). context_->GetDiagnostics()->Error( - DiagMessage(path) << "can't include static library when not building a static lib"); + android::DiagMessage(path) + << "can't include static library when not building a static lib"); return false; } @@ -868,7 +873,8 @@ class Linker { if (options_.no_static_lib_packages && !table->packages.empty()) { auto lib_package_result = GetStaticLibraryPackage(table); if (!lib_package_result.has_value()) { - context_->GetDiagnostics()->Error(DiagMessage(path) << lib_package_result.error()); + context_->GetDiagnostics()->Error(android::DiagMessage(path) + << lib_package_result.error()); return false; } lib_package_result.value()->name = context_->GetCompilationPackage(); @@ -879,7 +885,7 @@ class Linker { static_library_includes_.push_back(std::move(static_apk)); } else { if (!asset_source->AddAssetPath(path)) { - context_->GetDiagnostics()->Error(DiagMessage() + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to load include path " << path); return false; } @@ -912,7 +918,8 @@ class Linker { return true; } - std::optional<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res, IDiagnostics* diag) { + std::optional<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res, + android::IDiagnostics* diag) { TRACE_CALL(); // Make sure the first element is <manifest> with package attribute. xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get()); @@ -923,13 +930,13 @@ class Linker { AppInfo app_info; if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") { - diag->Error(DiagMessage(xml_res->file.source) << "root tag must be <manifest>"); + diag->Error(android::DiagMessage(xml_res->file.source) << "root tag must be <manifest>"); return {}; } xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package"); if (!package_attr) { - diag->Error(DiagMessage(xml_res->file.source) + diag->Error(android::DiagMessage(xml_res->file.source) << "<manifest> must have a 'package' attribute"); return {}; } @@ -939,7 +946,7 @@ class Linker { manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) { std::optional<uint32_t> maybe_code = ResourceUtils::ParseInt(version_code_attr->value); if (!maybe_code) { - diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number)) + diag->Error(android::DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number)) << "invalid android:versionCode '" << version_code_attr->value << "'"); return {}; } @@ -950,9 +957,9 @@ class Linker { manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor")) { std::optional<uint32_t> maybe_code = ResourceUtils::ParseInt(version_code_major_attr->value); if (!maybe_code) { - diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number)) - << "invalid android:versionCodeMajor '" - << version_code_major_attr->value << "'"); + diag->Error(android::DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number)) + << "invalid android:versionCodeMajor '" << version_code_major_attr->value + << "'"); return {}; } app_info.version_code_major = maybe_code.value(); @@ -962,7 +969,7 @@ class Linker { manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) { std::optional<uint32_t> maybe_code = ResourceUtils::ParseInt(revision_code_attr->value); if (!maybe_code) { - diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number)) + diag->Error(android::DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number)) << "invalid android:revisionCode '" << revision_code_attr->value << "'"); return {}; } @@ -1009,21 +1016,21 @@ class Linker { // We have a package that is not related to the one we're building! for (const auto& type : package->types) { for (const auto& entry : type->entries) { - ResourceNameRef res_name(package->name, type->type, entry->name); + ResourceNameRef res_name(package->name, type->named_type, entry->name); for (const auto& config_value : entry->values) { // Special case the occurrence of an ID that is being generated // for the 'android' package. This is due to legacy reasons. if (ValueCast<Id>(config_value->value.get()) && package->name == "android") { - context_->GetDiagnostics()->Warn(DiagMessage(config_value->value->GetSource()) - << "generated id '" << res_name - << "' for external package '" << package->name - << "'"); + context_->GetDiagnostics()->Warn( + android::DiagMessage(config_value->value->GetSource()) + << "generated id '" << res_name << "' for external package '" << package->name + << "'"); } else { - context_->GetDiagnostics()->Error(DiagMessage(config_value->value->GetSource()) - << "defined resource '" << res_name - << "' for external package '" << package->name - << "'"); + context_->GetDiagnostics()->Error( + android::DiagMessage(config_value->value->GetSource()) + << "defined resource '" << res_name << "' for external package '" + << package->name << "'"); error = true; } } @@ -1046,9 +1053,10 @@ class Linker { for (const auto& type : package->types) { for (const auto& entry : type->entries) { if (entry->id) { - ResourceNameRef res_name(package->name, type->type, entry->name); - context_->GetDiagnostics()->Error(DiagMessage() << "resource " << res_name << " has ID " - << entry->id.value() << " assigned"); + ResourceNameRef res_name(package->name, type->named_type, entry->name); + context_->GetDiagnostics()->Error(android::DiagMessage() + << "resource " << res_name << " has ID " + << entry->id.value() << " assigned"); return false; } } @@ -1057,7 +1065,117 @@ class Linker { return true; } - std::unique_ptr<IArchiveWriter> MakeArchiveWriter(const StringPiece& out) { + bool VerifyLocaleFormat(xml::XmlResource* manifest, android::IDiagnostics* diag) { + // Skip it if the Manifest doesn't declare the localeConfig attribute within the <application> + // element. + const xml::Element* application = manifest->root->FindChild("", "application"); + if (!application) { + return true; + } + const xml::Attribute* localeConfig = + application->FindAttribute(xml::kSchemaAndroid, "localeConfig"); + if (!localeConfig) { + return true; + } + + // Deserialize XML from the compiled file + if (localeConfig->compiled_value) { + const auto localeconfig_reference = ValueCast<Reference>(localeConfig->compiled_value.get()); + const auto localeconfig_entry = + ResolveTableEntry(context_, &final_table_, localeconfig_reference); + if (!localeconfig_entry) { + // If locale config is resolved from external symbols - skip validation. + if (context_->GetExternalSymbols()->FindByReference(*localeconfig_reference)) { + return true; + } + context_->GetDiagnostics()->Error( + android::DiagMessage(localeConfig->compiled_value->GetSource()) + << "no localeConfig entry"); + return false; + } + for (const auto& value : localeconfig_entry->values) { + const FileReference* file_ref = ValueCast<FileReference>(value->value.get()); + if (!file_ref) { + context_->GetDiagnostics()->Error( + android::DiagMessage(localeConfig->compiled_value->GetSource()) + << "no file reference"); + return false; + } + io::IFile* file = file_ref->file; + if (!file) { + context_->GetDiagnostics()->Error(android::DiagMessage(file_ref->GetSource()) + << "file not found"); + return false; + } + std::unique_ptr<io::IData> data = file->OpenAsData(); + if (!data) { + context_->GetDiagnostics()->Error(android::DiagMessage(file->GetSource()) + << "failed to open file"); + return false; + } + pb::XmlNode pb_xml_node; + if (!pb_xml_node.ParseFromArray(data->data(), static_cast<int>(data->size()))) { + context_->GetDiagnostics()->Error(android::DiagMessage(file->GetSource()) + << "failed to parse proto XML"); + return false; + } + + std::string error; + std::unique_ptr<xml::XmlResource> localeConfig_xml = + DeserializeXmlResourceFromPb(pb_xml_node, &error); + if (!localeConfig_xml) { + context_->GetDiagnostics()->Error(android::DiagMessage(file->GetSource()) + << "failed to deserialize proto XML: " << error); + return false; + } + xml::Element* localeConfig_el = xml::FindRootElement(localeConfig_xml->root.get()); + if (!localeConfig_el) { + diag->Error(android::DiagMessage(file->GetSource()) << "no root tag defined"); + return false; + } + if (localeConfig_el->name != "locale-config") { + diag->Error(android::DiagMessage(file->GetSource()) + << "invalid element name: " << localeConfig_el->name + << ", expected: locale-config"); + return false; + } + for (const xml::Element* child_el : localeConfig_el->GetChildElements()) { + if (child_el->name == "locale") { + if (const xml::Attribute* locale_name_attr = + child_el->FindAttribute(xml::kSchemaAndroid, "name")) { + const std::string& locale_name = locale_name_attr->value; + const std::string valid_name = ConvertToBCP47Tag(locale_name); + // Start to verify the locale format + ConfigDescription config; + if (!ConfigDescription::Parse(valid_name, &config)) { + diag->Error(android::DiagMessage(file->GetSource()) + << "invalid configuration: " << locale_name); + return false; + } + } else { + diag->Error(android::DiagMessage(file->GetSource()) + << "the attribute android:name is not found"); + return false; + } + } else { + diag->Error(android::DiagMessage(file->GetSource()) + << "invalid element name: " << child_el->name << ", expected: locale"); + return false; + } + } + } + } + return true; + } + + std::string ConvertToBCP47Tag(const std::string& locale) { + std::string bcp47tag = "b+"; + bcp47tag += locale; + std::replace(bcp47tag.begin(), bcp47tag.end(), '-', '+'); + return bcp47tag; + } + + std::unique_ptr<IArchiveWriter> MakeArchiveWriter(StringPiece out) { if (options_.output_to_directory) { return CreateDirectoryArchiveWriter(context_->GetDiagnostics(), out); } else { @@ -1069,10 +1187,11 @@ class Linker { TRACE_CALL(); switch (format) { case OutputFormat::kApk: { - BigBuffer buffer(1024); + android::BigBuffer buffer(1024); TableFlattener flattener(options_.table_flattener_options, &buffer); if (!flattener.Consume(context_, table)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table"); + context_->GetDiagnostics()->Error(android::DiagMessage() + << "failed to flatten resource table"); return false; } @@ -1092,8 +1211,8 @@ class Linker { return false; } - bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate, - const StringPiece& out_package, const JavaClassGeneratorOptions& java_options, + bool WriteJavaFile(ResourceTable* table, StringPiece package_name_to_generate, + StringPiece out_package, const JavaClassGeneratorOptions& java_options, const std::optional<std::string>& out_text_symbols_path = {}) { if (!options_.generate_java_class_path && !out_text_symbols_path) { return true; @@ -1105,7 +1224,7 @@ class Linker { out_path = options_.generate_java_class_path.value(); file::AppendPath(&out_path, file::PackageToPath(out_package)); if (!file::mkdirs(out_path)) { - context_->GetDiagnostics()->Error(DiagMessage() + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to create directory '" << out_path << "'"); return false; } @@ -1114,8 +1233,9 @@ class Linker { fout = util::make_unique<io::FileOutputStream>(out_path); if (fout->HadError()) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path - << "': " << fout->GetError()); + context_->GetDiagnostics()->Error(android::DiagMessage() + << "failed writing to '" << out_path + << "': " << fout->GetError()); return false; } } @@ -1124,7 +1244,7 @@ class Linker { if (out_text_symbols_path) { fout_text = util::make_unique<io::FileOutputStream>(out_text_symbols_path.value()); if (fout_text->HadError()) { - context_->GetDiagnostics()->Error(DiagMessage() + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed writing to '" << out_text_symbols_path.value() << "': " << fout_text->GetError()); return false; @@ -1133,7 +1253,7 @@ class Linker { JavaClassGenerator generator(context_, table, java_options); if (!generator.Generate(package_name_to_generate, out_package, fout.get(), fout_text.get())) { - context_->GetDiagnostics()->Error(DiagMessage(out_path) << generator.GetError()); + context_->GetDiagnostics()->Error(android::DiagMessage(out_path) << generator.GetError()); return false; } @@ -1257,8 +1377,8 @@ class Linker { file::AppendPath(&out_path, file::PackageToPath(package_utf8)); if (!file::mkdirs(out_path)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to create directory '" << out_path - << "'"); + context_->GetDiagnostics()->Error(android::DiagMessage() + << "failed to create directory '" << out_path << "'"); return false; } @@ -1266,8 +1386,8 @@ class Linker { io::FileOutputStream fout(out_path); if (fout.HadError()) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to open '" << out_path - << "': " << fout.GetError()); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to open '" << out_path + << "': " << fout.GetError()); return false; } @@ -1276,8 +1396,8 @@ class Linker { fout.Flush(); if (fout.HadError()) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path - << "': " << fout.GetError()); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed writing to '" << out_path + << "': " << fout.GetError()); return false; } return true; @@ -1292,8 +1412,8 @@ class Linker { const std::string& out_path = out.value(); io::FileOutputStream fout(out_path); if (fout.HadError()) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to open '" << out_path - << "': " << fout.GetError()); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to open '" << out_path + << "': " << fout.GetError()); return false; } @@ -1302,8 +1422,8 @@ class Linker { fout.Flush(); if (fout.HadError()) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path - << "': " << fout.GetError()); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed writing to '" << out_path + << "': " << fout.GetError()); return false; } return true; @@ -1312,12 +1432,13 @@ class Linker { bool MergeStaticLibrary(const std::string& input, bool override) { TRACE_CALL(); if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() << "merging static library " << input); + context_->GetDiagnostics()->Note(android::DiagMessage() + << "merging static library " << input); } std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(input, context_->GetDiagnostics()); if (apk == nullptr) { - context_->GetDiagnostics()->Error(DiagMessage(input) << "invalid static library"); + context_->GetDiagnostics()->Error(android::DiagMessage(input) << "invalid static library"); return false; } @@ -1328,7 +1449,7 @@ class Linker { auto lib_package_result = GetStaticLibraryPackage(table); if (!lib_package_result.has_value()) { - context_->GetDiagnostics()->Error(DiagMessage(input) << lib_package_result.error()); + context_->GetDiagnostics()->Error(android::DiagMessage(input) << lib_package_result.error()); return false; } @@ -1347,12 +1468,12 @@ class Linker { // Clear the package name, so as to make the resources look like they are coming from the // local package. pkg->name = ""; - result = table_merger_->Merge(Source(input), table, override); + result = table_merger_->Merge(android::Source(input), table, override); } else { // This is the proper way to merge libraries, where the package name is // preserved and resource names are mangled. - result = table_merger_->MergeAndMangle(Source(input), pkg->name, table); + result = table_merger_->MergeAndMangle(android::Source(input), pkg->name, table); } if (!result) { @@ -1364,7 +1485,7 @@ class Linker { return true; } - bool MergeExportedSymbols(const Source& source, + bool MergeExportedSymbols(const android::Source& source, const std::vector<SourcedResourceName>& exported_symbols) { TRACE_CALL(); // Add the exports of this file to the table. @@ -1394,7 +1515,7 @@ class Linker { bool MergeCompiledFile(const ResourceFile& compiled_file, io::IFile* file, bool override) { TRACE_CALL(); if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() + context_->GetDiagnostics()->Note(android::DiagMessage() << "merging '" << compiled_file.name << "' from compiled file " << compiled_file.source); } @@ -1413,14 +1534,14 @@ class Linker { bool MergeArchive(const std::string& input, bool override) { TRACE_CALL(); if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() << "merging archive " << input); + context_->GetDiagnostics()->Note(android::DiagMessage() << "merging archive " << input); } std::string error_str; std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::Create(input, &error_str); if (!collection) { - context_->GetDiagnostics()->Error(DiagMessage(input) << error_str); + context_->GetDiagnostics()->Error(android::DiagMessage(input) << error_str); return false; } @@ -1460,31 +1581,32 @@ class Linker { // where we could have other files like classes.dex. bool MergeFile(io::IFile* file, bool override) { TRACE_CALL(); - const Source& src = file->GetSource(); + const android::Source& src = file->GetSource(); if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) { // Since AAPT compiles these file types and appends .flat to them, seeing // their raw extensions is a sign that they weren't compiled. const StringPiece file_type = util::EndsWith(src.path, ".xml") ? "XML" : "PNG"; - context_->GetDiagnostics()->Error(DiagMessage(src) << "uncompiled " << file_type - << " file passed as argument. Must be " - "compiled first into .flat file."); + context_->GetDiagnostics()->Error(android::DiagMessage(src) + << "uncompiled " << file_type + << " file passed as argument. Must be " + "compiled first into .flat file."); return false; } else if (!util::EndsWith(src.path, ".apc") && !util::EndsWith(src.path, ".flat")) { if (context_->IsVerbose()) { - context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring unrecognized file"); + context_->GetDiagnostics()->Warn(android::DiagMessage(src) << "ignoring unrecognized file"); return true; } } std::unique_ptr<io::InputStream> input_stream = file->OpenInputStream(); if (input_stream == nullptr) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open file"); + context_->GetDiagnostics()->Error(android::DiagMessage(src) << "failed to open file"); return false; } if (input_stream->HadError()) { - context_->GetDiagnostics()->Error(DiagMessage(src) + context_->GetDiagnostics()->Error(android::DiagMessage(src) << "failed to open file: " << input_stream->GetError()); return false; } @@ -1493,7 +1615,7 @@ class Linker { ContainerReader reader(input_stream.get()); if (reader.HadError()) { - context_->GetDiagnostics()->Error(DiagMessage(src) + context_->GetDiagnostics()->Error(android::DiagMessage(src) << "failed to read file: " << reader.GetError()); return false; } @@ -1503,21 +1625,22 @@ class Linker { TRACE_NAME(std::string("Process ResTable:") + file->GetSource().path); pb::ResourceTable pb_table; if (!entry->GetResTable(&pb_table)) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read resource table: " - << entry->GetError()); + context_->GetDiagnostics()->Error( + android::DiagMessage(src) << "failed to read resource table: " << entry->GetError()); return false; } ResourceTable table; std::string error; if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &error)) { - context_->GetDiagnostics()->Error(DiagMessage(src) + context_->GetDiagnostics()->Error(android::DiagMessage(src) << "failed to deserialize resource table: " << error); return false; } if (!table_merger_->Merge(src, &table, override)) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to merge resource table"); + context_->GetDiagnostics()->Error(android::DiagMessage(src) + << "failed to merge resource table"); return false; } } else if (entry->Type() == ContainerEntryType::kResFile) { @@ -1526,15 +1649,15 @@ class Linker { off64_t offset; size_t len; if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &len)) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to get resource file: " - << entry->GetError()); + context_->GetDiagnostics()->Error( + android::DiagMessage(src) << "failed to get resource file: " << entry->GetError()); return false; } ResourceFile resource_file; std::string error; if (!DeserializeCompiledFileFromPb(pb_compiled_file, &resource_file, &error)) { - context_->GetDiagnostics()->Error(DiagMessage(src) + context_->GetDiagnostics()->Error(android::DiagMessage(src) << "failed to read compiled header: " << error); return false; } @@ -1563,10 +1686,10 @@ class Linker { auto iter = merged_assets.find(full_key); if (iter == merged_assets.end()) { - merged_assets.emplace(std::move(full_key), - util::make_unique<io::RegularFile>(Source(std::move(full_path)))); + merged_assets.emplace(std::move(full_key), util::make_unique<io::RegularFile>( + android::Source(std::move(full_path)))); } else if (context_->IsVerbose()) { - context_->GetDiagnostics()->Warn(DiagMessage(iter->second->GetSource()) + context_->GetDiagnostics()->Warn(android::DiagMessage(iter->second->GetSource()) << "asset file overrides '" << full_path << "'"); } } @@ -1651,10 +1774,10 @@ class Linker { continue; } - context_->GetDiagnostics()->Note(DiagMessage() << "generating " - << round_icon_reference->name.value() - << " with config \"" << config_value->config - << "\" for round icon compatibility"); + context_->GetDiagnostics()->Note(android::DiagMessage() + << "generating " << round_icon_reference->name.value() + << " with config \"" << config_value->config + << "\" for round icon compatibility"); CloningValueTransformer cloner(&table->string_pool); auto value = icon_reference->Transform(cloner); @@ -1680,7 +1803,7 @@ class Linker { if (util::IsAndroidSharedUserId(context_->GetCompilationPackage(), shared_user_id)) { return true; } - DiagMessage error_msg(manifest_el->line_number); + android::DiagMessage error_msg(manifest_el->line_number); error_msg << "attribute 'sharedUserId' in <manifest> tag is not a valid shared user id: '" << shared_user_id << "'"; if (options_.manifest_fixer_options.warn_validation) { @@ -1756,7 +1879,7 @@ class Linker { ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set); if (!file_flattener.Flatten(table, writer)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed linking file resources"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed linking file resources"); return false; } @@ -1787,8 +1910,8 @@ class Linker { if (context_->IsVerbose()) { context_->GetDiagnostics()->Note( - DiagMessage() << "rewriting resource package name for feature split to '" - << new_package_name << "'"); + android::DiagMessage() << "rewriting resource package name for feature split to '" + << new_package_name << "'"); } package_to_rewrite->name = new_package_name; } @@ -1808,7 +1931,7 @@ class Linker { } if (!success) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resource table"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to write resource table"); } return success; } @@ -1869,7 +1992,7 @@ class Linker { // Verify we're building a regular app. if (context_->GetPackageType() != PackageType::kApp) { context_->GetDiagnostics()->Error( - DiagMessage() << "package 'android' can only be built as a regular app"); + android::DiagMessage() << "package 'android' can only be built as a regular app"); return 1; } } @@ -1882,7 +2005,7 @@ class Linker { table_merger_ = util::make_unique<TableMerger>(context_, &final_table_, table_merger_options); if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() + context_->GetDiagnostics()->Note(android::DiagMessage() << StringPrintf("linking package '%s' using package ID %02x", context_->GetCompilationPackage().data(), context_->GetPackageId())); @@ -1903,14 +2026,14 @@ class Linker { for (const std::string& input : input_files) { if (!MergePath(input, false)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed parsing input"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed parsing input"); return 1; } } for (const std::string& input : options_.overlay_files) { if (!MergePath(input, true)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed parsing overlays"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed parsing overlays"); return 1; } } @@ -1923,14 +2046,15 @@ class Linker { PrivateAttributeMover mover; if (context_->GetPackageId() == kAndroidPackageId && !mover.Consume(context_, &final_table_)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed moving private attributes"); + context_->GetDiagnostics()->Error(android::DiagMessage() + << "failed moving private attributes"); return 1; } // Assign IDs if we are building a regular app. IdAssigner id_assigner(&options_.stable_id_map); if (!id_assigner.Consume(context_, &final_table_)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed assigning IDs"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed assigning IDs"); return 1; } @@ -1939,7 +2063,7 @@ class Linker { for (auto& package : final_table_.packages) { for (auto& type : package->types) { for (auto& entry : type->entries) { - ResourceName name(package->name, type->type, entry->name); + ResourceName name(package->name, type->named_type, entry->name); // The IDs are guaranteed to exist. options_.stable_id_map[std::move(name)] = entry->id.value(); } @@ -1974,7 +2098,7 @@ class Linker { // are just identifiers. if (context_->GetMinSdkVersion() < SDK_O && context_->GetPackageType() == PackageType::kApp) { if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() + context_->GetDiagnostics()->Note(android::DiagMessage() << "enabling pre-O feature split ID rewriting"); } context_->GetExternalSymbols()->SetDelegate( @@ -1985,7 +2109,7 @@ class Linker { // We want to force any references to these to fail the build. if (!options_.no_resource_removal) { if (!NoDefaultResourceRemover{}.Consume(context_, &final_table_)) { - context_->GetDiagnostics()->Error(DiagMessage() + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed removing resources with no defaults"); return 1; } @@ -1993,19 +2117,19 @@ class Linker { ReferenceLinker linker; if (!options_.merge_only && !linker.Consume(context_, &final_table_)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed linking references"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed linking references"); return 1; } if (context_->GetPackageType() == PackageType::kStaticLib) { if (!options_.products.empty()) { - context_->GetDiagnostics()->Warn(DiagMessage() + context_->GetDiagnostics()->Warn(android::DiagMessage() << "can't select products when building static library"); } } else { ProductFilter product_filter(options_.products); if (!product_filter.Consume(context_, &final_table_)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed stripping products"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed stripping products"); return 1; } } @@ -2013,14 +2137,14 @@ class Linker { if (!options_.no_auto_version) { AutoVersioner versioner; if (!versioner.Consume(context_, &final_table_)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed versioning styles"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed versioning styles"); return 1; } } if (context_->GetPackageType() != PackageType::kStaticLib && context_->GetMinSdkVersion() > 0) { if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() + context_->GetDiagnostics()->Note(android::DiagMessage() << "collapsing resource versions for minimum SDK " << context_->GetMinSdkVersion()); } @@ -2039,9 +2163,8 @@ class Linker { ConfigDescription config_description; if (!ConfigDescription::Parse(config_string, &config_description)) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed to parse --excluded-configs " - << config_string); + context_->GetDiagnostics()->Error( + android::DiagMessage() << "failed to parse --excluded-configs " << config_string); return 1; } @@ -2050,7 +2173,8 @@ class Linker { ResourceExcluder excluder(excluded_configs); if (!excluder.Consume(context_, &final_table_)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed excluding configurations"); + context_->GetDiagnostics()->Error(android::DiagMessage() + << "failed excluding configurations"); return 1; } } @@ -2058,7 +2182,7 @@ class Linker { if (!options_.no_resource_deduping) { ResourceDeduper deduper; if (!deduper.Consume(context_, &final_table_)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed deduping resources"); return 1; } } @@ -2070,7 +2194,7 @@ class Linker { if (context_->GetPackageType() == PackageType::kStaticLib) { if (options_.table_splitter_options.config_filter != nullptr || !options_.table_splitter_options.preferred_densities.empty()) { - context_->GetDiagnostics()->Warn(DiagMessage() + context_->GetDiagnostics()->Warn(android::DiagMessage() << "can't strip resources when building static library"); } } else { @@ -2081,7 +2205,7 @@ class Linker { AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints); if (origConstraintSize != options_.split_constraints.size()) { - context_->GetDiagnostics()->Warn(DiagMessage() + context_->GetDiagnostics()->Warn(android::DiagMessage() << "requested to split resources prior to min sdk of " << context_->GetMinSdkVersion()); } @@ -2096,7 +2220,7 @@ class Linker { auto split_constraints_iter = options_.split_constraints.begin(); for (std::unique_ptr<ResourceTable>& split_table : table_splitter.splits()) { if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage(*path_iter) + context_->GetDiagnostics()->Note(android::DiagMessage(*path_iter) << "generating split with configurations '" << util::Joiner(split_constraints_iter->configs, ", ") << "'"); @@ -2104,7 +2228,7 @@ class Linker { std::unique_ptr<IArchiveWriter> archive_writer = MakeArchiveWriter(*path_iter); if (!archive_writer) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to create archive"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to create archive"); return 1; } @@ -2114,7 +2238,7 @@ class Linker { XmlReferenceLinker linker(&final_table_); if (!linker.Consume(context_, split_manifest.get())) { - context_->GetDiagnostics()->Error(DiagMessage() + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to create Split AndroidManifest.xml"); return 1; } @@ -2132,7 +2256,7 @@ class Linker { // Start writing the base APK. std::unique_ptr<IArchiveWriter> archive_writer = MakeArchiveWriter(options_.output_path); if (!archive_writer) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to create archive"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to create archive"); return 1; } @@ -2176,10 +2300,14 @@ class Linker { } if (error) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed processing manifest"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed processing manifest"); return 1; } + if (!VerifyLocaleFormat(manifest_xml.get(), context_->GetDiagnostics())) { + return 1; + }; + if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) { return 1; } @@ -2245,7 +2373,7 @@ int LinkCommand::Action(const std::vector<std::string>& args) { const std::string path = arg.substr(1, arg.size() - 1); std::string error; if (!file::AppendArgsFromFile(path, &arg_list, &error)) { - context.GetDiagnostics()->Error(DiagMessage(path) << error); + context.GetDiagnostics()->Error(android::DiagMessage(path) << error); return 1; } } else { @@ -2259,7 +2387,7 @@ int LinkCommand::Action(const std::vector<std::string>& args) { const std::string path = arg.substr(1, arg.size() - 1); std::string error; if (!file::AppendArgsFromFile(path, &options_.overlay_files, &error)) { - context.GetDiagnostics()->Error(DiagMessage(path) << error); + context.GetDiagnostics()->Error(android::DiagMessage(path) << error); return 1; } } else { @@ -2272,9 +2400,9 @@ int LinkCommand::Action(const std::vector<std::string>& args) { } if (int{shared_lib_} + int{static_lib_} + int{proto_format_} > 1) { - context.GetDiagnostics()->Error( - DiagMessage() - << "only one of --shared-lib, --static-lib, or --proto_format can be defined"); + context.GetDiagnostics() + ->Error(android::DiagMessage() + << "only one of --shared-lib, --static-lib, or --proto_format can be defined"); return 1; } @@ -2282,17 +2410,21 @@ int LinkCommand::Action(const std::vector<std::string>& args) { // If a shared library styleable in a public R.java uses a private attribute, attempting to // reference the private attribute within the styleable array will cause a link error because // the private attribute will not be emitted in the public R.java. - context.GetDiagnostics()->Error(DiagMessage() + context.GetDiagnostics()->Error(android::DiagMessage() << "--shared-lib cannot currently be used in combination with" << " --private-symbols"); return 1; } if (options_.merge_only && !static_lib_) { - context.GetDiagnostics()->Error( - DiagMessage() << "the --merge-only flag can be only used when building a static library"); + context.GetDiagnostics() + ->Error(android::DiagMessage() + << "the --merge-only flag can be only used when building a static library"); return 1; } + if (options_.use_sparse_encoding) { + options_.table_flattener_options.sparse_entries = SparseEntriesMode::Enabled; + } // The default build type. context.SetPackageType(PackageType::kApp); @@ -2311,15 +2443,16 @@ int LinkCommand::Action(const std::vector<std::string>& args) { if (package_id_) { if (context.GetPackageType() != PackageType::kApp) { context.GetDiagnostics()->Error( - DiagMessage() << "can't specify --package-id when not building a regular app"); + android::DiagMessage() << "can't specify --package-id when not building a regular app"); return 1; } const std::optional<uint32_t> maybe_package_id_int = ResourceUtils::ParseInt(package_id_.value()); if (!maybe_package_id_int) { - context.GetDiagnostics()->Error(DiagMessage() << "package ID '" << package_id_.value() - << "' is not a valid integer"); + context.GetDiagnostics()->Error(android::DiagMessage() + << "package ID '" << package_id_.value() + << "' is not a valid integer"); return 1; } @@ -2328,7 +2461,7 @@ int LinkCommand::Action(const std::vector<std::string>& args) { || package_id_int == kFrameworkPackageId || (!options_.allow_reserved_package_id && package_id_int < kAppPackageId)) { context.GetDiagnostics()->Error( - DiagMessage() << StringPrintf( + android::DiagMessage() << StringPrintf( "invalid package ID 0x%02x. Must be in the range 0x7f-0xff.", package_id_int)); return 1; } @@ -2339,14 +2472,14 @@ int LinkCommand::Action(const std::vector<std::string>& args) { for (std::string& extra_package : extra_java_packages_) { // A given package can actually be a colon separated list of packages. for (StringPiece package : util::Split(extra_package, ':')) { - options_.extra_java_packages.insert(package.to_string()); + options_.extra_java_packages.emplace(package); } } if (product_list_) { for (StringPiece product : util::Tokenize(product_list_.value(), ',')) { if (product != "" && product != "default") { - options_.products.insert(product.to_string()); + options_.products.emplace(product); } } } @@ -2392,7 +2525,7 @@ int LinkCommand::Action(const std::vector<std::string>& args) { const std::string path = regex.substr(1, regex.size() -1); std::string error; if (!file::AppendSetArgsFromFile(path, &options_.extensions_to_not_compress, &error)) { - context.GetDiagnostics()->Error(DiagMessage(path) << error); + context.GetDiagnostics()->Error(android::DiagMessage(path) << error); return 1; } } else { diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index d8c76e297ec5..1b1e93bd480a 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -20,12 +20,12 @@ #include <regex> #include "Command.h" -#include "Diagnostics.h" #include "Resource.h" -#include "split/TableSplitter.h" +#include "androidfw/IDiagnostics.h" #include "format/binary/TableFlattener.h" #include "format/proto/ProtoSerialize.h" #include "link/ManifestFixer.h" +#include "split/TableSplitter.h" #include "trace/TraceBuffer.h" namespace aapt { @@ -69,6 +69,7 @@ struct LinkOptions { bool no_resource_removal = false; bool no_xml_namespaces = false; bool do_not_compress_anything = false; + bool use_sparse_encoding = false; std::unordered_set<std::string> extensions_to_not_compress; std::optional<std::regex> regex_to_not_compress; @@ -111,8 +112,7 @@ struct LinkOptions { class LinkCommand : public Command { public: - explicit LinkCommand(IDiagnostics* diag) : Command("link", "l"), - diag_(diag) { + explicit LinkCommand(android::IDiagnostics* diag) : Command("link", "l"), diag_(diag) { SetDescription("Links resources into an apk."); AddRequiredFlag("-o", "Output path.", &options_.output_path, Command::kPath); AddRequiredFlag("--manifest", "Path to the Android manifest to build.", @@ -157,8 +157,11 @@ class LinkCommand : public Command { "defaults. Use this only when building runtime resource overlay packages.", &options_.no_resource_removal); AddOptionalSwitch("--enable-sparse-encoding", - "This decreases APK size at the cost of resource retrieval performance.", - &options_.table_flattener_options.use_sparse_entries); + "This decreases APK size at the cost of resource retrieval performance.", + &options_.use_sparse_encoding); + AddOptionalSwitch("--enable-compact-entries", + "This decreases APK size by using compact resource entries for simple data types.", + &options_.table_flattener_options.use_compact_entries); AddOptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01.", &legacy_x_flag_); AddOptionalSwitch("-z", "Require localization of strings marked 'suggested'.", @@ -206,6 +209,13 @@ class LinkCommand : public Command { AddOptionalFlag("--compile-sdk-version-name", "Version name to inject into the AndroidManifest.xml if none is present.", &options_.manifest_fixer_options.compile_sdk_version_codename); + AddOptionalSwitch( + "--no-compile-sdk-metadata", + "Suppresses output of compile SDK-related attributes in AndroidManifest.xml,\n" + "including android:compileSdkVersion and platformBuildVersion.", + &options_.manifest_fixer_options.no_compile_sdk_metadata); + AddOptionalFlagList("--fingerprint-prefix", "Fingerprint prefix to add to install constraints.", + &options_.manifest_fixer_options.fingerprint_prefixes); AddOptionalSwitch("--shared-lib", "Generates a shared Android runtime library.", &shared_lib_); AddOptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib_); @@ -270,6 +280,8 @@ class LinkCommand : public Command { "Changes the name of the target package for overlay. Most useful\n" "when used in conjunction with --rename-manifest-package.", &options_.manifest_fixer_options.rename_overlay_target_package); + AddOptionalFlag("--rename-overlay-category", "Changes the category for the overlay.", + &options_.manifest_fixer_options.rename_overlay_category); AddOptionalFlagList("-0", "File suffix not to compress.", &options_.extensions_to_not_compress); AddOptionalSwitch("--no-compress", "Do not compress any resources.", @@ -316,7 +328,7 @@ class LinkCommand : public Command { int Action(const std::vector<std::string>& args) override; private: - IDiagnostics* diag_; + android::IDiagnostics* diag_; LinkOptions options_; std::vector<std::string> overlay_arg_list_; diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp index 430c184ef87d..725a1b86f616 100644 --- a/tools/aapt2/cmd/Link_test.cpp +++ b/tools/aapt2/cmd/Link_test.cpp @@ -19,6 +19,7 @@ #include <android-base/file.h> #include "AppInfo.h" +#include "Diagnostics.h" #include "LoadedApk.h" #include "test/Test.h" @@ -86,7 +87,8 @@ TEST_F(LinkTest, KeepRawXmlStrings) { // Check that the raw string index has been set to the correct string pool entry int32_t raw_index = tree.getAttributeValueStringID(0); ASSERT_THAT(raw_index, Ne(-1)); - EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007")); + EXPECT_THAT(android::util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), + Eq("007")); } TEST_F(LinkTest, NoCompressAssets) { @@ -409,7 +411,7 @@ struct SourceXML { static void BuildApk(const std::vector<SourceXML>& source_files, const std::string& apk_path, LinkCommandBuilder&& link_args, CommandTestFixture* fixture, - IDiagnostics* diag) { + android::IDiagnostics* diag) { TemporaryDir res_dir; TemporaryDir compiled_res_dir; for (auto& source_file : source_files) { @@ -422,7 +424,7 @@ static void BuildApk(const std::vector<SourceXML>& source_files, const std::stri static void BuildSDK(const std::vector<SourceXML>& source_files, const std::string& apk_path, const std::string& java_root_path, CommandTestFixture* fixture, - IDiagnostics* diag) { + android::IDiagnostics* diag) { auto android_manifest = ManifestBuilder(fixture).SetPackageName("android").Build(); auto android_link_args = LinkCommandBuilder(fixture) @@ -434,7 +436,7 @@ static void BuildSDK(const std::vector<SourceXML>& source_files, const std::stri } static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string& java_path, - CommandTestFixture* fixture, IDiagnostics* diag) { + CommandTestFixture* fixture, android::IDiagnostics* diag) { const std::string android_values = R"(<resources> <public type="attr" name="finalized_res" id="0x01010001"/> @@ -470,7 +472,7 @@ static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string& } static void BuildFinalizedSDK(const std::string& apk_path, const std::string& java_path, - CommandTestFixture* fixture, IDiagnostics* diag) { + CommandTestFixture* fixture, android::IDiagnostics* diag) { const std::string android_values = R"(<resources> <public type="attr" name="finalized_res" id="0x01010001"/> @@ -510,7 +512,7 @@ static void BuildFinalizedSDK(const std::string& apk_path, const std::string& ja static void BuildAppAgainstSDK(const std::string& apk_path, const std::string& java_path, const std::string& sdk_path, CommandTestFixture* fixture, - IDiagnostics* diag) { + android::IDiagnostics* diag) { const std::string app_values = R"(<resources xmlns:android="http://schemas.android.com/apk/res/android"> <attr name="bar" /> @@ -573,7 +575,7 @@ TEST_F(LinkTest, StagedAndroidApi) { android::AssetManager2 am; auto android_asset = android::ApkAssets::Load(android_apk); ASSERT_THAT(android_asset, NotNull()); - ASSERT_TRUE(am.SetApkAssets({android_asset.get()})); + ASSERT_TRUE(am.SetApkAssets({android_asset})); auto result = am.GetResourceId("android:attr/finalized_res"); ASSERT_TRUE(result.has_value()); @@ -629,7 +631,7 @@ TEST_F(LinkTest, FinalizedAndroidApi) { auto app_against_non_final = android::ApkAssets::Load(app_apk); ASSERT_THAT(android_asset, NotNull()); ASSERT_THAT(app_against_non_final, NotNull()); - ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()})); + ASSERT_TRUE(am.SetApkAssets({android_asset, app_against_non_final})); auto result = am.GetResourceId("android:attr/finalized_res"); ASSERT_TRUE(result.has_value()); @@ -665,7 +667,7 @@ TEST_F(LinkTest, FinalizedAndroidApi) { auto app_against_final = android::ApkAssets::Load(app_apk_respin); ASSERT_THAT(app_against_final, NotNull()); - ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()})); + ASSERT_TRUE(am.SetApkAssets({android_asset, app_against_final})); { auto style = am.GetBag(0x7f020000); @@ -783,4 +785,212 @@ TEST_F(LinkTest, MacroSubstitution) { EXPECT_THAT(xml_attrs[1].value, Eq("Hello World!")); } +TEST_F(LinkTest, LocaleConfigVerification) { + StdErrDiagnostics diag; + const std::string compiled_files_dir = GetTestPath("compiled"); + + // Normal case + ASSERT_TRUE(CompileFile(GetTestPath("res/xml/locales_config.xml"), R"( + <locale-config xmlns:android="http://schemas.android.com/apk/res/android"> + <locale android:name="en-US"/> + <locale android:name="pt"/> + <locale android:name="es-419"/> + <locale android:name="zh-Hans-SG"/> + </locale-config>)", + compiled_files_dir, &diag)); + + const std::string localeconfig_manifest = GetTestPath("localeconfig_manifest.xml"); + WriteFile(localeconfig_manifest, android::base::StringPrintf(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.aapt2.app"> + + <application + android:localeConfig="@xml/locales_config"> + </application> + </manifest>)")); + + const std::string out_apk = GetTestPath("out.apk"); + + auto link_args = LinkCommandBuilder(this) + .SetManifestFile(localeconfig_manifest) + .AddCompiledResDir(compiled_files_dir, &diag) + .Build(out_apk); + ASSERT_TRUE(Link(link_args, &diag)); + + // Empty locale list + ASSERT_TRUE(CompileFile(GetTestPath("res/xml/empty_locales_config.xml"), R"( + <locale-config xmlns:android="http://schemas.android.com/apk/res/android"> + </locale-config>)", + compiled_files_dir, &diag)); + + const std::string empty_localeconfig_manifest = GetTestPath("empty_localeconfig_manifest.xml"); + WriteFile(empty_localeconfig_manifest, android::base::StringPrintf(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.aapt2.app"> + + <application + android:localeConfig="@xml/empty_locales_config"> + </application> + </manifest>)")); + + auto link1_args = LinkCommandBuilder(this) + .SetManifestFile(empty_localeconfig_manifest) + .AddCompiledResDir(compiled_files_dir, &diag) + .Build(out_apk); + ASSERT_TRUE(Link(link1_args, &diag)); +} + +TEST_F(LinkTest, LocaleConfigVerificationExternalSymbol) { + StdErrDiagnostics diag; + const std::string base_files_dir = GetTestPath("base"); + ASSERT_TRUE(CompileFile(GetTestPath("res/xml/locales_config.xml"), R"( + <locale-config xmlns:android="http://schemas.android.com/apk/res/android"> + <locale android:name="en-US"/> + <locale android:name="pt"/> + <locale android:name="es-419"/> + <locale android:name="zh-Hans-SG"/> + </locale-config>)", + base_files_dir, &diag)); + const std::string base_apk = GetTestPath("base.apk"); + std::vector<std::string> link_args = { + "--manifest", + GetDefaultManifest("com.aapt2.app"), + "-o", + base_apk, + }; + ASSERT_TRUE(Link(link_args, base_files_dir, &diag)); + + const std::string localeconfig_manifest = GetTestPath("localeconfig_manifest.xml"); + const std::string out_apk = GetTestPath("out.apk"); + WriteFile(localeconfig_manifest, android::base::StringPrintf(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.aapt2.app"> + + <application + android:localeConfig="@xml/locales_config"> + </application> + </manifest>)")); + link_args = LinkCommandBuilder(this) + .SetManifestFile(localeconfig_manifest) + .AddParameter("-I", base_apk) + .Build(out_apk); + ASSERT_TRUE(Link(link_args, &diag)); +} + +TEST_F(LinkTest, LocaleConfigWrongTag) { + StdErrDiagnostics diag; + const std::string compiled_files_dir = GetTestPath("compiled"); + + // Invalid element: locale1-config + ASSERT_TRUE(CompileFile(GetTestPath("res/xml/wrong_locale_config.xml"), R"( + <locale1-config xmlns:android="http://schemas.android.com/apk/res/android"> + <locale android:name="en-US"/> + <locale android:name="pt"/> + <locale android:name="es-419"/> + <locale android:name="zh-Hans-SG"/> + </locale1-config>)", + compiled_files_dir, &diag)); + + const std::string locale1config_manifest = GetTestPath("locale1config_manifest.xml"); + WriteFile(locale1config_manifest, android::base::StringPrintf(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.aapt2.app"> + + <application + android:localeConfig="@xml/wrong_locale_config"> + </application> + </manifest>)")); + + const std::string out_apk = GetTestPath("out.apk"); + auto link_args = LinkCommandBuilder(this) + .SetManifestFile(locale1config_manifest) + .AddCompiledResDir(compiled_files_dir, &diag) + .Build(out_apk); + ASSERT_FALSE(Link(link_args, &diag)); + + // Invalid element: locale1 + ASSERT_TRUE(CompileFile(GetTestPath("res/xml/wrong_locale.xml"), R"( + <locale-config xmlns:android="http://schemas.android.com/apk/res/android"> + <locale1 android:name="en-US"/> + <locale android:name="pt"/> + <locale android:name="es-419"/> + <locale android:name="zh-Hans-SG"/> + </locale-config>)", + compiled_files_dir, &diag)); + + const std::string locale1_manifest = GetTestPath("locale1_manifest.xml"); + WriteFile(locale1_manifest, android::base::StringPrintf(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.aapt2.app"> + + <application + android:localeConfig="@xml/wrong_locale"> + </application> + </manifest>)")); + + auto link1_args = LinkCommandBuilder(this) + .SetManifestFile(locale1_manifest) + .AddCompiledResDir(compiled_files_dir, &diag) + .Build(out_apk); + ASSERT_FALSE(Link(link1_args, &diag)); + + // Invalid attribute: android:name1 + ASSERT_TRUE(CompileFile(GetTestPath("res/xml/wrong_attribute.xml"), R"( + <locale-config xmlns:android="http://schemas.android.com/apk/res/android"> + <locale android:name1="en-US"/> + <locale android:name="pt"/> + <locale android:name="es-419"/> + <locale android:name="zh-Hans-SG"/> + </locale-config>)", + compiled_files_dir, &diag)); + + const std::string wrong_attribute_manifest = GetTestPath("wrong_attribute_manifest.xml"); + WriteFile(wrong_attribute_manifest, android::base::StringPrintf(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.aapt2.app"> + + <application + android:localeConfig="@xml/wrong_attribute"> + </application> + </manifest>)")); + + auto link2_args = LinkCommandBuilder(this) + .SetManifestFile(wrong_attribute_manifest) + .AddCompiledResDir(compiled_files_dir, &diag) + .Build(out_apk); + ASSERT_FALSE(Link(link2_args, &diag)); +} + +TEST_F(LinkTest, LocaleConfigWrongLocaleFormat) { + StdErrDiagnostics diag; + const std::string compiled_files_dir = GetTestPath("compiled"); + + // Invalid locale: en-U + ASSERT_TRUE(CompileFile(GetTestPath("res/xml/wrong_locale.xml"), R"( + <locale-config xmlns:android="http://schemas.android.com/apk/res/android"> + <locale android:name="en-U"/> + <locale android:name="pt"/> + <locale android:name="es-419"/> + <locale android:name="zh-Hans-SG"/> + </locale-config>)", + compiled_files_dir, &diag)); + + const std::string wrong_locale_manifest = GetTestPath("wrong_locale_manifest.xml"); + WriteFile(wrong_locale_manifest, android::base::StringPrintf(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.aapt2.app"> + + <application + android:localeConfig="@xml/wrong_locale"> + </application> + </manifest>)")); + + const std::string out_apk = GetTestPath("out.apk"); + auto link_args = LinkCommandBuilder(this) + .SetManifestFile(wrong_locale_manifest) + .AddCompiledResDir(compiled_files_dir, &diag) + .Build(out_apk); + ASSERT_FALSE(Link(link_args, &diag)); +} + } // namespace aapt diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index caa3e60d6af1..dbe79701bf5c 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -16,21 +16,24 @@ #include "Optimize.h" +#include <map> #include <memory> +#include <set> +#include <string> +#include <utility> #include <vector> -#include "android-base/file.h" -#include "android-base/stringprintf.h" - -#include "androidfw/ConfigDescription.h" -#include "androidfw/ResourceTypes.h" -#include "androidfw/StringPiece.h" - #include "Diagnostics.h" #include "LoadedApk.h" #include "ResourceUtils.h" #include "SdkConstants.h" #include "ValueVisitor.h" +#include "android-base/file.h" +#include "android-base/stringprintf.h" +#include "androidfw/ConfigDescription.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/ResourceTypes.h" +#include "androidfw/StringPiece.h" #include "cmd/Util.h" #include "configuration/ConfigurationParser.h" #include "filter/AbiFilter.h" @@ -39,9 +42,9 @@ #include "io/BigBufferStream.h" #include "io/Util.h" #include "optimize/MultiApkGenerator.h" +#include "optimize/Obfuscator.h" #include "optimize/ResourceDeduper.h" #include "optimize/ResourceFilter.h" -#include "optimize/ResourcePathShortener.h" #include "optimize/VersionCollapser.h" #include "split/TableSplitter.h" #include "util/Files.h" @@ -69,7 +72,7 @@ class OptimizeContext : public IAaptContext { return PackageType::kApp; } - IDiagnostics* GetDiagnostics() override { + android::IDiagnostics* GetDiagnostics() override { return &diagnostics_; } @@ -115,11 +118,11 @@ class OptimizeContext : public IAaptContext { } private: - DISALLOW_COPY_AND_ASSIGN(OptimizeContext); - StdErrDiagnostics diagnostics_; bool verbose_ = false; int sdk_version_ = 0; + + DISALLOW_COPY_AND_ASSIGN(OptimizeContext); }; class Optimizer { @@ -130,12 +133,12 @@ class Optimizer { int Run(std::unique_ptr<LoadedApk> apk) { if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK..."); + context_->GetDiagnostics()->Note(android::DiagMessage() << "Optimizing APK..."); } if (!options_.resources_exclude_list.empty()) { ResourceFilter filter(options_.resources_exclude_list); if (!filter.Consume(context_, apk->GetResourceTable())) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed filtering resources"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed filtering resources"); return 1; } } @@ -147,20 +150,30 @@ class Optimizer { ResourceDeduper deduper; if (!deduper.Consume(context_, apk->GetResourceTable())) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources"); + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed deduping resources"); return 1; } - if (options_.shorten_resource_paths) { - ResourcePathShortener shortener(options_.table_flattener_options.shortened_path_map); - if (!shortener.Consume(context_, apk->GetResourceTable())) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed shortening resource paths"); + Obfuscator obfuscator(options_); + if (obfuscator.IsEnabled()) { + if (!obfuscator.Consume(context_, apk->GetResourceTable())) { + context_->GetDiagnostics()->Error(android::DiagMessage() + << "failed shortening resource paths"); + return 1; + } + + if (options_.obfuscation_map_path && + !obfuscator.WriteObfuscationMap(options_.obfuscation_map_path.value())) { + context_->GetDiagnostics()->Error(android::DiagMessage() + << "failed to write the obfuscation map to file"); return 1; } + + // TODO(b/246489170): keep the old option and format until transform to the new one if (options_.shortened_paths_map_path && !WriteShortenedPathsMap(options_.table_flattener_options.shortened_path_map, options_.shortened_paths_map_path.value())) { - context_->GetDiagnostics()->Error(DiagMessage() + context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to write shortened resource paths to file"); return 1; } @@ -183,9 +196,10 @@ class Optimizer { auto split_constraints_iter = options_.split_constraints.begin(); for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) { if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note( - DiagMessage(*path_iter) << "generating split with configurations '" - << util::Joiner(split_constraints_iter->configs, ", ") << "'"); + context_->GetDiagnostics()->Note(android::DiagMessage(*path_iter) + << "generating split with configurations '" + << util::Joiner(split_constraints_iter->configs, ", ") + << "'"); } // Generate an AndroidManifest.xml for each split. @@ -228,7 +242,7 @@ class Optimizer { private: bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) { - BigBuffer manifest_buffer(4096); + android::BigBuffer manifest_buffer(4096); XmlFlattener xml_flattener(&manifest_buffer, {}); if (!xml_flattener.Consume(context_, manifest)) { return false; @@ -254,8 +268,8 @@ class Optimizer { } if (file_ref->file == nullptr) { - ResourceNameRef name(pkg->name, type->type, entry->name); - context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource()) + ResourceNameRef name(pkg->name, type->named_type, entry->name); + context_->GetDiagnostics()->Warn(android::DiagMessage(file_ref->GetSource()) << "file for resource " << name << " with config '" << config_value->config << "' not found"); continue; @@ -276,7 +290,7 @@ class Optimizer { } } - BigBuffer table_buffer(4096); + android::BigBuffer table_buffer(4096); TableFlattener table_flattener(options_.table_flattener_options, &table_buffer); if (!table_flattener.Consume(context_, table)) { return false; @@ -287,6 +301,7 @@ class Optimizer { ArchiveEntry::kAlign, writer); } + // TODO(b/246489170): keep the old option and format until transform to the new one bool WriteShortenedPathsMap(const std::map<std::string, std::string> &path_map, const std::string &file_path) { std::stringstream ss; @@ -300,51 +315,15 @@ class Optimizer { OptimizeContext* context_; }; -bool ParseConfig(const std::string& content, IAaptContext* context, OptimizeOptions* options) { - size_t line_no = 0; - for (StringPiece line : util::Tokenize(content, '\n')) { - line_no++; - line = util::TrimWhitespace(line); - if (line.empty()) { - continue; - } - - auto split_line = util::Split(line, '#'); - if (split_line.size() < 2) { - context->GetDiagnostics()->Error(DiagMessage(line) << "No # found in line"); - return false; - } - StringPiece resource_string = split_line[0]; - StringPiece directives = split_line[1]; - ResourceNameRef resource_name; - if (!ResourceUtils::ParseResourceName(resource_string, &resource_name)) { - context->GetDiagnostics()->Error(DiagMessage(line) << "Malformed resource name"); - return false; - } - if (!resource_name.package.empty()) { - context->GetDiagnostics()->Error(DiagMessage(line) - << "Package set for resource. Only use type/name"); - return false; - } - for (StringPiece directive : util::Tokenize(directives, ',')) { - if (directive == "remove") { - options->resources_exclude_list.insert(resource_name.ToResourceName()); - } else if (directive == "no_collapse" || directive == "no_obfuscate") { - options->table_flattener_options.name_collapse_exemptions.insert( - resource_name.ToResourceName()); - } - } - } - return true; -} - bool ExtractConfig(const std::string& path, IAaptContext* context, OptimizeOptions* options) { std::string content; if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) { - context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading config file"); + context->GetDiagnostics()->Error(android::DiagMessage(path) << "failed reading config file"); return false; } - return ParseConfig(content, context, options); + return ParseResourceConfig(content, context, options->resources_exclude_list, + options->table_flattener_options.name_collapse_exemptions, + options->table_flattener_options.path_shorten_exemptions); } bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk, @@ -356,7 +335,7 @@ bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk, auto app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics()); if (!app_info) { - context->GetDiagnostics()->Error(DiagMessage() + context->GetDiagnostics()->Error(android::DiagMessage() << "failed to extract data from AndroidManifest.xml"); return false; } @@ -376,7 +355,7 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { const std::string& apk_path = args[0]; OptimizeContext context; context.SetVerbose(verbose_); - IDiagnostics* diag = context.GetDiagnostics(); + android::IDiagnostics* diag = context.GetDiagnostics(); if (config_path_) { std::string& path = config_path_.value(); @@ -384,12 +363,12 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { if (for_path) { options_.apk_artifacts = for_path.value().WithDiagnostics(diag).Parse(apk_path); if (!options_.apk_artifacts) { - diag->Error(DiagMessage() << "Failed to parse the output artifact list"); + diag->Error(android::DiagMessage() << "Failed to parse the output artifact list"); return 1; } } else { - diag->Error(DiagMessage() << "Could not parse config file " << path); + diag->Error(android::DiagMessage() << "Could not parse config file " << path); return 1; } @@ -402,8 +381,8 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { if (!kept_artifacts_.empty()) { for (const std::string& artifact_str : kept_artifacts_) { - for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) { - options_.kept_artifacts.insert(artifact.to_string()); + for (StringPiece artifact : util::Tokenize(artifact_str, ',')) { + options_.kept_artifacts.emplace(artifact); } } } @@ -411,11 +390,13 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { // Since we know that we are going to process the APK (not just print targets), make sure we // have somewhere to write them to. if (!options_.output_dir) { - diag->Error(DiagMessage() << "Output directory is required when using a configuration file"); + diag->Error(android::DiagMessage() + << "Output directory is required when using a configuration file"); return 1; } } else if (print_only_) { - diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations"); + diag->Error(android::DiagMessage() + << "Asked to print artifacts without providing a configurations"); return 1; } @@ -424,9 +405,16 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { return 1; } + if (options_.enable_sparse_encoding) { + options_.table_flattener_options.sparse_entries = SparseEntriesMode::Enabled; + } + if (options_.force_sparse_encoding) { + options_.table_flattener_options.sparse_entries = SparseEntriesMode::Forced; + } + if (target_densities_) { // Parse the target screen densities. - for (const StringPiece& config_str : util::Tokenize(target_densities_.value(), ',')) { + for (StringPiece config_str : util::Tokenize(target_densities_.value(), ',')) { std::optional<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag); if (!target_density) { return 1; diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h index ff63e8dd76d4..ee53af107b17 100644 --- a/tools/aapt2/cmd/Optimize.h +++ b/tools/aapt2/cmd/Optimize.h @@ -26,8 +26,6 @@ namespace aapt { struct OptimizeOptions { - friend class OptimizeCommand; - // Path to the output APK. std::optional<std::string> output_path; // Path to the output APK directory for splits. @@ -60,7 +58,17 @@ struct OptimizeOptions { bool shorten_resource_paths = false; // Path to the output map of original resource paths to shortened paths. + // TODO(b/246489170): keep the old option and format until transform to the new one std::optional<std::string> shortened_paths_map_path; + + // Whether sparse encoding should be used for O+ resources. + bool enable_sparse_encoding = false; + + // Whether sparse encoding should be used for all resources. + bool force_sparse_encoding = false; + + // Path to the output map of original resource paths/names to obfuscated paths/names. + std::optional<std::string> obfuscation_map_path; }; class OptimizeCommand : public Command { @@ -96,21 +104,42 @@ class OptimizeCommand : public Command { "Comma separated list of artifacts to keep. If none are specified,\n" "all artifacts will be kept.", &kept_artifacts_); - AddOptionalSwitch("--enable-sparse-encoding", + AddOptionalSwitch( + "--enable-sparse-encoding", "Enables encoding sparse entries using a binary search tree.\n" - "This decreases APK size at the cost of resource retrieval performance.", - &options_.table_flattener_options.use_sparse_entries); + "This decreases APK size at the cost of resource retrieval performance.\n" + "Only applies sparse encoding to Android O+ resources or all resources if minSdk of " + "the APK is O+", + &options_.enable_sparse_encoding); + AddOptionalSwitch("--force-sparse-encoding", + "Enables encoding sparse entries using a binary search tree.\n" + "This decreases APK size at the cost of resource retrieval performance.\n" + "Applies sparse encoding to all resources regardless of minSdk.", + &options_.force_sparse_encoding); AddOptionalSwitch("--collapse-resource-names", "Collapses resource names to a single value in the key string pool. Resources can \n" "be exempted using the \"no_collapse\" directive in a file specified by " "--resources-config-path.", &options_.table_flattener_options.collapse_key_stringpool); AddOptionalSwitch("--shorten-resource-paths", - "Shortens the paths of resources inside the APK.", + "Shortens the paths of resources inside the APK. Resources can be exempted using the \n" + "\"no_path_shorten\" directive in a file specified by --resources-config-path.", &options_.shorten_resource_paths); + // TODO(b/246489170): keep the old option and format until transform to the new one AddOptionalFlag("--resource-path-shortening-map", - "Path to output the map of old resource paths to shortened paths.", - &options_.shortened_paths_map_path); + "[Deprecated]Path to output the map of old resource paths to shortened paths.", + &options_.shortened_paths_map_path); + AddOptionalFlag("--save-obfuscation-map", + "Path to output the map of original paths/names to obfuscated paths/names.", + &options_.obfuscation_map_path); + AddOptionalSwitch( + "--deduplicate-entry-values", + "Whether to deduplicate pairs of resource entry and value for simple resources.\n" + "This is recommended to be used together with '--collapse-resource-names' flag or for\n" + "APKs where resource names are manually collapsed. For such APKs this flag allows to\n" + "store the same resource value only once in resource table which decreases APK size.\n" + "Has no effect on APKs where resource names are kept.", + &options_.table_flattener_options.deduplicate_entry_values); AddOptionalSwitch("-v", "Enables verbose logging", &verbose_); } diff --git a/tools/aapt2/cmd/Optimize_test.cpp b/tools/aapt2/cmd/Optimize_test.cpp deleted file mode 100644 index ac681e85b3d6..000000000000 --- a/tools/aapt2/cmd/Optimize_test.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2019 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 "Optimize.h" - -#include "AppInfo.h" -#include "Diagnostics.h" -#include "LoadedApk.h" -#include "Resource.h" -#include "test/Test.h" - -using testing::Contains; -using testing::Eq; - -namespace aapt { - -bool ParseConfig(const std::string&, IAaptContext*, OptimizeOptions*); - -using OptimizeTest = CommandTestFixture; - -TEST_F(OptimizeTest, ParseConfigWithNoCollapseExemptions) { - const std::string& content = R"( -string/foo#no_collapse -dimen/bar#no_collapse -)"; - aapt::test::Context context; - OptimizeOptions options; - ParseConfig(content, &context, &options); - - const std::set<ResourceName>& name_collapse_exemptions = - options.table_flattener_options.name_collapse_exemptions; - - ASSERT_THAT(name_collapse_exemptions.size(), Eq(2)); - EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo"))); - EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar"))); -} - -TEST_F(OptimizeTest, ParseConfigWithNoObfuscateExemptions) { - const std::string& content = R"( -string/foo#no_obfuscate -dimen/bar#no_obfuscate -)"; - aapt::test::Context context; - OptimizeOptions options; - ParseConfig(content, &context, &options); - - const std::set<ResourceName>& name_collapse_exemptions = - options.table_flattener_options.name_collapse_exemptions; - - ASSERT_THAT(name_collapse_exemptions.size(), Eq(2)); - EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo"))); - EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar"))); -} - -} // namespace aapt diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index 3244fb83fa4b..1671e1e1a6a1 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -34,10 +34,11 @@ using ::android::base::StringPrintf; namespace aapt { -std::optional<uint16_t> ParseTargetDensityParameter(const StringPiece& arg, IDiagnostics* diag) { +std::optional<uint16_t> ParseTargetDensityParameter(StringPiece arg, android::IDiagnostics* diag) { ConfigDescription preferred_density_config; if (!ConfigDescription::Parse(arg, &preferred_density_config)) { - diag->Error(DiagMessage() << "invalid density '" << arg << "' for --preferred-density option"); + diag->Error(android::DiagMessage() + << "invalid density '" << arg << "' for --preferred-density option"); return {}; } @@ -46,14 +47,14 @@ std::optional<uint16_t> ParseTargetDensityParameter(const StringPiece& arg, IDia if (preferred_density_config.diff(ConfigDescription::DefaultConfig()) != ConfigDescription::CONFIG_DENSITY) { - diag->Error(DiagMessage() << "invalid preferred density '" << arg << "'. " - << "Preferred density must only be a density value"); + diag->Error(android::DiagMessage() << "invalid preferred density '" << arg << "'. " + << "Preferred density must only be a density value"); return {}; } return preferred_density_config.density; } -bool ParseSplitParameter(const StringPiece& arg, IDiagnostics* diag, std::string* out_path, +bool ParseSplitParameter(StringPiece arg, android::IDiagnostics* diag, std::string* out_path, SplitConstraints* out_split) { CHECK(diag != nullptr); CHECK(out_path != nullptr); @@ -67,19 +68,19 @@ bool ParseSplitParameter(const StringPiece& arg, IDiagnostics* diag, std::string std::vector<std::string> parts = util::Split(arg, sSeparator); if (parts.size() != 2) { - diag->Error(DiagMessage() << "invalid split parameter '" << arg << "'"); - diag->Note(DiagMessage() << "should be --split path/to/output.apk" << sSeparator - << "<config>[,<config>...]."); + diag->Error(android::DiagMessage() << "invalid split parameter '" << arg << "'"); + diag->Note(android::DiagMessage() << "should be --split path/to/output.apk" << sSeparator + << "<config>[,<config>...]."); return false; } *out_path = parts[0]; out_split->name = parts[1]; - for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) { + for (StringPiece config_str : util::Tokenize(parts[1], ',')) { ConfigDescription config; if (!ConfigDescription::Parse(config_str, &config)) { - diag->Error(DiagMessage() << "invalid config '" << config_str << "' in split parameter '" - << arg << "'"); + diag->Error(android::DiagMessage() + << "invalid config '" << config_str << "' in split parameter '" << arg << "'"); return false; } out_split->configs.insert(config); @@ -88,21 +89,22 @@ bool ParseSplitParameter(const StringPiece& arg, IDiagnostics* diag, std::string } std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std::string>& args, - IDiagnostics* diag) { + android::IDiagnostics* diag) { std::unique_ptr<AxisConfigFilter> filter = util::make_unique<AxisConfigFilter>(); for (const std::string& config_arg : args) { - for (const StringPiece& config_str : util::Tokenize(config_arg, ',')) { + for (StringPiece config_str : util::Tokenize(config_arg, ',')) { ConfigDescription config; LocaleValue lv; if (lv.InitFromFilterString(config_str)) { lv.WriteTo(&config); } else if (!ConfigDescription::Parse(config_str, &config)) { - diag->Error(DiagMessage() << "invalid config '" << config_str << "' for -c option"); + diag->Error(android::DiagMessage() + << "invalid config '" << config_str << "' for -c option"); return {}; } if (config.density != 0) { - diag->Warn(DiagMessage() << "ignoring density '" << config << "' for -c option"); + diag->Warn(android::DiagMessage() << "ignoring density '" << config << "' for -c option"); } else { filter->AddConfig(config); } @@ -331,7 +333,7 @@ static std::optional<int> ExtractSdkVersion(const xml::Attribute& attr, std::str } std::optional<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, - IDiagnostics* diag) { + android::IDiagnostics* diag) { // Make sure the first element is <manifest> with package attribute. const xml::Element* manifest_el = xml_res.root.get(); if (manifest_el == nullptr) { @@ -341,20 +343,21 @@ std::optional<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& AppInfo app_info; if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") { - diag->Error(DiagMessage(xml_res.file.source) << "root tag must be <manifest>"); + diag->Error(android::DiagMessage(xml_res.file.source) << "root tag must be <manifest>"); return {}; } const xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package"); if (!package_attr) { - diag->Error(DiagMessage(xml_res.file.source) << "<manifest> must have a 'package' attribute"); + diag->Error(android::DiagMessage(xml_res.file.source) + << "<manifest> must have a 'package' attribute"); return {}; } std::string error_msg; std::optional<std::string> maybe_package = ExtractCompiledString(*package_attr, &error_msg); if (!maybe_package) { - diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) + diag->Error(android::DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) << "invalid package name: " << error_msg); return {}; } @@ -364,7 +367,7 @@ std::optional<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) { std::optional<uint32_t> maybe_code = ExtractCompiledInt(*version_code_attr, &error_msg); if (!maybe_code) { - diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) + diag->Error(android::DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) << "invalid android:versionCode: " << error_msg); return {}; } @@ -375,8 +378,8 @@ std::optional<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor")) { std::optional<uint32_t> maybe_code = ExtractCompiledInt(*version_code_major_attr, &error_msg); if (!maybe_code) { - diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) - << "invalid android:versionCodeMajor: " << error_msg); + diag->Error(android::DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) + << "invalid android:versionCodeMajor: " << error_msg); return {}; } app_info.version_code_major = maybe_code.value(); @@ -386,7 +389,7 @@ std::optional<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) { std::optional<uint32_t> maybe_code = ExtractCompiledInt(*revision_code_attr, &error_msg); if (!maybe_code) { - diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) + diag->Error(android::DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) << "invalid android:revisionCode: " << error_msg); return {}; } @@ -397,7 +400,7 @@ std::optional<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& std::optional<std::string> maybe_split_name = ExtractCompiledString(*split_name_attr, &error_msg); if (!maybe_split_name) { - diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) + diag->Error(android::DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number)) << "invalid split name: " << error_msg); return {}; } @@ -409,7 +412,7 @@ std::optional<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) { std::optional<int> maybe_sdk = ExtractSdkVersion(*min_sdk, &error_msg); if (!maybe_sdk) { - diag->Error(DiagMessage(xml_res.file.source.WithLine(uses_sdk_el->line_number)) + diag->Error(android::DiagMessage(xml_res.file.source.WithLine(uses_sdk_el->line_number)) << "invalid android:minSdkVersion: " << error_msg); return {}; } @@ -443,4 +446,44 @@ std::regex GetRegularExpression(const std::string &input) { return case_insensitive; } +bool ParseResourceConfig(const std::string& content, IAaptContext* context, + std::unordered_set<ResourceName>& out_resource_exclude_list, + std::set<ResourceName>& out_name_collapse_exemptions, + std::set<ResourceName>& out_path_shorten_exemptions) { + for (StringPiece line : util::Tokenize(content, '\n')) { + line = util::TrimWhitespace(line); + if (line.empty()) { + continue; + } + + auto split_line = util::Split(line, '#'); + if (split_line.size() < 2) { + context->GetDiagnostics()->Error(android::DiagMessage(line) << "No # found in line"); + return false; + } + StringPiece resource_string = split_line[0]; + StringPiece directives = split_line[1]; + ResourceNameRef resource_name; + if (!ResourceUtils::ParseResourceName(resource_string, &resource_name)) { + context->GetDiagnostics()->Error(android::DiagMessage(line) << "Malformed resource name"); + return false; + } + if (!resource_name.package.empty()) { + context->GetDiagnostics()->Error(android::DiagMessage(line) + << "Package set for resource. Only use type/name"); + return false; + } + for (StringPiece directive : util::Tokenize(directives, ',')) { + if (directive == "remove") { + out_resource_exclude_list.insert(resource_name.ToResourceName()); + } else if (directive == "no_collapse" || directive == "no_obfuscate") { + out_name_collapse_exemptions.insert(resource_name.ToResourceName()); + } else if (directive == "no_path_shorten") { + out_path_shorten_exemptions.insert(resource_name.ToResourceName()); + } + } + } + return true; +} + } // namespace aapt diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h index 1b98eb468700..712c07b71695 100644 --- a/tools/aapt2/cmd/Util.h +++ b/tools/aapt2/cmd/Util.h @@ -18,13 +18,15 @@ #define AAPT_SPLIT_UTIL_H #include <regex> - -#include "androidfw/StringPiece.h" +#include <set> +#include <unordered_set> #include "AppInfo.h" -#include "Diagnostics.h" #include "SdkConstants.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/StringPiece.h" #include "filter/ConfigFilter.h" +#include "process/IResourceTableConsumer.h" #include "split/TableSplitter.h" #include "xml/XmlDom.h" @@ -32,19 +34,19 @@ namespace aapt { // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc). // Returns Nothing and logs a human friendly error message if the string was not legal. -std::optional<uint16_t> ParseTargetDensityParameter(const android::StringPiece& arg, - IDiagnostics* diag); +std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg, + android::IDiagnostics* diag); // Parses a string of the form 'path/to/output.apk:<config>[,<config>...]' and fills in // `out_path` with the path and `out_split` with the set of ConfigDescriptions. // Returns false and logs a human friendly error message if the string was not legal. -bool ParseSplitParameter(const android::StringPiece& arg, IDiagnostics* diag, std::string* out_path, - SplitConstraints* out_split); +bool ParseSplitParameter(android::StringPiece arg, android::IDiagnostics* diag, + std::string* out_path, SplitConstraints* out_split); // Parses a set of config filter strings of the form 'en,fr-rFR' and returns an IConfigFilter. // Returns nullptr and logs a human friendly error message if the string was not legal. std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std::string>& args, - IDiagnostics* diag); + android::IDiagnostics* diag); // Adjust the SplitConstraints so that their SDK version is stripped if it // is less than or equal to the min_sdk. Otherwise the resources that have had @@ -60,7 +62,7 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, // Extracts relevant info from the AndroidManifest.xml. std::optional<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, - IDiagnostics* diag); + android::IDiagnostics* diag); // Returns a copy of 'name' which conforms to the regex '[a-zA-Z]+[a-zA-Z0-9_]*' by // replacing nonconforming characters with underscores. @@ -77,6 +79,11 @@ void SetLongVersionCode(xml::Element* manifest, uint64_t version_code); // Returns a case insensitive regular expression based on the input. std::regex GetRegularExpression(const std::string &input); +bool ParseResourceConfig(const std::string& content, IAaptContext* context, + std::unordered_set<ResourceName>& out_resource_exclude_list, + std::set<ResourceName>& out_name_collapse_exemptions, + std::set<ResourceName>& out_path_shorten_exemptions); + } // namespace aapt #endif /* AAPT_SPLIT_UTIL_H */ diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp index ac1f981d753c..139bfbcd0f41 100644 --- a/tools/aapt2/cmd/Util_test.cpp +++ b/tools/aapt2/cmd/Util_test.cpp @@ -25,6 +25,7 @@ #include "util/Files.h" using ::android::ConfigDescription; +using testing::UnorderedElementsAre; namespace aapt { @@ -102,7 +103,7 @@ TEST (UtilTest, LongVersionCodeUndefined) { TEST (UtilTest, ParseSplitParameters) { - IDiagnostics* diagnostics = test::ContextBuilder().Build().get()->GetDiagnostics(); + android::IDiagnostics* diagnostics = test::ContextBuilder().Build().get()->GetDiagnostics(); std::string path; SplitConstraints constraints; ConfigDescription expected_configuration; @@ -356,7 +357,7 @@ TEST (UtilTest, ParseSplitParameters) { TEST (UtilTest, AdjustSplitConstraintsForMinSdk) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - IDiagnostics* diagnostics = context.get()->GetDiagnostics(); + android::IDiagnostics* diagnostics = context.get()->GetDiagnostics(); std::vector<SplitConstraints> test_constraints; std::string path; @@ -411,4 +412,72 @@ TEST (UtilTest, RegularExpressionNonEnglish) { EXPECT_FALSE(std::regex_search("file.koncowka", expression)); } +TEST(UtilTest, ParseConfigWithDirectives) { + const std::string& content = R"( +bool/remove_me#remove +bool/keep_name#no_collapse +layout/keep_path#no_path_shorten +string/foo#no_obfuscate +dimen/bar#no_obfuscate +layout/keep_name_and_path#no_collapse,no_path_shorten +)"; + aapt::test::Context context; + std::unordered_set<ResourceName> resource_exclusion; + std::set<ResourceName> name_collapse_exemptions; + std::set<ResourceName> path_shorten_exemptions; + + EXPECT_TRUE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions, + path_shorten_exemptions)); + + EXPECT_THAT(name_collapse_exemptions, + UnorderedElementsAre(ResourceName({}, ResourceType::kString, "foo"), + ResourceName({}, ResourceType::kDimen, "bar"), + ResourceName({}, ResourceType::kBool, "keep_name"), + ResourceName({}, ResourceType::kLayout, "keep_name_and_path"))); + EXPECT_THAT(path_shorten_exemptions, + UnorderedElementsAre(ResourceName({}, ResourceType::kLayout, "keep_path"), + ResourceName({}, ResourceType::kLayout, "keep_name_and_path"))); + EXPECT_THAT(resource_exclusion, + UnorderedElementsAre(ResourceName({}, ResourceType::kBool, "remove_me"))); +} + +TEST(UtilTest, ParseConfigResourceWithPackage) { + const std::string& content = R"( +package:bool/remove_me#remove +)"; + aapt::test::Context context; + std::unordered_set<ResourceName> resource_exclusion; + std::set<ResourceName> name_collapse_exemptions; + std::set<ResourceName> path_shorten_exemptions; + + EXPECT_FALSE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions, + path_shorten_exemptions)); +} + +TEST(UtilTest, ParseConfigInvalidName) { + const std::string& content = R"( +package:bool/1231#remove +)"; + aapt::test::Context context; + std::unordered_set<ResourceName> resource_exclusion; + std::set<ResourceName> name_collapse_exemptions; + std::set<ResourceName> path_shorten_exemptions; + + EXPECT_FALSE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions, + path_shorten_exemptions)); +} + +TEST(UtilTest, ParseConfigNoHash) { + const std::string& content = R"( +package:bool/my_bool +)"; + aapt::test::Context context; + std::unordered_set<ResourceName> resource_exclusion; + std::set<ResourceName> name_collapse_exemptions; + std::set<ResourceName> path_shorten_exemptions; + + EXPECT_FALSE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions, + path_shorten_exemptions)); +} + } // namespace aapt diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp index fa816be43be3..b3f98a9d3e30 100644 --- a/tools/aapt2/compile/IdAssigner.cpp +++ b/tools/aapt2/compile/IdAssigner.cpp @@ -107,10 +107,10 @@ struct IdAssignerContext { // Returns whether the id was reserved successfully. // Reserving identifiers must be completed before `NextId` is called for the first time. bool ReserveId(const ResourceName& name, ResourceId id, const Visibility& visibility, - IDiagnostics* diag); + android::IDiagnostics* diag); // Retrieves the next available resource id that has not been reserved. - std::optional<ResourceId> NextId(const ResourceName& name, IDiagnostics* diag); + std::optional<ResourceId> NextId(const ResourceName& name, android::IDiagnostics* diag); private: std::string package_name_; @@ -128,7 +128,7 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) { for (auto& package : table->packages) { for (auto& type : package->types) { for (auto& entry : type->entries) { - const ResourceName name(package->name, type->type, entry->name); + const ResourceName name(package->name, type->named_type, entry->name); if (entry->id && !assigned_ids.ReserveId(name, entry->id.value(), entry->visibility, context->GetDiagnostics())) { return false; @@ -175,7 +175,7 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) { for (auto& package : table->packages) { for (auto& type : package->types) { for (auto& entry : type->entries) { - const ResourceName name(package->name, type->type, entry->name); + const ResourceName name(package->name, type->named_type, entry->name); if (entry->id) { continue; } @@ -267,11 +267,11 @@ Result<ResourceId> TypeGroup::NextId() { } bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id, - const Visibility& visibility, IDiagnostics* diag) { + const Visibility& visibility, android::IDiagnostics* diag) { if (package_id_ != id.package_id()) { - diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name - << " because package already has ID " << std::hex - << (int)id.package_id()); + diag->Error(android::DiagMessage() + << "can't assign ID " << id << " to resource " << name + << " because package already has ID " << std::hex << (int)id.package_id()); return false; } @@ -282,8 +282,8 @@ bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id, // another type. auto assign_result = type_id_finder_.ReserveId(key, id.type_id()); if (!assign_result.has_value()) { - diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name - << " because type " << assign_result.error()); + diag->Error(android::DiagMessage() << "can't assign ID " << id << " to resource " << name + << " because type " << assign_result.error()); return false; } type = types_.emplace(key, TypeGroup(package_id_, id.type_id())).first; @@ -293,24 +293,25 @@ bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id, // Ensure that non-staged resources can only exist in one type ID. auto non_staged_type = non_staged_type_ids_.emplace(name.type.type, id.type_id()); if (!non_staged_type.second && non_staged_type.first->second != id.type_id()) { - diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name - << " because type already has ID " << std::hex - << (int)id.type_id()); + diag->Error(android::DiagMessage() + << "can't assign ID " << id << " to resource " << name + << " because type already has ID " << std::hex << (int)id.type_id()); return false; } } auto assign_result = type->second.ReserveId(name, id); if (!assign_result.has_value()) { - diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name << " because " - << assign_result.error()); + diag->Error(android::DiagMessage() << "can't assign ID " << id << " to resource " << name + << " because " << assign_result.error()); return false; } return true; } -std::optional<ResourceId> IdAssignerContext::NextId(const ResourceName& name, IDiagnostics* diag) { +std::optional<ResourceId> IdAssignerContext::NextId(const ResourceName& name, + android::IDiagnostics* diag) { // The package name is not known during the compile stage. // Resources without a package name are considered a part of the app being linked. CHECK(name.package.empty() || name.package == package_name_); @@ -331,8 +332,8 @@ std::optional<ResourceId> IdAssignerContext::NextId(const ResourceName& name, ID auto assign_result = type->second.NextId(); if (!assign_result.has_value()) { - diag->Error(DiagMessage() << "can't assign resource ID to resource " << name << " because " - << assign_result.error()); + diag->Error(android::DiagMessage() << "can't assign resource ID to resource " << name + << " because " << assign_result.error()); return {}; } return assign_result.value(); diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp index d3575716ae4f..8911dad39470 100644 --- a/tools/aapt2/compile/IdAssigner_test.cpp +++ b/tools/aapt2/compile/IdAssigner_test.cpp @@ -191,12 +191,12 @@ TEST_F(IdAssignerTests, ExaustEntryIdsLastIdIsPublic) { for (auto& entry : type->entries) { if (!entry->id) { return ::testing::AssertionFailure() - << "resource " << ResourceNameRef(package->name, type->type, entry->name) + << "resource " << ResourceNameRef(package->name, type->named_type, entry->name) << " has no ID"; } if (!seen_ids.insert(entry->id.value()).second) { return ::testing::AssertionFailure() - << "resource " << ResourceNameRef(package->name, type->type, entry->name) + << "resource " << ResourceNameRef(package->name, type->named_type, entry->name) << " has a non-unique ID" << std::hex << entry->id.value() << std::dec; } } diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp index de1c3bb3dd7e..444f82109de4 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser.cpp +++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp @@ -47,19 +47,19 @@ class Visitor : public xml::PackageAwareVisitor { return; } - const Source src = xml_resource_->file.source.WithLine(el->line_number); + const android::Source src = xml_resource_->file.source.WithLine(el->line_number); xml::Attribute* attr = el->FindAttribute({}, "name"); if (!attr) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "missing 'name' attribute"); + context_->GetDiagnostics()->Error(android::DiagMessage(src) << "missing 'name' attribute"); error_ = true; return; } std::optional<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value); if (!ref) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "invalid XML attribute '" << attr->value - << "'"); + context_->GetDiagnostics()->Error(android::DiagMessage(src) + << "invalid XML attribute '" << attr->value << "'"); error_ = true; return; } @@ -67,7 +67,7 @@ class Visitor : public xml::PackageAwareVisitor { const ResourceName& name = ref.value().name.value(); std::optional<xml::ExtractedPackage> maybe_pkg = TransformPackageAlias(name.package); if (!maybe_pkg) { - context_->GetDiagnostics()->Error(DiagMessage(src) + context_->GetDiagnostics()->Error(android::DiagMessage(src) << "invalid namespace prefix '" << name.package << "'"); error_ = true; return; @@ -136,15 +136,15 @@ bool InlineXmlFormatParser::Consume(IAaptContext* context, xml::XmlResource* doc // Extracted elements must be the only child of <aapt:attr>. // Make sure there is one root node in the children (ignore empty text). for (std::unique_ptr<xml::Node>& child : decl.el->children) { - const Source child_source = doc->file.source.WithLine(child->line_number); + const android::Source child_source = doc->file.source.WithLine(child->line_number); if (xml::Text* t = xml::NodeCast<xml::Text>(child.get())) { if (!util::TrimWhitespace(t->text).empty()) { - context->GetDiagnostics()->Error(DiagMessage(child_source) + context->GetDiagnostics()->Error(android::DiagMessage(child_source) << "can't extract text into its own resource"); return false; } } else if (new_doc->root) { - context->GetDiagnostics()->Error(DiagMessage(child_source) + context->GetDiagnostics()->Error(android::DiagMessage(child_source) << "inline XML resources must have a single root"); return false; } else { @@ -160,7 +160,7 @@ bool InlineXmlFormatParser::Consume(IAaptContext* context, xml::XmlResource* doc // Get the parent element of <aapt:attr> xml::Element* parent_el = decl.el->parent; if (!parent_el) { - context->GetDiagnostics()->Error(DiagMessage(new_doc->file.source) + context->GetDiagnostics()->Error(android::DiagMessage(new_doc->file.source) << "no suitable parent for inheriting attribute"); return false; } diff --git a/tools/aapt2/compile/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp index c931da48c889..4538ecc56e4c 100644 --- a/tools/aapt2/compile/NinePatch.cpp +++ b/tools/aapt2/compile/NinePatch.cpp @@ -218,11 +218,9 @@ inline static uint32_t get_alpha(uint32_t color) { static bool PopulateBounds(const std::vector<Range>& padding, const std::vector<Range>& layout_bounds, - const std::vector<Range>& stretch_regions, - const int32_t length, int32_t* padding_start, - int32_t* padding_end, int32_t* layout_start, - int32_t* layout_end, const StringPiece& edge_name, - std::string* out_err) { + const std::vector<Range>& stretch_regions, const int32_t length, + int32_t* padding_start, int32_t* padding_end, int32_t* layout_start, + int32_t* layout_end, StringPiece edge_name, std::string* out_err) { if (padding.size() > 1) { std::stringstream err_stream; err_stream << "too many padding sections on " << edge_name << " border"; diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp index d396d81d699a..76db815129dd 100644 --- a/tools/aapt2/compile/Png.cpp +++ b/tools/aapt2/compile/Png.cpp @@ -24,11 +24,10 @@ #include <string> #include <vector> +#include "androidfw/BigBuffer.h" #include "androidfw/ResourceTypes.h" - -#include "Source.h" +#include "androidfw/Source.h" #include "trace/TraceBuffer.h" -#include "util/BigBuffer.h" #include "util/Util.h" namespace aapt { @@ -91,7 +90,7 @@ static void readDataFromStream(png_structp readPtr, png_bytep data, static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) { - BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr)); + android::BigBuffer* outBuffer = reinterpret_cast<android::BigBuffer*>(png_get_io_ptr(writePtr)); png_bytep buf = outBuffer->NextBlock<png_byte>(length); memcpy(buf, data, length); } @@ -99,15 +98,15 @@ static void writeDataToStream(png_structp writePtr, png_bytep data, static void flushDataToStream(png_structp /*writePtr*/) {} static void logWarning(png_structp readPtr, png_const_charp warningMessage) { - IDiagnostics* diag = - reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr)); - diag->Warn(DiagMessage() << warningMessage); + android::IDiagnostics* diag = + reinterpret_cast<android::IDiagnostics*>(png_get_error_ptr(readPtr)); + diag->Warn(android::DiagMessage() << warningMessage); } -static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, +static bool readPng(android::IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) { if (setjmp(png_jmpbuf(readPtr))) { - diag->Error(DiagMessage() << "failed reading png"); + diag->Error(android::DiagMessage() << "failed reading png"); return false; } @@ -245,10 +244,9 @@ PNG_COLOR_TYPE_RGB_ALPHA) { #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define ABS(a) ((a) < 0 ? -(a) : (a)) -static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, - int grayscaleTolerance, png_colorp rgbPalette, - png_bytep alphaPalette, int* paletteEntries, - bool* hasTransparency, int* colorType, +static void analyze_image(android::IDiagnostics* diag, const PngInfo& imageInfo, + int grayscaleTolerance, png_colorp rgbPalette, png_bytep alphaPalette, + int* paletteEntries, bool* hasTransparency, int* colorType, png_bytepp outRows) { int w = imageInfo.width; int h = imageInfo.height; @@ -383,8 +381,8 @@ static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, *colorType = PNG_COLOR_TYPE_PALETTE; } else { if (maxGrayDeviation <= grayscaleTolerance) { - diag->Note(DiagMessage() << "forcing image to gray (max deviation = " - << maxGrayDeviation << ")"); + diag->Note(android::DiagMessage() + << "forcing image to gray (max deviation = " << maxGrayDeviation << ")"); *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA; } else { *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; @@ -431,10 +429,10 @@ static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, } } -static bool writePng(IDiagnostics* diag, png_structp writePtr, - png_infop infoPtr, PngInfo* info, int grayScaleTolerance) { +static bool writePng(android::IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, + PngInfo* info, int grayScaleTolerance) { if (setjmp(png_jmpbuf(writePtr))) { - diag->Error(DiagMessage() << "failed to write png"); + diag->Error(android::DiagMessage() << "failed to write png"); return false; } @@ -463,8 +461,8 @@ static bool writePng(IDiagnostics* diag, png_structp writePtr, png_set_compression_level(writePtr, Z_BEST_COMPRESSION); if (kDebug) { - diag->Note(DiagMessage() << "writing image: w = " << info->width - << ", h = " << info->height); + diag->Note(android::DiagMessage() + << "writing image: w = " << info->width << ", h = " << info->height); } png_color rgbPalette[256]; @@ -486,24 +484,21 @@ static bool writePng(IDiagnostics* diag, png_structp writePtr, if (kDebug) { switch (colorType) { case PNG_COLOR_TYPE_PALETTE: - diag->Note(DiagMessage() << "has " << paletteEntries << " colors" - << (hasTransparency ? " (with alpha)" : "") - << ", using PNG_COLOR_TYPE_PALLETTE"); + diag->Note(android::DiagMessage() << "has " << paletteEntries << " colors" + << (hasTransparency ? " (with alpha)" : "") + << ", using PNG_COLOR_TYPE_PALLETTE"); break; case PNG_COLOR_TYPE_GRAY: - diag->Note(DiagMessage() - << "is opaque gray, using PNG_COLOR_TYPE_GRAY"); + diag->Note(android::DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY"); break; case PNG_COLOR_TYPE_GRAY_ALPHA: - diag->Note(DiagMessage() - << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA"); + diag->Note(android::DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA"); break; case PNG_COLOR_TYPE_RGB: - diag->Note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB"); + diag->Note(android::DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB"); break; case PNG_COLOR_TYPE_RGB_ALPHA: - diag->Note(DiagMessage() - << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA"); + diag->Note(android::DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA"); break; } } @@ -537,7 +532,7 @@ static bool writePng(IDiagnostics* diag, png_structp writePtr, // base 9 patch data if (kDebug) { - diag->Note(DiagMessage() << "adding 9-patch info.."); + diag->Note(android::DiagMessage() << "adding 9-patch info.."); } memcpy((char*)unknowns[pIndex].name, "npTc", 5); unknowns[pIndex].data = (png_byte*)info->serialize9Patch(); @@ -614,11 +609,10 @@ static bool writePng(IDiagnostics* diag, png_structp writePtr, &interlaceType, &compressionType, nullptr); if (kDebug) { - diag->Note(DiagMessage() << "image written: w = " << width - << ", h = " << height << ", d = " << bitDepth - << ", colors = " << colorType - << ", inter = " << interlaceType - << ", comp = " << compressionType); + diag->Note(android::DiagMessage() + << "image written: w = " << width << ", h = " << height << ", d = " << bitDepth + << ", colors = " << colorType << ", inter = " << interlaceType + << ", comp = " << compressionType); } return true; } @@ -1232,20 +1226,20 @@ getout: return true; } -bool Png::process(const Source& source, std::istream* input, - BigBuffer* outBuffer, const PngOptions& options) { +bool Png::process(const android::Source& source, std::istream* input, android::BigBuffer* outBuffer, + const PngOptions& options) { TRACE_CALL(); png_byte signature[kPngSignatureSize]; // Read the PNG signature first. if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) { - mDiag->Error(DiagMessage() << strerror(errno)); + mDiag->Error(android::DiagMessage() << strerror(errno)); return false; } // If the PNG signature doesn't match, bail early. if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { - mDiag->Error(DiagMessage() << "not a valid png file"); + mDiag->Error(android::DiagMessage() << "not a valid png file"); return false; } @@ -1258,13 +1252,13 @@ bool Png::process(const Source& source, std::istream* input, readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); if (!readPtr) { - mDiag->Error(DiagMessage() << "failed to allocate read ptr"); + mDiag->Error(android::DiagMessage() << "failed to allocate read ptr"); goto bail; } infoPtr = png_create_info_struct(readPtr); if (!infoPtr) { - mDiag->Error(DiagMessage() << "failed to allocate info ptr"); + mDiag->Error(android::DiagMessage() << "failed to allocate info ptr"); goto bail; } @@ -1281,7 +1275,7 @@ bool Png::process(const Source& source, std::istream* input, if (util::EndsWith(source.path, ".9.png")) { std::string errorMsg; if (!do9Patch(&pngInfo, &errorMsg)) { - mDiag->Error(DiagMessage() << errorMsg); + mDiag->Error(android::DiagMessage() << errorMsg); goto bail; } } @@ -1289,13 +1283,13 @@ bool Png::process(const Source& source, std::istream* input, writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); if (!writePtr) { - mDiag->Error(DiagMessage() << "failed to allocate write ptr"); + mDiag->Error(android::DiagMessage() << "failed to allocate write ptr"); goto bail; } writeInfoPtr = png_create_info_struct(writePtr); if (!writeInfoPtr) { - mDiag->Error(DiagMessage() << "failed to allocate write info ptr"); + mDiag->Error(android::DiagMessage() << "failed to allocate write info ptr"); goto bail; } diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h index 7ca1f0ec7800..a8b7dd18f12f 100644 --- a/tools/aapt2/compile/Png.h +++ b/tools/aapt2/compile/Png.h @@ -21,13 +21,12 @@ #include <string> #include "android-base/macros.h" - -#include "Diagnostics.h" -#include "Source.h" +#include "androidfw/BigBuffer.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/Source.h" #include "compile/Image.h" #include "io/Io.h" #include "process/IResourceTableConsumer.h" -#include "util/BigBuffer.h" namespace aapt { @@ -43,15 +42,16 @@ struct PngOptions { */ class Png { public: - explicit Png(IDiagnostics* diag) : mDiag(diag) {} + explicit Png(android::IDiagnostics* diag) : mDiag(diag) { + } - bool process(const Source& source, std::istream* input, BigBuffer* outBuffer, + bool process(const android::Source& source, std::istream* input, android::BigBuffer* outBuffer, const PngOptions& options); private: DISALLOW_COPY_AND_ASSIGN(Png); - IDiagnostics* mDiag; + android::IDiagnostics* mDiag; }; /** @@ -59,7 +59,7 @@ class Png { */ class PngChunkFilter : public io::InputStream { public: - explicit PngChunkFilter(const android::StringPiece& data); + explicit PngChunkFilter(android::StringPiece data); virtual ~PngChunkFilter() = default; bool Next(const void** buffer, size_t* len) override; @@ -90,7 +90,8 @@ class PngChunkFilter : public io::InputStream { /** * Reads a PNG from the InputStream into memory as an RGBA Image. */ -std::unique_ptr<Image> ReadPng(IAaptContext* context, const Source& source, io::InputStream* in); +std::unique_ptr<Image> ReadPng(IAaptContext* context, const android::Source& source, + io::InputStream* in); /** * Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp index 4db2392b4eab..2e55d0c82b7b 100644 --- a/tools/aapt2/compile/PngChunkFilter.cpp +++ b/tools/aapt2/compile/PngChunkFilter.cpp @@ -70,7 +70,7 @@ static bool IsPngChunkAllowed(uint32_t type) { } } -PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) { +PngChunkFilter::PngChunkFilter(StringPiece data) : data_(data) { if (util::StartsWith(data_, kPngSignature)) { window_start_ = 0; window_end_ = kPngSignatureSize; diff --git a/tools/aapt2/compile/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp index 1f4ea44d9f86..4ef87ba3671b 100644 --- a/tools/aapt2/compile/PngCrunch.cpp +++ b/tools/aapt2/compile/PngCrunch.cpp @@ -67,14 +67,14 @@ class PngWriteStructDeleter { // Custom warning logging method that uses IDiagnostics. static void LogWarning(png_structp png_ptr, png_const_charp warning_msg) { - IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(png_ptr); - diag->Warn(DiagMessage() << warning_msg); + android::IDiagnostics* diag = (android::IDiagnostics*)png_get_error_ptr(png_ptr); + diag->Warn(android::DiagMessage() << warning_msg); } // Custom error logging method that uses IDiagnostics. static void LogError(png_structp png_ptr, png_const_charp error_msg) { - IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(png_ptr); - diag->Error(DiagMessage() << error_msg); + android::IDiagnostics* diag = (android::IDiagnostics*)png_get_error_ptr(png_ptr); + diag->Error(android::DiagMessage() << error_msg); // Causes libpng to longjmp to the spot where setjmp was set. This is how libpng does // error handling. If this custom error handler method were to return, libpng would, by @@ -143,10 +143,11 @@ static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, png_size_t } } -std::unique_ptr<Image> ReadPng(IAaptContext* context, const Source& source, io::InputStream* in) { +std::unique_ptr<Image> ReadPng(IAaptContext* context, const android::Source& source, + io::InputStream* in) { TRACE_CALL(); // Create a diagnostics that has the source information encoded. - SourcePathDiagnostics source_diag(source, context->GetDiagnostics()); + android::SourcePathDiagnostics source_diag(source, context->GetDiagnostics()); // Read the first 8 bytes of the file looking for the PNG signature. // Bail early if it does not match. @@ -154,15 +155,16 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, const Source& source, io:: size_t buffer_size; if (!in->Next((const void**)&signature, &buffer_size)) { if (in->HadError()) { - source_diag.Error(DiagMessage() << "failed to read PNG signature: " << in->GetError()); + source_diag.Error(android::DiagMessage() + << "failed to read PNG signature: " << in->GetError()); } else { - source_diag.Error(DiagMessage() << "not enough data for PNG signature"); + source_diag.Error(android::DiagMessage() << "not enough data for PNG signature"); } return {}; } if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { - source_diag.Error(DiagMessage() << "file signature does not match PNG signature"); + source_diag.Error(android::DiagMessage() << "file signature does not match PNG signature"); return {}; } @@ -174,14 +176,14 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, const Source& source, io:: // version of libpng. png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (read_ptr == nullptr) { - source_diag.Error(DiagMessage() << "failed to create libpng read png_struct"); + source_diag.Error(android::DiagMessage() << "failed to create libpng read png_struct"); return {}; } // Create and initialize the memory for image header and data. png_infop info_ptr = png_create_info_struct(read_ptr); if (info_ptr == nullptr) { - source_diag.Error(DiagMessage() << "failed to create libpng read png_info"); + source_diag.Error(android::DiagMessage() << "failed to create libpng read png_info"); png_destroy_read_struct(&read_ptr, nullptr, nullptr); return {}; } @@ -254,7 +256,7 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, const Source& source, io:: // something // that can always be represented by 9-patch. if (width > std::numeric_limits<int32_t>::max() || height > std::numeric_limits<int32_t>::max()) { - source_diag.Error(DiagMessage() + source_diag.Error(android::DiagMessage() << "PNG image dimensions are too large: " << width << "x" << height); return {}; } @@ -490,14 +492,16 @@ bool WritePng(IAaptContext* context, const Image* image, // version of libpng. png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (write_ptr == nullptr) { - context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng write png_struct"); + context->GetDiagnostics()->Error(android::DiagMessage() + << "failed to create libpng write png_struct"); return false; } // Allocate memory to store image header data. png_infop write_info_ptr = png_create_info_struct(write_ptr); if (write_info_ptr == nullptr) { - context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng write png_info"); + context->GetDiagnostics()->Error(android::DiagMessage() + << "failed to create libpng write png_info"); png_destroy_write_struct(&write_ptr, nullptr); return false; } @@ -575,7 +579,7 @@ bool WritePng(IAaptContext* context, const Image* image, } if (context->IsVerbose()) { - DiagMessage msg; + android::DiagMessage msg; msg << " paletteSize=" << color_palette.size() << " alphaPaletteSize=" << alpha_palette.size() << " maxGrayDeviation=" << max_gray_deviation @@ -590,7 +594,7 @@ bool WritePng(IAaptContext* context, const Image* image, nine_patch != nullptr, color_palette.size(), alpha_palette.size()); if (context->IsVerbose()) { - DiagMessage msg; + android::DiagMessage msg; msg << "encoding PNG "; if (nine_patch) { msg << "(with 9-patch) as "; diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index 2461438c49b6..09a8560f984a 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -21,6 +21,7 @@ #include "ResourceTable.h" #include "ResourceValues.h" #include "ValueVisitor.h" +#include "androidfw/Util.h" #include "compile/Pseudolocalizer.h" #include "util/Util.h" @@ -53,7 +54,7 @@ inline static bool operator<(const UnifiedSpan& left, const UnifiedSpan& right) return false; } -inline static UnifiedSpan SpanToUnifiedSpan(const StringPool::Span& span) { +inline static UnifiedSpan SpanToUnifiedSpan(const android::StringPool::Span& span) { return UnifiedSpan{*span.name, span.first_char, span.last_char}; } @@ -111,7 +112,7 @@ static std::vector<UnifiedSpan> MergeSpans(const StyledString& string) { std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string, Pseudolocalizer::Method method, - StringPool* pool) { + android::StringPool* pool) { Pseudolocalizer localizer(method); // Collect the spans and untranslatable sections into one set of spans, sorted by first_char. @@ -121,7 +122,7 @@ std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string, // All Span indices are UTF-16 based, according to the resources.arsc format expected by the // runtime. So we will do all our processing in UTF-16, then convert back. - const std::u16string text16 = util::Utf8ToUtf16(string->value->value); + const std::u16string text16 = android::util::Utf8ToUtf16(string->value->value); // Convenient wrapper around the text that allows us to work with StringPieces. const StringPiece16 text(text16); @@ -154,7 +155,7 @@ std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string, cursor += substr.size(); // Pseudolocalize the substring. - std::string new_substr = util::Utf16ToUtf8(substr); + std::string new_substr = android::util::Utf16ToUtf8(substr); if (translatable) { new_substr = localizer.Text(new_substr); } @@ -181,7 +182,7 @@ std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string, cursor += substr.size(); // Pseudolocalize the substring. - std::string new_substr = util::Utf16ToUtf8(substr); + std::string new_substr = android::util::Utf16ToUtf8(substr); if (translatable) { new_substr = localizer.Text(new_substr); } @@ -199,16 +200,18 @@ std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string, } // Finish the pseudolocalization at the end of the string. - new_string += localizer.Text(util::Utf16ToUtf8(text.substr(cursor, text.size() - cursor))); + new_string += + localizer.Text(android::util::Utf16ToUtf8(text.substr(cursor, text.size() - cursor))); new_string += localizer.End(); - StyleString localized; + android::StyleString localized; localized.str = std::move(new_string); // Convert the UnifiedSpans into regular Spans, skipping the UntranslatableSections. for (UnifiedSpan& span : merged_spans) { if (span.tag) { - localized.spans.push_back(Span{std::move(span.tag.value()), span.first_char, span.last_char}); + localized.spans.push_back( + android::Span{std::move(span.tag.value()), span.first_char, span.last_char}); } } return util::make_unique<StyledString>(pool->MakeRef(localized)); @@ -222,8 +225,9 @@ class Visitor : public ValueVisitor { std::unique_ptr<Value> value; std::unique_ptr<Item> item; - Visitor(StringPool* pool, Pseudolocalizer::Method method) - : pool_(pool), method_(method), localizer_(method) {} + Visitor(android::StringPool* pool, Pseudolocalizer::Method method) + : pool_(pool), method_(method), localizer_(method) { + } void Visit(Plural* plural) override { CloningValueTransformer cloner(pool_); @@ -284,7 +288,7 @@ class Visitor : public ValueVisitor { private: DISALLOW_COPY_AND_ASSIGN(Visitor); - StringPool* pool_; + android::StringPool* pool_; Pseudolocalizer::Method method_; Pseudolocalizer localizer_; }; @@ -313,8 +317,8 @@ ConfigDescription ModifyConfigForPseudoLocale(const ConfigDescription& base, } void PseudolocalizeIfNeeded(const Pseudolocalizer::Method method, - ResourceConfigValue* original_value, - StringPool* pool, ResourceEntry* entry) { + ResourceConfigValue* original_value, android::StringPool* pool, + ResourceEntry* entry) { Visitor visitor(pool, method); original_value->value->Accept(&visitor); diff --git a/tools/aapt2/compile/PseudolocaleGenerator.h b/tools/aapt2/compile/PseudolocaleGenerator.h index ace378603f65..44e6e3e86f92 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.h +++ b/tools/aapt2/compile/PseudolocaleGenerator.h @@ -17,14 +17,15 @@ #ifndef AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H #define AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H -#include "StringPool.h" +#include "androidfw/StringPool.h" #include "compile/Pseudolocalizer.h" #include "process/IResourceTableConsumer.h" namespace aapt { -std::unique_ptr<StyledString> PseudolocalizeStyledString( - StyledString* string, Pseudolocalizer::Method method, StringPool* pool); +std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string, + Pseudolocalizer::Method method, + android::StringPool* pool); struct PseudolocaleGenerator : public IResourceTableConsumer { bool Consume(IAaptContext* context, ResourceTable* table) override; diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp index 432d7bfdad49..2f90cbf722c2 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp @@ -24,10 +24,11 @@ using ::android::ConfigDescription; namespace aapt { TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { - StringPool pool; - StyleString original_style; + android::StringPool pool; + android::StyleString original_style; original_style.str = "Hello world!"; - original_style.spans = {Span{"i", 1, 10}, Span{"b", 2, 3}, Span{"b", 6, 7}}; + original_style.spans = {android::Span{"i", 1, 10}, android::Span{"b", 2, 3}, + android::Span{"b", 6, 7}}; std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), @@ -48,7 +49,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { EXPECT_EQ(std::u16string(u"Hello ").size(), new_string->value->spans[2].first_char); EXPECT_EQ(std::u16string(u"Hello w").size(), new_string->value->spans[2].last_char); - original_style.spans.insert(original_style.spans.begin(), Span{"em", 0, 11u}); + original_style.spans.insert(original_style.spans.begin(), android::Span{"em", 0, 11u}); new_string = PseudolocalizeStyledString( util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), @@ -71,10 +72,10 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { } TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentNestedTags) { - StringPool pool; - StyleString original_style; + android::StringPool pool; + android::StyleString original_style; original_style.str = "bold"; - original_style.spans = {Span{"b", 0, 3}, Span{"i", 0, 3}}; + original_style.spans = {android::Span{"b", 0, 3}, android::Span{"i", 0, 3}}; std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), @@ -93,10 +94,10 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentNestedTags) { } TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentTagsUnsorted) { - StringPool pool; - StyleString original_style; + android::StringPool pool; + android::StyleString original_style; original_style.str = "bold"; - original_style.spans = {Span{"i", 2, 3}, Span{"b", 0, 1}}; + original_style.spans = {android::Span{"i", 2, 3}, android::Span{"b", 0, 1}}; std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), @@ -115,11 +116,11 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentTagsUnsorted) { } TEST(PseudolocaleGeneratorTest, PseudolocalizeNestedAndAdjacentTags) { - StringPool pool; - StyleString original_style; + android::StringPool pool; + android::StyleString original_style; original_style.str = "This sentence is not what you think it is at all."; - original_style.spans = {Span{"b", 16u, 19u}, Span{"em", 29u, 47u}, Span{"i", 38u, 40u}, - Span{"b", 44u, 47u}}; + original_style.spans = {android::Span{"b", 16u, 19u}, android::Span{"em", 29u, 47u}, + android::Span{"i", 38u, 40u}, android::Span{"b", 44u, 47u}}; std::unique_ptr<StyledString> new_string = PseudolocalizeStyledString( util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), @@ -154,10 +155,10 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeNestedAndAdjacentTags) { } TEST(PseudolocaleGeneratorTest, PseudolocalizePartsOfString) { - StringPool pool; - StyleString original_style; + android::StringPool pool; + android::StyleString original_style; original_style.str = "This should NOT be pseudolocalized."; - original_style.spans = {Span{"em", 4u, 14u}, Span{"i", 18u, 33u}}; + original_style.spans = {android::Span{"em", 4u, 14u}, android::Span{"i", 18u, 33u}}; std::unique_ptr<StyledString> original_string = util::make_unique<StyledString>(pool.MakeRef(original_style)); original_string->untranslatable_sections = {UntranslatableSection{11u, 15u}}; @@ -263,9 +264,10 @@ TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) { std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); { - StyleString original_style; + android::StyleString original_style; original_style.str = "Hello world!"; - original_style.spans = {Span{"i", 1, 10}, Span{"b", 2, 3}, Span{"b", 6, 7}}; + original_style.spans = {android::Span{"i", 1, 10}, android::Span{"b", 2, 3}, + android::Span{"b", 6, 7}}; auto styled_string = util::make_unique<StyledString>(table->string_pool.MakeRef(original_style)); diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp index 3a515fad3202..463ce787dae7 100644 --- a/tools/aapt2/compile/Pseudolocalizer.cpp +++ b/tools/aapt2/compile/Pseudolocalizer.cpp @@ -20,36 +20,42 @@ using android::StringPiece; +using namespace std::literals; + namespace aapt { // String basis to generate expansion -static const std::string kExpansionString = +static constexpr auto kExpansionString = "one two three " "four five six seven eight nine ten eleven twelve thirteen " - "fourteen fiveteen sixteen seventeen nineteen twenty"; + "fourteen fiveteen sixteen seventeen nineteen twenty"sv; // Special unicode characters to override directionality of the words -static const std::string kRlm = "\u200f"; -static const std::string kRlo = "\u202e"; -static const std::string kPdf = "\u202c"; +static constexpr auto kRlm = "\u200f"sv; +static constexpr auto kRlo = "\u202e"sv; +static constexpr auto kPdf = "\u202c"sv; // Placeholder marks -static const std::string kPlaceholderOpen = "\u00bb"; -static const std::string kPlaceholderClose = "\u00ab"; +static constexpr auto kPlaceholderOpen = "\u00bb"sv; +static constexpr auto kPlaceholderClose = "\u00ab"sv; static const char kArgStart = '{'; static const char kArgEnd = '}'; class PseudoMethodNone : public PseudoMethodImpl { public: - std::string Text(const StringPiece& text) override { return text.to_string(); } - std::string Placeholder(const StringPiece& text) override { return text.to_string(); } + std::string Text(StringPiece text) override { + return std::string(text); + } + std::string Placeholder(StringPiece text) override { + return std::string(text); + } }; class PseudoMethodBidi : public PseudoMethodImpl { public: - std::string Text(const StringPiece& text) override; - std::string Placeholder(const StringPiece& text) override; + std::string Text(StringPiece text) override; + std::string Placeholder(StringPiece text) override; }; class PseudoMethodAccent : public PseudoMethodImpl { @@ -57,8 +63,8 @@ class PseudoMethodAccent : public PseudoMethodImpl { PseudoMethodAccent() : depth_(0), word_count_(0), length_(0) {} std::string Start() override; std::string End() override; - std::string Text(const StringPiece& text) override; - std::string Placeholder(const StringPiece& text) override; + std::string Text(StringPiece text) override; + std::string Placeholder(StringPiece text) override; private: size_t depth_; @@ -84,7 +90,7 @@ void Pseudolocalizer::SetMethod(Method method) { } } -std::string Pseudolocalizer::Text(const StringPiece& text) { +std::string Pseudolocalizer::Text(StringPiece text) { std::string out; size_t depth = last_depth_; size_t lastpos, pos; @@ -116,7 +122,7 @@ std::string Pseudolocalizer::Text(const StringPiece& text) { } size_t size = nextpos - lastpos; if (size) { - std::string chunk = text.substr(lastpos, size).to_string(); + std::string chunk(text.substr(lastpos, size)); if (pseudo) { chunk = impl_->Text(chunk); } else if (str[lastpos] == kArgStart && str[nextpos - 1] == kArgEnd) { @@ -301,21 +307,23 @@ static bool IsPossibleNormalPlaceholderEnd(const char c) { } static std::string PseudoGenerateExpansion(const unsigned int length) { - std::string result = kExpansionString; - const char* s = result.data(); + std::string result(kExpansionString); if (result.size() < length) { result += " "; result += PseudoGenerateExpansion(length - result.size()); } else { int ext = 0; // Should contain only whole words, so looking for a space - for (unsigned int i = length + 1; i < result.size(); ++i) { - ++ext; - if (s[i] == ' ') { - break; + { + const char* const s = result.data(); + for (unsigned int i = length + 1; i < result.size(); ++i) { + ++ext; + if (s[i] == ' ') { + break; + } } } - result = result.substr(0, length + ext); + result.resize(length + ext); } return result; } @@ -349,7 +357,7 @@ std::string PseudoMethodAccent::End() { * * Note: This leaves placeholder syntax untouched. */ -std::string PseudoMethodAccent::Text(const StringPiece& source) { +std::string PseudoMethodAccent::Text(StringPiece source) { const char* s = source.data(); std::string result; const size_t I = source.size(); @@ -435,12 +443,12 @@ std::string PseudoMethodAccent::Text(const StringPiece& source) { return result; } -std::string PseudoMethodAccent::Placeholder(const StringPiece& source) { +std::string PseudoMethodAccent::Placeholder(StringPiece source) { // Surround a placeholder with brackets - return kPlaceholderOpen + source.to_string() + kPlaceholderClose; + return (std::string(kPlaceholderOpen) += source) += kPlaceholderClose; } -std::string PseudoMethodBidi::Text(const StringPiece& source) { +std::string PseudoMethodBidi::Text(StringPiece source) { const char* s = source.data(); std::string result; bool lastspace = true; @@ -456,10 +464,10 @@ std::string PseudoMethodBidi::Text(const StringPiece& source) { space = (!escape && isspace(c)) || (escape && (c == 'n' || c == 't')); if (lastspace && !space) { // Word start - result += kRlm + kRlo; + (result += kRlm) += kRlo; } else if (!lastspace && space) { // Word end - result += kPdf + kRlm; + (result += kPdf) += kRlm; } lastspace = space; if (escape) { @@ -470,14 +478,14 @@ std::string PseudoMethodBidi::Text(const StringPiece& source) { } if (!lastspace) { // End of last word - result += kPdf + kRlm; + (result += kPdf) += kRlm; } return result; } -std::string PseudoMethodBidi::Placeholder(const StringPiece& source) { +std::string PseudoMethodBidi::Placeholder(StringPiece source) { // Surround a placeholder with directionality change sequence - return kRlm + kRlo + source.to_string() + kPdf + kRlm; + return (((std::string(kRlm) += kRlo) += source) += kPdf) += kRlm; } } // namespace aapt diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h index 6cf003b24157..2b94bcc87fc9 100644 --- a/tools/aapt2/compile/Pseudolocalizer.h +++ b/tools/aapt2/compile/Pseudolocalizer.h @@ -19,11 +19,10 @@ #include <memory> +#include "ResourceValues.h" #include "android-base/macros.h" #include "androidfw/StringPiece.h" - -#include "ResourceValues.h" -#include "StringPool.h" +#include "androidfw/StringPool.h" namespace aapt { @@ -32,8 +31,8 @@ class PseudoMethodImpl { virtual ~PseudoMethodImpl() {} virtual std::string Start() { return {}; } virtual std::string End() { return {}; } - virtual std::string Text(const android::StringPiece& text) = 0; - virtual std::string Placeholder(const android::StringPiece& text) = 0; + virtual std::string Text(android::StringPiece text) = 0; + virtual std::string Placeholder(android::StringPiece text) = 0; }; class Pseudolocalizer { @@ -48,7 +47,7 @@ class Pseudolocalizer { void SetMethod(Method method); std::string Start() { return impl_->Start(); } std::string End() { return impl_->End(); } - std::string Text(const android::StringPiece& text); + std::string Text(android::StringPiece text); private: std::unique_ptr<PseudoMethodImpl> impl_; diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp index bb72159f9e77..68cb36a078dd 100644 --- a/tools/aapt2/compile/XmlIdCollector.cpp +++ b/tools/aapt2/compile/XmlIdCollector.cpp @@ -38,8 +38,9 @@ struct IdCollector : public xml::Visitor { using xml::Visitor::Visit; explicit IdCollector(std::vector<SourcedResourceName>* out_symbols, - SourcePathDiagnostics* source_diag) : out_symbols_(out_symbols), - source_diag_(source_diag) {} + android::SourcePathDiagnostics* source_diag) + : out_symbols_(out_symbols), source_diag_(source_diag) { + } void Visit(xml::Element* element) override { for (xml::Attribute& attr : element->attributes) { @@ -48,8 +49,8 @@ struct IdCollector : public xml::Visitor { if (ResourceUtils::ParseReference(attr.value, &name, &create, nullptr)) { if (create && name.type.type == ResourceType::kId) { if (!text::IsValidResourceEntryName(name.entry)) { - source_diag_->Error(DiagMessage(element->line_number) - << "id '" << name << "' has an invalid entry name"); + source_diag_->Error(android::DiagMessage(element->line_number) + << "id '" << name << "' has an invalid entry name"); } else { auto iter = std::lower_bound(out_symbols_->begin(), out_symbols_->end(), name, cmp_name); @@ -67,7 +68,7 @@ struct IdCollector : public xml::Visitor { private: std::vector<SourcedResourceName>* out_symbols_; - SourcePathDiagnostics* source_diag_; + android::SourcePathDiagnostics* source_diag_; }; } // namespace @@ -75,7 +76,7 @@ struct IdCollector : public xml::Visitor { bool XmlIdCollector::Consume(IAaptContext* context, xml::XmlResource* xmlRes) { TRACE_CALL(); xmlRes->file.exported_symbols.clear(); - SourcePathDiagnostics source_diag(xmlRes->file.source, context->GetDiagnostics()); + android::SourcePathDiagnostics source_diag(xmlRes->file.source, context->GetDiagnostics()); IdCollector collector(&xmlRes->file.exported_symbols, &source_diag); xmlRes->root->Accept(&collector); return !source_diag.HadError(); diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp index e7a45851e239..1b0325325778 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -23,12 +23,11 @@ #include <string> #include <utility> +#include "ResourceUtils.h" #include "android-base/file.h" #include "android-base/logging.h" #include "androidfw/ConfigDescription.h" - -#include "Diagnostics.h" -#include "ResourceUtils.h" +#include "androidfw/IDiagnostics.h" #include "configuration/ConfigurationParser.internal.h" #include "io/File.h" #include "io/FileSystem.h" @@ -88,15 +87,10 @@ const std::array<StringPiece, 8> kAbiToStringMap = { constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt"; -/** A default noop diagnostics context. */ -class NoopDiagnostics : public IDiagnostics { - public: - void Log(Level level, DiagMessageActual& actualMsg) override {} -}; -NoopDiagnostics noop_; +android::NoOpDiagnostics noop_; /** Returns the value of the label attribute for a given element. */ -std::string GetLabel(const Element* element, IDiagnostics* diag) { +std::string GetLabel(const Element* element, android::IDiagnostics* diag) { std::string label; for (const auto& attr : element->attributes) { if (attr.name == "label") { @@ -106,18 +100,18 @@ std::string GetLabel(const Element* element, IDiagnostics* diag) { } if (label.empty()) { - diag->Error(DiagMessage() << "No label found for element " << element->name); + diag->Error(android::DiagMessage() << "No label found for element " << element->name); } return label; } /** Returns the value of the version-code-order attribute for a given element. */ -std::optional<int32_t> GetVersionCodeOrder(const Element* element, IDiagnostics* diag) { +std::optional<int32_t> GetVersionCodeOrder(const Element* element, android::IDiagnostics* diag) { const xml::Attribute* version = element->FindAttribute("", "version-code-order"); if (version == nullptr) { std::string label = GetLabel(element, diag); - diag->Error(DiagMessage() << "No version-code-order found for element '" << element->name - << "' with label '" << label << "'"); + diag->Error(android::DiagMessage() << "No version-code-order found for element '" + << element->name << "' with label '" << label << "'"); return {}; } return std::stoi(version->value); @@ -158,15 +152,15 @@ bool CopyXmlReferences(const std::optional<std::string>& name, const Group<T>& g * success, or false if the either the placeholder is not found in the name, or the value is not * present and the placeholder was. */ -bool ReplacePlaceholder(const StringPiece& placeholder, const std::optional<StringPiece>& value, - std::string* name, IDiagnostics* diag) { +bool ReplacePlaceholder(StringPiece placeholder, const std::optional<StringPiece>& value, + std::string* name, android::IDiagnostics* diag) { size_t offset = name->find(placeholder.data()); bool found = (offset != std::string::npos); // Make sure the placeholder was present if the desired value is present. if (!found) { if (value) { - diag->Error(DiagMessage() << "Missing placeholder for artifact: " << placeholder); + diag->Error(android::DiagMessage() << "Missing placeholder for artifact: " << placeholder); return false; } return true; @@ -176,7 +170,8 @@ bool ReplacePlaceholder(const StringPiece& placeholder, const std::optional<Stri // Make sure the placeholder was not present if the desired value was not present. if (!value) { - diag->Error(DiagMessage() << "Placeholder present but no value for artifact: " << placeholder); + diag->Error(android::DiagMessage() + << "Placeholder present but no value for artifact: " << placeholder); return false; } @@ -184,7 +179,7 @@ bool ReplacePlaceholder(const StringPiece& placeholder, const std::optional<Stri // Make sure there was only one instance of the placeholder. if (name->find(placeholder.data()) != std::string::npos) { - diag->Error(DiagMessage() << "Placeholder present multiple times: " << placeholder); + diag->Error(android::DiagMessage() << "Placeholder present multiple times: " << placeholder); return false; } return true; @@ -195,12 +190,12 @@ bool ReplacePlaceholder(const StringPiece& placeholder, const std::optional<Stri * element was successfully processed, otherwise returns false. */ using ActionHandler = std::function<bool(configuration::PostProcessingConfiguration* config, - xml::Element* element, IDiagnostics* diag)>; + xml::Element* element, android::IDiagnostics* diag)>; /** Binds an ActionHandler to the current configuration being populated. */ xml::XmlNodeAction::ActionFuncWithDiag Bind(configuration::PostProcessingConfiguration* config, const ActionHandler& handler) { - return [config, handler](xml::Element* root_element, SourcePathDiagnostics* diag) { + return [config, handler](xml::Element* root_element, android::SourcePathDiagnostics* diag) { return handler(config, root_element, diag); }; } @@ -209,10 +204,10 @@ xml::XmlNodeAction::ActionFuncWithDiag Bind(configuration::PostProcessingConfigu std::optional<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact, const std::string& apk_name, const PostProcessingConfiguration& config, - IDiagnostics* diag) { + android::IDiagnostics* diag) { if (!artifact.name && !config.artifact_format) { - diag->Error( - DiagMessage() << "Artifact does not have a name and no global name template defined"); + diag->Error(android::DiagMessage() + << "Artifact does not have a name and no global name template defined"); return {}; } @@ -221,54 +216,54 @@ std::optional<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifac : artifact.ToArtifactName(config.artifact_format.value(), apk_name, diag); if (!artifact_name) { - diag->Error(DiagMessage() << "Could not determine split APK artifact name"); + diag->Error(android::DiagMessage() << "Could not determine split APK artifact name"); return {}; } OutputArtifact output_artifact; output_artifact.name = artifact_name.value(); - SourcePathDiagnostics src_diag{{output_artifact.name}, diag}; + android::SourcePathDiagnostics src_diag{{output_artifact.name}, diag}; bool has_errors = false; if (!CopyXmlReferences(artifact.abi_group, config.abi_groups, &output_artifact.abis)) { - src_diag.Error(DiagMessage() << "Could not lookup required ABIs: " - << artifact.abi_group.value()); + src_diag.Error(android::DiagMessage() + << "Could not lookup required ABIs: " << artifact.abi_group.value()); has_errors = true; } if (!CopyXmlReferences(artifact.locale_group, config.locale_groups, &output_artifact.locales)) { - src_diag.Error(DiagMessage() << "Could not lookup required locales: " - << artifact.locale_group.value()); + src_diag.Error(android::DiagMessage() + << "Could not lookup required locales: " << artifact.locale_group.value()); has_errors = true; } if (!CopyXmlReferences(artifact.screen_density_group, config.screen_density_groups, &output_artifact.screen_densities)) { - src_diag.Error(DiagMessage() << "Could not lookup required screen densities: " - << artifact.screen_density_group.value()); + src_diag.Error(android::DiagMessage() << "Could not lookup required screen densities: " + << artifact.screen_density_group.value()); has_errors = true; } if (!CopyXmlReferences(artifact.device_feature_group, config.device_feature_groups, &output_artifact.features)) { - src_diag.Error(DiagMessage() << "Could not lookup required device features: " - << artifact.device_feature_group.value()); + src_diag.Error(android::DiagMessage() << "Could not lookup required device features: " + << artifact.device_feature_group.value()); has_errors = true; } if (!CopyXmlReferences(artifact.gl_texture_group, config.gl_texture_groups, &output_artifact.textures)) { - src_diag.Error(DiagMessage() << "Could not lookup required OpenGL texture formats: " - << artifact.gl_texture_group.value()); + src_diag.Error(android::DiagMessage() << "Could not lookup required OpenGL texture formats: " + << artifact.gl_texture_group.value()); has_errors = true; } if (artifact.android_sdk) { auto entry = config.android_sdks.find(artifact.android_sdk.value()); if (entry == config.android_sdks.end()) { - src_diag.Error(DiagMessage() << "Could not lookup required Android SDK version: " - << artifact.android_sdk.value()); + src_diag.Error(android::DiagMessage() << "Could not lookup required Android SDK version: " + << artifact.android_sdk.value()); has_errors = true; } else { output_artifact.android_sdk = {entry->second}; @@ -288,9 +283,9 @@ namespace configuration { /** Returns the binary reprasentation of the XML configuration. */ std::optional<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents, const std::string& config_path, - IDiagnostics* diag) { + android::IDiagnostics* diag) { StringInputStream in(contents); - std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source(config_path)); + std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, android::Source(config_path)); if (!doc) { return {}; } @@ -298,14 +293,14 @@ std::optional<PostProcessingConfiguration> ExtractConfiguration(const std::strin // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace. Element* root = doc->root.get(); if (root == nullptr) { - diag->Error(DiagMessage() << "Could not find the root element in the XML document"); + diag->Error(android::DiagMessage() << "Could not find the root element in the XML document"); return {}; } std::string& xml_ns = root->namespace_uri; if (!xml_ns.empty()) { if (xml_ns != kAaptXmlNs) { - diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns); + diag->Error(android::DiagMessage() << "Unknown namespace found on root element: " << xml_ns); return {}; } @@ -336,24 +331,24 @@ std::optional<PostProcessingConfiguration> ExtractConfiguration(const std::strin Bind(&config, DeviceFeatureGroupTagHandler)); if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) { - diag->Error(DiagMessage() << "Could not process XML document"); + diag->Error(android::DiagMessage() << "Could not process XML document"); return {}; } return {config}; } -const StringPiece& AbiToString(Abi abi) { +StringPiece AbiToString(Abi abi) { return kAbiToStringMap.at(static_cast<size_t>(abi)); } /** * Returns the common artifact base name from a template string. */ -std::optional<std::string> ToBaseName(std::string result, const StringPiece& apk_name, - IDiagnostics* diag) { +std::optional<std::string> ToBaseName(std::string result, StringPiece apk_name, + android::IDiagnostics* diag) { const StringPiece ext = file::GetExtension(apk_name); - size_t end_index = apk_name.to_string().rfind(ext.to_string()); + size_t end_index = apk_name.rfind(ext); const std::string base_name = (end_index != std::string::npos) ? std::string{apk_name.begin(), end_index} : ""; @@ -376,17 +371,17 @@ std::optional<std::string> ToBaseName(std::string result, const StringPiece& apk // If no extension is specified, and the name template does not end in the current extension, // add the existing extension. if (!util::EndsWith(result, ext)) { - result.append(ext.to_string()); + result.append(ext); } } return result; } -std::optional<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format, - const StringPiece& apk_name, - IDiagnostics* diag) const { - std::optional<std::string> base = ToBaseName(format.to_string(), apk_name, diag); +std::optional<std::string> ConfiguredArtifact::ToArtifactName(StringPiece format, + StringPiece apk_name, + android::IDiagnostics* diag) const { + std::optional<std::string> base = ToBaseName(std::string(format), apk_name, diag); if (!base) { return {}; } @@ -419,8 +414,8 @@ std::optional<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& return result; } -std::optional<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name, - IDiagnostics* diag) const { +std::optional<std::string> ConfiguredArtifact::Name(StringPiece apk_name, + android::IDiagnostics* diag) const { if (!name) { return {}; } @@ -444,7 +439,7 @@ ConfigurationParser::ConfigurationParser(std::string contents, const std::string } std::optional<std::vector<OutputArtifact>> ConfigurationParser::Parse( - const android::StringPiece& apk_path) { + android::StringPiece apk_path) { std::optional<PostProcessingConfiguration> maybe_config = ExtractConfiguration(contents_, config_path_, diag_); if (!maybe_config) { @@ -452,7 +447,7 @@ std::optional<std::vector<OutputArtifact>> ConfigurationParser::Parse( } // Convert from a parsed configuration to a list of artifacts for processing. - const std::string& apk_name = file::GetFilename(apk_path).to_string(); + const std::string apk_name(file::GetFilename(apk_path)); std::vector<OutputArtifact> output_artifacts; PostProcessingConfiguration& config = maybe_config.value(); @@ -473,7 +468,7 @@ std::optional<std::vector<OutputArtifact>> ConfigurationParser::Parse( } if (!config.ValidateVersionCodeOrdering(diag_)) { - diag_->Error(DiagMessage() << "could not validate post processing configuration"); + diag_->Error(android::DiagMessage() << "could not validate post processing configuration"); valid = false; } @@ -493,7 +488,7 @@ namespace configuration { namespace handler { bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_element, - IDiagnostics* diag) { + android::IDiagnostics* diag) { ConfiguredArtifact artifact{}; for (const auto& attr : root_element->attributes) { if (attr.name == "name") { @@ -511,8 +506,8 @@ bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_eleme } else if (attr.name == "device-feature-group") { artifact.device_feature_group = {attr.value}; } else { - diag->Note(DiagMessage() << "Unknown artifact attribute: " << attr.name << " = " - << attr.value); + diag->Note(android::DiagMessage() + << "Unknown artifact attribute: " << attr.name << " = " << attr.value); } } config->artifacts.push_back(artifact); @@ -520,11 +515,11 @@ bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_eleme }; bool ArtifactFormatTagHandler(PostProcessingConfiguration* config, Element* root_element, - IDiagnostics* /* diag */) { + android::IDiagnostics* /* diag */) { for (auto& node : root_element->children) { xml::Text* t; if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - config->artifact_format = TrimWhitespace(t->text).to_string(); + config->artifact_format.emplace(TrimWhitespace(t->text)); break; } } @@ -532,7 +527,7 @@ bool ArtifactFormatTagHandler(PostProcessingConfiguration* config, Element* root }; bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_element, - IDiagnostics* diag) { + android::IDiagnostics* diag) { std::string label = GetLabel(root_element, diag); if (label.empty()) { return false; @@ -560,17 +555,17 @@ bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_eleme for (auto* child : root_element->GetChildElements()) { if (child->name != "abi") { - diag->Error(DiagMessage() << "Unexpected element in ABI group: " << child->name); + diag->Error(android::DiagMessage() << "Unexpected element in ABI group: " << child->name); valid = false; } else { for (auto& node : child->children) { xml::Text* t; if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string()); + auto abi = kStringToAbiMap.find(TrimWhitespace(t->text)); if (abi != kStringToAbiMap.end()) { group.push_back(abi->second); } else { - diag->Error(DiagMessage() << "Could not parse ABI value: " << t->text); + diag->Error(android::DiagMessage() << "Could not parse ABI value: " << t->text); valid = false; } break; @@ -583,7 +578,7 @@ bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_eleme }; bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* root_element, - IDiagnostics* diag) { + android::IDiagnostics* diag) { std::string label = GetLabel(root_element, diag); if (label.empty()) { return false; @@ -609,9 +604,8 @@ bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* // Copy the density with the minimum SDK version stripped out. group.push_back(config_descriptor.CopyWithoutSdkVersion()); } else { - diag->Error(DiagMessage() - << "Could not parse config descriptor for empty screen-density-group: " - << label); + diag->Error(android::DiagMessage() + << "Could not parse config descriptor for empty screen-density-group: " << label); valid = false; } @@ -620,15 +614,15 @@ bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* for (auto* child : root_element->GetChildElements()) { if (child->name != "screen-density") { - diag->Error(DiagMessage() << "Unexpected root_element in screen density group: " - << child->name); + diag->Error(android::DiagMessage() + << "Unexpected root_element in screen density group: " << child->name); valid = false; } else { for (auto& node : child->children) { xml::Text* t; if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { ConfigDescription config_descriptor; - const android::StringPiece& text = TrimWhitespace(t->text); + android::StringPiece text = TrimWhitespace(t->text); bool parsed = ConfigDescription::Parse(text, &config_descriptor); if (parsed && (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == @@ -636,7 +630,7 @@ bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* // Copy the density with the minimum SDK version stripped out. group.push_back(config_descriptor.CopyWithoutSdkVersion()); } else { - diag->Error(DiagMessage() + diag->Error(android::DiagMessage() << "Could not parse config descriptor for screen-density: " << text); valid = false; } @@ -650,7 +644,7 @@ bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* }; bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_element, - IDiagnostics* diag) { + android::IDiagnostics* diag) { std::string label = GetLabel(root_element, diag); if (label.empty()) { return false; @@ -676,9 +670,8 @@ bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_el // Copy the locale with the minimum SDK version stripped out. group.push_back(config_descriptor.CopyWithoutSdkVersion()); } else { - diag->Error(DiagMessage() - << "Could not parse config descriptor for empty screen-density-group: " - << label); + diag->Error(android::DiagMessage() + << "Could not parse config descriptor for empty screen-density-group: " << label); valid = false; } @@ -687,15 +680,15 @@ bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_el for (auto* child : root_element->GetChildElements()) { if (child->name != "locale") { - diag->Error(DiagMessage() << "Unexpected root_element in screen density group: " - << child->name); + diag->Error(android::DiagMessage() + << "Unexpected root_element in screen density group: " << child->name); valid = false; } else { for (auto& node : child->children) { xml::Text* t; if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { ConfigDescription config_descriptor; - const android::StringPiece& text = TrimWhitespace(t->text); + android::StringPiece text = TrimWhitespace(t->text); bool parsed = ConfigDescription::Parse(text, &config_descriptor); if (parsed && (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == @@ -703,7 +696,7 @@ bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_el // Copy the locale with the minimum SDK version stripped out. group.push_back(config_descriptor.CopyWithoutSdkVersion()); } else { - diag->Error(DiagMessage() + diag->Error(android::DiagMessage() << "Could not parse config descriptor for screen-density: " << text); valid = false; } @@ -717,7 +710,7 @@ bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_el }; bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_element, - IDiagnostics* diag) { + android::IDiagnostics* diag) { AndroidSdk entry = AndroidSdk::ForMinSdk(-1); bool valid = true; for (const auto& attr : root_element->attributes) { @@ -746,13 +739,14 @@ bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_ele } if (!valid_attr) { - diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value); + diag->Error(android::DiagMessage() + << "Invalid attribute: " << attr.name << " = " << attr.value); valid = false; } } if (entry.min_sdk_version == -1) { - diag->Error(DiagMessage() << "android-sdk is missing minSdkVersion attribute"); + diag->Error(android::DiagMessage() << "android-sdk is missing minSdkVersion attribute"); valid = false; } @@ -760,7 +754,7 @@ bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_ele for (auto node : root_element->GetChildElements()) { if (node->name == "manifest") { if (entry.manifest) { - diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates."); + diag->Warn(android::DiagMessage() << "Found multiple manifest tags. Ignoring duplicates."); continue; } entry.manifest = {AndroidManifest()}; @@ -772,7 +766,7 @@ bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_ele }; bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root_element, - IDiagnostics* diag) { + android::IDiagnostics* diag) { std::string label = GetLabel(root_element, diag); if (label.empty()) { return false; @@ -791,7 +785,8 @@ bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root GlTexture result; for (auto* child : root_element->GetChildElements()) { if (child->name != "gl-texture") { - diag->Error(DiagMessage() << "Unexpected element in GL texture group: " << child->name); + diag->Error(android::DiagMessage() + << "Unexpected element in GL texture group: " << child->name); valid = false; } else { for (const auto& attr : child->attributes) { @@ -803,14 +798,15 @@ bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root for (auto* element : child->GetChildElements()) { if (element->name != "texture-path") { - diag->Error(DiagMessage() << "Unexpected element in gl-texture element: " << child->name); + diag->Error(android::DiagMessage() + << "Unexpected element in gl-texture element: " << child->name); valid = false; continue; } for (auto& node : element->children) { xml::Text* t; if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - result.texture_paths.push_back(TrimWhitespace(t->text).to_string()); + result.texture_paths.emplace_back(TrimWhitespace(t->text)); } } } @@ -822,7 +818,7 @@ bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root }; bool DeviceFeatureGroupTagHandler(PostProcessingConfiguration* config, Element* root_element, - IDiagnostics* diag) { + android::IDiagnostics* diag) { std::string label = GetLabel(root_element, diag); if (label.empty()) { return false; @@ -840,14 +836,14 @@ bool DeviceFeatureGroupTagHandler(PostProcessingConfiguration* config, Element* for (auto* child : root_element->GetChildElements()) { if (child->name != "supports-feature") { - diag->Error(DiagMessage() << "Unexpected root_element in device feature group: " - << child->name); + diag->Error(android::DiagMessage() + << "Unexpected root_element in device feature group: " << child->name); valid = false; } else { for (auto& node : child->children) { xml::Text* t; if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - group.push_back(TrimWhitespace(t->text).to_string()); + group.emplace_back(TrimWhitespace(t->text)); break; } } diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h index 195b4baac319..d66f4ab000a3 100644 --- a/tools/aapt2/configuration/ConfigurationParser.h +++ b/tools/aapt2/configuration/ConfigurationParser.h @@ -24,8 +24,7 @@ #include <vector> #include "androidfw/ConfigDescription.h" - -#include "Diagnostics.h" +#include "androidfw/IDiagnostics.h" namespace aapt { @@ -44,7 +43,7 @@ enum class Abi { }; /** Helper method to convert an ABI to a string representing the path within the APK. */ -const android::StringPiece& AbiToString(Abi abi); +android::StringPiece AbiToString(Abi abi); /** * Represents an individual locale. When a locale is included, it must be @@ -126,9 +125,6 @@ struct OutputArtifact { } // namespace configuration -// Forward declaration of classes used in the API. -struct IDiagnostics; - /** * XML configuration file parser for the split and optimize commands. */ @@ -145,7 +141,7 @@ class ConfigurationParser { } /** Sets the diagnostics context to use when parsing. */ - ConfigurationParser& WithDiagnostics(IDiagnostics* diagnostics) { + ConfigurationParser& WithDiagnostics(android::IDiagnostics* diagnostics) { diag_ = diagnostics; return *this; } @@ -154,8 +150,7 @@ class ConfigurationParser { * Parses the configuration file and returns the results. If the configuration could not be parsed * the result is empty and any errors will be displayed with the provided diagnostics context. */ - std::optional<std::vector<configuration::OutputArtifact>> Parse( - const android::StringPiece& apk_path); + std::optional<std::vector<configuration::OutputArtifact>> Parse(android::StringPiece apk_path); protected: /** @@ -166,7 +161,7 @@ class ConfigurationParser { ConfigurationParser(std::string contents, const std::string& config_path); /** Returns the current diagnostics context to any subclasses. */ - IDiagnostics* diagnostics() { + android::IDiagnostics* diagnostics() { return diag_; } @@ -176,7 +171,7 @@ class ConfigurationParser { /** Path to the input configuration. */ const std::string config_path_; /** The diagnostics context to send messages to. */ - IDiagnostics* diag_; + android::IDiagnostics* diag_; }; } // namespace aapt diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h index 42ef51591d4f..198f730f1e12 100644 --- a/tools/aapt2/configuration/ConfigurationParser.internal.h +++ b/tools/aapt2/configuration/ConfigurationParser.internal.h @@ -47,15 +47,16 @@ using Entry = std::unordered_map<std::string, T>; template <class T> using Group = Entry<OrderedEntry<T>>; -template<typename T> -bool IsGroupValid(const Group<T>& group, const std::string& name, IDiagnostics* diag) { +template <typename T> +bool IsGroupValid(const Group<T>& group, const std::string& name, android::IDiagnostics* diag) { std::set<int32_t> orders; for (const auto& p : group) { orders.insert(p.second.order); } bool valid = orders.size() == group.size(); if (!valid) { - diag->Error(DiagMessage() << name << " have overlapping version-code-order attributes"); + diag->Error(android::DiagMessage() + << name << " have overlapping version-code-order attributes"); } return valid; } @@ -137,12 +138,12 @@ struct ConfiguredArtifact { std::optional<std::string> gl_texture_group; /** Convert an artifact name template into a name string based on configuration contents. */ - std::optional<std::string> ToArtifactName(const android::StringPiece& format, - const android::StringPiece& apk_name, - IDiagnostics* diag) const; + std::optional<std::string> ToArtifactName(android::StringPiece format, + android::StringPiece apk_name, + android::IDiagnostics* diag) const; /** Convert an artifact name template into a name string based on configuration contents. */ - std::optional<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const; + std::optional<std::string> Name(android::StringPiece apk_name, android::IDiagnostics* diag) const; }; /** AAPT2 XML configuration file binary representation. */ @@ -157,7 +158,7 @@ struct PostProcessingConfiguration { Group<GlTexture> gl_texture_groups; Entry<AndroidSdk> android_sdks; - bool ValidateVersionCodeOrdering(IDiagnostics* diag) { + bool ValidateVersionCodeOrdering(android::IDiagnostics* diag) { bool valid = IsGroupValid(abi_groups, "abi-groups", diag); valid &= IsGroupValid(screen_density_groups, "screen-density-groups", diag); valid &= IsGroupValid(locale_groups, "locale-groups", diag); @@ -215,41 +216,41 @@ struct PostProcessingConfiguration { /** Parses the provided XML document returning the post processing configuration. */ std::optional<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents, const std::string& config_path, - IDiagnostics* diag); + android::IDiagnostics* diag); namespace handler { /** Handler for <artifact> tags. */ bool ArtifactTagHandler(configuration::PostProcessingConfiguration* config, xml::Element* element, - IDiagnostics* diag); + android::IDiagnostics* diag); /** Handler for <artifact-format> tags. */ bool ArtifactFormatTagHandler(configuration::PostProcessingConfiguration* config, - xml::Element* element, IDiagnostics* diag); + xml::Element* element, android::IDiagnostics* diag); /** Handler for <abi-group> tags. */ bool AbiGroupTagHandler(configuration::PostProcessingConfiguration* config, xml::Element* element, - IDiagnostics* diag); + android::IDiagnostics* diag); /** Handler for <screen-density-group> tags. */ bool ScreenDensityGroupTagHandler(configuration::PostProcessingConfiguration* config, - xml::Element* element, IDiagnostics* diag); + xml::Element* element, android::IDiagnostics* diag); /** Handler for <locale-group> tags. */ bool LocaleGroupTagHandler(configuration::PostProcessingConfiguration* config, - xml::Element* element, IDiagnostics* diag); + xml::Element* element, android::IDiagnostics* diag); /** Handler for <android-sdk> tags. */ bool AndroidSdkTagHandler(configuration::PostProcessingConfiguration* config, xml::Element* element, - IDiagnostics* diag); + android::IDiagnostics* diag); /** Handler for <gl-texture-group> tags. */ bool GlTextureGroupTagHandler(configuration::PostProcessingConfiguration* config, - xml::Element* element, IDiagnostics* diag); + xml::Element* element, android::IDiagnostics* diag); /** Handler for <device-feature-group> tags. */ bool DeviceFeatureGroupTagHandler(configuration::PostProcessingConfiguration* config, - xml::Element* element, IDiagnostics* diag); + xml::Element* element, android::IDiagnostics* diag); } // namespace handler } // namespace configuration diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index 9828b97982ed..a43bf1b60f42 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -16,18 +16,24 @@ #include "DumpManifest.h" +#include <androidfw/ApkParsing.h> + #include <algorithm> +#include <array> +#include <memory> +#include <set> +#include <string_view> +#include <vector> #include "LoadedApk.h" #include "SdkConstants.h" #include "ValueVisitor.h" +#include "androidfw/ConfigDescription.h" #include "io/File.h" #include "io/FileStream.h" #include "process/IResourceTableConsumer.h" #include "xml/XmlDom.h" -#include "androidfw/ConfigDescription.h" - using ::android::base::StringPrintf; using ::android::ConfigDescription; @@ -112,7 +118,101 @@ static xml::Attribute* FindAttribute(xml::Element *el, const std::string &packag return el->FindAttribute(package, name); } +class Architectures { + public: + std::set<std::string> architectures; + std::set<std::string> alt_architectures; + + void Print(text::Printer* printer) { + if (!architectures.empty()) { + printer->Print("native-code:"); + for (auto& arch : architectures) { + printer->Print(StringPrintf(" '%s'", arch.data())); + } + printer->Print("\n"); + } + if (!alt_architectures.empty()) { + printer->Print("alt-native-code:"); + for (auto& arch : alt_architectures) { + printer->Print(StringPrintf(" '%s'", arch.data())); + } + printer->Print("\n"); + } + } + + void ToProto(pb::Badging* out_badging) { + auto out_architectures = out_badging->mutable_architectures(); + for (auto& arch : architectures) { + out_architectures->add_architectures(arch); + } + for (auto& arch : alt_architectures) { + out_architectures->add_alt_architectures(arch); + } + } +}; + +const static std::array<std::string_view, 14> printable_components{"app-widget", + "device-admin", + "ime", + "wallpaper", + "accessibility", + "print-service", + "payment", + "search", + "document-provider", + "launcher", + "notification-listener", + "dream", + "camera", + "camera-secure"}; + +class Components { + public: + std::set<std::string, std::less<>> discovered_components; + bool other_activities = false; + bool other_receivers = false; + bool other_services = false; + + void Print(text::Printer* printer) { + for (auto& component : printable_components) { + if (discovered_components.find(component) != discovered_components.end()) { + printer->Print(StringPrintf("provides-component:'%s'\n", component.data())); + } + } + // Print presence of main activity + if (discovered_components.find("main") != discovered_components.end()) { + printer->Print("main\n"); + } + + if (other_activities) { + printer->Print("other-activities\n"); + } + if (other_receivers) { + printer->Print("other-receivers\n"); + } + if (other_services) { + printer->Print("other-services\n"); + } + } + + void ToProto(pb::Badging* out_badging) { + auto out_components = out_badging->mutable_components(); + for (auto& component : printable_components) { + auto discovered = discovered_components.find(component); + if (discovered != discovered_components.end()) { + out_components->add_provided_components(*discovered); + } + } + out_components->set_main(discovered_components.find("main") != discovered_components.end()); + out_components->set_other_activities(other_activities); + out_components->set_other_receivers(other_receivers); + out_components->set_other_services(other_services); + } +}; + class CommonFeatureGroup; +class FeatureGroup; +class SupportsScreen; class ManifestExtractor { public: @@ -125,10 +225,16 @@ class ManifestExtractor { Element() = default; virtual ~Element() = default; - static std::unique_ptr<Element> Inflate(ManifestExtractor* extractor, xml::Element* el); + static std::unique_ptr<Element> Inflate(ManifestExtractor* extractor, xml::Element* el, + const std::string& parent_tag); /** Writes out the extracted contents of the element. */ - virtual void Print(text::Printer* printer) { } + virtual void Print(text::Printer* printer) { + } + + /** Saves extracted information into Badging proto. */ + virtual void ToProto(pb::Badging* out_badging) { + } /** Adds an element to the list of children of the element. */ void AddChild(std::unique_ptr<Element>& child) { children_.push_back(std::move(child)); } @@ -146,10 +252,15 @@ class ManifestExtractor { } /** Retrieves the extracted xml element tag. */ - const std::string tag() const { + const std::string& tag() const { return tag_; } + /** Whether this element has special Extract/Print/ToProto logic. */ + bool is_featured() const { + return featured_; + } + protected: ManifestExtractor* extractor() const { return extractor_; @@ -291,6 +402,8 @@ class ManifestExtractor { return &(*intValue->value); } else if (RawString* rawValue = ValueCast<RawString>(value)) { return &(*rawValue->value); + } else if (StyledString* styledStrValue = ValueCast<StyledString>(value)) { + return &(styledStrValue->value->value); } else if (FileReference* strValue = ValueCast<FileReference>(value)) { return &(*strValue->path); } @@ -321,6 +434,7 @@ class ManifestExtractor { ManifestExtractor* extractor_; std::vector<std::unique_ptr<Element>> children_; std::string tag_; + bool featured_ = false; }; friend Element; @@ -338,12 +452,19 @@ class ManifestExtractor { return config; } - bool Dump(text::Printer* printer, IDiagnostics* diag); + bool Extract(android::IDiagnostics* diag); + bool Dump(text::Printer* printer); + bool DumpProto(pb::Badging* out_badging); /** Recursively visit the xml element tree and return a processed badging element tree. */ - std::unique_ptr<Element> Visit(xml::Element* element); + std::unique_ptr<Element> Visit(xml::Element* element, const std::string& parent_tag); + + /** Resets target SDK to 0. */ + void ResetTargetSdk() { + target_sdk_ = 0; + } - /** Raises the target sdk value if the min target is greater than the current target. */ + /** Raises the target sdk value if the min target is greater than the current target. */ void RaiseTargetSdk(int32_t min_target) { if (min_target > target_sdk_) { target_sdk_ = min_target; @@ -354,7 +475,7 @@ class ManifestExtractor { * Retrieves the default feature group that features are added into when <uses-feature> * are not in a <feature-group> element. **/ - CommonFeatureGroup* GetCommonFeatureGroup() { + CommonFeatureGroup* common_feature_group() { return commonFeatureGroup_.get(); } @@ -375,7 +496,7 @@ class ManifestExtractor { } /** Retrieves the current stack of parent during data extraction. */ - const std::vector<Element*> parent_stack() const { + const std::vector<Element*>& parent_stack() const { return parent_stack_; } @@ -387,11 +508,19 @@ class ManifestExtractor { DumpManifestOptions& options_; private: + std::unique_ptr<xml::XmlResource> doc_; std::unique_ptr<CommonFeatureGroup> commonFeatureGroup_ = util::make_unique<CommonFeatureGroup>(); std::map<std::string, ConfigDescription> locales_; std::map<uint16_t, ConfigDescription> densities_; std::vector<Element*> parent_stack_; int32_t target_sdk_ = 0; + + std::unique_ptr<ManifestExtractor::Element> root_element_; + std::vector<std::unique_ptr<ManifestExtractor::Element>> implied_permissions_; + std::vector<FeatureGroup*> feature_groups_; + Components components_; + Architectures architectures_; + const SupportsScreen* supports_screen_; }; template<typename T> T* ElementCast(ManifestExtractor::Element* element); @@ -415,8 +544,9 @@ static ManifestExtractor::Element* FindElement(ManifestExtractor::Element* root, if (f(root)) { return root; } - for (auto& child : root->children()) { - if (auto b2 = FindElement(child.get(), f)) { + const auto& children = root->children(); + for (auto it = children.rbegin(); it != children.rend(); ++it) { + if (auto b2 = FindElement(it->get(), f)) { return b2; } } @@ -427,6 +557,7 @@ static ManifestExtractor::Element* FindElement(ManifestExtractor::Element* root, class Manifest : public ManifestExtractor::Element { public: Manifest() = default; + bool only_package_name; std::string package; int32_t versionCode; std::string versionName; @@ -462,7 +593,54 @@ class Manifest : public ManifestExtractor::Element { installLocation = GetAttributeInteger(FindAttribute(manifest, INSTALL_LOCATION_ATTR)); } + void ToProto(pb::Badging* out_badging) override { + auto out_package = out_badging->mutable_package(); + out_package->set_package(package); + out_package->set_version_code(versionCode); + out_package->set_version_name(versionName); + if (compilesdkVersion) { + out_package->set_compile_sdk_version(*compilesdkVersion); + } + if (compilesdkVersionCodename) { + out_package->set_compile_sdk_version_codename(*compilesdkVersionCodename); + } + if (platformVersionName) { + out_package->set_platform_version_name(*platformVersionName); + } else if (platformVersionNameInt) { + out_package->set_platform_version_name(std::to_string(*platformVersionNameInt)); + } + if (platformVersionCode) { + out_package->set_platform_version_code(*platformVersionCode); + } else if (platformVersionCodeInt) { + out_package->set_platform_version_code(std::to_string(*platformVersionCodeInt)); + } + + if (installLocation) { + switch (*installLocation) { + case 0: + out_package->set_install_location(pb::PackageInfo_InstallLocation_AUTO); + break; + case 1: + out_package->set_install_location(pb::PackageInfo_InstallLocation_INTERNAL_ONLY); + break; + case 2: + out_package->set_install_location(pb::PackageInfo_InstallLocation_PREFER_EXTERNAL); + break; + default: + break; + } + } + } + void Print(text::Printer* printer) override { + if (only_package_name) { + printer->Println(StringPrintf("package: %s", package.data())); + } else { + PrintFull(printer); + } + } + + void PrintFull(text::Printer* printer) { printer->Print(StringPrintf("package: name='%s' ", package.data())); printer->Print(StringPrintf("versionCode='%s' ", (versionCode > 0) ? std::to_string(versionCode).data() : "")); @@ -598,6 +776,27 @@ class Application : public ManifestExtractor::Element { printer->Print("application-debuggable\n"); } } + + void ToProto(pb::Badging* out_badging) override { + auto application = out_badging->mutable_application(); + application->set_label(android::ResTable::normalizeForOutput(label.data())); + application->set_icon(icon); + application->set_banner(banner); + application->set_test_only(test_only != 0); + application->set_game(is_game != 0); + application->set_debuggable(debuggable != 0); + + auto out_locale_labels = application->mutable_locale_labels(); + for (auto& p : locale_labels) { + if (!p.first.empty()) { + (*out_locale_labels)[p.first] = p.second; + } + } + auto out_density_icons = application->mutable_density_icons(); + for (auto& p : density_icons) { + (*out_density_icons)[p.first] = p.second; + } + } }; /** Represents <uses-sdk> elements. **/ @@ -617,6 +816,10 @@ class UsesSdkBadging : public ManifestExtractor::Element { target_sdk = GetAttributeInteger(FindAttribute(element, TARGET_SDK_VERSION_ATTR)); target_sdk_name = GetAttributeString(FindAttribute(element, TARGET_SDK_VERSION_ATTR)); + // Resets target SDK first. This is required if APK contains multiple <uses-sdk> elements, + // we only need to take the latest values. + extractor()->ResetTargetSdk(); + // Detect the target sdk of the element if ((min_sdk_name && *min_sdk_name == "Donut") || (target_sdk_name && *target_sdk_name == "Donut")) { @@ -647,6 +850,23 @@ class UsesSdkBadging : public ManifestExtractor::Element { printer->Print(StringPrintf("targetSdkVersion:'%s'\n", target_sdk_name->data())); } } + + void ToProto(pb::Badging* out_badging) override { + auto out_sdks = out_badging->mutable_uses_sdk(); + if (min_sdk) { + out_sdks->set_min_sdk_version(*min_sdk); + } else if (min_sdk_name) { + out_sdks->set_min_sdk_version_name(*min_sdk_name); + } + if (max_sdk) { + out_sdks->set_max_sdk_version(*max_sdk); + } + if (target_sdk) { + out_sdks->set_target_sdk_version(*target_sdk); + } else if (target_sdk_name) { + out_sdks->set_target_sdk_version_name(*target_sdk_name); + } + } }; /** Represents <uses-configuration> elements. **/ @@ -691,6 +911,15 @@ class UsesConfiguarion : public ManifestExtractor::Element { } printer->Print("\n"); } + + void ToProto(pb::Badging* out_badging) override { + auto out_configuration = out_badging->add_uses_configurations(); + out_configuration->set_req_touch_screen(req_touch_screen); + out_configuration->set_req_keyboard_type(req_keyboard_type); + out_configuration->set_req_hard_keyboard(req_hard_keyboard); + out_configuration->set_req_navigation(req_navigation); + out_configuration->set_req_five_way_nav(req_five_way_nav); + } }; /** Represents <supports-screen> elements. **/ @@ -733,54 +962,24 @@ class SupportsScreen : public ManifestExtractor::Element { } } - void PrintScreens(text::Printer* printer, int32_t target_sdk) { - int32_t small_screen_temp = small_screen; - int32_t normal_screen_temp = normal_screen; - int32_t large_screen_temp = large_screen; - int32_t xlarge_screen_temp = xlarge_screen; - int32_t any_density_temp = any_density; - - // Determine default values for any unspecified screen sizes, - // based on the target SDK of the package. As of 4 (donut) - // the screen size support was introduced, so all default to - // enabled. - if (small_screen_temp > 0) { - small_screen_temp = target_sdk >= SDK_DONUT ? -1 : 0; - } - if (normal_screen_temp > 0) { - normal_screen_temp = -1; - } - if (large_screen_temp > 0) { - large_screen_temp = target_sdk >= SDK_DONUT ? -1 : 0; - } - if (xlarge_screen_temp > 0) { - // Introduced in Gingerbread. - xlarge_screen_temp = target_sdk >= SDK_GINGERBREAD ? -1 : 0; - } - if (any_density_temp > 0) { - any_density_temp = (target_sdk >= SDK_DONUT || requires_smallest_width_dp > 0 || - compatible_width_limit_dp > 0) - ? -1 - : 0; - } - + void PrintScreens(text::Printer* printer, int32_t target_sdk) const { // Print the formatted screen info printer->Print("supports-screens:"); - if (small_screen_temp != 0) { + if (IsSmallScreenSupported(target_sdk)) { printer->Print(" 'small'"); } - if (normal_screen_temp != 0) { + if (normal_screen != 0) { printer->Print(" 'normal'"); } - if (large_screen_temp != 0) { + if (IsLargeScreenSupported(target_sdk)) { printer->Print(" 'large'"); } - if (xlarge_screen_temp != 0) { + if (IsXLargeScreenSupported(target_sdk)) { printer->Print(" 'xlarge'"); } printer->Print("\n"); printer->Print(StringPrintf("supports-any-density: '%s'\n", - (any_density_temp ) ? "true" : "false")); + (IsAnyDensitySupported(target_sdk)) ? "true" : "false")); if (requires_smallest_width_dp > 0) { printer->Print(StringPrintf("requires-smallest-width:'%d'\n", requires_smallest_width_dp)); } @@ -791,6 +990,60 @@ class SupportsScreen : public ManifestExtractor::Element { printer->Print(StringPrintf("largest-width-limit:'%d'\n", largest_width_limit_dp)); } } + + void ToProtoScreens(pb::Badging* out_badging, int32_t target_sdk) const { + auto supports_screen = out_badging->mutable_supports_screen(); + if (IsSmallScreenSupported(target_sdk)) { + supports_screen->add_screens(pb::SupportsScreen_ScreenType_SMALL); + } + if (normal_screen != 0) { + supports_screen->add_screens(pb::SupportsScreen_ScreenType_NORMAL); + } + if (IsLargeScreenSupported(target_sdk)) { + supports_screen->add_screens(pb::SupportsScreen_ScreenType_LARGE); + } + if (IsXLargeScreenSupported(target_sdk)) { + supports_screen->add_screens(pb::SupportsScreen_ScreenType_XLARGE); + } + supports_screen->set_supports_any_densities(IsAnyDensitySupported(target_sdk)); + supports_screen->set_requires_smallest_width_dp(requires_smallest_width_dp); + supports_screen->set_compatible_width_limit_dp(compatible_width_limit_dp); + supports_screen->set_largest_width_limit_dp(largest_width_limit_dp); + } + + private: + // Determine default values for any unspecified screen sizes, + // based on the target SDK of the package. As of 4 (donut) + // the screen size support was introduced, so all default to + // enabled. + bool IsSmallScreenSupported(int32_t target_sdk) const { + if (small_screen > 0) { + return target_sdk >= SDK_DONUT; + } + return small_screen != 0; + } + + bool IsLargeScreenSupported(int32_t target_sdk) const { + if (large_screen > 0) { + return target_sdk >= SDK_DONUT; + } + return large_screen != 0; + } + + bool IsXLargeScreenSupported(int32_t target_sdk) const { + if (xlarge_screen > 0) { + return target_sdk >= SDK_GINGERBREAD; + } + return xlarge_screen != 0; + } + + bool IsAnyDensitySupported(int32_t target_sdk) const { + if (any_density > 0) { + return target_sdk >= SDK_DONUT || requires_smallest_width_dp > 0 || + compatible_width_limit_dp > 0; + } + return any_density != 0; + } }; /** Represents <feature-group> elements. **/ @@ -821,9 +1074,21 @@ class FeatureGroup : public ManifestExtractor::Element { } } + virtual void GroupToProto(pb::Badging* out_badging) { + auto feature_group = out_badging->add_feature_groups(); + feature_group->set_label(label); + feature_group->set_open_gles_version(open_gles_version); + for (auto& feature : features_) { + auto out_feature = feature_group->add_features(); + out_feature->set_name(feature.first); + out_feature->set_required(feature.second.required); + out_feature->set_version(feature.second.version); + } + } + /** Adds a feature to the feature group. */ void AddFeature(const std::string& name, bool required = true, int32_t version = -1) { - features_.insert(std::make_pair(name, Feature{ required, version })); + features_.insert_or_assign(name, Feature{required, version}); if (required) { if (name == "android.hardware.camera.autofocus" || name == "android.hardware.camera.flash") { @@ -910,6 +1175,23 @@ class CommonFeatureGroup : public FeatureGroup { } } + virtual void GroupToProto(pb::Badging* out_badging) override { + FeatureGroup::GroupToProto(out_badging); + auto feature_group = + out_badging->mutable_feature_groups(out_badging->feature_groups_size() - 1); + for (auto& feature : implied_features_) { + if (features_.find(feature.first) == features_.end()) { + auto out_feature = feature_group->add_features(); + out_feature->set_name(feature.first); + auto implied_data = out_feature->mutable_implied_data(); + implied_data->set_from_sdk_23_permission(feature.second.implied_from_sdk_k23); + for (auto& reason : feature.second.reasons) { + implied_data->add_reasons(reason); + } + } + } + } + /** Returns true if the feature group has the given feature. */ bool HasFeature(const std::string& name) override { return FeatureGroup::HasFeature(name) @@ -1050,7 +1332,7 @@ class UsesFeature : public ManifestExtractor::Element { // common feature group FeatureGroup* feature_group = ElementCast<FeatureGroup>(extractor()->parent_stack()[0]); if (!feature_group) { - feature_group = extractor()->GetCommonFeatureGroup(); + feature_group = extractor()->common_feature_group(); } else { // All features in side of <feature-group> elements are required. required = true; @@ -1068,12 +1350,14 @@ class UsesFeature : public ManifestExtractor::Element { class UsesPermission : public ManifestExtractor::Element { public: UsesPermission() = default; + bool implied; std::string name; std::vector<std::string> requiredFeatures; std::vector<std::string> requiredNotFeatures; int32_t required = true; int32_t maxSdkVersion = -1; int32_t usesPermissionFlags = 0; + std::string impliedReason; void Extract(xml::Element* element) override { name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); @@ -1094,7 +1378,7 @@ class UsesPermission : public ManifestExtractor::Element { FindAttribute(element, USES_PERMISSION_FLAGS_ATTR), 0); if (!name.empty()) { - CommonFeatureGroup* common = extractor()->GetCommonFeatureGroup(); + CommonFeatureGroup* common = extractor()->common_feature_group(); common->addImpliedFeaturesForPermission(extractor()->target_sdk(), name, false); } } @@ -1126,17 +1410,37 @@ class UsesPermission : public ManifestExtractor::Element { printer->Print("\n"); } } + if (implied) { + printer->Print(StringPrintf("uses-implied-permission: name='%s'", name.data())); + if (maxSdkVersion >= 0) { + printer->Print(StringPrintf(" maxSdkVersion='%d'", maxSdkVersion)); + } + if ((usesPermissionFlags & kNeverForLocation) != 0) { + printer->Print(StringPrintf(" usesPermissionFlags='neverForLocation'")); + } + printer->Print(StringPrintf(" reason='%s'\n", impliedReason.data())); + } } - void PrintImplied(text::Printer* printer, const std::string& reason) { - printer->Print(StringPrintf("uses-implied-permission: name='%s'", name.data())); - if (maxSdkVersion >= 0) { - printer->Print(StringPrintf(" maxSdkVersion='%d'", maxSdkVersion)); - } - if ((usesPermissionFlags & kNeverForLocation) != 0) { - printer->Print(StringPrintf(" usesPermissionFlags='neverForLocation'")); + void ToProto(pb::Badging* out_badging) override { + if (!name.empty()) { + auto permission = out_badging->add_uses_permissions(); + permission->set_name(name); + if (maxSdkVersion > 0) { + permission->set_max_sdk_version(maxSdkVersion); + } + if ((usesPermissionFlags & kNeverForLocation) != 0) { + permission->mutable_permission_flags()->set_never_for_location(true); + } + for (auto& requiredFeature : requiredFeatures) { + permission->add_required_features(requiredFeature); + } + for (auto& requiredNotFeature : requiredNotFeatures) { + permission->add_required_not_features(requiredNotFeature); + } + permission->set_required(required != 0); + permission->set_implied(implied); } - printer->Print(StringPrintf(" reason='%s'\n", reason.data())); } }; @@ -1184,7 +1488,7 @@ class UsesPermissionSdk23 : public ManifestExtractor::Element { maxSdkVersion = GetAttributeInteger(FindAttribute(element, MAX_SDK_VERSION_ATTR)); if (name) { - CommonFeatureGroup* common = extractor()->GetCommonFeatureGroup(); + CommonFeatureGroup* common = extractor()->common_feature_group(); common->addImpliedFeaturesForPermission(extractor()->target_sdk(), *name, true); } } @@ -1198,6 +1502,17 @@ class UsesPermissionSdk23 : public ManifestExtractor::Element { printer->Print("\n"); } } + + void ToProto(pb::Badging* out_badging) override { + if (name) { + auto permission = out_badging->add_uses_permissions(); + permission->set_sdk23_and_above(true); + permission->set_name(*name); + if (maxSdkVersion) { + permission->set_max_sdk_version(*maxSdkVersion); + } + } + } }; /** Represents <permission> elements. These elements are only printing when dumping permissions. **/ @@ -1215,6 +1530,12 @@ class Permission : public ManifestExtractor::Element { printer->Print(StringPrintf("permission: %s\n", name.data())); } } + + void ToProto(pb::Badging* out_badging) override { + if (!name.empty()) { + out_badging->add_permissions()->set_name(name); + } + } }; /** Represents <activity> elements. **/ @@ -1256,7 +1577,7 @@ class Activity : public ManifestExtractor::Element { auto orientation = GetAttributeInteger(FindAttribute(element, SCREEN_ORIENTATION_ATTR)); if (orientation) { - CommonFeatureGroup* common = extractor()->GetCommonFeatureGroup(); + CommonFeatureGroup* common = extractor()->common_feature_group(); int orien = *orientation; if (orien == 0 || orien == 6 || orien == 8) { // Requests landscape, sensorLandscape, or reverseLandscape. @@ -1295,6 +1616,22 @@ class Activity : public ManifestExtractor::Element { icon.data(), banner.data())); } } + + void ToProto(pb::Badging* out_badging) override { + if (has_main_action && has_launcher_category) { + auto activity = out_badging->mutable_launchable_activity(); + activity->set_name(name); + activity->set_label(android::ResTable::normalizeForOutput(label.data())); + activity->set_icon(icon); + } + if (has_leanback_launcher_category) { + auto activity = out_badging->mutable_leanback_launchable_activity(); + activity->set_name(name); + activity->set_label(android::ResTable::normalizeForOutput(label.data())); + activity->set_icon(icon); + activity->set_banner(banner); + } + } }; /** Represents <intent-filter> elements. */ @@ -1382,11 +1719,8 @@ class UsesLibrary : public ManifestExtractor::Element { int required; void Extract(xml::Element* element) override { - auto parent_stack = extractor()->parent_stack(); - if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) { - name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); - required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1); - } + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1); } void Print(text::Printer* printer) override { @@ -1395,6 +1729,14 @@ class UsesLibrary : public ManifestExtractor::Element { (required == 0) ? "-not-required" : "", name.data())); } } + + void ToProto(pb::Badging* out_badging) override { + if (!name.empty()) { + auto uses_library = out_badging->add_uses_libraries(); + uses_library->set_name(name); + uses_library->set_required(required != 0); + } + } }; /** Represents <static-library> elements. **/ @@ -1406,12 +1748,9 @@ class StaticLibrary : public ManifestExtractor::Element { int versionMajor; void Extract(xml::Element* element) override { - auto parent_stack = extractor()->parent_stack(); - if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) { - name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); - version = GetAttributeIntegerDefault(FindAttribute(element, VERSION_ATTR), 0); - versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0); - } + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + version = GetAttributeIntegerDefault(FindAttribute(element, VERSION_ATTR), 0); + versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0); } void Print(text::Printer* printer) override { @@ -1419,6 +1758,13 @@ class StaticLibrary : public ManifestExtractor::Element { "static-library: name='%s' version='%d' versionMajor='%d'\n", name.data(), version, versionMajor)); } + + void ToProto(pb::Badging* out_badging) override { + auto static_library = out_badging->mutable_static_library(); + static_library->set_name(name); + static_library->set_version(version); + static_library->set_version_major(versionMajor); + } }; /** Represents <uses-static-library> elements. **/ @@ -1431,13 +1777,10 @@ class UsesStaticLibrary : public ManifestExtractor::Element { std::vector<std::string> certDigests; void Extract(xml::Element* element) override { - auto parent_stack = extractor()->parent_stack(); - if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) { - name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); - version = GetAttributeIntegerDefault(FindAttribute(element, VERSION_ATTR), 0); - versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0); - AddCertDigest(element); - } + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + version = GetAttributeIntegerDefault(FindAttribute(element, VERSION_ATTR), 0); + versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0); + AddCertDigest(element); } void AddCertDigest(xml::Element* element) { @@ -1459,6 +1802,16 @@ class UsesStaticLibrary : public ManifestExtractor::Element { } printer->Print("\n"); } + + void ToProto(pb::Badging* out_badging) override { + auto uses_static_library = out_badging->add_uses_static_libraries(); + uses_static_library->set_name(name); + uses_static_library->set_version(version); + uses_static_library->set_version_major(versionMajor); + for (auto& cert : certDigests) { + uses_static_library->add_certificates(cert); + } + } }; /** Represents <sdk-library> elements. **/ @@ -1469,17 +1822,20 @@ class SdkLibrary : public ManifestExtractor::Element { int versionMajor; void Extract(xml::Element* element) override { - auto parent_stack = extractor()->parent_stack(); - if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) { - name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); - versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0); - } + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0); } void Print(text::Printer* printer) override { printer->Print( StringPrintf("sdk-library: name='%s' versionMajor='%d'\n", name.data(), versionMajor)); } + + void ToProto(pb::Badging* out_badging) override { + auto sdk_library = out_badging->mutable_sdk_library(); + sdk_library->set_name(name); + sdk_library->set_version_major(versionMajor); + } }; /** Represents <uses-sdk-library> elements. **/ @@ -1491,12 +1847,9 @@ class UsesSdkLibrary : public ManifestExtractor::Element { std::vector<std::string> certDigests; void Extract(xml::Element* element) override { - auto parent_stack = extractor()->parent_stack(); - if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) { - name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); - versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0); - AddCertDigest(element); - } + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0); + AddCertDigest(element); } void AddCertDigest(xml::Element* element) { @@ -1517,6 +1870,15 @@ class UsesSdkLibrary : public ManifestExtractor::Element { } printer->Print("\n"); } + + void ToProto(pb::Badging* out_badging) override { + auto uses_sdk_library = out_badging->add_uses_sdk_libraries(); + uses_sdk_library->set_name(name); + uses_sdk_library->set_version_major(versionMajor); + for (auto& cert : certDigests) { + uses_sdk_library->add_certificates(cert); + } + } }; /** Represents <uses-native-library> elements. **/ @@ -1527,11 +1889,8 @@ class UsesNativeLibrary : public ManifestExtractor::Element { int required; void Extract(xml::Element* element) override { - auto parent_stack = extractor()->parent_stack(); - if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) { - name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); - required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1); - } + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1); } void Print(text::Printer* printer) override { @@ -1540,6 +1899,14 @@ class UsesNativeLibrary : public ManifestExtractor::Element { (required == 0) ? "-not-required" : "", name.data())); } } + + void ToProto(pb::Badging* out_badging) override { + if (!name.empty()) { + auto uses_native_library = out_badging->add_uses_native_libraries(); + uses_native_library->set_name(name); + uses_native_library->set_required(required != 0); + } + } }; /** @@ -1565,21 +1932,39 @@ class MetaData : public ManifestExtractor::Element { void Print(text::Printer* printer) override { if (extractor()->options_.include_meta_data && !name.empty()) { - printer->Print(StringPrintf("meta-data: name='%s' ", name.data())); + printer->Print(StringPrintf("meta-data: name='%s'", name.data())); if (!value.empty()) { - printer->Print(StringPrintf("value='%s' ", value.data())); + printer->Print(StringPrintf(" value='%s'", value.data())); } else if (value_int) { - printer->Print(StringPrintf("value='%d' ", *value_int)); + printer->Print(StringPrintf(" value='%d'", *value_int)); } else { if (!resource.empty()) { - printer->Print(StringPrintf("resource='%s' ", resource.data())); + printer->Print(StringPrintf(" resource='%s'", resource.data())); } else if (resource_int) { - printer->Print(StringPrintf("resource='%d' ", *resource_int)); + printer->Print(StringPrintf(" resource='%d'", *resource_int)); } } printer->Print("\n"); } } + + void ToProto(pb::Badging* out_badging) override { + if (!name.empty()) { + auto metadata = out_badging->add_metadata(); + metadata->set_name(name); + if (!value.empty()) { + metadata->set_value_string(value); + } else if (value_int) { + metadata->set_value_int(*value_int); + } else { + if (!resource.empty()) { + metadata->set_resource_string(resource); + } else if (resource_int) { + metadata->set_resource_int(*resource_int); + } + } + } + } }; /** @@ -1599,10 +1984,11 @@ class Action : public ManifestExtractor::Element { if (ElementCast<Activity>(parent_stack[1])) { // Detects the presence of a particular type of activity. Activity* activity = ElementCast<Activity>(parent_stack[1]); - auto map = std::map<std::string, std::string>({ - { "android.intent.action.MAIN" , "main" }, - { "android.intent.action.VIDEO_CAMERA" , "camera" }, - { "android.intent.action.STILL_IMAGE_CAMERA_SECURE" , "camera-secure" }, + static const auto map = std::map<std::string, std::string>({ + {"android.intent.action.MAIN", "main"}, + {"android.media.action.VIDEO_CAMERA", "camera"}, + {"android.media.action.STILL_IMAGE_CAMERA", "camera"}, + {"android.media.action.STILL_IMAGE_CAMERA_SECURE", "camera-secure"}, }); auto entry = map.find(action); @@ -1709,6 +2095,13 @@ class SupportsInput : public ManifestExtractor::Element { printer->Print("\n"); } } + + void ToProto(pb::Badging* out_badging) override { + auto supports_input = out_badging->mutable_supports_input(); + for (auto& input : inputs) { + supports_input->add_inputs(input); + } + } }; /** Represents <input-type> elements. **/ @@ -1727,6 +2120,33 @@ class InputType : public ManifestExtractor::Element { } }; +/** Represents <install-constraints> elements. **/ +class InstallConstraints : public ManifestExtractor::Element { + public: + InstallConstraints() = default; + std::vector<std::string> fingerprint_prefixes; + + void Extract(xml::Element* element) override { + for (xml::Element* child : element->GetChildElements()) { + if (child->name == "fingerprint-prefix") { + xml::Attribute* attr = child->FindAttribute(kAndroidNamespace, "value"); + if (attr) { + fingerprint_prefixes.push_back(attr->value); + } + } + } + } + + void Print(text::Printer* printer) override { + if (!fingerprint_prefixes.empty()) { + printer->Print(StringPrintf("install-constraints:\n")); + for (const auto& prefix : fingerprint_prefixes) { + printer->Print(StringPrintf(" fingerprint-prefix='%s'\n", prefix.c_str())); + } + } + } +}; + /** Represents <original-package> elements. **/ class OriginalPackage : public ManifestExtractor::Element { public: @@ -1742,6 +2162,12 @@ class OriginalPackage : public ManifestExtractor::Element { printer->Print(StringPrintf("original-package:'%s'\n", name->data())); } } + + void ToProto(pb::Badging* out_badging) override { + if (name) { + out_badging->mutable_package()->set_original_package(*name); + } + } }; @@ -1780,6 +2206,21 @@ class Overlay : public ManifestExtractor::Element { } printer->Print("\n"); } + + void ToProto(pb::Badging* out_badging) override { + auto overlay = out_badging->mutable_overlay(); + if (target_package) { + overlay->set_target_package(*target_package); + } + overlay->set_priority(priority); + overlay->set_static_(is_static); + if (required_property_name) { + overlay->set_required_property_name(*required_property_name); + } + if (required_property_value) { + overlay->set_required_property_value(*required_property_value); + } + } }; /** * Represents <package-verifier> elements. **/ @@ -1800,6 +2241,14 @@ class PackageVerifier : public ManifestExtractor::Element { name->data(), public_key->data())); } } + + void ToProto(pb::Badging* out_badging) override { + auto package_verifier = out_badging->mutable_package_verifier(); + if (name && public_key) { + package_verifier->set_name(*name); + package_verifier->set_public_key(*public_key); + } + } }; /** Represents <uses-package> elements. **/ @@ -1813,14 +2262,11 @@ class UsesPackage : public ManifestExtractor::Element { std::vector<std::string> certDigests; void Extract(xml::Element* element) override { - auto parent_stack = extractor()->parent_stack(); - if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) { - packageType = GetAttributeString(FindAttribute(element, PACKAGE_TYPE_ATTR)); - name = GetAttributeString(FindAttribute(element, NAME_ATTR)); - version = GetAttributeIntegerDefault(FindAttribute(element, VERSION_ATTR), 0); - versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0); - AddCertDigest(element); - } + packageType = GetAttributeString(FindAttribute(element, PACKAGE_TYPE_ATTR)); + name = GetAttributeString(FindAttribute(element, NAME_ATTR)); + version = GetAttributeIntegerDefault(FindAttribute(element, VERSION_ATTR), 0); + versionMajor = GetAttributeIntegerDefault(FindAttribute(element, VERSION_MAJOR_ATTR), 0); + AddCertDigest(element); } void AddCertDigest(xml::Element* element) { @@ -1848,6 +2294,21 @@ class UsesPackage : public ManifestExtractor::Element { } } } + + void ToProto(pb::Badging* out_badging) override { + if (name) { + auto uses_package = out_badging->add_uses_packages(); + uses_package->set_name(*name); + if (packageType) { + uses_package->set_package_type(*packageType); + uses_package->set_version(version); + uses_package->set_version_major(versionMajor); + for (auto& cert : certDigests) { + uses_package->add_certificates(cert); + } + } + } + } }; /** Represents <additional-certificate> elements. **/ @@ -1880,6 +2341,14 @@ class Screen : public ManifestExtractor::Element { size = GetAttributeInteger(FindAttribute(element, SCREEN_SIZE_ATTR)); density = GetAttributeInteger(FindAttribute(element, SCREEN_DENSITY_ATTR)); } + + void ToProto(pb::Badging* out_badging) override { + if (size && density) { + auto screen = out_badging->mutable_compatible_screens()->add_screens(); + screen->set_density(*density); + screen->set_size(*size); + } + } }; /** @@ -1925,6 +2394,12 @@ class SupportsGlTexture : public ManifestExtractor::Element { printer->Print(StringPrintf("supports-gl-texture:'%s'\n", name->data())); } } + + void ToProto(pb::Badging* out_badging) override { + if (name) { + out_badging->mutable_supports_gl_texture()->add_name(*name); + } + } }; /** Represents <property> elements. **/ @@ -1960,6 +2435,24 @@ class Property : public ManifestExtractor::Element { } printer->Print("\n"); } + + void ToProto(pb::Badging* out_badging) override { + if (!name.empty()) { + auto property = out_badging->add_properties(); + property->set_name(name); + if (!value.empty()) { + property->set_value_string(value); + } else if (value_int) { + property->set_value_int(*value_int); + } else { + if (!resource.empty()) { + property->set_resource_string(resource); + } else if (resource_int) { + property->set_resource_int(*resource_int); + } + } + } + } }; /** Recursively prints the extracted badging element. */ @@ -1970,44 +2463,46 @@ static void Print(ManifestExtractor::Element* el, text::Printer* printer) { } } -bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { +/** Recursively serializes extracted badging elements to proto. */ +static void ToProto(ManifestExtractor::Element* el, pb::Badging* out_badging) { + el->ToProto(out_badging); + for (auto& child : el->children()) { + ToProto(child.get(), out_badging); + } +} + +bool ManifestExtractor::Extract(android::IDiagnostics* diag) { // Load the manifest - std::unique_ptr<xml::XmlResource> doc = apk_->LoadXml("AndroidManifest.xml", diag); - if (doc == nullptr) { - diag->Error(DiagMessage() << "failed to find AndroidManifest.xml"); + doc_ = apk_->LoadXml("AndroidManifest.xml", diag); + if (doc_ == nullptr) { + diag->Error(android::DiagMessage() << "failed to find AndroidManifest.xml"); return false; } - xml::Element* element = doc->root.get(); + xml::Element* element = doc_->root.get(); if (element->name != "manifest") { - diag->Error(DiagMessage() << "manifest does not start with <manifest> tag"); + diag->Error(android::DiagMessage() << "manifest does not start with <manifest> tag"); return false; } // Print only the <uses-permission>, <uses-permission-sdk23>, and <permission> elements if // printing only permission elements is requested if (options_.only_permissions) { - std::unique_ptr<ManifestExtractor::Element> manifest_element = - ManifestExtractor::Element::Inflate(this, element); + root_element_ = ManifestExtractor::Element::Inflate(this, element, ""); + + if (auto manifest = ElementCast<Manifest>(root_element_.get())) { + manifest->only_package_name = true; - if (auto manifest = ElementCast<Manifest>(manifest_element.get())) { for (xml::Element* child : element->GetChildElements()) { if (child->name == "uses-permission" || child->name == "uses-permission-sdk-23" || child->name == "permission") { // Inflate the element and its descendants - auto permission_element = Visit(child); + auto permission_element = Visit(child, "manifest"); manifest->AddChild(permission_element); } } - - printer->Print(StringPrintf("package: %s\n", manifest->package.data())); - ForEachChild(manifest, [&printer](ManifestExtractor::Element* el) -> void { - el->Print(printer); - }); - return true; } - return false; } @@ -2041,27 +2536,24 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { } // Extract badging information - auto root = Visit(element); + root_element_ = Visit(element, ""); // Filter out all "uses-sdk" tags besides the very last tag. The android runtime only uses the // attribute values from the last defined tag. std::vector<UsesSdkBadging*> filtered_uses_sdk_tags; - for (const auto& child : root->children()) { + for (const auto& child : root_element_->children()) { if (auto uses_sdk = ElementCast<UsesSdkBadging>(child.get())) { filtered_uses_sdk_tags.emplace_back(uses_sdk); } } if (filtered_uses_sdk_tags.size() >= 2U) { filtered_uses_sdk_tags.pop_back(); - root->Filter([&](const ManifestExtractor::Element* e) { + root_element_->Filter([&](const ManifestExtractor::Element* e) { return std::find(filtered_uses_sdk_tags.begin(), filtered_uses_sdk_tags.end(), e) != filtered_uses_sdk_tags.end(); }); } - // Print the elements in order seen - Print(root.get(), printer); - /** Recursively checks the extracted elements for the specified permission. **/ auto FindPermission = [&](ManifestExtractor::Element* root, const std::string& name) -> ManifestExtractor::Element* { @@ -2073,30 +2565,30 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { }); }; - auto PrintPermission = [&printer](const std::string& name, const std::string& reason, - int32_t max_sdk_version) -> void { + auto AddImpliedPermission = [&](const std::string& name, const std::string& reason, + int32_t max_sdk_version) -> void { auto permission = util::make_unique<UsesPermission>(); permission->name = name; permission->maxSdkVersion = max_sdk_version; - permission->Print(printer); - permission->PrintImplied(printer, reason); + permission->implied = true; + permission->impliedReason = reason; + implied_permissions_.push_back(std::move(permission)); }; // Implied permissions // Pre-1.6 implicitly granted permission compatibility logic - CommonFeatureGroup* common_feature_group = GetCommonFeatureGroup(); bool insert_write_external = false; auto write_external_permission = ElementCast<UsesPermission>( - FindPermission(root.get(), "android.permission.WRITE_EXTERNAL_STORAGE")); + FindPermission(root_element_.get(), "android.permission.WRITE_EXTERNAL_STORAGE")); if (target_sdk() < SDK_DONUT) { if (!write_external_permission) { - PrintPermission("android.permission.WRITE_EXTERNAL_STORAGE", "targetSdkVersion < 4", -1); + AddImpliedPermission("android.permission.WRITE_EXTERNAL_STORAGE", "targetSdkVersion < 4", -1); insert_write_external = true; } - if (!FindPermission(root.get(), "android.permission.READ_PHONE_STATE")) { - PrintPermission("android.permission.READ_PHONE_STATE", "targetSdkVersion < 4", -1); + if (!FindPermission(root_element_.get(), "android.permission.READ_PHONE_STATE")) { + AddImpliedPermission("android.permission.READ_PHONE_STATE", "targetSdkVersion < 4", -1); } } @@ -2104,62 +2596,60 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { // force them to always take READ_EXTERNAL_STORAGE as well. We always // do this (regardless of target API version) because we can't have // an app with write permission but not read permission. - auto read_external = FindPermission(root.get(), "android.permission.READ_EXTERNAL_STORAGE"); + auto read_external = + FindPermission(root_element_.get(), "android.permission.READ_EXTERNAL_STORAGE"); if (!read_external && (insert_write_external || write_external_permission)) { - PrintPermission("android.permission.READ_EXTERNAL_STORAGE", - "requested WRITE_EXTERNAL_STORAGE", - (write_external_permission) ? write_external_permission->maxSdkVersion : -1); + AddImpliedPermission( + "android.permission.READ_EXTERNAL_STORAGE", "requested WRITE_EXTERNAL_STORAGE", + (write_external_permission) ? write_external_permission->maxSdkVersion : -1); } // Pre-JellyBean call log permission compatibility. if (target_sdk() < SDK_JELLY_BEAN) { - if (!FindPermission(root.get(), "android.permission.READ_CALL_LOG") - && FindPermission(root.get(), "android.permission.READ_CONTACTS")) { - PrintPermission("android.permission.READ_CALL_LOG", - "targetSdkVersion < 16 and requested READ_CONTACTS", -1); + if (!FindPermission(root_element_.get(), "android.permission.READ_CALL_LOG") && + FindPermission(root_element_.get(), "android.permission.READ_CONTACTS")) { + AddImpliedPermission("android.permission.READ_CALL_LOG", + "targetSdkVersion < 16 and requested READ_CONTACTS", -1); } - if (!FindPermission(root.get(), "android.permission.WRITE_CALL_LOG") - && FindPermission(root.get(), "android.permission.WRITE_CONTACTS")) { - PrintPermission("android.permission.WRITE_CALL_LOG", - "targetSdkVersion < 16 and requested WRITE_CONTACTS", -1); + if (!FindPermission(root_element_.get(), "android.permission.WRITE_CALL_LOG") && + FindPermission(root_element_.get(), "android.permission.WRITE_CONTACTS")) { + AddImpliedPermission("android.permission.WRITE_CALL_LOG", + "targetSdkVersion < 16 and requested WRITE_CONTACTS", -1); } } // If the app hasn't declared the touchscreen as a feature requirement (either // directly or implied, required or not), then the faketouch feature is implied. - if (!common_feature_group->HasFeature("android.hardware.touchscreen")) { - common_feature_group->addImpliedFeature("android.hardware.faketouch", - "default feature for all apps", false); + if (!common_feature_group()->HasFeature("android.hardware.touchscreen")) { + common_feature_group()->addImpliedFeature("android.hardware.faketouch", + "default feature for all apps", false); } // Only print the common feature group if no feature group is defined std::vector<FeatureGroup*> feature_groups; - ForEachChild(root.get(), [&feature_groups](ManifestExtractor::Element* el) -> void { + ForEachChild(root_element_.get(), [&feature_groups](ManifestExtractor::Element* el) -> void { if (auto feature_group = ElementCast<FeatureGroup>(el)) { feature_groups.push_back(feature_group); } }); if (feature_groups.empty()) { - common_feature_group->PrintGroup(printer); + feature_groups_.push_back(common_feature_group()); } else { // Merge the common feature group into the feature group for (auto& feature_group : feature_groups) { - feature_group->open_gles_version = std::max(feature_group->open_gles_version, - common_feature_group->open_gles_version); - feature_group->Merge(common_feature_group); - feature_group->PrintGroup(printer); + feature_group->Merge(common_feature_group()); + feature_groups_.push_back(feature_group); } }; // Collect the component types of the application - std::set<std::string> components; - ForEachChild(root.get(), [&components](ManifestExtractor::Element* el) -> void { + ForEachChild(root_element_.get(), [&](ManifestExtractor::Element* el) -> void { if (ElementCast<Action>(el)) { auto action = ElementCast<Action>(el); if (!action->component.empty()) { - components.insert(action->component); + components_.discovered_components.insert(action->component); return; } } @@ -2167,15 +2657,14 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { if (ElementCast<Category>(el)) { auto category = ElementCast<Category>(el); if (!category->component.empty()) { - components.insert(category->component); + components_.discovered_components.insert(category->component); return; } } }); // Check for the payment component - auto apk = apk_; - ForEachChild(root.get(), [&apk, &components, &diag](ManifestExtractor::Element* el) -> void { + ForEachChild(root_element_.get(), [this, &diag](ManifestExtractor::Element* el) -> void { if (auto service = ElementCast<Service>(el)) { auto host_apdu_action = ElementCast<Action>(FindElement(service, [&](ManifestExtractor::Element* el) -> bool { @@ -2193,159 +2682,112 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { return false; })); - ForEachChild(service, [&apk, &components, &diag, &host_apdu_action, - &offhost_apdu_action](ManifestExtractor::Element* el) -> void { - if (auto meta_data = ElementCast<MetaData>(el)) { - if ((meta_data->name == "android.nfc.cardemulation.host_apdu_service" && host_apdu_action) - || (meta_data->name == "android.nfc.cardemulation.off_host_apdu_service" - && offhost_apdu_action)) { - - // Attempt to load the resource file - if (!meta_data->resource.empty()) { - return; - } - auto resource = apk->LoadXml(meta_data->resource, diag); - if (!resource) { - return; - } - - // Look for the payment category on an <aid-group> element - auto& root = resource.get()->root; - if ((host_apdu_action && root->name == "host-apdu-service") - || (offhost_apdu_action && root->name == "offhost-apdu-service")) { - - for (auto& child : root->GetChildElements()) { - if (child->name == "aid-group") { - auto category = FindAttribute(child, CATEGORY_ATTR); - if (category && category->value == "payment") { - components.insert("payment"); - return; - } - } - } - } - } - } - }); + ForEachChild(service, + [this, &diag, &host_apdu_action, + &offhost_apdu_action](ManifestExtractor::Element* el) -> void { + if (auto meta_data = ElementCast<MetaData>(el)) { + if ((meta_data->name == "android.nfc.cardemulation.host_apdu_service" && + host_apdu_action) || + (meta_data->name == "android.nfc.cardemulation.off_host_apdu_service" && + offhost_apdu_action)) { + // Attempt to load the resource file + if (meta_data->resource.empty()) { + return; + } + auto resource = this->apk_->LoadXml(meta_data->resource, diag); + if (!resource) { + return; + } + + // Look for the payment category on an <aid-group> element + auto& root = resource.get()->root; + if ((host_apdu_action && root->name == "host-apdu-service") || + (offhost_apdu_action && root->name == "offhost-apdu-service")) { + for (auto& child : root->GetChildElements()) { + if (child->name == "aid-group") { + auto category = FindAttribute(child, CATEGORY_ATTR); + if (category && category->value == "payment") { + this->components_.discovered_components.insert("payment"); + return; + } + } + } + } + } + } + }); } }); - // Print the components types if they are present - auto PrintComponent = [&components, &printer](const std::string& component) -> void { - if (components.find(component) != components.end()) { - printer->Print(StringPrintf("provides-component:'%s'\n", component.data())); - } - }; - - PrintComponent("app-widget"); - PrintComponent("device-admin"); - PrintComponent("ime"); - PrintComponent("wallpaper"); - PrintComponent("accessibility"); - PrintComponent("print-service"); - PrintComponent("payment"); - PrintComponent("search"); - PrintComponent("document-provider"); - PrintComponent("launcher"); - PrintComponent("notification-listener"); - PrintComponent("dream"); - PrintComponent("camera"); - PrintComponent("camera-secure"); - - // Print presence of main activity - if (components.find("main") != components.end()) { - printer->Print("main\n"); - } - - // Print presence of activities, recivers, and services with no special components - FindElement(root.get(), [&printer](ManifestExtractor::Element* el) -> bool { + // Print presence of activities, receivers, and services with no special components + FindElement(root_element_.get(), [&](ManifestExtractor::Element* el) -> bool { if (auto activity = ElementCast<Activity>(el)) { if (!activity->has_component_) { - printer->Print("other-activities\n"); + components_.other_activities = true; return true; } } return false; }); - FindElement(root.get(), [&printer](ManifestExtractor::Element* el) -> bool { + FindElement(root_element_.get(), [&](ManifestExtractor::Element* el) -> bool { if (auto receiver = ElementCast<Receiver>(el)) { if (!receiver->has_component) { - printer->Print("other-receivers\n"); + components_.other_receivers = true; return true; } } return false; }); - FindElement(root.get(), [&printer](ManifestExtractor::Element* el) -> bool { + FindElement(root_element_.get(), [&](ManifestExtractor::Element* el) -> bool { if (auto service = ElementCast<Service>(el)) { if (!service->has_component) { - printer->Print("other-services\n"); + components_.other_services = true; return true; } } return false; }); - // Print the supported screens - SupportsScreen* screen = ElementCast<SupportsScreen>(FindElement(root.get(), - [&](ManifestExtractor::Element* el) -> bool { - return ElementCast<SupportsScreen>(el) != nullptr; - })); - - if (screen) { - screen->PrintScreens(printer, target_sdk_); - } else { - // Print the default supported screens - SupportsScreen default_screens; - default_screens.PrintScreens(printer, target_sdk_); - } + // Gather the supported screens + const static SupportsScreen default_screens{}; + SupportsScreen* screen = ElementCast<SupportsScreen>( + FindElement(root_element_.get(), [&](ManifestExtractor::Element* el) -> bool { + return ElementCast<SupportsScreen>(el) != nullptr; + })); + supports_screen_ = screen ? screen : &default_screens; - // Print all the unique locales of the apk - printer->Print("locales:"); - for (auto& config : locales_) { - if (config.first.empty()) { - printer->Print(" '--_--'"); - } else { - printer->Print(StringPrintf(" '%s'", config.first.data())); + bool has_renderscript_bitcode = false; + auto it = apk_->GetFileCollection()->Iterator(); + while (it->HasNext()) { + if (it->Next()->GetSource().path.ends_with(".bc")) { + has_renderscript_bitcode = true; + break; } } - printer->Print("\n"); - - // Print all the densities locales of the apk - printer->Print("densities:"); - for (auto& config : densities_) { - printer->Print(StringPrintf(" '%d'", config.first)); - } - printer->Print("\n"); - // Print the supported architectures of the app - std::set<std::string> architectures; - auto it = apk_->GetFileCollection()->Iterator(); + // Gather the supported architectures_ of the app + std::set<std::string> architectures_from_apk; + it = apk_->GetFileCollection()->Iterator(); while (it->HasNext()) { - auto file_path = it->Next()->GetSource().path; - - - size_t pos = file_path.find("lib/"); - if (pos != std::string::npos) { - file_path = file_path.substr(pos + 4); - pos = file_path.find('/'); - if (pos != std::string::npos) { - file_path = file_path.substr(0, pos); - } + auto file_path = it->Next()->GetSource().path.c_str(); - architectures.insert(file_path); + const char* last_slash = + android::util::ValidLibraryPathLastSlash(file_path, has_renderscript_bitcode, false); + if (last_slash) { + architectures_from_apk.insert(std::string(file_path + APK_LIB_LEN, last_slash)); } } // Determine if the application has multiArch supports - auto has_multi_arch = FindElement(root.get(), [&](ManifestExtractor::Element* el) -> bool { - if (auto application = ElementCast<Application>(el)) { - return application->has_multi_arch; - } - return false; - }); + auto has_multi_arch = + FindElement(root_element_.get(), [&](ManifestExtractor::Element* el) -> bool { + if (auto application = ElementCast<Application>(el)) { + return application->has_multi_arch; + } + return false; + }); bool output_alt_native_code = false; // A multiArch package is one that contains 64-bit and @@ -2366,84 +2808,151 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { const std::string kIntel64 = "x86_64"; const std::string kArm64 = "arm64-v8a"; - auto arch = architectures.find(kIntel64); - if (arch == architectures.end()) { - arch = architectures.find(kArm64); + auto arch = architectures_from_apk.find(kIntel64); + if (arch == architectures_from_apk.end()) { + arch = architectures_from_apk.find(kArm64); } - if (arch != architectures.end()) { - printer->Print(StringPrintf("native-code: '%s'\n", arch->data())); - architectures.erase(arch); + if (arch != architectures_from_apk.end()) { + architectures_.architectures.insert(*arch); + architectures_from_apk.erase(arch); output_alt_native_code = true; } } - - if (architectures.size() > 0) { + for (auto& arch : architectures_from_apk) { if (output_alt_native_code) { - printer->Print("alt-"); + architectures_.alt_architectures.insert(arch); + } else { + architectures_.architectures.insert(arch); } - printer->Print("native-code:"); - for (auto& arch : architectures) { - printer->Print(StringPrintf(" '%s'", arch.data())); + } + return true; +} + +bool ManifestExtractor::Dump(text::Printer* printer) { + Print(root_element_.get(), printer); + if (options_.only_permissions) { + return true; + } + + for (auto& implied_permission : implied_permissions_) { + implied_permission->Print(printer); + } + for (auto& feature_group : feature_groups_) { + feature_group->PrintGroup(printer); + } + components_.Print(printer); + supports_screen_->PrintScreens(printer, target_sdk_); + + // Print all the unique locales of the apk + printer->Print("locales:"); + for (auto& config : locales_) { + if (config.first.empty()) { + printer->Print(" '--_--'"); + } else { + printer->Print(StringPrintf(" '%s'", config.first.data())); } - printer->Print("\n"); } + printer->Print("\n"); + + // Print all the densities locales of the apk + printer->Print("densities:"); + for (auto& config : densities_) { + printer->Print(StringPrintf(" '%d'", config.first)); + } + printer->Print("\n"); + architectures_.Print(printer); return true; } +bool ManifestExtractor::DumpProto(pb::Badging* out_badging) { + ToProto(root_element_.get(), out_badging); + for (auto& implied_permission : implied_permissions_) { + implied_permission->ToProto(out_badging); + } + for (auto& feature_group : feature_groups_) { + feature_group->GroupToProto(out_badging); + } + components_.ToProto(out_badging); + supports_screen_->ToProtoScreens(out_badging, target_sdk_); + + for (auto& config : locales_) { + if (config.first.empty()) { + out_badging->add_locales("--_--"); + } else { + out_badging->add_locales(config.first); + } + } + for (auto& config : densities_) { + out_badging->add_densities(config.first); + } + + architectures_.ToProto(out_badging); + return true; +} + +template <typename T> +constexpr const char* GetExpectedTagForType() { + // This array does not appear at runtime, as GetExpectedTagForType function is used by compiler + // to inject proper 'expected_tag' into ElementCast. + std::array<std::pair<const char*, bool>, 38> tags = { + std::make_pair("action", std::is_same<Action, T>::value), + std::make_pair("activity", std::is_same<Activity, T>::value), + std::make_pair("additional-certificate", std::is_same<AdditionalCertificate, T>::value), + std::make_pair("application", std::is_same<Application, T>::value), + std::make_pair("category", std::is_same<Category, T>::value), + std::make_pair("compatible-screens", std::is_same<CompatibleScreens, T>::value), + std::make_pair("feature-group", std::is_same<FeatureGroup, T>::value), + std::make_pair("input-type", std::is_same<InputType, T>::value), + std::make_pair("install-constraints", std::is_same<InstallConstraints, T>::value), + std::make_pair("intent-filter", std::is_same<IntentFilter, T>::value), + std::make_pair("meta-data", std::is_same<MetaData, T>::value), + std::make_pair("manifest", std::is_same<Manifest, T>::value), + std::make_pair("original-package", std::is_same<OriginalPackage, T>::value), + std::make_pair("overlay", std::is_same<Overlay, T>::value), + std::make_pair("package-verifier", std::is_same<PackageVerifier, T>::value), + std::make_pair("permission", std::is_same<Permission, T>::value), + std::make_pair("property", std::is_same<Property, T>::value), + std::make_pair("provider", std::is_same<Provider, T>::value), + std::make_pair("receiver", std::is_same<Receiver, T>::value), + std::make_pair("required-feature", std::is_same<RequiredFeature, T>::value), + std::make_pair("required-not-feature", std::is_same<RequiredNotFeature, T>::value), + std::make_pair("screen", std::is_same<Screen, T>::value), + std::make_pair("service", std::is_same<Service, T>::value), + std::make_pair("sdk-library", std::is_same<SdkLibrary, T>::value), + std::make_pair("static-library", std::is_same<StaticLibrary, T>::value), + std::make_pair("supports-gl-texture", std::is_same<SupportsGlTexture, T>::value), + std::make_pair("supports-input", std::is_same<SupportsInput, T>::value), + std::make_pair("supports-screens", std::is_same<SupportsScreen, T>::value), + std::make_pair("uses-configuration", std::is_same<UsesConfiguarion, T>::value), + std::make_pair("uses-feature", std::is_same<UsesFeature, T>::value), + std::make_pair("uses-library", std::is_same<UsesLibrary, T>::value), + std::make_pair("uses-native-library", std::is_same<UsesNativeLibrary, T>::value), + std::make_pair("uses-package", std::is_same<UsesPackage, T>::value), + std::make_pair("uses-permission", std::is_same<UsesPermission, T>::value), + std::make_pair("uses-permission-sdk-23", std::is_same<UsesPermissionSdk23, T>::value), + std::make_pair("uses-sdk", std::is_same<UsesSdkBadging, T>::value), + std::make_pair("uses-sdk-library", std::is_same<UsesSdkLibrary, T>::value), + std::make_pair("uses-static-library", std::is_same<UsesStaticLibrary, T>::value), + }; + for (const auto& pair : tags) { + if (pair.second) { + return pair.first; + } + } + return nullptr; +} + /** * Returns the element casted to the type if the element is of that type. Otherwise, returns a null * pointer. **/ template<typename T> T* ElementCast(ManifestExtractor::Element* element) { - if (element == nullptr) { - return nullptr; - } - - const std::unordered_map<std::string, bool> kTagCheck = { - {"action", std::is_base_of<Action, T>::value}, - {"activity", std::is_base_of<Activity, T>::value}, - {"additional-certificate", std::is_base_of<AdditionalCertificate, T>::value}, - {"application", std::is_base_of<Application, T>::value}, - {"category", std::is_base_of<Category, T>::value}, - {"compatible-screens", std::is_base_of<CompatibleScreens, T>::value}, - {"feature-group", std::is_base_of<FeatureGroup, T>::value}, - {"input-type", std::is_base_of<InputType, T>::value}, - {"intent-filter", std::is_base_of<IntentFilter, T>::value}, - {"meta-data", std::is_base_of<MetaData, T>::value}, - {"manifest", std::is_base_of<Manifest, T>::value}, - {"original-package", std::is_base_of<OriginalPackage, T>::value}, - {"overlay", std::is_base_of<Overlay, T>::value}, - {"package-verifier", std::is_base_of<PackageVerifier, T>::value}, - {"permission", std::is_base_of<Permission, T>::value}, - {"property", std::is_base_of<Property, T>::value}, - {"provider", std::is_base_of<Provider, T>::value}, - {"receiver", std::is_base_of<Receiver, T>::value}, - {"required-feature", std::is_base_of<RequiredFeature, T>::value}, - {"required-not-feature", std::is_base_of<RequiredNotFeature, T>::value}, - {"screen", std::is_base_of<Screen, T>::value}, - {"service", std::is_base_of<Service, T>::value}, - {"sdk-library", std::is_base_of<SdkLibrary, T>::value}, - {"static-library", std::is_base_of<StaticLibrary, T>::value}, - {"supports-gl-texture", std::is_base_of<SupportsGlTexture, T>::value}, - {"supports-input", std::is_base_of<SupportsInput, T>::value}, - {"supports-screens", std::is_base_of<SupportsScreen, T>::value}, - {"uses-configuration", std::is_base_of<UsesConfiguarion, T>::value}, - {"uses-feature", std::is_base_of<UsesFeature, T>::value}, - {"uses-library", std::is_base_of<UsesLibrary, T>::value}, - {"uses-native-library", std::is_base_of<UsesNativeLibrary, T>::value}, - {"uses-package", std::is_base_of<UsesPackage, T>::value}, - {"uses-permission", std::is_base_of<UsesPermission, T>::value}, - {"uses-permission-sdk-23", std::is_base_of<UsesPermissionSdk23, T>::value}, - {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value}, - {"uses-sdk-library", std::is_base_of<UsesSdkLibrary, T>::value}, - {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value}, - }; - - auto check = kTagCheck.find(element->tag()); - if (check != kTagCheck.end() && check->second) { + constexpr const char* expected_tag = GetExpectedTagForType<T>(); + if (element != nullptr && expected_tag != nullptr && element->is_featured() && + element->tag() == expected_tag) { return static_cast<T*>(element); } return nullptr; @@ -2455,9 +2964,9 @@ std::unique_ptr<T> CreateType() { } std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( - ManifestExtractor* extractor, xml::Element* el) { - const std::unordered_map<std::string, - std::function<std::unique_ptr<ManifestExtractor::Element>()>> + ManifestExtractor* extractor, xml::Element* el, const std::string& parent_tag) { + static const std::unordered_map<std::string_view, + std::function<std::unique_ptr<ManifestExtractor::Element>()>> kTagCheck = { {"action", &CreateType<Action>}, {"activity", &CreateType<Activity>}, @@ -2467,6 +2976,7 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( {"compatible-screens", &CreateType<CompatibleScreens>}, {"feature-group", &CreateType<FeatureGroup>}, {"input-type", &CreateType<InputType>}, + {"install-constraints", &CreateType<InstallConstraints>}, {"intent-filter", &CreateType<IntentFilter>}, {"manifest", &CreateType<Manifest>}, {"meta-data", &CreateType<MetaData>}, @@ -2497,12 +3007,71 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( {"uses-sdk-library", &CreateType<UsesSdkLibrary>}, {"uses-static-library", &CreateType<UsesStaticLibrary>}, }; - + static constexpr std::array<std::pair<std::string_view, std::string_view>, 53> + kValidChildParentTags = { + std::make_pair("action", "intent-filter"), + std::make_pair("activity", "application"), + std::make_pair("additional-certificate", "uses-package"), + std::make_pair("additional-certificate", "uses-static-library"), + std::make_pair("application", "manifest"), + std::make_pair("category", "intent-filter"), + std::make_pair("compatible-screens", "manifest"), + std::make_pair("feature-group", "manifest"), + std::make_pair("input-type", "supports-input"), + std::make_pair("intent-filter", "activity"), + std::make_pair("intent-filter", "activity-alias"), + std::make_pair("intent-filter", "service"), + std::make_pair("intent-filter", "receiver"), + std::make_pair("intent-filter", "provider"), + std::make_pair("manifest", ""), + std::make_pair("meta-data", "activity"), + std::make_pair("meta-data", "activity-alias"), + std::make_pair("meta-data", "application"), + std::make_pair("meta-data", "service"), + std::make_pair("meta-data", "receiver"), + std::make_pair("meta-data", "provider"), + std::make_pair("original-package", "manifest"), + std::make_pair("overlay", "manifest"), + std::make_pair("package-verifier", "manifest"), + std::make_pair("permission", "manifest"), + std::make_pair("property", "activity"), + std::make_pair("property", "activity-alias"), + std::make_pair("property", "application"), + std::make_pair("property", "service"), + std::make_pair("property", "receiver"), + std::make_pair("property", "provider"), + std::make_pair("provider", "application"), + std::make_pair("receiver", "application"), + std::make_pair("required-feature", "uses-permission"), + std::make_pair("required-not-feature", "uses-permission"), + std::make_pair("screen", "compatible-screens"), + std::make_pair("service", "application"), + std::make_pair("sdk-library", "application"), + std::make_pair("static-library", "application"), + std::make_pair("supports-gl-texture", "manifest"), + std::make_pair("supports-input", "manifest"), + std::make_pair("supports-screens", "manifest"), + std::make_pair("uses-configuration", "manifest"), + std::make_pair("uses-feature", "feature-group"), + std::make_pair("uses-feature", "manifest"), + std::make_pair("uses-library", "application"), + std::make_pair("uses-native-library", "application"), + std::make_pair("uses-package", "application"), + std::make_pair("uses-permission", "manifest"), + std::make_pair("uses-permission-sdk-23", "manifest"), + std::make_pair("uses-sdk", "manifest"), + std::make_pair("uses-sdk-library", "application"), + std::make_pair("uses-static-library", "application"), + }; + bool is_valid_tag = std::find(kValidChildParentTags.begin(), kValidChildParentTags.end(), + std::make_pair<std::string_view, std::string_view>( + el->name, parent_tag)) != kValidChildParentTags.end(); // Attempt to map the xml tag to a element inflater std::unique_ptr<ManifestExtractor::Element> element; auto check = kTagCheck.find(el->name); - if (check != kTagCheck.end()) { + if (check != kTagCheck.end() && is_valid_tag) { element = check->second(); + element->featured_ = true; } else { element = util::make_unique<ManifestExtractor::Element>(); } @@ -2513,13 +3082,14 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( return element; } -std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Visit(xml::Element* el) { - auto element = ManifestExtractor::Element::Inflate(this, el); +std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Visit( + xml::Element* el, const std::string& parent_tag) { + auto element = ManifestExtractor::Element::Inflate(this, el, parent_tag); parent_stack_.insert(parent_stack_.begin(), element.get()); // Process the element and recursively visit the children for (xml::Element* child : el->GetChildElements()) { - auto v = Visit(child); + auto v = Visit(child, el->name); element->AddChild(v); } @@ -2527,11 +3097,23 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Visit(xml::Elemen return element; } - int DumpManifest(LoadedApk* apk, DumpManifestOptions& options, text::Printer* printer, - IDiagnostics* diag) { + android::IDiagnostics* diag) { ManifestExtractor extractor(apk, options); - return extractor.Dump(printer, diag) ? 0 : 1; + if (!extractor.Extract(diag)) { + return 1; + } + return extractor.Dump(printer) ? 0 : 1; +} + +int DumpBadgingProto(LoadedApk* apk, pb::Badging* out_badging, android::IDiagnostics* diag) { + DumpManifestOptions options{/* include_meta_data= */ true, + /* only_permissions= */ false}; + ManifestExtractor extractor(apk, options); + if (!extractor.Extract(diag)) { + return 1; + } + return extractor.DumpProto(out_badging) ? 0 : 1; } } // namespace aapt diff --git a/tools/aapt2/dump/DumpManifest.h b/tools/aapt2/dump/DumpManifest.h index daf22ed57a84..138b9e377b8a 100644 --- a/tools/aapt2/dump/DumpManifest.h +++ b/tools/aapt2/dump/DumpManifest.h @@ -17,8 +17,9 @@ #ifndef AAPT2_DUMP_MANIFEST_H #define AAPT2_DUMP_MANIFEST_H -#include "Diagnostics.h" +#include "ApkInfo.pb.h" #include "LoadedApk.h" +#include "androidfw/IDiagnostics.h" #include "text/Printer.h" namespace aapt { @@ -32,8 +33,11 @@ struct DumpManifestOptions { /** Print information extracted from the manifest of the APK. */ int DumpManifest(LoadedApk* apk, DumpManifestOptions& options, text::Printer* printer, - IDiagnostics* diag); + android::IDiagnostics* diag); + +/** Extracts badging data from the manifest of the APK and stores it in Badging proto. */ +int DumpBadgingProto(LoadedApk* apk, pb::Badging* out_badging, android::IDiagnostics* diag); } // namespace aapt -#endif // AAPT2_DUMP_MANIFEST_H
\ No newline at end of file +#endif // AAPT2_DUMP_MANIFEST_H diff --git a/tools/aapt2/filter/AbiFilter.cpp b/tools/aapt2/filter/AbiFilter.cpp index 9ace82ad4af7..908b1714bd14 100644 --- a/tools/aapt2/filter/AbiFilter.cpp +++ b/tools/aapt2/filter/AbiFilter.cpp @@ -23,15 +23,15 @@ namespace aapt { std::unique_ptr<AbiFilter> AbiFilter::FromAbiList(const std::vector<configuration::Abi>& abi_list) { - std::unordered_set<std::string> abi_set; + std::unordered_set<std::string_view> abi_set; for (auto& abi : abi_list) { - abi_set.insert(configuration::AbiToString(abi).to_string()); + abi_set.insert(configuration::AbiToString(abi)); } // Make unique by hand as the constructor is private. - return std::unique_ptr<AbiFilter>(new AbiFilter(abi_set)); + return std::unique_ptr<AbiFilter>(new AbiFilter(std::move(abi_set))); } -bool AbiFilter::Keep(const std::string& path) { +bool AbiFilter::Keep(std::string_view path) { // We only care about libraries. if (!util::StartsWith(path, kLibPrefix)) { return true; @@ -44,7 +44,7 @@ bool AbiFilter::Keep(const std::string& path) { } // Strip the lib/ prefix. - const std::string& path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen); + const auto path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen); return (abis_.find(path_abi) != abis_.end()); } diff --git a/tools/aapt2/filter/AbiFilter.h b/tools/aapt2/filter/AbiFilter.h index 2832711efb2c..7380f3f479ae 100644 --- a/tools/aapt2/filter/AbiFilter.h +++ b/tools/aapt2/filter/AbiFilter.h @@ -18,7 +18,7 @@ #define AAPT2_ABISPLITTER_H #include <memory> -#include <string> +#include <string_view> #include <unordered_set> #include <vector> @@ -39,16 +39,16 @@ class AbiFilter : public IPathFilter { static std::unique_ptr<AbiFilter> FromAbiList(const std::vector<configuration::Abi>& abi_list); /** Returns true if the path is for a native library in the list of desired ABIs. */ - bool Keep(const std::string& path) override; + bool Keep(std::string_view path) override; private: - explicit AbiFilter(std::unordered_set<std::string> abis) : abis_(std::move(abis)) { + explicit AbiFilter(std::unordered_set<std::string_view> abis) : abis_(std::move(abis)) { } /** The path prefix to where all native libs end up inside an APK file. */ static constexpr const char* kLibPrefix = "lib/"; static constexpr size_t kLibPrefixLen = 4; - const std::unordered_set<std::string> abis_; + const std::unordered_set<std::string_view> abis_; }; } // namespace aapt diff --git a/tools/aapt2/filter/Filter.h b/tools/aapt2/filter/Filter.h index f932f9ccc82e..baf4791f76c8 100644 --- a/tools/aapt2/filter/Filter.h +++ b/tools/aapt2/filter/Filter.h @@ -18,6 +18,7 @@ #define AAPT2_FILTER_H #include <string> +#include <string_view> #include <vector> #include "util/Util.h" @@ -30,7 +31,7 @@ class IPathFilter { virtual ~IPathFilter() = default; /** Returns true if the path should be kept. */ - virtual bool Keep(const std::string& path) = 0; + virtual bool Keep(std::string_view path) = 0; }; /** @@ -42,7 +43,7 @@ class PrefixFilter : public IPathFilter { } /** Returns true if the provided path matches the prefix. */ - bool Keep(const std::string& path) override { + bool Keep(std::string_view path) override { return util::StartsWith(path, prefix_); } @@ -59,7 +60,7 @@ class FilterChain : public IPathFilter { } /** Returns true if all filters keep the path. */ - bool Keep(const std::string& path) override { + bool Keep(std::string_view path) override { for (auto& filter : filters_) { if (!filter->Keep(path)) { return false; diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp index c20b053c37b1..e9a93d8b12ad 100644 --- a/tools/aapt2/format/Archive.cpp +++ b/tools/aapt2/format/Archive.cpp @@ -25,9 +25,9 @@ #include "android-base/macros.h" #include "android-base/utf8.h" #include "androidfw/StringPiece.h" -#include "ziparchive/zip_writer.h" - #include "util/Files.h" +#include "util/Util.h" +#include "ziparchive/zip_writer.h" using ::android::StringPiece; using ::android::base::SystemErrorCodeToString; @@ -40,8 +40,8 @@ class DirectoryWriter : public IArchiveWriter { public: DirectoryWriter() = default; - bool Open(const StringPiece& out_dir) { - dir_ = out_dir.to_string(); + bool Open(StringPiece out_dir) { + dir_ = std::string(out_dir); file::FileType type = file::GetFileType(dir_); if (type == file::FileType::kNonExistant) { error_ = "directory does not exist"; @@ -53,14 +53,14 @@ class DirectoryWriter : public IArchiveWriter { return true; } - bool StartEntry(const StringPiece& path, uint32_t flags) override { + bool StartEntry(StringPiece path, uint32_t flags) override { if (file_) { return false; } std::string full_path = dir_; file::AppendPath(&full_path, path); - file::mkdirs(file::GetStem(full_path).to_string()); + file::mkdirs(std::string(file::GetStem(full_path))); file_ = {::android::base::utf8::fopen(full_path.c_str(), "wb"), fclose}; if (!file_) { @@ -91,7 +91,7 @@ class DirectoryWriter : public IArchiveWriter { return true; } - bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override { + bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override { if (!StartEntry(path, flags)) { return false; } @@ -132,8 +132,8 @@ class ZipFileWriter : public IArchiveWriter { public: ZipFileWriter() = default; - bool Open(const StringPiece& path) { - file_ = {::android::base::utf8::fopen(path.to_string().c_str(), "w+b"), fclose}; + bool Open(StringPiece path) { + file_ = {::android::base::utf8::fopen(path.data(), "w+b"), fclose}; if (!file_) { error_ = SystemErrorCodeToString(errno); return false; @@ -142,7 +142,7 @@ class ZipFileWriter : public IArchiveWriter { return true; } - bool StartEntry(const StringPiece& path, uint32_t flags) override { + bool StartEntry(StringPiece path, uint32_t flags) override { if (!writer_) { return false; } @@ -182,7 +182,7 @@ class ZipFileWriter : public IArchiveWriter { return true; } - bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override { + bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override { while (true) { if (!StartEntry(path, flags)) { return false; @@ -256,21 +256,21 @@ class ZipFileWriter : public IArchiveWriter { } // namespace -std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag, - const StringPiece& path) { +std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnostics* diag, + StringPiece path) { std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>(); if (!writer->Open(path)) { - diag->Error(DiagMessage(path) << writer->GetError()); + diag->Error(android::DiagMessage(path) << writer->GetError()); return {}; } return std::move(writer); } -std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(IDiagnostics* diag, - const StringPiece& path) { +std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(android::IDiagnostics* diag, + StringPiece path) { std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>(); if (!writer->Open(path)) { - diag->Error(DiagMessage(path) << writer->GetError()); + diag->Error(android::DiagMessage(path) << writer->GetError()); return {}; } return std::move(writer); diff --git a/tools/aapt2/format/Archive.h b/tools/aapt2/format/Archive.h index 4e8a39df9165..6cde753a255d 100644 --- a/tools/aapt2/format/Archive.h +++ b/tools/aapt2/format/Archive.h @@ -22,12 +22,11 @@ #include <string> #include <vector> +#include "androidfw/BigBuffer.h" +#include "androidfw/IDiagnostics.h" #include "androidfw/StringPiece.h" #include "google/protobuf/io/zero_copy_stream_impl_lite.h" - -#include "Diagnostics.h" #include "io/Io.h" -#include "util/BigBuffer.h" #include "util/Files.h" namespace aapt { @@ -47,12 +46,12 @@ class IArchiveWriter : public ::google::protobuf::io::CopyingOutputStream { public: virtual ~IArchiveWriter() = default; - virtual bool WriteFile(const android::StringPiece& path, uint32_t flags, io::InputStream* in) = 0; + virtual bool WriteFile(android::StringPiece path, uint32_t flags, io::InputStream* in) = 0; // Starts a new entry and allows caller to write bytes to it sequentially. // Only use StartEntry if code you do not control needs to write to a CopyingOutputStream. // Prefer WriteFile instead of manually calling StartEntry/FinishEntry. - virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0; + virtual bool StartEntry(android::StringPiece path, uint32_t flags) = 0; // Called to finish writing an entry previously started by StartEntry. // Prefer WriteFile instead of manually calling StartEntry/FinishEntry. @@ -70,11 +69,11 @@ class IArchiveWriter : public ::google::protobuf::io::CopyingOutputStream { virtual std::string GetError() const = 0; }; -std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag, - const android::StringPiece& path); +std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnostics* diag, + android::StringPiece path); -std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(IDiagnostics* diag, - const android::StringPiece& path); +std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(android::IDiagnostics* diag, + android::StringPiece path); } // namespace aapt diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp index ceed3740f37a..3c44da710d94 100644 --- a/tools/aapt2/format/Archive_test.cpp +++ b/tools/aapt2/format/Archive_test.cpp @@ -50,7 +50,7 @@ std::unique_ptr<IArchiveWriter> MakeDirectoryWriter(const std::string& output_pa } std::unique_ptr<IArchiveWriter> MakeZipFileWriter(const std::string& output_path) { - file::mkdirs(file::GetStem(output_path).to_string()); + file::mkdirs(std::string(file::GetStem(output_path))); std::remove(output_path.c_str()); StdErrDiagnostics diag; diff --git a/tools/aapt2/format/Container.cpp b/tools/aapt2/format/Container.cpp index 9cef7b3d2ce3..1ff6c4996b91 100644 --- a/tools/aapt2/format/Container.cpp +++ b/tools/aapt2/format/Container.cpp @@ -76,7 +76,7 @@ bool ContainerWriter::AddResTableEntry(const pb::ResourceTable& table) { coded_out.WriteLittleEndian32(kResTable); // Write the aligned size. - const ::google::protobuf::uint64 size = table.ByteSize(); + const size_t size = table.ByteSizeLong(); const int padding = CalculatePaddingForAlignment(size); coded_out.WriteLittleEndian64(size); @@ -109,7 +109,7 @@ bool ContainerWriter::AddResFileEntry(const pb::internal::CompiledFile& file, coded_out.WriteLittleEndian32(kResFile); // Write the aligned size. - const ::google::protobuf::uint32 header_size = file.ByteSize(); + const size_t header_size = file.ByteSizeLong(); const int header_padding = CalculatePaddingForAlignment(header_size); const ::google::protobuf::uint64 data_size = in->TotalSize(); const int data_padding = CalculatePaddingForAlignment(data_size); diff --git a/tools/aapt2/format/Container.h b/tools/aapt2/format/Container.h index aa5c82cd322c..121c592537bf 100644 --- a/tools/aapt2/format/Container.h +++ b/tools/aapt2/format/Container.h @@ -19,14 +19,13 @@ #include <inttypes.h> -#include "google/protobuf/io/coded_stream.h" -#include "google/protobuf/io/zero_copy_stream.h" - #include "Resources.pb.h" #include "ResourcesInternal.pb.h" +#include "androidfw/BigBuffer.h" +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/io/zero_copy_stream.h" #include "io/Io.h" #include "io/Util.h" -#include "util/BigBuffer.h" namespace aapt { diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index c65c55024bab..75dcba581c90 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -18,19 +18,19 @@ #include <algorithm> #include <map> +#include <optional> #include <string> +#include "ResourceTable.h" +#include "ResourceUtils.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" #include "android-base/logging.h" #include "android-base/macros.h" #include "android-base/stringprintf.h" #include "androidfw/ResourceTypes.h" +#include "androidfw/Source.h" #include "androidfw/TypeWrappers.h" - -#include "ResourceTable.h" -#include "ResourceUtils.h" -#include "ResourceValues.h" -#include "Source.h" -#include "ValueVisitor.h" #include "format/binary/ResChunkPullParser.h" #include "util/Util.h" @@ -50,7 +50,7 @@ static std::u16string strcpy16_dtoh(const char16_t* src, size_t len) { std::u16string dst; dst.resize(utf16_len); for (size_t i = 0; i < utf16_len; i++) { - dst[i] = util::DeviceToHost16(src[i]); + dst[i] = android::util::DeviceToHost16(src[i]); } return dst; } @@ -87,8 +87,8 @@ class ReferenceIdToNameVisitor : public DescendingValueVisitor { } // namespace BinaryResourceParser::BinaryResourceParser(IDiagnostics* diag, ResourceTable* table, - const Source& source, const void* data, size_t len, - io::IFileCollection* files) + const android::Source& source, const void* data, + size_t len, io::IFileCollection* files) : diag_(diag), table_(table), source_(source), data_(data), data_len_(len), files_(files) { } @@ -96,13 +96,13 @@ bool BinaryResourceParser::Parse() { ResChunkPullParser parser(data_, data_len_); if (!ResChunkPullParser::IsGoodEvent(parser.Next())) { - diag_->Error(DiagMessage(source_) << "corrupt resources.arsc: " << parser.error()); + diag_->Error(android::DiagMessage(source_) << "corrupt resources.arsc: " << parser.error()); return false; } if (parser.chunk()->type != android::RES_TABLE_TYPE) { - diag_->Error(DiagMessage(source_) << StringPrintf("unknown chunk of type 0x%02x", - static_cast<int>(parser.chunk()->type))); + diag_->Error(android::DiagMessage(source_) << StringPrintf( + "unknown chunk of type 0x%02x", static_cast<int>(parser.chunk()->type))); return false; } @@ -112,18 +112,18 @@ bool BinaryResourceParser::Parse() { if (parser.Next() != ResChunkPullParser::Event::kEndDocument) { if (parser.event() == ResChunkPullParser::Event::kBadDocument) { - diag_->Warn(DiagMessage(source_) + diag_->Warn(android::DiagMessage(source_) << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error()); } else { - diag_->Warn(DiagMessage(source_) + diag_->Warn(android::DiagMessage(source_) << StringPrintf("unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE", static_cast<int>(parser.chunk()->type))); } } if (!staged_entries_to_remove_.empty()) { - diag_->Error(DiagMessage(source_) << "didn't find " << staged_entries_to_remove_.size() - << " original staged resources"); + diag_->Error(android::DiagMessage(source_) << "didn't find " << staged_entries_to_remove_.size() + << " original staged resources"); return false; } @@ -134,20 +134,20 @@ bool BinaryResourceParser::Parse() { bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) { const ResTable_header* table_header = ConvertTo<ResTable_header>(chunk); if (!table_header) { - diag_->Error(DiagMessage(source_) << "corrupt ResTable_header chunk"); + diag_->Error(android::DiagMessage(source_) << "corrupt ResTable_header chunk"); return false; } ResChunkPullParser parser(GetChunkData(&table_header->header), GetChunkDataLen(&table_header->header)); while (ResChunkPullParser::IsGoodEvent(parser.Next())) { - switch (util::DeviceToHost16(parser.chunk()->type)) { + switch (android::util::DeviceToHost16(parser.chunk()->type)) { case android::RES_STRING_POOL_TYPE: if (value_pool_.getError() == NO_INIT) { - status_t err = - value_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); + status_t err = value_pool_.setTo(parser.chunk(), + android::util::DeviceToHost32(parser.chunk()->size)); if (err != NO_ERROR) { - diag_->Error(DiagMessage(source_) + diag_->Error(android::DiagMessage(source_) << "corrupt string pool in ResTable: " << value_pool_.getError()); return false; } @@ -155,7 +155,7 @@ bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) { // Reserve some space for the strings we are going to add. table_->string_pool.HintWillAdd(value_pool_.size(), value_pool_.styleCount()); } else { - diag_->Warn(DiagMessage(source_) << "unexpected string pool in ResTable"); + diag_->Warn(android::DiagMessage(source_) << "unexpected string pool in ResTable"); } break; @@ -166,15 +166,15 @@ bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) { break; default: - diag_->Warn(DiagMessage(source_) + diag_->Warn(android::DiagMessage(source_) << "unexpected chunk type " - << static_cast<int>(util::DeviceToHost16(parser.chunk()->type))); + << static_cast<int>(android::util::DeviceToHost16(parser.chunk()->type))); break; } } if (parser.event() == ResChunkPullParser::Event::kBadDocument) { - diag_->Error(DiagMessage(source_) << "corrupt resource table: " << parser.error()); + diag_->Error(android::DiagMessage(source_) << "corrupt resource table: " << parser.error()); return false; } return true; @@ -185,13 +185,13 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset); const ResTable_package* package_header = ConvertTo<ResTable_package, kMinPackageSize>(chunk); if (!package_header) { - diag_->Error(DiagMessage(source_) << "corrupt ResTable_package chunk"); + diag_->Error(android::DiagMessage(source_) << "corrupt ResTable_package chunk"); return false; } - uint32_t package_id = util::DeviceToHost32(package_header->id); + uint32_t package_id = android::util::DeviceToHost32(package_header->id); if (package_id > std::numeric_limits<uint8_t>::max()) { - diag_->Error(DiagMessage(source_) << "package ID is too big (" << package_id << ")"); + diag_->Error(android::DiagMessage(source_) << "package ID is too big (" << package_id << ")"); return false; } @@ -199,9 +199,10 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { std::u16string package_name = strcpy16_dtoh((const char16_t*)package_header->name, arraysize(package_header->name)); - ResourceTablePackage* package = table_->FindOrCreatePackage(util::Utf16ToUtf8(package_name)); + ResourceTablePackage* package = + table_->FindOrCreatePackage(android::util::Utf16ToUtf8(package_name)); if (!package) { - diag_->Error(DiagMessage(source_) + diag_->Error(android::DiagMessage(source_) << "incompatible package '" << package_name << "' with ID " << package_id); return false; } @@ -214,26 +215,28 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { ResChunkPullParser parser(GetChunkData(&package_header->header), GetChunkDataLen(&package_header->header)); while (ResChunkPullParser::IsGoodEvent(parser.Next())) { - switch (util::DeviceToHost16(parser.chunk()->type)) { + switch (android::util::DeviceToHost16(parser.chunk()->type)) { case android::RES_STRING_POOL_TYPE: if (type_pool_.getError() == NO_INIT) { status_t err = - type_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); + type_pool_.setTo(parser.chunk(), android::util::DeviceToHost32(parser.chunk()->size)); if (err != NO_ERROR) { - diag_->Error(DiagMessage(source_) << "corrupt type string pool in " - << "ResTable_package: " << type_pool_.getError()); + diag_->Error(android::DiagMessage(source_) + << "corrupt type string pool in " + << "ResTable_package: " << type_pool_.getError()); return false; } } else if (key_pool_.getError() == NO_INIT) { status_t err = - key_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); + key_pool_.setTo(parser.chunk(), android::util::DeviceToHost32(parser.chunk()->size)); if (err != NO_ERROR) { - diag_->Error(DiagMessage(source_) << "corrupt key string pool in " - << "ResTable_package: " << key_pool_.getError()); + diag_->Error(android::DiagMessage(source_) + << "corrupt key string pool in " + << "ResTable_package: " << key_pool_.getError()); return false; } } else { - diag_->Warn(DiagMessage(source_) << "unexpected string pool"); + diag_->Warn(android::DiagMessage(source_) << "unexpected string pool"); } break; @@ -268,15 +271,15 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { break; default: - diag_->Warn(DiagMessage(source_) + diag_->Warn(android::DiagMessage(source_) << "unexpected chunk type " - << static_cast<int>(util::DeviceToHost16(parser.chunk()->type))); + << static_cast<int>(android::util::DeviceToHost16(parser.chunk()->type))); break; } } if (parser.event() == ResChunkPullParser::Event::kBadDocument) { - diag_->Error(DiagMessage(source_) << "corrupt ResTable_package: " << parser.error()); + diag_->Error(android::DiagMessage(source_) << "corrupt ResTable_package: " << parser.error()); return false; } @@ -290,18 +293,19 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package, const ResChunk_header* chunk, uint8_t package_id) { if (type_pool_.getError() != NO_ERROR) { - diag_->Error(DiagMessage(source_) << "missing type string pool"); + diag_->Error(android::DiagMessage(source_) << "missing type string pool"); return false; } const ResTable_typeSpec* type_spec = ConvertTo<ResTable_typeSpec>(chunk); if (!type_spec) { - diag_->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk"); + diag_->Error(android::DiagMessage(source_) << "corrupt ResTable_typeSpec chunk"); return false; } if (type_spec->id == 0) { - diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id); + diag_->Error(android::DiagMessage(source_) + << "ResTable_typeSpec has invalid id: " << type_spec->id); return false; } @@ -312,25 +316,26 @@ bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package, // There can only be 2^16 entries in a type, because that is the ID // space for entries (EEEE) in the resource ID 0xPPTTEEEE. if (entry_count > std::numeric_limits<uint16_t>::max()) { - diag_->Error(DiagMessage(source_) + diag_->Error(android::DiagMessage(source_) << "ResTable_typeSpec has too many entries (" << entry_count << ")"); return false; } - const size_t data_size = util::DeviceToHost32(type_spec->header.size) - - util::DeviceToHost16(type_spec->header.headerSize); + const size_t data_size = android::util::DeviceToHost32(type_spec->header.size) - + android::util::DeviceToHost16(type_spec->header.headerSize); if (entry_count * sizeof(uint32_t) > data_size) { - diag_->Error(DiagMessage(source_) << "ResTable_typeSpec too small to hold entries."); + diag_->Error(android::DiagMessage(source_) << "ResTable_typeSpec too small to hold entries."); return false; } // Record the type_spec_flags for later. We don't know resource names yet, and we need those // to mark resources as overlayable. const uint32_t* type_spec_flags = reinterpret_cast<const uint32_t*>( - reinterpret_cast<uintptr_t>(type_spec) + util::DeviceToHost16(type_spec->header.headerSize)); + reinterpret_cast<uintptr_t>(type_spec) + + android::util::DeviceToHost16(type_spec->header.headerSize)); for (size_t i = 0; i < entry_count; i++) { ResourceId id(package_id, type_spec->id, static_cast<size_t>(i)); - entry_type_spec_flags_[id] = util::DeviceToHost32(type_spec_flags[i]); + entry_type_spec_flags_[id] = android::util::DeviceToHost32(type_spec_flags[i]); } return true; } @@ -338,12 +343,12 @@ bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package, bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, const ResChunk_header* chunk, uint8_t package_id) { if (type_pool_.getError() != NO_ERROR) { - diag_->Error(DiagMessage(source_) << "missing type string pool"); + diag_->Error(android::DiagMessage(source_) << "missing type string pool"); return false; } if (key_pool_.getError() != NO_ERROR) { - diag_->Error(DiagMessage(source_) << "missing key string pool"); + diag_->Error(android::DiagMessage(source_) << "missing key string pool"); return false; } @@ -351,23 +356,24 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, // a lot and has its own code to handle variable size. const ResTable_type* type = ConvertTo<ResTable_type, kResTableTypeMinSize>(chunk); if (!type) { - diag_->Error(DiagMessage(source_) << "corrupt ResTable_type chunk"); + diag_->Error(android::DiagMessage(source_) << "corrupt ResTable_type chunk"); return false; } if (type->id == 0) { - diag_->Error(DiagMessage(source_) << "ResTable_type has invalid id: " << (int)type->id); + diag_->Error(android::DiagMessage(source_) + << "ResTable_type has invalid id: " << (int)type->id); return false; } ConfigDescription config; config.copyFromDtoH(type->config); - const std::string type_str = util::GetString(type_pool_, type->id - 1); - const ResourceType* parsed_type = ParseResourceType(type_str); + const std::string type_str = android::util::GetString(type_pool_, type->id - 1); + std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(type_str); if (!parsed_type) { - diag_->Warn(DiagMessage(source_) - << "invalid type name '" << type_str << "' for type with ID " << type->id); + diag_->Warn(android::DiagMessage(source_) + << "invalid type name '" << type_str << "' for type with ID " << int(type->id)); return true; } @@ -379,24 +385,21 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, } const ResourceName name(package->name, *parsed_type, - util::GetString(key_pool_, util::DeviceToHost32(entry->key.index))); + android::util::GetString(key_pool_, entry->key())); const ResourceId res_id(package_id, type->id, static_cast<uint16_t>(it.index())); std::unique_ptr<Value> resource_value; - if (entry->flags & ResTable_entry::FLAG_COMPLEX) { - const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry); - + if (auto mapEntry = entry->map_entry()) { // TODO(adamlesinski): Check that the entry count is valid. resource_value = ParseMapEntry(name, config, mapEntry); } else { - const Res_value* value = - (const Res_value*)((const uint8_t*)entry + util::DeviceToHost32(entry->size)); - resource_value = ParseValue(name, config, *value); + resource_value = ParseValue(name, config, entry->value()); } if (!resource_value) { - diag_->Error(DiagMessage(source_) << "failed to parse value for resource " << name << " (" - << res_id << ") with configuration '" << config << "'"); + diag_->Error(android::DiagMessage(source_) + << "failed to parse value for resource " << name << " (" << res_id + << ") with configuration '" << config << "'"); return false; } @@ -411,7 +414,7 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, .SetId(res_id, OnIdConflict::CREATE_ENTRY) .SetAllowMangled(true); - if (entry->flags & ResTable_entry::FLAG_PUBLIC) { + if (entry->flags() & ResTable_entry::FLAG_PUBLIC) { Visibility visibility{Visibility::Level::kPublic}; auto spec_flags = entry_type_spec_flags_.find(res_id); @@ -450,7 +453,7 @@ bool BinaryResourceParser::ParseLibrary(const ResChunk_header* chunk) { const size_t count = entries.size(); for (size_t i = 0; i < count; i++) { table_->included_packages_[entries.valueAt(i)] = - util::Utf16ToUtf8(StringPiece16(entries.keyAt(i).string())); + android::util::Utf16ToUtf8(StringPiece16(entries.keyAt(i).string())); } return true; } @@ -458,36 +461,39 @@ bool BinaryResourceParser::ParseLibrary(const ResChunk_header* chunk) { bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { const ResTable_overlayable_header* header = ConvertTo<ResTable_overlayable_header>(chunk); if (!header) { - diag_->Error(DiagMessage(source_) << "corrupt ResTable_category_header chunk"); + diag_->Error(android::DiagMessage(source_) << "corrupt ResTable_category_header chunk"); return false; } auto overlayable = std::make_shared<Overlayable>(); - overlayable->name = util::Utf16ToUtf8(strcpy16_dtoh((const char16_t*)header->name, - arraysize(header->name))); - overlayable->actor = util::Utf16ToUtf8(strcpy16_dtoh((const char16_t*)header->actor, - arraysize(header->name))); + overlayable->name = android::util::Utf16ToUtf8( + strcpy16_dtoh((const char16_t*)header->name, arraysize(header->name))); + overlayable->actor = android::util::Utf16ToUtf8( + strcpy16_dtoh((const char16_t*)header->actor, arraysize(header->name))); ResChunkPullParser parser(GetChunkData(chunk), GetChunkDataLen(chunk)); while (ResChunkPullParser::IsGoodEvent(parser.Next())) { - if (util::DeviceToHost16(parser.chunk()->type) == android::RES_TABLE_OVERLAYABLE_POLICY_TYPE) { + if (android::util::DeviceToHost16(parser.chunk()->type) == + android::RES_TABLE_OVERLAYABLE_POLICY_TYPE) { const ResTable_overlayable_policy_header* policy_header = ConvertTo<ResTable_overlayable_policy_header>(parser.chunk()); const ResTable_ref* const ref_begin = reinterpret_cast<const ResTable_ref*>( - ((uint8_t *)policy_header) + util::DeviceToHost32(policy_header->header.headerSize)); - const ResTable_ref* const ref_end = ref_begin - + util::DeviceToHost32(policy_header->entry_count); + ((uint8_t*)policy_header) + + android::util::DeviceToHost32(policy_header->header.headerSize)); + const ResTable_ref* const ref_end = + ref_begin + android::util::DeviceToHost32(policy_header->entry_count); for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) { - ResourceId res_id(util::DeviceToHost32(ref_iter->ident)); + ResourceId res_id(android::util::DeviceToHost32(ref_iter->ident)); const auto iter = id_index_.find(res_id); // If the overlayable chunk comes before the type chunks, the resource ids and resource name // pairing will not exist at this point. if (iter == id_index_.cend()) { - diag_->Error(DiagMessage(source_) << "failed to find resource name for overlayable" - << " resource " << res_id); + diag_->Error(android::DiagMessage(source_) + << "failed to find resource name for overlayable" + << " resource " << res_id); return false; } @@ -511,23 +517,23 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { bool BinaryResourceParser::ParseStagedAliases(const ResChunk_header* chunk) { auto header = ConvertTo<ResTable_staged_alias_header>(chunk); if (!header) { - diag_->Error(DiagMessage(source_) << "corrupt ResTable_staged_alias_header chunk"); + diag_->Error(android::DiagMessage(source_) << "corrupt ResTable_staged_alias_header chunk"); return false; } const auto ref_begin = reinterpret_cast<const ResTable_staged_alias_entry*>( - ((uint8_t*)header) + util::DeviceToHost32(header->header.headerSize)); - const auto ref_end = ref_begin + util::DeviceToHost32(header->count); + ((uint8_t*)header) + android::util::DeviceToHost32(header->header.headerSize)); + const auto ref_end = ref_begin + android::util::DeviceToHost32(header->count); for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) { - const auto staged_id = ResourceId(util::DeviceToHost32(ref_iter->stagedResId)); - const auto finalized_id = ResourceId(util::DeviceToHost32(ref_iter->finalizedResId)); + const auto staged_id = ResourceId(android::util::DeviceToHost32(ref_iter->stagedResId)); + const auto finalized_id = ResourceId(android::util::DeviceToHost32(ref_iter->finalizedResId)); // If the staged alias chunk comes before the type chunks, the resource ids and resource name // pairing will not exist at this point. const auto iter = id_index_.find(finalized_id); if (iter == id_index_.cend()) { - diag_->Error(DiagMessage(source_) << "failed to find resource name for finalized" - << " resource ID " << finalized_id); + diag_->Error(android::DiagMessage(source_) << "failed to find resource name for finalized" + << " resource ID " << finalized_id); return false; } @@ -563,9 +569,9 @@ std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& na if (file_ref != nullptr) { file_ref->file = files_->FindFile(*file_ref->path); if (file_ref->file == nullptr) { - diag_->Warn(DiagMessage() << "resource " << name << " for config '" << config - << "' is a file reference to '" << *file_ref->path - << "' but no such path exists"); + diag_->Warn(android::DiagMessage() << "resource " << name << " for config '" << config + << "' is a file reference to '" << *file_ref->path + << "' but no such path exists"); } } } @@ -594,8 +600,8 @@ std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry(const ResourceNameRef // We can ignore the value here. return util::make_unique<Id>(); default: - diag_->Error(DiagMessage() << "illegal map type '" << name.type << "' (" - << (int)name.type.type << ")"); + diag_->Error(android::DiagMessage() + << "illegal map type '" << name.type << "' (" << (int)name.type.type << ")"); break; } return {}; @@ -605,18 +611,18 @@ std::unique_ptr<Style> BinaryResourceParser::ParseStyle(const ResourceNameRef& n const ConfigDescription& config, const ResTable_map_entry* map) { std::unique_ptr<Style> style = util::make_unique<Style>(); - if (util::DeviceToHost32(map->parent.ident) != 0) { + if (android::util::DeviceToHost32(map->parent.ident) != 0) { // The parent is a regular reference to a resource. - style->parent = Reference(util::DeviceToHost32(map->parent.ident)); + style->parent = Reference(android::util::DeviceToHost32(map->parent.ident)); } for (const ResTable_map& map_entry : map) { - if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) { + if (Res_INTERNALID(android::util::DeviceToHost32(map_entry.name.ident))) { continue; } Style::Entry style_entry; - style_entry.key = Reference(util::DeviceToHost32(map_entry.name.ident)); + style_entry.key = Reference(android::util::DeviceToHost32(map_entry.name.ident)); style_entry.value = ParseValue(name, config, map_entry.value); if (!style_entry.value) { return {}; @@ -630,20 +636,20 @@ std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr(const ResourceNameRef const ConfigDescription& config, const ResTable_map_entry* map) { std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(); - attr->SetWeak((util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0); + attr->SetWeak((android::util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0); // First we must discover what type of attribute this is. Find the type mask. auto type_mask_iter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool { - return util::DeviceToHost32(entry.name.ident) == ResTable_map::ATTR_TYPE; + return android::util::DeviceToHost32(entry.name.ident) == ResTable_map::ATTR_TYPE; }); if (type_mask_iter != end(map)) { - attr->type_mask = util::DeviceToHost32(type_mask_iter->value.data); + attr->type_mask = android::util::DeviceToHost32(type_mask_iter->value.data); } for (const ResTable_map& map_entry : map) { - if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) { - switch (util::DeviceToHost32(map_entry.name.ident)) { + if (Res_INTERNALID(android::util::DeviceToHost32(map_entry.name.ident))) { + switch (android::util::DeviceToHost32(map_entry.name.ident)) { case ResTable_map::ATTR_MIN: attr->min_int = static_cast<int32_t>(map_entry.value.data); break; @@ -656,9 +662,9 @@ std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr(const ResourceNameRef if (attr->type_mask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) { Attribute::Symbol symbol; - symbol.value = util::DeviceToHost32(map_entry.value.data); + symbol.value = android::util::DeviceToHost32(map_entry.value.data); symbol.type = map_entry.value.dataType; - symbol.symbol = Reference(util::DeviceToHost32(map_entry.name.ident)); + symbol.symbol = Reference(android::util::DeviceToHost32(map_entry.name.ident)); attr->symbols.push_back(std::move(symbol)); } } @@ -687,7 +693,7 @@ std::unique_ptr<Plural> BinaryResourceParser::ParsePlural(const ResourceNameRef& return {}; } - switch (util::DeviceToHost32(map_entry.name.ident)) { + switch (android::util::DeviceToHost32(map_entry.name.ident)) { case ResTable_map::ATTR_ZERO: plural->values[Plural::Zero] = std::move(item); break; diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h index 1c83166c5cce..8f6949e7e630 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.h +++ b/tools/aapt2/format/binary/BinaryResourceParser.h @@ -19,13 +19,13 @@ #include <string> +#include "ResourceTable.h" +#include "ResourceValues.h" #include "android-base/macros.h" #include "androidfw/ConfigDescription.h" #include "androidfw/ResourceTypes.h" - -#include "ResourceTable.h" -#include "ResourceValues.h" -#include "Source.h" +#include "androidfw/Source.h" +#include "androidfw/Util.h" #include "process/IResourceTableConsumer.h" #include "util/Util.h" @@ -40,8 +40,9 @@ class BinaryResourceParser { public: // Creates a parser, which will read `len` bytes from `data`, and add any resources parsed to // `table`. `source` is for logging purposes. - BinaryResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, - const void* data, size_t data_len, io::IFileCollection* files = nullptr); + BinaryResourceParser(android::IDiagnostics* diag, ResourceTable* table, + const android::Source& source, const void* data, size_t data_len, + io::IFileCollection* files = nullptr); // Parses the binary resource table and returns true if successful. bool Parse(); @@ -91,10 +92,10 @@ class BinaryResourceParser { */ bool CollectMetaData(const android::ResTable_map& map_entry, Value* value); - IDiagnostics* diag_; + android::IDiagnostics* diag_; ResourceTable* table_; - const Source source_; + const android::Source source_; const void* data_; const size_t data_len_; @@ -132,11 +133,11 @@ namespace android { // Iterator functionality for ResTable_map_entry. inline const ResTable_map* begin(const ResTable_map_entry* map) { - return (const ResTable_map*)((const uint8_t*)map + ::aapt::util::DeviceToHost32(map->size)); + return (const ResTable_map*)((const uint8_t*)map + android::util::DeviceToHost32(map->size)); } inline const ResTable_map* end(const ResTable_map_entry* map) { - return begin(map) + aapt::util::DeviceToHost32(map->count); + return begin(map) + android::util::DeviceToHost32(map->count); } } // namespace android diff --git a/tools/aapt2/format/binary/ChunkWriter.h b/tools/aapt2/format/binary/ChunkWriter.h index 1892a295dcf5..e1a403476ff8 100644 --- a/tools/aapt2/format/binary/ChunkWriter.h +++ b/tools/aapt2/format/binary/ChunkWriter.h @@ -18,16 +18,15 @@ #define AAPT_FORMAT_BINARY_CHUNKWRITER_H #include "android-base/macros.h" +#include "androidfw/BigBuffer.h" #include "androidfw/ResourceTypes.h" - -#include "util/BigBuffer.h" #include "util/Util.h" namespace aapt { class ChunkWriter { public: - explicit inline ChunkWriter(BigBuffer* buffer) : buffer_(buffer) { + explicit inline ChunkWriter(android::BigBuffer* buffer) : buffer_(buffer) { } ChunkWriter(ChunkWriter&&) = default; ChunkWriter& operator=(ChunkWriter&&) = default; @@ -37,8 +36,8 @@ class ChunkWriter { start_size_ = buffer_->size(); T* chunk = buffer_->NextBlock<T>(); header_ = &chunk->header; - header_->type = util::HostToDevice16(type); - header_->headerSize = util::HostToDevice16(sizeof(T)); + header_->type = android::util::HostToDevice16(type); + header_->headerSize = android::util::HostToDevice16(sizeof(T)); return chunk; } @@ -47,7 +46,7 @@ class ChunkWriter { return buffer_->NextBlock<T>(count); } - inline BigBuffer* buffer() { + inline android::BigBuffer* buffer() { return buffer_; } @@ -61,14 +60,14 @@ class ChunkWriter { inline android::ResChunk_header* Finish() { buffer_->Align4(); - header_->size = util::HostToDevice32(buffer_->size() - start_size_); + header_->size = android::util::HostToDevice32(buffer_->size() - start_size_); return header_; } private: DISALLOW_COPY_AND_ASSIGN(ChunkWriter); - BigBuffer* buffer_; + android::BigBuffer* buffer_; size_t start_size_ = 0; android::ResChunk_header* header_ = nullptr; }; @@ -77,8 +76,8 @@ template <> inline android::ResChunk_header* ChunkWriter::StartChunk(uint16_t type) { start_size_ = buffer_->size(); header_ = buffer_->NextBlock<android::ResChunk_header>(); - header_->type = util::HostToDevice16(type); - header_->headerSize = util::HostToDevice16(sizeof(android::ResChunk_header)); + header_->type = android::util::HostToDevice16(type); + header_->headerSize = android::util::HostToDevice16(sizeof(android::ResChunk_header)); return header_; } diff --git a/tools/aapt2/format/binary/ResChunkPullParser.cpp b/tools/aapt2/format/binary/ResChunkPullParser.cpp index fd6919d1de60..2f3df5cae829 100644 --- a/tools/aapt2/format/binary/ResChunkPullParser.cpp +++ b/tools/aapt2/format/binary/ResChunkPullParser.cpp @@ -17,12 +17,13 @@ #include "format/binary/ResChunkPullParser.h" #include <inttypes.h> + #include <cstddef> #include "android-base/logging.h" #include "android-base/stringprintf.h" #include "androidfw/ResourceTypes.h" - +#include "androidfw/Util.h" #include "util/Util.h" namespace aapt { @@ -32,8 +33,9 @@ using android::base::StringPrintf; static std::string ChunkHeaderDump(const ResChunk_header* header) { return StringPrintf("(type=%02" PRIx16 " header_size=%" PRIu16 " size=%" PRIu32 ")", - util::DeviceToHost16(header->type), util::DeviceToHost16(header->headerSize), - util::DeviceToHost32(header->size)); + android::util::DeviceToHost16(header->type), + android::util::DeviceToHost16(header->headerSize), + android::util::DeviceToHost32(header->size)); } ResChunkPullParser::Event ResChunkPullParser::Next() { @@ -45,7 +47,7 @@ ResChunkPullParser::Event ResChunkPullParser::Next() { current_chunk_ = data_; } else { current_chunk_ = (const ResChunk_header*)(((const char*)current_chunk_) + - util::DeviceToHost32(current_chunk_->size)); + android::util::DeviceToHost32(current_chunk_->size)); } const std::ptrdiff_t diff = (const char*)current_chunk_ - (const char*)data_; @@ -61,16 +63,16 @@ ResChunkPullParser::Event ResChunkPullParser::Next() { return (event_ = Event::kBadDocument); } - if (util::DeviceToHost16(current_chunk_->headerSize) < sizeof(ResChunk_header)) { + if (android::util::DeviceToHost16(current_chunk_->headerSize) < sizeof(ResChunk_header)) { error_ = "chunk has too small header"; current_chunk_ = nullptr; return (event_ = Event::kBadDocument); - } else if (util::DeviceToHost32(current_chunk_->size) < - util::DeviceToHost16(current_chunk_->headerSize)) { + } else if (android::util::DeviceToHost32(current_chunk_->size) < + android::util::DeviceToHost16(current_chunk_->headerSize)) { error_ = "chunk's total size is smaller than header " + ChunkHeaderDump(current_chunk_); current_chunk_ = nullptr; return (event_ = Event::kBadDocument); - } else if (offset + util::DeviceToHost32(current_chunk_->size) > len_) { + } else if (offset + android::util::DeviceToHost32(current_chunk_->size) > len_) { error_ = "chunk's data extends past the end of the document " + ChunkHeaderDump(current_chunk_); current_chunk_ = nullptr; return (event_ = Event::kBadDocument); diff --git a/tools/aapt2/format/binary/ResChunkPullParser.h b/tools/aapt2/format/binary/ResChunkPullParser.h index 5ff13598a31d..0f46db1876fb 100644 --- a/tools/aapt2/format/binary/ResChunkPullParser.h +++ b/tools/aapt2/format/binary/ResChunkPullParser.h @@ -21,7 +21,7 @@ #include "android-base/macros.h" #include "androidfw/ResourceTypes.h" - +#include "androidfw/Util.h" #include "util/Util.h" namespace aapt { @@ -69,18 +69,19 @@ class ResChunkPullParser { template <typename T, size_t MinSize = sizeof(T)> inline static const T* ConvertTo(const android::ResChunk_header* chunk) { - if (util::DeviceToHost16(chunk->headerSize) < MinSize) { + if (android::util::DeviceToHost16(chunk->headerSize) < MinSize) { return nullptr; } return reinterpret_cast<const T*>(chunk); } inline static const uint8_t* GetChunkData(const android::ResChunk_header* chunk) { - return reinterpret_cast<const uint8_t*>(chunk) + util::DeviceToHost16(chunk->headerSize); + return reinterpret_cast<const uint8_t*>(chunk) + android::util::DeviceToHost16(chunk->headerSize); } inline static uint32_t GetChunkDataLen(const android::ResChunk_header* chunk) { - return util::DeviceToHost32(chunk->size) - util::DeviceToHost16(chunk->headerSize); + return android::util::DeviceToHost32(chunk->size) - + android::util::DeviceToHost16(chunk->headerSize); } // diff --git a/tools/aapt2/format/binary/ResEntryWriter.cpp b/tools/aapt2/format/binary/ResEntryWriter.cpp new file mode 100644 index 000000000000..9dc205f4c1ba --- /dev/null +++ b/tools/aapt2/format/binary/ResEntryWriter.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2022 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 "format/binary/ResEntryWriter.h" + +#include "ValueVisitor.h" +#include "androidfw/BigBuffer.h" +#include "androidfw/ResourceTypes.h" +#include "androidfw/Util.h" +#include "format/binary/ResourceTypeExtensions.h" + +namespace aapt { + +struct less_style_entries { + bool operator()(const Style::Entry* a, const Style::Entry* b) const { + if (a->key.id) { + if (b->key.id) { + return cmp_ids_dynamic_after_framework(a->key.id.value(), b->key.id.value()); + } + return true; + } + if (!b->key.id) { + return a->key.name.value() < b->key.name.value(); + } + return false; + } +}; + +class MapFlattenVisitor : public ConstValueVisitor { + public: + using ConstValueVisitor::Visit; + + MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer) + : out_entry_(out_entry), buffer_(buffer) { + } + + void Visit(const Attribute* attr) override { + { + Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE)); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask); + FlattenEntry(&key, &val); + } + + if (attr->min_int != std::numeric_limits<int32_t>::min()) { + Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN)); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->min_int)); + FlattenEntry(&key, &val); + } + + if (attr->max_int != std::numeric_limits<int32_t>::max()) { + Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX)); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->max_int)); + FlattenEntry(&key, &val); + } + + for (const Attribute::Symbol& s : attr->symbols) { + BinaryPrimitive val(s.type, s.value); + FlattenEntry(&s.symbol, &val); + } + } + + void Visit(const Style* style) override { + if (style->parent) { + const Reference& parent_ref = style->parent.value(); + CHECK(bool(parent_ref.id)) << "parent has no ID"; + out_entry_->parent.ident = android::util::HostToDevice32(parent_ref.id.value().id); + } + + // Sort the style. + std::vector<const Style::Entry*> sorted_entries; + for (const auto& entry : style->entries) { + sorted_entries.emplace_back(&entry); + } + + std::sort(sorted_entries.begin(), sorted_entries.end(), less_style_entries()); + + for (const Style::Entry* entry : sorted_entries) { + FlattenEntry(&entry->key, entry->value.get()); + } + } + + void Visit(const Styleable* styleable) override { + for (auto& attr_ref : styleable->entries) { + BinaryPrimitive val(Res_value{}); + FlattenEntry(&attr_ref, &val); + } + } + + void Visit(const Array* array) override { + const size_t count = array->elements.size(); + for (size_t i = 0; i < count; i++) { + Reference key(android::ResTable_map::ATTR_MIN + i); + FlattenEntry(&key, array->elements[i].get()); + } + } + + void Visit(const Plural* plural) override { + const size_t count = plural->values.size(); + for (size_t i = 0; i < count; i++) { + if (!plural->values[i]) { + continue; + } + + ResourceId q; + switch (i) { + case Plural::Zero: + q.id = android::ResTable_map::ATTR_ZERO; + break; + + case Plural::One: + q.id = android::ResTable_map::ATTR_ONE; + break; + + case Plural::Two: + q.id = android::ResTable_map::ATTR_TWO; + break; + + case Plural::Few: + q.id = android::ResTable_map::ATTR_FEW; + break; + + case Plural::Many: + q.id = android::ResTable_map::ATTR_MANY; + break; + + case Plural::Other: + q.id = android::ResTable_map::ATTR_OTHER; + break; + + default: + LOG(FATAL) << "unhandled plural type"; + break; + } + + Reference key(q); + FlattenEntry(&key, plural->values[i].get()); + } + } + + /** + * Call this after visiting a Value. This will finish any work that + * needs to be done to prepare the entry. + */ + void Finish() { + out_entry_->count = android::util::HostToDevice32(entry_count_); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor); + + void FlattenKey(const Reference* key, ResTable_map* out_entry) { + CHECK(bool(key->id)) << "key has no ID"; + out_entry->name.ident = android::util::HostToDevice32(key->id.value().id); + } + + void FlattenValue(const Item* value, ResTable_map* out_entry) { + CHECK(value->Flatten(&out_entry->value)) << "flatten failed"; + } + + void FlattenEntry(const Reference* key, Item* value) { + ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>(); + FlattenKey(key, out_entry); + FlattenValue(value, out_entry); + out_entry->value.size = android::util::HostToDevice16(sizeof(out_entry->value)); + entry_count_++; + } + + ResTable_entry_ext* out_entry_; + BigBuffer* buffer_; + size_t entry_count_ = 0; +}; + +template <typename T> +void WriteEntry(const FlatEntry* entry, T* out_result, bool compact = false) { + static_assert(std::is_same_v<ResTable_entry, T> || std::is_same_v<ResTable_entry_ext, T>, + "T must be ResTable_entry or ResTable_entry_ext"); + + ResTable_entry* out_entry = (ResTable_entry*)out_result; + uint16_t flags = 0; + + if (entry->entry->visibility.level == Visibility::Level::kPublic) { + flags |= ResTable_entry::FLAG_PUBLIC; + } + + if (entry->value->IsWeak()) { + flags |= ResTable_entry::FLAG_WEAK; + } + + if constexpr (std::is_same_v<ResTable_entry_ext, T>) { + flags |= ResTable_entry::FLAG_COMPLEX; + } + + if (!compact) { + out_entry->full.flags = android::util::HostToDevice16(flags); + out_entry->full.key.index = android::util::HostToDevice32(entry->entry_key); + out_entry->full.size = android::util::HostToDevice16(sizeof(T)); + } else { + Res_value value; + CHECK(entry->entry_key < 0xffffu) << "cannot encode key in 16-bit"; + CHECK(compact && (std::is_same_v<ResTable_entry, T>)) << "cannot encode complex entry"; + CHECK(ValueCast<Item>(entry->value)->Flatten(&value)) << "flatten failed"; + + flags |= ResTable_entry::FLAG_COMPACT | (value.dataType << 8); + out_entry->compact.flags = android::util::HostToDevice16(flags); + out_entry->compact.key = android::util::HostToDevice16(entry->entry_key); + out_entry->compact.data = value.data; + } +} + +int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer) { + int32_t offset = buffer->size(); + ResTable_entry_ext* out_entry = buffer->NextBlock<ResTable_entry_ext>(); + WriteEntry<ResTable_entry_ext>(map_entry, out_entry); + + MapFlattenVisitor visitor(out_entry, buffer); + map_entry->value->Accept(&visitor); + visitor.Finish(); + return offset; +} + +template <bool compact_entry, typename T> +std::pair<int32_t, T*> WriteItemToBuffer(const FlatEntry* item_entry, BigBuffer* buffer) { + int32_t offset = buffer->size(); + T* out_entry = buffer->NextBlock<T>(); + + if constexpr (compact_entry) { + WriteEntry(item_entry, out_entry, true); + } else { + WriteEntry(item_entry, &out_entry->entry); + CHECK(ValueCast<Item>(item_entry->value)->Flatten(&out_entry->value)) << "flatten failed"; + out_entry->value.size = android::util::HostToDevice16(sizeof(out_entry->value)); + } + return {offset, out_entry}; +} + +// explicitly specialize both versions +template std::pair<int32_t, ResEntryValue<false>*> WriteItemToBuffer<false>( + const FlatEntry* item_entry, BigBuffer* buffer); + +template std::pair<int32_t, ResEntryValue<true>*> WriteItemToBuffer<true>( + const FlatEntry* item_entry, BigBuffer* buffer); + +} // namespace aapt diff --git a/tools/aapt2/format/binary/ResEntryWriter.h b/tools/aapt2/format/binary/ResEntryWriter.h new file mode 100644 index 000000000000..c11598ec12f7 --- /dev/null +++ b/tools/aapt2/format/binary/ResEntryWriter.h @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2022 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 AAPT_FORMAT_BINARY_RESENTRY_SERIALIZER_H +#define AAPT_FORMAT_BINARY_RESENTRY_SERIALIZER_H + +#include <unordered_map> + +#include "ResourceTable.h" +#include "ValueVisitor.h" +#include "android-base/macros.h" +#include "androidfw/BigBuffer.h" +#include "androidfw/ResourceTypes.h" + +namespace aapt { + +using android::BigBuffer; +using android::Res_value; +using android::ResTable_entry; +using android::ResTable_map; + +struct FlatEntry { + const ResourceTableEntryView* entry; + const Value* value; + + // The entry string pool index to the entry's name. + uint32_t entry_key; +}; + +// Pair of ResTable_entry and Res_value. These pairs are stored sequentially in values buffer. +// We introduce this structure for ResEntryWriter to a have single allocation using +// BigBuffer::NextBlock which allows to return it back with BigBuffer::Backup. +struct ResEntryValuePair { + ResTable_entry entry; + Res_value value; +}; + +static_assert(sizeof(ResEntryValuePair) == sizeof(ResTable_entry) + sizeof(Res_value), + "ResEntryValuePair must not have padding between entry and value."); + +template <bool compact> +using ResEntryValue = std::conditional_t<compact, ResTable_entry, ResEntryValuePair>; + +// References ResEntryValue object stored in BigBuffer used as a key in std::unordered_map. +// Allows access to memory address where ResEntryValue is stored. +template <bool compact> +union ResEntryValueRef { + using T = ResEntryValue<compact>; + const std::reference_wrapper<const T> ref; + const u_char* ptr; + + explicit ResEntryValueRef(const T& rev) : ref(rev) { + } +}; + +// Hasher which computes hash of ResEntryValue using its bytes representation in memory. +struct ResEntryValueContentHasher { + template <typename R> + std::size_t operator()(const R& ref) const { + return android::JenkinsHashMixBytes(0, ref.ptr, sizeof(typename R::T)); + } +}; + +// Equaler which compares ResEntryValuePairs using theirs bytes representation in memory. +struct ResEntryValueContentEqualTo { + template <typename R> + bool operator()(const R& a, const R& b) const { + return std::memcmp(a.ptr, b.ptr, sizeof(typename R::T)) == 0; + } +}; + +// Base class that allows to write FlatEntries into entries_buffer. +class ResEntryWriter { + public: + virtual ~ResEntryWriter() = default; + + // Writes resource table entry and its value into 'entries_buffer_' and returns offset + // in the buffer where entry was written. + int32_t Write(const FlatEntry* entry) { + if (ValueCast<Item>(entry->value) != nullptr) { + return WriteItem(entry); + } else { + return WriteMap(entry); + } + } + + protected: + ResEntryWriter(BigBuffer* entries_buffer) : entries_buffer_(entries_buffer) { + } + BigBuffer* entries_buffer_; + + virtual int32_t WriteItem(const FlatEntry* entry) = 0; + + virtual int32_t WriteMap(const FlatEntry* entry) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(ResEntryWriter); +}; + +int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer); + +template <bool compact_entry, typename T=ResEntryValue<compact_entry>> +std::pair<int32_t, T*> WriteItemToBuffer(const FlatEntry* item_entry, BigBuffer* buffer); + +// ResEntryWriter which writes FlatEntries sequentially into entries_buffer. +// Next entry is always written right after previous one in the buffer. +template <bool compact_entry = false> +class SequentialResEntryWriter : public ResEntryWriter { + public: + explicit SequentialResEntryWriter(BigBuffer* entries_buffer) + : ResEntryWriter(entries_buffer) { + } + ~SequentialResEntryWriter() override = default; + + int32_t WriteItem(const FlatEntry* entry) override { + auto result = WriteItemToBuffer<compact_entry>(entry, entries_buffer_); + return result.first; + } + + int32_t WriteMap(const FlatEntry* entry) override { + return WriteMapToBuffer(entry, entries_buffer_); + } + + private: + DISALLOW_COPY_AND_ASSIGN(SequentialResEntryWriter); +}; + +// ResEntryWriter that writes only unique entry and value pairs into entries_buffer. +// Next entry is written into buffer only if there is no entry with the same bytes representation +// in memory written before. Otherwise returns offset of already written entry. +template <bool compact_entry = false> +class DeduplicateItemsResEntryWriter : public ResEntryWriter { + public: + explicit DeduplicateItemsResEntryWriter(BigBuffer* entries_buffer) + : ResEntryWriter(entries_buffer) { + } + ~DeduplicateItemsResEntryWriter() override = default; + + int32_t WriteItem(const FlatEntry* entry) override { + const auto& [offset, out_entry] = WriteItemToBuffer<compact_entry>(entry, entries_buffer_); + + auto [it, inserted] = entry_offsets.insert({Ref{*out_entry}, offset}); + if (inserted) { + // If inserted just return a new offset as this is a first time we store + // this entry + return offset; + } + + // If not inserted this means that this is a duplicate, backup allocated block to the buffer + // and return offset of previously stored entry + entries_buffer_->BackUp(sizeof(*out_entry)); + return it->second; + } + + int32_t WriteMap(const FlatEntry* entry) override { + return WriteMapToBuffer(entry, entries_buffer_); + } + + private: + DISALLOW_COPY_AND_ASSIGN(DeduplicateItemsResEntryWriter); + + using Ref = ResEntryValueRef<compact_entry>; + using Map = std::unordered_map<Ref, int32_t, + ResEntryValueContentHasher, + ResEntryValueContentEqualTo>; + Map entry_offsets; +}; + +} // namespace aapt + +#endif diff --git a/tools/aapt2/format/binary/ResEntryWriter_test.cpp b/tools/aapt2/format/binary/ResEntryWriter_test.cpp new file mode 100644 index 000000000000..4cb17c33e64a --- /dev/null +++ b/tools/aapt2/format/binary/ResEntryWriter_test.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2022 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 "format/binary/ResEntryWriter.h" + +#include "androidfw/BigBuffer.h" +#include "format/binary/ResourceTypeExtensions.h" +#include "test/Test.h" +#include "util/Util.h" + +using ::android::BigBuffer; +using ::android::Res_value; +using ::android::ResTable_map; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::IsNull; +using ::testing::Ne; +using ::testing::NotNull; + +namespace aapt { + +using SequentialResEntryWriterTest = CommandTestFixture; +using DeduplicateItemsResEntryWriterTest = CommandTestFixture; + +std::vector<int32_t> WriteAllEntries(const ResourceTableView& table, ResEntryWriter& writer) { + std::vector<int32_t> result = {}; + for (const auto& type : table.packages[0].types) { + for (const auto& entry : type.entries) { + for (const auto& value : entry.values) { + auto flat_entry = FlatEntry{&entry, value->value.get(), 0}; + result.push_back(writer.Write(&flat_entry)); + } + } + } + return result; +} + +TEST_F(SequentialResEntryWriterTest, WriteEntriesOneByOne) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("com.app.test:id/id1", ResourceId(0x7f010000)) + .AddSimple("com.app.test:id/id2", ResourceId(0x7f010001)) + .AddSimple("com.app.test:id/id3", ResourceId(0x7f010002)) + .Build(); + + { + BigBuffer out(512); + SequentialResEntryWriter<false> writer(&out); + auto offsets = WriteAllEntries(table->GetPartitionedView(), writer); + + std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair), + 2 * sizeof(ResEntryValuePair)}; + EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair)); + EXPECT_EQ(offsets, expected_offsets); + } + + { + /* expect a compact entry to only take sizeof(ResTable_entry) */ + BigBuffer out(512); + SequentialResEntryWriter<true> writer(&out); + auto offsets = WriteAllEntries(table->GetPartitionedView(), writer); + + std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry), + 2 * sizeof(ResTable_entry)}; + EXPECT_EQ(out.size(), 3 * sizeof(ResTable_entry)); + EXPECT_EQ(offsets, expected_offsets); + } +}; + +TEST_F(SequentialResEntryWriterTest, WriteMapEntriesOneByOne) { + std::unique_ptr<Array> array1 = util::make_unique<Array>(); + array1->elements.push_back( + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)); + array1->elements.push_back( + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)); + std::unique_ptr<Array> array2 = util::make_unique<Array>(); + array2->elements.push_back( + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)); + array2->elements.push_back( + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)); + + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .AddValue("com.app.test:array/arr1", std::move(array1)) + .AddValue("com.app.test:array/arr2", std::move(array2)) + .Build(); + + { + BigBuffer out(512); + SequentialResEntryWriter<false> writer(&out); + auto offsets = WriteAllEntries(table->GetPartitionedView(), writer); + + std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)}; + EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map))); + EXPECT_EQ(offsets, expected_offsets); + } + + { + /* compact_entry should have no impact to map items */ + BigBuffer out(512); + SequentialResEntryWriter<true> writer(&out); + auto offsets = WriteAllEntries(table->GetPartitionedView(), writer); + + std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)}; + EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map))); + EXPECT_EQ(offsets, expected_offsets); + } +}; + +TEST_F(DeduplicateItemsResEntryWriterTest, DeduplicateItemEntries) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("com.app.test:id/id1", ResourceId(0x7f010000)) + .AddSimple("com.app.test:id/id2", ResourceId(0x7f010001)) + .AddSimple("com.app.test:id/id3", ResourceId(0x7f010002)) + .Build(); + + { + BigBuffer out(512); + DeduplicateItemsResEntryWriter<false> writer(&out); + auto offsets = WriteAllEntries(table->GetPartitionedView(), writer); + + std::vector<int32_t> expected_offsets{0, 0, 0}; + EXPECT_EQ(out.size(), sizeof(ResEntryValuePair)); + EXPECT_EQ(offsets, expected_offsets); + } + + { + /* expect a compact entry to only take sizeof(ResTable_entry) */ + BigBuffer out(512); + DeduplicateItemsResEntryWriter<true> writer(&out); + auto offsets = WriteAllEntries(table->GetPartitionedView(), writer); + + std::vector<int32_t> expected_offsets{0, 0, 0}; + EXPECT_EQ(out.size(), sizeof(ResTable_entry)); + EXPECT_EQ(offsets, expected_offsets); + } +}; + +TEST_F(DeduplicateItemsResEntryWriterTest, WriteMapEntriesOneByOne) { + std::unique_ptr<Array> array1 = util::make_unique<Array>(); + array1->elements.push_back( + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)); + array1->elements.push_back( + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)); + std::unique_ptr<Array> array2 = util::make_unique<Array>(); + array2->elements.push_back( + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)); + array2->elements.push_back( + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)); + + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .AddValue("com.app.test:array/arr1", std::move(array1)) + .AddValue("com.app.test:array/arr2", std::move(array2)) + .Build(); + + { + BigBuffer out(512); + DeduplicateItemsResEntryWriter<false> writer(&out); + auto offsets = WriteAllEntries(table->GetPartitionedView(), writer); + + std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)}; + EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map))); + EXPECT_EQ(offsets, expected_offsets); + } + + { + /* compact_entry should have no impact to map items */ + BigBuffer out(512); + DeduplicateItemsResEntryWriter<true> writer(&out); + auto offsets = WriteAllEntries(table->GetPartitionedView(), writer); + + std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)}; + EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map))); + EXPECT_EQ(offsets, expected_offsets); + } + }; + +} // namespace aapt
\ No newline at end of file diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index a9192e889c17..8c594ba553a0 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -16,24 +16,24 @@ #include "format/binary/TableFlattener.h" -#include <algorithm> -#include <numeric> +#include <limits> #include <sstream> #include <type_traits> +#include <variant> +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "SdkConstants.h" #include "android-base/logging.h" #include "android-base/macros.h" #include "android-base/stringprintf.h" +#include "androidfw/BigBuffer.h" #include "androidfw/ResourceUtils.h" - -#include "ResourceTable.h" -#include "ResourceValues.h" -#include "SdkConstants.h" -#include "ValueVisitor.h" #include "format/binary/ChunkWriter.h" +#include "format/binary/ResEntryWriter.h" #include "format/binary/ResourceTypeExtensions.h" +#include "optimize/Obfuscator.h" #include "trace/TraceBuffer.h" -#include "util/BigBuffer.h" using namespace android; @@ -54,225 +54,68 @@ static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) { size_t i; const char16_t* src_data = src.data(); for (i = 0; i < len - 1 && i < src.size(); i++) { - dst[i] = util::HostToDevice16((uint16_t)src_data[i]); + dst[i] = android::util::HostToDevice16((uint16_t)src_data[i]); } dst[i] = 0; } -static bool cmp_style_entries(const Style::Entry* a, const Style::Entry* b) { - if (a->key.id) { - if (b->key.id) { - return cmp_ids_dynamic_after_framework(a->key.id.value(), b->key.id.value()); - } - return true; - } else if (!b->key.id) { - return a->key.name.value() < b->key.name.value(); - } - return false; -} - -struct FlatEntry { - const ResourceTableEntryView* entry; - const Value* value; - - // The entry string pool index to the entry's name. - uint32_t entry_key; -}; - -class MapFlattenVisitor : public ConstValueVisitor { - public: - using ConstValueVisitor::Visit; - - MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer) - : out_entry_(out_entry), buffer_(buffer) { - } - - void Visit(const Attribute* attr) override { - { - Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE)); - BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask); - FlattenEntry(&key, &val); - } - - if (attr->min_int != std::numeric_limits<int32_t>::min()) { - Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN)); - BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->min_int)); - FlattenEntry(&key, &val); - } - - if (attr->max_int != std::numeric_limits<int32_t>::max()) { - Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX)); - BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->max_int)); - FlattenEntry(&key, &val); - } - - for (const Attribute::Symbol& s : attr->symbols) { - BinaryPrimitive val(s.type, s.value); - FlattenEntry(&s.symbol, &val); - } - } - - void Visit(const Style* style) override { - if (style->parent) { - const Reference& parent_ref = style->parent.value(); - CHECK(bool(parent_ref.id)) << "parent has no ID"; - out_entry_->parent.ident = util::HostToDevice32(parent_ref.id.value().id); - } - - // Sort the style. - std::vector<const Style::Entry*> sorted_entries; - for (const auto& entry : style->entries) { - sorted_entries.emplace_back(&entry); - } - - std::sort(sorted_entries.begin(), sorted_entries.end(), cmp_style_entries); - - for (const Style::Entry* entry : sorted_entries) { - FlattenEntry(&entry->key, entry->value.get()); - } - } - - void Visit(const Styleable* styleable) override { - for (auto& attr_ref : styleable->entries) { - BinaryPrimitive val(Res_value{}); - FlattenEntry(&attr_ref, &val); - } - } - - void Visit(const Array* array) override { - const size_t count = array->elements.size(); - for (size_t i = 0; i < count; i++) { - Reference key(android::ResTable_map::ATTR_MIN + i); - FlattenEntry(&key, array->elements[i].get()); - } - } - - void Visit(const Plural* plural) override { - const size_t count = plural->values.size(); - for (size_t i = 0; i < count; i++) { - if (!plural->values[i]) { - continue; - } - - ResourceId q; - switch (i) { - case Plural::Zero: - q.id = android::ResTable_map::ATTR_ZERO; - break; - - case Plural::One: - q.id = android::ResTable_map::ATTR_ONE; - break; - - case Plural::Two: - q.id = android::ResTable_map::ATTR_TWO; - break; - - case Plural::Few: - q.id = android::ResTable_map::ATTR_FEW; - break; - - case Plural::Many: - q.id = android::ResTable_map::ATTR_MANY; - break; - - case Plural::Other: - q.id = android::ResTable_map::ATTR_OTHER; - break; - - default: - LOG(FATAL) << "unhandled plural type"; - break; - } - - Reference key(q); - FlattenEntry(&key, plural->values[i].get()); - } - } - - /** - * Call this after visiting a Value. This will finish any work that - * needs to be done to prepare the entry. - */ - void Finish() { - out_entry_->count = util::HostToDevice32(entry_count_); - } - - private: - DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor); - - void FlattenKey(const Reference* key, ResTable_map* out_entry) { - CHECK(bool(key->id)) << "key has no ID"; - out_entry->name.ident = util::HostToDevice32(key->id.value().id); - } - - void FlattenValue(const Item* value, ResTable_map* out_entry) { - CHECK(value->Flatten(&out_entry->value)) << "flatten failed"; - } - - void FlattenEntry(const Reference* key, Item* value) { - ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>(); - FlattenKey(key, out_entry); - FlattenValue(value, out_entry); - out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value)); - entry_count_++; - } - - ResTable_entry_ext* out_entry_; - BigBuffer* buffer_; - size_t entry_count_ = 0; -}; - struct OverlayableChunk { std::string actor; - Source source; + android::Source source; std::map<PolicyFlags, std::set<ResourceId>> policy_ids; }; class PackageFlattener { public: PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package, - const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries, + const std::map<size_t, std::string>* shared_libs, + SparseEntriesMode sparse_entries, + bool compact_entries, bool collapse_key_stringpool, - const std::set<ResourceName>& name_collapse_exemptions) + const std::set<ResourceName>& name_collapse_exemptions, + bool deduplicate_entry_values) : context_(context), diag_(context->GetDiagnostics()), package_(package), shared_libs_(shared_libs), - use_sparse_entries_(use_sparse_entries), + sparse_entries_(sparse_entries), + compact_entries_(compact_entries), collapse_key_stringpool_(collapse_key_stringpool), - name_collapse_exemptions_(name_collapse_exemptions) { + name_collapse_exemptions_(name_collapse_exemptions), + deduplicate_entry_values_(deduplicate_entry_values) { } bool FlattenPackage(BigBuffer* buffer) { TRACE_CALL(); ChunkWriter pkg_writer(buffer); ResTable_package* pkg_header = pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE); - pkg_header->id = util::HostToDevice32(package_.id.value()); + pkg_header->id = android::util::HostToDevice32(package_.id.value()); // AAPT truncated the package name, so do the same. // Shared libraries require full package names, so don't truncate theirs. if (context_->GetPackageType() != PackageType::kApp && package_.name.size() >= arraysize(pkg_header->name)) { - diag_->Error(DiagMessage() << "package name '" << package_.name - << "' is too long. " - "Shared libraries cannot have truncated package names"); + diag_->Error(android::DiagMessage() + << "package name '" << package_.name + << "' is too long. " + "Shared libraries cannot have truncated package names"); return false; } // Copy the package name in device endianness. - strcpy16_htod(pkg_header->name, arraysize(pkg_header->name), util::Utf8ToUtf16(package_.name)); + strcpy16_htod(pkg_header->name, arraysize(pkg_header->name), + android::util::Utf8ToUtf16(package_.name)); // Serialize the types. We do this now so that our type and key strings // are populated. We write those first. - BigBuffer type_buffer(1024); + android::BigBuffer type_buffer(1024); FlattenTypes(&type_buffer); - pkg_header->typeStrings = util::HostToDevice32(pkg_writer.size()); - StringPool::FlattenUtf16(pkg_writer.buffer(), type_pool_, diag_); + pkg_header->typeStrings = android::util::HostToDevice32(pkg_writer.size()); + android::StringPool::FlattenUtf16(pkg_writer.buffer(), type_pool_, diag_); - pkg_header->keyStrings = util::HostToDevice32(pkg_writer.size()); - StringPool::FlattenUtf8(pkg_writer.buffer(), key_pool_, diag_); + pkg_header->keyStrings = android::util::HostToDevice32(pkg_writer.size()); + android::StringPool::FlattenUtf8(pkg_writer.buffer(), key_pool_, diag_); // Append the types. buffer->AppendBuffer(std::move(type_buffer)); @@ -297,45 +140,31 @@ class PackageFlattener { private: DISALLOW_COPY_AND_ASSIGN(PackageFlattener); - template <typename T, bool IsItem> - T* WriteEntry(FlatEntry* entry, BigBuffer* buffer) { - static_assert( - std::is_same<ResTable_entry, T>::value || std::is_same<ResTable_entry_ext, T>::value, - "T must be ResTable_entry or ResTable_entry_ext"); - - T* result = buffer->NextBlock<T>(); - ResTable_entry* out_entry = (ResTable_entry*)result; - if (entry->entry->visibility.level == Visibility::Level::kPublic) { - out_entry->flags |= ResTable_entry::FLAG_PUBLIC; - } - - if (entry->value->IsWeak()) { - out_entry->flags |= ResTable_entry::FLAG_WEAK; - } - - if (!IsItem) { - out_entry->flags |= ResTable_entry::FLAG_COMPLEX; - } - - out_entry->flags = util::HostToDevice16(out_entry->flags); - out_entry->key.index = util::HostToDevice32(entry->entry_key); - out_entry->size = util::HostToDevice16(sizeof(T)); - return result; + // Use compact entries only if + // 1) it is enabled, and that + // 2) the entries will be accessed on platforms U+, and + // 3) all entry keys can be encoded in 16 bits + bool UseCompactEntries(const ConfigDescription& config, std::vector<FlatEntry>* entries) const { + return compact_entries_ && + (context_->GetMinSdkVersion() > SDK_TIRAMISU || config.sdkVersion > SDK_TIRAMISU) && + std::none_of(entries->cbegin(), entries->cend(), + [](const auto& e) { return e.entry_key >= std::numeric_limits<uint16_t>::max(); }); } - bool FlattenValue(FlatEntry* entry, BigBuffer* buffer) { - if (const Item* item = ValueCast<Item>(entry->value)) { - WriteEntry<ResTable_entry, true>(entry, buffer); - Res_value* outValue = buffer->NextBlock<Res_value>(); - CHECK(item->Flatten(outValue)) << "flatten failed"; - outValue->size = util::HostToDevice16(sizeof(*outValue)); + std::unique_ptr<ResEntryWriter> GetResEntryWriter(bool dedup, bool compact, BigBuffer* buffer) { + if (dedup) { + if (compact) { + return std::make_unique<DeduplicateItemsResEntryWriter<true>>(buffer); + } else { + return std::make_unique<DeduplicateItemsResEntryWriter<false>>(buffer); + } } else { - ResTable_entry_ext* out_entry = WriteEntry<ResTable_entry_ext, false>(entry, buffer); - MapFlattenVisitor visitor(out_entry, buffer); - entry->value->Accept(&visitor); - visitor.Finish(); + if (compact) { + return std::make_unique<SequentialResEntryWriter<true>>(buffer); + } else { + return std::make_unique<SequentialResEntryWriter<false>>(buffer); + } } - return true; } bool FlattenConfig(const ResourceTableTypeView& type, const ConfigDescription& config, @@ -353,28 +182,33 @@ class PackageFlattener { std::vector<uint32_t> offsets; offsets.resize(num_total_entries, 0xffffffffu); - BigBuffer values_buffer(512); + bool compact_entry = UseCompactEntries(config, entries); + + android::BigBuffer values_buffer(512); + auto res_entry_writer = GetResEntryWriter(deduplicate_entry_values_, + compact_entry, &values_buffer); + for (FlatEntry& flat_entry : *entries) { CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries); - offsets[flat_entry.entry->id.value()] = values_buffer.size(); - if (!FlattenValue(&flat_entry, &values_buffer)) { - diag_->Error(DiagMessage() - << "failed to flatten resource '" - << ResourceNameRef(package_.name, type.type, flat_entry.entry->name) - << "' for configuration '" << config << "'"); - return false; - } + offsets[flat_entry.entry->id.value()] = res_entry_writer->Write(&flat_entry); } - bool sparse_encode = use_sparse_entries_; + // whether the offsets can be represented in 2 bytes + bool short_offsets = (values_buffer.size() / 4u) < std::numeric_limits<uint16_t>::max(); - // Only sparse encode if the entries will be read on platforms O+. - sparse_encode = - sparse_encode && (context_->GetMinSdkVersion() >= SDK_O || config.sdkVersion >= SDK_O); + bool sparse_encode = sparse_entries_ == SparseEntriesMode::Enabled || + sparse_entries_ == SparseEntriesMode::Forced; + + if (sparse_entries_ == SparseEntriesMode::Forced || + (context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0)) { + // Sparse encode if forced or sdk version is not set in context and config. + } else { + // Otherwise, only sparse encode if the entries will be read on platforms S_V2+. + sparse_encode = sparse_encode && (context_->GetMinSdkVersion() >= SDK_S_V2); + } // Only sparse encode if the offsets are representable in 2 bytes. - sparse_encode = - sparse_encode && (values_buffer.size() / 4u) <= std::numeric_limits<uint16_t>::max(); + sparse_encode = sparse_encode && short_offsets; // Only sparse encode if the ratio of populated entries to total entries is below some // threshold. @@ -382,27 +216,37 @@ class PackageFlattener { sparse_encode && ((100 * entries->size()) / num_total_entries) < kSparseEncodingThreshold; if (sparse_encode) { - type_header->entryCount = util::HostToDevice32(entries->size()); + type_header->entryCount = android::util::HostToDevice32(entries->size()); type_header->flags |= ResTable_type::FLAG_SPARSE; ResTable_sparseTypeEntry* indices = type_writer.NextBlock<ResTable_sparseTypeEntry>(entries->size()); for (size_t i = 0; i < num_total_entries; i++) { if (offsets[i] != ResTable_type::NO_ENTRY) { CHECK((offsets[i] & 0x03) == 0); - indices->idx = util::HostToDevice16(i); - indices->offset = util::HostToDevice16(offsets[i] / 4u); + indices->idx = android::util::HostToDevice16(i); + indices->offset = android::util::HostToDevice16(offsets[i] / 4u); indices++; } } } else { - type_header->entryCount = util::HostToDevice32(num_total_entries); - uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries); - for (size_t i = 0; i < num_total_entries; i++) { - indices[i] = util::HostToDevice32(offsets[i]); + type_header->entryCount = android::util::HostToDevice32(num_total_entries); + if (compact_entry && short_offsets) { + // use 16-bit offset only when compact_entry is true + type_header->flags |= ResTable_type::FLAG_OFFSET16; + uint16_t* indices = type_writer.NextBlock<uint16_t>(num_total_entries); + for (size_t i = 0; i < num_total_entries; i++) { + indices[i] = android::util::HostToDevice16(offsets[i] / 4u); + } + } else { + uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries); + for (size_t i = 0; i < num_total_entries; i++) { + indices[i] = android::util::HostToDevice32(offsets[i]); + } } } - type_header->entriesStart = util::HostToDevice32(type_writer.size()); + type_writer.buffer()->Align4(); + type_header->entriesStart = android::util::HostToDevice32(type_writer.size()); type_writer.buffer()->AppendBuffer(std::move(values_buffer)); type_writer.Finish(); return true; @@ -416,12 +260,12 @@ class PackageFlattener { ChunkWriter alias_writer(buffer); auto header = alias_writer.StartChunk<ResTable_staged_alias_header>(RES_TABLE_STAGED_ALIAS_TYPE); - header->count = util::HostToDevice32(aliases_.size()); + header->count = android::util::HostToDevice32(aliases_.size()); auto mapping = alias_writer.NextBlock<ResTable_staged_alias_entry>(aliases_.size()); for (auto& p : aliases_) { - mapping->stagedResId = util::HostToDevice32(p.first); - mapping->finalizedResId = util::HostToDevice32(p.second); + mapping->stagedResId = android::util::HostToDevice32(p.first); + mapping->finalizedResId = android::util::HostToDevice32(p.second); ++mapping; } alias_writer.Finish(); @@ -447,7 +291,7 @@ class PackageFlattener { ResourceId id = android::make_resid(package_.id.value(), type.id.value(), entry.id.value()); CHECK(seen_ids.find(id) == seen_ids.end()) << "multiple overlayable definitions found for resource " - << ResourceName(package_.name, type.type, entry.name).to_string(); + << ResourceName(package_.name, type.named_type, entry.name).to_string(); seen_ids.insert(id); // Find the overlayable chunk with the specified name @@ -461,11 +305,11 @@ class PackageFlattener { OverlayableChunk& chunk = iter->second; if (!(chunk.source == item.overlayable->source)) { // The name of an overlayable set of resources must be unique - context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source) - << "duplicate overlayable name" - << item.overlayable->name << "'"); - context_->GetDiagnostics()->Error(DiagMessage(chunk.source) - << "previous declaration here"); + context_->GetDiagnostics()->Error(android::DiagMessage(item.overlayable->source) + << "duplicate overlayable name" + << item.overlayable->name << "'"); + context_->GetDiagnostics()->Error(android::DiagMessage(chunk.source) + << "previous declaration here"); return false; } @@ -474,7 +318,7 @@ class PackageFlattener { } if (item.policies == 0) { - context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source) + context_->GetDiagnostics()->Error(android::DiagMessage(item.overlayable->source) << "overlayable " << entry.name << " does not specify policy"); return false; @@ -499,38 +343,36 @@ class PackageFlattener { auto* overlayable_type = overlayable_writer.StartChunk<ResTable_overlayable_header>(RES_TABLE_OVERLAYABLE_TYPE); if (name.size() >= arraysize(overlayable_type->name)) { - diag_->Error(DiagMessage() << "overlayable name '" << name - << "' exceeds maximum length (" - << arraysize(overlayable_type->name) - << " utf16 characters)"); + diag_->Error(android::DiagMessage() + << "overlayable name '" << name << "' exceeds maximum length (" + << arraysize(overlayable_type->name) << " utf16 characters)"); return false; } strcpy16_htod(overlayable_type->name, arraysize(overlayable_type->name), - util::Utf8ToUtf16(name)); + android::util::Utf8ToUtf16(name)); if (overlayable.actor.size() >= arraysize(overlayable_type->actor)) { - diag_->Error(DiagMessage() << "overlayable name '" << overlayable.actor - << "' exceeds maximum length (" - << arraysize(overlayable_type->actor) - << " utf16 characters)"); + diag_->Error(android::DiagMessage() + << "overlayable name '" << overlayable.actor << "' exceeds maximum length (" + << arraysize(overlayable_type->actor) << " utf16 characters)"); return false; } strcpy16_htod(overlayable_type->actor, arraysize(overlayable_type->actor), - util::Utf8ToUtf16(overlayable.actor)); + android::util::Utf8ToUtf16(overlayable.actor)); // Write each policy block for the overlayable for (auto& policy_ids : overlayable.policy_ids) { ChunkWriter policy_writer(buffer); auto* policy_type = policy_writer.StartChunk<ResTable_overlayable_policy_header>( RES_TABLE_OVERLAYABLE_POLICY_TYPE); - policy_type->policy_flags = - static_cast<PolicyFlags>(util::HostToDevice32(static_cast<uint32_t>(policy_ids.first))); - policy_type->entry_count = util::HostToDevice32(static_cast<uint32_t>( - policy_ids.second.size())); + policy_type->policy_flags = static_cast<PolicyFlags>( + android::util::HostToDevice32(static_cast<uint32_t>(policy_ids.first))); + policy_type->entry_count = + android::util::HostToDevice32(static_cast<uint32_t>(policy_ids.second.size())); // Write the ids after the policy header auto* id_block = policy_writer.NextBlock<ResTable_ref>(policy_ids.second.size()); for (const ResourceId& id : policy_ids.second) { - id_block->ident = util::HostToDevice32(id.id); + id_block->ident = android::util::HostToDevice32(id.id); id_block++; } policy_writer.Finish(); @@ -559,7 +401,7 @@ class PackageFlattener { // Since the entries are sorted by ID, the last one will be the biggest. const size_t num_entries = sorted_entries.back().id.value() + 1; - spec_header->entryCount = util::HostToDevice32(num_entries); + spec_header->entryCount = android::util::HostToDevice32(num_entries); // Reserve space for the masks of each resource in this type. These // show for which configuration axis the resource changes. @@ -571,17 +413,18 @@ class PackageFlattener { // Populate the config masks for this entry. uint32_t& entry_config_masks = config_masks[entry_id]; if (entry.visibility.level == Visibility::Level::kPublic) { - entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); + entry_config_masks |= android::util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); } if (entry.visibility.staged_api) { - entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_STAGED_API); + entry_config_masks |= android::util::HostToDevice32(ResTable_typeSpec::SPEC_STAGED_API); } const size_t config_count = entry.values.size(); for (size_t i = 0; i < config_count; i++) { const ConfigDescription& config = entry.values[i]->config; for (size_t j = i + 1; j < config_count; j++) { - config_masks[entry_id] |= util::HostToDevice32(config.diff(entry.values[j]->config)); + config_masks[entry_id] |= + android::util::HostToDevice32(config.diff(entry.values[j]->config)); } } } @@ -592,7 +435,8 @@ class PackageFlattener { bool FlattenTypes(BigBuffer* buffer) { size_t expected_type_id = 1; for (const ResourceTableTypeView& type : package_.types) { - if (type.type == ResourceType::kStyleable || type.type == ResourceType::kMacro) { + if (type.named_type.type == ResourceType::kStyleable || + type.named_type.type == ResourceType::kMacro) { // Styleables and macros are not real resource types. continue; } @@ -606,7 +450,7 @@ class PackageFlattener { expected_type_id++; } expected_type_id++; - type_pool_.MakeRef(to_string(type.type)); + type_pool_.MakeRef(type.named_type.to_string()); if (!FlattenTypeSpec(type, type.entries, buffer)) { return false; @@ -623,9 +467,6 @@ class PackageFlattener { // table. std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map; - // hardcoded string uses characters which make it an invalid resource name - const std::string obfuscated_resource_name = "0_resource_name_obfuscated"; - for (const ResourceTableEntryView& entry : type.entries) { if (entry.staged_id) { aliases_.insert(std::make_pair( @@ -634,14 +475,31 @@ class PackageFlattener { } uint32_t local_key_index; - ResourceName resource_name({}, type.type, entry.name); - if (!collapse_key_stringpool_ || - name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) { - local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index(); - } else { - // resource isn't exempt from collapse, add it as obfuscated value - local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index(); - } + auto onObfuscate = [this, &local_key_index, &entry](Obfuscator::Result obfuscatedResult, + const ResourceName& resource_name) { + if (obfuscatedResult == Obfuscator::Result::Keep_ExemptionList) { + local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index(); + } else if (obfuscatedResult == Obfuscator::Result::Keep_Overlayable) { + // if the resource name of the specific entry is obfuscated and this + // entry is in the overlayable list, the overlay can't work on this + // overlayable at runtime because the name has been obfuscated in + // resources.arsc during flatten operation. + const OverlayableItem& item = entry.overlayable_item.value(); + context_->GetDiagnostics()->Warn(android::DiagMessage(item.overlayable->source) + << "The resource name of overlayable entry '" + << resource_name.to_string() + << "' shouldn't be obfuscated in resources.arsc"); + + local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index(); + } else { + local_key_index = + (uint32_t)key_pool_.MakeRef(Obfuscator::kObfuscatedResourceName).index(); + } + }; + + Obfuscator::ObfuscateResourceName(collapse_key_stringpool_, name_collapse_exemptions_, + type.named_type, entry, onObfuscate); + // Group values by configuration. for (auto& config_value : entry.values) { config_to_entry_list_map[config_value->config].push_back( @@ -667,36 +525,38 @@ class PackageFlattener { const size_t num_entries = (package_.id.value() == 0x00 ? 1 : 0) + shared_libs_->size(); CHECK(num_entries > 0); - lib_header->count = util::HostToDevice32(num_entries); + lib_header->count = android::util::HostToDevice32(num_entries); ResTable_lib_entry* lib_entry = buffer->NextBlock<ResTable_lib_entry>(num_entries); if (package_.id.value() == 0x00) { // Add this package - lib_entry->packageId = util::HostToDevice32(0x00); + lib_entry->packageId = android::util::HostToDevice32(0x00); strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName), - util::Utf8ToUtf16(package_.name)); + android::util::Utf8ToUtf16(package_.name)); ++lib_entry; } for (auto& map_entry : *shared_libs_) { - lib_entry->packageId = util::HostToDevice32(map_entry.first); + lib_entry->packageId = android::util::HostToDevice32(map_entry.first); strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName), - util::Utf8ToUtf16(map_entry.second)); + android::util::Utf8ToUtf16(map_entry.second)); ++lib_entry; } lib_writer.Finish(); } IAaptContext* context_; - IDiagnostics* diag_; + android::IDiagnostics* diag_; const ResourceTablePackageView package_; const std::map<size_t, std::string>* shared_libs_; - bool use_sparse_entries_; - StringPool type_pool_; - StringPool key_pool_; + SparseEntriesMode sparse_entries_; + bool compact_entries_; + android::StringPool type_pool_; + android::StringPool key_pool_; bool collapse_key_stringpool_; const std::set<ResourceName>& name_collapse_exemptions_; std::map<uint32_t, uint32_t> aliases_; + bool deduplicate_entry_values_; }; } // namespace @@ -705,26 +565,27 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { TRACE_CALL(); // We must do this before writing the resources, since the string pool IDs may change. table->string_pool.Prune(); - table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int { - int diff = util::compare(a.priority, b.priority); - if (diff == 0) { - diff = a.config.compare(b.config); - } - return diff; - }); + table->string_pool.Sort( + [](const android::StringPool::Context& a, const android::StringPool::Context& b) -> int { + int diff = util::compare(a.priority, b.priority); + if (diff == 0) { + diff = a.config.compare(b.config); + } + return diff; + }); // Write the ResTable header. const auto& table_view = table->GetPartitionedView(ResourceTableViewOptions{.create_alias_entries = true}); ChunkWriter table_writer(buffer_); ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE); - table_header->packageCount = util::HostToDevice32(table_view.packages.size()); + table_header->packageCount = android::util::HostToDevice32(table_view.packages.size()); // Flatten the values string pool. - StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool, - context->GetDiagnostics()); + android::StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool, + context->GetDiagnostics()); - BigBuffer package_buffer(1024); + android::BigBuffer package_buffer(1024); // Flatten each package. for (auto& package : table_view.packages) { @@ -737,7 +598,7 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { if (!result.second && result.first->second != package.name) { // A mapping for this package ID already exists, and is a different package. Error! context->GetDiagnostics()->Error( - DiagMessage() << android::base::StringPrintf( + android::DiagMessage() << android::base::StringPrintf( "can't map package ID %02x to '%s'. Already mapped to '%s'", package_id, package.name.c_str(), result.first->second.c_str())); return false; @@ -746,8 +607,11 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { } PackageFlattener flattener(context, package, &table->included_packages_, - options_.use_sparse_entries, options_.collapse_key_stringpool, - options_.name_collapse_exemptions); + options_.sparse_entries, + options_.use_compact_entries, + options_.collapse_key_stringpool, + options_.name_collapse_exemptions, + options_.deduplicate_entry_values); if (!flattener.FlattenPackage(&package_buffer)) { return false; } diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h index 4360db190146..0633bc81cb25 100644 --- a/tools/aapt2/format/binary/TableFlattener.h +++ b/tools/aapt2/format/binary/TableFlattener.h @@ -14,15 +14,19 @@ * limitations under the License. */ -#ifndef AAPT_FORMAT_BINARY_TABLEFLATTENER_H -#define AAPT_FORMAT_BINARY_TABLEFLATTENER_H +#ifndef TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_ +#define TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_ -#include "android-base/macros.h" +#include <map> +#include <set> +#include <string> +#include <unordered_map> #include "Resource.h" #include "ResourceTable.h" +#include "android-base/macros.h" +#include "androidfw/BigBuffer.h" #include "process/IResourceTableConsumer.h" -#include "util/BigBuffer.h" namespace aapt { @@ -30,12 +34,23 @@ namespace aapt { // preferred. constexpr const size_t kSparseEncodingThreshold = 60; +enum class SparseEntriesMode { + // Disables sparse encoding for entries. + Disabled, + // Enables sparse encoding for all entries for APKs with O+ minSdk. For APKs with minSdk less + // than O only applies sparse encoding for resource configuration available on O+. + Enabled, + // Enables sparse encoding for all entries regardless of minSdk. + Forced, +}; + struct TableFlattenerOptions { - // When true, types for configurations with a sparse set of entries are encoded + // When enabled, types for configurations with a sparse set of entries are encoded // as a sparse map of entry ID and offset to actual data. - // This is only available on platforms O+ and will only be respected when - // minSdk is O+. - bool use_sparse_entries = false; + SparseEntriesMode sparse_entries = SparseEntriesMode::Disabled; + + // When true, use compact entries for simple data + bool use_compact_entries = false; // When true, the key string pool in the final ResTable // is collapsed to a single entry. All resource entries @@ -45,25 +60,45 @@ struct TableFlattenerOptions { // Set of resources to avoid collapsing to a single entry in key stringpool. std::set<ResourceName> name_collapse_exemptions; + // Set of resources to avoid path shortening. + std::set<ResourceName> path_shorten_exemptions; + // Map from original resource paths to shortened resource paths. std::map<std::string, std::string> shortened_path_map; + + // When enabled, only unique pairs of entry and value are stored in type chunks. + // + // By default, all such pairs are unique because a reference to resource name in the string pool + // is a part of the pair. But when resource names are collapsed (using 'collapse_key_stringpool' + // flag or manually) the same data might be duplicated multiple times in the same type chunk. + // + // For example: an application has 3 boolean resources with collapsed names and 3 'true' values + // are defined for these resources in 'default' configuration. All pairs of entry and value for + // these resources will have the same binary representation and stored only once in type chunk + // instead of three times when this flag is disabled. + // + // This applies only to simple entries (entry->flags & ResTable_entry::FLAG_COMPLEX == 0). + bool deduplicate_entry_values = false; + + // Map from original resource ids to obfuscated names. + std::unordered_map<uint32_t, std::string> id_resource_map; }; class TableFlattener : public IResourceTableConsumer { public: - explicit TableFlattener(const TableFlattenerOptions& options, BigBuffer* buffer) + explicit TableFlattener(const TableFlattenerOptions& options, android::BigBuffer* buffer) : options_(options), buffer_(buffer) { } bool Consume(IAaptContext* context, ResourceTable* table) override; private: - DISALLOW_COPY_AND_ASSIGN(TableFlattener); - TableFlattenerOptions options_; - BigBuffer* buffer_; + android::BigBuffer* buffer_; + + DISALLOW_COPY_AND_ASSIGN(TableFlattener); }; } // namespace aapt -#endif /* AAPT_FORMAT_BINARY_TABLEFLATTENER_H */ +#endif // TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_ diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index cd1c0af702cf..0f1168514c4a 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -45,7 +45,7 @@ class TableFlattenerTest : public ::testing::Test { ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options, ResourceTable* table, std::string* out_content) { - BigBuffer buffer(1024); + android::BigBuffer buffer(1024); TableFlattener flattener(options, &buffer); if (!flattener.Consume(context, table)) { return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; @@ -84,7 +84,7 @@ class TableFlattenerTest : public ::testing::Test { return ::testing::AssertionSuccess(); } - ::testing::AssertionResult Exists(ResTable* table, const StringPiece& expected_name, + ::testing::AssertionResult Exists(ResTable* table, StringPiece expected_name, const ResourceId& expected_id, const ConfigDescription& expected_config, const uint8_t expected_data_type, const uint32_t expected_data, @@ -254,13 +254,13 @@ TEST_F(TableFlattenerTest, FlattenArray) { // Parse the flattened resource table ResChunkPullParser parser(result.data(), result.size()); ASSERT_TRUE(parser.IsGoodEvent(parser.Next())); - ASSERT_EQ(util::DeviceToHost16(parser.chunk()->type), RES_TABLE_TYPE); + ASSERT_EQ(android::util::DeviceToHost16(parser.chunk()->type), RES_TABLE_TYPE); // Retrieve the package of the entry ResChunkPullParser table_parser(GetChunkData(parser.chunk()), GetChunkDataLen(parser.chunk())); const ResChunk_header* package_chunk = nullptr; while (table_parser.IsGoodEvent(table_parser.Next())) { - if (util::DeviceToHost16(table_parser.chunk()->type) == RES_TABLE_PACKAGE_TYPE) { + if (android::util::DeviceToHost16(table_parser.chunk()->type) == RES_TABLE_PACKAGE_TYPE) { package_chunk = table_parser.chunk(); break; } @@ -272,7 +272,7 @@ TEST_F(TableFlattenerTest, FlattenArray) { GetChunkDataLen(table_parser.chunk())); const ResChunk_header* type_chunk = nullptr; while (package_parser.IsGoodEvent(package_parser.Next())) { - if (util::DeviceToHost16(package_parser.chunk()->type) == RES_TABLE_TYPE_TYPE) { + if (android::util::DeviceToHost16(package_parser.chunk()->type) == RES_TABLE_TYPE_TYPE) { type_chunk = package_parser.chunk(); break; } @@ -282,7 +282,7 @@ TEST_F(TableFlattenerTest, FlattenArray) { ASSERT_NE(type_chunk, nullptr); TypeVariant typeVariant((const ResTable_type*) type_chunk); auto entry = (const ResTable_map_entry*)*typeVariant.beginEntries(); - ASSERT_EQ(util::DeviceToHost16(entry->count), 2u); + ASSERT_EQ(android::util::DeviceToHost16(entry->count), 2u); // Check that the value and name of the array entries are correct auto values = (const ResTable_map*)(((const uint8_t *)entry) + entry->size); @@ -326,18 +326,18 @@ static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries( return table; } -TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) { +TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() .SetCompilationPackage("android") .SetPackageId(0x01) - .SetMinSdkVersion(SDK_O) + .SetMinSdkVersion(SDK_S_V2) .Build(); const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB"); auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); TableFlattenerOptions options; - options.use_sparse_entries = true; + options.sparse_entries = SparseEntriesMode::Enabled; std::string no_sparse_contents; ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); @@ -369,18 +369,40 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) { EXPECT_EQ(4u, value->value.data); } -TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) { +TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionSV2) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder() + .SetCompilationPackage("android") + .SetPackageId(0x01) + .SetMinSdkVersion(SDK_LOLLIPOP) + .Build(); + + const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v32"); + auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); + + TableFlattenerOptions options; + options.sparse_entries = SparseEntriesMode::Enabled; + + std::string no_sparse_contents; + ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); + + std::string sparse_contents; + ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents)); + + EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size()); +} + +TEST_F(TableFlattenerTest, FlattenSparseEntryRegardlessOfMinSdkWhenForced) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() .SetCompilationPackage("android") .SetPackageId(0x01) .SetMinSdkVersion(SDK_LOLLIPOP) .Build(); - const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v26"); + const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB"); auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); TableFlattenerOptions options; - options.use_sparse_entries = true; + options.sparse_entries = SparseEntriesMode::Forced; std::string no_sparse_contents; ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); @@ -391,6 +413,46 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) { EXPECT_GT(no_sparse_contents.size(), sparse_contents.size()); } +TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) { + std::unique_ptr<IAaptContext> context = + test::ContextBuilder().SetCompilationPackage("android").SetPackageId(0x01).Build(); + + const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB"); + auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); + + TableFlattenerOptions options; + options.sparse_entries = SparseEntriesMode::Enabled; + + std::string no_sparse_contents; + ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); + + std::string sparse_contents; + ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents)); + + EXPECT_GT(no_sparse_contents.size(), sparse_contents.size()); + + // Attempt to parse the sparse contents. + + ResourceTable sparse_table; + BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"), + sparse_contents.data(), sparse_contents.size()); + ASSERT_TRUE(parser.Parse()); + + auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0", + sparse_config); + ASSERT_THAT(value, NotNull()); + EXPECT_EQ(0u, value->value.data); + + ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1", + sparse_config), + IsNull()); + + value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4", + sparse_config); + ASSERT_THAT(value, NotNull()); + EXPECT_EQ(4u, value->value.data); +} + TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() .SetCompilationPackage("android") @@ -402,7 +464,7 @@ TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) { auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.80f); TableFlattenerOptions options; - options.use_sparse_entries = true; + options.sparse_entries = SparseEntriesMode::Enabled; std::string no_sparse_contents; ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); @@ -607,6 +669,87 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucce ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)*idx, 0u)); } +TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithDeduplicationSucceeds) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) + .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) + .AddValue("com.app.test:id/three", ResourceId(0x7f020002), + test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000))) + .AddValue("com.app.test:integer/one", ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) + .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), + ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) + .AddString("com.app.test:string/test1", ResourceId(0x7f040000), "foo") + .AddString("com.app.test:string/test2", ResourceId(0x7f040001), "foo") + .AddString("com.app.test:string/test3", ResourceId(0x7f040002), "bar") + .AddString("com.app.test:string/test4", ResourceId(0x7f040003), "foo") + .AddString("com.app.test:layout/bar1", ResourceId(0x7f050000), "res/layout/bar.xml") + .AddString("com.app.test:layout/bar2", ResourceId(0x7f050001), "res/layout/bar.xml") + .Build(); + + TableFlattenerOptions options; + options.collapse_key_stringpool = true; + options.deduplicate_entry_values = true; + + ResTable res_table; + + ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", + ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u, + ResTable_config::CONFIG_VERSION)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", + ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, + 2u, ResTable_config::CONFIG_VERSION)); + + std::u16string foo_str = u"foo"; + std::u16string bar_str = u"bar"; + auto foo_idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size()); + auto bar_idx = res_table.getTableStringBlock(0)->indexOfString(bar_str.data(), bar_str.size()); + ASSERT_TRUE(foo_idx.has_value()); + EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated", + ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, (uint32_t)*foo_idx, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated", + ResourceId(0x7f040001), {}, Res_value::TYPE_STRING, (uint32_t)*foo_idx, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated", + ResourceId(0x7f040002), {}, Res_value::TYPE_STRING, (uint32_t)*bar_idx, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated", + ResourceId(0x7f040003), {}, Res_value::TYPE_STRING, (uint32_t)*foo_idx, 0u)); + + std::u16string bar_path = u"res/layout/bar.xml"; + auto bar_path_idx = + res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); + ASSERT_TRUE(bar_path_idx.has_value()); + EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated", + ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)*bar_path_idx, + 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated", + ResourceId(0x7f050001), {}, Res_value::TYPE_STRING, (uint32_t)*bar_path_idx, + 0u)); + + std::string deduplicated_output; + std::string sequential_output; + Flatten(context_.get(), options, table.get(), &deduplicated_output); + options.deduplicate_entry_values = false; + Flatten(context_.get(), options, table.get(), &sequential_output); + + // We have 4 duplicates: 0x7f020001 id, 0x7f040001 string, 0x7f040003 string, 0x7f050001 layout. + EXPECT_EQ(sequential_output.size(), + deduplicated_output.size() + 4 * (sizeof(ResTable_entry) + sizeof(Res_value))); +} + TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() @@ -837,4 +980,93 @@ TEST_F(TableFlattenerTest, FlattenOverlayableNoPolicyFails) { ASSERT_FALSE(Flatten(context_.get(), {}, table.get(), &output_table)); } +TEST_F(TableFlattenerTest, FlattenCustomResourceTypes) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("com.app.test:id/one", ResourceId(0x7f010000)) + .AddSimple("com.app.test:id.2/two", ResourceId(0x7f020000)) + .AddValue("com.app.test:integer/one", ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 10u)) + .AddValue("com.app.test:integer.1/one", ResourceId(0x7f040000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) + .AddValue("com.app.test:integer.1/one", test::ParseConfigOrDie("v1"), + ResourceId(0x7f040000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) + .AddString("com.app.test:layout.custom/bar", ResourceId(0x7f050000), "res/layout/bar.xml") + .Build(); + + ResTable res_table; + ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f010000), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:id.2/two", ResourceId(0x7f020000), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000), {}, + Res_value::TYPE_INT_DEC, 10u, 0u)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer.1/one", ResourceId(0x7f040000), {}, + Res_value::TYPE_INT_DEC, 1u, ResTable_config::CONFIG_VERSION)); + + EXPECT_TRUE(Exists(&res_table, "com.app.test:integer.1/one", ResourceId(0x7f040000), + test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u, + ResTable_config::CONFIG_VERSION)); + + std::u16string bar_path = u"res/layout/bar.xml"; + auto idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size()); + ASSERT_TRUE(idx.has_value()); + EXPECT_TRUE(Exists(&res_table, "com.app.test:layout.custom/bar", ResourceId(0x7f050000), {}, + Res_value::TYPE_STRING, (uint32_t)*idx, 0u)); +} + +TEST_F(TableFlattenerTest, FlattenTypeEntryWithNameCollapseNotInExemption) { + OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme")); + overlayable_item.policies |= PolicyFlags::PUBLIC; + + std::string name = "com.app.test:color/overlayable_color"; + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddValue("com.app.test:color/overlayable_color", ResourceId(0x7f010000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_COLOR_ARGB8), + 0xffaabbcc)) + .SetOverlayable(name, overlayable_item) + .Build(); + + TableFlattenerOptions options; + options.collapse_key_stringpool = true; + + ResTable res_table; + EXPECT_THAT(Flatten(context_.get(), options, table.get(), &res_table), testing::IsTrue()); + EXPECT_THAT(Exists(&res_table, "com.app.test:color/overlayable_color", ResourceId(0x7f010000), {}, + Res_value::TYPE_INT_COLOR_ARGB8, 0xffaabbcc, 0u), + testing::IsTrue()); +} + +TEST_F(TableFlattenerTest, FlattenTypeEntryWithNameCollapseInExemption) { + OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme")); + overlayable_item.policies |= PolicyFlags::PUBLIC; + + std::string name = "com.app.test:color/overlayable_color"; + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddValue("com.app.test:color/overlayable_color", ResourceId(0x7f010000), + util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_COLOR_ARGB8), + 0xffaabbcc)) + .SetOverlayable(name, overlayable_item) + .Build(); + + TableFlattenerOptions options; + options.collapse_key_stringpool = true; + options.name_collapse_exemptions.insert( + ResourceName({}, ResourceType::kColor, "overlayable_color")); + + ResTable res_table; + EXPECT_THAT(Flatten(context_.get(), options, table.get(), &res_table), testing::IsTrue()); + EXPECT_THAT(Exists(&res_table, "com.app.test:color/overlayable_color", ResourceId(0x7f010000), {}, + Res_value::TYPE_INT_COLOR_ARGB8, 0xffaabbcc, 0u), + testing::IsTrue()); +} + } // namespace aapt diff --git a/tools/aapt2/format/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp index cdbe8828b29b..05f975177cd1 100644 --- a/tools/aapt2/format/binary/XmlFlattener.cpp +++ b/tools/aapt2/format/binary/XmlFlattener.cpp @@ -64,11 +64,11 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { public: using xml::ConstVisitor::Visit; - StringPool pool; - std::map<uint8_t, StringPool> package_pools; + android::StringPool pool; + std::map<uint8_t, android::StringPool> package_pools; struct StringFlattenDest { - StringPool::Ref ref; + android::StringPool::Ref ref; ResStringPool_ref* dest; }; @@ -79,7 +79,7 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { } void Visit(const xml::Text* node) override { - std::string text = util::TrimWhitespace(node->text).to_string(); + std::string text(util::TrimWhitespace(node->text)); // Skip whitespace only text nodes. if (text.empty()) { @@ -88,16 +88,16 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { // Compact leading and trailing whitespace into a single space if (isspace(node->text[0])) { - text = ' ' + text; + text.insert(text.begin(), ' '); } - if (isspace(node->text[node->text.length() - 1])) { - text = text + ' '; + if (isspace(node->text.back())) { + text += ' '; } ChunkWriter writer(buffer_); ResXMLTree_node* flat_node = writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE); - flat_node->lineNumber = util::HostToDevice32(node->line_number); - flat_node->comment.index = util::HostToDevice32(-1); + flat_node->lineNumber = android::util::HostToDevice32(node->line_number); + flat_node->comment.index = android::util::HostToDevice32(-1); ResXMLTree_cdataExt* flat_text = writer.NextBlock<ResXMLTree_cdataExt>(); AddString(text, kLowPriority, &flat_text->data); @@ -116,8 +116,8 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { ChunkWriter start_writer(buffer_); ResXMLTree_node* flat_node = start_writer.StartChunk<ResXMLTree_node>(RES_XML_START_ELEMENT_TYPE); - flat_node->lineNumber = util::HostToDevice32(node->line_number); - flat_node->comment.index = util::HostToDevice32(-1); + flat_node->lineNumber = android::util::HostToDevice32(node->line_number); + flat_node->comment.index = android::util::HostToDevice32(-1); ResXMLTree_attrExt* flat_elem = start_writer.NextBlock<ResXMLTree_attrExt>(); @@ -126,8 +126,8 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { true /* treat_empty_string_as_null */); AddString(node->name, kLowPriority, &flat_elem->name, true /* treat_empty_string_as_null */); - flat_elem->attributeStart = util::HostToDevice16(sizeof(*flat_elem)); - flat_elem->attributeSize = util::HostToDevice16(sizeof(ResXMLTree_attribute)); + flat_elem->attributeStart = android::util::HostToDevice16(sizeof(*flat_elem)); + flat_elem->attributeSize = android::util::HostToDevice16(sizeof(ResXMLTree_attribute)); WriteAttributes(node, flat_elem, &start_writer); @@ -140,8 +140,8 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { ChunkWriter end_writer(buffer_); ResXMLTree_node* flat_end_node = end_writer.StartChunk<ResXMLTree_node>(RES_XML_END_ELEMENT_TYPE); - flat_end_node->lineNumber = util::HostToDevice32(node->line_number); - flat_end_node->comment.index = util::HostToDevice32(-1); + flat_end_node->lineNumber = android::util::HostToDevice32(node->line_number); + flat_end_node->comment.index = android::util::HostToDevice32(-1); ResXMLTree_endElementExt* flat_end_elem = end_writer.NextBlock<ResXMLTree_endElementExt>(); AddString(node->namespace_uri, kLowPriority, &flat_end_elem->ns, @@ -165,21 +165,21 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { // We are adding strings to a StringPool whose strings will be sorted and merged with other // string pools. That means we can't encode the ID of a string directly. Instead, we defer the // writing of the ID here, until after the StringPool is merged and sorted. - void AddString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest, + void AddString(StringPiece str, uint32_t priority, android::ResStringPool_ref* dest, bool treat_empty_string_as_null = false) { if (str.empty() && treat_empty_string_as_null) { // Some parts of the runtime treat null differently than empty string. - dest->index = util::DeviceToHost32(-1); + dest->index = android::util::DeviceToHost32(-1); } else { string_refs.push_back( - StringFlattenDest{pool.MakeRef(str, StringPool::Context(priority)), dest}); + StringFlattenDest{pool.MakeRef(str, android::StringPool::Context(priority)), dest}); } } // We are adding strings to a StringPool whose strings will be sorted and merged with other // string pools. That means we can't encode the ID of a string directly. Instead, we defer the // writing of the ID here, until after the StringPool is merged and sorted. - void AddString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) { + void AddString(const android::StringPool::Ref& ref, android::ResStringPool_ref* dest) { string_refs.push_back(StringFlattenDest{ref, dest}); } @@ -187,8 +187,8 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { ChunkWriter writer(buffer_); ResXMLTree_node* flatNode = writer.StartChunk<ResXMLTree_node>(type); - flatNode->lineNumber = util::HostToDevice32(decl.line_number); - flatNode->comment.index = util::HostToDevice32(-1); + flatNode->lineNumber = android::util::HostToDevice32(decl.line_number); + flatNode->comment.index = android::util::HostToDevice32(-1); ResXMLTree_namespaceExt* flat_ns = writer.NextBlock<ResXMLTree_namespaceExt>(); AddString(decl.prefix, kLowPriority, &flat_ns->prefix); @@ -217,7 +217,7 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { std::sort(filtered_attrs_.begin(), filtered_attrs_.end(), cmp_xml_attribute_by_id); - flat_elem->attributeCount = util::HostToDevice16(filtered_attrs_.size()); + flat_elem->attributeCount = android::util::HostToDevice16(filtered_attrs_.size()); ResXMLTree_attribute* flat_attr = writer->NextBlock<ResXMLTree_attribute>(filtered_attrs_.size()); @@ -226,12 +226,12 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { // Assign the indices for specific attributes. if (xml_attr->compiled_attribute && xml_attr->compiled_attribute.value().id && xml_attr->compiled_attribute.value().id.value() == kIdAttr) { - flat_elem->idIndex = util::HostToDevice16(attribute_index); + flat_elem->idIndex = android::util::HostToDevice16(attribute_index); } else if (xml_attr->namespace_uri.empty()) { if (xml_attr->name == "class") { - flat_elem->classIndex = util::HostToDevice16(attribute_index); + flat_elem->classIndex = android::util::HostToDevice16(attribute_index); } else if (xml_attr->name == "style") { - flat_elem->styleIndex = util::HostToDevice16(attribute_index); + flat_elem->styleIndex = android::util::HostToDevice16(attribute_index); } } attribute_index++; @@ -241,7 +241,7 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { AddString(xml_attr->namespace_uri, kLowPriority, &flat_attr->ns, true /* treat_empty_string_as_null */); - flat_attr->rawValue.index = util::HostToDevice32(-1); + flat_attr->rawValue.index = android::util::HostToDevice32(-1); if (!xml_attr->compiled_attribute || !xml_attr->compiled_attribute.value().id) { // The attribute has no associated ResourceID, so the string order doesn't matter. @@ -256,8 +256,9 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { // Lookup the StringPool for this package and make the reference there. const xml::AaptAttribute& aapt_attr = xml_attr->compiled_attribute.value(); - StringPool::Ref name_ref = package_pools[aapt_attr.id.value().package_id()].MakeRef( - xml_attr->name, StringPool::Context(aapt_attr.id.value().id)); + android::StringPool::Ref name_ref = + package_pools[aapt_attr.id.value().package_id()].MakeRef( + xml_attr->name, android::StringPool::Context(aapt_attr.id.value().id)); // Add it to the list of strings to flatten. AddString(name_ref, &flat_attr->name); @@ -298,7 +299,7 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { AddString(xml_attr->value, kLowPriority, &flat_attr->rawValue); } - flat_attr->typedValue.size = util::HostToDevice16(sizeof(flat_attr->typedValue)); + flat_attr->typedValue.size = android::util::HostToDevice16(sizeof(flat_attr->typedValue)); flat_attr++; } } @@ -313,7 +314,7 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { } // namespace bool XmlFlattener::Flatten(IAaptContext* context, const xml::Node* node) { - BigBuffer node_buffer(1024); + android::BigBuffer node_buffer(1024); XmlFlattenerVisitor visitor(&node_buffer, options_); node->Accept(&visitor); @@ -323,13 +324,14 @@ bool XmlFlattener::Flatten(IAaptContext* context, const xml::Node* node) { } // Sort the string pool so that attribute resource IDs show up first. - visitor.pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int { - return util::compare(a.priority, b.priority); - }); + visitor.pool.Sort( + [](const android::StringPool::Context& a, const android::StringPool::Context& b) -> int { + return util::compare(a.priority, b.priority); + }); // Now we flatten the string pool references into the correct places. for (const auto& ref_entry : visitor.string_refs) { - ref_entry.dest->index = util::HostToDevice32(ref_entry.ref.index()); + ref_entry.dest->index = android::util::HostToDevice32(ref_entry.ref.index()); } // Write the XML header. @@ -338,9 +340,9 @@ bool XmlFlattener::Flatten(IAaptContext* context, const xml::Node* node) { // Flatten the StringPool. if (options_.use_utf16) { - StringPool::FlattenUtf16(buffer_, visitor.pool, context->GetDiagnostics()); + android::StringPool::FlattenUtf16(buffer_, visitor.pool, context->GetDiagnostics()); } else { - StringPool::FlattenUtf8(buffer_, visitor.pool, context->GetDiagnostics()); + android::StringPool::FlattenUtf8(buffer_, visitor.pool, context->GetDiagnostics()); } { @@ -353,7 +355,7 @@ bool XmlFlattener::Flatten(IAaptContext* context, const xml::Node* node) { // When we see the first non-resource ID, we're done. break; } - *res_id_map_writer.NextBlock<uint32_t>() = util::HostToDevice32(id.id); + *res_id_map_writer.NextBlock<uint32_t>() = android::util::HostToDevice32(id.id); } res_id_map_writer.Finish(); } diff --git a/tools/aapt2/format/binary/XmlFlattener.h b/tools/aapt2/format/binary/XmlFlattener.h index 1f9e777f7a1a..e18c1e5b3fe1 100644 --- a/tools/aapt2/format/binary/XmlFlattener.h +++ b/tools/aapt2/format/binary/XmlFlattener.h @@ -18,9 +18,8 @@ #define AAPT_FORMAT_BINARY_XMLFLATTENER_H #include "android-base/macros.h" - +#include "androidfw/BigBuffer.h" #include "process/IResourceTableConsumer.h" -#include "util/BigBuffer.h" #include "xml/XmlDom.h" namespace aapt { @@ -36,7 +35,7 @@ struct XmlFlattenerOptions { class XmlFlattener { public: - XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) + XmlFlattener(android::BigBuffer* buffer, XmlFlattenerOptions options) : buffer_(buffer), options_(options) { } @@ -47,7 +46,7 @@ class XmlFlattener { bool Flatten(IAaptContext* context, const xml::Node* node); - BigBuffer* buffer_; + android::BigBuffer* buffer_; XmlFlattenerOptions options_; }; diff --git a/tools/aapt2/format/binary/XmlFlattener_test.cpp b/tools/aapt2/format/binary/XmlFlattener_test.cpp index d97e8882e5a2..6d0022cad307 100644 --- a/tools/aapt2/format/binary/XmlFlattener_test.cpp +++ b/tools/aapt2/format/binary/XmlFlattener_test.cpp @@ -16,11 +16,10 @@ #include "format/binary/XmlFlattener.h" +#include "androidfw/BigBuffer.h" #include "androidfw/ResourceTypes.h" - #include "link/Linkers.h" #include "test/Test.h" -#include "util/BigBuffer.h" #include "util/Util.h" using ::aapt::test::StrEq; @@ -59,13 +58,13 @@ class XmlFlattenerTest : public ::testing::Test { const XmlFlattenerOptions& options = {}) { using namespace android; // For NO_ERROR on windows because it is a macro. - BigBuffer buffer(1024); + android::BigBuffer buffer(1024); XmlFlattener flattener(&buffer, options); if (!flattener.Consume(context_.get(), doc)) { return ::testing::AssertionFailure() << "failed to flatten XML Tree"; } - std::unique_ptr<uint8_t[]> data = util::Copy(buffer); + std::unique_ptr<uint8_t[]> data = android::util::Copy(buffer); if (out_tree->setTo(data.get(), buffer.size(), true) != NO_ERROR) { return ::testing::AssertionFailure() << "flattened XML is corrupt"; } diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index 236c38167545..09ef9bddd3bd 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -16,15 +16,15 @@ #include "format/proto/ProtoDeserialize.h" -#include "android-base/logging.h" -#include "android-base/macros.h" -#include "androidfw/ResourceTypes.h" -#include "androidfw/Locale.h" - #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" +#include "android-base/logging.h" +#include "android-base/macros.h" +#include "androidfw/Locale.h" +#include "androidfw/ResourceTypes.h" +#include "androidfw/Util.h" using ::android::ConfigDescription; using ::android::LocaleValue; @@ -354,12 +354,13 @@ bool DeserializeConfigFromPb(const pb::Configuration& pb_config, ConfigDescripti out_config->screenWidth = static_cast<uint16_t>(pb_config.screen_width()); out_config->screenHeight = static_cast<uint16_t>(pb_config.screen_height()); out_config->sdkVersion = static_cast<uint16_t>(pb_config.sdk_version()); + out_config->grammaticalInflection = pb_config.grammatical_gender(); return true; } static void DeserializeSourceFromPb(const pb::Source& pb_source, const ResStringPool& src_pool, - Source* out_source) { - out_source->path = util::GetString(src_pool, pb_source.path_idx()); + android::Source* out_source) { + out_source->path = android::util::GetString(src_pool, pb_source.path_idx()); out_source->line = static_cast<size_t>(pb_source.position().line_number()); } @@ -429,8 +430,8 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr ResourceTablePackage* pkg = out_table->FindOrCreatePackage(pb_package.package_name()); for (const pb::Type& pb_type : pb_package.type()) { - const ResourceType* res_type = ParseResourceType(pb_type.name()); - if (res_type == nullptr) { + auto res_type = ParseResourceNamedType(pb_type.name()); + if (!res_type) { std::ostringstream error; error << "unknown type '" << pb_type.name() << "'"; *out_error = error.str(); @@ -515,7 +516,7 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(), pb_entry.entry_id().id()); if (resid.is_valid()) { - id_index[resid] = ResourceNameRef(pkg->name, type->type, entry->name); + id_index[resid] = ResourceNameRef(pkg->name, type->named_type, entry->name); } for (const pb::ConfigValue& pb_config_value : pb_entry.config_value()) { @@ -562,6 +563,11 @@ bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollecti } } + for (const pb::DynamicRefTable& dynamic_ref : pb_table.dynamic_ref_table()) { + out_table->included_packages_.insert( + {dynamic_ref.package_id().id(), dynamic_ref.package_name()}); + } + // Deserialize the overlayable groups of the table std::vector<std::shared_ptr<Overlayable>> overlayables; for (const pb::Overlayable& pb_overlayable : pb_table.overlayable()) { @@ -680,7 +686,7 @@ static bool DeserializeMacroFromPb(const pb::MacroBody& pb_ref, Macro* out_ref, if (pb_ref.has_style_string()) { out_ref->style_string.str = pb_ref.style_string().str(); for (const auto& span : pb_ref.style_string().spans()) { - out_ref->style_string.spans.emplace_back(Span{ + out_ref->style_string.spans.emplace_back(android::Span{ .name = span.name(), .first_char = span.start_index(), .last_char = span.end_index()}); } } @@ -705,7 +711,7 @@ template <typename T> static void DeserializeItemMetaDataFromPb(const T& pb_item, const android::ResStringPool& src_pool, Value* out_value) { if (pb_item.has_source()) { - Source source; + android::Source source; DeserializeSourceFromPb(pb_item.source(), src_pool, &source); out_value->SetSource(std::move(source)); } @@ -733,8 +739,8 @@ static size_t DeserializePluralEnumFromPb(const pb::Plural_Arity& arity) { std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value, const android::ResStringPool& src_pool, const ConfigDescription& config, - StringPool* value_pool, io::IFileCollection* files, - std::string* out_error) { + android::StringPool* value_pool, + io::IFileCollection* files, std::string* out_error) { std::unique_ptr<Value> value; if (pb_value.has_item()) { value = DeserializeItemFromPb(pb_value.item(), src_pool, config, value_pool, files, out_error); @@ -774,7 +780,7 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value, } if (pb_style.has_parent_source()) { - Source parent_source; + android::Source parent_source; DeserializeSourceFromPb(pb_style.parent_source(), src_pool, &parent_source); style->parent.value().SetSource(std::move(parent_source)); } @@ -870,7 +876,8 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value, std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, const android::ResStringPool& src_pool, - const ConfigDescription& config, StringPool* value_pool, + const ConfigDescription& config, + android::StringPool* value_pool, io::IFileCollection* files, std::string* out_error) { switch (pb_item.value_case()) { case pb::Item::kRef: { @@ -960,29 +967,32 @@ std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, case pb::Item::kStr: { return util::make_unique<String>( - value_pool->MakeRef(pb_item.str().value(), StringPool::Context(config))); + value_pool->MakeRef(pb_item.str().value(), android::StringPool::Context(config))); } break; case pb::Item::kRawStr: { return util::make_unique<RawString>( - value_pool->MakeRef(pb_item.raw_str().value(), StringPool::Context(config))); + value_pool->MakeRef(pb_item.raw_str().value(), android::StringPool::Context(config))); } break; case pb::Item::kStyledStr: { const pb::StyledString& pb_str = pb_item.styled_str(); - StyleString style_str{pb_str.value()}; + android::StyleString style_str{pb_str.value()}; for (const pb::StyledString::Span& pb_span : pb_str.span()) { - style_str.spans.push_back(Span{pb_span.tag(), pb_span.first_char(), pb_span.last_char()}); + style_str.spans.push_back( + android::Span{pb_span.tag(), pb_span.first_char(), pb_span.last_char()}); } return util::make_unique<StyledString>(value_pool->MakeRef( - style_str, StringPool::Context(StringPool::Context::kNormalPriority, config))); + style_str, + android::StringPool::Context(android::StringPool::Context::kNormalPriority, config))); } break; case pb::Item::kFile: { const pb::FileReference& pb_file = pb_item.file(); std::unique_ptr<FileReference> file_ref = util::make_unique<FileReference>(value_pool->MakeRef( - pb_file.path(), StringPool::Context(StringPool::Context::kHighPriority, config))); + pb_file.path(), + android::StringPool::Context(android::StringPool::Context::kHighPriority, config))); file_ref->type = DeserializeFileReferenceTypeFromPb(pb_file.type()); if (files != nullptr) { file_ref->file = files->FindFile(*file_ref->path); @@ -1011,8 +1021,8 @@ std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode return resource; } -bool DeserializeXmlFromPb(const pb::XmlNode& pb_node, xml::Element* out_el, StringPool* value_pool, - std::string* out_error) { +bool DeserializeXmlFromPb(const pb::XmlNode& pb_node, xml::Element* out_el, + android::StringPool* value_pool, std::string* out_error) { const pb::XmlElement& pb_el = pb_node.element(); out_el->name = pb_el.name(); out_el->namespace_uri = pb_el.namespace_uri(); @@ -1042,7 +1052,7 @@ bool DeserializeXmlFromPb(const pb::XmlNode& pb_node, xml::Element* out_el, Stri if (attr.compiled_value == nullptr) { return {}; } - attr.compiled_value->SetSource(Source().WithLine(pb_attr.source().line_number())); + attr.compiled_value->SetSource(android::Source().WithLine(pb_attr.source().line_number())); } out_el->attributes.push_back(std::move(attr)); } diff --git a/tools/aapt2/format/proto/ProtoDeserialize.h b/tools/aapt2/format/proto/ProtoDeserialize.h index 723a1c095a50..95de3cb52017 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.h +++ b/tools/aapt2/format/proto/ProtoDeserialize.h @@ -17,16 +17,15 @@ #ifndef AAPT_FORMAT_PROTO_PROTODESERIALIZE_H #define AAPT_FORMAT_PROTO_PROTODESERIALIZE_H -#include "android-base/macros.h" -#include "androidfw/ConfigDescription.h" -#include "androidfw/ResourceTypes.h" - #include "Configuration.pb.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "Resources.pb.h" #include "ResourcesInternal.pb.h" -#include "StringPool.h" +#include "android-base/macros.h" +#include "androidfw/ConfigDescription.h" +#include "androidfw/ResourceTypes.h" +#include "androidfw/StringPool.h" #include "io/File.h" #include "xml/XmlDom.h" @@ -35,20 +34,20 @@ namespace aapt { std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value, const android::ResStringPool& src_pool, const android::ConfigDescription& config, - StringPool* value_pool, io::IFileCollection* files, - std::string* out_error); + android::StringPool* value_pool, + io::IFileCollection* files, std::string* out_error); std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, const android::ResStringPool& src_pool, const android::ConfigDescription& config, - StringPool* value_pool, io::IFileCollection* files, - std::string* out_error); + android::StringPool* value_pool, + io::IFileCollection* files, std::string* out_error); std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode& pb_node, std::string* out_error); -bool DeserializeXmlFromPb(const pb::XmlNode& pb_node, xml::Element* out_el, StringPool* value_pool, - std::string* out_error); +bool DeserializeXmlFromPb(const pb::XmlNode& pb_node, xml::Element* out_el, + android::StringPool* value_pool, std::string* out_error); bool DeserializeConfigFromPb(const pb::Configuration& pb_config, android::ConfigDescription* out_config, std::string* out_error); diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index f3b7f758e170..0903205b5eb2 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -17,7 +17,8 @@ #include "format/proto/ProtoSerialize.h" #include "ValueVisitor.h" -#include "util/BigBuffer.h" +#include "androidfw/BigBuffer.h" +#include "optimize/Obfuscator.h" using android::ConfigDescription; @@ -25,22 +26,24 @@ using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; namespace aapt { -void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool, IDiagnostics* diag) { - BigBuffer buffer(1024); - StringPool::FlattenUtf8(&buffer, pool, diag); +void SerializeStringPoolToPb(const android::StringPool& pool, pb::StringPool* out_pb_pool, + android::IDiagnostics* diag) { + android::BigBuffer buffer(1024); + android::StringPool::FlattenUtf8(&buffer, pool, diag); std::string* data = out_pb_pool->mutable_data(); data->reserve(buffer.size()); size_t offset = 0; - for (const BigBuffer::Block& block : buffer) { + for (const android::BigBuffer::Block& block : buffer) { data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size); offset += block.size; } } -void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* out_pb_source) { - StringPool::Ref ref = src_pool->MakeRef(source.path); +void SerializeSourceToPb(const android::Source& source, android::StringPool* src_pool, + pb::Source* out_pb_source) { + android::StringPool::Ref ref = src_pool->MakeRef(source.path); out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index())); if (source.line) { out_pb_source->mutable_position()->set_line_number(static_cast<uint32_t>(source.line.value())); @@ -272,11 +275,15 @@ void SerializeConfig(const ConfigDescription& config, pb::Configuration* out_pb_ } out_pb_config->set_sdk_version(config.sdkVersion); + + // The constant values are the same across the structs. + out_pb_config->set_grammatical_gender( + static_cast<pb::Configuration_GrammaticalGender>(config.grammaticalInflection)); } static void SerializeOverlayableItemToPb(const OverlayableItem& overlayable_item, std::vector<Overlayable*>& serialized_overlayables, - StringPool* source_pool, pb::Entry* pb_entry, + android::StringPool* source_pool, pb::Entry* pb_entry, pb::ResourceTable* pb_table) { // Retrieve the index of the overlayable in the list of groups that have already been serialized. size_t i; @@ -337,13 +344,17 @@ static void SerializeOverlayableItemToPb(const OverlayableItem& overlayable_item } void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table, - IDiagnostics* diag, SerializeTableOptions options) { - auto source_pool = (options.exclude_sources) ? nullptr : util::make_unique<StringPool>(); + android::IDiagnostics* diag, SerializeTableOptions options) { + auto source_pool = (options.exclude_sources) ? nullptr : util::make_unique<android::StringPool>(); pb::ToolFingerprint* pb_fingerprint = out_table->add_tool_fingerprint(); pb_fingerprint->set_tool(util::GetToolName()); pb_fingerprint->set_version(util::GetToolFingerprint()); - + for (auto it = table.included_packages_.begin(); it != table.included_packages_.end(); ++it) { + pb::DynamicRefTable* pb_dynamic_ref = out_table->add_dynamic_ref_table(); + pb_dynamic_ref->mutable_package_id()->set_id(it->first); + pb_dynamic_ref->set_package_name(it->second); + } std::vector<Overlayable*> overlayables; auto table_view = table.GetPartitionedView(); for (const auto& package : table_view.packages) { @@ -358,23 +369,23 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table if (type.id) { pb_type->mutable_type_id()->set_id(type.id.value()); } - pb_type->set_name(to_string(type.type).to_string()); + pb_type->set_name(type.named_type.to_string()); - // hardcoded string uses characters which make it an invalid resource name - static const char* obfuscated_resource_name = "0_resource_name_obfuscated"; for (const auto& entry : type.entries) { pb::Entry* pb_entry = pb_type->add_entry(); if (entry.id) { pb_entry->mutable_entry_id()->set_id(entry.id.value()); } - ResourceName resource_name({}, type.type, entry.name); - if (options.collapse_key_stringpool && - options.name_collapse_exemptions.find(resource_name) == - options.name_collapse_exemptions.end()) { - pb_entry->set_name(obfuscated_resource_name); - } else { - pb_entry->set_name(entry.name); - } + auto onObfuscate = [pb_entry, &entry](Obfuscator::Result obfuscatedResult, + const ResourceName& resource_name) { + pb_entry->set_name(obfuscatedResult == Obfuscator::Result::Obfuscated + ? Obfuscator::kObfuscatedResourceName + : entry.name); + }; + + Obfuscator::ObfuscateResourceName(options.collapse_key_stringpool, + options.name_collapse_exemptions, type.named_type, entry, + onObfuscate); // Write the Visibility struct. pb::Visibility* pb_visibility = pb_entry->mutable_visibility(); @@ -482,7 +493,7 @@ static void SerializeMacroToPb(const Macro& ref, pb::MacroBody* pb_macro) { } template <typename T> -static void SerializeItemMetaDataToPb(const Item& item, T* pb_item, StringPool* src_pool) { +static void SerializeItemMetaDataToPb(const Item& item, T* pb_item, android::StringPool* src_pool) { if (src_pool != nullptr) { SerializeSourceToPb(item.GetSource(), src_pool, pb_item->mutable_source()); } @@ -526,7 +537,7 @@ class ValueSerializer : public ConstValueVisitor { public: using ConstValueVisitor::Visit; - ValueSerializer(pb::Value* out_value, StringPool* src_pool) + ValueSerializer(pb::Value* out_value, android::StringPool* src_pool) : out_value_(out_value), src_pool_(src_pool) { } @@ -545,7 +556,7 @@ class ValueSerializer : public ConstValueVisitor { void Visit(const StyledString* str) override { pb::StyledString* pb_str = out_value_->mutable_item()->mutable_styled_str(); pb_str->set_value(str->value->value); - for (const StringPool::Span& span : str->value->spans) { + for (const android::StringPool::Span& span : str->value->spans) { pb::StyledString::Span* pb_span = pb_str->add_span(); pb_span->set_tag(*span.name); pb_span->set_first_char(span.first_char); @@ -693,12 +704,12 @@ class ValueSerializer : public ConstValueVisitor { private: pb::Value* out_value_; - StringPool* src_pool_; + android::StringPool* src_pool_; }; } // namespace -void SerializeValueToPb(const Value& value, pb::Value* out_value, StringPool* src_pool) { +void SerializeValueToPb(const Value& value, pb::Value* out_value, android::StringPool* src_pool) { ValueSerializer serializer(out_value, src_pool); value.Accept(&serializer); diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h index b0d56307fbe4..b0a70d90f1b4 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.h +++ b/tools/aapt2/format/proto/ProtoSerialize.h @@ -17,15 +17,14 @@ #ifndef AAPT_FORMAT_PROTO_PROTOSERIALIZE_H #define AAPT_FORMAT_PROTO_PROTOSERIALIZE_H -#include "android-base/macros.h" -#include "androidfw/ConfigDescription.h" - #include "Configuration.pb.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "Resources.pb.h" #include "ResourcesInternal.pb.h" -#include "StringPool.h" +#include "android-base/macros.h" +#include "androidfw/ConfigDescription.h" +#include "androidfw/StringPool.h" #include "xml/XmlDom.h" namespace aapt { @@ -51,7 +50,8 @@ struct SerializeTableOptions { // Serializes a Value to its protobuf representation. An optional StringPool will hold the // source path string. -void SerializeValueToPb(const Value& value, pb::Value* out_value, StringPool* src_pool = nullptr); +void SerializeValueToPb(const Value& value, pb::Value* out_value, + android::StringPool* src_pool = nullptr); // Serialize an Item into its protobuf representation. pb::Item does not store the source path nor // comments of an Item. @@ -67,14 +67,15 @@ void SerializeXmlResourceToPb(const xml::XmlResource& resource, pb::XmlNode* out // Serializes a StringPool into its protobuf representation, which is really just the binary // ResStringPool representation stuffed into a bytes field. -void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool, IDiagnostics* diag); +void SerializeStringPoolToPb(const android::StringPool& pool, pb::StringPool* out_pb_pool, + android::IDiagnostics* diag); // Serializes a ConfigDescription into its protobuf representation. void SerializeConfig(const android::ConfigDescription& config, pb::Configuration* out_pb_config); // Serializes a ResourceTable into its protobuf representation. void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table, - IDiagnostics* diag, SerializeTableOptions options = {}); + android::IDiagnostics* diag, SerializeTableOptions options = {}); // Serializes a ResourceFile into its protobuf representation. void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file); diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index d1d72e012b31..afb83562b129 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -35,7 +35,7 @@ namespace aapt { class MockFileCollection : public io::IFileCollection { public: - MOCK_METHOD1(FindFile, io::IFile*(const StringPiece& path)); + MOCK_METHOD1(FindFile, io::IFile*(StringPiece path)); MOCK_METHOD0(Iterator, std::unique_ptr<io::IFileCollectionIterator>()); MOCK_METHOD0(GetDirSeparator, char()); }; @@ -127,9 +127,9 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { context->GetDiagnostics())); // Make a styled string. - StyleString style_string; + android::StyleString style_string; style_string.str = "hello"; - style_string.spans.push_back(Span{"b", 0u, 4u}); + style_string.spans.push_back(android::Span{"b", 0u, 4u}); ASSERT_TRUE(table->AddResource( NewResourceBuilder(test::ParseNameOrDie("com.app.a:string/styled")) .SetValue(util::make_unique<StyledString>(table->string_pool.MakeRef(style_string))) @@ -164,8 +164,8 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { // Make an overlayable resource. OverlayableItem overlayable_item(std::make_shared<Overlayable>( - "OverlayableName", "overlay://theme", Source("res/values/overlayable.xml", 40))); - overlayable_item.source = Source("res/values/overlayable.xml", 42); + "OverlayableName", "overlay://theme", android::Source("res/values/overlayable.xml", 40))); + overlayable_item.source = android::Source("res/values/overlayable.xml", 42); ASSERT_TRUE( table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:integer/overlayable")) .SetOverlayable(overlayable_item) @@ -271,7 +271,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeXml) { attr.compiled_attribute = xml::AaptAttribute(Attribute{}, ResourceId(0x01010000)); attr.compiled_value = ResourceUtils::TryParseItemForAttribute(attr.value, android::ResTable_map::TYPE_DIMENSION); - attr.compiled_value->SetSource(Source().WithLine(25)); + attr.compiled_value->SetSource(android::Source().WithLine(25)); element.attributes.push_back(std::move(attr)); std::unique_ptr<xml::Text> text = util::make_unique<xml::Text>(); @@ -292,7 +292,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeXml) { pb::XmlNode pb_xml; SerializeXmlToPb(element, &pb_xml); - StringPool pool; + android::StringPool pool; xml::Element actual_el; std::string error; ASSERT_TRUE(DeserializeXmlFromPb(pb_xml, &actual_el, &pool, &error)); @@ -365,7 +365,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeXmlTrimEmptyWhitepsace) { options.remove_empty_text_nodes = true; SerializeXmlToPb(element, &pb_xml, options); - StringPool pool; + android::StringPool pool; xml::Element actual_el; std::string error; ASSERT_TRUE(DeserializeXmlFromPb(pb_xml, &actual_el, &pool, &error)); @@ -491,7 +491,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializePrimitives) { EXPECT_THAT(bp->value.data, Eq(ResourceUtils::MakeEmpty()->value.data)); } -static void ExpectConfigSerializes(const StringPiece& config_str) { +static void ExpectConfigSerializes(StringPiece config_str) { const ConfigDescription expected_config = test::ParseConfigOrDie(config_str); pb::Configuration pb_config; SerializeConfig(expected_config, &pb_config); @@ -581,9 +581,13 @@ TEST(ProtoSerializeTest, SerializeDeserializeConfiguration) { ExpectConfigSerializes("v8"); + ExpectConfigSerializes("en-feminine"); + ExpectConfigSerializes("en-neuter-v34"); + ExpectConfigSerializes("feminine-v34"); + ExpectConfigSerializes( - "mcc123-mnc456-b+en+GB-ldltr-sw300dp-w300dp-h400dp-large-long-round-widecg-highdr-land-car-" - "night-xhdpi-stylus-keysexposed-qwerty-navhidden-dpad-300x200-v23"); + "mcc123-mnc456-b+en+GB-masculine-ldltr-sw300dp-w300dp-h400dp-large-long-round-widecg-highdr-" + "land-car-night-xhdpi-stylus-keysexposed-qwerty-navhidden-dpad-300x200-v23"); } TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { @@ -898,7 +902,8 @@ TEST(ProtoSerializeTest, SerializeMacro) { auto original = std::make_unique<Macro>(); original->raw_value = "\nThis being human is a guest house."; original->style_string.str = " This being human is a guest house."; - original->style_string.spans.emplace_back(Span{.name = "b", .first_char = 12, .last_char = 16}); + original->style_string.spans.emplace_back( + android::Span{.name = "b", .first_char = 12, .last_char = 16}); original->untranslatable_sections.emplace_back(UntranslatableSection{.start = 12, .end = 17}); original->alias_namespaces.emplace_back( Macro::Namespace{.alias = "prefix", .package_name = "package.name", .is_private = true}); @@ -951,4 +956,100 @@ TEST(ProtoSerializeTest, StagedId) { EXPECT_THAT(result.value().entry->staged_id.value().id, Eq(ResourceId(0x01ff0001))); } +TEST(ProtoSerializeTest, CustomResourceTypes) { + const uint32_t id_one_id = 0x7f020000; + const uint32_t id_2_two_id = 0x7f030000; + const uint32_t integer_three_id = 0x7f030000; + const uint32_t integer_1_four_id = 0x7f030000; + const uint32_t layout_bar_id = 0x7f050000; + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("com.app.test:id/one", ResourceId(id_one_id)) + .AddSimple("com.app.test:id.2/two", ResourceId(id_2_two_id)) + .AddValue( + "com.app.test:integer/one", ResourceId(integer_three_id), + util::make_unique<BinaryPrimitive>(uint8_t(android::Res_value::TYPE_INT_DEC), 10u)) + .AddValue( + "com.app.test:integer.1/one", ResourceId(integer_1_four_id), + util::make_unique<BinaryPrimitive>(uint8_t(android::Res_value::TYPE_INT_DEC), 1u)) + .AddValue( + "com.app.test:integer.1/one", test::ParseConfigOrDie("v1"), + ResourceId(integer_1_four_id), + util::make_unique<BinaryPrimitive>(uint8_t(android::Res_value::TYPE_INT_DEC), 2u)) + .AddFileReference("com.app.test:layout.custom/bar", ResourceId(layout_bar_id), + "res/layout/bar.xml") + .Build(); + + test::TestFile file_a("res/layout/bar.xml"); + MockFileCollection files; + EXPECT_CALL(files, FindFile(Eq("res/layout/bar.xml"))).WillRepeatedly(::testing::Return(&file_a)); + + ResourceTable new_table; + pb::ResourceTable pb_table; + std::string error; + SerializeTableToPb(*table, &pb_table, context->GetDiagnostics()); + DeserializeTableFromPb(pb_table, &files, &new_table, &error); + ASSERT_THAT(error, IsEmpty()); + + auto bp = test::GetValueForConfigAndProduct<BinaryPrimitive>( + &new_table, "com.app.test:integer.1/one", ConfigDescription::DefaultConfig(), ""); + ASSERT_THAT(bp, NotNull()); + EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_DEC)); + EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseInt("1")->value.data)); + + bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "com.app.test:integer.1/one", + test::ParseConfigOrDie("v1"), ""); + ASSERT_THAT(bp, NotNull()); + EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_DEC)); + EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseInt("2")->value.data)); + + bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "com.app.test:integer/one", + ConfigDescription::DefaultConfig(), ""); + ASSERT_THAT(bp, NotNull()); + EXPECT_THAT(bp->value.dataType, Eq(android::Res_value::TYPE_INT_DEC)); + EXPECT_THAT(bp->value.data, Eq(ResourceUtils::TryParseInt("10")->value.data)); + + bp = test::GetValueForConfigAndProduct<BinaryPrimitive>(&new_table, "com.app.test:integer/one", + test::ParseConfigOrDie("v1"), ""); + ASSERT_THAT(bp, IsNull()); + + auto id = test::GetValueForConfigAndProduct<Id>(&new_table, "com.app.test:id/one", + ConfigDescription::DefaultConfig(), ""); + ASSERT_THAT(id, NotNull()); + + id = test::GetValueForConfigAndProduct<Id>(&new_table, "com.app.test:id.2/two", + ConfigDescription::DefaultConfig(), ""); + ASSERT_THAT(id, NotNull()); + + auto custom_layout = test::GetValueForConfigAndProduct<FileReference>( + &new_table, "com.app.test:layout.custom/bar", ConfigDescription::DefaultConfig(), ""); + ASSERT_THAT(custom_layout, NotNull()); + EXPECT_THAT(*(custom_layout->path), Eq("res/layout/bar.xml")); +} + +TEST(ProtoSerializeTest, SerializeDynamicRef) { + std::unique_ptr<IAaptContext> context = + test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build(); + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build(); + table->included_packages_.insert({20, "foobar"}); + table->included_packages_.insert({30, "barfoo"}); + + ResourceTable new_table; + pb::ResourceTable pb_table; + MockFileCollection files; + std::string error; + SerializeTableToPb(*table, &pb_table, context->GetDiagnostics()); + ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)); + EXPECT_THAT(error, IsEmpty()); + + int result = new_table.included_packages_.size(); + EXPECT_THAT(result, Eq(2)); + auto it = new_table.included_packages_.begin(); + EXPECT_THAT(it->first, Eq(20)); + EXPECT_THAT(it->second, Eq("foobar")); + it++; + EXPECT_THAT(it->first, Eq(30)); + EXPECT_THAT(it->second, Eq("barfoo")); +} } // namespace aapt diff --git a/tools/aapt2/integration-tests/CommandTests/android-28.jar b/tools/aapt2/integration-tests/CommandTests/android-33.jar Binary files differindex ef7576d17c6d..08cf49d854e2 100644 --- a/tools/aapt2/integration-tests/CommandTests/android-28.jar +++ b/tools/aapt2/integration-tests/CommandTests/android-33.jar diff --git a/tools/aapt2/integration-tests/DumpTest/built_with_aapt.apk b/tools/aapt2/integration-tests/DumpTest/built_with_aapt.apk Binary files differnew file mode 100644 index 000000000000..090ebe5687dd --- /dev/null +++ b/tools/aapt2/integration-tests/DumpTest/built_with_aapt.apk diff --git a/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt b/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt new file mode 100644 index 000000000000..cc0b3bf5d2fb --- /dev/null +++ b/tools/aapt2/integration-tests/DumpTest/built_with_aapt_expected.txt @@ -0,0 +1,11 @@ +package: name='com.aapt.app' versionCode='222' versionName='222' platformBuildVersionName='12' platformBuildVersionCode='32' compileSdkVersion='32' compileSdkVersionCodename='12' +sdkVersion:'22' +targetSdkVersion:'32' +application: label='App' icon='' +feature-group: label='' + uses-feature: name='android.hardware.faketouch' + uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps' +supports-screens: 'small' 'normal' 'large' 'xlarge' +supports-any-density: 'true' +locales: +densities: diff --git a/tools/aapt2/integration-tests/DumpTest/components.apk b/tools/aapt2/integration-tests/DumpTest/components.apk Binary files differnew file mode 100644 index 000000000000..a34ec83f3dae --- /dev/null +++ b/tools/aapt2/integration-tests/DumpTest/components.apk diff --git a/tools/aapt2/integration-tests/DumpTest/components_expected.txt b/tools/aapt2/integration-tests/DumpTest/components_expected.txt new file mode 100644 index 000000000000..9c81fb83ca15 --- /dev/null +++ b/tools/aapt2/integration-tests/DumpTest/components_expected.txt @@ -0,0 +1,57 @@ +package: name='com.example.bundletool.minimal' versionCode='1' versionName='1.0' platformBuildVersionName='12' platformBuildVersionCode='31' compileSdkVersion='31' compileSdkVersionCodename='12' +sdkVersion:'21' +targetSdkVersion:'31' +uses-configuration: reqTouchScreen='3' reqKeyboardType='2' reqHardKeyboard='-1' reqNavigation='3' reqFiveWayNav='-1' +supports-gl-texture:'GL_OES_compressed_paletted_texture' +uses-permission: name='android.permission.BIND_ACCESSIBILITY_SERVICE' maxSdkVersion='24' +uses-permission-sdk-23: name='android.permission.RECEIVE_SMS' +uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE' +compatible-screens:'500/240','400/160' +application-label:'minimal' +application-icon-160:'res/uF.xml' +application-icon-240:'res/uF.xml' +application-icon-320:'res/uF.xml' +application-icon-480:'res/uF.xml' +application-icon-640:'res/uF.xml' +application-icon-65534:'res/uF.xml' +application: label='minimal' icon='res/uF.xml' +uses-library:'mylib1' +uses-library-not-required:'my_optional_lib' +uses-native-library:'native1' +uses-native-library-not-required:'optional' +launchable-activity: name='com.example.bundletool.minimal.MainActivity' label='minimal' icon='' +meta-data: name='android.nfc.cardemulation.host_apdu_service' resource='res/dU.xml' +uses-permission: name='android.permission.READ_EXTERNAL_STORAGE' +uses-implied-permission: name='android.permission.READ_EXTERNAL_STORAGE' reason='requested WRITE_EXTERNAL_STORAGE' +feature-group: label='' + uses-feature: name='android.hardware.bluetooth' + uses-feature: name='android.hardware.camera' + uses-feature: name='android.hardware.faketouch' + uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps' + uses-feature-sdk-23: name='android.hardware.telephony' + uses-implied-feature-sdk-23: name='android.hardware.telephony' reason='requested a telephony permission' +provides-component:'app-widget' +provides-component:'device-admin' +provides-component:'ime' +provides-component:'wallpaper' +provides-component:'accessibility' +provides-component:'print-service' +provides-component:'payment' +provides-component:'search' +provides-component:'document-provider' +provides-component:'notification-listener' +provides-component:'dream' +provides-component:'camera' +provides-component:'camera-secure' +main +other-receivers +other-services +supports-screens: 'normal' 'large' 'xlarge' +supports-any-density: 'true' +requires-smallest-width:'240' +compatible-width-limit:'360' +largest-width-limit:'480' +locales: '--_--' +densities: '160' '240' '320' '480' '640' '65534' +native-code: 'x86_64' +alt-native-code: 'x86' diff --git a/tools/aapt2/integration-tests/DumpTest/components_expected_proto.txt b/tools/aapt2/integration-tests/DumpTest/components_expected_proto.txt new file mode 100644 index 000000000000..d866479f04db --- /dev/null +++ b/tools/aapt2/integration-tests/DumpTest/components_expected_proto.txt @@ -0,0 +1,167 @@ +badging { + package { + package: "com.example.bundletool.minimal" + version_code: 1 + version_name: "1.0" + platform_version_name: "12" + platform_version_code: "31" + compile_sdk_version: 31 + compile_sdk_version_codename: "12" + } + application { + label: "minimal" + icon: "res/uF.xml" + density_icons { + key: 160 + value: "res/uF.xml" + } + density_icons { + key: 240 + value: "res/uF.xml" + } + density_icons { + key: 320 + value: "res/uF.xml" + } + density_icons { + key: 480 + value: "res/uF.xml" + } + density_icons { + key: 640 + value: "res/uF.xml" + } + density_icons { + key: 65534 + value: "res/uF.xml" + } + } + uses_sdk { + min_sdk_version: 21 + target_sdk_version: 31 + } + supports_screen { + screens: NORMAL + screens: LARGE + screens: XLARGE + supports_any_densities: true + requires_smallest_width_dp: 240 + compatible_width_limit_dp: 360 + largest_width_limit_dp: 480 + } + launchable_activity { + name: "com.example.bundletool.minimal.MainActivity" + label: "minimal" + } + compatible_screens { + screens { + size: 500 + density: 240 + } + screens { + size: 400 + density: 160 + } + } + architectures { + architectures: "x86_64" + alt_architectures: "x86" + } + supports_gl_texture { + name: "GL_OES_compressed_paletted_texture" + } + components { + main: true + other_receivers: true + other_services: true + provided_components: "app-widget" + provided_components: "device-admin" + provided_components: "ime" + provided_components: "wallpaper" + provided_components: "accessibility" + provided_components: "print-service" + provided_components: "payment" + provided_components: "search" + provided_components: "document-provider" + provided_components: "notification-listener" + provided_components: "dream" + provided_components: "camera" + provided_components: "camera-secure" + } + locales: "--_--" + densities: 160 + densities: 240 + densities: 320 + densities: 480 + densities: 640 + densities: 65534 + uses_configurations { + req_touch_screen: 3 + req_keyboard_type: 2 + req_hard_keyboard: -1 + req_navigation: 3 + req_five_way_nav: -1 + } + feature_groups { + features { + name: "android.hardware.bluetooth" + required: true + } + features { + name: "android.hardware.camera" + required: true + } + features { + name: "android.hardware.faketouch" + implied_data { + reasons: "default feature for all apps" + } + } + features { + name: "android.hardware.telephony" + implied_data { + from_sdk_23_permission: true + reasons: "requested a telephony permission" + } + } + } + uses_permissions { + name: "android.permission.BIND_ACCESSIBILITY_SERVICE" + max_sdk_version: 24 + required: true + } + uses_permissions { + name: "android.permission.RECEIVE_SMS" + sdk23_and_above: true + } + uses_permissions { + name: "android.permission.WRITE_EXTERNAL_STORAGE" + required: true + } + uses_permissions { + name: "android.permission.READ_EXTERNAL_STORAGE" + required: true + implied: true + } + permissions { + name: "minimal.FIRST_PERMISSION" + } + uses_libraries { + name: "mylib1" + required: true + } + uses_libraries { + name: "my_optional_lib" + } + uses_native_libraries { + name: "native1" + required: true + } + uses_native_libraries { + name: "optional" + } + metadata { + name: "android.nfc.cardemulation.host_apdu_service" + resource_string: "res/dU.xml" + } +} diff --git a/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt b/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt new file mode 100644 index 000000000000..6da6fc6f12c3 --- /dev/null +++ b/tools/aapt2/integration-tests/DumpTest/components_full_proto.txt @@ -0,0 +1,2312 @@ +badging { + package { + package: "com.example.bundletool.minimal" + version_code: 1 + version_name: "1.0" + platform_version_name: "12" + platform_version_code: "31" + compile_sdk_version: 31 + compile_sdk_version_codename: "12" + } + application { + label: "minimal" + icon: "res/uF.xml" + density_icons { + key: 160 + value: "res/uF.xml" + } + density_icons { + key: 240 + value: "res/uF.xml" + } + density_icons { + key: 320 + value: "res/uF.xml" + } + density_icons { + key: 480 + value: "res/uF.xml" + } + density_icons { + key: 640 + value: "res/uF.xml" + } + density_icons { + key: 65534 + value: "res/uF.xml" + } + } + uses_sdk { + min_sdk_version: 21 + target_sdk_version: 31 + } + supports_screen { + screens: NORMAL + screens: LARGE + screens: XLARGE + supports_any_densities: true + requires_smallest_width_dp: 240 + compatible_width_limit_dp: 360 + largest_width_limit_dp: 480 + } + launchable_activity { + name: "com.example.bundletool.minimal.MainActivity" + label: "minimal" + } + compatible_screens { + screens { + size: 500 + density: 240 + } + screens { + size: 400 + density: 160 + } + } + architectures { + architectures: "x86_64" + alt_architectures: "x86" + } + supports_gl_texture { + name: "GL_OES_compressed_paletted_texture" + } + components { + main: true + other_receivers: true + other_services: true + provided_components: "app-widget" + provided_components: "device-admin" + provided_components: "ime" + provided_components: "wallpaper" + provided_components: "accessibility" + provided_components: "print-service" + provided_components: "payment" + provided_components: "search" + provided_components: "document-provider" + provided_components: "notification-listener" + provided_components: "dream" + provided_components: "camera" + provided_components: "camera-secure" + } + locales: "--_--" + densities: 160 + densities: 240 + densities: 320 + densities: 480 + densities: 640 + densities: 65534 + uses_configurations { + req_touch_screen: 3 + req_keyboard_type: 2 + req_hard_keyboard: -1 + req_navigation: 3 + req_five_way_nav: -1 + } + feature_groups { + features { + name: "android.hardware.bluetooth" + required: true + } + features { + name: "android.hardware.camera" + required: true + } + features { + name: "android.hardware.faketouch" + implied_data { + reasons: "default feature for all apps" + } + } + features { + name: "android.hardware.telephony" + implied_data { + from_sdk_23_permission: true + reasons: "requested a telephony permission" + } + } + } + uses_permissions { + name: "android.permission.BIND_ACCESSIBILITY_SERVICE" + max_sdk_version: 24 + required: true + } + uses_permissions { + name: "android.permission.RECEIVE_SMS" + sdk23_and_above: true + } + uses_permissions { + name: "android.permission.WRITE_EXTERNAL_STORAGE" + required: true + } + uses_permissions { + name: "android.permission.READ_EXTERNAL_STORAGE" + required: true + implied: true + } + permissions { + name: "minimal.FIRST_PERMISSION" + } + uses_libraries { + name: "mylib1" + required: true + } + uses_libraries { + name: "my_optional_lib" + } + uses_native_libraries { + name: "native1" + required: true + } + uses_native_libraries { + name: "optional" + } + metadata { + name: "android.nfc.cardemulation.host_apdu_service" + resource_string: "res/dU.xml" + } +} +resource_table { + source_pool { + data: "\001\000\034\000$\000\000\000\001\000\000\000\000\000\000\000\000\001\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + } + package { + package_id { + id: 127 + } + package_name: "com.example.bundletool.minimal" + type { + type_id { + id: 1 + } + name: "color" + entry { + entry_id { + } + name: "black" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + prim { + color_argb8_value: 4278190080 + } + } + } + } + } + entry { + entry_id { + id: 1 + } + name: "purple_200" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + prim { + color_argb8_value: 4290479868 + } + } + } + } + } + entry { + entry_id { + id: 2 + } + name: "purple_500" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + prim { + color_argb8_value: 4284612846 + } + } + } + } + } + entry { + entry_id { + id: 3 + } + name: "purple_700" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + prim { + color_argb8_value: 4281794739 + } + } + } + } + } + entry { + entry_id { + id: 4 + } + name: "teal_200" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + prim { + color_argb8_value: 4278442693 + } + } + } + } + } + entry { + entry_id { + id: 5 + } + name: "teal_700" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + prim { + color_argb8_value: 4278290310 + } + } + } + } + } + entry { + entry_id { + id: 6 + } + name: "white" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + prim { + color_argb8_value: 4294967295 + } + } + } + } + } + } + type { + type_id { + id: 2 + } + name: "dimen" + entry { + entry_id { + } + name: "fab_margin" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + prim { + dimension_value: 4097 + } + } + } + } + } + } + type { + type_id { + id: 3 + } + name: "drawable" + entry { + entry_id { + } + name: "$ic_launcher_foreground__0" + visibility { + source { + } + } + config_value { + config { + density: 65534 + sdk_version: 24 + } + value { + source { + } + item { + file { + path: "res/Za.xml" + type: BINARY_XML + } + } + } + } + } + entry { + entry_id { + id: 1 + } + name: "ic_launcher_background" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + file { + path: "res/3N.xml" + type: BINARY_XML + } + } + } + } + } + entry { + entry_id { + id: 2 + } + name: "ic_launcher_foreground" + visibility { + source { + } + } + config_value { + config { + density: 65534 + sdk_version: 24 + } + value { + source { + } + item { + file { + path: "res/qm.xml" + type: BINARY_XML + } + } + } + } + } + } + type { + type_id { + id: 4 + } + name: "mipmap" + entry { + entry_id { + } + name: "ic_launcher" + visibility { + source { + } + } + config_value { + config { + density: 160 + } + value { + source { + } + item { + file { + path: "res/u3.png" + type: PNG + } + } + } + } + config_value { + config { + density: 240 + } + value { + source { + } + item { + file { + path: "res/SD.png" + type: PNG + } + } + } + } + config_value { + config { + density: 320 + } + value { + source { + } + item { + file { + path: "res/jy.png" + type: PNG + } + } + } + } + config_value { + config { + density: 480 + } + value { + source { + } + item { + file { + path: "res/D2.png" + type: PNG + } + } + } + } + config_value { + config { + density: 640 + } + value { + source { + } + item { + file { + path: "res/CG.png" + type: PNG + } + } + } + } + config_value { + config { + density: 65534 + sdk_version: 26 + } + value { + source { + } + item { + file { + path: "res/uF.xml" + type: BINARY_XML + } + } + } + } + } + entry { + entry_id { + id: 1 + } + name: "ic_launcher_round" + visibility { + source { + } + } + config_value { + config { + density: 160 + } + value { + source { + } + item { + file { + path: "res/7c.png" + type: PNG + } + } + } + } + config_value { + config { + density: 240 + } + value { + source { + } + item { + file { + path: "res/tf.png" + type: PNG + } + } + } + } + config_value { + config { + density: 320 + } + value { + source { + } + item { + file { + path: "res/1S.png" + type: PNG + } + } + } + } + config_value { + config { + density: 480 + } + value { + source { + } + item { + file { + path: "res/5Q.png" + type: PNG + } + } + } + } + config_value { + config { + density: 640 + } + value { + source { + } + item { + file { + path: "res/C9.png" + type: PNG + } + } + } + } + config_value { + config { + density: 65534 + sdk_version: 26 + } + value { + source { + } + item { + file { + path: "res/oy.xml" + type: BINARY_XML + } + } + } + } + } + } + type { + type_id { + id: 5 + } + name: "string" + entry { + entry_id { + } + name: "action_settings" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + str { + value: "Settings" + } + } + } + } + } + entry { + entry_id { + id: 1 + } + name: "app_name" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + str { + value: "minimal" + } + } + } + } + } + entry { + entry_id { + id: 2 + } + name: "first_fragment_label" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + str { + value: "First Fragment" + } + } + } + } + } + entry { + entry_id { + id: 3 + } + name: "hello_first_fragment" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + str { + value: "Hello first fragment" + } + } + } + } + } + entry { + entry_id { + id: 4 + } + name: "hello_second_fragment" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + str { + value: "Hello second fragment. Arg: %1$s" + } + } + } + } + } + entry { + entry_id { + id: 5 + } + name: "next" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + str { + value: "Next" + } + } + } + } + } + entry { + entry_id { + id: 6 + } + name: "previous" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + str { + value: "Previous" + } + } + } + } + } + entry { + entry_id { + id: 7 + } + name: "second_fragment_label" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + str { + value: "Second Fragment" + } + } + } + } + } + } + type { + type_id { + id: 6 + } + name: "xml" + entry { + entry_id { + } + name: "apduservice" + visibility { + source { + } + } + config_value { + config { + } + value { + source { + } + item { + file { + path: "res/dU.xml" + type: BINARY_XML + } + } + } + } + } + } + } + tool_fingerprint { + tool: "Android Asset Packaging Tool (aapt)" + version: "2.19-SOONG BUILD NUMBER PLACEHOLDER" + } +} +xml_files { + path: "res/oy.xml" + root { + element { + namespace_declaration { + prefix: "android" + uri: "http://schemas.android.com/apk/res/android" + source { + line_number: 2 + } + } + name: "adaptive-icon" + child { + element { + name: "background" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "drawable" + source { + } + resource_id: 16843161 + compiled_item { + ref { + id: 2130903041 + } + } + } + } + source { + line_number: 3 + } + } + child { + element { + name: "foreground" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "drawable" + source { + } + resource_id: 16843161 + compiled_item { + ref { + id: 2130903042 + } + } + } + } + source { + line_number: 4 + } + } + } + source { + line_number: 2 + } + } +} +xml_files { + path: "AndroidManifest.xml" + root { + element { + namespace_declaration { + prefix: "android" + uri: "http://schemas.android.com/apk/res/android" + source { + line_number: 2 + } + } + name: "manifest" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "versionCode" + source { + } + resource_id: 16843291 + compiled_item { + prim { + int_decimal_value: 1 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "versionName" + value: "1.0" + resource_id: 16843292 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "compileSdkVersion" + source { + } + resource_id: 16844146 + compiled_item { + prim { + int_decimal_value: 31 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "compileSdkVersionCodename" + value: "12" + resource_id: 16844147 + } + attribute { + name: "package" + value: "com.example.bundletool.minimal" + } + attribute { + name: "platformBuildVersionCode" + source { + } + compiled_item { + prim { + int_decimal_value: 31 + } + } + } + attribute { + name: "platformBuildVersionName" + source { + } + compiled_item { + prim { + int_decimal_value: 12 + } + } + } + child { + element { + name: "uses-sdk" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "minSdkVersion" + source { + } + resource_id: 16843276 + compiled_item { + prim { + int_decimal_value: 21 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "targetSdkVersion" + source { + } + resource_id: 16843376 + compiled_item { + prim { + int_decimal_value: 31 + } + } + } + } + source { + line_number: 7 + } + } + child { + element { + name: "supports-screens" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "anyDensity" + source { + } + resource_id: 16843372 + compiled_item { + prim { + boolean_value: true + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "smallScreens" + source { + } + resource_id: 16843396 + compiled_item { + prim { + boolean_value: false + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "normalScreens" + source { + } + resource_id: 16843397 + compiled_item { + prim { + boolean_value: true + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "largeScreens" + source { + } + resource_id: 16843398 + compiled_item { + prim { + boolean_value: true + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "requiresSmallestWidthDp" + source { + } + resource_id: 16843620 + compiled_item { + prim { + int_decimal_value: 240 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "compatibleWidthLimitDp" + source { + } + resource_id: 16843621 + compiled_item { + prim { + int_decimal_value: 360 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "largestWidthLimitDp" + source { + } + resource_id: 16843622 + compiled_item { + prim { + int_decimal_value: 480 + } + } + } + } + source { + line_number: 11 + } + } + child { + element { + name: "uses-configuration" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "reqTouchScreen" + source { + } + resource_id: 16843303 + compiled_item { + prim { + int_decimal_value: 3 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "reqKeyboardType" + source { + } + resource_id: 16843304 + compiled_item { + prim { + int_decimal_value: 2 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "reqHardKeyboard" + source { + } + resource_id: 16843305 + compiled_item { + prim { + boolean_value: true + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "reqNavigation" + source { + } + resource_id: 16843306 + compiled_item { + prim { + int_decimal_value: 3 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "reqFiveWayNav" + source { + } + resource_id: 16843314 + compiled_item { + prim { + boolean_value: true + } + } + } + } + source { + line_number: 20 + } + } + child { + element { + name: "supports-gl-texture" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "GL_OES_compressed_paletted_texture" + resource_id: 16842755 + } + } + source { + line_number: 27 + } + } + child { + element { + name: "permission" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "minimal.FIRST_PERMISSION" + resource_id: 16842755 + } + } + source { + line_number: 29 + } + } + child { + element { + name: "uses-feature" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.hardware.camera" + resource_id: 16842755 + } + } + source { + line_number: 31 + } + } + child { + element { + name: "uses-feature" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.hardware.bluetooth" + resource_id: 16842755 + } + } + source { + line_number: 32 + } + } + child { + element { + name: "uses-permission" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.permission.BIND_ACCESSIBILITY_SERVICE" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "maxSdkVersion" + source { + } + resource_id: 16843377 + compiled_item { + prim { + int_decimal_value: 24 + } + } + } + } + source { + line_number: 34 + } + } + child { + element { + name: "uses-permission-sdk-23" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.permission.RECEIVE_SMS" + resource_id: 16842755 + } + } + source { + line_number: 38 + } + } + child { + element { + name: "uses-permission" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.permission.WRITE_EXTERNAL_STORAGE" + resource_id: 16842755 + } + } + source { + line_number: 40 + } + } + child { + element { + name: "compatible-screens" + child { + element { + name: "screen" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "screenSize" + source { + } + resource_id: 16843466 + compiled_item { + prim { + int_decimal_value: 500 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "screenDensity" + source { + } + resource_id: 16843467 + compiled_item { + prim { + int_decimal_value: 240 + } + } + } + } + source { + line_number: 43 + } + } + child { + element { + name: "screen" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "screenSize" + source { + } + resource_id: 16843466 + compiled_item { + prim { + int_decimal_value: 400 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "screenDensity" + source { + } + resource_id: 16843467 + compiled_item { + prim { + int_decimal_value: 160 + } + } + } + } + source { + line_number: 46 + } + } + } + source { + line_number: 42 + } + } + child { + element { + name: "application" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "label" + source { + } + resource_id: 16842753 + compiled_item { + ref { + id: 2131034113 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "icon" + source { + } + resource_id: 16842754 + compiled_item { + ref { + id: 2130968576 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "allowBackup" + source { + } + resource_id: 16843392 + compiled_item { + prim { + boolean_value: true + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "supportsRtl" + source { + } + resource_id: 16843695 + compiled_item { + prim { + boolean_value: true + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "multiArch" + source { + } + resource_id: 16843918 + compiled_item { + prim { + boolean_value: true + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "roundIcon" + source { + } + resource_id: 16844076 + compiled_item { + ref { + id: 2130968577 + } + } + } + child { + element { + name: "uses-library" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "mylib1" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "required" + source { + } + resource_id: 16843406 + compiled_item { + prim { + boolean_value: true + } + } + } + } + source { + line_number: 58 + } + } + child { + element { + name: "uses-library" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "my_optional_lib" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "required" + source { + } + resource_id: 16843406 + compiled_item { + prim { + boolean_value: false + } + } + } + } + source { + line_number: 61 + } + } + child { + element { + name: "uses-native-library" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "native1" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "required" + source { + } + resource_id: 16843406 + compiled_item { + prim { + boolean_value: true + } + } + } + } + source { + line_number: 65 + } + } + child { + element { + name: "uses-native-library" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "optional" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "required" + source { + } + resource_id: 16843406 + compiled_item { + prim { + boolean_value: false + } + } + } + } + source { + line_number: 68 + } + } + child { + element { + name: "activity" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "label" + source { + } + resource_id: 16842753 + compiled_item { + ref { + id: 2131034113 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.MainActivity" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "exported" + source { + } + resource_id: 16842768 + compiled_item { + prim { + boolean_value: false + } + } + } + child { + element { + name: "intent-filter" + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.intent.action.MAIN" + resource_id: 16842755 + } + } + source { + line_number: 77 + } + } + child { + element { + name: "category" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.intent.category.LAUNCHER" + resource_id: 16842755 + } + } + source { + line_number: 79 + } + } + } + source { + line_number: 76 + } + } + } + source { + line_number: 72 + } + } + child { + element { + name: "activity" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "label" + source { + } + resource_id: 16842753 + compiled_item { + ref { + id: 2131034113 + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.AnotherActivity" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "exported" + source { + } + resource_id: 16842768 + compiled_item { + prim { + boolean_value: false + } + } + } + child { + element { + name: "intent-filter" + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.media.action.VIDEO_CAMERA" + resource_id: 16842755 + } + } + source { + line_number: 87 + } + } + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.media.action.STILL_IMAGE_CAMERA_SECURE" + resource_id: 16842755 + } + } + source { + line_number: 88 + } + } + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.intent.action.SEARCH" + resource_id: 16842755 + } + } + source { + line_number: 89 + } + } + } + source { + line_number: 86 + } + } + } + source { + line_number: 82 + } + } + child { + element { + name: "receiver" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.OneReceiver" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "exported" + source { + } + resource_id: 16842768 + compiled_item { + prim { + boolean_value: false + } + } + } + child { + element { + name: "intent-filter" + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.appwidget.action.APPWIDGET_UPDATE" + resource_id: 16842755 + } + } + source { + line_number: 97 + } + } + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.app.action.DEVICE_ADMIN_ENABLED" + resource_id: 16842755 + } + } + source { + line_number: 98 + } + } + } + source { + line_number: 96 + } + } + } + source { + line_number: 93 + } + } + child { + element { + name: "receiver" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.TwoReceiver" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "permission" + value: "android.permission.BIND_DEVICE_ADMIN" + resource_id: 16842758 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "exported" + source { + } + resource_id: 16842768 + compiled_item { + prim { + boolean_value: false + } + } + } + child { + element { + name: "intent-filter" + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.app.action.DEVICE_ADMIN_ENABLED" + resource_id: 16842755 + } + } + source { + line_number: 106 + } + } + } + source { + line_number: 105 + } + } + } + source { + line_number: 101 + } + } + child { + element { + name: "receiver" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.ThreeReceiver" + resource_id: 16842755 + } + } + source { + line_number: 109 + } + } + child { + element { + name: "service" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.OneService" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "exported" + source { + } + resource_id: 16842768 + compiled_item { + prim { + boolean_value: false + } + } + } + child { + element { + name: "intent-filter" + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.view.InputMethod" + resource_id: 16842755 + } + } + source { + line_number: 115 + } + } + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.service.wallpaper.WallpaperService" + resource_id: 16842755 + } + } + source { + line_number: 116 + } + } + } + source { + line_number: 114 + } + } + } + source { + line_number: 111 + } + } + child { + element { + name: "service" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.Services$TwoService" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "permission" + value: "android.permission.BIND_ACCESSIBILITY_SERVICE" + resource_id: 16842758 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "exported" + source { + } + resource_id: 16842768 + compiled_item { + prim { + boolean_value: false + } + } + } + child { + element { + name: "intent-filter" + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.accessibilityservice.AccessibilityService" + resource_id: 16842755 + } + } + source { + line_number: 124 + } + } + } + source { + line_number: 123 + } + } + } + source { + line_number: 119 + } + } + child { + element { + name: "service" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.Services$ThreeService" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "permission" + value: "android.permission.BIND_PRINT_SERVICE" + resource_id: 16842758 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "exported" + source { + } + resource_id: 16842768 + compiled_item { + prim { + boolean_value: false + } + } + } + child { + element { + name: "intent-filter" + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.printservice.PrintService" + resource_id: 16842755 + } + } + source { + line_number: 132 + } + } + } + source { + line_number: 131 + } + } + } + source { + line_number: 127 + } + } + child { + element { + name: "service" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.Services$FourService" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "permission" + value: "android.permission.BIND_NFC_SERVICE" + resource_id: 16842758 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "exported" + source { + } + resource_id: 16842768 + compiled_item { + prim { + boolean_value: false + } + } + } + child { + element { + name: "intent-filter" + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.nfc.cardemulation.action.HOST_APDU_SERVICE" + resource_id: 16842755 + } + } + source { + line_number: 140 + } + } + } + source { + line_number: 139 + } + } + child { + element { + name: "meta-data" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.nfc.cardemulation.host_apdu_service" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "resource" + source { + } + resource_id: 16842789 + compiled_item { + ref { + id: 2131099648 + } + } + } + } + source { + line_number: 143 + } + } + } + source { + line_number: 135 + } + } + child { + element { + name: "service" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.Services$FiveService" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "permission" + value: "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" + resource_id: 16842758 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "exported" + source { + } + resource_id: 16842768 + compiled_item { + prim { + boolean_value: false + } + } + } + child { + element { + name: "intent-filter" + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.service.notification.NotificationListenerService" + resource_id: 16842755 + } + } + source { + line_number: 152 + } + } + } + source { + line_number: 151 + } + } + } + source { + line_number: 147 + } + } + child { + element { + name: "service" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.Services$SixService" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "permission" + value: "android.permission.BIND_DREAM_SERVICE" + resource_id: 16842758 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "exported" + source { + } + resource_id: 16842768 + compiled_item { + prim { + boolean_value: false + } + } + } + child { + element { + name: "intent-filter" + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.service.dreams.DreamService" + resource_id: 16842755 + } + } + source { + line_number: 160 + } + } + } + source { + line_number: 159 + } + } + } + source { + line_number: 155 + } + } + child { + element { + name: "service" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.Services$SevenService" + resource_id: 16842755 + } + } + source { + line_number: 163 + } + } + child { + element { + name: "provider" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "com.example.bundletool.minimal.OneProvider" + resource_id: 16842755 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "permission" + value: "android.permission.MANAGE_DOCUMENTS" + resource_id: 16842758 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "exported" + source { + } + resource_id: 16842768 + compiled_item { + prim { + boolean_value: true + } + } + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "authorities" + value: "A" + resource_id: 16842776 + } + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "grantUriPermissions" + source { + } + resource_id: 16842779 + compiled_item { + prim { + boolean_value: true + } + } + } + child { + element { + name: "intent-filter" + child { + element { + name: "action" + attribute { + namespace_uri: "http://schemas.android.com/apk/res/android" + name: "name" + value: "android.content.action.DOCUMENTS_PROVIDER" + resource_id: 16842755 + } + } + source { + line_number: 172 + } + } + } + source { + line_number: 171 + } + } + } + source { + line_number: 165 + } + } + } + source { + line_number: 51 + } + } + } + source { + line_number: 2 + } + } +} diff --git a/tools/aapt2/integration-tests/DumpTest/components_permissions_expected.txt b/tools/aapt2/integration-tests/DumpTest/components_permissions_expected.txt new file mode 100644 index 000000000000..f79de5cba11d --- /dev/null +++ b/tools/aapt2/integration-tests/DumpTest/components_permissions_expected.txt @@ -0,0 +1,5 @@ +package: com.example.bundletool.minimal +permission: minimal.FIRST_PERMISSION +uses-permission: name='android.permission.BIND_ACCESSIBILITY_SERVICE' maxSdkVersion='24' +uses-permission-sdk-23: name='android.permission.RECEIVE_SMS' +uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE' diff --git a/tools/aapt2/integration-tests/DumpTest/minimal.apk b/tools/aapt2/integration-tests/DumpTest/minimal.apk Binary files differnew file mode 100644 index 000000000000..a8415faca390 --- /dev/null +++ b/tools/aapt2/integration-tests/DumpTest/minimal.apk diff --git a/tools/aapt2/integration-tests/DumpTest/minimal_expected.txt b/tools/aapt2/integration-tests/DumpTest/minimal_expected.txt new file mode 100644 index 000000000000..85ab5d80cd39 --- /dev/null +++ b/tools/aapt2/integration-tests/DumpTest/minimal_expected.txt @@ -0,0 +1,92 @@ +package: name='com.lato.bubblegirl' versionCode='33' versionName='1.0.0' platformBuildVersionName='8.1.0' platformBuildVersionCode='27' +sdkVersion:'19' +targetSdkVersion:'26' +application-label:'Bubble Girl' +application-label-ar:'Bubble Girl' +application-label-az:'Bubble Girl' +application-label-be:'Bubble Girl' +application-label-bg:'Bubble Girl' +application-label-bn:'Bubble Girl' +application-label-bs:'Bubble Girl' +application-label-ca:'Bubble Girl' +application-label-cs:'Bubble Girl' +application-label-da:'Bubble Girl' +application-label-de:'Bubble Girl' +application-label-el:'Bubble Girl' +application-label-es:'Bubble Girl' +application-label-es-ES:'Bubble Girl' +application-label-et:'Bubble Girl' +application-label-eu:'Bubble Girl' +application-label-fa:'Bubble Girl' +application-label-fi:'Bubble Girl' +application-label-fr:'Bubble Girl' +application-label-fr-CA:'Bubble Girl' +application-label-gl:'Bubble Girl' +application-label-hi:'Bubble Girl' +application-label-hr:'Bubble Girl' +application-label-hu:'Bubble Girl' +application-label-hy:'Bubble Girl' +application-label-in:'Bubble Girl' +application-label-is:'Bubble Girl' +application-label-it:'Bubble Girl' +application-label-iw:'Bubble Girl' +application-label-ja:'Bubble Girl' +application-label-jv:'Bubble Girl' +application-label-ka:'Bubble Girl' +application-label-kk:'Bubble Girl' +application-label-kn:'Bubble Girl' +application-label-ko:'Bubble Girl' +application-label-lt:'Bubble Girl' +application-label-lv:'Bubble Girl' +application-label-mk:'Bubble Girl' +application-label-ml:'Bubble Girl' +application-label-mr:'Bubble Girl' +application-label-ms:'Bubble Girl' +application-label-nb:'Bubble Girl' +application-label-nl:'Bubble Girl' +application-label-pa:'Bubble Girl' +application-label-pl:'Bubble Girl' +application-label-pt-BR:'Bubble Girl' +application-label-pt-PT:'Bubble Girl' +application-label-ro:'Bubble Girl' +application-label-ru:'Bubble Girl' +application-label-sk:'Bubble Girl' +application-label-sl:'Bubble Girl' +application-label-sq:'Bubble Girl' +application-label-sr:'Bubble Girl' +application-label-su:'Bubble Girl' +application-label-sv:'Bubble Girl' +application-label-ta:'Bubble Girl' +application-label-te:'Bubble Girl' +application-label-th:'Bubble Girl' +application-label-tl:'Bubble Girl' +application-label-tr:'Bubble Girl' +application-label-tt:'Bubble Girl' +application-label-uk:'Bubble Girl' +application-label-vi:'Bubble Girl' +application-label-zh-CN:'Bubble Girl' +application-label-zh-HK:'Bubble Girl' +application-label-zh-TW:'Bubble Girl' +application-icon-160:'res/theme/1f.png' +application-icon-240:'res/theme/1f.png' +application-icon-320:'res/theme/1f.png' +application-icon-480:'res/theme/1f.png' +application-icon-640:'res/theme/1f.png' +application: label='Bubble Girl' icon='res/theme/1f.png' +launchable-activity: name='com.sonymobile.runtimeskinning.livewallpaperlib.configactivity.LauncherActivity' label='' icon='' +uses-library:'com.sony.device' +uses-permission: name='com.sonymobile.permission.RUNTIME_SKIN' +feature-group: label='' + uses-gl-es: '0x30000' + uses-feature: name='android.software.live_wallpaper' + uses-feature: name='android.hardware.faketouch' + uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps' + uses-feature: name='android.hardware.screen.portrait' + uses-implied-feature: name='android.hardware.screen.portrait' reason='one or more activities have specified a portrait orientation' +provides-component:'wallpaper' +main +other-activities +supports-screens: 'small' 'normal' 'large' 'xlarge' +supports-any-density: 'true' +locales: '--_--' 'ar' 'az' 'be' 'bg' 'bn' 'bs' 'ca' 'cs' 'da' 'de' 'el' 'es' 'es-ES' 'et' 'eu' 'fa' 'fi' 'fr' 'fr-CA' 'gl' 'hi' 'hr' 'hu' 'hy' 'in' 'is' 'it' 'iw' 'ja' 'jv' 'ka' 'kk' 'kn' 'ko' 'lt' 'lv' 'mk' 'ml' 'mr' 'ms' 'nb' 'nl' 'pa' 'pl' 'pt-BR' 'pt-PT' 'ro' 'ru' 'sk' 'sl' 'sq' 'sr' 'su' 'sv' 'ta' 'te' 'th' 'tl' 'tr' 'tt' 'uk' 'vi' 'zh-CN' 'zh-HK' 'zh-TW' +densities: '160' '240' '320' '480' '640' diff --git a/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk.apk b/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk.apk Binary files differnew file mode 100644 index 000000000000..7b269a5b8e5a --- /dev/null +++ b/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk.apk diff --git a/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk_expected.txt b/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk_expected.txt new file mode 100644 index 000000000000..85e8d0a3cbba --- /dev/null +++ b/tools/aapt2/integration-tests/DumpTest/multiple_uses_sdk_expected.txt @@ -0,0 +1,23 @@ +package: name='com.test.e17wmultiapknexus' versionCode='107' versionName='14' platformBuildVersionName='2.3.3' platformBuildVersionCode='10' +sdkVersion:'1' +application-label:'w45wmultiapknexus_10' +application-icon-120:'res/drawable-ldpi-v4/icon.png' +application-icon-160:'res/drawable-mdpi-v4/icon.png' +application-icon-240:'res/drawable-hdpi-v4/icon.png' +application: label='w45wmultiapknexus_10' icon='res/drawable-mdpi-v4/icon.png' +launchable-activity: name='com.test.e17wmultiapknexus.TestActivity' label='' icon='' +compatible-screens:'500/320' +uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE' +uses-implied-permission: name='android.permission.WRITE_EXTERNAL_STORAGE' reason='targetSdkVersion < 4' +uses-permission: name='android.permission.READ_PHONE_STATE' +uses-implied-permission: name='android.permission.READ_PHONE_STATE' reason='targetSdkVersion < 4' +uses-permission: name='android.permission.READ_EXTERNAL_STORAGE' +uses-implied-permission: name='android.permission.READ_EXTERNAL_STORAGE' reason='requested WRITE_EXTERNAL_STORAGE' +feature-group: label='' + uses-feature: name='android.hardware.faketouch' + uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps' +main +supports-screens: 'small' 'normal' 'large' 'xlarge' +supports-any-density: 'true' +locales: '--_--' +densities: '120' '160' '240' diff --git a/tools/aapt2/io/BigBufferStream.h b/tools/aapt2/io/BigBufferStream.h index 8b5c8b84cd3c..63a5e5756ed4 100644 --- a/tools/aapt2/io/BigBufferStream.h +++ b/tools/aapt2/io/BigBufferStream.h @@ -17,15 +17,15 @@ #ifndef AAPT_IO_BIGBUFFERSTREAM_H #define AAPT_IO_BIGBUFFERSTREAM_H +#include "androidfw/BigBuffer.h" #include "io/Io.h" -#include "util/BigBuffer.h" namespace aapt { namespace io { class BigBufferInputStream : public KnownSizeInputStream { public: - inline explicit BigBufferInputStream(const BigBuffer* buffer) + inline explicit BigBufferInputStream(const android::BigBuffer* buffer) : buffer_(buffer), iter_(buffer->begin()) { } virtual ~BigBufferInputStream() = default; @@ -47,15 +47,15 @@ class BigBufferInputStream : public KnownSizeInputStream { private: DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream); - const BigBuffer* buffer_; - BigBuffer::const_iterator iter_; + const android::BigBuffer* buffer_; + android::BigBuffer::const_iterator iter_; size_t offset_ = 0; size_t bytes_read_ = 0; }; class BigBufferOutputStream : public OutputStream { public: - inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) { + inline explicit BigBufferOutputStream(android::BigBuffer* buffer) : buffer_(buffer) { } virtual ~BigBufferOutputStream() = default; @@ -70,7 +70,7 @@ class BigBufferOutputStream : public OutputStream { private: DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream); - BigBuffer* buffer_; + android::BigBuffer* buffer_; }; } // namespace io diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h index 565aad6f2284..08d497def8a4 100644 --- a/tools/aapt2/io/File.h +++ b/tools/aapt2/io/File.h @@ -22,8 +22,7 @@ #include <vector> #include "android-base/macros.h" - -#include "Source.h" +#include "androidfw/Source.h" #include "io/Data.h" #include "util/Files.h" #include "util/Util.h" @@ -49,7 +48,7 @@ class IFile { // Returns the source of this file. This is for presentation to the user and // may not be a valid file system path (for example, it may contain a '@' sign to separate // the files within a ZIP archive from the path to the containing ZIP archive. - virtual const Source& GetSource() const = 0; + virtual const android::Source& GetSource() const = 0; IFile* CreateFileSegment(size_t offset, size_t len); @@ -76,7 +75,7 @@ class FileSegment : public IFile { std::unique_ptr<IData> OpenAsData() override; std::unique_ptr<io::InputStream> OpenInputStream() override; - const Source& GetSource() const override { + const android::Source& GetSource() const override { return file_->GetSource(); } @@ -102,7 +101,7 @@ class IFileCollection { public: virtual ~IFileCollection() = default; - virtual IFile* FindFile(const android::StringPiece& path) = 0; + virtual IFile* FindFile(android::StringPiece path) = 0; virtual std::unique_ptr<IFileCollectionIterator> Iterator() = 0; virtual char GetDirSeparator() = 0; }; diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp index fc2e45e74b4d..a64982a7fa5c 100644 --- a/tools/aapt2/io/FileSystem.cpp +++ b/tools/aapt2/io/FileSystem.cpp @@ -19,13 +19,12 @@ #include <dirent.h> #include "android-base/errors.h" +#include "androidfw/Source.h" #include "androidfw/StringPiece.h" -#include "utils/FileMap.h" -#include "Source.h" #include "io/FileStream.h" #include "util/Files.h" - #include "util/Util.h" +#include "utils/FileMap.h" using ::android::StringPiece; using ::android::base::SystemErrorCodeToString; @@ -33,7 +32,8 @@ using ::android::base::SystemErrorCodeToString; namespace aapt { namespace io { -RegularFile::RegularFile(const Source& source) : source_(source) {} +RegularFile::RegularFile(const android::Source& source) : source_(source) { +} std::unique_ptr<IData> RegularFile::OpenAsData() { android::FileMap map; @@ -50,7 +50,7 @@ std::unique_ptr<io::InputStream> RegularFile::OpenInputStream() { return util::make_unique<FileInputStream>(source_.path); } -const Source& RegularFile::GetSource() const { +const android::Source& RegularFile::GetSource() const { return source_; } @@ -67,8 +67,8 @@ IFile* FileCollectionIterator::Next() { return result; } -std::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiece& root, - std::string* outError) { +std::unique_ptr<FileCollection> FileCollection::Create(android::StringPiece root, + std::string* outError) { std::unique_ptr<FileCollection> collection = std::unique_ptr<FileCollection>(new FileCollection()); @@ -80,7 +80,7 @@ std::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiec std::vector<std::string> sorted_files; while (struct dirent *entry = readdir(d.get())) { - std::string prefix_path = root.to_string(); + std::string prefix_path(root); file::AppendPath(&prefix_path, entry->d_name); // The directory to iterate over looking for files @@ -117,12 +117,19 @@ std::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiec return collection; } -IFile* FileCollection::InsertFile(const StringPiece& path) { - return (files_[path.to_string()] = util::make_unique<RegularFile>(Source(path))).get(); +IFile* FileCollection::InsertFile(StringPiece path) { + auto file = util::make_unique<RegularFile>(android::Source(path)); + auto it = files_.lower_bound(path); + if (it != files_.end() && it->first == path) { + it->second = std::move(file); + } else { + it = files_.emplace_hint(it, path, std::move(file)); + } + return it->second.get(); } -IFile* FileCollection::FindFile(const StringPiece& path) { - auto iter = files_.find(path.to_string()); +IFile* FileCollection::FindFile(StringPiece path) { + auto iter = files_.find(path); if (iter != files_.end()) { return iter->second.get(); } diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h index 04c6fa15bc85..0e798fc1b975 100644 --- a/tools/aapt2/io/FileSystem.h +++ b/tools/aapt2/io/FileSystem.h @@ -27,16 +27,16 @@ namespace io { // A regular file from the file system. Uses mmap to open the data. class RegularFile : public IFile { public: - explicit RegularFile(const Source& source); + explicit RegularFile(const android::Source& source); std::unique_ptr<IData> OpenAsData() override; std::unique_ptr<io::InputStream> OpenInputStream() override; - const Source& GetSource() const override; + const android::Source& GetSource() const override; private: DISALLOW_COPY_AND_ASSIGN(RegularFile); - Source source_; + android::Source source_; }; class FileCollection; @@ -60,12 +60,11 @@ class FileCollection : public IFileCollection { FileCollection() = default; /** Creates a file collection containing all files contained in the specified root directory. */ - static std::unique_ptr<FileCollection> Create(const android::StringPiece& path, - std::string* outError); + static std::unique_ptr<FileCollection> Create(android::StringPiece path, std::string* outError); // Adds a file located at path. Returns the IFile representation of that file. - IFile* InsertFile(const android::StringPiece& path); - IFile* FindFile(const android::StringPiece& path) override; + IFile* InsertFile(android::StringPiece path); + IFile* FindFile(android::StringPiece path) override; std::unique_ptr<IFileCollectionIterator> Iterator() override; char GetDirSeparator() override; @@ -74,7 +73,7 @@ class FileCollection : public IFileCollection { friend class FileCollectionIterator; - std::map<std::string, std::unique_ptr<IFile>> files_; + std::map<std::string, std::unique_ptr<IFile>, std::less<>> files_; }; } // namespace io diff --git a/tools/aapt2/io/StringStream.cpp b/tools/aapt2/io/StringStream.cpp index 4ca04a8c7477..9c497882b99b 100644 --- a/tools/aapt2/io/StringStream.cpp +++ b/tools/aapt2/io/StringStream.cpp @@ -21,7 +21,7 @@ using ::android::StringPiece; namespace aapt { namespace io { -StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) { +StringInputStream::StringInputStream(StringPiece str) : str_(str), offset_(0u) { } bool StringInputStream::Next(const void** data, size_t* size) { diff --git a/tools/aapt2/io/StringStream.h b/tools/aapt2/io/StringStream.h index f29890ab7ee5..f7bdecca0dee 100644 --- a/tools/aapt2/io/StringStream.h +++ b/tools/aapt2/io/StringStream.h @@ -29,7 +29,7 @@ namespace io { class StringInputStream : public KnownSizeInputStream { public: - explicit StringInputStream(const android::StringPiece& str); + explicit StringInputStream(android::StringPiece str); bool Next(const void** data, size_t* size) override; diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp index bb925c9b3f8e..79d8d527fe8b 100644 --- a/tools/aapt2/io/Util.cpp +++ b/tools/aapt2/io/Util.cpp @@ -26,44 +26,48 @@ using ::google::protobuf::io::ZeroCopyOutputStream; namespace aapt { namespace io { -bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path, +bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path, uint32_t compression_flags, IArchiveWriter* writer) { TRACE_CALL(); if (context->IsVerbose()) { - context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path << " to archive"); + context->GetDiagnostics()->Note(android::DiagMessage() + << "writing " << out_path << " to archive"); } if (!writer->WriteFile(out_path, compression_flags, in)) { - context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << out_path - << " to archive: " << writer->GetError()); + context->GetDiagnostics()->Error(android::DiagMessage() + << "failed to write " << out_path + << " to archive: " << writer->GetError()); return false; } return true; } -bool CopyFileToArchive(IAaptContext* context, io::IFile* file, const std::string& out_path, +bool CopyFileToArchive(IAaptContext* context, io::IFile* file, std::string_view out_path, uint32_t compression_flags, IArchiveWriter* writer) { TRACE_CALL(); std::unique_ptr<io::IData> data = file->OpenAsData(); if (!data) { - context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "failed to open file"); + context->GetDiagnostics()->Error(android::DiagMessage(file->GetSource()) + << "failed to open file"); return false; } return CopyInputStreamToArchive(context, data.get(), out_path, compression_flags, writer); } bool CopyFileToArchivePreserveCompression(IAaptContext* context, io::IFile* file, - const std::string& out_path, IArchiveWriter* writer) { + std::string_view out_path, IArchiveWriter* writer) { uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u; return CopyFileToArchive(context, file, out_path, compression_flags, writer); } bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg, - const std::string& out_path, uint32_t compression_flags, + std::string_view out_path, uint32_t compression_flags, IArchiveWriter* writer) { TRACE_CALL(); if (context->IsVerbose()) { - context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path << " to archive"); + context->GetDiagnostics()->Note(android::DiagMessage() + << "writing " << out_path << " to archive"); } if (writer->StartEntry(out_path, compression_flags)) { @@ -72,8 +76,8 @@ bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* prot // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface. ::google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); if (!proto_msg->SerializeToZeroCopyStream(&adaptor)) { - context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << out_path - << " to archive"); + context->GetDiagnostics()->Error(android::DiagMessage() + << "failed to write " << out_path << " to archive"); return false; } } @@ -82,8 +86,8 @@ bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* prot return true; } } - context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << out_path - << " to archive: " << writer->GetError()); + context->GetDiagnostics()->Error(android::DiagMessage() << "failed to write " << out_path + << " to archive: " << writer->GetError()); return false; } @@ -106,7 +110,7 @@ bool Copy(OutputStream* out, InputStream* in) { return !in->HadError(); } -bool Copy(OutputStream* out, const StringPiece& in) { +bool Copy(OutputStream* out, StringPiece in) { const char* in_buffer = in.data(); size_t in_len = in.size(); while (in_len != 0) { diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h index 5cb8206db23c..685f522a2e71 100644 --- a/tools/aapt2/io/Util.h +++ b/tools/aapt2/io/Util.h @@ -17,12 +17,11 @@ #ifndef AAPT_IO_UTIL_H #define AAPT_IO_UTIL_H -#include <string> - -#include "google/protobuf/message.h" -#include "google/protobuf/io/coded_stream.h" +#include <string_view> #include "format/Archive.h" +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/message.h" #include "io/File.h" #include "io/Io.h" #include "process/IResourceTableConsumer.h" @@ -30,23 +29,23 @@ namespace aapt { namespace io { -bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path, +bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path, uint32_t compression_flags, IArchiveWriter* writer); -bool CopyFileToArchive(IAaptContext* context, IFile* file, const std::string& out_path, +bool CopyFileToArchive(IAaptContext* context, IFile* file, std::string_view out_path, uint32_t compression_flags, IArchiveWriter* writer); bool CopyFileToArchivePreserveCompression(IAaptContext* context, IFile* file, - const std::string& out_path, IArchiveWriter* writer); + std::string_view out_path, IArchiveWriter* writer); bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg, - const std::string& out_path, uint32_t compression_flags, + std::string_view out_path, uint32_t compression_flags, IArchiveWriter* writer); // Copies the data from in to out. Returns false if there was an error. // If there was an error, check the individual streams' HadError/GetError methods. bool Copy(OutputStream* out, InputStream* in); -bool Copy(OutputStream* out, const ::android::StringPiece& in); +bool Copy(OutputStream* out, android::StringPiece in); bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in); class OutputStreamAdaptor : public io::OutputStream { @@ -131,8 +130,7 @@ class ProtoInputStreamReader { template <typename T> bool ReadMessage(T *message) { ZeroCopyInputAdaptor adapter(in_); google::protobuf::io::CodedInputStream coded_stream(&adapter); - coded_stream.SetTotalBytesLimit(std::numeric_limits<int32_t>::max(), - coded_stream.BytesUntilTotalBytesLimit()); + coded_stream.SetTotalBytesLimit(std::numeric_limits<int32_t>::max()); return message->ParseFromCodedStream(&coded_stream); } diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp index 4380586b1d3c..4a5385d90d3b 100644 --- a/tools/aapt2/io/ZipArchive.cpp +++ b/tools/aapt2/io/ZipArchive.cpp @@ -16,22 +16,21 @@ #include "io/ZipArchive.h" -#include "utils/FileMap.h" -#include "ziparchive/zip_archive.h" - -#include "Source.h" +#include "androidfw/Source.h" #include "trace/TraceBuffer.h" #include "util/Files.h" #include "util/Util.h" +#include "utils/FileMap.h" +#include "ziparchive/zip_archive.h" using ::android::StringPiece; namespace aapt { namespace io { -ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, - const Source& source) - : zip_handle_(handle), zip_entry_(entry), source_(source) {} +ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const android::Source& source) + : zip_handle_(handle), zip_entry_(entry), source_(source) { +} std::unique_ptr<IData> ZipFile::OpenAsData() { // The file will fail to be mmaped if it is empty @@ -68,7 +67,7 @@ std::unique_ptr<io::InputStream> ZipFile::OpenInputStream() { return OpenAsData(); } -const Source& ZipFile::GetSource() const { +const android::Source& ZipFile::GetSource() const { return source_; } @@ -92,8 +91,8 @@ IFile* ZipFileCollectionIterator::Next() { ZipFileCollection::ZipFileCollection() : handle_(nullptr) {} -std::unique_ptr<ZipFileCollection> ZipFileCollection::Create( - const StringPiece& path, std::string* out_error) { +std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(StringPiece path, + std::string* out_error) { TRACE_CALL(); constexpr static const int32_t kEmptyArchive = -6; @@ -132,7 +131,7 @@ std::unique_ptr<ZipFileCollection> ZipFileCollection::Create( } std::unique_ptr<IFile> file = util::make_unique<ZipFile>(collection->handle_, zip_data, - Source(zip_entry_path, path.to_string())); + android::Source(zip_entry_path, path)); collection->files_by_name_[zip_entry_path] = file.get(); collection->files_.push_back(std::move(file)); } @@ -145,8 +144,8 @@ std::unique_ptr<ZipFileCollection> ZipFileCollection::Create( return collection; } -IFile* ZipFileCollection::FindFile(const StringPiece& path) { - auto iter = files_by_name_.find(path.to_string()); +IFile* ZipFileCollection::FindFile(StringPiece path) { + auto iter = files_by_name_.find(path); if (iter != files_by_name_.end()) { return iter->second; } diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h index b283e57d4011..c263aa490d22 100644 --- a/tools/aapt2/io/ZipArchive.h +++ b/tools/aapt2/io/ZipArchive.h @@ -32,17 +32,17 @@ namespace io { // and copied into memory when opened. Otherwise it is mmapped from the ZIP archive. class ZipFile : public IFile { public: - ZipFile(::ZipArchiveHandle handle, const ::ZipEntry& entry, const Source& source); + ZipFile(::ZipArchiveHandle handle, const ::ZipEntry& entry, const android::Source& source); std::unique_ptr<IData> OpenAsData() override; std::unique_ptr<io::InputStream> OpenInputStream() override; - const Source& GetSource() const override; + const android::Source& GetSource() const override; bool WasCompressed() override; private: ::ZipArchiveHandle zip_handle_; ::ZipEntry zip_entry_; - Source source_; + android::Source source_; }; class ZipFileCollection; @@ -61,10 +61,10 @@ class ZipFileCollectionIterator : public IFileCollectionIterator { // An IFileCollection that represents a ZIP archive and the entries within it. class ZipFileCollection : public IFileCollection { public: - static std::unique_ptr<ZipFileCollection> Create(const android::StringPiece& path, + static std::unique_ptr<ZipFileCollection> Create(android::StringPiece path, std::string* outError); - io::IFile* FindFile(const android::StringPiece& path) override; + io::IFile* FindFile(android::StringPiece path) override; std::unique_ptr<IFileCollectionIterator> Iterator() override; char GetDirSeparator() override; @@ -76,7 +76,7 @@ class ZipFileCollection : public IFileCollection { ZipArchiveHandle handle_; std::vector<std::unique_ptr<IFile>> files_; - std::map<std::string, IFile*> files_by_name_; + std::map<std::string, IFile*, std::less<>> files_by_name_; }; } // namespace io diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp index 482d91aeb491..87da09a7b054 100644 --- a/tools/aapt2/java/AnnotationProcessor.cpp +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -30,7 +30,7 @@ using ::android::StringPiece; namespace aapt { -StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment) { +StringPiece AnnotationProcessor::ExtractFirstSentence(StringPiece comment) { Utf8Iterator iter(comment); while (iter.HasNext()) { const char32_t codepoint = iter.Next(); @@ -62,7 +62,7 @@ static std::array<AnnotationRule, 2> sAnnotationRules = {{ }}; void AnnotationProcessor::AppendCommentLine(std::string comment) { - static const std::string sDeprecated = "@deprecated"; + static constexpr std::string_view sDeprecated = "@deprecated"; // Treat deprecated specially, since we don't remove it from the source comment. if (comment.find(sDeprecated) != std::string::npos) { @@ -74,7 +74,7 @@ void AnnotationProcessor::AppendCommentLine(std::string comment) { if (idx != std::string::npos) { // Captures all parameters associated with the specified annotation rule // by matching the first pair of parantheses after the rule. - std::regex re(rule.doc_str.to_string() + "\\s*\\((.+)\\)"); + std::regex re(std::string(rule.doc_str) += "\\s*\\((.+)\\)"); std::smatch match_result; const bool is_match = std::regex_search(comment, match_result, re); // We currently only capture and preserve parameters for SystemApi. @@ -97,7 +97,7 @@ void AnnotationProcessor::AppendCommentLine(std::string comment) { // If there was trimming to do, copy the string. if (trimmed.size() != comment.size()) { - comment = trimmed.to_string(); + comment = std::string(trimmed); } if (!has_comments_) { @@ -107,12 +107,12 @@ void AnnotationProcessor::AppendCommentLine(std::string comment) { comment_ << "\n * " << std::move(comment); } -void AnnotationProcessor::AppendComment(const StringPiece& comment) { +void AnnotationProcessor::AppendComment(StringPiece comment) { // We need to process line by line to clean-up whitespace and append prefixes. for (StringPiece line : util::Tokenize(comment, '\n')) { line = util::TrimWhitespace(line); if (!line.empty()) { - AppendCommentLine(line.to_string()); + AppendCommentLine(std::string(line)); } } } @@ -126,7 +126,7 @@ void AnnotationProcessor::AppendNewLine() { void AnnotationProcessor::Print(Printer* printer, bool strip_api_annotations) const { if (has_comments_) { std::string result = comment_.str(); - for (const StringPiece& line : util::Tokenize(result, '\n')) { + for (StringPiece line : util::Tokenize(result, '\n')) { printer->Println(line); } printer->Println(" */"); diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h index f217afb16f32..db3437e3b5b1 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -56,11 +56,11 @@ class AnnotationProcessor { // Extracts the first sentence of a comment. The algorithm selects the substring starting from // the beginning of the string, and ending at the first '.' character that is followed by a // whitespace character. If these requirements are not met, the whole string is returned. - static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment); + static android::StringPiece ExtractFirstSentence(android::StringPiece comment); // Adds more comments. Resources can have value definitions for various configurations, and // each of the definitions may have comments that need to be processed. - void AppendComment(const android::StringPiece& comment); + void AppendComment(android::StringPiece comment); void AppendNewLine(); diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp index 3163497f0da6..98f3bd2018b0 100644 --- a/tools/aapt2/java/ClassDefinition.cpp +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -27,8 +27,8 @@ void ClassMember::Print(bool /*final*/, Printer* printer, bool strip_api_annotat processor_.Print(printer, strip_api_annotations); } -void MethodDefinition::AppendStatement(const StringPiece& statement) { - statements_.push_back(statement.to_string()); +void MethodDefinition::AppendStatement(StringPiece statement) { + statements_.emplace_back(statement); } void MethodDefinition::Print(bool final, Printer* printer, bool) const { @@ -110,8 +110,8 @@ constexpr static const char* sWarningHeader = " * should not be modified by hand.\n" " */\n\n"; -void ClassDefinition::WriteJavaFile(const ClassDefinition* def, const StringPiece& package, - bool final, bool strip_api_annotations, io::OutputStream* out) { +void ClassDefinition::WriteJavaFile(const ClassDefinition* def, StringPiece package, bool final, + bool strip_api_annotations, io::OutputStream* out) { Printer printer(out); printer.Print(sWarningHeader).Print("package ").Print(package).Println(";"); printer.Println(); diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index 2acdadb3c034..63c99821a836 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -59,8 +59,8 @@ class ClassMember { template <typename T> class PrimitiveMember : public ClassMember { public: - PrimitiveMember(const android::StringPiece& name, const T& val, bool staged_api = false) - : name_(name.to_string()), val_(val), staged_api_(staged_api) { + PrimitiveMember(android::StringPiece name, const T& val, bool staged_api = false) + : name_(name), val_(val), staged_api_(staged_api) { } bool empty() const override { @@ -104,8 +104,8 @@ class PrimitiveMember : public ClassMember { template <> class PrimitiveMember<std::string> : public ClassMember { public: - PrimitiveMember(const android::StringPiece& name, const std::string& val, bool staged_api = false) - : name_(name.to_string()), val_(val) { + PrimitiveMember(android::StringPiece name, const std::string& val, bool staged_api = false) + : name_(name), val_(val) { } bool empty() const override { @@ -141,7 +141,8 @@ using StringMember = PrimitiveMember<std::string>; template <typename T, typename StringConverter> class PrimitiveArrayMember : public ClassMember { public: - explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {} + explicit PrimitiveArrayMember(android::StringPiece name) : name_(name) { + } void AddElement(const T& val) { elements_.emplace_back(val); @@ -209,12 +210,12 @@ using ResourceArrayMember = PrimitiveArrayMember<std::variant<ResourceId, FieldR class MethodDefinition : public ClassMember { public: // Expected method signature example: 'public static void onResourcesLoaded(int p)'. - explicit MethodDefinition(const android::StringPiece& signature) - : signature_(signature.to_string()) {} + explicit MethodDefinition(android::StringPiece signature) : signature_(signature) { + } // Appends a single statement to the method. It should include no newlines or else // formatting may be broken. - void AppendStatement(const android::StringPiece& statement); + void AppendStatement(android::StringPiece statement); // Not quite the same as a name, but good enough. const std::string& GetName() const override { @@ -239,11 +240,12 @@ enum class ClassQualifier { kNone, kStatic }; class ClassDefinition : public ClassMember { public: - static void WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package, - bool final, bool strip_api_annotations, io::OutputStream* out); + static void WriteJavaFile(const ClassDefinition* def, android::StringPiece package, bool final, + bool strip_api_annotations, io::OutputStream* out); - ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty) - : name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {} + ClassDefinition(android::StringPiece name, ClassQualifier qualifier, bool createIfEmpty) + : name_(name), qualifier_(qualifier), create_if_empty_(createIfEmpty) { + } enum class Result { kAdded, diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index a963d9893f2f..7665d0e8d9cb 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -57,14 +57,14 @@ static const std::set<StringPiece> sJavaIdentifiers = { "transient", "try", "void", "volatile", "while", "true", "false", "null"}; -static bool IsValidSymbol(const StringPiece& symbol) { +static bool IsValidSymbol(StringPiece symbol) { return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end(); } // Java symbols can not contain . or -, but those are valid in a resource name. // Replace those with '_'. -std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) { - std::string output = symbol.to_string(); +std::string JavaClassGenerator::TransformToFieldName(StringPiece symbol) { + std::string output(symbol); for (char& c : output) { if (c == '.' || c == '-') { c = '_'; @@ -84,7 +84,7 @@ std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) // Foo_bar static std::string TransformNestedAttr(const ResourceNameRef& attr_name, const std::string& styleable_class_name, - const StringPiece& package_name_to_generate) { + StringPiece package_name_to_generate) { std::string output = styleable_class_name; // We may reference IDs from other packages, so prefix the entry name with @@ -226,16 +226,15 @@ static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) { static FieldReference GetRFieldReference(const ResourceName& name, StringPiece fallback_package_name) { - const std::string package_name = - name.package.empty() ? fallback_package_name.to_string() : name.package; + const std::string_view package_name = name.package.empty() ? fallback_package_name : name.package; const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry); - return FieldReference(StringPrintf("%s.R.%s.%s", package_name.c_str(), - name.type.to_string().data(), entry.c_str())); + return FieldReference( + StringPrintf("%s.R.%s.%s", package_name.data(), name.type.to_string().data(), entry.c_str())); } bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, const Styleable& styleable, - const StringPiece& package_name_to_generate, + StringPiece package_name_to_generate, ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method, Printer* r_txt_printer) { @@ -314,7 +313,8 @@ bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res return true; } const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment(); - return attr_comment_line.contains("@removed") || attr_comment_line.contains("@hide"); + return attr_comment_line.find("@removed") != std::string::npos || + attr_comment_line.find("@hide") != std::string::npos; }); documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end()); @@ -397,7 +397,7 @@ bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res comment = styleable_attr.symbol.value().attribute->GetComment(); } - if (comment.contains("@removed")) { + if (comment.find("@removed") != std::string::npos) { // Removed attributes are public but hidden from the documentation, so // don't emit them as part of the class documentation. continue; @@ -497,7 +497,7 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso } if (out_rewrite_method != nullptr) { - const std::string type_str = name.type.to_string(); + const auto type_str = name.type.to_string(); out_rewrite_method->AppendStatement( StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | packageIdBits;", type_str.data(), field_name.data(), type_str.data(), field_name.data())); @@ -505,8 +505,7 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso } std::optional<std::string> JavaClassGenerator::UnmangleResource( - const StringPiece& package_name, const StringPiece& package_name_to_generate, - const ResourceEntry& entry) { + StringPiece package_name, StringPiece package_name_to_generate, const ResourceEntry& entry) { if (SkipSymbol(entry.visibility.level)) { return {}; } @@ -528,7 +527,7 @@ std::optional<std::string> JavaClassGenerator::UnmangleResource( return {std::move(unmangled_name)}; } -bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate, +bool JavaClassGenerator::ProcessType(StringPiece package_name_to_generate, const ResourceTablePackage& package, const ResourceTableType& type, ClassDefinition* out_type_class_def, @@ -548,10 +547,11 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate } // We need to make sure we hide the fact that we are generating kAttrPrivate attributes. - const ResourceNameRef resource_name( - package_name_to_generate, - type.type == ResourceType::kAttrPrivate ? ResourceType::kAttr : type.type, - unmangled_name.value()); + const auto target_type = type.named_type.type == ResourceType::kAttrPrivate + ? ResourceNamedTypeWithDefaultName(ResourceType::kAttr) + : type.named_type; + const ResourceNameRef resource_name(package_name_to_generate, target_type, + unmangled_name.value()); // Check to see if the unmangled name is a valid Java name (not a keyword). if (!IsValidSymbol(unmangled_name.value())) { @@ -576,7 +576,7 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate return true; } -bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out, +bool JavaClassGenerator::Generate(StringPiece package_name_to_generate, OutputStream* out, OutputStream* out_r_txt) { return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt); } @@ -590,8 +590,8 @@ static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations } } -bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, - const StringPiece& out_package_name, OutputStream* out, +bool JavaClassGenerator::Generate(StringPiece package_name_to_generate, + StringPiece out_package_name, OutputStream* out, OutputStream* out_r_txt) { ClassDefinition r_class("R", ClassQualifier::kNone, true); std::unique_ptr<MethodDefinition> rewrite_method; @@ -616,7 +616,8 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, for (const auto& package : table_->packages) { for (const auto& type : package->types) { - if (type->type == ResourceType::kAttrPrivate || type->type == ResourceType::kMacro) { + if (type->named_type.type == ResourceType::kAttrPrivate || + type->named_type.type == ResourceType::kMacro) { // We generate kAttrPrivate as part of the kAttr type, so skip them here. // Macros are not actual resources, so skip them as well. continue; @@ -628,7 +629,7 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, std::unique_ptr<ClassDefinition> class_def; if (out != nullptr) { class_def = util::make_unique<ClassDefinition>( - to_string(type->type), ClassQualifier::kStatic, force_creation_if_empty); + to_string(type->named_type.type), ClassQualifier::kStatic, force_creation_if_empty); } if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(), @@ -636,9 +637,10 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, return false; } - if (type->type == ResourceType::kAttr) { + if (type->named_type.type == ResourceType::kAttr) { // Also include private attributes in this same class. - if (const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate)) { + if (const ResourceTableType* priv_type = + package->FindTypeWithDefaultName(ResourceType::kAttrPrivate)) { if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(), rewrite_method.get(), r_txt_printer.get())) { return false; @@ -646,7 +648,7 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, } } - if (out != nullptr && type->type == ResourceType::kStyleable && is_public) { + if (out != nullptr && type->named_type.type == ResourceType::kStyleable && is_public) { // When generating a public R class, we don't want Styleable to be part // of the API. It is only emitted for documentation purposes. class_def->GetCommentBuilder()->AppendComment("@doconly"); diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h index b45a2f12db35..234df04472ce 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -70,16 +70,16 @@ class JavaClassGenerator { // All symbols technically belong to a single package, but linked libraries will // have their names mangled, denoting that they came from a different package. // We need to generate these symbols in a separate file. Returns true on success. - bool Generate(const android::StringPiece& package_name_to_generate, io::OutputStream* out, + bool Generate(android::StringPiece package_name_to_generate, io::OutputStream* out, io::OutputStream* out_r_txt = nullptr); - bool Generate(const android::StringPiece& package_name_to_generate, - const android::StringPiece& output_package_name, io::OutputStream* out, + bool Generate(android::StringPiece package_name_to_generate, + android::StringPiece output_package_name, io::OutputStream* out, io::OutputStream* out_r_txt = nullptr); const std::string& GetError() const; - static std::string TransformToFieldName(const android::StringPiece& symbol); + static std::string TransformToFieldName(android::StringPiece symbol); private: bool SkipSymbol(Visibility::Level state); @@ -87,11 +87,11 @@ class JavaClassGenerator { // Returns the unmangled resource entry name if the unmangled package is the same as // package_name_to_generate. Returns nothing if the resource should be skipped. - std::optional<std::string> UnmangleResource(const android::StringPiece& package_name, - const android::StringPiece& package_name_to_generate, + std::optional<std::string> UnmangleResource(android::StringPiece package_name, + android::StringPiece package_name_to_generate, const ResourceEntry& entry); - bool ProcessType(const android::StringPiece& package_name_to_generate, + bool ProcessType(android::StringPiece package_name_to_generate, const ResourceTablePackage& package, const ResourceTableType& type, ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def, text::Printer* r_txt_printer); @@ -106,8 +106,7 @@ class JavaClassGenerator { // its package ID if `out_rewrite_method` is not nullptr. // `package_name_to_generate` is the package bool ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, - const Styleable& styleable, - const android::StringPiece& package_name_to_generate, + const Styleable& styleable, android::StringPiece package_name_to_generate, ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method, text::Printer* r_txt_printer); diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index a0db41baecb4..65b63b7c0c61 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -18,7 +18,7 @@ #include <algorithm> -#include "Source.h" +#include "androidfw/Source.h" #include "java/ClassDefinition.h" #include "java/JavaClassGenerator.h" #include "text/Unicode.h" @@ -28,7 +28,8 @@ using ::aapt::text::IsJavaIdentifier; namespace aapt { -static std::optional<std::string> ExtractJavaIdentifier(IDiagnostics* diag, const Source& source, +static std::optional<std::string> ExtractJavaIdentifier(android::IDiagnostics* diag, + const android::Source& source, const std::string& value) { std::string result = value; size_t pos = value.rfind('.'); @@ -42,22 +43,22 @@ static std::optional<std::string> ExtractJavaIdentifier(IDiagnostics* diag, cons } if (result.empty()) { - diag->Error(DiagMessage(source) << "empty symbol"); + diag->Error(android::DiagMessage(source) << "empty symbol"); return {}; } if (!IsJavaIdentifier(result)) { - diag->Error(DiagMessage(source) << "invalid Java identifier '" << result << "'"); + diag->Error(android::DiagMessage(source) << "invalid Java identifier '" << result << "'"); return {}; } return result; } -static bool WriteSymbol(const Source& source, IDiagnostics* diag, xml::Element* el, - ClassDefinition* class_def) { +static bool WriteSymbol(const android::Source& source, android::IDiagnostics* diag, + xml::Element* el, ClassDefinition* class_def) { xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); if (!attr) { - diag->Error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'"); + diag->Error(android::DiagMessage(source) << "<" << el->name << "> must define 'android:name'"); return false; } @@ -72,21 +73,22 @@ static bool WriteSymbol(const Source& source, IDiagnostics* diag, xml::Element* string_member->GetCommentBuilder()->AppendComment(el->comment); if (class_def->AddMember(std::move(string_member)) == ClassDefinition::Result::kOverridden) { - diag->Warn(DiagMessage(source.WithLine(el->line_number)) + diag->Warn(android::DiagMessage(source.WithLine(el->line_number)) << "duplicate definitions of '" << result.value() << "', overriding previous"); } return true; } -std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, xml::XmlResource* res) { +std::unique_ptr<ClassDefinition> GenerateManifestClass(android::IDiagnostics* diag, + xml::XmlResource* res) { xml::Element* el = xml::FindRootElement(res->root.get()); if (!el) { - diag->Error(DiagMessage(res->file.source) << "no root tag defined"); + diag->Error(android::DiagMessage(res->file.source) << "no root tag defined"); return {}; } if (el->name != "manifest" && !el->namespace_uri.empty()) { - diag->Error(DiagMessage(res->file.source) << "no <manifest> root tag defined"); + diag->Error(android::DiagMessage(res->file.source) << "no <manifest> root tag defined"); return {}; } diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h index 3f6645facaa2..3a45ef6d5ecc 100644 --- a/tools/aapt2/java/ManifestClassGenerator.h +++ b/tools/aapt2/java/ManifestClassGenerator.h @@ -17,13 +17,14 @@ #ifndef AAPT_JAVA_MANIFESTCLASSGENERATOR_H #define AAPT_JAVA_MANIFESTCLASSGENERATOR_H -#include "Diagnostics.h" +#include "androidfw/IDiagnostics.h" #include "java/ClassDefinition.h" #include "xml/XmlDom.h" namespace aapt { -std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, xml::XmlResource* res); +std::unique_ptr<ClassDefinition> GenerateManifestClass(android::IDiagnostics* diag, + xml::XmlResource* res); } // namespace aapt diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index e53e22070b62..80a46d553960 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -517,7 +517,7 @@ bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table for (auto& type : pkg->types) { for (auto& entry : type->entries) { for (auto& config_value : entry->values) { - ResourceName from(pkg->name, type->type, entry->name); + ResourceName from(pkg->name, type->named_type, entry->name); ReferenceVisitor visitor(context, from, keep_set); config_value->value->Accept(&visitor); } diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h index a01b64d024d2..267f7ede274a 100644 --- a/tools/aapt2/java/ProguardRules.h +++ b/tools/aapt2/java/ProguardRules.h @@ -22,12 +22,11 @@ #include <set> #include <string> -#include "androidfw/StringPiece.h" - #include "Resource.h" #include "ResourceTable.h" -#include "Source.h" #include "ValueVisitor.h" +#include "androidfw/Source.h" +#include "androidfw/StringPiece.h" #include "io/Io.h" #include "process/IResourceTableConsumer.h" #include "xml/XmlDom.h" @@ -37,7 +36,7 @@ namespace proguard { struct UsageLocation { ResourceName name; - Source source; + android::Source source; }; struct NameAndSignature { diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp index 328ac97090a8..3dbd7e613a3e 100644 --- a/tools/aapt2/link/AutoVersioner.cpp +++ b/tools/aapt2/link/AutoVersioner.cpp @@ -75,7 +75,7 @@ bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) { CloningValueTransformer cloner(&table->string_pool); for (auto& package : table->packages) { for (auto& type : package->types) { - if (type->type != ResourceType::kStyle) { + if (type->named_type.type != ResourceType::kStyle) { continue; } diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h index be6c930b9284..44cd276f77a2 100644 --- a/tools/aapt2/link/Linkers.h +++ b/tools/aapt2/link/Linkers.h @@ -101,9 +101,10 @@ class ProductFilter : public IResourceTableConsumer { explicit ProductFilter(std::unordered_set<std::string> products) : products_(products) { } - ResourceConfigValueIter SelectProductToKeep( - const ResourceNameRef& name, const ResourceConfigValueIter begin, - const ResourceConfigValueIter end, IDiagnostics* diag); + ResourceConfigValueIter SelectProductToKeep(const ResourceNameRef& name, + const ResourceConfigValueIter begin, + const ResourceConfigValueIter end, + android::IDiagnostics* diag); bool Consume(IAaptContext* context, ResourceTable* table) override; diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 948b11b6795e..c4f6e70c0cc9 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -30,16 +30,104 @@ using android::StringPiece; namespace aapt { -static bool RequiredNameIsNotEmpty(xml::Element* el, SourcePathDiagnostics* diag) { +// This is to detect whether an <intent-filter> contains deeplink. +// See https://developer.android.com/training/app-links/deep-linking. +static bool HasDeepLink(xml::Element* intent_filter_el) { + xml::Element* action_el = intent_filter_el->FindChild({}, "action"); + xml::Element* category_el = intent_filter_el->FindChild({}, "category"); + xml::Element* data_el = intent_filter_el->FindChild({}, "data"); + if (action_el == nullptr || category_el == nullptr || data_el == nullptr) { + return false; + } + + // Deeplinks must specify the ACTION_VIEW intent action. + constexpr const char* action_view = "android.intent.action.VIEW"; + if (intent_filter_el->FindChildWithAttribute({}, "action", xml::kSchemaAndroid, "name", + action_view) == nullptr) { + return false; + } + + // Deeplinks must have scheme included in <data> tag. + xml::Attribute* data_scheme_attr = data_el->FindAttribute(xml::kSchemaAndroid, "scheme"); + if (data_scheme_attr == nullptr || data_scheme_attr->value.empty()) { + return false; + } + + // Deeplinks must include BROWSABLE category. + constexpr const char* category_browsable = "android.intent.category.BROWSABLE"; + if (intent_filter_el->FindChildWithAttribute({}, "category", xml::kSchemaAndroid, "name", + category_browsable) == nullptr) { + return false; + } + return true; +} + +static bool VerifyDeeplinkPathAttribute(xml::Element* data_el, android::SourcePathDiagnostics* diag, + const std::string& attr_name) { + xml::Attribute* attr = data_el->FindAttribute(xml::kSchemaAndroid, attr_name); + if (attr != nullptr && !attr->value.empty()) { + StringPiece attr_value = attr->value; + const char* startChar = attr_value.begin(); + if (attr_name == "pathPattern") { + // pathPattern starts with '.' or '*' does not need leading slash. + // Reference starts with @ does not need leading slash. + if (*startChar == '/' || *startChar == '.' || *startChar == '*' || *startChar == '@') { + return true; + } else { + diag->Error(android::DiagMessage(data_el->line_number) + << "attribute 'android:" << attr_name << "' in <" << data_el->name + << "> tag has value of '" << attr_value + << "', it must be in a pattern start with '.' or '*', otherwise must start " + "with a leading slash '/'"); + return false; + } + } else { + // Reference starts with @ does not need leading slash. + if (*startChar == '/' || *startChar == '@') { + return true; + } else { + diag->Error(android::DiagMessage(data_el->line_number) + << "attribute 'android:" << attr_name << "' in <" << data_el->name + << "> tag has value of '" << attr_value + << "', it must start with a leading slash '/'"); + return false; + } + } + } + return true; +} + +static bool VerifyDeepLinkIntentAction(xml::Element* intent_filter_el, + android::SourcePathDiagnostics* diag) { + if (!HasDeepLink(intent_filter_el)) { + return true; + } + + xml::Element* data_el = intent_filter_el->FindChild({}, "data"); + if (data_el != nullptr) { + if (!VerifyDeeplinkPathAttribute(data_el, diag, "path")) { + return false; + } + if (!VerifyDeeplinkPathAttribute(data_el, diag, "pathPrefix")) { + return false; + } + if (!VerifyDeeplinkPathAttribute(data_el, diag, "pathPattern")) { + return false; + } + } + return true; +} + +static bool RequiredNameIsNotEmpty(xml::Element* el, android::SourcePathDiagnostics* diag) { xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); if (attr == nullptr) { - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "<" << el->name << "> is missing attribute 'android:name'"); return false; } if (attr->value.empty()) { - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "attribute 'android:name' in <" << el->name << "> tag must not be empty"); return false; } @@ -48,7 +136,7 @@ static bool RequiredNameIsNotEmpty(xml::Element* el, SourcePathDiagnostics* diag // This is how PackageManager builds class names from AndroidManifest.xml entries. static bool NameIsJavaClassName(xml::Element* el, xml::Attribute* attr, - SourcePathDiagnostics* diag) { + android::SourcePathDiagnostics* diag) { // We allow unqualified class names (ie: .HelloActivity) // Since we don't know the package name, we can just make a fake one here and // the test will be identical as long as the real package name is valid too. @@ -60,51 +148,50 @@ static bool NameIsJavaClassName(xml::Element* el, xml::Attribute* attr, : attr->value; if (!util::IsJavaClassName(qualified_class_name)) { - diag->Error(DiagMessage(el->line_number) - << "attribute 'android:name' in <" << el->name - << "> tag must be a valid Java class name"); + diag->Error(android::DiagMessage(el->line_number) << "attribute 'android:name' in <" << el->name + << "> tag must be a valid Java class name"); return false; } return true; } -static bool OptionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { +static bool OptionalNameIsJavaClassName(xml::Element* el, android::SourcePathDiagnostics* diag) { if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) { return NameIsJavaClassName(el, attr, diag); } return true; } -static bool RequiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { +static bool RequiredNameIsJavaClassName(xml::Element* el, android::SourcePathDiagnostics* diag) { xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); if (attr == nullptr) { - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "<" << el->name << "> is missing attribute 'android:name'"); return false; } return NameIsJavaClassName(el, attr, diag); } -static bool RequiredNameIsJavaPackage(xml::Element* el, SourcePathDiagnostics* diag) { +static bool RequiredNameIsJavaPackage(xml::Element* el, android::SourcePathDiagnostics* diag) { xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); if (attr == nullptr) { - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "<" << el->name << "> is missing attribute 'android:name'"); return false; } if (!util::IsJavaPackageName(attr->value)) { - diag->Error(DiagMessage(el->line_number) << "attribute 'android:name' in <" << el->name - << "> tag must be a valid Java package name"); + diag->Error(android::DiagMessage(el->line_number) << "attribute 'android:name' in <" << el->name + << "> tag must be a valid Java package name"); return false; } return true; } static xml::XmlNodeAction::ActionFuncWithDiag RequiredAndroidAttribute(const std::string& attr) { - return [=](xml::Element* el, SourcePathDiagnostics* diag) -> bool { + return [=](xml::Element* el, android::SourcePathDiagnostics* diag) -> bool { if (el->FindAttribute(xml::kSchemaAndroid, attr) == nullptr) { - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "<" << el->name << "> is missing required attribute 'android:" << attr << "'"); return false; } @@ -114,17 +201,17 @@ static xml::XmlNodeAction::ActionFuncWithDiag RequiredAndroidAttribute(const std static xml::XmlNodeAction::ActionFuncWithDiag RequiredOneAndroidAttribute( const std::string& attrName1, const std::string& attrName2) { - return [=](xml::Element* el, SourcePathDiagnostics* diag) -> bool { + return [=](xml::Element* el, android::SourcePathDiagnostics* diag) -> bool { xml::Attribute* attr1 = el->FindAttribute(xml::kSchemaAndroid, attrName1); xml::Attribute* attr2 = el->FindAttribute(xml::kSchemaAndroid, attrName2); if (attr1 == nullptr && attr2 == nullptr) { - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "<" << el->name << "> is missing required attribute 'android:" << attrName1 << "' or 'android:" << attrName2 << "'"); return false; } if (attr1 != nullptr && attr2 != nullptr) { - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "<" << el->name << "> can only specify one of attribute 'android:" << attrName1 << "' or 'android:" << attrName2 << "'"); return false; @@ -133,7 +220,7 @@ static xml::XmlNodeAction::ActionFuncWithDiag RequiredOneAndroidAttribute( }; } -static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics* diag) { +static bool AutoGenerateIsFeatureSplit(xml::Element* el, android::SourcePathDiagnostics* diag) { constexpr const char* kFeatureSplit = "featureSplit"; constexpr const char* kIsFeatureSplit = "isFeatureSplit"; @@ -149,7 +236,7 @@ static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics* if (!ResourceUtils::ParseBool(attr->value).value_or(false)) { // The isFeatureSplit attribute is false, which conflicts with the use // of "featureSplit". - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "attribute 'featureSplit' used in <manifest> but 'android:isFeatureSplit' " "is not 'true'"); return false; @@ -163,7 +250,7 @@ static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics* return true; } -static bool AutoGenerateIsSplitRequired(xml::Element* el, SourcePathDiagnostics* diag) { +static bool AutoGenerateIsSplitRequired(xml::Element* el, android::SourcePathDiagnostics* diag) { constexpr const char* kRequiredSplitTypes = "requiredSplitTypes"; constexpr const char* kIsSplitRequired = "isSplitRequired"; @@ -175,7 +262,7 @@ static bool AutoGenerateIsSplitRequired(xml::Element* el, SourcePathDiagnostics* if (!ResourceUtils::ParseBool(attr->value).value_or(false)) { // The isFeatureSplit attribute is false, which conflicts with the use // of "featureSplit". - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "attribute 'requiredSplitTypes' used in <manifest> but " "'android:isSplitRequired' is not 'true'"); return false; @@ -189,18 +276,18 @@ static bool AutoGenerateIsSplitRequired(xml::Element* el, SourcePathDiagnostics* } static bool VerifyManifest(xml::Element* el, xml::XmlActionExecutorPolicy policy, - SourcePathDiagnostics* diag) { + android::SourcePathDiagnostics* diag) { xml::Attribute* attr = el->FindAttribute({}, "package"); if (!attr) { - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "<manifest> tag is missing 'package' attribute"); return false; } else if (ResourceUtils::IsReference(attr->value)) { - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "attribute 'package' in <manifest> tag must not be a reference"); return false; } else if (!util::IsAndroidPackageName(attr->value)) { - DiagMessage error_msg(el->line_number); + android::DiagMessage error_msg(el->line_number); error_msg << "attribute 'package' in <manifest> tag is not a valid Android package name: '" << attr->value << "'"; if (policy == xml::XmlActionExecutorPolicy::kAllowListWarning) { @@ -215,8 +302,9 @@ static bool VerifyManifest(xml::Element* el, xml::XmlActionExecutorPolicy policy attr = el->FindAttribute({}, "split"); if (attr) { if (!util::IsJavaPackageName(attr->value)) { - diag->Error(DiagMessage(el->line_number) << "attribute 'split' in <manifest> tag is not a " - "valid split name"); + diag->Error(android::DiagMessage(el->line_number) + << "attribute 'split' in <manifest> tag is not a " + "valid split name"); return false; } } @@ -225,11 +313,11 @@ static bool VerifyManifest(xml::Element* el, xml::XmlActionExecutorPolicy policy // The coreApp attribute in <manifest> is not a regular AAPT attribute, so type // checking on it is manual. -static bool FixCoreAppAttribute(xml::Element* el, SourcePathDiagnostics* diag) { +static bool FixCoreAppAttribute(xml::Element* el, android::SourcePathDiagnostics* diag) { if (xml::Attribute* attr = el->FindAttribute("", "coreApp")) { std::unique_ptr<BinaryPrimitive> result = ResourceUtils::TryParseBool(attr->value); if (!result) { - diag->Error(DiagMessage(el->line_number) << "attribute coreApp must be a boolean"); + diag->Error(android::DiagMessage(el->line_number) << "attribute coreApp must be a boolean"); return false; } attr->compiled_value = std::move(result); @@ -238,11 +326,11 @@ static bool FixCoreAppAttribute(xml::Element* el, SourcePathDiagnostics* diag) { } // Checks that <uses-feature> has android:glEsVersion or android:name, not both (or neither). -static bool VerifyUsesFeature(xml::Element* el, SourcePathDiagnostics* diag) { +static bool VerifyUsesFeature(xml::Element* el, android::SourcePathDiagnostics* diag) { bool has_name = false; if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) { if (attr->value.empty()) { - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "android:name in <uses-feature> must not be empty"); return false; } @@ -252,7 +340,7 @@ static bool VerifyUsesFeature(xml::Element* el, SourcePathDiagnostics* diag) { bool has_gl_es_version = false; if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "glEsVersion")) { if (has_name) { - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "cannot define both android:name and android:glEsVersion in <uses-feature>"); return false; } @@ -260,7 +348,7 @@ static bool VerifyUsesFeature(xml::Element* el, SourcePathDiagnostics* diag) { } if (!has_name && !has_gl_es_version) { - diag->Error(DiagMessage(el->line_number) + diag->Error(android::DiagMessage(el->line_number) << "<uses-feature> must have either android:name or android:glEsVersion attribute"); return false; } @@ -294,40 +382,36 @@ static void EnsureNamespaceIsDeclared(const std::string& prefix, const std::stri } } -bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, - IDiagnostics* diag) { +bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagnostics* diag) { // First verify some options. if (options_.rename_manifest_package) { if (!util::IsJavaPackageName(options_.rename_manifest_package.value())) { - diag->Error(DiagMessage() << "invalid manifest package override '" - << options_.rename_manifest_package.value() - << "'"); + diag->Error(android::DiagMessage() << "invalid manifest package override '" + << options_.rename_manifest_package.value() << "'"); return false; } } if (options_.rename_instrumentation_target_package) { if (!util::IsJavaPackageName(options_.rename_instrumentation_target_package.value())) { - diag->Error(DiagMessage() + diag->Error(android::DiagMessage() << "invalid instrumentation target package override '" - << options_.rename_instrumentation_target_package.value() - << "'"); + << options_.rename_instrumentation_target_package.value() << "'"); return false; } } if (options_.rename_overlay_target_package) { if (!util::IsJavaPackageName(options_.rename_overlay_target_package.value())) { - diag->Error(DiagMessage() - << "invalid overlay target package override '" - << options_.rename_overlay_target_package.value() - << "'"); + diag->Error(android::DiagMessage() << "invalid overlay target package override '" + << options_.rename_overlay_target_package.value() << "'"); return false; } } // Common <intent-filter> actions. xml::XmlNodeAction intent_filter_action; + intent_filter_action.Action(VerifyDeepLinkIntentAction); intent_filter_action["action"].Action(RequiredNameIsNotEmpty); intent_filter_action["category"].Action(RequiredNameIsNotEmpty); intent_filter_action["data"]; @@ -449,13 +533,18 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["attribution"]["inherit-from"]; manifest_action["original-package"]; manifest_action["overlay"].Action([&](xml::Element* el) -> bool { - if (!options_.rename_overlay_target_package) { - return true; + if (options_.rename_overlay_target_package) { + if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "targetPackage")) { + attr->value = options_.rename_overlay_target_package.value(); + } } - - if (xml::Attribute* attr = - el->FindAttribute(xml::kSchemaAndroid, "targetPackage")) { - attr->value = options_.rename_overlay_target_package.value(); + if (options_.rename_overlay_category) { + if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "category")) { + attr->value = options_.rename_overlay_category.value(); + } else { + el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, "category", + options_.rename_overlay_category.value()}); + } } return true; }); @@ -557,8 +646,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, return true; } -static void FullyQualifyClassName(const StringPiece& package, const StringPiece& attr_ns, - const StringPiece& attr_name, xml::Element* el) { +static void FullyQualifyClassName(StringPiece package, StringPiece attr_ns, StringPiece attr_name, + xml::Element* el) { xml::Attribute* attr = el->FindAttribute(attr_ns, attr_name); if (attr != nullptr) { if (std::optional<std::string> new_value = @@ -568,7 +657,7 @@ static void FullyQualifyClassName(const StringPiece& package, const StringPiece& } } -static bool RenameManifestPackage(const StringPiece& package_override, xml::Element* manifest_el) { +static bool RenameManifestPackage(StringPiece package_override, xml::Element* manifest_el) { xml::Attribute* attr = manifest_el->FindAttribute({}, "package"); // We've already verified that the manifest element is present, with a package @@ -576,7 +665,7 @@ static bool RenameManifestPackage(const StringPiece& package_override, xml::Elem CHECK(attr != nullptr); std::string original_package = std::move(attr->value); - attr->value = package_override.to_string(); + attr->value.assign(package_override); xml::Element* application_el = manifest_el->FindChild({}, "application"); if (application_el != nullptr) { @@ -615,7 +704,7 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) { TRACE_CALL(); xml::Element* root = xml::FindRootElement(doc->root.get()); if (!root || !root->namespace_uri.empty() || root->name != "manifest") { - context->GetDiagnostics()->Error(DiagMessage(doc->file.source) + context->GetDiagnostics()->Error(android::DiagMessage(doc->file.source) << "root tag must be <manifest>"); return false; } @@ -630,7 +719,7 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) { root->InsertChild(0, std::move(uses_sdk)); } - if (options_.compile_sdk_version) { + if (!options_.no_compile_sdk_metadata && options_.compile_sdk_version) { xml::Attribute* attr = root->FindOrCreateAttribute(xml::kSchemaAndroid, "compileSdkVersion"); // Make sure we un-compile the value if it was set to something else. @@ -642,10 +731,9 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) { // Make sure we un-compile the value if it was set to something else. attr->compiled_value = {}; attr->value = options_.compile_sdk_version.value(); - } - if (options_.compile_sdk_version_codename) { + if (!options_.no_compile_sdk_metadata && options_.compile_sdk_version_codename) { xml::Attribute* attr = root->FindOrCreateAttribute(xml::kSchemaAndroid, "compileSdkVersionCodename"); @@ -660,6 +748,23 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) { attr->value = options_.compile_sdk_version_codename.value(); } + if (!options_.fingerprint_prefixes.empty()) { + xml::Element* install_constraints_el = root->FindChild({}, "install-constraints"); + if (install_constraints_el == nullptr) { + std::unique_ptr<xml::Element> install_constraints = std::make_unique<xml::Element>(); + install_constraints->name = "install-constraints"; + install_constraints_el = install_constraints.get(); + root->AppendChild(std::move(install_constraints)); + } + for (const std::string& prefix : options_.fingerprint_prefixes) { + std::unique_ptr<xml::Element> prefix_el = std::make_unique<xml::Element>(); + prefix_el->name = "fingerprint-prefix"; + xml::Attribute* attr = prefix_el->FindOrCreateAttribute(xml::kSchemaAndroid, "value"); + attr->value = prefix; + install_constraints_el->AppendChild(std::move(prefix_el)); + } + } + xml::XmlActionExecutor executor; if (!BuildRules(&executor, context->GetDiagnostics())) { return false; diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h index d5d1d1770e1c..42938a4f8176 100644 --- a/tools/aapt2/link/ManifestFixer.h +++ b/tools/aapt2/link/ManifestFixer.h @@ -18,11 +18,10 @@ #define AAPT_LINK_MANIFESTFIXER_H #include <string> +#include <vector> #include "android-base/macros.h" - #include "process/IResourceTableConsumer.h" - #include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" @@ -48,6 +47,9 @@ struct ManifestFixerOptions { // <overlay>. std::optional<std::string> rename_overlay_target_package; + // The category to use instead of the one defined in 'android:category' in <overlay>. + std::optional<std::string> rename_overlay_category; + // The version name to set if 'android:versionName' is not defined in <manifest> or if // replace_version is set. std::optional<std::string> version_name_default; @@ -65,13 +67,17 @@ struct ManifestFixerOptions { std::optional<std::string> revision_code_default; // The version of the framework being compiled against to set for 'android:compileSdkVersion' in - // the <manifest> tag. + // the <manifest> tag. Not used if no_compile_sdk_metadata is set. std::optional<std::string> compile_sdk_version; // The version codename of the framework being compiled against to set for - // 'android:compileSdkVersionCodename' in the <manifest> tag. + // 'android:compileSdkVersionCodename' in the <manifest> tag. Not used if no_compile_sdk_metadata + // is set. std::optional<std::string> compile_sdk_version_codename; + // The fingerprint prefixes to be added to the <install-constraints> tag. + std::vector<std::string> fingerprint_prefixes; + // Whether validation errors should be treated only as warnings. If this is 'true', then an // incorrect node will not result in an error, but only as a warning, and the parsing will // continue. @@ -82,6 +88,9 @@ struct ManifestFixerOptions { // Whether to replace the manifest version with the the command line version bool replace_version = false; + + // Whether to suppress `android:compileSdkVersion*` and `platformBuildVersion*` attributes. + bool no_compile_sdk_metadata = false; }; // Verifies that the manifest is correctly formed and inserts defaults where specified with @@ -96,7 +105,7 @@ class ManifestFixer : public IXmlResourceConsumer { private: DISALLOW_COPY_AND_ASSIGN(ManifestFixer); - bool BuildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag); + bool BuildRules(xml::XmlActionExecutor* executor, android::IDiagnostics* diag); ManifestFixerOptions options_; }; diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 432f10bdab97..6151a8e910d9 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -61,12 +61,12 @@ struct ManifestFixerTest : public ::testing::Test { .Build(); } - std::unique_ptr<xml::XmlResource> Verify(const StringPiece& str) { + std::unique_ptr<xml::XmlResource> Verify(StringPiece str) { return VerifyWithOptions(str, {}); } - std::unique_ptr<xml::XmlResource> VerifyWithOptions( - const StringPiece& str, const ManifestFixerOptions& options) { + std::unique_ptr<xml::XmlResource> VerifyWithOptions(StringPiece str, + const ManifestFixerOptions& options) { std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(str); ManifestFixer fixer(options); if (fixer.Consume(mContext.get(), doc.get())) { @@ -351,6 +351,54 @@ TEST_F(ManifestFixerTest, EXPECT_THAT(attr->value, StrEq("com.android")); } +TEST_F(ManifestFixerTest, AddOverlayCategory) { + ManifestFixerOptions options; + options.rename_overlay_category = std::string("category"); + + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <overlay android:targetName="Customization" android:targetPackage="android" /> + </manifest>)EOF", + options); + ASSERT_THAT(doc, NotNull()); + + xml::Element* manifest_el = doc->root.get(); + ASSERT_THAT(manifest_el, NotNull()); + + xml::Element* overlay_el = manifest_el->FindChild({}, "overlay"); + ASSERT_THAT(overlay_el, NotNull()); + + xml::Attribute* attr = overlay_el->FindAttribute(xml::kSchemaAndroid, "category"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("category")); +} + +TEST_F(ManifestFixerTest, OverrideOverlayCategory) { + ManifestFixerOptions options; + options.rename_overlay_category = std::string("category"); + + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <overlay android:targetName="Customization" + android:targetPackage="android" + android:category="yrogetac"/> + </manifest>)EOF", + options); + ASSERT_THAT(doc, NotNull()); + + xml::Element* manifest_el = doc->root.get(); + ASSERT_THAT(manifest_el, NotNull()); + + xml::Element* overlay_el = manifest_el->FindChild({}, "overlay"); + ASSERT_THAT(overlay_el, NotNull()); + + xml::Attribute* attr = overlay_el->FindAttribute(xml::kSchemaAndroid, "category"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("category")); +} + TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) { ManifestFixerOptions options; options.version_name_default = std::string("Beta"); @@ -844,6 +892,35 @@ TEST_F(ManifestFixerTest, InsertCompileSdkVersions) { EXPECT_THAT(attr->value, StrEq("P")); } +TEST_F(ManifestFixerTest, DoNotInsertCompileSdkVersions) { + std::string input = R"(<manifest package="com.pkg" />)"; + ManifestFixerOptions options; + options.no_compile_sdk_metadata = true; + options.compile_sdk_version = {"28"}; + options.compile_sdk_version_codename = {"P"}; + + std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options); + ASSERT_THAT(manifest, NotNull()); + + // There should be a declaration of kSchemaAndroid, even when the input + // didn't have one. + EXPECT_EQ(manifest->root->namespace_decls.size(), 1); + EXPECT_EQ(manifest->root->namespace_decls[0].prefix, "android"); + EXPECT_EQ(manifest->root->namespace_decls[0].uri, xml::kSchemaAndroid); + + xml::Attribute* attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersion"); + ASSERT_THAT(attr, IsNull()); + + attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersionCodename"); + ASSERT_THAT(attr, IsNull()); + + attr = manifest->root->FindAttribute("", "platformBuildVersionCode"); + ASSERT_THAT(attr, IsNull()); + + attr = manifest->root->FindAttribute("", "platformBuildVersionName"); + ASSERT_THAT(attr, IsNull()); +} + TEST_F(ManifestFixerTest, OverrideCompileSdkVersions) { std::string input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" @@ -917,6 +994,63 @@ TEST_F(ManifestFixerTest, UnexpectedElementsInManifest) { ASSERT_THAT(manifest, IsNull()); } +TEST_F(ManifestFixerTest, InsertFingerprintPrefixIfNotExist) { + std::string input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + </manifest>)"; + ManifestFixerOptions options; + options.fingerprint_prefixes = {"foo", "bar"}; + + std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options); + ASSERT_THAT(manifest, NotNull()); + xml::Element* install_constraints = manifest->root.get()->FindChild({}, "install-constraints"); + ASSERT_THAT(install_constraints, NotNull()); + std::vector<xml::Element*> fingerprint_prefixes = install_constraints->GetChildElements(); + EXPECT_EQ(fingerprint_prefixes.size(), 2); + xml::Attribute* attr; + EXPECT_THAT(fingerprint_prefixes[0]->name, StrEq("fingerprint-prefix")); + attr = fingerprint_prefixes[0]->FindAttribute(xml::kSchemaAndroid, "value"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("foo")); + EXPECT_THAT(fingerprint_prefixes[1]->name, StrEq("fingerprint-prefix")); + attr = fingerprint_prefixes[1]->FindAttribute(xml::kSchemaAndroid, "value"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("bar")); +} + +TEST_F(ManifestFixerTest, AppendFingerprintPrefixIfExists) { + std::string input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <install-constraints> + <fingerprint-prefix android:value="foo" /> + </install-constraints> + </manifest>)"; + ManifestFixerOptions options; + options.fingerprint_prefixes = {"bar", "baz"}; + + std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options); + ASSERT_THAT(manifest, NotNull()); + xml::Element* install_constraints = manifest->root.get()->FindChild({}, "install-constraints"); + ASSERT_THAT(install_constraints, NotNull()); + std::vector<xml::Element*> fingerprint_prefixes = install_constraints->GetChildElements(); + EXPECT_EQ(fingerprint_prefixes.size(), 3); + xml::Attribute* attr; + EXPECT_THAT(fingerprint_prefixes[0]->name, StrEq("fingerprint-prefix")); + attr = fingerprint_prefixes[0]->FindAttribute(xml::kSchemaAndroid, "value"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("foo")); + EXPECT_THAT(fingerprint_prefixes[1]->name, StrEq("fingerprint-prefix")); + attr = fingerprint_prefixes[1]->FindAttribute(xml::kSchemaAndroid, "value"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("bar")); + EXPECT_THAT(fingerprint_prefixes[2]->name, StrEq("fingerprint-prefix")); + attr = fingerprint_prefixes[2]->FindAttribute(xml::kSchemaAndroid, "value"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("baz")); +} + TEST_F(ManifestFixerTest, UsesLibraryMustHaveNonEmptyName) { std::string input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" @@ -1020,4 +1154,364 @@ TEST_F(ManifestFixerTest, ComponentPropertyOnlyOneAttributeDefined) { </manifest>)"; EXPECT_THAT(Verify(input), NotNull()); } + +TEST_F(ManifestFixerTest, IntentFilterActionMustHaveNonEmptyName) { + std::string input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); + + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); + + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); +} + +TEST_F(ManifestFixerTest, IntentFilterCategoryMustHaveNonEmptyName) { + std::string input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <category android:name="" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); + + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <category /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); + + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); +} + +TEST_F(ManifestFixerTest, IntentFilterPathMustStartWithLeadingSlashOnDeepLinks) { + // No DeepLink. + std::string input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <data /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); + + // No DeepLink, missing ACTION_VIEW. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" + android:pathPrefix="pathPattern" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); + + // DeepLink, missing DEFAULT category while DEFAULT is recommended but not required. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" + android:pathPrefix="pathPattern" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); + + // No DeepLink, missing BROWSABLE category. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:scheme="http" + android:host="www.example.com" + android:pathPrefix="pathPattern" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); + + // No DeepLink, missing 'android:scheme' in <data> tag. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:host="www.example.com" + android:pathPrefix="pathPattern" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); + + // No DeepLink, <action> is ACTION_MAIN not ACTION_VIEW. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" + android:pathPrefix="pathPattern" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); + + // DeepLink with no leading slash in android:path. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" + android:path="path" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); + + // DeepLink with leading slash in android:path. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" + android:path="/path" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); + + // DeepLink with no leading slash in android:pathPrefix. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" + android:pathPrefix="pathPrefix" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); + + // DeepLink with leading slash in android:pathPrefix. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" + android:pathPrefix="/pathPrefix" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); + + // DeepLink with no leading slash in android:pathPattern. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" + android:pathPattern="pathPattern" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); + + // DeepLink with leading slash in android:pathPattern. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" + android:pathPattern="/pathPattern" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); + + // DeepLink with '.' start in pathPattern. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" + android:pathPattern=".*\\.pathPattern" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); + + // DeepLink with '*' start in pathPattern. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" + android:pathPattern="*" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); + + // DeepLink with string reference as a path. + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="http" + android:host="www.example.com" + android:path="@string/startup_uri" /> + </intent-filter> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); +} } // namespace aapt diff --git a/tools/aapt2/link/NoDefaultResourceRemover.cpp b/tools/aapt2/link/NoDefaultResourceRemover.cpp index 05990de6a9b3..2a5163f22e17 100644 --- a/tools/aapt2/link/NoDefaultResourceRemover.cpp +++ b/tools/aapt2/link/NoDefaultResourceRemover.cpp @@ -76,15 +76,15 @@ bool NoDefaultResourceRemover::Consume(IAaptContext* context, ResourceTable* tab }); for (auto iter = remove_iter; iter != end_iter; ++iter) { - const ResourceName name(pkg->name, type->type, (*iter)->name); - IDiagnostics* diag = context->GetDiagnostics(); - diag->Warn(DiagMessage() << "removing resource " << name - << " without required default value"); + const ResourceName name(pkg->name, type->named_type, (*iter)->name); + android::IDiagnostics* diag = context->GetDiagnostics(); + diag->Warn(android::DiagMessage() + << "removing resource " << name << " without required default value"); if (context->IsVerbose()) { - diag->Note(DiagMessage() << " did you forget to remove all definitions?"); + diag->Note(android::DiagMessage() << " did you forget to remove all definitions?"); for (const auto& config_value : (*iter)->values) { if (config_value->value != nullptr) { - diag->Note(DiagMessage(config_value->value->GetSource()) << "defined here"); + diag->Note(android::DiagMessage(config_value->value->GetSource()) << "defined here"); } } } diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp index 675b02a7e161..8c6c743dfff0 100644 --- a/tools/aapt2/link/PrivateAttributeMover.cpp +++ b/tools/aapt2/link/PrivateAttributeMover.cpp @@ -57,7 +57,7 @@ OutputIterator move_if(InputContainer& input_container, OutputIterator result, P bool PrivateAttributeMover::Consume(IAaptContext* context, ResourceTable* table) { for (auto& package : table->packages) { - ResourceTableType* type = package->FindType(ResourceType::kAttr); + ResourceTableType* type = package->FindTypeWithDefaultName(ResourceType::kAttr); if (!type) { continue; } @@ -80,7 +80,8 @@ bool PrivateAttributeMover::Consume(IAaptContext* context, ResourceTable* table) continue; } - ResourceTableType* priv_attr_type = package->FindOrCreateType(ResourceType::kAttrPrivate); + auto attr_private_type = ResourceNamedTypeWithDefaultName(ResourceType::kAttrPrivate); + ResourceTableType* priv_attr_type = package->FindOrCreateType(attr_private_type); CHECK(priv_attr_type->entries.empty()); priv_attr_type->entries = std::move(private_attr_entries); } diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp index 168234b36e4a..32335b7f5a9f 100644 --- a/tools/aapt2/link/PrivateAttributeMover_test.cpp +++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp @@ -41,13 +41,13 @@ TEST(PrivateAttributeMoverTest, MovePrivateAttributes) { ResourceTablePackage* package = table->FindPackage("android"); ASSERT_NE(package, nullptr); - ResourceTableType* type = package->FindType(ResourceType::kAttr); + ResourceTableType* type = package->FindTypeWithDefaultName(ResourceType::kAttr); ASSERT_NE(type, nullptr); ASSERT_EQ(type->entries.size(), 2u); EXPECT_NE(type->FindEntry("publicA"), nullptr); EXPECT_NE(type->FindEntry("publicB"), nullptr); - type = package->FindType(ResourceType::kAttrPrivate); + type = package->FindTypeWithDefaultName(ResourceType::kAttrPrivate); ASSERT_NE(type, nullptr); ASSERT_EQ(type->entries.size(), 2u); EXPECT_NE(type->FindEntry("privateA"), nullptr); @@ -68,11 +68,11 @@ TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefi ResourceTablePackage* package = table->FindPackage("android"); ASSERT_NE(package, nullptr); - ResourceTableType* type = package->FindType(ResourceType::kAttr); + ResourceTableType* type = package->FindTypeWithDefaultName(ResourceType::kAttr); ASSERT_NE(type, nullptr); ASSERT_EQ(type->entries.size(), 2u); - type = package->FindType(ResourceType::kAttrPrivate); + type = package->FindTypeWithDefaultName(ResourceType::kAttrPrivate); ASSERT_EQ(type, nullptr); } @@ -87,12 +87,12 @@ TEST(PrivateAttributeMoverTest, DoNotCreatePrivateAttrsIfNoneExist) { ResourceTablePackage* package = table->FindPackage("android"); ASSERT_NE(nullptr, package); - ASSERT_EQ(nullptr, package->FindType(ResourceType::kAttrPrivate)); + ASSERT_EQ(nullptr, package->FindTypeWithDefaultName(ResourceType::kAttrPrivate)); PrivateAttributeMover mover; ASSERT_TRUE(mover.Consume(context.get(), table.get())); - ASSERT_EQ(nullptr, package->FindType(ResourceType::kAttrPrivate)); + ASSERT_EQ(nullptr, package->FindTypeWithDefaultName(ResourceType::kAttrPrivate)); } } // namespace aapt diff --git a/tools/aapt2/link/ProductFilter.cpp b/tools/aapt2/link/ProductFilter.cpp index 793740af3021..9544986fda76 100644 --- a/tools/aapt2/link/ProductFilter.cpp +++ b/tools/aapt2/link/ProductFilter.cpp @@ -23,7 +23,7 @@ namespace aapt { ProductFilter::ResourceConfigValueIter ProductFilter::SelectProductToKeep( const ResourceNameRef& name, const ResourceConfigValueIter begin, - const ResourceConfigValueIter end, IDiagnostics* diag) { + const ResourceConfigValueIter end, android::IDiagnostics* diag) { ResourceConfigValueIter default_product_iter = end; ResourceConfigValueIter selected_product_iter = end; @@ -32,16 +32,15 @@ ProductFilter::ResourceConfigValueIter ProductFilter::SelectProductToKeep( if (products_.find(config_value->product) != products_.end()) { if (selected_product_iter != end) { // We have two possible values for this product! - diag->Error(DiagMessage(config_value->value->GetSource()) - << "selection of product '" << config_value->product - << "' for resource " << name << " is ambiguous"); + diag->Error(android::DiagMessage(config_value->value->GetSource()) + << "selection of product '" << config_value->product << "' for resource " + << name << " is ambiguous"); ResourceConfigValue* previously_selected_config_value = selected_product_iter->get(); - diag->Note( - DiagMessage(previously_selected_config_value->value->GetSource()) - << "product '" << previously_selected_config_value->product - << "' is also a candidate"); + diag->Note(android::DiagMessage(previously_selected_config_value->value->GetSource()) + << "product '" << previously_selected_config_value->product + << "' is also a candidate"); return end; } @@ -52,15 +51,13 @@ ProductFilter::ResourceConfigValueIter ProductFilter::SelectProductToKeep( if (config_value->product.empty() || config_value->product == "default") { if (default_product_iter != end) { // We have two possible default values. - diag->Error(DiagMessage(config_value->value->GetSource()) - << "multiple default products defined for resource " - << name); + diag->Error(android::DiagMessage(config_value->value->GetSource()) + << "multiple default products defined for resource " << name); ResourceConfigValue* previously_default_config_value = default_product_iter->get(); - diag->Note( - DiagMessage(previously_default_config_value->value->GetSource()) - << "default product also defined here"); + diag->Note(android::DiagMessage(previously_default_config_value->value->GetSource()) + << "default product also defined here"); return end; } @@ -70,8 +67,7 @@ ProductFilter::ResourceConfigValueIter ProductFilter::SelectProductToKeep( } if (default_product_iter == end) { - diag->Error(DiagMessage() << "no default product defined for resource " - << name); + diag->Error(android::DiagMessage() << "no default product defined for resource " << name); return end; } @@ -98,7 +94,7 @@ bool ProductFilter::Consume(IAaptContext* context, ResourceTable* table) { // End of the array, or we saw a different config, // so this must be the end of a range of products. // Select the product to keep from the set of products defined. - ResourceNameRef name(pkg->name, type->type, entry->name); + ResourceNameRef name(pkg->name, type->named_type, entry->name); auto value_to_keep = SelectProductToKeep( name, start_range_iter, iter, context->GetDiagnostics()); if (value_to_keep == iter) { diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/link/ProductFilter_test.cpp index 4f78bbcece33..2cb9afa05cad 100644 --- a/tools/aapt2/link/ProductFilter_test.cpp +++ b/tools/aapt2/link/ProductFilter_test.cpp @@ -31,27 +31,29 @@ TEST(ProductFilterTest, SelectTwoProducts) { ResourceTable table; ASSERT_TRUE(table.AddResource( NewResourceBuilder(test::ParseNameOrDie("android:string/one")) - .SetValue(test::ValueBuilder<Id>().SetSource(Source("land/default.xml")).Build(), land) + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/default.xml")).Build(), + land) .Build(), context->GetDiagnostics())); ASSERT_TRUE(table.AddResource( NewResourceBuilder(test::ParseNameOrDie("android:string/one")) - .SetValue(test::ValueBuilder<Id>().SetSource(Source("land/tablet.xml")).Build(), land, - "tablet") + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/tablet.xml")).Build(), + land, "tablet") .Build(), context->GetDiagnostics())); ASSERT_TRUE(table.AddResource( NewResourceBuilder(test::ParseNameOrDie("android:string/one")) - .SetValue(test::ValueBuilder<Id>().SetSource(Source("port/default.xml")).Build(), port) + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/default.xml")).Build(), + port) .Build(), context->GetDiagnostics())); ASSERT_TRUE(table.AddResource( NewResourceBuilder(test::ParseNameOrDie("android:string/one")) - .SetValue(test::ValueBuilder<Id>().SetSource(Source("port/tablet.xml")).Build(), port, - "tablet") + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/tablet.xml")).Build(), + port, "tablet") .Build(), context->GetDiagnostics())); @@ -74,13 +76,14 @@ TEST(ProductFilterTest, SelectDefaultProduct) { ResourceTable table; ASSERT_TRUE(table.AddResource( NewResourceBuilder(test::ParseNameOrDie("android:string/one")) - .SetValue(test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build()) + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build()) .Build(), context->GetDiagnostics())); ASSERT_TRUE(table.AddResource( NewResourceBuilder(test::ParseNameOrDie("android:string/one")) - .SetValue(test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(), {}, "tablet") + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("tablet.xml")).Build(), {}, + "tablet") .Build(), context->GetDiagnostics())); ; @@ -102,20 +105,21 @@ TEST(ProductFilterTest, FailOnAmbiguousProduct) { ResourceTable table; ASSERT_TRUE(table.AddResource( NewResourceBuilder(test::ParseNameOrDie("android:string/one")) - .SetValue(test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build()) + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build()) .Build(), context->GetDiagnostics())); ASSERT_TRUE(table.AddResource( NewResourceBuilder(test::ParseNameOrDie("android:string/one")) - .SetValue(test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(), {}, "tablet") + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("tablet.xml")).Build(), {}, + "tablet") .Build(), context->GetDiagnostics())); ASSERT_TRUE(table.AddResource( NewResourceBuilder(test::ParseNameOrDie("android:string/one")) - .SetValue(test::ValueBuilder<Id>().SetSource(Source("no-sdcard.xml")).Build(), {}, - "no-sdcard") + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("no-sdcard.xml")).Build(), + {}, "no-sdcard") .Build(), context->GetDiagnostics())); @@ -127,15 +131,15 @@ TEST(ProductFilterTest, FailOnMultipleDefaults) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); ResourceTable table; - ASSERT_TRUE( - table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/one")) - .SetValue(test::ValueBuilder<Id>().SetSource(Source(".xml")).Build()) - .Build(), - context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source(".xml")).Build()) + .Build(), + context->GetDiagnostics())); ASSERT_TRUE(table.AddResource( NewResourceBuilder(test::ParseNameOrDie("android:string/one")) - .SetValue(test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(), {}, + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build(), {}, "default") .Build(), context->GetDiagnostics())); diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 5372cf243951..9dadfb26a3f8 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -16,16 +16,15 @@ #include "link/ReferenceLinker.h" -#include "android-base/logging.h" -#include "android-base/stringprintf.h" -#include "androidfw/ResourceTypes.h" - -#include "Diagnostics.h" #include "ResourceParser.h" #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" +#include "android-base/logging.h" +#include "android-base/stringprintf.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/ResourceTypes.h" #include "link/Linkers.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" @@ -82,7 +81,7 @@ std::unique_ptr<Reference> ReferenceLinkerTransformer::TransformDerived(const Re if (auto ref = ValueCast<Reference>(linked_item_ptr)) { return std::unique_ptr<Reference>(ref); } - context_->GetDiagnostics()->Error(DiagMessage(value->GetSource()) + context_->GetDiagnostics()->Error(android::DiagMessage(value->GetSource()) << "value of '" << LoggingResourceName(*value, callsite_, package_decls_) << "' must be a resource reference"); @@ -130,7 +129,7 @@ std::unique_ptr<Style> ReferenceLinkerTransformer::TransformDerived(const Style* // check is fast and we avoid creating a DiagMessage when the match is successful. if (!symbol->attribute->Matches(*entry.value, nullptr)) { // The actual type of this item is incompatible with the attribute. - DiagMessage msg(entry.key.GetSource()); + android::DiagMessage msg(entry.key.GetSource()); // Call the matches method again, this time with a DiagMessage so we fill in the actual // error message. @@ -139,7 +138,7 @@ std::unique_ptr<Style> ReferenceLinkerTransformer::TransformDerived(const Style* error_ = true; } } else { - context_->GetDiagnostics()->Error(DiagMessage(entry.key.GetSource()) + context_->GetDiagnostics()->Error(android::DiagMessage(entry.key.GetSource()) << "style attribute '" << LoggingResourceName(entry.key, callsite_, package_decls_) << "' " << err_str); @@ -190,8 +189,7 @@ class EmptyDeclStack : public xml::IPackageDeclStack { public: EmptyDeclStack() = default; - std::optional<xml::ExtractedPackage> TransformPackageAlias( - const StringPiece& alias) const override { + std::optional<xml::ExtractedPackage> TransformPackageAlias(StringPiece alias) const override { if (alias.empty()) { return xml::ExtractedPackage{{}, true /*private*/}; } @@ -207,8 +205,7 @@ struct MacroDeclStack : public xml::IPackageDeclStack { : alias_namespaces_(std::move(namespaces)) { } - std::optional<xml::ExtractedPackage> TransformPackageAlias( - const StringPiece& alias) const override { + std::optional<xml::ExtractedPackage> TransformPackageAlias(StringPiece alias) const override { if (alias.empty()) { return xml::ExtractedPackage{{}, true /*private*/}; } @@ -344,7 +341,7 @@ std::optional<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Ref void ReferenceLinker::WriteAttributeName(const Reference& ref, const CallSite& callsite, const xml::IPackageDeclStack* decls, - DiagMessage* out_msg) { + android::DiagMessage* out_msg) { CHECK(out_msg != nullptr); if (!ref.name) { *out_msg << ref.id.value(); @@ -393,7 +390,7 @@ std::unique_ptr<Item> ReferenceLinker::LinkReference(const CallSite& callsite, auto result = table->FindResource(transformed_reference.name.value()); if (!result || result.value().entry->values.empty()) { context->GetDiagnostics()->Error( - DiagMessage(reference.GetSource()) + android::DiagMessage(reference.GetSource()) << "failed to find definition for " << LoggingResourceName(transformed_reference, callsite, decls)); return {}; @@ -424,7 +421,7 @@ std::unique_ptr<Item> ReferenceLinker::LinkReference(const CallSite& callsite, macro_values[0]->config, *context->GetDiagnostics()); if (new_value == nullptr) { context->GetDiagnostics()->Error( - DiagMessage(reference.GetSource()) + android::DiagMessage(reference.GetSource()) << "failed to substitute macro " << LoggingResourceName(transformed_reference, callsite, decls) << ": failed to parse contents as one of type(s) " << Attribute::MaskString(type_flags)); @@ -450,7 +447,7 @@ std::unique_ptr<Item> ReferenceLinker::LinkReference(const CallSite& callsite, return std::move(new_ref); } - context->GetDiagnostics()->Error(DiagMessage(reference.GetSource()) + context->GetDiagnostics()->Error(android::DiagMessage(reference.GetSource()) << "resource " << LoggingResourceName(transformed_reference, callsite, decls) << " " << err_str); @@ -468,22 +465,21 @@ bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) { for (auto& type : package->types) { for (auto& entry : type->entries) { // First, unmangle the name if necessary. - ResourceName name(package->name, type->type, entry->name); + ResourceName name(package->name, type->named_type, entry->name); NameMangler::Unmangle(&name.entry, &name.package); // Symbol state information may be lost if there is no value for the resource. if (entry->visibility.level != Visibility::Level::kUndefined && entry->values.empty()) { - context->GetDiagnostics()->Error(DiagMessage(entry->visibility.source) - << "no definition for declared symbol '" << name - << "'"); + context->GetDiagnostics()->Error(android::DiagMessage(entry->visibility.source) + << "no definition for declared symbol '" << name << "'"); error = true; } // Ensure that definitions for values declared as overlayable exist if (entry->overlayable_item && entry->values.empty()) { - context->GetDiagnostics()->Error(DiagMessage(entry->overlayable_item.value().source) - << "no definition for overlayable symbol '" - << name << "'"); + context->GetDiagnostics()->Error( + android::DiagMessage(entry->overlayable_item.value().source) + << "no definition for overlayable symbol '" << name << "'"); error = true; } diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h index b46085397c52..9fb25e1bc2c9 100644 --- a/tools/aapt2/link/ReferenceLinker.h +++ b/tools/aapt2/link/ReferenceLinker.h @@ -32,7 +32,7 @@ namespace aapt { class ReferenceLinkerTransformer : public CloningValueTransformer { public: ReferenceLinkerTransformer(const CallSite& callsite, IAaptContext* context, SymbolTable* symbols, - StringPool* string_pool, ResourceTable* table, + android::StringPool* string_pool, ResourceTable* table, xml::IPackageDeclStack* decl) : CloningValueTransformer(string_pool), callsite_(callsite), @@ -110,7 +110,8 @@ class ReferenceLinker : public IResourceTableConsumer { // Same as WriteResourceName but omits the 'attr' part. static void WriteAttributeName(const Reference& ref, const CallSite& callsite, - const xml::IPackageDeclStack* decls, DiagMessage* out_msg); + const xml::IPackageDeclStack* decls, + android::DiagMessage* out_msg); // Returns a fully linked version a resource reference. // diff --git a/tools/aapt2/link/ResourceExcluder.cpp b/tools/aapt2/link/ResourceExcluder.cpp index b3b9dc47fd84..59393cbd0add 100644 --- a/tools/aapt2/link/ResourceExcluder.cpp +++ b/tools/aapt2/link/ResourceExcluder.cpp @@ -50,12 +50,9 @@ void RemoveIfExcluded(std::set<std::pair<ConfigDescription, int>>& excluded_conf if (masked_diff == 0) { if (context->IsVerbose()) { - context->GetDiagnostics()->Note( - DiagMessage(value->value->GetSource()) - << "excluded resource \"" - << entry->name - << "\" with config " - << config.toString()); + context->GetDiagnostics()->Note(android::DiagMessage(value->value->GetSource()) + << "excluded resource \"" << entry->name + << "\" with config " << config.toString()); } value->value = {}; return; diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index d78f0ac17f67..67a48283e8b6 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -37,7 +37,7 @@ TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table, CHECK(main_package_ != nullptr) << "package name or ID already taken"; } -bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay) { +bool TableMerger::Merge(const android::Source& src, ResourceTable* table, bool overlay) { TRACE_CALL(); // We allow adding new resources if this is not an overlay, or if the options allow overlays // to add new resources. @@ -45,7 +45,8 @@ bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay) { } // This will merge packages with the same package name (or no package name). -bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, bool overlay, bool allow_new) { +bool TableMerger::MergeImpl(const android::Source& src, ResourceTable* table, bool overlay, + bool allow_new) { bool error = false; for (auto& package : table->packages) { // Only merge an empty package or the package we're building. @@ -65,13 +66,14 @@ bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, bool overla // This will merge and mangle resources from a static library. It is assumed that all FileReferences // have correctly set their io::IFile*. -bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_name, +bool TableMerger::MergeAndMangle(const android::Source& src, StringPiece package_name, ResourceTable* table) { bool error = false; for (auto& package : table->packages) { // Warn of packages with an unrelated ID. if (package_name != package->name) { - context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring package " << package->name); + context_->GetDiagnostics()->Warn(android::DiagMessage(src) + << "ignoring package " << package->name); continue; } @@ -82,8 +84,8 @@ bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_n return !error; } -static bool MergeType(IAaptContext* context, const Source& src, ResourceTableType* dst_type, - ResourceTableType* src_type) { +static bool MergeType(IAaptContext* context, const android::Source& src, + ResourceTableType* dst_type, ResourceTableType* src_type) { if (src_type->visibility_level >= dst_type->visibility_level) { // The incoming type's visibility is stronger, so we should override the visibility. dst_type->visibility_level = src_type->visibility_level; @@ -91,15 +93,15 @@ static bool MergeType(IAaptContext* context, const Source& src, ResourceTableTyp return true; } -static bool MergeEntry(IAaptContext* context, const Source& src, - ResourceEntry* dst_entry, ResourceEntry* src_entry, - bool strict_visibility) { +static bool MergeEntry(IAaptContext* context, const android::Source& src, ResourceEntry* dst_entry, + ResourceEntry* src_entry, bool strict_visibility) { if (strict_visibility && dst_entry->visibility.level != Visibility::Level::kUndefined && src_entry->visibility.level != dst_entry->visibility.level) { - context->GetDiagnostics()->Error( - DiagMessage(src) << "cannot merge resource '" << dst_entry->name << "' with conflicting visibilities: " - << "public and private"); + context->GetDiagnostics()->Error(android::DiagMessage(src) + << "cannot merge resource '" << dst_entry->name + << "' with conflicting visibilities: " + << "public and private"); return false; } @@ -114,8 +116,9 @@ static bool MergeEntry(IAaptContext* context, const Source& src, dst_entry->visibility.level == Visibility::Level::kPublic && dst_entry->id && src_entry->id && src_entry->id != dst_entry->id) { // Both entries are public and have different IDs. - context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge entry '" << src_entry->name - << "': conflicting public IDs"); + context->GetDiagnostics()->Error(android::DiagMessage(src) + << "cannot merge entry '" << src_entry->name + << "': conflicting public IDs"); return false; } @@ -139,11 +142,12 @@ static bool MergeEntry(IAaptContext* context, const Source& src, // Do not allow a resource with an overlayable declaration to have that overlayable // declaration redefined. - context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable_item.value().source) - << "duplicate overlayable declaration for resource '" - << src_entry->name << "'"); - context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable_item.value().source) - << "previous declaration here"); + context->GetDiagnostics()->Error( + android::DiagMessage(src_entry->overlayable_item.value().source) + << "duplicate overlayable declaration for resource '" << src_entry->name << "'"); + context->GetDiagnostics()->Error( + android::DiagMessage(dst_entry->overlayable_item.value().source) + << "previous declaration here"); return false; } } @@ -154,10 +158,10 @@ static bool MergeEntry(IAaptContext* context, const Source& src, if (src_entry->staged_id) { if (dst_entry->staged_id && dst_entry->staged_id.value().id != src_entry->staged_id.value().id) { - context->GetDiagnostics()->Error(DiagMessage(src_entry->staged_id.value().source) + context->GetDiagnostics()->Error(android::DiagMessage(src_entry->staged_id.value().source) << "conflicting staged id declaration for resource '" << src_entry->name << "'"); - context->GetDiagnostics()->Error(DiagMessage(dst_entry->staged_id.value().source) + context->GetDiagnostics()->Error(android::DiagMessage(dst_entry->staged_id.value().source) << "previous declaration here"); } dst_entry->staged_id = std::move(src_entry->staged_id); @@ -174,7 +178,7 @@ static bool MergeEntry(IAaptContext* context, const Source& src, // If both values are Styleables/Styles, we just merge them into the existing value. static ResourceTable::CollisionResult ResolveMergeCollision( bool override_styles_instead_of_overlaying, Value* existing, Value* incoming, - StringPool* pool) { + android::StringPool* pool) { if (Styleable* existing_styleable = ValueCast<Styleable>(existing)) { if (Styleable* incoming_styleable = ValueCast<Styleable>(incoming)) { // Styleables get merged. @@ -194,13 +198,10 @@ static ResourceTable::CollisionResult ResolveMergeCollision( return ResourceTable::ResolveValueCollision(existing, incoming); } -static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context, - const ResourceNameRef& res_name, - bool overlay, - bool override_styles_instead_of_overlaying, - ResourceConfigValue* dst_config_value, - ResourceConfigValue* src_config_value, - StringPool* pool) { +static ResourceTable::CollisionResult MergeConfigValue( + IAaptContext* context, const ResourceNameRef& res_name, bool overlay, + bool override_styles_instead_of_overlaying, ResourceConfigValue* dst_config_value, + ResourceConfigValue* src_config_value, android::StringPool* pool) { using CollisionResult = ResourceTable::CollisionResult; Value* dst_value = dst_config_value->value.get(); @@ -220,22 +221,22 @@ static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context, } // Error! - context->GetDiagnostics()->Error(DiagMessage(src_value->GetSource()) + context->GetDiagnostics()->Error(android::DiagMessage(src_value->GetSource()) << "resource '" << res_name << "' has a conflicting value for " << "configuration (" << src_config_value->config << ")"); - context->GetDiagnostics()->Note(DiagMessage(dst_value->GetSource()) + context->GetDiagnostics()->Note(android::DiagMessage(dst_value->GetSource()) << "originally defined here"); return CollisionResult::kConflict; } return collision_result; } -bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, bool mangle_package, - bool overlay, bool allow_new_resources) { +bool TableMerger::DoMerge(const android::Source& src, ResourceTablePackage* src_package, + bool mangle_package, bool overlay, bool allow_new_resources) { bool error = false; for (auto& src_type : src_package->types) { - ResourceTableType* dst_type = main_package_->FindOrCreateType(src_type->type); + ResourceTableType* dst_type = main_package_->FindOrCreateType(src_type->named_type); if (!MergeType(context_, src, dst_type, src_type.get())) { error = true; continue; @@ -254,14 +255,15 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, dst_entry = dst_type->FindEntry(entry_name); } - const ResourceNameRef res_name(src_package->name, src_type->type, src_entry->name); + const ResourceNameRef res_name(src_package->name, src_type->named_type, src_entry->name); if (!dst_entry) { - context_->GetDiagnostics()->Error(DiagMessage(src) + context_->GetDiagnostics()->Error(android::DiagMessage(src) << "resource " << res_name << " does not override an existing resource"); - context_->GetDiagnostics()->Note(DiagMessage(src) << "define an <add-resource> tag or use " - << "--auto-add-overlay"); + context_->GetDiagnostics()->Note(android::DiagMessage(src) + << "define an <add-resource> tag or use " + << "--auto-add-overlay"); error = true; continue; } @@ -324,8 +326,8 @@ std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile( const std::string& package, const FileReference& file_ref) { StringPiece prefix, entry, suffix; if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) { - std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string()); - std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string(); + std::string mangled_entry = NameMangler::MangleEntry(package, entry); + std::string newPath = (std::string(prefix) += mangled_entry) += suffix; std::unique_ptr<FileReference> new_file_ref = util::make_unique<FileReference>(main_table_->string_pool.MakeRef(newPath)); new_file_ref->SetComment(file_ref.GetComment()); @@ -349,7 +351,7 @@ bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFi file_ref->file = file; ResourceTablePackage* pkg = table.FindOrCreatePackage(file_desc.name.package); - pkg->FindOrCreateType(file_desc.name.type.type) + pkg->FindOrCreateType(file_desc.name.type) ->FindOrCreateEntry(file_desc.name.entry) ->FindOrCreateValue(file_desc.config, {}) ->value = std::move(file_ref); diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index e01a0c186392..37daf42f51e5 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -61,17 +61,18 @@ class TableMerger { // References are made to this ResourceTable for efficiency reasons. TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options); - inline const std::set<std::string>& merged_packages() const { + inline const std::set<std::string, std::less<>>& merged_packages() const { return merged_packages_; } // Merges resources from the same or empty package. This is for local sources. // If overlay is true, the resources are treated as overlays. - bool Merge(const Source& src, ResourceTable* table, bool overlay); + bool Merge(const android::Source& src, ResourceTable* table, bool overlay); // Merges resources from the given package, mangling the name. This is for static libraries. // All FileReference values must have their io::IFile set. - bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table); + bool MergeAndMangle(const android::Source& src, android::StringPiece package, + ResourceTable* table); // Merges a compiled file that belongs to this same or empty package. bool MergeFile(const ResourceFile& fileDesc, bool overlay, io::IFile* file); @@ -83,11 +84,12 @@ class TableMerger { ResourceTable* main_table_; TableMergerOptions options_; ResourceTablePackage* main_package_; - std::set<std::string> merged_packages_; + std::set<std::string, std::less<>> merged_packages_; - bool MergeImpl(const Source& src, ResourceTable* src_table, bool overlay, bool allow_new); + bool MergeImpl(const android::Source& src, ResourceTable* src_table, bool overlay, + bool allow_new); - bool DoMerge(const Source& src, ResourceTablePackage* src_package, bool mangle_package, + bool DoMerge(const android::Source& src, ResourceTablePackage* src_package, bool mangle_package, bool overlay, bool allow_new_resources); std::unique_ptr<FileReference> CloneAndMangleFile(const std::string& package, diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index 4cbf2d3a826c..56a7c3b55f8d 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -94,7 +94,7 @@ TEST_F(TableMergerTest, MergeFile) { ResourceFile file_desc; file_desc.config = test::ParseConfigOrDie("hdpi-v4"); file_desc.name = test::ParseNameOrDie("layout/main"); - file_desc.source = Source("res/layout-hdpi/main.xml"); + file_desc.source = android::Source("res/layout-hdpi/main.xml"); test::TestFile test_file("path/to/res/layout-hdpi/main.xml.flat"); ASSERT_TRUE(merger.MergeFile(file_desc, false /*overlay*/, &test_file)); diff --git a/tools/aapt2/link/XmlCompatVersioner.cpp b/tools/aapt2/link/XmlCompatVersioner.cpp index 957b64cd8dbb..482c227a3670 100644 --- a/tools/aapt2/link/XmlCompatVersioner.cpp +++ b/tools/aapt2/link/XmlCompatVersioner.cpp @@ -22,7 +22,7 @@ namespace aapt { -static xml::Attribute CopyAttr(const xml::Attribute& src, StringPool* out_string_pool) { +static xml::Attribute CopyAttr(const xml::Attribute& src, android::StringPool* out_string_pool) { CloningValueTransformer cloner(out_string_pool); xml::Attribute dst{src.namespace_uri, src.name, src.value, src.compiled_attribute}; if (src.compiled_value != nullptr) { @@ -34,7 +34,7 @@ static xml::Attribute CopyAttr(const xml::Attribute& src, StringPool* out_string // Returns false if the attribute is not copied because an existing attribute takes precedence // (came from a rule). static bool CopyAttribute(const xml::Attribute& src_attr, bool generated, xml::Element* dst_el, - StringPool* out_string_pool) { + android::StringPool* out_string_pool) { CloningValueTransformer cloner(out_string_pool); xml::Attribute* dst_attr = dst_el->FindAttribute(src_attr.namespace_uri, src_attr.name); if (dst_attr != nullptr) { @@ -58,7 +58,7 @@ void XmlCompatVersioner::ProcessRule(const xml::Element& src_el, const xml::Attr const util::Range<ApiVersion>& api_range, bool generated, xml::Element* dst_el, std::set<ApiVersion>* out_apis_referenced, - StringPool* out_string_pool) { + android::StringPool* out_string_pool) { if (src_attr_version <= api_range.start) { // The API is compatible, so don't check the rule and just copy. if (!CopyAttribute(src_attr, generated, dst_el, out_string_pool)) { @@ -156,7 +156,7 @@ DegradeToManyRule::DegradeToManyRule(std::vector<ReplacementAttr> attrs) } static inline std::unique_ptr<Item> CloneIfNotNull(const std::unique_ptr<Item>& src, - StringPool* out_string_pool) { + android::StringPool* out_string_pool) { if (src == nullptr) { return {}; } @@ -166,7 +166,7 @@ static inline std::unique_ptr<Item> CloneIfNotNull(const std::unique_ptr<Item>& std::vector<DegradeResult> DegradeToManyRule::Degrade(const xml::Element& src_el, const xml::Attribute& src_attr, - StringPool* out_string_pool) const { + android::StringPool* out_string_pool) const { std::vector<DegradeResult> result; result.reserve(attrs_.size()); for (const ReplacementAttr& attr : attrs_) { diff --git a/tools/aapt2/link/XmlCompatVersioner.h b/tools/aapt2/link/XmlCompatVersioner.h index 998061806279..22f98281b7ac 100644 --- a/tools/aapt2/link/XmlCompatVersioner.h +++ b/tools/aapt2/link/XmlCompatVersioner.h @@ -45,7 +45,7 @@ class IDegradeRule { virtual std::vector<DegradeResult> Degrade(const xml::Element& src_el, const xml::Attribute& src_attr, - StringPool* out_string_pool) const = 0; + android::StringPool* out_string_pool) const = 0; private: DISALLOW_COPY_AND_ASSIGN(IDegradeRule); @@ -70,7 +70,7 @@ class XmlCompatVersioner { void ProcessRule(const xml::Element& src_el, const xml::Attribute& src_attr, const ApiVersion& src_attr_version, const IDegradeRule* rule, const util::Range<ApiVersion>& api_range, bool generated, xml::Element* dst_el, - std::set<ApiVersion>* out_apis_referenced, StringPool* out_string_pool); + std::set<ApiVersion>* out_apis_referenced, android::StringPool* out_string_pool); const Rules* rules_; }; @@ -87,7 +87,7 @@ class DegradeToManyRule : public IDegradeRule { virtual ~DegradeToManyRule() = default; std::vector<DegradeResult> Degrade(const xml::Element& src_el, const xml::Attribute& src_attr, - StringPool* out_string_pool) const override; + android::StringPool* out_string_pool) const override; private: DISALLOW_COPY_AND_ASSIGN(DegradeToManyRule); diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index 1f8548b5de75..d2e9bd770a31 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -14,14 +14,12 @@ * limitations under the License. */ -#include "link/Linkers.h" - -#include "androidfw/ResourceTypes.h" - -#include "Diagnostics.h" #include "ResourceUtils.h" #include "SdkConstants.h" #include "ValueVisitor.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/ResourceTypes.h" +#include "link/Linkers.h" #include "link/ReferenceLinker.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" @@ -38,7 +36,7 @@ class XmlVisitor : public xml::PackageAwareVisitor { public: using xml::PackageAwareVisitor::Visit; - XmlVisitor(const Source& source, StringPool* pool, const CallSite& callsite, + XmlVisitor(const android::Source& source, android::StringPool* pool, const CallSite& callsite, IAaptContext* context, ResourceTable* table, SymbolTable* symbols) : source_(source), callsite_(callsite), @@ -61,7 +59,7 @@ class XmlVisitor : public xml::PackageAwareVisitor { } } - const Source source = source_.WithLine(el->line_number); + const android::Source source = source_.WithLine(el->line_number); for (xml::Attribute& attr : el->attributes) { // If the attribute has no namespace, interpret values as if // they were assigned to the default Attribute. @@ -80,7 +78,7 @@ class XmlVisitor : public xml::PackageAwareVisitor { ReferenceLinker::CompileXmlAttribute(attr_ref, callsite_, context_, symbols_, &err_str); if (!attr.compiled_attribute) { - DiagMessage error_msg(source); + android::DiagMessage error_msg(source); error_msg << "attribute "; ReferenceLinker::WriteAttributeName(attr_ref, callsite_, this, &error_msg); error_msg << " " << err_str; @@ -99,7 +97,7 @@ class XmlVisitor : public xml::PackageAwareVisitor { attr.compiled_value = attr.compiled_value->Transform(reference_transformer_); } else if ((attribute->type_mask & android::ResTable_map::TYPE_STRING) == 0) { // We won't be able to encode this as a string. - DiagMessage msg(source); + android::DiagMessage msg(source); msg << "'" << attr.value << "' is incompatible with attribute " << attr.name << " " << *attribute; context_->GetDiagnostics()->Error(msg); @@ -118,7 +116,7 @@ class XmlVisitor : public xml::PackageAwareVisitor { private: DISALLOW_COPY_AND_ASSIGN(XmlVisitor); - Source source_; + android::Source source_; const CallSite& callsite_; IAaptContext* context_; SymbolTable* symbols_; diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp index c686a10a3fa9..f01db3ddca2e 100644 --- a/tools/aapt2/optimize/MultiApkGenerator.cpp +++ b/tools/aapt2/optimize/MultiApkGenerator.cpp @@ -65,7 +65,7 @@ class ContextWrapper : public IAaptContext { return context_->GetExternalSymbols(); } - IDiagnostics* GetDiagnostics() override { + android::IDiagnostics* GetDiagnostics() override { if (source_diag_) { return source_diag_.get(); } @@ -97,8 +97,8 @@ class ContextWrapper : public IAaptContext { } void SetSource(const std::string& source) { - source_diag_ = - util::make_unique<SourcePathDiagnostics>(Source{source}, context_->GetDiagnostics()); + source_diag_ = util::make_unique<android::SourcePathDiagnostics>(android::Source{source}, + context_->GetDiagnostics()); } const std::set<std::string>& GetSplitNameDependencies() override { @@ -107,18 +107,18 @@ class ContextWrapper : public IAaptContext { private: IAaptContext* context_; - std::unique_ptr<SourcePathDiagnostics> source_diag_; + std::unique_ptr<android::SourcePathDiagnostics> source_diag_; int min_sdk_ = -1; }; class SignatureFilter : public IPathFilter { - bool Keep(const std::string& path) override { + bool Keep(std::string_view path) override { static std::regex signature_regex(R"regex(^META-INF/.*\.(RSA|DSA|EC|SF)$)regex"); - if (std::regex_search(path, signature_regex)) { + if (std::regex_search(path.begin(), path.end(), signature_regex)) { return false; } - return !(path == "META-INF/MANIFEST.MF"); + return path != "META-INF/MANIFEST.MF"; } }; @@ -143,7 +143,8 @@ bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) { if (it == artifacts_to_keep.end()) { filtered_artifacts.insert(artifact.name); if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage(artifact.name) << "skipping artifact"); + context_->GetDiagnostics()->Note(android::DiagMessage(artifact.name) + << "skipping artifact"); } continue; } else { @@ -158,28 +159,29 @@ bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) { return false; } - IDiagnostics* diag = wrapped_context.GetDiagnostics(); + android::IDiagnostics* diag = wrapped_context.GetDiagnostics(); std::unique_ptr<XmlResource> manifest; if (!UpdateManifest(artifact, &manifest, diag)) { - diag->Error(DiagMessage() << "could not update AndroidManifest.xml for output artifact"); + diag->Error(android::DiagMessage() + << "could not update AndroidManifest.xml for output artifact"); return false; } std::string out = options.out_dir; if (!file::mkdirs(out)) { - diag->Warn(DiagMessage() << "could not create out dir: " << out); + diag->Warn(android::DiagMessage() << "could not create out dir: " << out); } file::AppendPath(&out, artifact.name); if (context_->IsVerbose()) { - diag->Note(DiagMessage() << "Generating split: " << out); + diag->Note(android::DiagMessage() << "Generating split: " << out); } std::unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(diag, out); if (context_->IsVerbose()) { - diag->Note(DiagMessage() << "Writing output: " << out); + diag->Note(android::DiagMessage() << "Writing output: " << out); } filters.AddFilter(util::make_unique<SignatureFilter>()); @@ -192,22 +194,25 @@ bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) { // Make sure all of the requested artifacts were valid. If there are any kept artifacts left, // either the config or the command line was wrong. if (!artifacts_to_keep.empty()) { - context_->GetDiagnostics()->Error( - DiagMessage() << "The configuration and command line to filter artifacts do not match"); + context_->GetDiagnostics() + ->Error(android::DiagMessage() + << "The configuration and command line to filter artifacts do not match"); - context_->GetDiagnostics()->Error(DiagMessage() << kept_artifacts.size() << " kept:"); + context_->GetDiagnostics()->Error(android::DiagMessage() << kept_artifacts.size() << " kept:"); for (const auto& artifact : kept_artifacts) { - context_->GetDiagnostics()->Error(DiagMessage() << " " << artifact); + context_->GetDiagnostics()->Error(android::DiagMessage() << " " << artifact); } - context_->GetDiagnostics()->Error(DiagMessage() << filtered_artifacts.size() << " filtered:"); + context_->GetDiagnostics()->Error(android::DiagMessage() + << filtered_artifacts.size() << " filtered:"); for (const auto& artifact : filtered_artifacts) { - context_->GetDiagnostics()->Error(DiagMessage() << " " << artifact); + context_->GetDiagnostics()->Error(android::DiagMessage() << " " << artifact); } - context_->GetDiagnostics()->Error(DiagMessage() << artifacts_to_keep.size() << " missing:"); + context_->GetDiagnostics()->Error(android::DiagMessage() + << artifacts_to_keep.size() << " missing:"); for (const auto& artifact : artifacts_to_keep) { - context_->GetDiagnostics()->Error(DiagMessage() << " " << artifact); + context_->GetDiagnostics()->Error(android::DiagMessage() << " " << artifact); } return false; @@ -250,7 +255,8 @@ std::unique_ptr<ResourceTable> MultiApkGenerator::FilterTable(IAaptContext* cont VersionCollapser collapser; if (!collapser.Consume(&wrapped_context, table.get())) { - context->GetDiagnostics()->Error(DiagMessage() << "Failed to strip versioned resources"); + context->GetDiagnostics()->Error(android::DiagMessage() + << "Failed to strip versioned resources"); return {}; } @@ -261,7 +267,7 @@ std::unique_ptr<ResourceTable> MultiApkGenerator::FilterTable(IAaptContext* cont bool MultiApkGenerator::UpdateManifest(const OutputArtifact& artifact, std::unique_ptr<XmlResource>* updated_manifest, - IDiagnostics* diag) { + android::IDiagnostics* diag) { const xml::XmlResource* apk_manifest = apk_->GetManifest(); if (apk_manifest == nullptr) { return false; @@ -277,20 +283,21 @@ bool MultiApkGenerator::UpdateManifest(const OutputArtifact& artifact, } if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") { - diag->Error(DiagMessage(manifest->file.source) << "root tag must be <manifest>"); + diag->Error(android::DiagMessage(manifest->file.source) << "root tag must be <manifest>"); return false; } // Retrieve the versionCode attribute. auto version_code = manifest_el->FindAttribute(kSchemaAndroid, "versionCode"); if (!version_code) { - diag->Error(DiagMessage(manifest->file.source) << "manifest must have a versionCode attribute"); + diag->Error(android::DiagMessage(manifest->file.source) + << "manifest must have a versionCode attribute"); return false; } auto version_code_value = ValueCast<BinaryPrimitive>(version_code->compiled_value.get()); if (!version_code_value) { - diag->Error(DiagMessage(manifest->file.source) << "versionCode is invalid"); + diag->Error(android::DiagMessage(manifest->file.source) << "versionCode is invalid"); return false; } @@ -300,7 +307,7 @@ bool MultiApkGenerator::UpdateManifest(const OutputArtifact& artifact, if (version_code_major) { version_code_major_value = ValueCast<BinaryPrimitive>(version_code_major->compiled_value.get()); if (!version_code_major_value) { - diag->Error(DiagMessage(manifest->file.source) << "versionCodeMajor is invalid"); + diag->Error(android::DiagMessage(manifest->file.source) << "versionCodeMajor is invalid"); return false; } } @@ -325,13 +332,15 @@ bool MultiApkGenerator::UpdateManifest(const OutputArtifact& artifact, } else { // There was no minSdkVersion. This is strange since at this point we should have been // through the manifest fixer which sets the default minSdkVersion. - diag->Error(DiagMessage(manifest->file.source) << "missing minSdkVersion from <uses-sdk>"); + diag->Error(android::DiagMessage(manifest->file.source) + << "missing minSdkVersion from <uses-sdk>"); return false; } } else { // No uses-sdk present. This is strange since at this point we should have been // through the manifest fixer which should have added it. - diag->Error(DiagMessage(manifest->file.source) << "missing <uses-sdk> from <manifest>"); + diag->Error(android::DiagMessage(manifest->file.source) + << "missing <uses-sdk> from <manifest>"); return false; } } diff --git a/tools/aapt2/optimize/MultiApkGenerator.h b/tools/aapt2/optimize/MultiApkGenerator.h index 4a5a6c3d5915..8d8ed651a763 100644 --- a/tools/aapt2/optimize/MultiApkGenerator.h +++ b/tools/aapt2/optimize/MultiApkGenerator.h @@ -22,10 +22,9 @@ #include <unordered_set> #include <vector> -#include "androidfw/ConfigDescription.h" - -#include "Diagnostics.h" #include "LoadedApk.h" +#include "androidfw/ConfigDescription.h" +#include "androidfw/IDiagnostics.h" #include "configuration/ConfigurationParser.h" namespace aapt { @@ -58,12 +57,13 @@ class MultiApkGenerator { FilterChain* chain); private: - IDiagnostics* GetDiagnostics() { + android::IDiagnostics* GetDiagnostics() { return context_->GetDiagnostics(); } bool UpdateManifest(const configuration::OutputArtifact& artifact, - std::unique_ptr<xml::XmlResource>* updated_manifest, IDiagnostics* diag); + std::unique_ptr<xml::XmlResource>* updated_manifest, + android::IDiagnostics* diag); /** * Adds the <screen> elements to the parent node for the provided density configuration. diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp new file mode 100644 index 000000000000..8f12f735736e --- /dev/null +++ b/tools/aapt2/optimize/Obfuscator.cpp @@ -0,0 +1,237 @@ +/* + * 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 "optimize/Obfuscator.h" + +#include <fstream> +#include <map> +#include <set> +#include <string> +#include <unordered_set> + +#include "ResourceTable.h" +#include "ValueVisitor.h" +#include "androidfw/StringPiece.h" +#include "util/Util.h" + +static const char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_"; + +namespace aapt { + +Obfuscator::Obfuscator(OptimizeOptions& optimizeOptions) + : options_(optimizeOptions.table_flattener_options), + shorten_resource_paths_(optimizeOptions.shorten_resource_paths), + collapse_key_stringpool_(optimizeOptions.table_flattener_options.collapse_key_stringpool) { +} + +std::string ShortenFileName(android::StringPiece file_path, int output_length) { + std::size_t hash_num = std::hash<android::StringPiece>{}(file_path); + std::string result = ""; + // Convert to (modified) base64 so that it is a proper file path. + for (int i = 0; i < output_length; i++) { + uint8_t sextet = hash_num & 0x3f; + hash_num >>= 6; + result += base64_chars[sextet]; + } + return result; +} + +// Return the optimal hash length such that at most 10% of resources collide in +// their shortened path. +// Reference: http://matt.might.net/articles/counting-hash-collisions/ +int OptimalShortenedLength(int num_resources) { + if (num_resources > 4000) { + return 3; + } else { + return 2; + } +} + +std::string GetShortenedPath(android::StringPiece shortened_filename, + android::StringPiece extension, int collision_count) { + std::string shortened_path = std::string("res/") += shortened_filename; + if (collision_count > 0) { + shortened_path += std::to_string(collision_count); + } + shortened_path += extension; + return shortened_path; +} + +// implement custom comparator of FileReference pointers so as to use the +// underlying filepath as key rather than the integer address. This is to ensure +// determinism of output for colliding files. +struct PathComparator { + bool operator()(const FileReference* lhs, const FileReference* rhs) const { + return lhs->path->compare(*rhs->path); + } +}; + +static bool HandleShortenFilePaths(ResourceTable* table, + std::map<std::string, std::string>& shortened_path_map, + const std::set<ResourceName>& path_shorten_exemptions) { + // used to detect collisions + std::unordered_set<std::string> shortened_paths; + std::set<FileReference*, PathComparator> file_refs; + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + ResourceName resource_name({}, type->named_type, entry->name); + if (path_shorten_exemptions.find(resource_name) != path_shorten_exemptions.end()) { + continue; + } + for (auto& config_value : entry->values) { + FileReference* file_ref = ValueCast<FileReference>(config_value->value.get()); + if (file_ref) { + file_refs.insert(file_ref); + } + } + } + } + } + int num_chars = OptimalShortenedLength(file_refs.size()); + for (auto& file_ref : file_refs) { + android::StringPiece res_subdir, actual_filename, extension; + util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension); + + // Android detects ColorStateLists via pathname, skip res/color* + if (util::StartsWith(res_subdir, "res/color")) continue; + + std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars); + int collision_count = 0; + std::string shortened_path = GetShortenedPath(shortened_filename, extension, collision_count); + while (shortened_paths.find(shortened_path) != shortened_paths.end()) { + collision_count++; + shortened_path = GetShortenedPath(shortened_filename, extension, collision_count); + } + shortened_paths.insert(shortened_path); + shortened_path_map.insert({*file_ref->path, shortened_path}); + file_ref->path = table->string_pool.MakeRef(shortened_path, file_ref->path.GetContext()); + } + return true; +} + +void Obfuscator::ObfuscateResourceName( + const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions, + const ResourceNamedType& type_name, const ResourceTableEntryView& entry, + const android::base::function_ref<void(Result obfuscatedResult, const ResourceName&)> + onObfuscate) { + ResourceName resource_name({}, type_name, entry.name); + if (!collapse_key_stringpool || + name_collapse_exemptions.find(resource_name) != name_collapse_exemptions.end()) { + onObfuscate(Result::Keep_ExemptionList, resource_name); + } else { + // resource isn't exempt from collapse, add it as obfuscated value + if (entry.overlayable_item) { + // if the resource name of the specific entry is obfuscated and this + // entry is in the overlayable list, the overlay can't work on this + // overlayable at runtime because the name has been obfuscated in + // resources.arsc during flatten operation. + onObfuscate(Result::Keep_Overlayable, resource_name); + } else { + onObfuscate(Result::Obfuscated, resource_name); + } + } +} + +static bool HandleCollapseKeyStringPool( + const ResourceTable* table, const bool collapse_key_string_pool, + const std::set<ResourceName>& name_collapse_exemptions, + std::unordered_map<uint32_t, std::string>& id_resource_map) { + if (!collapse_key_string_pool) { + return true; + } + + int entryResId = 0; + auto onObfuscate = [&entryResId, &id_resource_map](const Obfuscator::Result obfuscatedResult, + const ResourceName& resource_name) { + if (obfuscatedResult == Obfuscator::Result::Obfuscated) { + id_resource_map.insert({entryResId, resource_name.entry}); + } + }; + + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + if (!entry->id.has_value() || entry->name.empty()) { + continue; + } + entryResId = entry->id->id; + ResourceTableEntryView entry_view{ + .name = entry->name, + .id = entry->id ? entry->id.value().entry_id() : (std::optional<uint16_t>)std::nullopt, + .visibility = entry->visibility, + .allow_new = entry->allow_new, + .overlayable_item = entry->overlayable_item, + .staged_id = entry->staged_id}; + + Obfuscator::ObfuscateResourceName(collapse_key_string_pool, name_collapse_exemptions, + type->named_type, entry_view, onObfuscate); + } + } + } + + return true; +} + +bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) { + HandleCollapseKeyStringPool(table, options_.collapse_key_stringpool, + options_.name_collapse_exemptions, options_.id_resource_map); + if (shorten_resource_paths_) { + return HandleShortenFilePaths(table, options_.shortened_path_map, + options_.path_shorten_exemptions); + } + return true; +} + +bool Obfuscator::WriteObfuscationMap(const std::string& file_path) const { + pb::ResourceMappings resourceMappings; + for (const auto& [id, name] : options_.id_resource_map) { + auto* collapsedNameMapping = resourceMappings.mutable_collapsed_names()->add_resource_names(); + collapsedNameMapping->set_id(id); + collapsedNameMapping->set_name(name); + } + + for (const auto& [original_path, shortened_path] : options_.shortened_path_map) { + auto* resource_path = resourceMappings.mutable_shortened_paths()->add_resource_paths(); + resource_path->set_original_path(original_path); + resource_path->set_shortened_path(shortened_path); + } + + { // RAII style, output the pb content to file and close fout in destructor + std::ofstream fout(file_path, std::ios::out | std::ios::trunc | std::ios::binary); + if (!fout.is_open()) { + return false; + } + return resourceMappings.SerializeToOstream(&fout); + } +} + +/** + * Tell the optimizer whether it's needed to dump information for de-obfuscating. + * + * There are two conditions need to dump the information for de-obfuscating. + * * the option of shortening file paths is enabled. + * * the option of collapsing resource names is enabled. + * @return true if the information needed for de-obfuscating, otherwise false + */ +bool Obfuscator::IsEnabled() const { + return shorten_resource_paths_ || collapse_key_stringpool_; +} + +} // namespace aapt diff --git a/tools/aapt2/optimize/Obfuscator.h b/tools/aapt2/optimize/Obfuscator.h new file mode 100644 index 000000000000..5ccf54383aae --- /dev/null +++ b/tools/aapt2/optimize/Obfuscator.h @@ -0,0 +1,65 @@ +/* + * 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 TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_ +#define TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_ + +#include <set> +#include <string> + +#include "ResourceMetadata.pb.h" +#include "ResourceTable.h" +#include "android-base/function_ref.h" +#include "android-base/macros.h" +#include "cmd/Optimize.h" +#include "format/binary/TableFlattener.h" +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +class ResourceTable; + +// Maps resources in the apk to shortened paths. +class Obfuscator : public IResourceTableConsumer { + public: + explicit Obfuscator(OptimizeOptions& optimizeOptions); + + bool Consume(IAaptContext* context, ResourceTable* table) override; + + bool WriteObfuscationMap(const std::string& file_path) const; + + bool IsEnabled() const; + + enum class Result { Obfuscated, Keep_ExemptionList, Keep_Overlayable }; + + // hardcoded string uses characters which make it an invalid resource name + static constexpr char kObfuscatedResourceName[] = "0_resource_name_obfuscated"; + + static void ObfuscateResourceName( + const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions, + const ResourceNamedType& type_name, const ResourceTableEntryView& entry, + const android::base::function_ref<void(Result, const ResourceName&)> onObfuscate); + + private: + TableFlattenerOptions& options_; + const bool shorten_resource_paths_; + const bool collapse_key_stringpool_; + DISALLOW_COPY_AND_ASSIGN(Obfuscator); +}; + +} // namespace aapt + +#endif // TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_ diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp new file mode 100644 index 000000000000..940cf1096f92 --- /dev/null +++ b/tools/aapt2/optimize/Obfuscator_test.cpp @@ -0,0 +1,338 @@ +/* + * 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 "optimize/Obfuscator.h" + +#include <map> +#include <memory> +#include <string> + +#include "ResourceTable.h" +#include "android-base/file.h" +#include "test/Test.h" + +using ::aapt::test::GetValue; +using ::testing::AnyOf; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::IsFalse; +using ::testing::IsTrue; +using ::testing::Not; +using ::testing::NotNull; + +android::StringPiece GetExtension(android::StringPiece path) { + auto iter = std::find(path.begin(), path.end(), '.'); + return android::StringPiece(iter, path.end() - iter); +} + +void FillTable(aapt::test::ResourceTableBuilder& builder, int start, int end) { + for (int i = start; i < end; i++) { + builder.AddFileReference("android:drawable/xmlfile" + std::to_string(i), + "res/drawable/xmlfile" + std::to_string(i) + ".xml"); + } +} + +namespace aapt { + +TEST(ObfuscatorTest, FileRefPathsChangedInResourceTable) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:drawable/xmlfile", "res/drawables/xmlfile.xml") + .AddFileReference("android:drawable/xmlfile2", "res/drawables/xmlfile2.xml") + .AddString("android:string/string", "res/should/still/be/the/same.png") + .Build(); + + OptimizeOptions options{.shorten_resource_paths = true}; + std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map; + ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get())); + + // Expect that the path map is populated + ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end()))); + ASSERT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end()))); + + // The file paths were changed + EXPECT_THAT(path_map.at("res/drawables/xmlfile.xml"), Not(Eq("res/drawables/xmlfile.xml"))); + EXPECT_THAT(path_map.at("res/drawables/xmlfile2.xml"), Not(Eq("res/drawables/xmlfile2.xml"))); + + // Different file paths should remain different + EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], + Not(Eq(path_map["res/drawables/xmlfile2.xml"]))); + + FileReference* ref = GetValue<FileReference>(table.get(), "android:drawable/xmlfile"); + ASSERT_THAT(ref, NotNull()); + // The map correctly points to the new location of the file + EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], Eq(*ref->path)); + + // Strings should not be affected, only file paths + EXPECT_THAT(*GetValue<String>(table.get(), "android:string/string")->value, + Eq("res/should/still/be/the/same.png")); + EXPECT_THAT(path_map.find("res/should/still/be/the/same.png"), Eq(path_map.end())); +} + +TEST(ObfuscatorTest, SkipColorFileRefPaths) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:color/colorlist", "res/color/colorlist.xml") + .AddFileReference("android:color/colorlist", "res/color-mdp-v21/colorlist.xml", + test::ParseConfigOrDie("mdp-v21")) + .Build(); + + OptimizeOptions options{.shorten_resource_paths = true}; + std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map; + ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get())); + + // Expect that the path map to not contain the ColorStateList + ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end())); + ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end())); +} + +TEST(ObfuscatorTest, SkipPathShortenExemptions) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:drawable/xmlfile", "res/drawables/xmlfile.xml") + .AddFileReference("android:drawable/xmlfile2", "res/drawables/xmlfile2.xml") + .AddString("android:string/string", "res/should/still/be/the/same.png") + .Build(); + + OptimizeOptions options{.shorten_resource_paths = true}; + TableFlattenerOptions& flattenerOptions = options.table_flattener_options; + flattenerOptions.path_shorten_exemptions.insert( + ResourceName({}, ResourceType::kDrawable, "xmlfile")); + std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map; + ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get())); + + // Expect that the path map to not contain the first drawable which is in exemption set + EXPECT_THAT(path_map.find("res/drawables/xmlfile.xml"), Eq(path_map.end())); + + // Expect that the path map to contain the second drawable which is not in exemption set + EXPECT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end()))); + + FileReference* ref = GetValue<FileReference>(table.get(), "android:drawable/xmlfile"); + EXPECT_THAT(ref, NotNull()); + ASSERT_THAT(HasFailure(), IsFalse()); + // The path of first drawable in exemption was not changed + EXPECT_THAT("res/drawables/xmlfile.xml", Eq(*ref->path)); + + // The file path of second drawable not in exemption set was changed + EXPECT_THAT(path_map.at("res/drawables/xmlfile2.xml"), Not(Eq("res/drawables/xmlfile2.xml"))); + + FileReference* ref2 = GetValue<FileReference>(table.get(), "android:drawable/xmlfile2"); + ASSERT_THAT(ref, NotNull()); + // The map of second drawable not in exemption correctly points to the new location of the file + EXPECT_THAT(path_map["res/drawables/xmlfile2.xml"], Eq(*ref2->path)); +} + +TEST(ObfuscatorTest, KeepExtensions) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + std::string original_xml_path = "res/drawable/xmlfile.xml"; + std::string original_png_path = "res/drawable/pngfile.png"; + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:color/xmlfile", original_xml_path) + .AddFileReference("android:color/pngfile", original_png_path) + .Build(); + + OptimizeOptions options{.shorten_resource_paths = true}; + std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map; + ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get())); + + // Expect that the path map is populated + ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end()))); + ASSERT_THAT(path_map.find("res/drawable/pngfile.png"), Not(Eq(path_map.end()))); + + auto shortend_xml_path = path_map[original_xml_path]; + auto shortend_png_path = path_map[original_png_path]; + + EXPECT_THAT(GetExtension(path_map[original_xml_path]), Eq(android::StringPiece(".xml"))); + EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png"))); +} + +TEST(ObfuscatorTest, DeterministicallyHandleCollisions) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + // 4000 resources is the limit at which the hash space is expanded to 3 + // letters to reduce collisions, we want as many collisions as possible thus + // N-1. + const auto kNumResources = 3999; + const auto kNumTries = 5; + + test::ResourceTableBuilder builder1; + FillTable(builder1, 0, kNumResources); + std::unique_ptr<ResourceTable> table1 = builder1.Build(); + OptimizeOptions options{.shorten_resource_paths = true}; + std::map<std::string, std::string>& expected_mapping = + options.table_flattener_options.shortened_path_map; + ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table1.get())); + + // We are trying to ensure lack of non-determinism, it is not simple to prove + // a negative, thus we must try the test a few times so that the test itself + // is non-flaky. Basically create the pathmap 5 times from the same set of + // resources but a different order of addition and then ensure they are always + // mapped to the same short path. + for (int i = 0; i < kNumTries; i++) { + test::ResourceTableBuilder builder2; + // This loop adds resources to the resource table in the range of + // [0:kNumResources). Adding the file references in different order makes + // non-determinism more likely to surface. Thus we add resources + // [start_index:kNumResources) first then [0:start_index). We also use a + // different start_index each run. + int start_index = (kNumResources / kNumTries) * i; + FillTable(builder2, start_index, kNumResources); + FillTable(builder2, 0, start_index); + std::unique_ptr<ResourceTable> table2 = builder2.Build(); + + OptimizeOptions actualOptimizerOptions{.shorten_resource_paths = true}; + TableFlattenerOptions& actual_options = actualOptimizerOptions.table_flattener_options; + std::map<std::string, std::string>& actual_mapping = actual_options.shortened_path_map; + ASSERT_TRUE(Obfuscator(actualOptimizerOptions).Consume(context.get(), table2.get())); + + for (auto& item : actual_mapping) { + ASSERT_THAT(expected_mapping[item.first], Eq(item.second)); + } + } +} + +TEST(ObfuscatorTest, DumpIdResourceMap) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme")); + overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION; + overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION; + overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION; + + std::string original_xml_path = "res/drawable/xmlfile.xml"; + std::string original_png_path = "res/drawable/pngfile.png"; + + std::string name = "com.app.test:string/overlayable"; + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:color/xmlfile", original_xml_path) + .AddFileReference("android:color/pngfile", original_png_path) + .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000), + aapt::util::make_unique<aapt::BinaryPrimitive>( + uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc)) + .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hi") + .AddString("com.app.test:string/in_exemption", ResourceId(0x7f030001), "Hi") + .AddString(name, ResourceId(0x7f030002), "HI") + .SetOverlayable(name, overlayable_item) + .Build(); + + OptimizeOptions options{.shorten_resource_paths = true}; + TableFlattenerOptions& flattenerOptions = options.table_flattener_options; + flattenerOptions.collapse_key_stringpool = true; + flattenerOptions.name_collapse_exemptions.insert( + ResourceName({}, ResourceType::kString, "in_exemption")); + auto& id_resource_map = flattenerOptions.id_resource_map; + ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get())); + + // Expect that the id resource name map is populated + EXPECT_THAT(id_resource_map.at(0x7f020000), Eq("mycolor")); + EXPECT_THAT(id_resource_map.at(0x7f030000), Eq("mystring")); + EXPECT_THAT(id_resource_map.find(0x7f030001), Eq(id_resource_map.end())); + EXPECT_THAT(id_resource_map.find(0x7f030002), Eq(id_resource_map.end())); +} + +TEST(ObfuscatorTest, IsEnabledWithDefaultOption) { + OptimizeOptions options; + Obfuscator obfuscatorWithDefaultOption(options); + ASSERT_THAT(obfuscatorWithDefaultOption.IsEnabled(), Eq(false)); +} + +TEST(ObfuscatorTest, IsEnabledWithShortenPathOption) { + OptimizeOptions options{.shorten_resource_paths = true}; + Obfuscator obfuscatorWithShortenPathOption(options); + ASSERT_THAT(obfuscatorWithShortenPathOption.IsEnabled(), Eq(true)); +} + +TEST(ObfuscatorTest, IsEnabledWithCollapseStringPoolOption) { + OptimizeOptions options; + options.table_flattener_options.collapse_key_stringpool = true; + Obfuscator obfuscatorWithCollapseStringPoolOption(options); + ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true)); +} + +TEST(ObfuscatorTest, IsEnabledWithShortenPathAndCollapseStringPoolOption) { + OptimizeOptions options{.shorten_resource_paths = true}; + options.table_flattener_options.collapse_key_stringpool = true; + Obfuscator obfuscatorWithCollapseStringPoolOption(options); + ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true)); +} + +static std::unique_ptr<ResourceTable> getProtocolBufferTableUnderTest() { + std::string original_xml_path = "res/drawable/xmlfile.xml"; + std::string original_png_path = "res/drawable/pngfile.png"; + + return test::ResourceTableBuilder() + .AddFileReference("com.app.test:drawable/xmlfile", original_xml_path) + .AddFileReference("com.app.test:drawable/pngfile", original_png_path) + .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000), + aapt::util::make_unique<aapt::BinaryPrimitive>( + uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc)) + .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hello world") + .Build(); +} + +TEST(ObfuscatorTest, WriteObfuscationMapInProtocolBufferFormat) { + OptimizeOptions options{.shorten_resource_paths = true}; + options.table_flattener_options.collapse_key_stringpool = true; + Obfuscator obfuscator(options); + ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(), + getProtocolBufferTableUnderTest().get())); + + obfuscator.WriteObfuscationMap("obfuscated_map.pb"); + + std::string pbOut; + android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */); + EXPECT_THAT(pbOut, HasSubstr("drawable/xmlfile.xml")); + EXPECT_THAT(pbOut, HasSubstr("drawable/pngfile.png")); + EXPECT_THAT(pbOut, HasSubstr("mycolor")); + EXPECT_THAT(pbOut, HasSubstr("mystring")); + pb::ResourceMappings resourceMappings; + EXPECT_THAT(resourceMappings.ParseFromString(pbOut), IsTrue()); + EXPECT_THAT(resourceMappings.collapsed_names().resource_names_size(), Eq(2)); + auto& resource_names = resourceMappings.collapsed_names().resource_names(); + EXPECT_THAT(resource_names.at(0).name(), AnyOf(Eq("mycolor"), Eq("mystring"))); + EXPECT_THAT(resource_names.at(1).name(), AnyOf(Eq("mycolor"), Eq("mystring"))); + auto& shortened_paths = resourceMappings.shortened_paths(); + EXPECT_THAT(shortened_paths.resource_paths_size(), Eq(2)); + EXPECT_THAT(shortened_paths.resource_paths(0).original_path(), + AnyOf(Eq("res/drawable/pngfile.png"), Eq("res/drawable/xmlfile.xml"))); + EXPECT_THAT(shortened_paths.resource_paths(1).original_path(), + AnyOf(Eq("res/drawable/pngfile.png"), Eq("res/drawable/xmlfile.xml"))); +} + +TEST(ObfuscatorTest, WriteObfuscatingMapWithNonEnabledOption) { + OptimizeOptions options; + Obfuscator obfuscator(options); + ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(), + getProtocolBufferTableUnderTest().get())); + + obfuscator.WriteObfuscationMap("obfuscated_map.pb"); + + std::string pbOut; + android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */); + ASSERT_THAT(pbOut, Eq("")); +} + +} // namespace aapt diff --git a/tools/aapt2/optimize/ResourceDeduper.cpp b/tools/aapt2/optimize/ResourceDeduper.cpp index 0278b439cfae..c71cb4c21020 100644 --- a/tools/aapt2/optimize/ResourceDeduper.cpp +++ b/tools/aapt2/optimize/ResourceDeduper.cpp @@ -77,12 +77,11 @@ class DominatedKeyValueRemover : public DominatorTree::BottomUpVisitor { } } if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note( - DiagMessage(node_value->value->GetSource()) - << "removing dominated duplicate resource with name \"" - << entry_->name << "\""); - context_->GetDiagnostics()->Note( - DiagMessage(parent_value->value->GetSource()) << "dominated here"); + context_->GetDiagnostics()->Note(android::DiagMessage(node_value->value->GetSource()) + << "removing dominated duplicate resource with name \"" + << entry_->name << "\""); + context_->GetDiagnostics()->Note(android::DiagMessage(parent_value->value->GetSource()) + << "dominated here"); } node_value->value = {}; } diff --git a/tools/aapt2/optimize/ResourceFilter.cpp b/tools/aapt2/optimize/ResourceFilter.cpp index 08c045bf68f7..db84b66ecd2d 100644 --- a/tools/aapt2/optimize/ResourceFilter.cpp +++ b/tools/aapt2/optimize/ResourceFilter.cpp @@ -28,7 +28,7 @@ bool ResourceFilter::Consume(IAaptContext* context, ResourceTable* table) { for (auto& package : table->packages) { for (auto& type : package->types) { for (auto it = type->entries.begin(); it != type->entries.end(); ) { - ResourceName resource = ResourceName({}, type->type, (*it)->name); + ResourceName resource = ResourceName({}, type->named_type, (*it)->name); if (exclude_list_.find(resource) != exclude_list_.end()) { it = type->entries.erase(it); } else { diff --git a/tools/aapt2/optimize/ResourcePathShortener.cpp b/tools/aapt2/optimize/ResourcePathShortener.cpp deleted file mode 100644 index 7ff9bf5aa8df..000000000000 --- a/tools/aapt2/optimize/ResourcePathShortener.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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 "optimize/ResourcePathShortener.h" - -#include <set> -#include <unordered_set> - -#include "androidfw/StringPiece.h" - -#include "ResourceTable.h" -#include "ValueVisitor.h" -#include "util/Util.h" - - -static const std::string base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789-_"; - -namespace aapt { - -ResourcePathShortener::ResourcePathShortener( - std::map<std::string, std::string>& path_map_out) - : path_map_(path_map_out) { -} - -std::string ShortenFileName(const android::StringPiece& file_path, int output_length) { - std::size_t hash_num = std::hash<android::StringPiece>{}(file_path); - std::string result = ""; - // Convert to (modified) base64 so that it is a proper file path. - for (int i = 0; i < output_length; i++) { - uint8_t sextet = hash_num & 0x3f; - hash_num >>= 6; - result += base64_chars[sextet]; - } - return result; -} - - -// Return the optimal hash length such that at most 10% of resources collide in -// their shortened path. -// Reference: http://matt.might.net/articles/counting-hash-collisions/ -int OptimalShortenedLength(int num_resources) { - if (num_resources > 4000) { - return 3; - } else { - return 2; - } -} - -std::string GetShortenedPath(const android::StringPiece& shortened_filename, - const android::StringPiece& extension, int collision_count) { - std::string shortened_path = "res/" + shortened_filename.to_string(); - if (collision_count > 0) { - shortened_path += std::to_string(collision_count); - } - shortened_path += extension; - return shortened_path; -} - -// implement custom comparator of FileReference pointers so as to use the -// underlying filepath as key rather than the integer address. This is to ensure -// determinism of output for colliding files. -struct PathComparator { - bool operator() (const FileReference* lhs, const FileReference* rhs) const { - return lhs->path->compare(*rhs->path); - } -}; - -bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table) { - // used to detect collisions - std::unordered_set<std::string> shortened_paths; - std::set<FileReference*, PathComparator> file_refs; - for (auto& package : table->packages) { - for (auto& type : package->types) { - for (auto& entry : type->entries) { - for (auto& config_value : entry->values) { - FileReference* file_ref = ValueCast<FileReference>(config_value->value.get()); - if (file_ref) { - file_refs.insert(file_ref); - } - } - } - } - } - int num_chars = OptimalShortenedLength(file_refs.size()); - for (auto& file_ref : file_refs) { - android::StringPiece res_subdir, actual_filename, extension; - util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension); - - // Android detects ColorStateLists via pathname, skip res/color* - if (util::StartsWith(res_subdir, "res/color")) - continue; - - std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars); - int collision_count = 0; - std::string shortened_path = GetShortenedPath(shortened_filename, extension, collision_count); - while (shortened_paths.find(shortened_path) != shortened_paths.end()) { - collision_count++; - shortened_path = GetShortenedPath(shortened_filename, extension, collision_count); - } - shortened_paths.insert(shortened_path); - path_map_.insert({*file_ref->path, shortened_path}); - file_ref->path = table->string_pool.MakeRef(shortened_path, file_ref->path.GetContext()); - } - return true; -} - -} // namespace aapt diff --git a/tools/aapt2/optimize/ResourcePathShortener.h b/tools/aapt2/optimize/ResourcePathShortener.h deleted file mode 100644 index f1074ef083bd..000000000000 --- a/tools/aapt2/optimize/ResourcePathShortener.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H -#define AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H - -#include <map> - -#include "android-base/macros.h" - -#include "process/IResourceTableConsumer.h" - -namespace aapt { - -class ResourceTable; - -// Maps resources in the apk to shortened paths. -class ResourcePathShortener : public IResourceTableConsumer { - public: - explicit ResourcePathShortener(std::map<std::string, std::string>& path_map_out); - - bool Consume(IAaptContext* context, ResourceTable* table) override; - - private: - DISALLOW_COPY_AND_ASSIGN(ResourcePathShortener); - std::map<std::string, std::string>& path_map_; -}; - -} // namespace aapt - -#endif // AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H diff --git a/tools/aapt2/optimize/ResourcePathShortener_test.cpp b/tools/aapt2/optimize/ResourcePathShortener_test.cpp deleted file mode 100644 index f5a02be0ea5e..000000000000 --- a/tools/aapt2/optimize/ResourcePathShortener_test.cpp +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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 "optimize/ResourcePathShortener.h" - -#include "ResourceTable.h" -#include "test/Test.h" - -using ::aapt::test::GetValue; -using ::testing::Not; -using ::testing::NotNull; -using ::testing::Eq; - -android::StringPiece GetExtension(android::StringPiece path) { - auto iter = std::find(path.begin(), path.end(), '.'); - return android::StringPiece(iter, path.end() - iter); -} - -void FillTable(aapt::test::ResourceTableBuilder& builder, int start, int end) { - for (int i=start; i<end; i++) { - builder.AddFileReference( - "android:drawable/xmlfile" + std::to_string(i), - "res/drawable/xmlfile" + std::to_string(i) + ".xml"); - } -} - -namespace aapt { - -TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - - std::unique_ptr<ResourceTable> table = - test::ResourceTableBuilder() - .AddFileReference("android:drawable/xmlfile", "res/drawables/xmlfile.xml") - .AddFileReference("android:drawable/xmlfile2", "res/drawables/xmlfile2.xml") - .AddString("android:string/string", "res/should/still/be/the/same.png") - .Build(); - - std::map<std::string, std::string> path_map; - ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get())); - - // Expect that the path map is populated - ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end()))); - ASSERT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end()))); - - // The file paths were changed - EXPECT_THAT(path_map.at("res/drawables/xmlfile.xml"), Not(Eq("res/drawables/xmlfile.xml"))); - EXPECT_THAT(path_map.at("res/drawables/xmlfile2.xml"), Not(Eq("res/drawables/xmlfile2.xml"))); - - // Different file paths should remain different - EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], - Not(Eq(path_map["res/drawables/xmlfile2.xml"]))); - - FileReference* ref = - GetValue<FileReference>(table.get(), "android:drawable/xmlfile"); - ASSERT_THAT(ref, NotNull()); - // The map correctly points to the new location of the file - EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], Eq(*ref->path)); - - // Strings should not be affected, only file paths - EXPECT_THAT( - *GetValue<String>(table.get(), "android:string/string")->value, - Eq("res/should/still/be/the/same.png")); - EXPECT_THAT(path_map.find("res/should/still/be/the/same.png"), Eq(path_map.end())); -} - -TEST(ResourcePathShortenerTest, SkipColorFileRefPaths) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - - std::unique_ptr<ResourceTable> table = - test::ResourceTableBuilder() - .AddFileReference("android:color/colorlist", "res/color/colorlist.xml") - .AddFileReference("android:color/colorlist", - "res/color-mdp-v21/colorlist.xml", - test::ParseConfigOrDie("mdp-v21")) - .Build(); - - std::map<std::string, std::string> path_map; - ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get())); - - // Expect that the path map to not contain the ColorStateList - ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end())); - ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end())); -} - -TEST(ResourcePathShortenerTest, KeepExtensions) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - - std::string original_xml_path = "res/drawable/xmlfile.xml"; - std::string original_png_path = "res/drawable/pngfile.png"; - - std::unique_ptr<ResourceTable> table = - test::ResourceTableBuilder() - .AddFileReference("android:color/xmlfile", original_xml_path) - .AddFileReference("android:color/pngfile", original_png_path) - .Build(); - - std::map<std::string, std::string> path_map; - ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get())); - - // Expect that the path map is populated - ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end()))); - ASSERT_THAT(path_map.find("res/drawable/pngfile.png"), Not(Eq(path_map.end()))); - - auto shortend_xml_path = path_map[original_xml_path]; - auto shortend_png_path = path_map[original_png_path]; - - EXPECT_THAT(GetExtension(path_map[original_xml_path]), Eq(android::StringPiece(".xml"))); - EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png"))); -} - -TEST(ResourcePathShortenerTest, DeterministicallyHandleCollisions) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - - // 4000 resources is the limit at which the hash space is expanded to 3 - // letters to reduce collisions, we want as many collisions as possible thus - // N-1. - const auto kNumResources = 3999; - const auto kNumTries = 5; - - test::ResourceTableBuilder builder1; - FillTable(builder1, 0, kNumResources); - std::unique_ptr<ResourceTable> table1 = builder1.Build(); - std::map<std::string, std::string> expected_mapping; - ASSERT_TRUE(ResourcePathShortener(expected_mapping).Consume(context.get(), table1.get())); - - // We are trying to ensure lack of non-determinism, it is not simple to prove - // a negative, thus we must try the test a few times so that the test itself - // is non-flaky. Basically create the pathmap 5 times from the same set of - // resources but a different order of addition and then ensure they are always - // mapped to the same short path. - for (int i=0; i<kNumTries; i++) { - test::ResourceTableBuilder builder2; - // This loop adds resources to the resource table in the range of - // [0:kNumResources). Adding the file references in different order makes - // non-determinism more likely to surface. Thus we add resources - // [start_index:kNumResources) first then [0:start_index). We also use a - // different start_index each run. - int start_index = (kNumResources/kNumTries)*i; - FillTable(builder2, start_index, kNumResources); - FillTable(builder2, 0, start_index); - std::unique_ptr<ResourceTable> table2 = builder2.Build(); - - std::map<std::string, std::string> actual_mapping; - ASSERT_TRUE(ResourcePathShortener(actual_mapping).Consume(context.get(), table2.get())); - - for (auto& item : actual_mapping) { - ASSERT_THAT(expected_mapping[item.first], Eq(item.second)); - } - } -} - -} // namespace aapt diff --git a/tools/aapt2/optimize/VersionCollapser_test.cpp b/tools/aapt2/optimize/VersionCollapser_test.cpp index aa0d0c095f57..18dcd6bace77 100644 --- a/tools/aapt2/optimize/VersionCollapser_test.cpp +++ b/tools/aapt2/optimize/VersionCollapser_test.cpp @@ -23,7 +23,7 @@ using android::StringPiece; namespace aapt { static std::unique_ptr<ResourceTable> BuildTableWithConfigs( - const StringPiece& name, std::initializer_list<std::string> list) { + StringPiece name, std::initializer_list<std::string> list) { test::ResourceTableBuilder builder; for (const std::string& item : list) { builder.AddSimple(name, test::ParseConfigOrDie(item)); diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h index 9c4b323db433..e41e45a29b3e 100644 --- a/tools/aapt2/process/IResourceTableConsumer.h +++ b/tools/aapt2/process/IResourceTableConsumer.h @@ -22,11 +22,11 @@ #include <set> #include <sstream> -#include "Diagnostics.h" #include "NameMangler.h" #include "Resource.h" #include "ResourceValues.h" -#include "Source.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/Source.h" namespace aapt { @@ -45,7 +45,7 @@ struct IAaptContext { virtual PackageType GetPackageType() = 0; virtual SymbolTable* GetExternalSymbols() = 0; - virtual IDiagnostics* GetDiagnostics() = 0; + virtual android::IDiagnostics* GetDiagnostics() = 0; virtual const std::string& GetCompilationPackage() = 0; virtual uint8_t GetPackageId() = 0; virtual NameMangler* GetNameMangler() = 0; diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 92b45c397eed..d78baf9ffeb4 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -218,17 +218,11 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName( return symbol; } -bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) { +bool AssetManagerSymbolSource::AddAssetPath(StringPiece path) { TRACE_CALL(); - if (std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path.data())) { + if (auto apk = ApkAssets::Load(path.data())) { apk_assets_.push_back(std::move(apk)); - - std::vector<const ApkAssets*> apk_assets; - for (const std::unique_ptr<const ApkAssets>& apk_asset : apk_assets_) { - apk_assets.push_back(apk_asset.get()); - } - - asset_manager_.SetApkAssets(apk_assets); + asset_manager_.SetApkAssets(apk_assets_); return true; } return false; @@ -251,7 +245,7 @@ bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId, return true; } - for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) { + for (auto&& assets : apk_assets_) { for (const std::unique_ptr<const android::LoadedPackage>& loaded_package : assets->GetLoadedArsc()->GetPackages()) { if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) { @@ -266,10 +260,11 @@ bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId, static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable( android::AssetManager2& am, ResourceId id) { using namespace android; - if (am.GetApkAssets().empty()) { + if (am.GetApkAssetsCount() == 0) { return {}; } + auto op = am.StartOperation(); auto bag_result = am.GetBag(id.id); if (!bag_result.has_value()) { return nullptr; diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index c17837c224ab..36eb0bab6046 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -192,7 +192,7 @@ class AssetManagerSymbolSource : public ISymbolSource { public: AssetManagerSymbolSource() = default; - bool AddAssetPath(const android::StringPiece& path); + bool AddAssetPath(android::StringPiece path); std::map<size_t, std::string> GetAssignedPackageIds() const; bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const; @@ -207,8 +207,8 @@ class AssetManagerSymbolSource : public ISymbolSource { } private: + std::vector<android::AssetManager2::ApkAssetsPtr> apk_assets_; android::AssetManager2 asset_manager_; - std::vector<std::unique_ptr<const android::ApkAssets>> apk_assets_; DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource); }; diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp index ddc210189793..e576709a776d 100644 --- a/tools/aapt2/process/SymbolTable_test.cpp +++ b/tools/aapt2/process/SymbolTable_test.cpp @@ -17,9 +17,9 @@ #include "process/SymbolTable.h" #include "SdkConstants.h" +#include "androidfw/BigBuffer.h" #include "format/binary/TableFlattener.h" #include "test/Test.h" -#include "util/BigBuffer.h" using ::testing::Eq; using ::testing::IsNull; diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp index 116b2ab9aa98..58f2b1dcdf87 100644 --- a/tools/aapt2/split/TableSplitter.cpp +++ b/tools/aapt2/split/TableSplitter.cpp @@ -160,17 +160,15 @@ bool TableSplitter::VerifySplitConstraints(IAaptContext* context) { for (size_t i = 0; i < split_constraints_.size(); i++) { if (split_constraints_[i].configs.size() == 0) { // For now, treat this as a warning. We may consider aborting processing. - context->GetDiagnostics()->Warn(DiagMessage() - << "no configurations for constraint '" - << split_constraints_[i].name << "'"); + context->GetDiagnostics()->Warn(android::DiagMessage() << "no configurations for constraint '" + << split_constraints_[i].name << "'"); } for (size_t j = i + 1; j < split_constraints_.size(); j++) { for (const ConfigDescription& config : split_constraints_[i].configs) { if (split_constraints_[j].configs.find(config) != split_constraints_[j].configs.end()) { - context->GetDiagnostics()->Error(DiagMessage() - << "config '" << config - << "' appears in multiple splits, " - << "target split ambiguous"); + context->GetDiagnostics()->Error( + android::DiagMessage() << "config '" << config << "' appears in multiple splits, " + << "target split ambiguous"); error = true; } } @@ -189,7 +187,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { } for (auto& type : pkg->types) { - if (type->type == ResourceType::kMipmap) { + if (type->named_type.type == ResourceType::kMipmap) { // Always keep mipmaps. continue; } @@ -241,7 +239,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { // Create the same resource structure in the split. We do this lazily because we might // not have actual values for each type/entry. ResourceTablePackage* split_pkg = split_table->FindPackage(pkg->name); - ResourceTableType* split_type = split_pkg->FindOrCreateType(type->type); + ResourceTableType* split_type = split_pkg->FindOrCreateType(type->named_type); split_type->visibility_level = type->visibility_level; ResourceEntry* split_entry = split_type->FindOrCreateEntry(entry->name); diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp index 23331de02df7..65f63dc68e54 100644 --- a/tools/aapt2/test/Builders.cpp +++ b/tools/aapt2/test/Builders.cpp @@ -16,9 +16,9 @@ #include "test/Builders.h" +#include "Diagnostics.h" #include "android-base/logging.h" #include "androidfw/StringPiece.h" - #include "io/StringStream.h" #include "test/Common.h" #include "util/Util.h" @@ -34,61 +34,53 @@ using ::android::StringPiece; namespace aapt { namespace test { -ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name, - const ResourceId& id) { +ResourceTableBuilder& ResourceTableBuilder::AddSimple(StringPiece name, const ResourceId& id) { return AddValue(name, id, util::make_unique<Id>()); } -ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name, +ResourceTableBuilder& ResourceTableBuilder::AddSimple(StringPiece name, const ConfigDescription& config, const ResourceId& id) { return AddValue(name, config, id, util::make_unique<Id>()); } -ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name, - const StringPiece& ref) { +ResourceTableBuilder& ResourceTableBuilder::AddReference(StringPiece name, StringPiece ref) { return AddReference(name, {}, ref); } -ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name, - const ResourceId& id, - const StringPiece& ref) { +ResourceTableBuilder& ResourceTableBuilder::AddReference(StringPiece name, const ResourceId& id, + StringPiece ref) { return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref))); } -ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, - const StringPiece& str) { +ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, StringPiece str) { return AddString(name, {}, str); } -ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id, - const StringPiece& str) { +ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, const ResourceId& id, + StringPiece str) { return AddValue(name, id, util::make_unique<String>(table_->string_pool.MakeRef(str))); } -ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id, +ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, const ResourceId& id, const ConfigDescription& config, - const StringPiece& str) { + StringPiece str) { return AddValue(name, config, id, util::make_unique<String>(table_->string_pool.MakeRef(str))); } -ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, - const StringPiece& path, +ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, StringPiece path, io::IFile* file) { return AddFileReference(name, {}, path, file); } -ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, - const ResourceId& id, - const StringPiece& path, - io::IFile* file) { +ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, const ResourceId& id, + StringPiece path, io::IFile* file) { auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path)); file_ref->file = file; return AddValue(name, id, std::move(file_ref)); } -ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, - const StringPiece& path, +ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, StringPiece path, const ConfigDescription& config, io::IFile* file) { auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path)); @@ -96,17 +88,17 @@ ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& return AddValue(name, config, {}, std::move(file_ref)); } -ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, +ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name, std::unique_ptr<Value> value) { return AddValue(name, {}, std::move(value)); } -ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, const ResourceId& id, +ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name, const ResourceId& id, std::unique_ptr<Value> value) { return AddValue(name, {}, id, std::move(value)); } -ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, +ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name, const ConfigDescription& config, const ResourceId& id, std::unique_ptr<Value> value) { @@ -121,8 +113,7 @@ ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, return *this; } -ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name, - const ResourceId& id, +ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(StringPiece name, const ResourceId& id, Visibility::Level level, bool allow_new) { ResourceName res_name = ParseNameOrDie(name); @@ -136,9 +127,8 @@ ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& na return *this; } -ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& name, +ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(StringPiece name, const OverlayableItem& overlayable) { - ResourceName res_name = ParseNameOrDie(name); CHECK(table_->AddResource( NewResourceBuilder(res_name).SetOverlayable(overlayable).SetAllowMangled(true).Build(), @@ -151,7 +141,7 @@ ResourceTableBuilder& ResourceTableBuilder::Add(NewResource&& res) { return *this; } -StringPool* ResourceTableBuilder::string_pool() { +android::StringPool* ResourceTableBuilder::string_pool() { return &table_->string_pool; } @@ -159,8 +149,7 @@ std::unique_ptr<ResourceTable> ResourceTableBuilder::Build() { return std::move(table_); } -std::unique_ptr<Reference> BuildReference(const StringPiece& ref, - const std::optional<ResourceId>& id) { +std::unique_ptr<Reference> BuildReference(StringPiece ref, const std::optional<ResourceId>& id) { std::unique_ptr<Reference> reference = util::make_unique<Reference>(ParseNameOrDie(ref)); reference->id = id; return reference; @@ -188,7 +177,7 @@ AttributeBuilder& AttributeBuilder::SetWeak(bool weak) { return *this; } -AttributeBuilder& AttributeBuilder::AddItem(const StringPiece& name, uint32_t value) { +AttributeBuilder& AttributeBuilder::AddItem(StringPiece name, uint32_t value) { attr_->symbols.push_back( Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value}); return *this; @@ -198,17 +187,17 @@ std::unique_ptr<Attribute> AttributeBuilder::Build() { return std::move(attr_); } -StyleBuilder& StyleBuilder::SetParent(const StringPiece& str) { +StyleBuilder& StyleBuilder::SetParent(StringPiece str) { style_->parent = Reference(ParseNameOrDie(str)); return *this; } -StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, std::unique_ptr<Item> value) { +StyleBuilder& StyleBuilder::AddItem(StringPiece str, std::unique_ptr<Item> value) { style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)}); return *this; } -StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, const ResourceId& id, +StyleBuilder& StyleBuilder::AddItem(StringPiece str, const ResourceId& id, std::unique_ptr<Item> value) { AddItem(str, std::move(value)); style_->entries.back().key.id = id; @@ -219,8 +208,7 @@ std::unique_ptr<Style> StyleBuilder::Build() { return std::move(style_); } -StyleableBuilder& StyleableBuilder::AddItem(const StringPiece& str, - const std::optional<ResourceId>& id) { +StyleableBuilder& StyleableBuilder::AddItem(StringPiece str, const std::optional<ResourceId>& id) { styleable_->entries.push_back(Reference(ParseNameOrDie(str))); styleable_->entries.back().id = id; return *this; @@ -230,18 +218,18 @@ std::unique_ptr<Styleable> StyleableBuilder::Build() { return std::move(styleable_); } -std::unique_ptr<xml::XmlResource> BuildXmlDom(const StringPiece& str) { +std::unique_ptr<xml::XmlResource> BuildXmlDom(StringPiece str) { std::string input = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; input.append(str.data(), str.size()); StringInputStream in(input); StdErrDiagnostics diag; - std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, Source("test.xml")); + std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, android::Source("test.xml")); CHECK(doc != nullptr && doc->root != nullptr) << "failed to parse inline XML string"; return doc; } std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context, - const StringPiece& str) { + StringPiece str) { std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str); doc->file.name.package = context->GetCompilationPackage(); return doc; diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 55778aea40af..098535d8526f 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -38,44 +38,39 @@ class ResourceTableBuilder { public: ResourceTableBuilder() = default; - ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {}); - ResourceTableBuilder& AddSimple(const android::StringPiece& name, + ResourceTableBuilder& AddSimple(android::StringPiece name, const ResourceId& id = {}); + ResourceTableBuilder& AddSimple(android::StringPiece name, const android::ConfigDescription& config, const ResourceId& id = {}); - ResourceTableBuilder& AddReference(const android::StringPiece& name, - const android::StringPiece& ref); - ResourceTableBuilder& AddReference(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& ref); - ResourceTableBuilder& AddString(const android::StringPiece& name, - const android::StringPiece& str); - ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& str); - ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id, + ResourceTableBuilder& AddReference(android::StringPiece name, android::StringPiece ref); + ResourceTableBuilder& AddReference(android::StringPiece name, const ResourceId& id, + android::StringPiece ref); + ResourceTableBuilder& AddString(android::StringPiece name, android::StringPiece str); + ResourceTableBuilder& AddString(android::StringPiece name, const ResourceId& id, + android::StringPiece str); + ResourceTableBuilder& AddString(android::StringPiece name, const ResourceId& id, const android::ConfigDescription& config, - const android::StringPiece& str); - ResourceTableBuilder& AddFileReference(const android::StringPiece& name, - const android::StringPiece& path, + android::StringPiece str); + ResourceTableBuilder& AddFileReference(android::StringPiece name, android::StringPiece path, io::IFile* file = nullptr); - ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& path, - io::IFile* file = nullptr); - ResourceTableBuilder& AddFileReference(const android::StringPiece& name, - const android::StringPiece& path, + ResourceTableBuilder& AddFileReference(android::StringPiece name, const ResourceId& id, + android::StringPiece path, io::IFile* file = nullptr); + ResourceTableBuilder& AddFileReference(android::StringPiece name, android::StringPiece path, const android::ConfigDescription& config, io::IFile* file = nullptr); - ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value); - ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id, + ResourceTableBuilder& AddValue(android::StringPiece name, std::unique_ptr<Value> value); + ResourceTableBuilder& AddValue(android::StringPiece name, const ResourceId& id, + std::unique_ptr<Value> value); + ResourceTableBuilder& AddValue(android::StringPiece name, + const android::ConfigDescription& config, const ResourceId& id, std::unique_ptr<Value> value); - ResourceTableBuilder& AddValue(const android::StringPiece& name, - const android::ConfigDescription& config, - const ResourceId& id, std::unique_ptr<Value> value); - ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id, + ResourceTableBuilder& SetSymbolState(android::StringPiece name, const ResourceId& id, Visibility::Level level, bool allow_new = false); - ResourceTableBuilder& SetOverlayable(const android::StringPiece& name, + ResourceTableBuilder& SetOverlayable(android::StringPiece name, const OverlayableItem& overlayable); ResourceTableBuilder& Add(NewResource&& res); - StringPool* string_pool(); + android::StringPool* string_pool(); std::unique_ptr<ResourceTable> Build(); private: @@ -84,7 +79,7 @@ class ResourceTableBuilder { std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>(); }; -std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref, +std::unique_ptr<Reference> BuildReference(android::StringPiece ref, const std::optional<ResourceId>& id = {}); std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data); @@ -97,11 +92,11 @@ class ValueBuilder { template <typename... Args> ValueBuilder& SetSource(Args&&... args) { - value_->SetSource(Source{std::forward<Args>(args)...}); + value_->SetSource(android::Source{std::forward<Args>(args)...}); return *this; } - ValueBuilder& SetComment(const android::StringPiece& str) { + ValueBuilder& SetComment(android::StringPiece str) { value_->SetComment(str); return *this; } @@ -121,7 +116,7 @@ class AttributeBuilder { AttributeBuilder(); AttributeBuilder& SetTypeMask(uint32_t typeMask); AttributeBuilder& SetWeak(bool weak); - AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value); + AttributeBuilder& AddItem(android::StringPiece name, uint32_t value); std::unique_ptr<Attribute> Build(); private: @@ -133,9 +128,9 @@ class AttributeBuilder { class StyleBuilder { public: StyleBuilder() = default; - StyleBuilder& SetParent(const android::StringPiece& str); - StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value); - StyleBuilder& AddItem(const android::StringPiece& str, const ResourceId& id, + StyleBuilder& SetParent(android::StringPiece str); + StyleBuilder& AddItem(android::StringPiece str, std::unique_ptr<Item> value); + StyleBuilder& AddItem(android::StringPiece str, const ResourceId& id, std::unique_ptr<Item> value); std::unique_ptr<Style> Build(); @@ -148,8 +143,7 @@ class StyleBuilder { class StyleableBuilder { public: StyleableBuilder() = default; - StyleableBuilder& AddItem(const android::StringPiece& str, - const std::optional<ResourceId>& id = {}); + StyleableBuilder& AddItem(android::StringPiece str, const std::optional<ResourceId>& id = {}); std::unique_ptr<Styleable> Build(); private: @@ -158,9 +152,9 @@ class StyleableBuilder { std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>(); }; -std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str); +std::unique_ptr<xml::XmlResource> BuildXmlDom(android::StringPiece str); std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context, - const android::StringPiece& str); + android::StringPiece str); class ArtifactBuilder { public: @@ -257,7 +251,11 @@ class ConfigDescriptionBuilder { return *this; } ConfigDescriptionBuilder& setInputPad0(uint8_t inputPad0) { - config_.inputPad0 = inputPad0; + config_.inputFieldPad0 = inputPad0; + return *this; + } + ConfigDescriptionBuilder& setGrammaticalInflection(uint8_t value) { + config_.grammaticalInflection = value; return *this; } ConfigDescriptionBuilder& setScreenWidth(uint16_t screenWidth) { diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp index e029d025b366..cdf245341844 100644 --- a/tools/aapt2/test/Common.cpp +++ b/tools/aapt2/test/Common.cpp @@ -21,8 +21,8 @@ using android::ConfigDescription; namespace aapt { namespace test { -struct TestDiagnosticsImpl : public IDiagnostics { - void Log(Level level, DiagMessageActual& actual_msg) override { +struct TestDiagnosticsImpl : public android::IDiagnostics { + void Log(Level level, android::DiagMessageActual& actual_msg) override { switch (level) { case Level::Note: return; @@ -38,16 +38,15 @@ struct TestDiagnosticsImpl : public IDiagnostics { } }; -IDiagnostics* GetDiagnostics() { +android::IDiagnostics* GetDiagnostics() { static TestDiagnosticsImpl diag; return &diag; } template <> -Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, - const android::StringPiece& res_name, +Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, android::StringPiece res_name, const ConfigDescription& config, - const android::StringPiece& product) { + android::StringPiece product) { std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name)); if (result) { ResourceConfigValue* config_value = result.value().entry->FindValue(config, product); diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 7006964d6f88..83a0f3f3f652 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -37,24 +37,24 @@ namespace aapt { namespace test { -IDiagnostics* GetDiagnostics(); +android::IDiagnostics* GetDiagnostics(); -inline ResourceName ParseNameOrDie(const android::StringPiece& str) { +inline ResourceName ParseNameOrDie(android::StringPiece str) { ResourceNameRef ref; CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name: " << str; return ref.ToResourceName(); } -inline android::ConfigDescription ParseConfigOrDie(const android::StringPiece& str) { - android::ConfigDescription config; +inline android::ConfigDescription ParseConfigOrDie(android::StringPiece str) { + android::ConfigDescription config; CHECK(android::ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str; return config; } template <typename T = Value> -T* GetValueForConfigAndProduct(ResourceTable* table, const android::StringPiece& res_name, +T* GetValueForConfigAndProduct(ResourceTable* table, android::StringPiece res_name, const android::ConfigDescription& config, - const android::StringPiece& product) { + android::StringPiece product) { std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name)); if (result) { ResourceConfigValue* config_value = result.value().entry->FindValue(config, product); @@ -66,25 +66,25 @@ T* GetValueForConfigAndProduct(ResourceTable* table, const android::StringPiece& } template <> -Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, - const android::StringPiece& res_name, +Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, android::StringPiece res_name, const android::ConfigDescription& config, - const android::StringPiece& product); + android::StringPiece product); template <typename T = Value> -T* GetValueForConfig(ResourceTable* table, const android::StringPiece& res_name, +T* GetValueForConfig(ResourceTable* table, android::StringPiece res_name, const android::ConfigDescription& config) { return GetValueForConfigAndProduct<T>(table, res_name, config, {}); } template <typename T = Value> -T* GetValue(ResourceTable* table, const android::StringPiece& res_name) { +T* GetValue(ResourceTable* table, android::StringPiece res_name) { return GetValueForConfig<T>(table, res_name, {}); } class TestFile : public io::IFile { public: - explicit TestFile(const android::StringPiece& path) : source_(path) {} + explicit TestFile(android::StringPiece path) : source_(path) { + } std::unique_ptr<io::IData> OpenAsData() override { return {}; @@ -94,14 +94,14 @@ class TestFile : public io::IFile { return OpenAsData(); } - const Source& GetSource() const override { + const android::Source& GetSource() const override { return source_; } private: DISALLOW_COPY_AND_ASSIGN(TestFile); - Source source_; + android::Source source_; }; } // namespace test diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h index e1b8dd5687ff..c5331fb87381 100644 --- a/tools/aapt2/test/Context.h +++ b/tools/aapt2/test/Context.h @@ -19,10 +19,10 @@ #include <list> +#include "Diagnostics.h" +#include "NameMangler.h" #include "android-base/logging.h" #include "android-base/macros.h" - -#include "NameMangler.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" #include "test/Common.h" @@ -43,7 +43,7 @@ class Context : public IAaptContext { return &symbols_; } - IDiagnostics* GetDiagnostics() override { + android::IDiagnostics* GetDiagnostics() override { return &diagnostics_; } @@ -52,8 +52,8 @@ class Context : public IAaptContext { return compilation_package_.value(); } - void SetCompilationPackage(const android::StringPiece& package) { - compilation_package_ = package.to_string(); + void SetCompilationPackage(android::StringPiece package) { + compilation_package_ = std::string(package); } uint8_t GetPackageId() override { @@ -111,8 +111,8 @@ class ContextBuilder { return *this; } - ContextBuilder& SetCompilationPackage(const android::StringPiece& package) { - context_->compilation_package_ = package.to_string(); + ContextBuilder& SetCompilationPackage(android::StringPiece package) { + context_->compilation_package_ = std::string(package); return *this; } @@ -149,7 +149,7 @@ class ContextBuilder { class StaticSymbolSourceBuilder { public: - StaticSymbolSourceBuilder& AddPublicSymbol(const android::StringPiece& name, ResourceId id, + StaticSymbolSourceBuilder& AddPublicSymbol(android::StringPiece name, ResourceId id, std::unique_ptr<Attribute> attr = {}) { std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(id, std::move(attr), true); @@ -159,7 +159,7 @@ class StaticSymbolSourceBuilder { return *this; } - StaticSymbolSourceBuilder& AddSymbol(const android::StringPiece& name, ResourceId id, + StaticSymbolSourceBuilder& AddSymbol(android::StringPiece name, ResourceId id, std::unique_ptr<Attribute> attr = {}) { std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(id, std::move(attr), false); diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp index ddc1853ca13c..428372f31d0d 100644 --- a/tools/aapt2/test/Fixture.cpp +++ b/tools/aapt2/test/Fixture.cpp @@ -16,16 +16,16 @@ #include "test/Fixture.h" -#include <dirent.h> - #include <android-base/errors.h> #include <android-base/file.h> #include <android-base/stringprintf.h> #include <android-base/utf8.h> #include <androidfw/StringPiece.h> +#include <dirent.h> #include <gmock/gmock.h> #include <gtest/gtest.h> +#include "Diagnostics.h" #include "cmd/Compile.h" #include "cmd/Link.h" #include "io/FileStream.h" @@ -38,11 +38,12 @@ namespace aapt { const char* CommandTestFixture::kDefaultPackageName = "com.aapt.command.test"; -void ClearDirectory(const android::StringPiece& path) { - const std::string root_dir = path.to_string(); +void ClearDirectory(android::StringPiece path) { + const std::string root_dir(path); std::unique_ptr<DIR, decltype(closedir)*> dir(opendir(root_dir.data()), closedir); if (!dir) { - StdErrDiagnostics().Error(DiagMessage() << android::base::SystemErrorCodeToString(errno)); + StdErrDiagnostics().Error(android::DiagMessage() + << android::base::SystemErrorCodeToString(errno)); return; } @@ -90,38 +91,38 @@ void TestDirectoryFixture::WriteFile(const std::string& path, const std::string& } bool CommandTestFixture::CompileFile(const std::string& path, const std::string& contents, - const android::StringPiece& out_dir, IDiagnostics* diag) { + android::StringPiece out_dir, android::IDiagnostics* diag) { WriteFile(path, contents); CHECK(file::mkdirs(out_dir.data())); return CompileCommand(diag).Execute({path, "-o", out_dir, "-v"}, &std::cerr) == 0; } -bool CommandTestFixture::Link(const std::vector<std::string>& args, IDiagnostics* diag) { +bool CommandTestFixture::Link(const std::vector<std::string>& args, android::IDiagnostics* diag) { std::vector<android::StringPiece> link_args; for(const std::string& arg : args) { link_args.emplace_back(arg); } // Link against the android SDK - std::string android_sdk = file::BuildPath({android::base::GetExecutableDirectory(), - "integration-tests", "CommandTests", - "android-28.jar"}); + std::string android_sdk = + file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "CommandTests", + "android-33.jar"}); link_args.insert(link_args.end(), {"-I", android_sdk}); return LinkCommand(diag).Execute(link_args, &std::cerr) == 0; } -bool CommandTestFixture::Link(const std::vector<std::string>& args, - const android::StringPiece& flat_dir, IDiagnostics* diag) { +bool CommandTestFixture::Link(const std::vector<std::string>& args, android::StringPiece flat_dir, + android::IDiagnostics* diag) { std::vector<android::StringPiece> link_args; for(const std::string& arg : args) { link_args.emplace_back(arg); } // Link against the android SDK - std::string android_sdk = file::BuildPath({android::base::GetExecutableDirectory(), - "integration-tests", "CommandTests", - "android-28.jar"}); + std::string android_sdk = + file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "CommandTests", + "android-33.jar"}); link_args.insert(link_args.end(), {"-I", android_sdk}); // Add the files from the compiled resources directory to the link file arguments @@ -146,7 +147,7 @@ std::string CommandTestFixture::GetDefaultManifest(const char* package_name) { } std::unique_ptr<io::IData> CommandTestFixture::OpenFileAsData(LoadedApk* apk, - const android::StringPiece& path) { + android::StringPiece path) { return apk ->GetFileCollection() ->FindFile(path) @@ -210,7 +211,7 @@ LinkCommandBuilder& LinkCommandBuilder::AddFlag(const std::string& flag) { } LinkCommandBuilder& LinkCommandBuilder::AddCompiledResDir(const std::string& dir, - IDiagnostics* diag) { + android::IDiagnostics* diag) { if (auto files = file::FindFiles(dir, diag)) { for (std::string& compile_file : files.value()) { args_.emplace_back(file::BuildPath({dir, compile_file})); diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h index f8c4889aee3b..ba4a734e03bb 100644 --- a/tools/aapt2/test/Fixture.h +++ b/tools/aapt2/test/Fixture.h @@ -48,7 +48,7 @@ class TestDirectoryFixture : public ::testing::Test { // Retrieves the absolute path of the specified relative path in the test directory. Directories // should be separated using forward slashes ('/'), and these slashes will be translated to // backslashes when running Windows tests. - std::string GetTestPath(const android::StringPiece& path) { + std::string GetTestPath(android::StringPiece path) { std::string base = temp_dir_; for (android::StringPiece part : util::Split(path, '/')) { file::AppendPath(&base, part); @@ -73,22 +73,21 @@ class CommandTestFixture : public TestDirectoryFixture { // Wries the contents of the file to the specified path. The file is compiled and the flattened // file is written to the out directory. bool CompileFile(const std::string& path, const std::string& contents, - const android::StringPiece& flat_out_dir, IDiagnostics* diag); + android::StringPiece flat_out_dir, android::IDiagnostics* diag); // Executes the link command with the specified arguments. - bool Link(const std::vector<std::string>& args, IDiagnostics* diag); + bool Link(const std::vector<std::string>& args, android::IDiagnostics* diag); // Executes the link command with the specified arguments. The flattened files residing in the // flat directory will be added to the link command as file arguments. - bool Link(const std::vector<std::string>& args, const android::StringPiece& flat_dir, - IDiagnostics* diag); + bool Link(const std::vector<std::string>& args, android::StringPiece flat_dir, + android::IDiagnostics* diag); // Creates a minimal android manifest within the test directory and returns the file path. std::string GetDefaultManifest(const char* package_name = kDefaultPackageName); // Returns pointer to data inside APK files - std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk, - const android::StringPiece& path); + std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk, android::StringPiece path); // Asserts that loading the tree from the specified file in the apk succeeds. void AssertLoadXml(LoadedApk* apk, const io::IData* data, @@ -114,7 +113,7 @@ struct ManifestBuilder { struct LinkCommandBuilder { explicit LinkCommandBuilder(CommandTestFixture* fixture); - LinkCommandBuilder& AddCompiledResDir(const std::string& dir, IDiagnostics* diag); + LinkCommandBuilder& AddCompiledResDir(const std::string& dir, android::IDiagnostics* diag); LinkCommandBuilder& AddFlag(const std::string& flag); LinkCommandBuilder& AddParameter(const std::string& param, const std::string& value); LinkCommandBuilder& SetManifestFile(const std::string& manifest_path); diff --git a/tools/aapt2/text/Printer.cpp b/tools/aapt2/text/Printer.cpp index 243800c9385f..8e491aca794d 100644 --- a/tools/aapt2/text/Printer.cpp +++ b/tools/aapt2/text/Printer.cpp @@ -26,7 +26,7 @@ using ::android::StringPiece; namespace aapt { namespace text { -Printer& Printer::Println(const StringPiece& str) { +Printer& Printer::Println(StringPiece str) { Print(str); return Print("\n"); } @@ -35,7 +35,7 @@ Printer& Printer::Println() { return Print("\n"); } -Printer& Printer::Print(const StringPiece& str) { +Printer& Printer::Print(StringPiece str) { if (error_) { return *this; } @@ -47,7 +47,7 @@ Printer& Printer::Print(const StringPiece& str) { const auto new_line_iter = std::find(remaining_str_begin, remaining_str_end, '\n'); // We will copy the string up until the next new-line (or end of string). - const StringPiece str_to_copy = str.substr(remaining_str_begin, new_line_iter); + const StringPiece str_to_copy(remaining_str_begin, new_line_iter - remaining_str_begin); if (!str_to_copy.empty()) { if (needs_indent_) { for (int i = 0; i < indent_level_; i++) { diff --git a/tools/aapt2/text/Printer.h b/tools/aapt2/text/Printer.h index f399f8ea5e0f..f7ad98bfd981 100644 --- a/tools/aapt2/text/Printer.h +++ b/tools/aapt2/text/Printer.h @@ -31,8 +31,8 @@ class Printer { explicit Printer(::aapt::io::OutputStream* out) : out_(out) { } - Printer& Print(const ::android::StringPiece& str); - Printer& Println(const ::android::StringPiece& str); + Printer& Print(android::StringPiece str); + Printer& Println(android::StringPiece str); Printer& Println(); void Indent(); diff --git a/tools/aapt2/text/Unicode.cpp b/tools/aapt2/text/Unicode.cpp index 3735b3e841e0..5e25be3e2812 100644 --- a/tools/aapt2/text/Unicode.cpp +++ b/tools/aapt2/text/Unicode.cpp @@ -77,7 +77,7 @@ bool IsWhitespace(char32_t codepoint) { (codepoint == 0x3000); } -bool IsJavaIdentifier(const StringPiece& str) { +bool IsJavaIdentifier(StringPiece str) { Utf8Iterator iter(str); // Check the first character. @@ -99,7 +99,7 @@ bool IsJavaIdentifier(const StringPiece& str) { return true; } -bool IsValidResourceEntryName(const StringPiece& str) { +bool IsValidResourceEntryName(StringPiece str) { Utf8Iterator iter(str); // Check the first character. diff --git a/tools/aapt2/text/Unicode.h b/tools/aapt2/text/Unicode.h index 546714e9a290..ab3e82b00f08 100644 --- a/tools/aapt2/text/Unicode.h +++ b/tools/aapt2/text/Unicode.h @@ -46,11 +46,11 @@ bool IsWhitespace(char32_t codepoint); // Returns true if the UTF8 string can be used as a Java identifier. // NOTE: This does not check against the set of reserved Java keywords. -bool IsJavaIdentifier(const android::StringPiece& str); +bool IsJavaIdentifier(android::StringPiece str); // Returns true if the UTF8 string can be used as the entry name of a resource name. // This is the `entry` part of package:type/entry. -bool IsValidResourceEntryName(const android::StringPiece& str); +bool IsValidResourceEntryName(android::StringPiece str); } // namespace text } // namespace aapt diff --git a/tools/aapt2/text/Utf8Iterator.cpp b/tools/aapt2/text/Utf8Iterator.cpp index 20b9073b9a26..0bd8a375a255 100644 --- a/tools/aapt2/text/Utf8Iterator.cpp +++ b/tools/aapt2/text/Utf8Iterator.cpp @@ -24,7 +24,7 @@ using ::android::StringPiece; namespace aapt { namespace text { -Utf8Iterator::Utf8Iterator(const StringPiece& str) +Utf8Iterator::Utf8Iterator(StringPiece str) : str_(str), current_pos_(0), next_pos_(0), current_codepoint_(0) { DoNext(); } diff --git a/tools/aapt2/text/Utf8Iterator.h b/tools/aapt2/text/Utf8Iterator.h index 9318401876d1..2bba1984a8ce 100644 --- a/tools/aapt2/text/Utf8Iterator.h +++ b/tools/aapt2/text/Utf8Iterator.h @@ -25,7 +25,7 @@ namespace text { class Utf8Iterator { public: - explicit Utf8Iterator(const android::StringPiece& str); + explicit Utf8Iterator(android::StringPiece str); bool HasNext() const; diff --git a/tools/aapt2/trace/TraceBuffer.cpp b/tools/aapt2/trace/TraceBuffer.cpp index b4b31d9daf6e..da5373936306 100644 --- a/tools/aapt2/trace/TraceBuffer.cpp +++ b/tools/aapt2/trace/TraceBuffer.cpp @@ -103,7 +103,7 @@ Trace::Trace(const std::string& tag, const std::vector<android::StringPiece>& ar s << tag; s << " "; for (auto& arg : args) { - s << arg.to_string(); + s << arg; s << " "; } tracebuffer::Add(s.str(), tracebuffer::kBegin); @@ -124,7 +124,7 @@ FlushTrace::FlushTrace(const std::string& basepath, const std::string& tag, s << tag; s << " "; for (auto& arg : args) { - s << arg.to_string(); + s << arg; s << " "; } tracebuffer::Add(s.str(), tracebuffer::kBegin); diff --git a/tools/aapt2/util/BigBuffer.cpp b/tools/aapt2/util/BigBuffer.cpp deleted file mode 100644 index 75fa78915b65..000000000000 --- a/tools/aapt2/util/BigBuffer.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2015 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 "util/BigBuffer.h" - -#include <algorithm> -#include <memory> -#include <vector> - -#include "android-base/logging.h" - -namespace aapt { - -void* BigBuffer::NextBlockImpl(size_t size) { - if (!blocks_.empty()) { - Block& block = blocks_.back(); - if (block.block_size_ - block.size >= size) { - void* out_buffer = block.buffer.get() + block.size; - block.size += size; - size_ += size; - return out_buffer; - } - } - - const size_t actual_size = std::max(block_size_, size); - - Block block = {}; - - // Zero-allocate the block's buffer. - block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[actual_size]()); - CHECK(block.buffer); - - block.size = size; - block.block_size_ = actual_size; - - blocks_.push_back(std::move(block)); - size_ += size; - return blocks_.back().buffer.get(); -} - -void* BigBuffer::NextBlock(size_t* out_size) { - if (!blocks_.empty()) { - Block& block = blocks_.back(); - if (block.size != block.block_size_) { - void* out_buffer = block.buffer.get() + block.size; - size_t size = block.block_size_ - block.size; - block.size = block.block_size_; - size_ += size; - *out_size = size; - return out_buffer; - } - } - - // Zero-allocate the block's buffer. - Block block = {}; - block.buffer = std::unique_ptr<uint8_t[]>(new uint8_t[block_size_]()); - CHECK(block.buffer); - block.size = block_size_; - block.block_size_ = block_size_; - blocks_.push_back(std::move(block)); - size_ += block_size_; - *out_size = block_size_; - return blocks_.back().buffer.get(); -} - -std::string BigBuffer::to_string() const { - std::string result; - for (const Block& block : blocks_) { - result.append(block.buffer.get(), block.buffer.get() + block.size); - } - return result; -} - -} // namespace aapt diff --git a/tools/aapt2/util/BigBuffer.h b/tools/aapt2/util/BigBuffer.h deleted file mode 100644 index d4b3abce68a7..000000000000 --- a/tools/aapt2/util/BigBuffer.h +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2015 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 AAPT_BIG_BUFFER_H -#define AAPT_BIG_BUFFER_H - -#include <cstring> -#include <memory> -#include <string> -#include <type_traits> -#include <vector> - -#include "android-base/logging.h" -#include "android-base/macros.h" - -namespace aapt { - -/** - * Inspired by protobuf's ZeroCopyOutputStream, offers blocks of memory - * in which to write without knowing the full size of the entire payload. - * This is essentially a list of memory blocks. As one fills up, another - * block is allocated and appended to the end of the list. - */ -class BigBuffer { - public: - /** - * A contiguous block of allocated memory. - */ - struct Block { - /** - * Pointer to the memory. - */ - std::unique_ptr<uint8_t[]> buffer; - - /** - * Size of memory that is currently occupied. The actual - * allocation may be larger. - */ - size_t size; - - private: - friend class BigBuffer; - - /** - * The size of the memory block allocation. - */ - size_t block_size_; - }; - - typedef std::vector<Block>::const_iterator const_iterator; - - /** - * Create a BigBuffer with block allocation sizes - * of block_size. - */ - explicit BigBuffer(size_t block_size); - - BigBuffer(BigBuffer&& rhs) noexcept; - - /** - * Number of occupied bytes in all the allocated blocks. - */ - size_t size() const; - - /** - * Returns a pointer to an array of T, where T is - * a POD type. The elements are zero-initialized. - */ - template <typename T> - T* NextBlock(size_t count = 1); - - /** - * Returns the next block available and puts the size in out_count. - * This is useful for grabbing blocks where the size doesn't matter. - * Use BackUp() to give back any bytes that were not used. - */ - void* NextBlock(size_t* out_count); - - /** - * Backs up count bytes. This must only be called after NextBlock() - * and can not be larger than sizeof(T) * count of the last NextBlock() - * call. - */ - void BackUp(size_t count); - - /** - * Moves the specified BigBuffer into this one. When this method - * returns, buffer is empty. - */ - void AppendBuffer(BigBuffer&& buffer); - - /** - * Pads the block with 'bytes' bytes of zero values. - */ - void Pad(size_t bytes); - - /** - * Pads the block so that it aligns on a 4 byte boundary. - */ - void Align4(); - - size_t block_size() const; - - const_iterator begin() const; - const_iterator end() const; - - std::string to_string() const; - - private: - DISALLOW_COPY_AND_ASSIGN(BigBuffer); - - /** - * Returns a pointer to a buffer of the requested size. - * The buffer is zero-initialized. - */ - void* NextBlockImpl(size_t size); - - size_t block_size_; - size_t size_; - std::vector<Block> blocks_; -}; - -inline BigBuffer::BigBuffer(size_t block_size) - : block_size_(block_size), size_(0) {} - -inline BigBuffer::BigBuffer(BigBuffer&& rhs) noexcept - : block_size_(rhs.block_size_), - size_(rhs.size_), - blocks_(std::move(rhs.blocks_)) {} - -inline size_t BigBuffer::size() const { return size_; } - -inline size_t BigBuffer::block_size() const { return block_size_; } - -template <typename T> -inline T* BigBuffer::NextBlock(size_t count) { - static_assert(std::is_standard_layout<T>::value, - "T must be standard_layout type"); - CHECK(count != 0); - return reinterpret_cast<T*>(NextBlockImpl(sizeof(T) * count)); -} - -inline void BigBuffer::BackUp(size_t count) { - Block& block = blocks_.back(); - block.size -= count; - size_ -= count; -} - -inline void BigBuffer::AppendBuffer(BigBuffer&& buffer) { - std::move(buffer.blocks_.begin(), buffer.blocks_.end(), - std::back_inserter(blocks_)); - size_ += buffer.size_; - buffer.blocks_.clear(); - buffer.size_ = 0; -} - -inline void BigBuffer::Pad(size_t bytes) { NextBlock<char>(bytes); } - -inline void BigBuffer::Align4() { - const size_t unaligned = size_ % 4; - if (unaligned != 0) { - Pad(4 - unaligned); - } -} - -inline BigBuffer::const_iterator BigBuffer::begin() const { - return blocks_.begin(); -} - -inline BigBuffer::const_iterator BigBuffer::end() const { - return blocks_.end(); -} - -} // namespace aapt - -#endif // AAPT_BIG_BUFFER_H diff --git a/tools/aapt2/util/BigBuffer_test.cpp b/tools/aapt2/util/BigBuffer_test.cpp deleted file mode 100644 index 64dcc1dad9a2..000000000000 --- a/tools/aapt2/util/BigBuffer_test.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2015 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 "util/BigBuffer.h" - -#include "test/Test.h" - -using ::testing::NotNull; - -namespace aapt { - -TEST(BigBufferTest, AllocateSingleBlock) { - BigBuffer buffer(4); - - EXPECT_THAT(buffer.NextBlock<char>(2), NotNull()); - EXPECT_EQ(2u, buffer.size()); -} - -TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) { - BigBuffer buffer(16); - - char* b1 = buffer.NextBlock<char>(8); - EXPECT_THAT(b1, NotNull()); - - char* b2 = buffer.NextBlock<char>(4); - EXPECT_THAT(b2, NotNull()); - - EXPECT_EQ(b1 + 8, b2); -} - -TEST(BigBufferTest, AllocateExactSizeBlockIfLargerThanBlockSize) { - BigBuffer buffer(16); - - EXPECT_THAT(buffer.NextBlock<char>(32), NotNull()); - EXPECT_EQ(32u, buffer.size()); -} - -TEST(BigBufferTest, AppendAndMoveBlock) { - BigBuffer buffer(16); - - uint32_t* b1 = buffer.NextBlock<uint32_t>(); - ASSERT_THAT(b1, NotNull()); - *b1 = 33; - - { - BigBuffer buffer2(16); - b1 = buffer2.NextBlock<uint32_t>(); - ASSERT_THAT(b1, NotNull()); - *b1 = 44; - - buffer.AppendBuffer(std::move(buffer2)); - EXPECT_EQ(0u, buffer2.size()); // NOLINT - EXPECT_EQ(buffer2.begin(), buffer2.end()); - } - - EXPECT_EQ(2 * sizeof(uint32_t), buffer.size()); - - auto b = buffer.begin(); - ASSERT_NE(b, buffer.end()); - ASSERT_EQ(sizeof(uint32_t), b->size); - ASSERT_EQ(33u, *reinterpret_cast<uint32_t*>(b->buffer.get())); - ++b; - - ASSERT_NE(b, buffer.end()); - ASSERT_EQ(sizeof(uint32_t), b->size); - ASSERT_EQ(44u, *reinterpret_cast<uint32_t*>(b->buffer.get())); - ++b; - - ASSERT_EQ(b, buffer.end()); -} - -TEST(BigBufferTest, PadAndAlignProperly) { - BigBuffer buffer(16); - - ASSERT_THAT(buffer.NextBlock<char>(2), NotNull()); - ASSERT_EQ(2u, buffer.size()); - buffer.Pad(2); - ASSERT_EQ(4u, buffer.size()); - buffer.Align4(); - ASSERT_EQ(4u, buffer.size()); - buffer.Pad(2); - ASSERT_EQ(6u, buffer.size()); - buffer.Align4(); - ASSERT_EQ(8u, buffer.size()); -} - -} // namespace aapt diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp index 3285d8bafa4d..93c1b61f9a57 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -139,7 +139,7 @@ bool mkdirs(const std::string& path) { return ::android::base::utf8::mkdir(path.c_str(), mode) == 0 || errno == EEXIST; } -StringPiece GetStem(const StringPiece& path) { +StringPiece GetStem(StringPiece path) { const char* start = path.begin(); const char* end = path.end(); for (const char* current = end - 1; current != start - 1; --current) { @@ -150,7 +150,7 @@ StringPiece GetStem(const StringPiece& path) { return {}; } -StringPiece GetFilename(const StringPiece& path) { +StringPiece GetFilename(StringPiece path) { const char* end = path.end(); const char* last_dir_sep = path.begin(); for (const char* c = path.begin(); c != end; ++c) { @@ -161,7 +161,7 @@ StringPiece GetFilename(const StringPiece& path) { return StringPiece(last_dir_sep, end - last_dir_sep); } -StringPiece GetExtension(const StringPiece& path) { +StringPiece GetExtension(StringPiece path) { StringPiece filename = GetFilename(path); const char* const end = filename.end(); const char* c = std::find(filename.begin(), end, '.'); @@ -171,7 +171,7 @@ StringPiece GetExtension(const StringPiece& path) { return {}; } -bool IsHidden(const android::StringPiece& path) { +bool IsHidden(android::StringPiece path) { return util::StartsWith(GetFilename(path), "."); } @@ -193,16 +193,16 @@ std::string BuildPath(std::vector<const StringPiece>&& args) { if (args.empty()) { return ""; } - std::string out = args[0].to_string(); + std::string out{args[0]}; for (int i = 1; i < args.size(); i++) { file::AppendPath(&out, args[i]); } return out; } -std::string PackageToPath(const StringPiece& package) { +std::string PackageToPath(StringPiece package) { std::string out_path; - for (const StringPiece& part : util::Tokenize(package, '.')) { + for (StringPiece part : util::Tokenize(package, '.')) { AppendPath(&out_path, part); } return out_path; @@ -241,10 +241,10 @@ std::optional<FileMap> MmapPath(const std::string& path, std::string* out_error) return std::move(filemap); } -bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_arglist, +bool AppendArgsFromFile(StringPiece path, std::vector<std::string>* out_arglist, std::string* out_error) { std::string contents; - if (!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) { + if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) { if (out_error) { *out_error = "failed to read argument-list file"; } @@ -254,16 +254,16 @@ bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_a for (StringPiece line : util::Tokenize(contents, ' ')) { line = util::TrimWhitespace(line); if (!line.empty()) { - out_arglist->push_back(line.to_string()); + out_arglist->emplace_back(line); } } return true; } -bool AppendSetArgsFromFile(const StringPiece& path, std::unordered_set<std::string>* out_argset, +bool AppendSetArgsFromFile(StringPiece path, std::unordered_set<std::string>* out_argset, std::string* out_error) { std::string contents; - if(!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) { + if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) { if (out_error) { *out_error = "failed to read argument-list file"; } @@ -273,13 +273,13 @@ bool AppendSetArgsFromFile(const StringPiece& path, std::unordered_set<std::stri for (StringPiece line : util::Tokenize(contents, ' ')) { line = util::TrimWhitespace(line); if (!line.empty()) { - out_argset->insert(line.to_string()); + out_argset->emplace(line); } } return true; } -bool FileFilter::SetPattern(const StringPiece& pattern) { +bool FileFilter::SetPattern(StringPiece pattern) { pattern_tokens_ = util::SplitAndLowercase(pattern, ':'); return true; } @@ -333,9 +333,8 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const { if (ignore) { if (chatty) { - diag_->Warn(DiagMessage() - << "skipping " - << (type == FileType::kDirectory ? "dir '" : "file '") + diag_->Warn(android::DiagMessage() + << "skipping " << (type == FileType::kDirectory ? "dir '" : "file '") << filename << "' due to ignore pattern '" << token << "'"); } return false; @@ -344,12 +343,13 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const { return true; } -std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path, - IDiagnostics* diag, const FileFilter* filter) { - const std::string root_dir = path.to_string(); +std::optional<std::vector<std::string>> FindFiles(android::StringPiece path, + android::IDiagnostics* diag, + const FileFilter* filter) { + const auto& root_dir = path; std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir); if (!d) { - diag->Error(DiagMessage() << SystemErrorCodeToString(errno) << ": " << root_dir); + diag->Error(android::DiagMessage() << SystemErrorCodeToString(errno) << ": " << root_dir); return {}; } @@ -361,7 +361,7 @@ std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& pa } std::string file_name = entry->d_name; - std::string full_path = root_dir; + std::string full_path{root_dir}; AppendPath(&full_path, file_name); const FileType file_type = GetFileType(full_path); @@ -380,7 +380,7 @@ std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& pa // Now process subdirs. for (const std::string& subdir : subdirs) { - std::string full_subdir = root_dir; + std::string full_subdir{root_dir}; AppendPath(&full_subdir, subdir); std::optional<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter); if (!subfiles) { diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h index a2b1b58e5d4f..42eeaf2d2e2a 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -24,12 +24,11 @@ #include <vector> #include "android-base/macros.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/Source.h" #include "androidfw/StringPiece.h" #include "utils/FileMap.h" -#include "Diagnostics.h" -#include "Source.h" - namespace aapt { namespace file { @@ -67,38 +66,39 @@ std::string BuildPath(std::vector<const android::StringPiece>&& args); bool mkdirs(const std::string& path); // Returns all but the last part of the path. -android::StringPiece GetStem(const android::StringPiece& path); +android::StringPiece GetStem(android::StringPiece path); // Returns the last part of the path with extension. -android::StringPiece GetFilename(const android::StringPiece& path); +android::StringPiece GetFilename(android::StringPiece path); // Returns the extension of the path. This is the entire string after the first '.' of the last part // of the path. -android::StringPiece GetExtension(const android::StringPiece& path); +android::StringPiece GetExtension(android::StringPiece path); // Returns whether or not the name of the file or directory is a hidden file name -bool IsHidden(const android::StringPiece& path); +bool IsHidden(android::StringPiece path); // Converts a package name (com.android.app) to a path: com/android/app -std::string PackageToPath(const android::StringPiece& package); +std::string PackageToPath(android::StringPiece package); // Creates a FileMap for the file at path. std::optional<android::FileMap> MmapPath(const std::string& path, std::string* out_error); // Reads the file at path and appends each line to the outArgList vector. -bool AppendArgsFromFile(const android::StringPiece& path, std::vector<std::string>* out_arglist, +bool AppendArgsFromFile(android::StringPiece path, std::vector<std::string>* out_arglist, std::string* out_error); // Reads the file at path and appends each line to the outargset set. -bool AppendSetArgsFromFile(const android::StringPiece& path, - std::unordered_set<std::string>* out_argset, std::string* out_error); +bool AppendSetArgsFromFile(android::StringPiece path, std::unordered_set<std::string>* out_argset, + std::string* out_error); // Filter that determines which resource files/directories are // processed by AAPT. Takes a pattern string supplied by the user. // Pattern format is specified in the FileFilter::SetPattern() method. class FileFilter { public: - explicit FileFilter(IDiagnostics* diag) : diag_(diag) {} + explicit FileFilter(android::IDiagnostics* diag) : diag_(diag) { + } // Patterns syntax: // - Delimiter is : @@ -112,7 +112,7 @@ class FileFilter { // - The special filenames "." and ".." are always ignored. // - Otherwise the full string is matched. // - match is not case-sensitive. - bool SetPattern(const android::StringPiece& pattern); + bool SetPattern(android::StringPiece pattern); // Applies the filter, returning true for pass, false for fail. bool operator()(const std::string& filename, FileType type) const; @@ -120,14 +120,14 @@ class FileFilter { private: DISALLOW_COPY_AND_ASSIGN(FileFilter); - IDiagnostics* diag_; + android::IDiagnostics* diag_; std::vector<std::string> pattern_tokens_; }; // Returns a list of files relative to the directory identified by `path`. // An optional FileFilter filters out any files that don't pass. -std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path, - IDiagnostics* diag, +std::optional<std::vector<std::string>> FindFiles(android::StringPiece path, + android::IDiagnostics* diag, const FileFilter* filter = nullptr); } // namespace file diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index efbbf8ebe013..be877660ef72 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -23,11 +23,12 @@ #include "android-base/stringprintf.h" #include "android-base/strings.h" +#include "androidfw/BigBuffer.h" #include "androidfw/StringPiece.h" +#include "androidfw/Util.h" #include "build/version.h" #include "text/Unicode.h" #include "text/Utf8Iterator.h" -#include "util/BigBuffer.h" #include "utils/Unicode.h" using ::aapt::text::Utf8Iterator; @@ -42,15 +43,14 @@ namespace util { // See frameworks/base/core/java/android/content/pm/parsing/ParsingPackageUtils.java constexpr static const size_t kMaxPackageNameSize = 223; -static std::vector<std::string> SplitAndTransform( - const StringPiece& str, char sep, const std::function<char(char)>& f) { +static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, char (*f)(char)) { std::vector<std::string> parts; const StringPiece::const_iterator end = std::end(str); StringPiece::const_iterator start = std::begin(str); StringPiece::const_iterator current; do { current = std::find(start, end, sep); - parts.emplace_back(str.substr(start, current).to_string()); + parts.emplace_back(start, current); if (f) { std::string& part = parts.back(); std::transform(part.begin(), part.end(), part.begin(), f); @@ -60,29 +60,29 @@ static std::vector<std::string> SplitAndTransform( return parts; } -std::vector<std::string> Split(const StringPiece& str, char sep) { +std::vector<std::string> Split(StringPiece str, char sep) { return SplitAndTransform(str, sep, nullptr); } -std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) { - return SplitAndTransform(str, sep, ::tolower); +std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) { + return SplitAndTransform(str, sep, [](char c) -> char { return ::tolower(c); }); } -bool StartsWith(const StringPiece& str, const StringPiece& prefix) { +bool StartsWith(StringPiece str, StringPiece prefix) { if (str.size() < prefix.size()) { return false; } return str.substr(0, prefix.size()) == prefix; } -bool EndsWith(const StringPiece& str, const StringPiece& suffix) { +bool EndsWith(StringPiece str, StringPiece suffix) { if (str.size() < suffix.size()) { return false; } return str.substr(str.size() - suffix.size(), suffix.size()) == suffix; } -StringPiece TrimLeadingWhitespace(const StringPiece& str) { +StringPiece TrimLeadingWhitespace(StringPiece str) { if (str.size() == 0 || str.data() == nullptr) { return str; } @@ -96,7 +96,7 @@ StringPiece TrimLeadingWhitespace(const StringPiece& str) { return StringPiece(start, end - start); } -StringPiece TrimTrailingWhitespace(const StringPiece& str) { +StringPiece TrimTrailingWhitespace(StringPiece str) { if (str.size() == 0 || str.data() == nullptr) { return str; } @@ -110,7 +110,7 @@ StringPiece TrimTrailingWhitespace(const StringPiece& str) { return StringPiece(start, end - start); } -StringPiece TrimWhitespace(const StringPiece& str) { +StringPiece TrimWhitespace(StringPiece str) { if (str.size() == 0 || str.data() == nullptr) { return str; } @@ -129,9 +129,9 @@ StringPiece TrimWhitespace(const StringPiece& str) { return StringPiece(start, end - start); } -static int IsJavaNameImpl(const StringPiece& str) { +static int IsJavaNameImpl(StringPiece str) { int pieces = 0; - for (const StringPiece& piece : Tokenize(str, '.')) { + for (StringPiece piece : Tokenize(str, '.')) { pieces++; if (!text::IsJavaIdentifier(piece)) { return -1; @@ -140,17 +140,17 @@ static int IsJavaNameImpl(const StringPiece& str) { return pieces; } -bool IsJavaClassName(const StringPiece& str) { +bool IsJavaClassName(StringPiece str) { return IsJavaNameImpl(str) >= 2; } -bool IsJavaPackageName(const StringPiece& str) { +bool IsJavaPackageName(StringPiece str) { return IsJavaNameImpl(str) >= 1; } -static int IsAndroidNameImpl(const StringPiece& str) { +static int IsAndroidNameImpl(StringPiece str) { int pieces = 0; - for (const StringPiece& piece : Tokenize(str, '.')) { + for (StringPiece piece : Tokenize(str, '.')) { if (piece.empty()) { return -1; } @@ -172,15 +172,14 @@ static int IsAndroidNameImpl(const StringPiece& str) { return pieces; } -bool IsAndroidPackageName(const StringPiece& str) { +bool IsAndroidPackageName(StringPiece str) { if (str.size() > kMaxPackageNameSize) { return false; } return IsAndroidNameImpl(str) > 1 || str == "android"; } -bool IsAndroidSharedUserId(const android::StringPiece& package_name, - const android::StringPiece& shared_user_id) { +bool IsAndroidSharedUserId(android::StringPiece package_name, android::StringPiece shared_user_id) { if (shared_user_id.size() > kMaxPackageNameSize) { return false; } @@ -188,25 +187,24 @@ bool IsAndroidSharedUserId(const android::StringPiece& package_name, package_name == "android"; } -bool IsAndroidSplitName(const StringPiece& str) { +bool IsAndroidSplitName(StringPiece str) { return IsAndroidNameImpl(str) > 0; } -std::optional<std::string> GetFullyQualifiedClassName(const StringPiece& package, - const StringPiece& classname) { +std::optional<std::string> GetFullyQualifiedClassName(StringPiece package, StringPiece classname) { if (classname.empty()) { return {}; } if (util::IsJavaClassName(classname)) { - return classname.to_string(); + return std::string(classname); } if (package.empty()) { return {}; } - std::string result = package.to_string(); + std::string result{package}; if (classname.data()[0] != '.') { result += '.'; } @@ -250,7 +248,7 @@ static size_t ConsumeDigits(const char* start, const char* end) { return static_cast<size_t>(c - start); } -bool VerifyJavaStringFormat(const StringPiece& str) { +bool VerifyJavaStringFormat(StringPiece str) { const char* c = str.begin(); const char* const end = str.end(); @@ -340,108 +338,7 @@ bool VerifyJavaStringFormat(const StringPiece& str) { return true; } -std::string Utf8ToModifiedUtf8(const std::string& utf8) { - // Java uses Modified UTF-8 which only supports the 1, 2, and 3 byte formats of UTF-8. To encode - // 4 byte UTF-8 codepoints, Modified UTF-8 allows the use of surrogate pairs in the same format - // of CESU-8 surrogate pairs. Calculate the size of the utf8 string with all 4 byte UTF-8 - // codepoints replaced with 2 3 byte surrogate pairs - size_t modified_size = 0; - const size_t size = utf8.size(); - for (size_t i = 0; i < size; i++) { - if (((uint8_t) utf8[i] >> 4) == 0xF) { - modified_size += 6; - i += 3; - } else { - modified_size++; - } - } - - // Early out if no 4 byte codepoints are found - if (size == modified_size) { - return utf8; - } - - std::string output; - output.reserve(modified_size); - for (size_t i = 0; i < size; i++) { - if (((uint8_t) utf8[i] >> 4) == 0xF) { - int32_t codepoint = utf32_from_utf8_at(utf8.data(), size, i, nullptr); - - // Calculate the high and low surrogates as UTF-16 would - int32_t high = ((codepoint - 0x10000) / 0x400) + 0xD800; - int32_t low = ((codepoint - 0x10000) % 0x400) + 0xDC00; - - // Encode each surrogate in UTF-8 - output.push_back((char) (0xE4 | ((high >> 12) & 0xF))); - output.push_back((char) (0x80 | ((high >> 6) & 0x3F))); - output.push_back((char) (0x80 | (high & 0x3F))); - output.push_back((char) (0xE4 | ((low >> 12) & 0xF))); - output.push_back((char) (0x80 | ((low >> 6) & 0x3F))); - output.push_back((char) (0x80 | (low & 0x3F))); - i += 3; - } else { - output.push_back(utf8[i]); - } - } - - return output; -} - -std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8) { - // The UTF-8 representation will have a byte length less than or equal to the Modified UTF-8 - // representation. - std::string output; - output.reserve(modified_utf8.size()); - - size_t index = 0; - const size_t modified_size = modified_utf8.size(); - while (index < modified_size) { - size_t next_index; - int32_t high_surrogate = utf32_from_utf8_at(modified_utf8.data(), modified_size, index, - &next_index); - if (high_surrogate < 0) { - return {}; - } - - // Check that the first codepoint is within the high surrogate range - if (high_surrogate >= 0xD800 && high_surrogate <= 0xDB7F) { - int32_t low_surrogate = utf32_from_utf8_at(modified_utf8.data(), modified_size, next_index, - &next_index); - if (low_surrogate < 0) { - return {}; - } - - // Check that the second codepoint is within the low surrogate range - if (low_surrogate >= 0xDC00 && low_surrogate <= 0xDFFF) { - const char32_t codepoint = (char32_t) (((high_surrogate - 0xD800) * 0x400) - + (low_surrogate - 0xDC00) + 0x10000); - - // The decoded codepoint should represent a 4 byte, UTF-8 character - const size_t utf8_length = (size_t) utf32_to_utf8_length(&codepoint, 1); - if (utf8_length != 4) { - return {}; - } - - // Encode the UTF-8 representation of the codepoint into the string - char* start = &output[output.size()]; - output.resize(output.size() + utf8_length); - utf32_to_utf8((char32_t*) &codepoint, 1, start, utf8_length + 1); - - index = next_index; - continue; - } - } - - // Append non-surrogate pairs to the output string - for (size_t i = index; i < next_index; i++) { - output.push_back(modified_utf8[i]); - } - index = next_index; - } - return output; -} - -std::u16string Utf8ToUtf16(const StringPiece& utf8) { +std::u16string Utf8ToUtf16(StringPiece utf8) { ssize_t utf16_length = utf8_to_utf16_length( reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length()); if (utf16_length <= 0) { @@ -467,7 +364,7 @@ std::string Utf16ToUtf8(const StringPiece16& utf16) { return utf8; } -bool WriteAll(std::ostream& out, const BigBuffer& buffer) { +bool WriteAll(std::ostream& out, const android::BigBuffer& buffer) { for (const auto& b : buffer) { if (!out.write(reinterpret_cast<const char*>(b.buffer.get()), b.size)) { return false; @@ -476,23 +373,12 @@ bool WriteAll(std::ostream& out, const BigBuffer& buffer) { return true; } -std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer) { - std::unique_ptr<uint8_t[]> data = - std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]); - uint8_t* p = data.get(); - for (const auto& block : buffer) { - memcpy(p, block.buffer.get(), block.size); - p += block.size; - } - return data; -} - typename Tokenizer::iterator& Tokenizer::iterator::operator++() { const char* start = token_.end(); const char* end = str_.end(); if (start == end) { end_ = true; - token_.assign(token_.end(), 0); + token_ = StringPiece(token_.end(), 0); return *this; } @@ -500,12 +386,12 @@ typename Tokenizer::iterator& Tokenizer::iterator::operator++() { const char* current = start; while (current != end) { if (*current == separator_) { - token_.assign(start, current - start); + token_ = StringPiece(start, current - start); return *this; } ++current; } - token_.assign(start, end - start); + token_ = StringPiece(start, end - start); return *this; } @@ -520,15 +406,17 @@ bool Tokenizer::iterator::operator!=(const iterator& rhs) const { return !(*this == rhs); } -Tokenizer::iterator::iterator(const StringPiece& s, char sep, const StringPiece& tok, bool end) - : str_(s), separator_(sep), token_(tok), end_(end) {} +Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok, bool end) + : str_(s), separator_(sep), token_(tok), end_(end) { +} -Tokenizer::Tokenizer(const StringPiece& str, char sep) +Tokenizer::Tokenizer(StringPiece str, char sep) : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)), - end_(str, sep, StringPiece(str.end(), 0), true) {} + end_(str, sep, StringPiece(str.end(), 0), true) { +} -bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix, - StringPiece* out_entry, StringPiece* out_suffix) { +bool ExtractResFilePathParts(StringPiece path, StringPiece* out_prefix, StringPiece* out_entry, + StringPiece* out_suffix) { const StringPiece res_prefix("res/"); if (!StartsWith(path, res_prefix)) { return false; @@ -553,19 +441,5 @@ bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix, return true; } -StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx) { - if (auto str = pool.stringAt(idx); str.ok()) { - return *str; - } - return StringPiece16(); -} - -std::string GetString(const android::ResStringPool& pool, size_t idx) { - if (auto str = pool.string8At(idx); str.ok()) { - return ModifiedUtf8ToUtf8(str->to_string()); - } - return Utf16ToUtf8(GetString16(pool, idx)); -} - } // namespace util } // namespace aapt diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index c3efe6a63feb..40ff5b633d97 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -23,12 +23,11 @@ #include <string> #include <vector> +#include "androidfw/BigBuffer.h" #include "androidfw/ResourceTypes.h" #include "androidfw/StringPiece.h" #include "utils/ByteOrder.h" -#include "util/BigBuffer.h" - #ifdef _WIN32 // TODO(adamlesinski): remove once http://b/32447322 is resolved. // utils/ByteOrder.h includes winsock2.h on WIN32, @@ -49,44 +48,44 @@ struct Range { T end; }; -std::vector<std::string> Split(const android::StringPiece& str, char sep); -std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep); +std::vector<std::string> Split(android::StringPiece str, char sep); +std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep); // Returns true if the string starts with prefix. -bool StartsWith(const android::StringPiece& str, const android::StringPiece& prefix); +bool StartsWith(android::StringPiece str, android::StringPiece prefix); // Returns true if the string ends with suffix. -bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffix); +bool EndsWith(android::StringPiece str, android::StringPiece suffix); // Creates a new StringPiece that points to a substring of the original string without leading // whitespace. -android::StringPiece TrimLeadingWhitespace(const android::StringPiece& str); +android::StringPiece TrimLeadingWhitespace(android::StringPiece str); // Creates a new StringPiece that points to a substring of the original string without trailing // whitespace. -android::StringPiece TrimTrailingWhitespace(const android::StringPiece& str); +android::StringPiece TrimTrailingWhitespace(android::StringPiece str); // Creates a new StringPiece that points to a substring of the original string without leading or // trailing whitespace. -android::StringPiece TrimWhitespace(const android::StringPiece& str); +android::StringPiece TrimWhitespace(android::StringPiece str); // Tests that the string is a valid Java class name. -bool IsJavaClassName(const android::StringPiece& str); +bool IsJavaClassName(android::StringPiece str); // Tests that the string is a valid Java package name. -bool IsJavaPackageName(const android::StringPiece& str); +bool IsJavaPackageName(android::StringPiece str); // Tests that the string is a valid Android package name. More strict than a Java package name. // - First character of each component (separated by '.') must be an ASCII letter. // - Subsequent characters of a component can be ASCII alphanumeric or an underscore. // - Package must contain at least two components, unless it is 'android'. // - The maximum package name length is 223. -bool IsAndroidPackageName(const android::StringPiece& str); +bool IsAndroidPackageName(android::StringPiece str); // Tests that the string is a valid Android split name. // - First character of each component (separated by '.') must be an ASCII letter. // - Subsequent characters of a component can be ASCII alphanumeric or an underscore. -bool IsAndroidSplitName(const android::StringPiece& str); +bool IsAndroidSplitName(android::StringPiece str); // Tests that the string is a valid Android shared user id. // - First character of each component (separated by '.') must be an ASCII letter. @@ -94,8 +93,7 @@ bool IsAndroidSplitName(const android::StringPiece& str); // - Must contain at least two components, unless package name is 'android'. // - The maximum shared user id length is 223. // - Treat empty string as valid, it's the case of no shared user id. -bool IsAndroidSharedUserId(const android::StringPiece& package_name, - const android::StringPiece& shared_user_id); +bool IsAndroidSharedUserId(android::StringPiece package_name, android::StringPiece shared_user_id); // Converts the class name to a fully qualified class name from the given // `package`. Ex: @@ -104,8 +102,8 @@ bool IsAndroidSharedUserId(const android::StringPiece& package_name, // .asdf --> package.asdf // .a.b --> package.a.b // asdf.adsf --> asdf.adsf -std::optional<std::string> GetFullyQualifiedClassName(const android::StringPiece& package, - const android::StringPiece& class_name); +std::optional<std::string> GetFullyQualifiedClassName(android::StringPiece package, + android::StringPiece class_name); // Retrieves the formatted name of aapt2. const char* GetToolName(); @@ -149,29 +147,20 @@ template <typename Container> }; } -// Helper method to extract a UTF-16 string from a StringPool. If the string is stored as UTF-8, -// the conversion to UTF-16 happens within ResStringPool. -android::StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx); - -// Helper method to extract a UTF-8 string from a StringPool. If the string is stored as UTF-16, -// the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is done by this method, -// which maintains no state or cache. This means we must return an std::string copy. -std::string GetString(const android::ResStringPool& pool, size_t idx); - // Checks that the Java string format contains no non-positional arguments (arguments without // explicitly specifying an index) when there are more than one argument. This is an error // because translations may rearrange the order of the arguments in the string, which will // break the string interpolation. -bool VerifyJavaStringFormat(const android::StringPiece& str); +bool VerifyJavaStringFormat(android::StringPiece str); -bool AppendStyledString(const android::StringPiece& input, bool preserve_spaces, - std::string* out_str, std::string* out_error); +bool AppendStyledString(android::StringPiece input, bool preserve_spaces, std::string* out_str, + std::string* out_error); class StringBuilder { public: StringBuilder() = default; - StringBuilder& Append(const android::StringPiece& str); + StringBuilder& Append(android::StringPiece str); const std::string& ToString() const; const std::string& Error() const; bool IsEmpty() const; @@ -212,19 +201,8 @@ inline StringBuilder::operator bool() const { return error_.empty(); } -// Converts a UTF8 string into Modified UTF8 -std::string Utf8ToModifiedUtf8(const std::string& utf8); -std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8); - -// Converts a UTF8 string to a UTF16 string. -std::u16string Utf8ToUtf16(const android::StringPiece& utf8); -std::string Utf16ToUtf8(const android::StringPiece16& utf16); - // Writes the entire BigBuffer to the output stream. -bool WriteAll(std::ostream& out, const BigBuffer& buffer); - -// Copies the entire BigBuffer into a single buffer. -std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer); +bool WriteAll(std::ostream& out, const android::BigBuffer& buffer); // A Tokenizer implemented as an iterable collection. It does not allocate any memory on the heap // nor use standard containers. @@ -250,7 +228,7 @@ class Tokenizer { private: friend class Tokenizer; - iterator(const android::StringPiece& s, char sep, const android::StringPiece& tok, bool end); + iterator(android::StringPiece s, char sep, android::StringPiece tok, bool end); android::StringPiece str_; char separator_; @@ -258,7 +236,7 @@ class Tokenizer { bool end_; }; - Tokenizer(const android::StringPiece& str, char sep); + Tokenizer(android::StringPiece str, char sep); iterator begin() const { return begin_; @@ -273,26 +251,10 @@ class Tokenizer { const iterator end_; }; -inline Tokenizer Tokenize(const android::StringPiece& str, char sep) { +inline Tokenizer Tokenize(android::StringPiece str, char sep) { return Tokenizer(str, sep); } -inline uint16_t HostToDevice16(uint16_t value) { - return htods(value); -} - -inline uint32_t HostToDevice32(uint32_t value) { - return htodl(value); -} - -inline uint16_t DeviceToHost16(uint16_t value) { - return dtohs(value); -} - -inline uint32_t DeviceToHost32(uint32_t value) { - return dtohl(value); -} - // Given a path like: res/xml-sw600dp/foo.xml // // Extracts "res/xml-sw600dp/" into outPrefix. @@ -300,18 +262,20 @@ inline uint32_t DeviceToHost32(uint32_t value) { // Extracts ".xml" into outSuffix. // // Returns true if successful. -bool ExtractResFilePathParts(const android::StringPiece& path, android::StringPiece* out_prefix, +bool ExtractResFilePathParts(android::StringPiece path, android::StringPiece* out_prefix, android::StringPiece* out_entry, android::StringPiece* out_suffix); } // namespace util +} // namespace aapt + +namespace std { // Stream operator for functions. Calls the function with the stream as an argument. // In the aapt namespace for lookup. inline ::std::ostream& operator<<(::std::ostream& out, const ::std::function<::std::ostream&(::std::ostream&)>& f) { return f(out); } - -} // namespace aapt +} // namespace std #endif // AAPT_UTIL_H diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index 4ebcb115306f..15135690d0de 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -84,6 +84,14 @@ TEST(UtilTest, TokenizeAtEnd) { ASSERT_THAT(*iter, Eq(StringPiece())); } +TEST(UtilTest, TokenizeNone) { + auto tokenizer = util::Tokenize(StringPiece("none"), '.'); + auto iter = tokenizer.begin(); + ASSERT_THAT(*iter, Eq("none")); + ++iter; + ASSERT_THAT(iter, Eq(tokenizer.end())); +} + TEST(UtilTest, IsJavaClassName) { EXPECT_TRUE(util::IsJavaClassName("android.test.Class")); EXPECT_TRUE(util::IsJavaClassName("android.test.Class$Inner")); diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp index ea42d26358a8..3ccbaa2a4b6c 100644 --- a/tools/aapt2/xml/XmlActionExecutor.cpp +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -22,17 +22,19 @@ namespace aapt { namespace xml { static bool wrapper_one(const XmlNodeAction::ActionFunc& f, Element* el, - const XmlActionExecutorPolicy& policy, SourcePathDiagnostics*) { + const XmlActionExecutorPolicy& policy, android::SourcePathDiagnostics*) { return f(el); } static bool wrapper_two(const XmlNodeAction::ActionFuncWithDiag& f, Element* el, - const XmlActionExecutorPolicy& policy, SourcePathDiagnostics* diag) { + const XmlActionExecutorPolicy& policy, + android::SourcePathDiagnostics* diag) { return f(el, diag); } static bool wrapper_three(const XmlNodeAction::ActionFuncWithPolicyAndDiag& f, Element* el, - const XmlActionExecutorPolicy& policy, SourcePathDiagnostics* diag) { + const XmlActionExecutorPolicy& policy, + android::SourcePathDiagnostics* diag) { return f(el, policy, diag); } @@ -51,7 +53,7 @@ void XmlNodeAction::Action(XmlNodeAction::ActionFuncWithPolicyAndDiag f) { std::placeholders::_2, std::placeholders::_3)); } -static void PrintElementToDiagMessage(const Element* el, DiagMessage* msg) { +static void PrintElementToDiagMessage(const Element* el, android::DiagMessage* msg) { *msg << "<"; if (!el->namespace_uri.empty()) { *msg << el->namespace_uri << ":"; @@ -60,7 +62,7 @@ static void PrintElementToDiagMessage(const Element* el, DiagMessage* msg) { } bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPiece>* bread_crumb, - SourcePathDiagnostics* diag, Element* el) const { + android::SourcePathDiagnostics* diag, Element* el) const { bool error = false; for (const ActionFuncWithPolicyAndDiag& action : actions_) { error |= !action(el, policy, diag); @@ -78,11 +80,11 @@ bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPi } if (policy != XmlActionExecutorPolicy::kNone) { - DiagMessage error_msg(child_el->line_number); + android::DiagMessage error_msg(child_el->line_number); error_msg << "unexpected element "; PrintElementToDiagMessage(child_el, &error_msg); error_msg << " found in "; - for (const StringPiece& element : *bread_crumb) { + for (StringPiece element : *bread_crumb) { error_msg << "<" << element << ">"; } if (policy == XmlActionExecutorPolicy::kAllowListWarning) { @@ -99,14 +101,14 @@ bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPi return !error; } -bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, +bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, android::IDiagnostics* diag, XmlResource* doc) const { - SourcePathDiagnostics source_diag(doc->file.source, diag); + android::SourcePathDiagnostics source_diag(doc->file.source, diag); Element* el = doc->root.get(); if (!el) { if (policy == XmlActionExecutorPolicy::kAllowList) { - source_diag.Error(DiagMessage() << "no root XML tag found"); + source_diag.Error(android::DiagMessage() << "no root XML tag found"); return false; } return true; @@ -121,7 +123,7 @@ bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, IDiagnostics* di } if (policy == XmlActionExecutorPolicy::kAllowList) { - DiagMessage error_msg(el->line_number); + android::DiagMessage error_msg(el->line_number); error_msg << "unexpected root element "; PrintElementToDiagMessage(el, &error_msg); source_diag.Error(error_msg); diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h index 78c43345deb7..8cc4573d2c45 100644 --- a/tools/aapt2/xml/XmlActionExecutor.h +++ b/tools/aapt2/xml/XmlActionExecutor.h @@ -23,8 +23,7 @@ #include <vector> #include "android-base/macros.h" - -#include "Diagnostics.h" +#include "androidfw/IDiagnostics.h" #include "xml/XmlDom.h" namespace aapt { @@ -50,8 +49,8 @@ enum class XmlActionExecutorPolicy { class XmlNodeAction { public: using ActionFuncWithPolicyAndDiag = - std::function<bool(Element*, XmlActionExecutorPolicy, SourcePathDiagnostics*)>; - using ActionFuncWithDiag = std::function<bool(Element*, SourcePathDiagnostics*)>; + std::function<bool(Element*, XmlActionExecutorPolicy, android::SourcePathDiagnostics*)>; + using ActionFuncWithDiag = std::function<bool(Element*, android::SourcePathDiagnostics*)>; using ActionFunc = std::function<bool(Element*)>; // Find or create a child XmlNodeAction that will be performed for the child element with the @@ -69,7 +68,7 @@ class XmlNodeAction { friend class XmlActionExecutor; bool Execute(XmlActionExecutorPolicy policy, std::vector<::android::StringPiece>* bread_crumb, - SourcePathDiagnostics* diag, Element* el) const; + android::SourcePathDiagnostics* diag, Element* el) const; std::map<std::string, XmlNodeAction> map_; std::vector<ActionFuncWithPolicyAndDiag> actions_; @@ -88,7 +87,7 @@ class XmlActionExecutor { // Execute the defined actions for this XmlResource. // Returns true if all actions return true, otherwise returns false. - bool Execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, XmlResource* doc) const; + bool Execute(XmlActionExecutorPolicy policy, android::IDiagnostics* diag, XmlResource* doc) const; private: std::map<std::string, XmlNodeAction> map_; diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index 8b7eadf9fac9..8dea8ea52f92 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -169,7 +169,7 @@ static void XMLCALL CharacterDataHandler(void* user_data, const char* s, int len stack->last_text_node = util::make_unique<Text>(); stack->last_text_node->line_number = XML_GetCurrentLineNumber(parser); stack->last_text_node->column_number = XML_GetCurrentColumnNumber(parser); - stack->last_text_node->text = str.to_string(); + stack->last_text_node->text.assign(str); } static void XMLCALL CommentDataHandler(void* user_data, const char* comment) { @@ -183,7 +183,8 @@ static void XMLCALL CommentDataHandler(void* user_data, const char* comment) { stack->pending_comment += comment; } -std::unique_ptr<XmlResource> Inflate(InputStream* in, IDiagnostics* diag, const Source& source) { +std::unique_ptr<XmlResource> Inflate(InputStream* in, android::IDiagnostics* diag, + const android::Source& source) { Stack stack; std::unique_ptr<std::remove_pointer<XML_Parser>::type, decltype(XML_ParserFree)*> parser = { @@ -199,28 +200,29 @@ std::unique_ptr<XmlResource> Inflate(InputStream* in, IDiagnostics* diag, const size_t buffer_size = 0; while (in->Next(reinterpret_cast<const void**>(&buffer), &buffer_size)) { if (XML_Parse(parser.get(), buffer, buffer_size, false) == XML_STATUS_ERROR) { - diag->Error(DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser.get()))) + diag->Error(android::DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser.get()))) << XML_ErrorString(XML_GetErrorCode(parser.get()))); return {}; } } if (in->HadError()) { - diag->Error(DiagMessage(source) << in->GetError()); + diag->Error(android::DiagMessage(source) << in->GetError()); return {}; } else { // Finish off the parsing. if (XML_Parse(parser.get(), nullptr, 0u, true) == XML_STATUS_ERROR) { - diag->Error(DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser.get()))) + diag->Error(android::DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser.get()))) << XML_ErrorString(XML_GetErrorCode(parser.get()))); return {}; } } return util::make_unique<XmlResource>(ResourceFile{{}, {}, ResourceFile::Type::kUnknown, source}, - StringPool{}, std::move(stack.root)); + android::StringPool{}, std::move(stack.root)); } -static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPool* out_pool) { +static void CopyAttributes(Element* el, android::ResXMLParser* parser, + android::StringPool* out_pool) { const size_t attr_count = parser->getAttributeCount(); if (attr_count > 0) { el->attributes.reserve(attr_count); @@ -229,12 +231,12 @@ static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPoo size_t len; const char16_t* str16 = parser->getAttributeNamespace(i, &len); if (str16) { - attr.namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len)); + attr.namespace_uri = android::util::Utf16ToUtf8(StringPiece16(str16, len)); } str16 = parser->getAttributeName(i, &len); if (str16) { - attr.name = util::Utf16ToUtf8(StringPiece16(str16, len)); + attr.name = android::util::Utf16ToUtf8(StringPiece16(str16, len)); } uint32_t res_id = parser->getAttributeNameResID(i); @@ -244,7 +246,7 @@ static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPoo str16 = parser->getAttributeStringValue(i, &len); if (str16) { - attr.value = util::Utf16ToUtf8(StringPiece16(str16, len)); + attr.value = android::util::Utf16ToUtf8(StringPiece16(str16, len)); } android::Res_value res_value; @@ -294,12 +296,12 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string* size_t len; const char16_t* str16 = tree.getNamespacePrefix(&len); if (str16) { - decl.prefix = util::Utf16ToUtf8(StringPiece16(str16, len)); + decl.prefix = android::util::Utf16ToUtf8(StringPiece16(str16, len)); } str16 = tree.getNamespaceUri(&len); if (str16) { - decl.uri = util::Utf16ToUtf8(StringPiece16(str16, len)); + decl.uri = android::util::Utf16ToUtf8(StringPiece16(str16, len)); } if (pending_element == nullptr) { @@ -323,12 +325,12 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string* size_t len; const char16_t* str16 = tree.getElementNamespace(&len); if (str16) { - el->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len)); + el->namespace_uri = android::util::Utf16ToUtf8(StringPiece16(str16, len)); } str16 = tree.getElementName(&len); if (str16) { - el->name = util::Utf16ToUtf8(StringPiece16(str16, len)); + el->name = android::util::Utf16ToUtf8(StringPiece16(str16, len)); } Element* this_el = el.get(); @@ -349,7 +351,7 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string* size_t len; const char16_t* str16 = tree.getText(&len); if (str16) { - text->text = util::Utf16ToUtf8(StringPiece16(str16, len)); + text->text = android::util::Utf16ToUtf8(StringPiece16(str16, len)); } CHECK(!node_stack.empty()); node_stack.top()->AppendChild(std::move(text)); @@ -415,11 +417,11 @@ void Element::InsertChild(size_t index, std::unique_ptr<Node> child) { children.insert(children.begin() + index, std::move(child)); } -Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) { +Attribute* Element::FindAttribute(StringPiece ns, StringPiece name) { return const_cast<Attribute*>(static_cast<const Element*>(this)->FindAttribute(ns, name)); } -const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const { +const Attribute* Element::FindAttribute(StringPiece ns, StringPiece name) const { for (const auto& attr : attributes) { if (ns == attr.namespace_uri && name == attr.name) { return &attr; @@ -428,7 +430,7 @@ const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece return nullptr; } -void Element::RemoveAttribute(const StringPiece& ns, const StringPiece& name) { +void Element::RemoveAttribute(StringPiece ns, StringPiece name) { auto new_attr_end = std::remove_if(attributes.begin(), attributes.end(), [&](const Attribute& attr) -> bool { return ns == attr.namespace_uri && name == attr.name; @@ -437,34 +439,32 @@ void Element::RemoveAttribute(const StringPiece& ns, const StringPiece& name) { attributes.erase(new_attr_end, attributes.end()); } -Attribute* Element::FindOrCreateAttribute(const StringPiece& ns, const StringPiece& name) { +Attribute* Element::FindOrCreateAttribute(StringPiece ns, StringPiece name) { Attribute* attr = FindAttribute(ns, name); if (attr == nullptr) { - attributes.push_back(Attribute{ns.to_string(), name.to_string()}); + attributes.push_back(Attribute{std::string(ns), std::string(name)}); attr = &attributes.back(); } return attr; } -Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) { +Element* Element::FindChild(StringPiece ns, StringPiece name) { return FindChildWithAttribute(ns, name, {}, {}, {}); } -const Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) const { +const Element* Element::FindChild(StringPiece ns, StringPiece name) const { return FindChildWithAttribute(ns, name, {}, {}, {}); } -Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name, - const StringPiece& attr_ns, const StringPiece& attr_name, - const StringPiece& attr_value) { +Element* Element::FindChildWithAttribute(StringPiece ns, StringPiece name, StringPiece attr_ns, + StringPiece attr_name, StringPiece attr_value) { return const_cast<Element*>(static_cast<const Element*>(this)->FindChildWithAttribute( ns, name, attr_ns, attr_name, attr_value)); } -const Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name, - const StringPiece& attr_ns, - const StringPiece& attr_name, - const StringPiece& attr_value) const { +const Element* Element::FindChildWithAttribute(StringPiece ns, StringPiece name, + StringPiece attr_ns, StringPiece attr_name, + StringPiece attr_value) const { for (const auto& child : children) { if (const Element* el = NodeCast<Element>(child.get())) { if (ns == el->namespace_uri && name == el->name) { @@ -557,7 +557,7 @@ void PackageAwareVisitor::AfterVisitElement(Element* el) { } std::optional<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias( - const StringPiece& alias) const { + StringPiece alias) const { if (alias.empty()) { return ExtractedPackage{{}, false /*private*/}; } diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h index 5d31804d43b7..c253b0a1f4a9 100644 --- a/tools/aapt2/xml/XmlDom.h +++ b/tools/aapt2/xml/XmlDom.h @@ -21,11 +21,10 @@ #include <string> #include <vector> -#include "androidfw/StringPiece.h" - -#include "Diagnostics.h" #include "Resource.h" #include "ResourceValues.h" +#include "androidfw/IDiagnostics.h" +#include "androidfw/StringPiece.h" #include "io/Io.h" #include "util/Util.h" #include "xml/XmlUtil.h" @@ -97,27 +96,22 @@ class Element : public Node { void AppendChild(std::unique_ptr<Node> child); void InsertChild(size_t index, std::unique_ptr<Node> child); - Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name); - const Attribute* FindAttribute(const android::StringPiece& ns, - const android::StringPiece& name) const; - Attribute* FindOrCreateAttribute(const android::StringPiece& ns, - const android::StringPiece& name); - void RemoveAttribute(const android::StringPiece& ns, - const android::StringPiece& name); + Attribute* FindAttribute(android::StringPiece ns, android::StringPiece name); + const Attribute* FindAttribute(android::StringPiece ns, android::StringPiece name) const; + Attribute* FindOrCreateAttribute(android::StringPiece ns, android::StringPiece name); + void RemoveAttribute(android::StringPiece ns, android::StringPiece name); - Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name); - const Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name) const; + Element* FindChild(android::StringPiece ns, android::StringPiece name); + const Element* FindChild(android::StringPiece ns, android::StringPiece name) const; - Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name, - const android::StringPiece& attr_ns, - const android::StringPiece& attr_name, - const android::StringPiece& attr_value); + Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name, + android::StringPiece attr_ns, android::StringPiece attr_name, + android::StringPiece attr_value); - const Element* FindChildWithAttribute(const android::StringPiece& ns, - const android::StringPiece& name, - const android::StringPiece& attr_ns, - const android::StringPiece& attr_name, - const android::StringPiece& attr_value) const; + const Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name, + android::StringPiece attr_ns, + android::StringPiece attr_name, + android::StringPiece attr_value) const; std::vector<Element*> GetChildElements(); @@ -150,7 +144,7 @@ class XmlResource { // StringPool must come before the xml::Node. Destructors are called in reverse order, and // the xml::Node may have StringPool references that need to be destroyed before the StringPool // is destroyed. - StringPool string_pool; + android::StringPool string_pool; std::unique_ptr<xml::Element> root; @@ -158,7 +152,8 @@ class XmlResource { }; // Inflates an XML DOM from an InputStream, logging errors to the logger. -std::unique_ptr<XmlResource> Inflate(io::InputStream* in, IDiagnostics* diag, const Source& source); +std::unique_ptr<XmlResource> Inflate(io::InputStream* in, android::IDiagnostics* diag, + const android::Source& source); // Inflates an XML DOM from a binary ResXMLTree. std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, @@ -235,8 +230,7 @@ class PackageAwareVisitor : public Visitor, public IPackageDeclStack { public: using Visitor::Visit; - std::optional<ExtractedPackage> TransformPackageAlias( - const android::StringPiece& alias) const override; + std::optional<ExtractedPackage> TransformPackageAlias(android::StringPiece alias) const override; protected: PackageAwareVisitor() = default; diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp index 6c717dcd84c8..c50333894099 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -45,7 +45,7 @@ TEST(XmlDomTest, Inflate) { StdErrDiagnostics diag; StringInputStream in(input); - std::unique_ptr<XmlResource> doc = Inflate(&in, &diag, Source("test.xml")); + std::unique_ptr<XmlResource> doc = Inflate(&in, &diag, android::Source("test.xml")); ASSERT_THAT(doc, NotNull()); Element* el = doc->root.get(); @@ -77,13 +77,13 @@ TEST(XmlDomTest, BinaryInflate) { decl.line_number = 2u; doc->root->namespace_decls.push_back(decl); - BigBuffer buffer(4096); + android::BigBuffer buffer(4096); XmlFlattenerOptions options; options.keep_raw_values = true; XmlFlattener flattener(&buffer, options); ASSERT_TRUE(flattener.Consume(context.get(), doc.get())); - auto block = util::Copy(buffer); + auto block = android::util::Copy(buffer); std::unique_ptr<XmlResource> new_doc = Inflate(block.get(), buffer.size(), nullptr); ASSERT_THAT(new_doc, NotNull()); diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp index bfa07490b9c0..d79446bfae6f 100644 --- a/tools/aapt2/xml/XmlPullParser.cpp +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -140,8 +140,7 @@ const std::string& XmlPullParser::namespace_uri() const { return event_queue_.front().data2; } -std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias( - const StringPiece& alias) const { +std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias(StringPiece alias) const { if (alias.empty()) { return ExtractedPackage{{}, false /*private*/}; } @@ -307,7 +306,7 @@ void XMLCALL XmlPullParser::EndCdataSectionHandler(void* user_data) { parser->depth_ }); } -std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, const StringPiece& name) { +std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, StringPiece name) { auto iter = parser->FindAttribute("", name); if (iter != parser->end_attributes()) { return StringPiece(util::TrimWhitespace(iter->value)); @@ -315,8 +314,7 @@ std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, const Stri return {}; } -std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, - const StringPiece& name) { +std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, StringPiece name) { auto iter = parser->FindAttribute("", name); if (iter != parser->end_attributes()) { StringPiece trimmed = util::TrimWhitespace(iter->value); diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h index ab347728ae4b..fe4cd018d808 100644 --- a/tools/aapt2/xml/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -120,8 +120,7 @@ class XmlPullParser : public IPackageDeclStack { * If xmlns:app="http://schemas.android.com/apk/res-auto", then * 'package' will be set to 'defaultPackage'. */ - std::optional<ExtractedPackage> TransformPackageAlias( - const android::StringPiece& alias) const override; + std::optional<ExtractedPackage> TransformPackageAlias(android::StringPiece alias) const override; struct PackageDecl { std::string prefix; @@ -194,7 +193,7 @@ class XmlPullParser : public IPackageDeclStack { * Finds the attribute in the current element within the global namespace. */ std::optional<android::StringPiece> FindAttribute(const XmlPullParser* parser, - const android::StringPiece& name); + android::StringPiece name); /** * Finds the attribute in the current element within the global namespace. The @@ -202,7 +201,7 @@ std::optional<android::StringPiece> FindAttribute(const XmlPullParser* parser, * must not be the empty string. */ std::optional<android::StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, - const android::StringPiece& name); + android::StringPiece name); // // Implementation diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp index 114b5ba7ab1a..709755e69292 100644 --- a/tools/aapt2/xml/XmlUtil.cpp +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -27,7 +27,7 @@ using ::android::StringPiece; namespace aapt { namespace xml { -std::string BuildPackageNamespace(const StringPiece& package, bool private_reference) { +std::string BuildPackageNamespace(StringPiece package, bool private_reference) { std::string result = private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix; result.append(package.data(), package.size()); return result; @@ -41,7 +41,7 @@ std::optional<ExtractedPackage> ExtractPackageFromNamespace(const std::string& n if (package.empty()) { return {}; } - return ExtractedPackage{package.to_string(), false /* is_private */}; + return ExtractedPackage{std::string(package), false /* is_private */}; } else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) { StringPiece schema_prefix = kSchemaPrivatePrefix; @@ -50,7 +50,7 @@ std::optional<ExtractedPackage> ExtractPackageFromNamespace(const std::string& n if (package.empty()) { return {}; } - return ExtractedPackage{package.to_string(), true /* is_private */}; + return ExtractedPackage{std::string(package), true /* is_private */}; } else if (namespace_uri == kSchemaAuto) { return ExtractedPackage{std::string(), true /* is_private */}; diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h index 1ab05a93d314..ad676ca91886 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -59,8 +59,7 @@ std::optional<ExtractedPackage> ExtractPackageFromNamespace(const std::string& n // // If privateReference == true, the package will be of the form: // http://schemas.android.com/apk/prv/res/<package> -std::string BuildPackageNamespace(const android::StringPiece& package, - bool private_reference = false); +std::string BuildPackageNamespace(android::StringPiece package, bool private_reference = false); // Interface representing a stack of XML namespace declarations. When looking up the package for a // namespace prefix, the stack is checked from top to bottom. @@ -69,7 +68,7 @@ struct IPackageDeclStack { // Returns an ExtractedPackage struct if the alias given corresponds with a package declaration. virtual std::optional<ExtractedPackage> TransformPackageAlias( - const android::StringPiece& alias) const = 0; + android::StringPiece alias) const = 0; }; // Helper function for transforming the original Reference inRef to a fully qualified reference diff --git a/tools/bit/adb.cpp b/tools/bit/adb.cpp index f521a63255e1..201028ba900a 100644 --- a/tools/bit/adb.cpp +++ b/tools/bit/adb.cpp @@ -73,7 +73,7 @@ string get_system_property(const string& name, int* err) { Command cmd("adb"); - cmd.AddArg("shell"); + cmd.AddArg("exec-out"); cmd.AddArg("getprop"); cmd.AddArg(name); @@ -278,7 +278,7 @@ run_instrumentation_test(const string& packageName, const string& runner, const InstrumentationCallbacks* callbacks) { Command cmd("adb"); - cmd.AddArg("shell"); + cmd.AddArg("exec-out"); cmd.AddArg("am"); cmd.AddArg("instrument"); cmd.AddArg("-w"); diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp index fd184f50091a..0d48070fd0c6 100644 --- a/tools/bit/main.cpp +++ b/tools/bit/main.cpp @@ -52,24 +52,22 @@ struct Target { int testPassCount; int testFailCount; + int testIgnoreCount; int unknownFailureCount; // unknown failure == "Process crashed", etc. - bool actionsWithNoTests; Target(bool b, bool i, bool t, const string& p); }; Target::Target(bool b, bool i, bool t, const string& p) - :build(b), - install(i), - test(t), - pattern(p), - testActionCount(0), - testPassCount(0), - testFailCount(0), - unknownFailureCount(0), - actionsWithNoTests(false) -{ -} + : build(b), + install(i), + test(t), + pattern(p), + testActionCount(0), + testPassCount(0), + testFailCount(0), + testIgnoreCount(0), + unknownFailureCount(0) {} /** * Command line options. @@ -188,13 +186,12 @@ struct TestAction { // The number of tests that failed int failCount; + + // The number of tests that were ignored (because of @Ignore) + int ignoreCount; }; -TestAction::TestAction() - :passCount(0), - failCount(0) -{ -} +TestAction::TestAction() : passCount(0), failCount(0), ignoreCount(0) {} /** * Record for an activity that is going to be launched. @@ -278,7 +275,7 @@ TestResults::OnTestStatus(TestStatus& status) line << " of " << testCount; } } - line << ": " << m_currentAction->target->name << ':' << className << "\\#" << testName; + line << ": " << m_currentAction->target->name << ':' << className << "#" << testName; print_one_line("%s", line.str().c_str()); } else if ((resultCode == -1) || (resultCode == -2)) { // test failed @@ -286,9 +283,9 @@ TestResults::OnTestStatus(TestStatus& status) // all as "failures". m_currentAction->failCount++; m_currentAction->target->testFailCount++; - printf("%s\n%sFailed: %s:%s\\#%s%s\n", g_escapeClearLine, g_escapeRedBold, - m_currentAction->target->name.c_str(), className.c_str(), - testName.c_str(), g_escapeEndColor); + printf("%s\n%sFailed: %s:%s#%s%s\n", g_escapeClearLine, g_escapeRedBold, + m_currentAction->target->name.c_str(), className.c_str(), testName.c_str(), + g_escapeEndColor); bool stackFound; string stack = get_bundle_string(results, &stackFound, "stack", NULL); @@ -300,6 +297,13 @@ TestResults::OnTestStatus(TestStatus& status) } else if (stackFound) { printf("%s\n", stack.c_str()); } + } else if (resultCode == -3) { + // test ignored + m_currentAction->ignoreCount++; + m_currentAction->target->testIgnoreCount++; + printf("%s\n%sIgnored: %s:%s#%s%s\n", g_escapeClearLine, g_escapeYellowBold, + m_currentAction->target->name.c_str(), className.c_str(), testName.c_str(), + g_escapeEndColor); } } @@ -403,11 +407,14 @@ print_usage(FILE* out) { fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs all the\n"); fprintf(out, " tests in the ProtoOutputStreamBoolTest class.\n"); fprintf(out, "\n"); - fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\\#testWrite\n"); + fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest#testWrite\n"); fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n"); fprintf(out, " test method on that class.\n"); fprintf(out, "\n"); - fprintf(out, " bit CtsProtoTestCases:.ProtoOutputStreamBoolTest\\#testWrite,.ProtoOutputStreamBoolTest\\#testRepeated\n"); + fprintf(out, + " bit " + "CtsProtoTestCases:.ProtoOutputStreamBoolTest#testWrite,.ProtoOutputStreamBoolTest#" + "testRepeated\n"); fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n"); fprintf(out, " and testRepeated test methods on that class.\n"); fprintf(out, "\n"); @@ -450,6 +457,35 @@ print_usage(FILE* out) { fprintf(out, "\n"); } +/** + * Prints a possibly color-coded summary of test results. Example output: + * + * "34 passed, 0 failed, 1 ignored\n" + */ +static void print_results(int passed, int failed, int ignored) { + char const* nothing = ""; + char const* cp = nothing; + char const* cf = nothing; + char const* ci = nothing; + + if (failed > 0) { + cf = g_escapeRedBold; + } else if (passed > 0 || ignored > 0) { + cp = passed > 0 ? g_escapeGreenBold : nothing; + ci = ignored > 0 ? g_escapeYellowBold : nothing; + } else { + cp = g_escapeYellowBold; + cf = g_escapeYellowBold; + } + + if (ignored > 0) { + printf("%s%d passed%s, %s%d failed%s, %s%d ignored%s\n", cp, passed, g_escapeEndColor, cf, + failed, g_escapeEndColor, ci, ignored, g_escapeEndColor); + } else { + printf("%s%d passed%s, %s%d failed%s\n", cp, passed, g_escapeEndColor, cf, failed, + g_escapeEndColor); + } +} /** * Sets the appropriate flag* variables. If there is a problem with the @@ -812,7 +848,7 @@ run_phases(vector<Target*> targets, const Options& options) // Stop & Sync if (!options.noRestart) { - err = run_adb("shell", "stop", NULL); + err = run_adb("exec-out", "stop", NULL); check_error(err); } err = run_adb("remount", NULL); @@ -831,9 +867,9 @@ run_phases(vector<Target*> targets, const Options& options) } else { print_status("Restarting the runtime"); - err = run_adb("shell", "setprop", "sys.boot_completed", "0", NULL); + err = run_adb("exec-out", "setprop", "sys.boot_completed", "0", NULL); check_error(err); - err = run_adb("shell", "start", NULL); + err = run_adb("exec-out", "start", NULL); check_error(err); } @@ -846,7 +882,7 @@ run_phases(vector<Target*> targets, const Options& options) sleep(2); } sleep(1); - err = run_adb("shell", "wm", "dismiss-keyguard", NULL); + err = run_adb("exec-out", "wm", "dismiss-keyguard", NULL); check_error(err); } } @@ -863,7 +899,7 @@ run_phases(vector<Target*> targets, const Options& options) continue; } // TODO: if (!apk.file.fileInfo.exists || apk.file.HasChanged()) - err = run_adb("shell", "mkdir", "-p", dir.c_str(), NULL); + err = run_adb("exec-out", "mkdir", "-p", dir.c_str(), NULL); check_error(err); err = run_adb("push", pushed.file.filename.c_str(), pushed.dest.c_str(), NULL); check_error(err); @@ -945,9 +981,9 @@ run_phases(vector<Target*> targets, const Options& options) } } if (runAll) { - err = run_adb("shell", installedPath.c_str(), NULL); + err = run_adb("exec-out", installedPath.c_str(), NULL); } else { - err = run_adb("shell", installedPath.c_str(), filterArg.c_str(), NULL); + err = run_adb("exec-out", installedPath.c_str(), filterArg.c_str(), NULL); } if (err == 0) { target->testPassCount++; @@ -1035,22 +1071,10 @@ run_phases(vector<Target*> targets, const Options& options) err = run_instrumentation_test(action.packageName, action.runner, action.className, &testResults); check_error(err); - if (action.passCount == 0 && action.failCount == 0) { - action.target->actionsWithNoTests = true; - } int total = action.passCount + action.failCount; printf("%sRan %d test%s for %s. ", g_escapeClearLine, total, total > 1 ? "s" : "", action.target->name.c_str()); - if (action.passCount == 0 && action.failCount == 0) { - printf("%s%d passed, %d failed%s\n", g_escapeYellowBold, action.passCount, - action.failCount, g_escapeEndColor); - } else if (action.failCount > 0) { - printf("%d passed, %s%d failed%s\n", action.passCount, g_escapeRedBold, - action.failCount, g_escapeEndColor); - } else { - printf("%s%d passed%s, %d failed\n", g_escapeGreenBold, action.passCount, - g_escapeEndColor, action.failCount); - } + print_results(action.passCount, action.failCount, action.ignoreCount); if (!testResults.IsSuccess()) { printf("\n%sTest didn't finish successfully: %s%s\n", g_escapeRedBold, testResults.GetErrorMessage().c_str(), g_escapeEndColor); @@ -1073,7 +1097,7 @@ run_phases(vector<Target*> targets, const Options& options) const ActivityAction& action = activityActions[0]; string componentName = action.packageName + "/" + action.className; - err = run_adb("shell", "am", "start", componentName.c_str(), NULL); + err = run_adb("exec-out", "am", "start", componentName.c_str(), NULL); check_error(err); } @@ -1147,17 +1171,11 @@ run_phases(vector<Target*> targets, const Options& options) printf(" %sUnknown failure, see above message.%s\n", g_escapeRedBold, g_escapeEndColor); hasErrors = true; - } else if (target->actionsWithNoTests) { - printf(" %s%d passed, %d failed%s\n", g_escapeYellowBold, - target->testPassCount, target->testFailCount, g_escapeEndColor); - hasErrors = true; - } else if (target->testFailCount > 0) { - printf(" %d passed, %s%d failed%s\n", target->testPassCount, - g_escapeRedBold, target->testFailCount, g_escapeEndColor); - hasErrors = true; } else { - printf(" %s%d passed%s, %d failed\n", g_escapeGreenBold, - target->testPassCount, g_escapeEndColor, target->testFailCount); + printf(" %s%s ", target->name.c_str(), + padding.c_str() + target->name.length()); + print_results(target->testPassCount, target->testFailCount, + target->testIgnoreCount); } } } diff --git a/tools/codegen/Android.bp b/tools/codegen/Android.bp index e53ba3e18a86..a1df878df12e 100644 --- a/tools/codegen/Android.bp +++ b/tools/codegen/Android.bp @@ -9,7 +9,7 @@ package { java_binary_host { name: "codegen_cli", - manifest: "manifest.txt", + main_class: "com.android.codegen.MainKt", srcs: [ "src/**/*.kt", ], diff --git a/tools/codegen/manifest.txt b/tools/codegen/manifest.txt deleted file mode 100644 index 6e1018ba6b55..000000000000 --- a/tools/codegen/manifest.txt +++ /dev/null @@ -1 +0,0 @@ -Main-class: com.android.codegen.MainKt diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt index 5fc800b09ee9..685733386cae 100644 --- a/tools/codegen/src/com/android/codegen/Generators.kt +++ b/tools/codegen/src/com/android/codegen/Generators.kt @@ -393,7 +393,7 @@ private fun ClassPrinter.generateBuilderBuild() { fun ClassPrinter.generateParcelable() { val booleanFields = fields.filter { it.Type == "boolean" } val objectFields = fields.filter { it.Type !in PRIMITIVE_TYPES } - val nullableFields = objectFields.filter { it.mayBeNull } + val nullableFields = objectFields.filter { it.mayBeNull && it.Type !in PRIMITIVE_ARRAY_TYPES } val nonBooleanFields = fields - booleanFields @@ -457,7 +457,7 @@ fun ClassPrinter.generateParcelable() { hasAnnotation("@$DataClassEnum") -> +"dest.writeInt($internalGetter == null ? -1 : $internalGetter.ordinal());" else -> { - if (mayBeNull) !"if ($internalGetter != null) " + if (mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) !"if ($internalGetter != null) " var args = internalGetter if (ParcelMethodsSuffix.startsWith("Parcelable") || ParcelMethodsSuffix.startsWith("TypedObject") @@ -529,7 +529,7 @@ fun ClassPrinter.generateParcelable() { if (passContainer) { methodArgs.add(_name) !"$Type $_name = " - if (mayBeNull) { + if (mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) { +"null;" !"if ((flg & $fieldBit) != 0) {" pushIndent() @@ -539,7 +539,9 @@ fun ClassPrinter.generateParcelable() { +"$containerInitExpr;" } else { !"$Type $_name = " - if (mayBeNull) !"(flg & $fieldBit) == 0 ? null : " + if (mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) { + !"(flg & $fieldBit) == 0 ? null : " + } if (ParcelMethodsSuffix == "StrongInterface") { !"$FieldClass.Stub.asInterface(" } else if (Type !in PRIMITIVE_TYPES + "String" + "Bundle" && @@ -578,7 +580,7 @@ fun ClassPrinter.generateParcelable() { +";" // Cleanup if passContainer - if (passContainer && mayBeNull) { + if (passContainer && mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) { popIndent() rmEmptyLine() +"\n}" @@ -949,4 +951,4 @@ fun ClassPrinter.generateMetadata(file: File) { +"" +"@Deprecated" +"private void __metadata() {}\n" -}
\ No newline at end of file +} diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt index 4b508d022991..bcc6230fd53f 100755 --- a/tools/codegen/src/com/android/codegen/Main.kt +++ b/tools/codegen/src/com/android/codegen/Main.kt @@ -10,6 +10,7 @@ const val GENERATED_END = "// End of generated code" const val INDENT_SINGLE = " " val PRIMITIVE_TYPES = listOf("byte", "short", "int", "long", "char", "float", "double", "boolean") +val PRIMITIVE_ARRAY_TYPES = listOf("byte[]", "short[]", "int[]", "long[]", "char[]", "float[]", "double[]", "boolean[]") val BOXED_PRIMITIVE_TYPES = PRIMITIVE_TYPES.map { it.capitalize() } - "Int" + "Integer" - "Char" + "Character" val BUILTIN_SPECIAL_PARCELLINGS = listOf("Pattern") @@ -133,4 +134,4 @@ private fun handleUpdateFlag(cliArgs: Array<String>, sourceLines: List<String>): System.exit(0) } return cliArgs - "--update-only" -}
\ No newline at end of file +} diff --git a/tools/fonts/Android.bp b/tools/fonts/Android.bp index eeb9e3ceda1e..f8629f9bd0b8 100644 --- a/tools/fonts/Android.bp +++ b/tools/fonts/Android.bp @@ -24,12 +24,7 @@ package { python_defaults { name: "fonts_python_defaults", version: { - py2: { - enabled: false, - embedded_launcher: false, - }, py3: { - enabled: true, embedded_launcher: true, }, }, diff --git a/tools/fonts/font-scaling-array-generator.js b/tools/fonts/font-scaling-array-generator.js new file mode 100644 index 000000000000..59fd2e69764b --- /dev/null +++ b/tools/fonts/font-scaling-array-generator.js @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2022 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. + */ + +/** + Generates arrays for non-linear font scaling, to be pasted into + frameworks/base/core/java/android/content/res/FontScaleConverterFactory.java + + To use: + `node font-scaling-array-generator.js` + or just open a browser, open DevTools, and paste into the Console. +*/ + +/** + * Modify this to match your + * frameworks/base/packages/SettingsLib/res/values/arrays.xml#entryvalues_font_size + * array so that all possible scales are generated. + */ +const scales = [1.15, 1.30, 1.5, 1.8, 2]; + +const commonSpSizes = [8, 10, 12, 14, 18, 20, 24, 30, 100]; + +/** + * Enum for GENERATION_STYLE which determines how to generate the arrays. + */ +const GenerationStyle = { + /** + * Interpolates between hand-tweaked curves. This is the best option and + * shouldn't require any additional tweaking. + */ + CUSTOM_TWEAKED: 'CUSTOM_TWEAKED', + + /** + * Uses a curve equation that is mostly correct, but will need manual tweaking + * at some scales. + */ + CURVE: 'CURVE', + + /** + * Uses straight linear multiplication. Good starting point for manual + * tweaking. + */ + LINEAR: 'LINEAR' +} + +/** + * Determines how arrays are generated. Must be one of the GenerationStyle + * values. + */ +const GENERATION_STYLE = GenerationStyle.CUSTOM_TWEAKED; + +// These are hand-tweaked curves from which we will derive the other +// interstitial curves using linear interpolation, in the case of using +// GenerationStyle.CUSTOM_TWEAKED. +const interpolationTargets = { + 1.0: commonSpSizes, + 1.5: [12, 15, 18, 22, 24, 26, 28, 30, 100], + 2.0: [16, 20, 24, 26, 30, 34, 36, 38, 100] +}; + +/** + * Interpolate a value with specified extrema, to a new value between new + * extrema. + * + * @param value the current value + * @param inputMin minimum the input value can reach + * @param inputMax maximum the input value can reach + * @param outputMin minimum the output value can reach + * @param outputMax maximum the output value can reach + */ +function map(value, inputMin, inputMax, outputMin, outputMax) { + return outputMin + (outputMax - outputMin) * ((value - inputMin) / (inputMax - inputMin)); +} + +/*** + * Interpolate between values a and b. + */ +function lerp(a, b, fraction) { + return (a * (1.0 - fraction)) + (b * fraction); +} + +function generateRatios(scale) { + // Find the best two arrays to interpolate between. + let startTarget, endTarget; + let startTargetScale, endTargetScale; + const targetScales = Object.keys(interpolationTargets).sort(); + for (let i = 0; i < targetScales.length - 1; i++) { + const targetScaleKey = targetScales[i]; + const targetScale = parseFloat(targetScaleKey, 10); + const startTargetScaleKey = targetScaleKey; + const endTargetScaleKey = targetScales[i + 1]; + + if (scale < parseFloat(startTargetScaleKey, 10)) { + break; + } + + startTargetScale = parseFloat(startTargetScaleKey, 10); + endTargetScale = parseFloat(endTargetScaleKey, 10); + startTarget = interpolationTargets[startTargetScaleKey]; + endTarget = interpolationTargets[endTargetScaleKey]; + } + const interpolationProgress = map(scale, startTargetScale, endTargetScale, 0, 1); + + return commonSpSizes.map((sp, i) => { + const originalSizeDp = sp; + let newSizeDp; + switch (GENERATION_STYLE) { + case GenerationStyle.CUSTOM_TWEAKED: + newSizeDp = lerp(startTarget[i], endTarget[i], interpolationProgress); + break; + case GenerationStyle.CURVE: { + let coeff1; + let coeff2; + if (scale < 1) { + // \left(1.22^{-\left(x+5\right)}+0.5\right)\cdot x + coeff1 = -5; + coeff2 = scale; + } else { + // (1.22^{-\left(x-10\right)}+1\right)\cdot x + coeff1 = map(scale, 1, 2, 2, 8); + coeff2 = 1; + } + newSizeDp = ((Math.pow(1.22, (-(originalSizeDp - coeff1))) + coeff2) * originalSizeDp); + break; + } + case GenerationStyle.LINEAR: + newSizeDp = originalSizeDp * scale; + break; + default: + throw new Error('Invalid GENERATION_STYLE'); + } + return { + fromSp: sp, + toDp: newSizeDp + } + }); +} + +const scaleArrays = + scales + .map(scale => { + const scaleString = (scale * 100).toFixed(0); + return { + scale, + name: `font_size_original_sp_to_scaled_dp_${scaleString}_percent` + } + }) + .map(scaleArray => { + const items = generateRatios(scaleArray.scale); + + return { + ...scaleArray, + items + } + }); + +function formatDigit(d) { + const twoSignificantDigits = Math.round(d * 100) / 100; + return String(twoSignificantDigits).padStart(4, ' '); +} + +console.log( + '' + + scaleArrays.reduce( + (previousScaleArray, currentScaleArray) => { + const itemsFromSp = currentScaleArray.items.map(d => d.fromSp) + .map(formatDigit) + .join('f, '); + const itemsToDp = currentScaleArray.items.map(d => d.toDp) + .map(formatDigit) + .join('f, '); + + return previousScaleArray + ` + put( + /* scaleKey= */ ${currentScaleArray.scale}f, + new FontScaleConverter( + /* fromSp= */ + new float[] {${itemsFromSp}}, + /* toDp= */ + new float[] {${itemsToDp}}) + ); + `; + }, + '')); diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index 50fce57add39..006a02908643 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -54,6 +54,7 @@ LANG_TO_SCRIPT = { 'or': 'Orya', 'pa': 'Guru', 'pt': 'Latn', + 'pl': 'Latn', 'ru': 'Latn', 'sk': 'Latn', 'sl': 'Latn', @@ -388,7 +389,7 @@ def check_emoji_not_compat(all_emoji, equivalent_emoji): psname = get_psname(ttf) if "meta" in ttf: - assert 'Emji' not in ttf["meta"].data, 'NotoColorEmoji MUST NOT be a compat font' + assert 'Emji' not in ttf["meta"].data, 'NotoColorEmoji MUST be a compat font' def check_emoji_font_coverage(emoji_fonts, all_emoji, equivalent_emoji): diff --git a/tools/lint/OWNERS b/tools/lint/OWNERS index 7c0451900e32..33e237d306fc 100644 --- a/tools/lint/OWNERS +++ b/tools/lint/OWNERS @@ -2,4 +2,8 @@ brufino@google.com jsharkey@google.com per-file *CallingSettingsNonUserGetterMethods* = file:/packages/SettingsProvider/OWNERS +per-file *RegisterReceiverFlagDetector* = jacobhobbie@google.com +# Android lint in the Android platform maintainers +colefaust@google.com +farivar@google.com diff --git a/tools/lint/README.md b/tools/lint/README.md index b534b62cb395..b235ad60c799 100644 --- a/tools/lint/README.md +++ b/tools/lint/README.md @@ -1,15 +1,44 @@ -# Android Framework Lint Checker +# Android Lint Checks for AOSP -Custom lint checks written here are going to be executed for modules that opt in to those (e.g. any +Custom Android Lint checks are written here to be executed against java modules +in AOSP. These checks are broken down into two subdirectories: + +1. [Global Checks](#android-global-lint-checker) +2. [Framework Checks](#android-framework-lint-checker) + +# [Android Global Lint Checker](/global) +Checks written here are executed for the entire tree. The `AndroidGlobalLintChecker` +build target produces a jar file that is included in the overall build output +(`AndroidGlobalLintChecker.jar`). This file is then downloaded as a prebuilt under the +`prebuilts/cmdline-tools` subproject, and included by soong with all invocations of lint. + +## How to add new global lint checks +1. Write your detector with its issues and put it into + `global/checks/src/main/java/com/google/android/lint`. +2. Add your detector's issues into `AndroidGlobalIssueRegistry`'s `issues` + field. +3. Write unit tests for your detector in one file and put it into + `global/checks/test/java/com/google/android/lint`. +4. Have your change reviewed and merged. Once your change is merged, + obtain a build number from a successful build that includes your change. +5. Run `prebuilts/cmdline-tools/update-android-global-lint-checker.sh + <build_number>`. The script will create a commit that you can upload for + approval to the `prebuilts/cmdline-tools` subproject. +6. Done! Your lint check should be applied in lint report builds across the + entire tree! + +# [Android Framework Lint Checker](/framework) + +Checks written here are going to be executed for modules that opt in to those (e.g. any `services.XXX` module) and results will be automatically reported on CLs on gerrit. -## How to add new lint checks +## How to add new framework lint checks 1. Write your detector with its issues and put it into - `checks/src/main/java/com/google/android/lint`. + `framework/checks/src/main/java/com/google/android/lint`. 2. Add your detector's issues into `AndroidFrameworkIssueRegistry`'s `issues` field. 3. Write unit tests for your detector in one file and put it into - `checks/test/java/com/google/android/lint`. + `framework/checks/test/java/com/google/android/lint`. 4. Done! Your lint checks should be applied in lint report builds for modules that include `AndroidFrameworkLintChecker`. @@ -44,7 +73,11 @@ m out/soong/.intermediates/frameworks/base/services/autofill/services.autofill/a environment variable with the id of the lint. For example: `ANDROID_LINT_CHECK=UnusedTokenOfOriginalCallingIdentity m out/[...]/lint-report.html` -## Create or update a baseline +# How to apply automatic fixes suggested by lint + +See [lint_fix](fix/README.md) + +# Create or update a baseline Baseline files can be used to silence known errors (and warnings) that are deemed to be safe. When there is a lint-baseline.xml file in the root folder of the java library, soong will @@ -75,9 +108,10 @@ locally change the soong code in [lint.go](http://cs/aosp-master/build/soong/java/lint.go;l=451;rcl=2e778d5bc4a8d1d77b4f4a3029a4a254ad57db75) adding `cmd.Flag("--nowarn")` and running lint again. -## Documentation +# Documentation - [Android Lint Docs](https://googlesamples.github.io/android-custom-lint-rules/) +- [Lint Check Unit Testing](https://googlesamples.github.io/android-custom-lint-rules/api-guide/unit-testing.md.html) - [Android Lint source files](https://source.corp.google.com/studio-main/tools/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/) - [PSI source files](https://github.com/JetBrains/intellij-community/tree/master/java/java-psi-api/src/com/intellij/psi) - [UAST source files](https://upsource.jetbrains.com/idea-ce/structure/idea-ce-7b9b8cc138bbd90aec26433f82cd2c6838694003/uast/uast-common/src/org/jetbrains/uast) diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt deleted file mode 100644 index 8011b36c9a8f..000000000000 --- a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2022 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. - */ - -package com.google.android.lint - -import com.android.tools.lint.detector.api.AnnotationInfo -import com.android.tools.lint.detector.api.AnnotationOrigin -import com.android.tools.lint.detector.api.AnnotationUsageInfo -import com.android.tools.lint.detector.api.AnnotationUsageType -import com.android.tools.lint.detector.api.ConstantEvaluator -import com.android.tools.lint.detector.api.Category -import com.android.tools.lint.detector.api.Detector -import com.android.tools.lint.detector.api.Implementation -import com.android.tools.lint.detector.api.Issue -import com.android.tools.lint.detector.api.JavaContext -import com.android.tools.lint.detector.api.Scope -import com.android.tools.lint.detector.api.Severity -import com.android.tools.lint.detector.api.SourceCodeScanner -import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiClass -import com.intellij.psi.PsiMethod -import org.jetbrains.uast.UElement - -/** - * Lint Detector that ensures that any method overriding a method annotated - * with @EnforcePermission is also annotated with the exact same annotation. - * The intent is to surface the effective permission checks to the service - * implementations. - */ -class EnforcePermissionDetector : Detector(), SourceCodeScanner { - - val ENFORCE_PERMISSION = "android.annotation.EnforcePermission" - - override fun applicableAnnotations(): List<String> { - return listOf(ENFORCE_PERMISSION) - } - - private fun areAnnotationsEquivalent( - context: JavaContext, - anno1: PsiAnnotation, - anno2: PsiAnnotation - ): Boolean { - if (anno1.qualifiedName != anno2.qualifiedName) { - return false - } - val attr1 = anno1.parameterList.attributes - val attr2 = anno2.parameterList.attributes - if (attr1.size != attr2.size) { - return false - } - for (i in attr1.indices) { - if (attr1[i].name != attr2[i].name) { - return false - } - val v1 = ConstantEvaluator.evaluate(context, attr1[i].value) - val v2 = ConstantEvaluator.evaluate(context, attr2[i].value) - if (v1 != v2) { - return false - } - } - return true - } - - override fun visitAnnotationUsage( - context: JavaContext, - element: UElement, - annotationInfo: AnnotationInfo, - usageInfo: AnnotationUsageInfo - ) { - if (usageInfo.type == AnnotationUsageType.EXTENDS) { - val newClass = element.sourcePsi?.parent?.parent as PsiClass - val extendedClass: PsiClass = usageInfo.referenced as PsiClass - val newAnnotation = newClass.getAnnotation(ENFORCE_PERMISSION) - val extendedAnnotation = extendedClass.getAnnotation(ENFORCE_PERMISSION)!! - - val location = context.getLocation(element) - val newClassName = newClass.qualifiedName - val extendedClassName = extendedClass.qualifiedName - if (newAnnotation == null) { - val msg = "The class $newClassName extends the class $extendedClassName which " + - "is annotated with @EnforcePermission. The same annotation must be used " + - "on $newClassName." - context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg) - } else if (!areAnnotationsEquivalent(context, newAnnotation, extendedAnnotation)) { - val msg = "The class $newClassName is annotated with ${newAnnotation.text} " + - "which differs from the parent class $extendedClassName: " + - "${extendedAnnotation.text}. The same annotation must be used for " + - "both classes." - context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg) - } - } else if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE && - annotationInfo.origin == AnnotationOrigin.METHOD) { - val overridingMethod = element.sourcePsi as PsiMethod - val overriddenMethod = usageInfo.referenced as PsiMethod - val overridingAnnotation = overridingMethod.getAnnotation(ENFORCE_PERMISSION) - val overriddenAnnotation = overriddenMethod.getAnnotation(ENFORCE_PERMISSION)!! - - val location = context.getLocation(element) - val overridingClass = overridingMethod.parent as PsiClass - val overriddenClass = overriddenMethod.parent as PsiClass - val overridingName = "${overridingClass.name}.${overridingMethod.name}" - val overriddenName = "${overriddenClass.name}.${overriddenMethod.name}" - if (overridingAnnotation == null) { - val msg = "The method $overridingName overrides the method $overriddenName which " + - "is annotated with @EnforcePermission. The same annotation must be used " + - "on $overridingName" - context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg) - } else if (!areAnnotationsEquivalent( - context, overridingAnnotation, overriddenAnnotation)) { - val msg = "The method $overridingName is annotated with " + - "${overridingAnnotation.text} which differs from the overridden " + - "method $overriddenName: ${overriddenAnnotation.text}. The same " + - "annotation must be used for both methods." - context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg) - } - } - } - - companion object { - val EXPLANATION = """ - The @EnforcePermission annotation is used to indicate that the underlying binder code - has already verified the caller's permissions before calling the appropriate method. The - verification code is usually generated by the AIDL compiler, which also takes care of - annotating the generated Java code. - - In order to surface that information to platform developers, the same annotation must be - used on the implementation class or methods. - """ - - val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create( - id = "MissingEnforcePermissionAnnotation", - briefDescription = "Missing @EnforcePermission annotation on Binder method", - explanation = EXPLANATION, - category = Category.SECURITY, - priority = 6, - severity = Severity.ERROR, - implementation = Implementation( - EnforcePermissionDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) - ) - - val ISSUE_MISMATCHING_ENFORCE_PERMISSION: Issue = Issue.create( - id = "MismatchingEnforcePermissionAnnotation", - briefDescription = "Incorrect @EnforcePermission annotation on Binder method", - explanation = EXPLANATION, - category = Category.SECURITY, - priority = 6, - severity = Severity.ERROR, - implementation = Implementation( - EnforcePermissionDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) - ) - } -} diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt deleted file mode 100644 index f5f4ebee24e0..000000000000 --- a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2022 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. - */ - -package com.google.android.lint - -import com.android.tools.lint.checks.infrastructure.LintDetectorTest -import com.android.tools.lint.checks.infrastructure.TestFile -import com.android.tools.lint.checks.infrastructure.TestLintTask -import com.android.tools.lint.detector.api.Detector -import com.android.tools.lint.detector.api.Issue - -@Suppress("UnstableApiUsage") -class EnforcePermissionDetectorTest : LintDetectorTest() { - override fun getDetector(): Detector = EnforcePermissionDetector() - - override fun getIssues(): List<Issue> = listOf( - EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION, - EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION - ) - - override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) - - fun testDoesNotDetectIssuesCorrectAnnotationOnClass() { - lint().files(java( - """ - package test.pkg; - @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) - public class TestClass1 extends IFoo.Stub { - public void testMethod() {} - } - """).indented(), - *stubs - ) - .run() - .expectClean() - } - - fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() { - lint().files(java( - """ - package test.pkg; - import android.annotation.EnforcePermission; - public class TestClass2 extends IFooMethod.Stub { - @Override - @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) - public void testMethod() {} - } - """).indented(), - *stubs - ) - .run() - .expectClean() - } - - fun testDetectIssuesMismatchingAnnotationOnClass() { - lint().files(java( - """ - package test.pkg; - @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) - public class TestClass3 extends IFoo.Stub { - public void testMethod() {} - } - """).indented(), - *stubs - ) - .run() - .expect("""src/test/pkg/TestClass3.java:3: Error: The class test.pkg.TestClass3 is \ -annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \ -which differs from the parent class IFoo.Stub: \ -@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The \ -same annotation must be used for both classes. [MismatchingEnforcePermissionAnnotation] -public class TestClass3 extends IFoo.Stub { - ~~~~~~~~~ -1 errors, 0 warnings""".addLineContinuation()) - } - - fun testDetectIssuesMismatchingAnnotationOnMethod() { - lint().files(java( - """ - package test.pkg; - public class TestClass4 extends IFooMethod.Stub { - @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) - public void testMethod() {} - } - """).indented(), - *stubs - ) - .run() - .expect("""src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is \ -annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \ -which differs from the overridden method Stub.testMethod: \ -@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same \ -annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] - public void testMethod() {} - ~~~~~~~~~~ -1 errors, 0 warnings""".addLineContinuation()) - } - - fun testDetectIssuesMissingAnnotationOnClass() { - lint().files(java( - """ - package test.pkg; - public class TestClass5 extends IFoo.Stub { - public void testMethod() {} - } - """).indented(), - *stubs - ) - .run() - .expect("""src/test/pkg/TestClass5.java:2: Error: The class test.pkg.TestClass5 extends \ -the class IFoo.Stub which is annotated with @EnforcePermission. The same annotation must be \ -used on test.pkg.TestClass5. [MissingEnforcePermissionAnnotation] -public class TestClass5 extends IFoo.Stub { - ~~~~~~~~~ -1 errors, 0 warnings""".addLineContinuation()) - } - - fun testDetectIssuesMissingAnnotationOnMethod() { - lint().files(java( - """ - package test.pkg; - public class TestClass6 extends IFooMethod.Stub { - public void testMethod() {} - } - """).indented(), - *stubs - ) - .run() - .expect("""src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod \ -overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same \ -annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation] - public void testMethod() {} - ~~~~~~~~~~ -1 errors, 0 warnings""".addLineContinuation()) - } - - /* Stubs */ - - private val interfaceIFooStub: TestFile = java( - """ - @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) - public interface IFoo { - @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) - public static abstract class Stub implements IFoo { - @Override - public void testMethod() {} - } - public void testMethod(); - } - """ - ).indented() - - private val interfaceIFooMethodStub: TestFile = java( - """ - public interface IFooMethod { - public static abstract class Stub implements IFooMethod { - @Override - @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) - public void testMethod() {} - } - @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) - public void testMethod(); - } - """ - ).indented() - - private val manifestPermissionStub: TestFile = java( - """ - package android.Manifest; - class permission { - public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"; - public static final String INTERNET = "android.permission.INTERNET"; - } - """ - ).indented() - - private val enforcePermissionAnnotationStub: TestFile = java( - """ - package android.annotation; - public @interface EnforcePermission {} - """ - ).indented() - - private val stubs = arrayOf(interfaceIFooStub, interfaceIFooMethodStub, - manifestPermissionStub, enforcePermissionAnnotationStub) - - // Substitutes "backslash + new line" with an empty string to imitate line continuation - private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "") -} diff --git a/tools/lint/common/Android.bp b/tools/lint/common/Android.bp new file mode 100644 index 000000000000..898f88b8759c --- /dev/null +++ b/tools/lint/common/Android.bp @@ -0,0 +1,29 @@ +// Copyright (C) 2022 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. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_library_host { + name: "AndroidCommonLint", + srcs: ["src/main/java/**/*.kt"], + libs: ["lint_api"], + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt b/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt new file mode 100644 index 000000000000..0ef165f1523b --- /dev/null +++ b/tools/lint/common/src/main/java/com/google/android/lint/Constants.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint + +import com.google.android.lint.model.Method + +const val CLASS_STUB = "Stub" +const val CLASS_CONTEXT = "android.content.Context" +const val CLASS_ACTIVITY_MANAGER_SERVICE = "com.android.server.am.ActivityManagerService" +const val CLASS_ACTIVITY_MANAGER_INTERNAL = "android.app.ActivityManagerInternal" + +// Enforce permission APIs +val ENFORCE_PERMISSION_METHODS = listOf( + Method(CLASS_CONTEXT, "checkPermission"), + Method(CLASS_CONTEXT, "checkCallingPermission"), + Method(CLASS_CONTEXT, "checkCallingOrSelfPermission"), + Method(CLASS_CONTEXT, "enforcePermission"), + Method(CLASS_CONTEXT, "enforceCallingPermission"), + Method(CLASS_CONTEXT, "enforceCallingOrSelfPermission"), + Method(CLASS_ACTIVITY_MANAGER_SERVICE, "checkPermission"), + Method(CLASS_ACTIVITY_MANAGER_INTERNAL, "enforceCallingPermission") +) + +const val ANNOTATION_PERMISSION_METHOD = "android.annotation.PermissionMethod" +const val ANNOTATION_PERMISSION_NAME = "android.annotation.PermissionName" +const val ANNOTATION_PERMISSION_RESULT = "android.content.pm.PackageManager.PermissionResult" diff --git a/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt new file mode 100644 index 000000000000..9a7f8fa53d87 --- /dev/null +++ b/tools/lint/common/src/main/java/com/google/android/lint/PermissionMethodUtils.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint + +import com.android.tools.lint.detector.api.getUMethod +import org.jetbrains.uast.UAnnotation +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.UParameter +import org.jetbrains.uast.UQualifiedReferenceExpression + +fun isPermissionMethodCall(callExpression: UCallExpression): Boolean { + val method = callExpression.resolve()?.getUMethod() ?: return false + return hasPermissionMethodAnnotation(method) +} + +fun hasPermissionMethodAnnotation(method: UMethod): Boolean = + getPermissionMethodAnnotation(method) != null + +fun getPermissionMethodAnnotation(method: UMethod?): UAnnotation? = method?.uAnnotations + ?.firstOrNull { it.qualifiedName == ANNOTATION_PERMISSION_METHOD } + +fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any { + it.hasQualifiedName(ANNOTATION_PERMISSION_NAME) +} + +/** + * Attempts to return a CallExpression from a QualifiedReferenceExpression (or returns it directly if passed directly) + * @param callOrReferenceCall expected to be UCallExpression or UQualifiedReferenceExpression + * @return UCallExpression, if available + */ +fun findCallExpression(callOrReferenceCall: UElement?): UCallExpression? = + when (callOrReferenceCall) { + is UCallExpression -> callOrReferenceCall + is UQualifiedReferenceExpression -> callOrReferenceCall.selector as? UCallExpression + else -> null + } diff --git a/tools/lint/common/src/main/java/com/google/android/lint/model/Method.kt b/tools/lint/common/src/main/java/com/google/android/lint/model/Method.kt new file mode 100644 index 000000000000..3939b6109eaa --- /dev/null +++ b/tools/lint/common/src/main/java/com/google/android/lint/model/Method.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.model + +/** + * Data class to represent a Method + */ +data class Method(val clazz: String, val name: String) { + override fun toString(): String { + return "$clazz#$name" + } +} diff --git a/tools/lint/fix/Android.bp b/tools/lint/fix/Android.bp new file mode 100644 index 000000000000..43f21221ae5a --- /dev/null +++ b/tools/lint/fix/Android.bp @@ -0,0 +1,33 @@ +// Copyright (C) 2022 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. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +python_binary_host { + name: "lint_fix", + main: "soong_lint_fix.py", + srcs: ["soong_lint_fix.py"], +} + +python_library_host { + name: "soong_lint_fix", + srcs: ["soong_lint_fix.py"], +} diff --git a/tools/lint/fix/README.md b/tools/lint/fix/README.md new file mode 100644 index 000000000000..a5ac2be1c18a --- /dev/null +++ b/tools/lint/fix/README.md @@ -0,0 +1,30 @@ +# Refactoring the platform with lint +Inspiration: go/refactor-the-platform-with-lint\ +**Special Thanks: brufino@, azharaa@, for the prior work that made this all possible** + +## What is this? + +It's a python script that runs the framework linter, +and then (optionally) copies modified files back into the source tree.\ +Why python, you ask? Because python is cool ¯\_(ツ)_/¯. + +Incidentally, this exposes a much simpler way to run individual lint checks +against individual modules, so it's useful beyond applying fixes. + +## Why? + +Lint is not allowed to modify source files directly via lint's `--apply-suggestions` flag. +As a compromise, soong zips up the (potentially) modified sources and leaves them in an intermediate +directory. This script runs the lint, unpacks those files, and copies them back into the tree. + +## How do I run it? +**WARNING: You probably want to commit/stash any changes to your working tree before doing this...** + +``` +source build/envsetup.sh +lunch cf_x86_64_phone-userdebug # or any lunch target +m lint_fix +lint_fix -h +``` + +The script's help output explains things that are omitted here. diff --git a/tools/lint/fix/soong_lint_fix.py b/tools/lint/fix/soong_lint_fix.py new file mode 100644 index 000000000000..cd4d778d1dec --- /dev/null +++ b/tools/lint/fix/soong_lint_fix.py @@ -0,0 +1,173 @@ +# Copyright (C) 2022 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. + +import argparse +import json +import os +import shutil +import subprocess +import sys +import zipfile + +ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP") +ANDROID_PRODUCT_OUT = os.environ.get("ANDROID_PRODUCT_OUT") +PRODUCT_OUT = ANDROID_PRODUCT_OUT.removeprefix(f"{ANDROID_BUILD_TOP}/") + +SOONG_UI = "build/soong/soong_ui.bash" +PATH_PREFIX = "out/soong/.intermediates" +PATH_SUFFIX = "android_common/lint" +FIX_ZIP = "suggested-fixes.zip" + +class SoongLintFix: + """ + This class creates a command line tool that will + apply lint fixes to the platform via the necessary + combination of soong and shell commands. + + It breaks up these operations into a few "private" methods + that are intentionally exposed so experimental code can tweak behavior. + + The entry point, `run`, will apply lint fixes using the + intermediate `suggested-fixes` directory that soong creates during its + invocation of lint. + + Basic usage: + ``` + from soong_lint_fix import SoongLintFix + + SoongLintFix().run() + ``` + """ + def __init__(self): + self._parser = _setup_parser() + self._args = None + self._kwargs = None + self._path = None + self._target = None + + + def run(self, additional_setup=None, custom_fix=None): + """ + Run the script + """ + self._setup() + self._find_module() + self._lint() + + if not self._args.no_fix: + self._fix() + + if self._args.print: + self._print() + + def _setup(self): + self._args = self._parser.parse_args() + env = os.environ.copy() + if self._args.check: + env["ANDROID_LINT_CHECK"] = self._args.check + if self._args.lint_module: + env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._args.lint_module + + self._kwargs = { + "env": env, + "executable": "/bin/bash", + "shell": True, + } + + os.chdir(ANDROID_BUILD_TOP) + + + def _find_module(self): + print("Refreshing soong modules...") + try: + os.mkdir(ANDROID_PRODUCT_OUT) + except OSError: + pass + subprocess.call(f"{SOONG_UI} --make-mode {PRODUCT_OUT}/module-info.json", **self._kwargs) + print("done.") + + with open(f"{ANDROID_PRODUCT_OUT}/module-info.json") as f: + module_info = json.load(f) + + if self._args.module not in module_info: + sys.exit(f"Module {self._args.module} not found!") + + module_path = module_info[self._args.module]["path"][0] + print(f"Found module {module_path}/{self._args.module}.") + + self._path = f"{PATH_PREFIX}/{module_path}/{self._args.module}/{PATH_SUFFIX}" + self._target = f"{self._path}/lint-report.txt" + + + def _lint(self): + print("Cleaning up any old lint results...") + try: + os.remove(f"{self._target}") + os.remove(f"{self._path}/{FIX_ZIP}") + except FileNotFoundError: + pass + print("done.") + + print(f"Generating {self._target}") + subprocess.call(f"{SOONG_UI} --make-mode {self._target}", **self._kwargs) + print("done.") + + + def _fix(self): + print("Copying suggested fixes to the tree...") + with zipfile.ZipFile(f"{self._path}/{FIX_ZIP}") as zip: + for name in zip.namelist(): + if name.startswith("out") or not name.endswith(".java"): + continue + with zip.open(name) as src, open(f"{ANDROID_BUILD_TOP}/{name}", "wb") as dst: + shutil.copyfileobj(src, dst) + print("done.") + + + def _print(self): + print("### lint-report.txt ###", end="\n\n") + with open(self._target, "r") as f: + print(f.read()) + + +def _setup_parser(): + parser = argparse.ArgumentParser(description=""" + This is a python script that applies lint fixes to the platform: + 1. Set up the environment, etc. + 2. Run lint on the specified target. + 3. Copy the modified files, from soong's intermediate directory, back into the tree. + + **Gotcha**: You must have run `source build/envsetup.sh` and `lunch` first. + """, formatter_class=argparse.RawTextHelpFormatter) + + parser.add_argument('module', + help='The soong build module to run ' + '(e.g. framework-minus-apex or services.core.unboosted)') + + parser.add_argument('--check', + help='Which lint to run. Passed to the ANDROID_LINT_CHECK environment variable.') + + parser.add_argument('--lint-module', + help='Specific lint module to run. Passed to the ANDROID_LINT_CHECK_EXTRA_MODULES environment variable.') + + parser.add_argument('--no-fix', action='store_true', + help='Just build and run the lint, do NOT apply the fixes.') + + parser.add_argument('--print', action='store_true', + help='Print the contents of the generated lint-report.txt at the end.') + + return parser + +if __name__ == "__main__": + SoongLintFix().run()
\ No newline at end of file diff --git a/tools/lint/Android.bp b/tools/lint/framework/Android.bp index 17547ef8b561..30a6daaef2a4 100644 --- a/tools/lint/Android.bp +++ b/tools/lint/framework/Android.bp @@ -1,4 +1,4 @@ -// Copyright (C) 2021 The Android Open Source Project +// Copyright (C) 2022 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. @@ -29,6 +29,10 @@ java_library_host { "auto_service_annotations", "lint_api", ], + static_libs: [ + "AndroidCommonLint", + ], + kotlincflags: ["-Xjvm-default=all"], } java_test_host { @@ -42,5 +46,19 @@ java_test_host { ], test_options: { unit_test: true, + tradefed_options: [ + { + // lint bundles in some classes that were built with older versions + // of libraries, and no longer load. Since tradefed tries to load + // all classes in the jar to look for tests, it crashes loading them. + // Exclude these classes from tradefed's search. + name: "exclude-paths", + value: "org/apache", + }, + { + name: "exclude-paths", + value: "META-INF", + }, + ], }, } diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt new file mode 100644 index 000000000000..935badecf8d5 --- /dev/null +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 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. + */ + +package com.google.android.lint + +import com.android.tools.lint.client.api.IssueRegistry +import com.android.tools.lint.client.api.Vendor +import com.android.tools.lint.detector.api.CURRENT_API +import com.google.android.lint.parcel.SaferParcelChecker +import com.google.auto.service.AutoService + +@AutoService(IssueRegistry::class) +@Suppress("UnstableApiUsage") +class AndroidFrameworkIssueRegistry : IssueRegistry() { + override val issues = listOf( + CallingIdentityTokenDetector.ISSUE_UNUSED_TOKEN, + CallingIdentityTokenDetector.ISSUE_NON_FINAL_TOKEN, + CallingIdentityTokenDetector.ISSUE_NESTED_CLEAR_IDENTITY_CALLS, + CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK, + CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY, + CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY, + CallingIdentityTokenDetector.ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE, + CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED, + SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, + // TODO: Currently crashes due to OOM issue + // PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS, + PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE, + PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD, + ) + + override val api: Int + get() = CURRENT_API + + override val minApi: Int + get() = 8 + + override val vendor: Vendor = Vendor( + vendorName = "Android", + feedbackUrl = "http://b/issues/new?component=315013", + contact = "brufino@google.com" + ) +} diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt index 930378b168b2..0c375c358e61 100644 --- a/tools/lint/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt @@ -33,6 +33,7 @@ import org.jetbrains.uast.UBlockExpression import org.jetbrains.uast.UCallExpression import org.jetbrains.uast.UDeclarationsExpression import org.jetbrains.uast.UElement +import org.jetbrains.uast.UIfExpression import org.jetbrains.uast.ULocalVariable import org.jetbrains.uast.USimpleNameReferenceExpression import org.jetbrains.uast.UTryExpression @@ -52,10 +53,10 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { private val tokensMap = mutableMapOf<String, Token>() override fun getApplicableUastTypes(): List<Class<out UElement?>> = - listOf(ULocalVariable::class.java, UCallExpression::class.java) + listOf(ULocalVariable::class.java, UCallExpression::class.java) override fun createUastHandler(context: JavaContext): UElementHandler = - TokenUastHandler(context) + TokenUastHandler(context) /** File analysis starts with a clear map */ override fun beforeCheckFile(context: Context) { @@ -70,9 +71,9 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { override fun afterCheckFile(context: Context) { for (token in tokensMap.values) { context.report( - ISSUE_UNUSED_TOKEN, - token.location, - getIncidentMessageUnusedToken(token.variableName) + ISSUE_UNUSED_TOKEN, + token.location, + getIncidentMessageUnusedToken(token.variableName) ) } tokensMap.clear() @@ -96,9 +97,9 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { val variableName = node.getName() if (!node.isFinal) { context.report( - ISSUE_NON_FINAL_TOKEN, - location, - getIncidentMessageNonFinalToken(variableName) + ISSUE_NON_FINAL_TOKEN, + location, + getIncidentMessageNonFinalToken(variableName) ) } // If there exists an unused variable with the same name in the map, we can imply that @@ -106,9 +107,9 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { val oldToken = tokensMap[variableName] if (oldToken != null) { context.report( - ISSUE_UNUSED_TOKEN, - oldToken.location, - getIncidentMessageUnusedToken(oldToken.variableName) + ISSUE_UNUSED_TOKEN, + oldToken.location, + getIncidentMessageUnusedToken(oldToken.variableName) ) } // If there exists a token in the same scope as the current new token, it means that @@ -117,56 +118,84 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { val firstCallToken = findFirstTokenInScope(node) if (firstCallToken != null) { context.report( - ISSUE_NESTED_CLEAR_IDENTITY_CALLS, - createNestedLocation(firstCallToken, location), - getIncidentMessageNestedClearIdentityCallsPrimary( - firstCallToken.variableName, - variableName - ) + ISSUE_NESTED_CLEAR_IDENTITY_CALLS, + createNestedLocation(firstCallToken, location), + getIncidentMessageNestedClearIdentityCallsPrimary( + firstCallToken.variableName, + variableName + ) ) } // If the next statement in the tree is not a try-finally statement, we need to report // the "clearCallingIdentity() is not followed by try-finally" issue val finallyClause = (getNextStatementOfLocalVariable(node) as? UTryExpression) - ?.finallyClause + ?.finallyClause if (finallyClause == null) { context.report( - ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY, - location, - getIncidentMessageClearIdentityCallNotFollowedByTryFinally(variableName) + ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY, + location, + getIncidentMessageClearIdentityCallNotFollowedByTryFinally(variableName) ) } tokensMap[variableName] = Token( - variableName, - node.sourcePsi?.getUseScope(), - location, - finallyClause + variableName, + node.sourcePsi?.getUseScope(), + location, + finallyClause ) } - /** - * For every method(): - * - Checks use of caller-aware methods issue - * For every call of Binder.restoreCallingIdentity(token): - * - Checks for restoreCallingIdentity() not in the finally block issue - * - Removes token from tokensMap if token is within the scope of the method - */ override fun visitCallExpression(node: UCallExpression) { + when { + isMethodCall(node, Method.BINDER_CLEAR_CALLING_IDENTITY) -> { + checkClearCallingIdentityCall(node) + } + isMethodCall(node, Method.BINDER_RESTORE_CALLING_IDENTITY) -> { + checkRestoreCallingIdentityCall(node) + } + isCallerAwareMethod(node) -> checkCallerAwareMethod(node) + } + } + + private fun checkClearCallingIdentityCall(node: UCallExpression) { + var firstNonQualifiedParent = getFirstNonQualifiedParent(node) + // if the call expression is inside a ternary, and the ternary is assigned + // to a variable, then we are still technically assigning + // any result of clearCallingIdentity to a variable + if (firstNonQualifiedParent is UIfExpression && firstNonQualifiedParent.isTernary) { + firstNonQualifiedParent = firstNonQualifiedParent.uastParent + } + if (firstNonQualifiedParent !is ULocalVariable) { + context.report( + ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE, + context.getLocation(node), + getIncidentMessageResultOfClearIdentityCallNotStoredInVariable( + node.getQualifiedParentOrThis().asRenderString() + ) + ) + } + } + + private fun checkCallerAwareMethod(node: UCallExpression) { val token = findFirstTokenInScope(node) - if (isCallerAwareMethod(node) && token != null) { + if (token != null) { context.report( - ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY, - context.getLocation(node), - getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity( - token.variableName, - node.asRenderString() - ) + ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY, + context.getLocation(node), + getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity( + token.variableName, + node.asRenderString() + ) ) - return } - if (!isMethodCall(node, Method.BINDER_RESTORE_CALLING_IDENTITY)) return - val first = node.valueArguments[0].skipParenthesizedExprDown() - val arg = first as? USimpleNameReferenceExpression ?: return + } + + /** + * - Checks for restoreCallingIdentity() not in the finally block issue + * - Removes token from tokensMap if token is within the scope of the method + */ + private fun checkRestoreCallingIdentityCall(node: UCallExpression) { + val arg = node.valueArguments[0] as? USimpleNameReferenceExpression ?: return val variableName = arg.identifier val originalScope = tokensMap[variableName]?.scope ?: return val psi = arg.sourcePsi ?: return @@ -174,26 +203,31 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { // token declaration. If not within the scope, no action is needed because the token is // irrelevant i.e. not in the same scope or was not declared with clearCallingIdentity() if (!PsiSearchScopeUtil.isInScope(originalScope, psi)) return - // - We do not report "restore identity call not in finally" issue when there is no + // We do not report "restore identity call not in finally" issue when there is no // finally block because that case is already handled by "clear identity call not // followed by try-finally" issue - // - UCallExpression can be a child of UQualifiedReferenceExpression, i.e. - // receiver.selector, so to get the call's immediate parent we need to get the topmost - // parent qualified reference expression and access its parent if (tokensMap[variableName]?.finallyBlock != null && - skipParenthesizedExprUp(node.getQualifiedParentOrThis().uastParent) != - tokensMap[variableName]?.finallyBlock) { + getFirstNonQualifiedParent(node) != + tokensMap[variableName]?.finallyBlock + ) { context.report( - ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK, - context.getLocation(node), - getIncidentMessageRestoreIdentityCallNotInFinallyBlock(variableName) + ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK, + context.getLocation(node), + getIncidentMessageRestoreIdentityCallNotInFinallyBlock(variableName) ) } tokensMap.remove(variableName) } + private fun getFirstNonQualifiedParent(expression: UCallExpression): UElement? { + // UCallExpression can be a child of UQualifiedReferenceExpression, i.e. + // receiver.selector, so to get the call's immediate parent we need to get the topmost + // parent qualified reference expression and access its parent + return skipParenthesizedExprUp(expression.getQualifiedParentOrThis().uastParent) + } + private fun isCallerAwareMethod(expression: UCallExpression): Boolean = - callerAwareMethods.any { method -> isMethodCall(expression, method) } + callerAwareMethods.any { method -> isMethodCall(expression, method) } private fun isMethodCall( expression: UCallExpression, @@ -201,12 +235,12 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { ): Boolean { val psiMethod = expression.resolve() ?: return false return psiMethod.getName() == method.methodName && - context.evaluator.methodMatches( - psiMethod, - method.className, - /* allowInherit */ true, - *method.args - ) + context.evaluator.methodMatches( + psiMethod, + method.className, + /* allowInherit */ true, + *method.args + ) } /** @@ -255,7 +289,7 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { return declarations[indexInDeclarations + 1] } val enclosingBlock = node - .getParentOfType<UBlockExpression>(strict = true) ?: return null + .getParentOfType<UBlockExpression>(strict = true) ?: return null val expressions = enclosingBlock.expressions val indexInBlock = expressions.indexOf(declarationsExpression as UElement) return if (indexInBlock == -1) null else expressions.getOrNull(indexInBlock + 1) @@ -301,12 +335,12 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { secondCallTokenLocation: Location ): Location { return cloneLocation(secondCallTokenLocation) - .withSecondary( - cloneLocation(firstCallToken.location), - getIncidentMessageNestedClearIdentityCallsSecondary( - firstCallToken.variableName - ) + .withSecondary( + cloneLocation(firstCallToken.location), + getIncidentMessageNestedClearIdentityCallsSecondary( + firstCallToken.variableName ) + ) } private fun cloneLocation(location: Location): Location { @@ -347,20 +381,20 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { const val CLASS_USER_HANDLE = "android.os.UserHandle" private val callerAwareMethods = listOf( - Method.BINDER_GET_CALLING_PID, - Method.BINDER_GET_CALLING_UID, - Method.BINDER_GET_CALLING_UID_OR_THROW, - Method.BINDER_GET_CALLING_USER_HANDLE, - Method.USER_HANDLE_GET_CALLING_APP_ID, - Method.USER_HANDLE_GET_CALLING_USER_ID + Method.BINDER_GET_CALLING_PID, + Method.BINDER_GET_CALLING_UID, + Method.BINDER_GET_CALLING_UID_OR_THROW, + Method.BINDER_GET_CALLING_USER_HANDLE, + Method.USER_HANDLE_GET_CALLING_APP_ID, + Method.USER_HANDLE_GET_CALLING_USER_ID ) /** Issue: unused token from Binder.clearCallingIdentity() */ @JvmField val ISSUE_UNUSED_TOKEN: Issue = Issue.create( - id = "UnusedTokenOfOriginalCallingIdentity", - briefDescription = "Unused token of Binder.clearCallingIdentity()", - explanation = """ + id = "UnusedTokenOfOriginalCallingIdentity", + briefDescription = "Unused token of Binder.clearCallingIdentity()", + explanation = """ You cleared the original calling identity with \ `Binder.clearCallingIdentity()`, but have not used the returned token to \ restore the identity. @@ -370,26 +404,26 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { `token` is the result of `Binder.clearCallingIdentity()` """, - category = Category.SECURITY, - priority = 6, - severity = Severity.WARNING, - implementation = Implementation( - CallingIdentityTokenDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) + category = Category.SECURITY, + priority = 6, + severity = Severity.WARNING, + implementation = Implementation( + CallingIdentityTokenDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) ) private fun getIncidentMessageUnusedToken(variableName: String) = "`$variableName` has " + - "not been used to restore the calling identity. Introduce a `try`-`finally` " + - "after the declaration and call `Binder.restoreCallingIdentity($variableName)` " + - "in `finally` or remove `$variableName`." + "not been used to restore the calling identity. Introduce a `try`-`finally` " + + "after the declaration and call `Binder.restoreCallingIdentity($variableName)` " + + "in `finally` or remove `$variableName`." /** Issue: non-final token from Binder.clearCallingIdentity() */ @JvmField val ISSUE_NON_FINAL_TOKEN: Issue = Issue.create( - id = "NonFinalTokenOfOriginalCallingIdentity", - briefDescription = "Non-final token of Binder.clearCallingIdentity()", - explanation = """ + id = "NonFinalTokenOfOriginalCallingIdentity", + briefDescription = "Non-final token of Binder.clearCallingIdentity()", + explanation = """ You cleared the original calling identity with \ `Binder.clearCallingIdentity()`, but have not made the returned token `final`. @@ -397,47 +431,47 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { which can cause problems when restoring the identity with \ `Binder.restoreCallingIdentity(token)`. """, - category = Category.SECURITY, - priority = 6, - severity = Severity.WARNING, - implementation = Implementation( - CallingIdentityTokenDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) + category = Category.SECURITY, + priority = 6, + severity = Severity.WARNING, + implementation = Implementation( + CallingIdentityTokenDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) ) private fun getIncidentMessageNonFinalToken(variableName: String) = "`$variableName` is " + - "a non-final token from `Binder.clearCallingIdentity()`. Add `final` keyword to " + - "`$variableName`." + "a non-final token from `Binder.clearCallingIdentity()`. Add `final` keyword to " + + "`$variableName`." /** Issue: nested calls of Binder.clearCallingIdentity() */ @JvmField val ISSUE_NESTED_CLEAR_IDENTITY_CALLS: Issue = Issue.create( - id = "NestedClearCallingIdentityCalls", - briefDescription = "Nested calls of Binder.clearCallingIdentity()", - explanation = """ + id = "NestedClearCallingIdentityCalls", + briefDescription = "Nested calls of Binder.clearCallingIdentity()", + explanation = """ You cleared the original calling identity with \ `Binder.clearCallingIdentity()` twice without restoring identity with the \ result of the first call. Make sure to restore the identity after each clear identity call. """, - category = Category.SECURITY, - priority = 6, - severity = Severity.WARNING, - implementation = Implementation( - CallingIdentityTokenDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) + category = Category.SECURITY, + priority = 6, + severity = Severity.WARNING, + implementation = Implementation( + CallingIdentityTokenDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) ) private fun getIncidentMessageNestedClearIdentityCallsPrimary( firstCallVariableName: String, secondCallVariableName: String ): String = "The calling identity has already been cleared and returned into " + - "`$firstCallVariableName`. Move `$secondCallVariableName` declaration after " + - "restoring the calling identity with " + - "`Binder.restoreCallingIdentity($firstCallVariableName)`." + "`$firstCallVariableName`. Move `$secondCallVariableName` declaration after " + + "restoring the calling identity with " + + "`Binder.restoreCallingIdentity($firstCallVariableName)`." private fun getIncidentMessageNestedClearIdentityCallsSecondary( firstCallVariableName: String @@ -446,10 +480,10 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { /** Issue: Binder.clearCallingIdentity() is not followed by `try-finally` statement */ @JvmField val ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY: Issue = Issue.create( - id = "ClearIdentityCallNotFollowedByTryFinally", - briefDescription = "Binder.clearCallingIdentity() is not followed by try-finally " + - "statement", - explanation = """ + id = "ClearIdentityCallNotFollowedByTryFinally", + briefDescription = "Binder.clearCallingIdentity() is not followed by try-finally " + + "statement", + explanation = """ You cleared the original calling identity with \ `Binder.clearCallingIdentity()`, but the next statement is not a `try` \ statement. @@ -472,30 +506,30 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { code with your identity that was originally intended to run with the calling \ application's identity. """, - category = Category.SECURITY, - priority = 6, - severity = Severity.WARNING, - implementation = Implementation( - CallingIdentityTokenDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) + category = Category.SECURITY, + priority = 6, + severity = Severity.WARNING, + implementation = Implementation( + CallingIdentityTokenDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) ) private fun getIncidentMessageClearIdentityCallNotFollowedByTryFinally( variableName: String ): String = "You cleared the calling identity and returned the result into " + - "`$variableName`, but the next statement is not a `try`-`finally` statement. " + - "Define a `try`-`finally` block after `$variableName` declaration to ensure a " + - "safe restore of the calling identity by calling " + - "`Binder.restoreCallingIdentity($variableName)` and making it an immediate child " + - "of the `finally` block." + "`$variableName`, but the next statement is not a `try`-`finally` statement. " + + "Define a `try`-`finally` block after `$variableName` declaration to ensure a " + + "safe restore of the calling identity by calling " + + "`Binder.restoreCallingIdentity($variableName)` and making it an immediate child " + + "of the `finally` block." /** Issue: Binder.restoreCallingIdentity() is not in finally block */ @JvmField val ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK: Issue = Issue.create( - id = "RestoreIdentityCallNotInFinallyBlock", - briefDescription = "Binder.restoreCallingIdentity() is not in finally block", - explanation = """ + id = "RestoreIdentityCallNotInFinallyBlock", + briefDescription = "Binder.restoreCallingIdentity() is not in finally block", + explanation = """ You are restoring the original calling identity with \ `Binder.restoreCallingIdentity()`, but the call is not an immediate child of \ the `finally` block of the `try` statement. @@ -516,28 +550,28 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { `finally` block, you may run code with your identity that was originally \ intended to run with the calling application's identity. """, - category = Category.SECURITY, - priority = 6, - severity = Severity.WARNING, - implementation = Implementation( - CallingIdentityTokenDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) + category = Category.SECURITY, + priority = 6, + severity = Severity.WARNING, + implementation = Implementation( + CallingIdentityTokenDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) ) private fun getIncidentMessageRestoreIdentityCallNotInFinallyBlock( variableName: String ): String = "`Binder.restoreCallingIdentity($variableName)` is not an immediate child of " + - "the `finally` block of the try statement after `$variableName` declaration. " + - "Surround the call with `finally` block and call it unconditionally." + "the `finally` block of the try statement after `$variableName` declaration. " + + "Surround the call with `finally` block and call it unconditionally." /** Issue: Use of caller-aware methods after Binder.clearCallingIdentity() */ @JvmField val ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY: Issue = Issue.create( - id = "UseOfCallerAwareMethodsWithClearedIdentity", - briefDescription = "Use of caller-aware methods after " + - "Binder.clearCallingIdentity()", - explanation = """ + id = "UseOfCallerAwareMethodsWithClearedIdentity", + briefDescription = "Use of caller-aware methods after " + + "Binder.clearCallingIdentity()", + explanation = """ You cleared the original calling identity with \ `Binder.clearCallingIdentity()`, but used one of the methods below before \ restoring the identity. These methods will use your own identity instead of \ @@ -556,22 +590,59 @@ class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { UserHandle.getCallingUserId() ``` """, - category = Category.SECURITY, - priority = 6, - severity = Severity.WARNING, - implementation = Implementation( - CallingIdentityTokenDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) + category = Category.SECURITY, + priority = 6, + severity = Severity.WARNING, + implementation = Implementation( + CallingIdentityTokenDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) ) private fun getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity( variableName: String, methodName: String ): String = "You cleared the original identity with `Binder.clearCallingIdentity()` " + - "and returned into `$variableName`, so `$methodName` will be using your own " + - "identity instead of the caller's. Either explicitly query your own identity or " + - "move it after restoring the identity with " + - "`Binder.restoreCallingIdentity($variableName)`." + "and returned into `$variableName`, so `$methodName` will be using your own " + + "identity instead of the caller's. Either explicitly query your own identity or " + + "move it after restoring the identity with " + + "`Binder.restoreCallingIdentity($variableName)`." + + /** Issue: Result of Binder.clearCallingIdentity() is not stored in a variable */ + @JvmField + val ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE: Issue = Issue.create( + id = "ResultOfClearIdentityCallNotStoredInVariable", + briefDescription = "Result of Binder.clearCallingIdentity() is not stored in a " + + "variable", + explanation = """ + You cleared the original calling identity with \ + `Binder.clearCallingIdentity()`, but did not store the result of the method \ + call in a variable. You need to store the result in a variable and restore it later. + + Use the following pattern for running operations with your own identity: + + ``` + final long token = Binder.clearCallingIdentity(); + try { + // Code using your own identity + } finally { + Binder.restoreCallingIdentity(token); + } + ``` + """, + category = Category.SECURITY, + priority = 6, + severity = Severity.WARNING, + implementation = Implementation( + CallingIdentityTokenDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + private fun getIncidentMessageResultOfClearIdentityCallNotStoredInVariable( + methodName: String + ): String = "You cleared the original identity with `$methodName` but did not store the " + + "result in a variable. You need to store the result in a variable and restore it " + + "later." } } diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt index fe567da7c017..fe567da7c017 100644 --- a/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt new file mode 100644 index 000000000000..48540b1da565 --- /dev/null +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt @@ -0,0 +1,515 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint + +import com.android.tools.lint.client.api.UastParser +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Context +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.android.tools.lint.detector.api.interprocedural.CallGraph +import com.android.tools.lint.detector.api.interprocedural.CallGraphResult +import com.android.tools.lint.detector.api.interprocedural.searchForPaths +import com.intellij.psi.PsiAnonymousClass +import com.intellij.psi.PsiMethod +import java.util.LinkedList +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.UParameter +import org.jetbrains.uast.USimpleNameReferenceExpression +import org.jetbrains.uast.visitor.AbstractUastVisitor + +/** + * A lint checker to detect potential package visibility issues for system's APIs. APIs working + * in the system_server and taking the package name as a parameter may have chance to reveal + * package existence status on the device, and break the + * <a href="https://developer.android.com/about/versions/11/privacy/package-visibility"> + * Package Visibility</a> that we introduced in Android 11. + * <p> + * Take an example of the API `boolean setFoo(String packageName)`, a malicious app may have chance + * to detect package existence state on the device from the result of the API, if there is no + * package visibility filtering rule or uid identify checks applying to the parameter of the + * package name. + */ +class PackageVisibilityDetector : Detector(), SourceCodeScanner { + + // Enables call graph analysis + override fun isCallGraphRequired(): Boolean = true + + override fun analyzeCallGraph( + context: Context, + callGraph: CallGraphResult + ) { + val systemServerApiNodes = callGraph.callGraph.nodes.filter(::isSystemServerApi) + val sinkMethodNodes = callGraph.callGraph.nodes.filter { + // TODO(b/228285232): Remove enforce permission sink methods + isNodeInList(it, ENFORCE_PERMISSION_METHODS) || isNodeInList(it, APPOPS_METHODS) + } + val parser = context.client.getUastParser(context.project) + analyzeApisContainPackageNameParameters( + context, parser, systemServerApiNodes, sinkMethodNodes) + } + + /** + * Looking for API contains package name parameters, report the lint issue if the API does not + * invoke any sink methods. + */ + private fun analyzeApisContainPackageNameParameters( + context: Context, + parser: UastParser, + systemServerApiNodes: List<CallGraph.Node>, + sinkMethodNodes: List<CallGraph.Node> + ) { + for (apiNode in systemServerApiNodes) { + val apiMethod = apiNode.getUMethod() ?: continue + val pkgNameParamIndexes = apiMethod.uastParameters.mapIndexedNotNull { index, param -> + if (Parameter(param) in PACKAGE_NAME_PATTERNS && apiNode.isArgumentInUse(index)) { + index + } else { + null + } + }.takeIf(List<Int>::isNotEmpty) ?: continue + + for (pkgNameParamIndex in pkgNameParamIndexes) { + // Trace the call path of the method's argument, pass the lint checks if a sink + // method is found + if (traceArgumentCallPath( + apiNode, pkgNameParamIndex, PACKAGE_NAME_SINK_METHOD_LIST)) { + continue + } + // Pass the check if one of the sink methods is invoked + if (hasValidPath( + searchForPaths( + sources = listOf(apiNode), + isSink = { it in sinkMethodNodes }, + getNeighbors = { node -> node.edges.map { it.node!! } } + ) + ) + ) continue + + // Report issue + val reportElement = apiMethod.uastParameters[pkgNameParamIndex] as UElement + val location = parser.createLocation(reportElement) + context.report( + ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS, + location, + getMsgPackageNameNoPackageVisibilityFilters(apiMethod, pkgNameParamIndex) + ) + } + } + } + + /** + * Returns {@code true} if the method associated with the given node is a system server's + * public API that extends from Stub class. + */ + private fun isSystemServerApi( + node: CallGraph.Node + ): Boolean { + val method = node.getUMethod() ?: return false + if (!method.hasModifierProperty("public") || + method.uastBody == null || + method.containingClass is PsiAnonymousClass) { + return false + } + val className = method.containingClass?.qualifiedName ?: return false + if (!className.startsWith(SYSTEM_PACKAGE_PREFIX)) { + return false + } + return (method.containingClass ?: return false).supers + .filter { it.name == CLASS_STUB } + .filter { it.qualifiedName !in BYPASS_STUBS } + .any { it.findMethodBySignature(method, /* checkBases */ true) != null } + } + + /** + * Returns {@code true} if the list contains the node of the call graph. + */ + private fun isNodeInList( + node: CallGraph.Node, + filters: List<Method> + ): Boolean { + val method = node.getUMethod() ?: return false + return Method(method) in filters + } + + /** + * Trace the call paths of the argument of the method in the start entry. Return {@code true} + * if one of methods in the sink call list is invoked. + * Take an example of the call path: + * foo(packageName) -> a(packageName) -> b(packageName) -> filterAppAccess() + * It returns {@code true} if the filterAppAccess() is in the sink call list. + */ + private fun traceArgumentCallPath( + apiNode: CallGraph.Node, + pkgNameParamIndex: Int, + sinkList: List<Method> + ): Boolean { + val startEntry = TraceEntry(apiNode, pkgNameParamIndex) + val traceQueue = LinkedList<TraceEntry>().apply { add(startEntry) } + val allVisits = mutableSetOf<TraceEntry>().apply { add(startEntry) } + while (!traceQueue.isEmpty()) { + val entry = traceQueue.poll() + val entryNode = entry.node + val entryMethod = entryNode.getUMethod() ?: continue + val entryArgumentName = entryMethod.uastParameters[entry.argumentIndex].name + for (outEdge in entryNode.edges) { + val outNode = outEdge.node ?: continue + val outMethod = outNode.getUMethod() ?: continue + val outArgumentIndex = + outEdge.call?.findArgumentIndex( + entryArgumentName, outMethod.uastParameters.size) + val sinkMethod = findInSinkList(outMethod, sinkList) + if (sinkMethod == null) { + if (outArgumentIndex == null) { + // Path is not relevant to the sink method and argument + continue + } + // Path is relevant to the argument, add a new trace entry if never visit before + val newEntry = TraceEntry(outNode, outArgumentIndex) + if (newEntry !in allVisits) { + traceQueue.add(newEntry) + allVisits.add(newEntry) + } + continue + } + if (sinkMethod.matchArgument && outArgumentIndex == null) { + // The sink call is required to match the argument, but not found + continue + } + if (sinkMethod.checkCaller && + entryMethod.isInClearCallingIdentityScope(outEdge.call!!)) { + // The sink call is in the scope of Binder.clearCallingIdentify + continue + } + // A sink method is matched + return true + } + } + return false + } + + /** + * Returns the UMethod associated with the given node of call graph. + */ + private fun CallGraph.Node.getUMethod(): UMethod? = this.target.element as? UMethod + + /** + * Returns the system module name (e.g. com.android.server.pm) of the method of the + * call graph node. + */ + private fun CallGraph.Node.getModuleName(): String? { + val method = getUMethod() ?: return null + val className = method.containingClass?.qualifiedName ?: return null + if (!className.startsWith(SYSTEM_PACKAGE_PREFIX)) { + return null + } + val dotPos = className.indexOf(".", SYSTEM_PACKAGE_PREFIX.length) + if (dotPos == -1) { + return SYSTEM_PACKAGE_PREFIX + } + return className.substring(0, dotPos) + } + + /** + * Return {@code true} if the argument in the method's body is in-use. + */ + private fun CallGraph.Node.isArgumentInUse(argIndex: Int): Boolean { + val method = getUMethod() ?: return false + val argumentName = method.uastParameters[argIndex].name + var foundArg = false + val methodVisitor = object : AbstractUastVisitor() { + override fun visitSimpleNameReferenceExpression( + node: USimpleNameReferenceExpression + ): Boolean { + if (node.identifier == argumentName) { + foundArg = true + } + return true + } + } + method.uastBody?.accept(methodVisitor) + return foundArg + } + + /** + * Given an argument name, returns the index of argument in the call expression. + */ + private fun UCallExpression.findArgumentIndex( + argumentName: String, + parameterSize: Int + ): Int? { + if (valueArgumentCount == 0 || parameterSize == 0) { + return null + } + var match = false + val argVisitor = object : AbstractUastVisitor() { + override fun visitSimpleNameReferenceExpression( + node: USimpleNameReferenceExpression + ): Boolean { + if (node.identifier == argumentName) { + match = true + } + return true + } + override fun visitCallExpression(node: UCallExpression): Boolean { + return true + } + } + valueArguments.take(parameterSize).forEachIndexed { index, argument -> + argument.accept(argVisitor) + if (match) { + return index + } + } + return null + } + + /** + * Given a UMethod, returns a method from the sink method list. + */ + private fun findInSinkList( + uMethod: UMethod, + sinkCallList: List<Method> + ): Method? { + return sinkCallList.find { + it == Method(uMethod) || + it == Method(uMethod.containingClass?.qualifiedName ?: "", "*") + } + } + + /** + * Returns {@code true} if the call expression is in the scope of the + * Binder.clearCallingIdentify. + */ + private fun UMethod.isInClearCallingIdentityScope(call: UCallExpression): Boolean { + var isInScope = false + val methodVisitor = object : AbstractUastVisitor() { + private var clearCallingIdentity = 0 + override fun visitCallExpression(node: UCallExpression): Boolean { + if (call == node && clearCallingIdentity != 0) { + isInScope = true + return true + } + val visitMethod = Method(node.resolve() ?: return false) + if (visitMethod == METHOD_CLEAR_CALLING_IDENTITY) { + clearCallingIdentity++ + } else if (visitMethod == METHOD_RESTORE_CALLING_IDENTITY) { + clearCallingIdentity-- + } + return false + } + } + accept(methodVisitor) + return isInScope + } + + /** + * Checks the module name of the start node and the last node that invokes the sink method + * (e.g. checkPermission) in a path, returns {@code true} if one of the paths has the same + * module name for both nodes. + */ + private fun hasValidPath(paths: Collection<List<CallGraph.Node>>): Boolean { + for (pathNodes in paths) { + if (pathNodes.size < VALID_CALL_PATH_NODES_SIZE) { + continue + } + val startModule = pathNodes[0].getModuleName() ?: continue + val lastCallModule = pathNodes[pathNodes.size - 2].getModuleName() ?: continue + if (startModule == lastCallModule) { + return true + } + } + return false + } + + /** + * A data class to represent the method. + */ + private data class Method( + val clazz: String, + val name: String + ) { + // Used by traceArgumentCallPath to indicate that the method is required to match the + // argument name + var matchArgument = true + + // Used by traceArgumentCallPath to indicate that the method is required to check whether + // the Binder.clearCallingIdentity is invoked. + var checkCaller = false + + constructor( + clazz: String, + name: String, + matchArgument: Boolean = true, + checkCaller: Boolean = false + ) : this(clazz, name) { + this.matchArgument = matchArgument + this.checkCaller = checkCaller + } + + constructor( + method: PsiMethod + ) : this(method.containingClass?.qualifiedName ?: "", method.name) + + constructor( + method: com.google.android.lint.model.Method + ) : this(method.clazz, method.name) + } + + /** + * A data class to represent the parameter of the method. The parameter name is converted to + * lower case letters for comparison. + */ + private data class Parameter private constructor( + val typeName: String, + val parameterName: String + ) { + constructor(uParameter: UParameter) : this( + uParameter.type.canonicalText, + uParameter.name.lowercase() + ) + + companion object { + fun create(typeName: String, parameterName: String) = + Parameter(typeName, parameterName.lowercase()) + } + } + + /** + * A data class wraps a method node of the call graph and an index that indicates an + * argument of the method to record call trace information. + */ + private data class TraceEntry( + val node: CallGraph.Node, + val argumentIndex: Int + ) + + companion object { + private const val SYSTEM_PACKAGE_PREFIX = "com.android.server." + // A valid call path list needs to contain a start node and a sink node + private const val VALID_CALL_PATH_NODES_SIZE = 2 + + private const val CLASS_STRING = "java.lang.String" + private const val CLASS_PACKAGE_MANAGER = "android.content.pm.PackageManager" + private const val CLASS_IPACKAGE_MANAGER = "android.content.pm.IPackageManager" + private const val CLASS_APPOPS_MANAGER = "android.app.AppOpsManager" + private const val CLASS_BINDER = "android.os.Binder" + private const val CLASS_PACKAGE_MANAGER_INTERNAL = + "android.content.pm.PackageManagerInternal" + + // Patterns of package name parameter + private val PACKAGE_NAME_PATTERNS = setOf( + Parameter.create(CLASS_STRING, "packageName"), + Parameter.create(CLASS_STRING, "callingPackage"), + Parameter.create(CLASS_STRING, "callingPackageName"), + Parameter.create(CLASS_STRING, "pkgName"), + Parameter.create(CLASS_STRING, "callingPkg"), + Parameter.create(CLASS_STRING, "pkg") + ) + + // Package manager APIs + private val PACKAGE_NAME_SINK_METHOD_LIST = listOf( + Method(CLASS_PACKAGE_MANAGER_INTERNAL, "filterAppAccess", matchArgument = false), + Method(CLASS_PACKAGE_MANAGER_INTERNAL, "canQueryPackage"), + Method(CLASS_PACKAGE_MANAGER_INTERNAL, "isSameApp"), + Method(CLASS_PACKAGE_MANAGER, "*", checkCaller = true), + Method(CLASS_IPACKAGE_MANAGER, "*", checkCaller = true), + Method(CLASS_PACKAGE_MANAGER, "getPackagesForUid", matchArgument = false), + Method(CLASS_IPACKAGE_MANAGER, "getPackagesForUid", matchArgument = false) + ) + + // AppOps APIs which include uid and package visibility filters checks + private val APPOPS_METHODS = listOf( + Method(CLASS_APPOPS_MANAGER, "noteOp"), + Method(CLASS_APPOPS_MANAGER, "noteOpNoThrow"), + Method(CLASS_APPOPS_MANAGER, "noteOperation"), + Method(CLASS_APPOPS_MANAGER, "noteProxyOp"), + Method(CLASS_APPOPS_MANAGER, "noteProxyOpNoThrow"), + Method(CLASS_APPOPS_MANAGER, "startOp"), + Method(CLASS_APPOPS_MANAGER, "startOpNoThrow"), + Method(CLASS_APPOPS_MANAGER, "FinishOp"), + Method(CLASS_APPOPS_MANAGER, "finishProxyOp"), + Method(CLASS_APPOPS_MANAGER, "checkPackage") + ) + + // Enforce permission APIs + private val ENFORCE_PERMISSION_METHODS = + com.google.android.lint.ENFORCE_PERMISSION_METHODS + .map(PackageVisibilityDetector::Method) + + private val BYPASS_STUBS = listOf( + "android.content.pm.IPackageDataObserver.Stub", + "android.content.pm.IPackageDeleteObserver.Stub", + "android.content.pm.IPackageDeleteObserver2.Stub", + "android.content.pm.IPackageInstallObserver2.Stub", + "com.android.internal.app.IAppOpsCallback.Stub", + + // TODO(b/228285637): Do not bypass PackageManagerService API + "android.content.pm.IPackageManager.Stub", + "android.content.pm.IPackageManagerNative.Stub" + ) + + private val METHOD_CLEAR_CALLING_IDENTITY = + Method(CLASS_BINDER, "clearCallingIdentity") + private val METHOD_RESTORE_CALLING_IDENTITY = + Method(CLASS_BINDER, "restoreCallingIdentity") + + private fun getMsgPackageNameNoPackageVisibilityFilters( + method: UMethod, + argumentIndex: Int + ): String = "Api: ${method.name} contains a package name parameter: " + + "${method.uastParameters[argumentIndex].name} does not apply " + + "package visibility filtering rules." + + private val EXPLANATION = """ + APIs working in the system_server and taking the package name as a parameter may have + chance to reveal package existence status on the device, and break the package + visibility that we introduced in Android 11. + (https://developer.android.com/about/versions/11/privacy/package-visibility) + + Take an example of the API `boolean setFoo(String packageName)`, a malicious app may + have chance to get package existence state on the device from the result of the API, + if there is no package visibility filtering rule or uid identify checks applying to + the parameter of the package name. + + To resolve it, you could apply package visibility filtering rules to the package name + via PackageManagerInternal.filterAppAccess API, before starting to use the package name. + If the parameter is a calling package name, use the PackageManager API such as + PackageManager.getPackagesForUid to verify the calling identify. + """ + + val ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS = Issue.create( + id = "ApiMightLeakAppVisibility", + briefDescription = "Api takes package name parameter doesn't apply " + + "package visibility filters", + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 1, + severity = Severity.WARNING, + implementation = Implementation( + PackageVisibilityDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + } +} diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt new file mode 100644 index 000000000000..e12ec3d4a77c --- /dev/null +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint + +import com.android.tools.lint.client.api.UElementHandler +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.android.tools.lint.detector.api.getUMethod +import com.intellij.psi.PsiType +import org.jetbrains.uast.UAnnotation +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UExpression +import org.jetbrains.uast.UIfExpression +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.UQualifiedReferenceExpression +import org.jetbrains.uast.UReturnExpression +import org.jetbrains.uast.getContainingUMethod + +/** + * Stops incorrect usage of {@link PermissionMethod} + * TODO: add tests once re-enabled (b/240445172, b/247542171) + */ +class PermissionMethodDetector : Detector(), SourceCodeScanner { + + override fun getApplicableUastTypes(): List<Class<out UElement>> = + listOf(UAnnotation::class.java, UMethod::class.java) + + override fun createUastHandler(context: JavaContext): UElementHandler = + PermissionMethodHandler(context) + + private inner class PermissionMethodHandler(val context: JavaContext) : UElementHandler() { + override fun visitMethod(node: UMethod) { + if (hasPermissionMethodAnnotation(node)) return + if (onlyCallsPermissionMethod(node)) { + val location = context.getLocation(node.javaPsi.modifierList) + val fix = fix() + .annotate(ANNOTATION_PERMISSION_METHOD) + .range(location) + .autoFix() + .build() + + context.report( + ISSUE_CAN_BE_PERMISSION_METHOD, + location, + "Annotate method with @PermissionMethod", + fix + ) + } + } + + override fun visitAnnotation(node: UAnnotation) { + if (node.qualifiedName != ANNOTATION_PERMISSION_METHOD) return + val method = node.getContainingUMethod() ?: return + + if (!isPermissionMethodReturnType(method)) { + context.report( + ISSUE_PERMISSION_METHOD_USAGE, + context.getLocation(node), + """ + Methods annotated with `@PermissionMethod` should return `void`, \ + `boolean`, or `@PackageManager.PermissionResult int`." + """.trimIndent() + ) + } + + if (method.returnType == PsiType.INT && + method.annotations.none { it.hasQualifiedName(ANNOTATION_PERMISSION_RESULT) } + ) { + context.report( + ISSUE_PERMISSION_METHOD_USAGE, + context.getLocation(node), + """ + Methods annotated with `@PermissionMethod` that return `int` should \ + also be annotated with `@PackageManager.PermissionResult.`" + """.trimIndent() + ) + } + } + } + + companion object { + + private val EXPLANATION_PERMISSION_METHOD_USAGE = """ + `@PermissionMethod` should annotate methods that ONLY perform permission lookups. \ + Said methods should return `boolean`, `@PackageManager.PermissionResult int`, or return \ + `void` and potentially throw `SecurityException`. + """.trimIndent() + + @JvmField + val ISSUE_PERMISSION_METHOD_USAGE = Issue.create( + id = "PermissionMethodUsage", + briefDescription = "@PermissionMethod used incorrectly", + explanation = EXPLANATION_PERMISSION_METHOD_USAGE, + category = Category.CORRECTNESS, + priority = 5, + severity = Severity.ERROR, + implementation = Implementation( + PermissionMethodDetector::class.java, + Scope.JAVA_FILE_SCOPE + ), + enabledByDefault = true + ) + + private val EXPLANATION_CAN_BE_PERMISSION_METHOD = """ + Methods that only call other methods annotated with @PermissionMethod (and do NOTHING else) can themselves \ + be annotated with @PermissionMethod. For example: + ``` + void wrapperHelper() { + // Context.enforceCallingPermission is annotated with @PermissionMethod + context.enforceCallingPermission(SOME_PERMISSION) + } + ``` + """.trimIndent() + + @JvmField + val ISSUE_CAN_BE_PERMISSION_METHOD = Issue.create( + id = "CanBePermissionMethod", + briefDescription = "Method can be annotated with @PermissionMethod", + explanation = EXPLANATION_CAN_BE_PERMISSION_METHOD, + category = Category.SECURITY, + priority = 5, + severity = Severity.WARNING, + implementation = Implementation( + PermissionMethodDetector::class.java, + Scope.JAVA_FILE_SCOPE + ), + enabledByDefault = false + ) + + private fun isPermissionMethodReturnType(method: UMethod): Boolean = + listOf(PsiType.VOID, PsiType.INT, PsiType.BOOLEAN).contains(method.returnType) + + /** + * Identifies methods that... + * DO call other methods annotated with @PermissionMethod + * DO NOT do anything else + */ + private fun onlyCallsPermissionMethod(method: UMethod): Boolean { + val body = method.uastBody as? UBlockExpression ?: return false + if (body.expressions.isEmpty()) return false + for (expression in body.expressions) { + when (expression) { + is UQualifiedReferenceExpression -> { + if (!isPermissionMethodCall(expression.selector)) return false + } + is UReturnExpression -> { + if (!isPermissionMethodCall(expression.returnExpression)) return false + } + is UCallExpression -> { + if (!isPermissionMethodCall(expression)) return false + } + is UIfExpression -> { + if (expression.thenExpression !is UReturnExpression) return false + if (!isPermissionMethodCall(expression.condition)) return false + } + else -> return false + } + } + return true + } + + private fun isPermissionMethodCall(expression: UExpression?): Boolean { + return when (expression) { + is UQualifiedReferenceExpression -> + return isPermissionMethodCall(expression.selector) + is UCallExpression -> { + val calledMethod = expression.resolve()?.getUMethod() ?: return false + return hasPermissionMethodAnnotation(calledMethod) + } + else -> false + } + } + + private fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations + .any { it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD) } + } +} diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt new file mode 100644 index 000000000000..06c098df385d --- /dev/null +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.parcel + +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.LintFix +import com.android.tools.lint.detector.api.Location +import com.intellij.psi.PsiArrayType +import com.intellij.psi.PsiCallExpression +import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiIntersectionType +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiType +import com.intellij.psi.PsiTypeParameter +import com.intellij.psi.PsiWildcardType +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UExpression +import org.jetbrains.uast.UVariable + +/** + * Subclass this class and override {@link #getBoundingClass} to report an unsafe Parcel API issue + * with a fix that migrates towards the new safer API by appending an argument in the form of + * {@code com.package.ItemType.class} coming from the result of the overridden method. + */ +abstract class CallMigrator( + val method: Method, + private val rejects: Set<String> = emptySet(), +) { + open fun report(context: JavaContext, call: UCallExpression, method: PsiMethod) { + val location = context.getLocation(call) + val itemType = filter(getBoundingClass(context, call, method)) + val fix = (itemType as? PsiClassType)?.let { type -> + getParcelFix(location, this.method.name, getArgumentSuffix(type)) + } + val message = "Unsafe `${this.method.className}.${this.method.name}()` API usage" + context.report(SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, call, location, message, fix) + } + + protected open fun getArgumentSuffix(type: PsiClassType) = + ", ${type.rawType().canonicalText}.class" + + protected open fun getBoundingClass( + context: JavaContext, + call: UCallExpression, + method: PsiMethod, + ): PsiType? = null + + protected fun getItemType(type: PsiType, container: String): PsiClassType? { + val supers = getParentTypes(type).mapNotNull { it as? PsiClassType } + val containerType = supers.firstOrNull { it.rawType().canonicalText == container } + ?: return null + val itemType = containerType.parameters.getOrNull(0) ?: return null + // TODO: Expand to other types, see PsiTypeVisitor + return when (itemType) { + is PsiClassType -> itemType + is PsiWildcardType -> itemType.bound as PsiClassType + else -> null + } + } + + /** + * Tries to obtain the type expected by the "receiving" end given a certain {@link UElement}. + * + * This could be an assignment, an argument passed to a method call, to a constructor call, a + * type cast, etc. If no receiving end is found, the type of the UExpression itself is returned. + */ + protected fun getReceivingType(expression: UElement): PsiType? { + val parent = expression.uastParent + var type = when (parent) { + is UCallExpression -> { + val i = parent.valueArguments.indexOf(expression) + val psiCall = parent.sourcePsi as? PsiCallExpression ?: return null + val typeSubstitutor = psiCall.resolveMethodGenerics().substitutor + val method = psiCall.resolveMethod()!! + method.getSignature(typeSubstitutor).parameterTypes[i] + } + is UVariable -> parent.type + is UExpression -> parent.getExpressionType() + else -> null + } + if (type == null && expression is UExpression) { + type = expression.getExpressionType() + } + return type + } + + protected fun filter(type: PsiType?): PsiType? { + // It's important that PsiIntersectionType case is above the one that check the type in + // rejects, because for intersect types, the canonicalText is one of the terms. + if (type is PsiIntersectionType) { + return type.conjuncts.mapNotNull(this::filter).firstOrNull() + } + if (type == null || type.canonicalText in rejects) { + return null + } + if (type is PsiClassType && type.resolve() is PsiTypeParameter) { + return null + } + return type + } + + private fun getParentTypes(type: PsiType): Set<PsiType> = + type.superTypes.flatMap(::getParentTypes).toSet() + type + + protected fun getParcelFix(location: Location, method: String, arguments: String) = + LintFix + .create() + .name("Migrate to safer Parcel.$method() API") + .replace() + .range(location) + .pattern("$method\\s*\\(((?:.|\\n)*)\\)") + .with("\\k<1>$arguments") + .autoFix() + .build() +} + +/** + * This class derives the type to be appended by inferring the generic type of the {@code container} + * type (eg. "java.util.List") of the {@code argument}-th argument. + */ +class ContainerArgumentMigrator( + method: Method, + private val argument: Int, + private val container: String, + rejects: Set<String> = emptySet(), +) : CallMigrator(method, rejects) { + override fun getBoundingClass( + context: JavaContext, call: UCallExpression, method: PsiMethod + ): PsiType? { + val firstParamType = call.valueArguments[argument].getExpressionType() ?: return null + return getItemType(firstParamType, container)!! + } + + /** + * We need to insert a casting construct in the class parameter. For example: + * (Class<Foo<Bar>>) (Class<?>) Foo.class. + * This is needed for when the arguments of the conflict (eg. when there is List<Foo<Bar>> and + * class type is Class<Foo?). + */ + override fun getArgumentSuffix(type: PsiClassType): String { + if (type.parameters.isNotEmpty()) { + val rawType = type.rawType() + return ", (Class<${type.canonicalText}>) (Class<?>) ${rawType.canonicalText}.class" + } + return super.getArgumentSuffix(type) + } +} + +/** + * This class derives the type to be appended by inferring the generic type of the {@code container} + * type (eg. "java.util.List") of the return type of the method. + */ +class ContainerReturnMigrator( + method: Method, + private val container: String, + rejects: Set<String> = emptySet(), +) : CallMigrator(method, rejects) { + override fun getBoundingClass( + context: JavaContext, call: UCallExpression, method: PsiMethod + ): PsiType? { + val type = getReceivingType(call.uastParent!!) ?: return null + return getItemType(type, container) + } +} + +/** + * This class derives the type to be appended by inferring the expected type for the method result. + */ +class ReturnMigrator( + method: Method, + rejects: Set<String> = emptySet(), +) : CallMigrator(method, rejects) { + override fun getBoundingClass( + context: JavaContext, call: UCallExpression, method: PsiMethod + ): PsiType? { + return getReceivingType(call.uastParent!!) + } +} + +/** + * This class appends the class loader and the class object by deriving the type from the method + * result. + */ +class ReturnMigratorWithClassLoader( + method: Method, + rejects: Set<String> = emptySet(), +) : CallMigrator(method, rejects) { + override fun getBoundingClass( + context: JavaContext, call: UCallExpression, method: PsiMethod + ): PsiType? { + return getReceivingType(call.uastParent!!) + } + + override fun getArgumentSuffix(type: PsiClassType): String = + "${type.rawType().canonicalText}.class.getClassLoader(), " + + "${type.rawType().canonicalText}.class" + +} + +/** + * This class derives the type to be appended by inferring the expected array type + * for the method result. + */ +class ArrayReturnMigrator( + method: Method, + rejects: Set<String> = emptySet(), +) : CallMigrator(method, rejects) { + override fun getBoundingClass( + context: JavaContext, call: UCallExpression, method: PsiMethod + ): PsiType? { + val type = getReceivingType(call.uastParent!!) + return (type as? PsiArrayType)?.componentType + } +} diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/Method.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/Method.kt new file mode 100644 index 000000000000..0826e8e74431 --- /dev/null +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/Method.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.parcel + +data class Method( + val params: List<String>, + val clazz: String, + val name: String, + val parameters: List<String> +) { + constructor( + clazz: String, + name: String, + parameters: List<String> + ) : this( + listOf(), clazz, name, parameters + ) + + val signature: String + get() { + val prefix = if (params.isEmpty()) "" else "${params.joinToString(", ", "<", ">")} " + return "$prefix$clazz.$name(${parameters.joinToString()})" + } + + val className: String by lazy { + clazz.split(".").last() + } +} diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt new file mode 100644 index 000000000000..f92826316be4 --- /dev/null +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.parcel + +import com.android.tools.lint.detector.api.* +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiSubstitutor +import com.intellij.psi.PsiType +import com.intellij.psi.PsiTypeParameter +import org.jetbrains.uast.UCallExpression +import java.util.* + +@Suppress("UnstableApiUsage") +class SaferParcelChecker : Detector(), SourceCodeScanner { + override fun getApplicableMethodNames(): List<String> = + MIGRATORS + .map(CallMigrator::method) + .map(Method::name) + + override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { + if (!isAtLeastT(context)) return + val signature = getSignature(method) + val migrator = MIGRATORS.firstOrNull { it.method.signature == signature } ?: return + migrator.report(context, node, method) + } + + private fun getSignature(method: PsiMethod): String { + val name = UastLintUtils.getQualifiedName(method) + val signature = method.getSignature(PsiSubstitutor.EMPTY) + val parameters = + signature.parameterTypes.joinToString(transform = PsiType::getCanonicalText) + val types = signature.typeParameters.map(PsiTypeParameter::getName) + val prefix = if (types.isEmpty()) "" else types.joinToString(", ", "<", ">") + " " + return "$prefix$name($parameters)" + } + + private fun isAtLeastT(context: Context): Boolean { + val project = if (context.isGlobalAnalysis()) context.mainProject else context.project + return project.isAndroidProject && project.minSdkVersion.featureLevel >= 33 + } + + companion object { + @JvmField + val ISSUE_UNSAFE_API_USAGE: Issue = Issue.create( + id = "UnsafeParcelApi", + briefDescription = "Use of unsafe deserialization API", + explanation = """ + You are using a deprecated deserialization API that doesn't accept the expected class as\ + a parameter. This means that unexpected classes could be instantiated and\ + unexpected code executed. + + Please migrate to the safer alternative that takes an extra Class<T> parameter. + """, + category = Category.SECURITY, + priority = 8, + severity = Severity.WARNING, + + implementation = Implementation( + SaferParcelChecker::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + // Parcel + private val PARCEL_METHOD_READ_SERIALIZABLE = Method("android.os.Parcel", "readSerializable", listOf()) + private val PARCEL_METHOD_READ_ARRAY_LIST = Method("android.os.Parcel", "readArrayList", listOf("java.lang.ClassLoader")) + private val PARCEL_METHOD_READ_LIST = Method("android.os.Parcel", "readList", listOf("java.util.List", "java.lang.ClassLoader")) + private val PARCEL_METHOD_READ_PARCELABLE = Method(listOf("T"), "android.os.Parcel", "readParcelable", listOf("java.lang.ClassLoader")) + private val PARCEL_METHOD_READ_PARCELABLE_LIST = Method(listOf("T"), "android.os.Parcel", "readParcelableList", listOf("java.util.List<T>", "java.lang.ClassLoader")) + private val PARCEL_METHOD_READ_SPARSE_ARRAY = Method(listOf("T"), "android.os.Parcel", "readSparseArray", listOf("java.lang.ClassLoader")) + private val PARCEL_METHOD_READ_ARRAY = Method("android.os.Parcel", "readArray", listOf("java.lang.ClassLoader")) + private val PARCEL_METHOD_READ_PARCELABLE_ARRAY = Method("android.os.Parcel", "readParcelableArray", listOf("java.lang.ClassLoader")) + + // Bundle + private val BUNDLE_METHOD_GET_SERIALIZABLE = Method("android.os.Bundle", "getSerializable", listOf("java.lang.String")) + private val BUNDLE_METHOD_GET_PARCELABLE = Method(listOf("T"), "android.os.Bundle", "getParcelable", listOf("java.lang.String")) + private val BUNDLE_METHOD_GET_PARCELABLE_ARRAY_LIST = Method(listOf("T"), "android.os.Bundle", "getParcelableArrayList", listOf("java.lang.String")) + private val BUNDLE_METHOD_GET_PARCELABLE_ARRAY = Method("android.os.Bundle", "getParcelableArray", listOf("java.lang.String")) + private val BUNDLE_METHOD_GET_SPARSE_PARCELABLE_ARRAY = Method(listOf("T"), "android.os.Bundle", "getSparseParcelableArray", listOf("java.lang.String")) + + // Intent + private val INTENT_METHOD_GET_SERIALIZABLE_EXTRA = Method("android.content.Intent", "getSerializableExtra", listOf("java.lang.String")) + private val INTENT_METHOD_GET_PARCELABLE_EXTRA = Method(listOf("T"), "android.content.Intent", "getParcelableExtra", listOf("java.lang.String")) + private val INTENT_METHOD_GET_PARCELABLE_ARRAY_EXTRA = Method("android.content.Intent", "getParcelableArrayExtra", listOf("java.lang.String")) + private val INTENT_METHOD_GET_PARCELABLE_ARRAY_LIST_EXTRA = Method(listOf("T"), "android.content.Intent", "getParcelableArrayListExtra", listOf("java.lang.String")) + + // TODO: Write migrators for methods below + private val PARCEL_METHOD_READ_PARCELABLE_CREATOR = Method("android.os.Parcel", "readParcelableCreator", listOf("java.lang.ClassLoader")) + + private val MIGRATORS = listOf( + ReturnMigrator(PARCEL_METHOD_READ_PARCELABLE, setOf("android.os.Parcelable")), + ContainerArgumentMigrator(PARCEL_METHOD_READ_LIST, 0, "java.util.List"), + ContainerReturnMigrator(PARCEL_METHOD_READ_ARRAY_LIST, "java.util.Collection"), + ContainerReturnMigrator(PARCEL_METHOD_READ_SPARSE_ARRAY, "android.util.SparseArray"), + ContainerArgumentMigrator(PARCEL_METHOD_READ_PARCELABLE_LIST, 0, "java.util.List"), + ReturnMigratorWithClassLoader(PARCEL_METHOD_READ_SERIALIZABLE), + ArrayReturnMigrator(PARCEL_METHOD_READ_ARRAY, setOf("java.lang.Object")), + ArrayReturnMigrator(PARCEL_METHOD_READ_PARCELABLE_ARRAY, setOf("android.os.Parcelable")), + + ReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE, setOf("android.os.Parcelable")), + ContainerReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE_ARRAY_LIST, "java.util.Collection", setOf("android.os.Parcelable")), + ArrayReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE_ARRAY, setOf("android.os.Parcelable")), + ContainerReturnMigrator(BUNDLE_METHOD_GET_SPARSE_PARCELABLE_ARRAY, "android.util.SparseArray", setOf("android.os.Parcelable")), + ReturnMigrator(BUNDLE_METHOD_GET_SERIALIZABLE, setOf("java.io.Serializable")), + + ReturnMigrator(INTENT_METHOD_GET_PARCELABLE_EXTRA, setOf("android.os.Parcelable")), + ContainerReturnMigrator(INTENT_METHOD_GET_PARCELABLE_ARRAY_LIST_EXTRA, "java.util.Collection", setOf("android.os.Parcelable")), + ArrayReturnMigrator(INTENT_METHOD_GET_PARCELABLE_ARRAY_EXTRA, setOf("android.os.Parcelable")), + ReturnMigrator(INTENT_METHOD_GET_SERIALIZABLE_EXTRA, setOf("java.io.Serializable")), + ) + } +} diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt index e1a5c613dee1..d90f3e31baf9 100644 --- a/tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt +++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt @@ -27,12 +27,13 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { override fun getDetector(): Detector = CallingIdentityTokenDetector() override fun getIssues(): List<Issue> = listOf( - CallingIdentityTokenDetector.ISSUE_UNUSED_TOKEN, - CallingIdentityTokenDetector.ISSUE_NON_FINAL_TOKEN, - CallingIdentityTokenDetector.ISSUE_NESTED_CLEAR_IDENTITY_CALLS, - CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK, - CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY, - CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY + CallingIdentityTokenDetector.ISSUE_UNUSED_TOKEN, + CallingIdentityTokenDetector.ISSUE_NON_FINAL_TOKEN, + CallingIdentityTokenDetector.ISSUE_NESTED_CLEAR_IDENTITY_CALLS, + CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK, + CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY, + CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY, + CallingIdentityTokenDetector.ISSUE_RESULT_OF_CLEAR_IDENTITY_CALL_NOT_STORED_IN_VARIABLE ) override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) @@ -41,8 +42,8 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { fun testDoesNotDetectIssuesInCorrectScenario() { lint().files( - java( - """ + java( + """ package test.pkg; import android.os.Binder; public class TestClass1 extends Binder { @@ -62,22 +63,29 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { } finally { restoreCallingIdentity(token3); } + final Long token4 = true ? Binder.clearCallingIdentity() : null; + try { + } finally { + if (token4 != null) { + restoreCallingIdentity(token4); + } + } } } """ - ).indented(), - *stubs + ).indented(), + *stubs ) - .run() - .expectClean() + .run() + .expectClean() } /** Unused token issue tests */ fun testDetectsUnusedTokens() { lint().files( - java( - """ + java( + """ package test.pkg; import android.os.Binder; public class TestClass1 extends Binder { @@ -101,12 +109,12 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { } } """ - ).indented(), - *stubs + ).indented(), + *stubs ) - .run() - .expect( - """ + .run() + .expect( + """ src/test/pkg/TestClass1.java:5: Warning: token1 has not been used to \ restore the calling identity. Introduce a try-finally after the \ declaration and call Binder.restoreCallingIdentity(token1) in finally or \ @@ -127,13 +135,13 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 0 errors, 3 warnings """.addLineContinuation() - ) + ) } fun testDetectsUnusedTokensInScopes() { lint().files( - java( - """ + java( + """ package test.pkg; import android.os.Binder; public class TestClass1 { @@ -152,12 +160,12 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { } } """ - ).indented(), - *stubs + ).indented(), + *stubs ) - .run() - .expect( - """ + .run() + .expect( + """ src/test/pkg/TestClass1.java:5: Warning: token has not been used to \ restore the calling identity. Introduce a try-finally after the \ declaration and call Binder.restoreCallingIdentity(token) in finally or \ @@ -166,13 +174,13 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 0 errors, 1 warnings """.addLineContinuation() - ) + ) } fun testDoesNotDetectUsedTokensInScopes() { lint().files( - java( - """ + java( + """ package test.pkg; import android.os.Binder; public class TestClass1 { @@ -192,17 +200,17 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { } } """ - ).indented(), - *stubs + ).indented(), + *stubs ) - .run() - .expectClean() + .run() + .expectClean() } fun testDetectsUnusedTokensWithSimilarNamesInScopes() { lint().files( - java( - """ + java( + """ package test.pkg; import android.os.Binder; public class TestClass1 { @@ -220,12 +228,12 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { } } """ - ).indented(), - *stubs + ).indented(), + *stubs ) - .run() - .expect( - """ + .run() + .expect( + """ src/test/pkg/TestClass1.java:5: Warning: token has not been used to \ restore the calling identity. Introduce a try-finally after the \ declaration and call Binder.restoreCallingIdentity(token) in finally or \ @@ -240,15 +248,15 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 0 errors, 2 warnings """.addLineContinuation() - ) + ) } /** Non-final token issue tests */ fun testDetectsNonFinalTokens() { lint().files( - java( - """ + java( + """ package test.pkg; import android.os.Binder; public class TestClass1 extends Binder { @@ -271,12 +279,12 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { } } """ - ).indented(), - *stubs + ).indented(), + *stubs ) - .run() - .expect( - """ + .run() + .expect( + """ src/test/pkg/TestClass1.java:5: Warning: token1 is a non-final token from \ Binder.clearCallingIdentity(). Add final keyword to token1. \ [NonFinalTokenOfOriginalCallingIdentity] @@ -294,7 +302,7 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 0 errors, 3 warnings """.addLineContinuation() - ) + ) } /** Nested clearCallingIdentity() calls issue tests */ @@ -302,8 +310,8 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { fun testDetectsNestedClearCallingIdentityCalls() { // Pattern: clear - clear - clear - restore - restore - restore lint().files( - java( - """ + java( + """ package test.pkg; import android.os.Binder; public class TestClass1 extends Binder { @@ -326,12 +334,12 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { } } """ - ).indented(), - *stubs + ).indented(), + *stubs ) - .run() - .expect( - """ + .run() + .expect( + """ src/test/pkg/TestClass1.java:7: Warning: The calling identity has already \ been cleared and returned into token1. Move token2 declaration after \ restoring the calling identity with Binder.restoreCallingIdentity(token1). \ @@ -348,15 +356,15 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { src/test/pkg/TestClass1.java:5: Location of the token1 declaration. 0 errors, 2 warnings """.addLineContinuation() - ) + ) } /** clearCallingIdentity() not followed by try-finally issue tests */ fun testDetectsClearIdentityCallNotFollowedByTryFinally() { lint().files( - java( - """ + java( + """ package test.pkg; import android.os.Binder; public class TestClass1 extends Binder{ @@ -397,12 +405,12 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { } } """ - ).indented(), - *stubs + ).indented(), + *stubs ) - .run() - .expect( - """ + .run() + .expect( + """ src/test/pkg/TestClass1.java:5: Warning: You cleared the calling identity \ and returned the result into token, but the next statement is not a \ try-finally statement. Define a try-finally block after token declaration \ @@ -445,15 +453,15 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 0 errors, 5 warnings """.addLineContinuation() - ) + ) } /** restoreCallingIdentity() call not in finally block issue tests */ fun testDetectsRestoreCallingIdentityCallNotInFinally() { lint().files( - java( - """ + java( + """ package test.pkg; import android.os.Binder; public class TestClass1 extends Binder { @@ -482,12 +490,12 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { } } """ - ).indented(), - *stubs + ).indented(), + *stubs ) - .run() - .expect( - """ + .run() + .expect( + """ src/test/pkg/TestClass1.java:10: Warning: \ Binder.restoreCallingIdentity(token) is not an immediate child of the \ finally block of the try statement after token declaration. Surround the c\ @@ -511,13 +519,13 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 0 errors, 3 warnings """.addLineContinuation() - ) + ) } fun testDetectsRestoreCallingIdentityCallNotInFinallyInScopes() { lint().files( - java( - """ + java( + """ package test.pkg; import android.os.Binder; public class TestClass1 extends Binder { @@ -560,12 +568,12 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { } } """ - ).indented(), - *stubs + ).indented(), + *stubs ) - .run() - .expect( - """ + .run() + .expect( + """ src/test/pkg/TestClass1.java:11: Warning: \ Binder.restoreCallingIdentity(token1) is not an immediate child of the \ finally block of the try statement after token1 declaration. Surround the \ @@ -596,15 +604,15 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 0 errors, 4 warnings """.addLineContinuation() - ) + ) } /** Use of caller-aware methods after clearCallingIdentity() issue tests */ fun testDetectsUseOfCallerAwareMethodsWithClearedIdentityIssuesInScopes() { lint().files( - java( - """ + java( + """ package test.pkg; import android.os.Binder; import android.os.UserHandle; @@ -632,12 +640,12 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { } } """ - ).indented(), - *stubs + ).indented(), + *stubs ) - .run() - .expect( - """ + .run() + .expect( + """ src/test/pkg/TestClass1.java:8: Warning: You cleared the original identity \ with Binder.clearCallingIdentity() and returned into token, so \ getCallingPid() will be using your own identity instead of the \ @@ -736,13 +744,58 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 0 errors, 12 warnings """.addLineContinuation() - ) + ) + } + + /** Result of Binder.clearCallingIdentity() is not stored in a variable issue tests */ + + fun testDetectsResultOfClearIdentityCallNotStoredInVariable() { + lint().files( + java( + """ + package test.pkg; + import android.os.Binder; + public class TestClass1 extends Binder { + private void testMethod() { + Binder.clearCallingIdentity(); + android.os.Binder.clearCallingIdentity(); + clearCallingIdentity(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/test/pkg/TestClass1.java:5: Warning: You cleared the original identity \ + with Binder.clearCallingIdentity() but did not store the result in a \ + variable. You need to store the result in a variable and restore it later. \ + [ResultOfClearIdentityCallNotStoredInVariable] + Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:6: Warning: You cleared the original identity \ + with android.os.Binder.clearCallingIdentity() but did not store the result \ + in a variable. You need to store the result in a variable and restore it \ + later. [ResultOfClearIdentityCallNotStoredInVariable] + android.os.Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:7: Warning: You cleared the original identity \ + with clearCallingIdentity() but did not store the result in a variable. \ + You need to store the result in a variable and restore it later. \ + [ResultOfClearIdentityCallNotStoredInVariable] + clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 3 warnings + """.addLineContinuation() + ) } /** Stubs for classes used for testing */ private val binderStub: TestFile = java( - """ + """ package android.os; public class Binder { public static final native long clearCallingIdentity() { @@ -767,7 +820,7 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { ).indented() private val userHandleStub: TestFile = java( - """ + """ package android.os; import android.annotation.AppIdInt; import android.annotation.UserIdInt; @@ -792,7 +845,7 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { ).indented() private val userIdIntStub: TestFile = java( - """ + """ package android.annotation; public @interface UserIdInt { } @@ -800,7 +853,7 @@ class CallingIdentityTokenDetectorTest : LintDetectorTest() { ).indented() private val appIdIntStub: TestFile = java( - """ + """ package android.annotation; public @interface AppIdInt { } diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt index e72f38416310..e72f38416310 100644 --- a/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt +++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt new file mode 100644 index 000000000000..a70644ab8532 --- /dev/null +++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PackageVisibilityDetectorTest.kt @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class PackageVisibilityDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = PackageVisibilityDetector() + + override fun getIssues(): MutableList<Issue> = mutableListOf( + PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + fun testDetectIssuesParameterDoesNotApplyPackageVisibilityFilters() { + lint().files(java( + """ + package com.android.server.lint.test; + import android.internal.test.IFoo; + + public class TestClass extends IFoo.Stub { + @Override + public boolean hasPackage(String packageName) { + return packageName != null; + } + } + """).indented(), *stubs + ).run().expect( + """ + src/com/android/server/lint/test/TestClass.java:6: Warning: \ + Api: hasPackage contains a package name parameter: packageName does not apply \ + package visibility filtering rules. \ + [ApiMightLeakAppVisibility] + public boolean hasPackage(String packageName) { + ~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testDoesNotDetectIssuesApiInvokesAppOps() { + lint().files(java( + """ + package com.android.server.lint.test; + import android.app.AppOpsManager; + import android.os.Binder; + import android.internal.test.IFoo; + + public class TestClass extends IFoo.Stub { + private AppOpsManager mAppOpsManager; + + @Override + public boolean hasPackage(String packageName) { + checkPackage(packageName); + return packageName != null; + } + + private void checkPackage(String packageName) { + mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName); + } + } + """ + ).indented(), *stubs).run().expectClean() + } + + fun testDoesNotDetectIssuesApiInvokesEnforcePermission() { + lint().files(java( + """ + package com.android.server.lint.test; + import android.content.Context; + import android.internal.test.IFoo; + + public class TestClass extends IFoo.Stub { + private Context mContext; + + @Override + public boolean hasPackage(String packageName) { + enforcePermission(); + return packageName != null; + } + + private void enforcePermission() { + mContext.checkCallingPermission( + android.Manifest.permission.ACCESS_INPUT_FLINGER); + } + } + """ + ).indented(), *stubs).run().expectClean() + } + + fun testDoesNotDetectIssuesApiInvokesPackageManager() { + lint().files(java( + """ + package com.android.server.lint.test; + import android.content.pm.PackageInfo; + import android.content.pm.PackageManager; + import android.internal.test.IFoo; + + public class TestClass extends IFoo.Stub { + private PackageManager mPackageManager; + + @Override + public boolean hasPackage(String packageName) { + return getPackageInfo(packageName) != null; + } + + private PackageInfo getPackageInfo(String packageName) { + try { + return mPackageManager.getPackageInfo(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + } + """ + ).indented(), *stubs).run().expectClean() + } + + fun testDetectIssuesApiInvokesPackageManagerAndClearCallingIdentify() { + lint().files(java( + """ + package com.android.server.lint.test; + import android.content.pm.PackageInfo; + import android.content.pm.PackageManager; + import android.internal.test.IFoo;import android.os.Binder; + + public class TestClass extends IFoo.Stub { + private PackageManager mPackageManager; + + @Override + public boolean hasPackage(String packageName) { + return getPackageInfo(packageName) != null; + } + + private PackageInfo getPackageInfo(String packageName) { + long token = Binder.clearCallingIdentity(); + try { + try { + return mPackageManager.getPackageInfo(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } finally{ + Binder.restoreCallingIdentity(token); + } + } + } + """).indented(), *stubs + ).run().expect( + """ + src/com/android/server/lint/test/TestClass.java:10: Warning: \ + Api: hasPackage contains a package name parameter: packageName does not apply \ + package visibility filtering rules. \ + [ApiMightLeakAppVisibility] + public boolean hasPackage(String packageName) { + ~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testDoesNotDetectIssuesApiNotSystemPackagePrefix() { + lint().files(java( + """ + package com.test.not.system.prefix; + import android.internal.test.IFoo; + + public class TestClass extends IFoo.Stub { + @Override + public boolean hasPackage(String packageName) { + return packageName != null; + } + } + """ + ).indented(), *stubs).run().expectClean() + } + + private val contextStub: TestFile = java( + """ + package android.content; + + public abstract class Context { + public abstract int checkCallingPermission(String permission); + } + """ + ).indented() + + private val appOpsManagerStub: TestFile = java( + """ + package android.app; + + public class AppOpsManager { + public void checkPackage(int uid, String packageName) { + } + } + """ + ).indented() + + private val packageManagerStub: TestFile = java( + """ + package android.content.pm; + import android.content.pm.PackageInfo; + + public abstract class PackageManager { + public static class NameNotFoundException extends AndroidException { + } + + public abstract PackageInfo getPackageInfo(String packageName, int flags) + throws NameNotFoundException; + } + """ + ).indented() + + private val packageInfoStub: TestFile = java( + """ + package android.content.pm; + public class PackageInfo {} + """ + ).indented() + + private val binderStub: TestFile = java( + """ + package android.os; + + public class Binder { + public static final native long clearCallingIdentity(); + public static final native void restoreCallingIdentity(long token); + public static final native int getCallingUid(); + } + """ + ).indented() + + private val interfaceIFooStub: TestFile = java( + """ + package android.internal.test; + import android.os.Binder; + + public interface IFoo { + boolean hasPackage(String packageName); + public abstract static class Stub extends Binder implements IFoo { + } + } + """ + ).indented() + + private val stubs = arrayOf(contextStub, appOpsManagerStub, packageManagerStub, + packageInfoStub, binderStub, interfaceIFooStub) + + // Substitutes "backslash + new line" with an empty string to imitate line continuation + private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "") +} diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt new file mode 100644 index 000000000000..e686695ca804 --- /dev/null +++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt @@ -0,0 +1,823 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.parcel + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.checks.infrastructure.TestMode +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class SaferParcelCheckerTest : LintDetectorTest() { + override fun getDetector(): Detector = SaferParcelChecker() + + override fun getIssues(): List<Issue> = listOf( + SaferParcelChecker.ISSUE_UNSAFE_API_USAGE + ) + + override fun lint(): TestLintTask = + super.lint() + .allowMissingSdk(true) + // We don't do partial analysis in the platform + .skipTestModes(TestMode.PARTIAL) + + /** Parcel Tests */ + + fun testParcelDetectUnsafeReadSerializable() { + lint() + .files( + java( + """ + package test.pkg; + import android.os.Parcel; + import java.io.Serializable; + + public class TestClass { + private TestClass(Parcel p) { + Serializable ans = p.readSerializable(); + } + } + """ + ).indented(), + *includes + ) + .expectIdenticalTestModeOutput(false) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readSerializable() \ + API usage [UnsafeParcelApi] + Serializable ans = p.readSerializable(); + ~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testParcelDoesNotDetectSafeReadSerializable() { + lint() + .files( + java( + """ + package test.pkg; + import android.os.Parcel; + import java.io.Serializable; + + public class TestClass { + private TestClass(Parcel p) { + String ans = p.readSerializable(null, String.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + fun testParcelDetectUnsafeReadArrayList() { + lint() + .files( + java( + """ + package test.pkg; + import android.os.Parcel; + + public class TestClass { + private TestClass(Parcel p) { + ArrayList ans = p.readArrayList(null); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect( + """ + src/test/pkg/TestClass.java:6: Warning: Unsafe Parcel.readArrayList() API \ + usage [UnsafeParcelApi] + ArrayList ans = p.readArrayList(null); + ~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testParcelDoesNotDetectSafeReadArrayList() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + + public class TestClass { + private TestClass(Parcel p) { + ArrayList<Intent> ans = p.readArrayList(null, Intent.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + fun testParcelDetectUnsafeReadList() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + import java.util.List; + + public class TestClass { + private TestClass(Parcel p) { + List<Intent> list = new ArrayList<Intent>(); + p.readList(list, null); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect( + """ + src/test/pkg/TestClass.java:9: Warning: Unsafe Parcel.readList() API usage \ + [UnsafeParcelApi] + p.readList(list, null); + ~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testDParceloesNotDetectSafeReadList() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + import java.util.List; + + public class TestClass { + private TestClass(Parcel p) { + List<Intent> list = new ArrayList<Intent>(); + p.readList(list, null, Intent.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + fun testParcelDetectUnsafeReadParcelable() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + + public class TestClass { + private TestClass(Parcel p) { + Intent ans = p.readParcelable(null); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readParcelable() API \ + usage [UnsafeParcelApi] + Intent ans = p.readParcelable(null); + ~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testParcelDoesNotDetectSafeReadParcelable() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + + public class TestClass { + private TestClass(Parcel p) { + Intent ans = p.readParcelable(null, Intent.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + fun testParcelDetectUnsafeReadParcelableList() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + import java.util.List; + + public class TestClass { + private TestClass(Parcel p) { + List<Intent> list = new ArrayList<Intent>(); + List<Intent> ans = p.readParcelableList(list, null); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect( + """ + src/test/pkg/TestClass.java:9: Warning: Unsafe Parcel.readParcelableList() \ + API usage [UnsafeParcelApi] + List<Intent> ans = p.readParcelableList(list, null); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testParcelDoesNotDetectSafeReadParcelableList() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + import java.util.List; + + public class TestClass { + private TestClass(Parcel p) { + List<Intent> list = new ArrayList<Intent>(); + List<Intent> ans = + p.readParcelableList(list, null, Intent.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + fun testParcelDetectUnsafeReadSparseArray() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + import android.util.SparseArray; + + public class TestClass { + private TestClass(Parcel p) { + SparseArray<Intent> ans = p.readSparseArray(null); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect( + """ + src/test/pkg/TestClass.java:8: Warning: Unsafe Parcel.readSparseArray() API\ + usage [UnsafeParcelApi] + SparseArray<Intent> ans = p.readSparseArray(null); + ~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testParcelDoesNotDetectSafeReadSparseArray() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + import android.util.SparseArray; + + public class TestClass { + private TestClass(Parcel p) { + SparseArray<Intent> ans = + p.readSparseArray(null, Intent.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + fun testParcelDetectUnsafeReadSArray() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + + public class TestClass { + private TestClass(Parcel p) { + Intent[] ans = p.readArray(null); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readArray() API\ + usage [UnsafeParcelApi] + Intent[] ans = p.readArray(null); + ~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testParcelDoesNotDetectSafeReadArray() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + + public class TestClass { + private TestClass(Parcel p) { + Intent[] ans = p.readArray(null, Intent.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + fun testParcelDetectUnsafeReadParcelableSArray() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + + public class TestClass { + private TestClass(Parcel p) { + Intent[] ans = p.readParcelableArray(null); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readParcelableArray() API\ + usage [UnsafeParcelApi] + Intent[] ans = p.readParcelableArray(null); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testParcelDoesNotDetectSafeReadParcelableArray() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Parcel; + + public class TestClass { + private TestClass(Parcel p) { + Intent[] ans = p.readParcelableArray(null, Intent.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + /** Bundle Tests */ + + fun testBundleDetectUnsafeGetParcelable() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Bundle; + + public class TestClass { + private TestClass(Bundle b) { + Intent ans = b.getParcelable("key"); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelable() API usage [UnsafeParcelApi] + Intent ans = b.getParcelable("key"); + ~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testBundleDoesNotDetectSafeGetParcelable() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Bundle; + + public class TestClass { + private TestClass(Bundle b) { + Intent ans = b.getParcelable("key", Intent.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + fun testBundleDetectUnsafeGetParcelableArrayList() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Bundle; + + public class TestClass { + private TestClass(Bundle b) { + ArrayList<Intent> ans = b.getParcelableArrayList("key"); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelableArrayList() API usage [UnsafeParcelApi] + ArrayList<Intent> ans = b.getParcelableArrayList("key"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testBundleDoesNotDetectSafeGetParcelableArrayList() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Bundle; + + public class TestClass { + private TestClass(Bundle b) { + ArrayList<Intent> ans = b.getParcelableArrayList("key", Intent.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + fun testBundleDetectUnsafeGetParcelableArray() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Bundle; + + public class TestClass { + private TestClass(Bundle b) { + Intent[] ans = b.getParcelableArray("key"); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelableArray() API usage [UnsafeParcelApi] + Intent[] ans = b.getParcelableArray("key"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testBundleDoesNotDetectSafeGetParcelableArray() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Bundle; + + public class TestClass { + private TestClass(Bundle b) { + Intent[] ans = b.getParcelableArray("key", Intent.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + fun testBundleDetectUnsafeGetSparseParcelableArray() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Bundle; + + public class TestClass { + private TestClass(Bundle b) { + SparseArray<Intent> ans = b.getSparseParcelableArray("key"); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect( + """ + src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getSparseParcelableArray() API usage [UnsafeParcelApi] + SparseArray<Intent> ans = b.getSparseParcelableArray("key"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testBundleDoesNotDetectSafeGetSparseParcelableArray() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + import android.os.Bundle; + + public class TestClass { + private TestClass(Bundle b) { + SparseArray<Intent> ans = b.getSparseParcelableArray("key", Intent.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + /** Intent Tests */ + + fun testIntentDetectUnsafeGetParcelableExtra() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + + public class TestClass { + private TestClass(Intent i) { + Intent ans = i.getParcelableExtra("name"); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect( + """ + src/test/pkg/TestClass.java:6: Warning: Unsafe Intent.getParcelableExtra() API usage [UnsafeParcelApi] + Intent ans = i.getParcelableExtra("name"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testIntentDoesNotDetectSafeGetParcelableExtra() { + lint() + .files( + java( + """ + package test.pkg; + import android.content.Intent; + + public class TestClass { + private TestClass(Intent i) { + Intent ans = i.getParcelableExtra("name", Intent.class); + } + } + """ + ).indented(), + *includes + ) + .run() + .expect("No warnings.") + } + + + /** Stubs for classes used for testing */ + + + private val includes = + arrayOf( + manifest().minSdk("33"), + java( + """ + package android.os; + import java.util.ArrayList; + import java.util.List; + import java.util.Map; + import java.util.HashMap; + + public final class Parcel { + // Deprecated + public Object[] readArray(ClassLoader loader) { return null; } + public ArrayList readArrayList(ClassLoader loader) { return null; } + public HashMap readHashMap(ClassLoader loader) { return null; } + public void readList(List outVal, ClassLoader loader) {} + public void readMap(Map outVal, ClassLoader loader) {} + public <T extends Parcelable> T readParcelable(ClassLoader loader) { return null; } + public Parcelable[] readParcelableArray(ClassLoader loader) { return null; } + public Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) { return null; } + public <T extends Parcelable> List<T> readParcelableList(List<T> list, ClassLoader cl) { return null; } + public Serializable readSerializable() { return null; } + public <T> SparseArray<T> readSparseArray(ClassLoader loader) { return null; } + + // Replacements + public <T> T[] readArray(ClassLoader loader, Class<T> clazz) { return null; } + public <T> ArrayList<T> readArrayList(ClassLoader loader, Class<? extends T> clazz) { return null; } + public <K, V> HashMap<K,V> readHashMap(ClassLoader loader, Class<? extends K> clazzKey, Class<? extends V> clazzValue) { return null; } + public <T> void readList(List<? super T> outVal, ClassLoader loader, Class<T> clazz) {} + public <K, V> void readMap(Map<? super K, ? super V> outVal, ClassLoader loader, Class<K> clazzKey, Class<V> clazzValue) {} + public <T> T readParcelable(ClassLoader loader, Class<T> clazz) { return null; } + public <T> T[] readParcelableArray(ClassLoader loader, Class<T> clazz) { return null; } + public <T> Parcelable.Creator<T> readParcelableCreator(ClassLoader loader, Class<T> clazz) { return null; } + public <T> List<T> readParcelableList(List<T> list, ClassLoader cl, Class<T> clazz) { return null; } + public <T> T readSerializable(ClassLoader loader, Class<T> clazz) { return null; } + public <T> SparseArray<T> readSparseArray(ClassLoader loader, Class<? extends T> clazz) { return null; } + } + """ + ).indented(), + java( + """ + package android.os; + import java.util.ArrayList; + import java.util.List; + import java.util.Map; + import java.util.HashMap; + + public final class Bundle { + // Deprecated + public <T extends Parcelable> T getParcelable(String key) { return null; } + public <T extends Parcelable> ArrayList<T> getParcelableArrayList(String key) { return null; } + public Parcelable[] getParcelableArray(String key) { return null; } + public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(String key) { return null; } + + // Replacements + public <T> T getParcelable(String key, Class<T> clazz) { return null; } + public <T> ArrayList<T> getParcelableArrayList(String key, Class<? extends T> clazz) { return null; } + public <T> T[] getParcelableArray(String key, Class<T> clazz) { return null; } + public <T> SparseArray<T> getSparseParcelableArray(String key, Class<? extends T> clazz) { return null; } + + } + """ + ).indented(), + java( + """ + package android.os; + public interface Parcelable {} + """ + ).indented(), + java( + """ + package android.content; + public class Intent implements Parcelable, Cloneable { + // Deprecated + public <T extends Parcelable> T getParcelableExtra(String name) { return null; } + + // Replacements + public <T> T getParcelableExtra(String name, Class<T> clazz) { return null; } + + } + """ + ).indented(), + java( + """ + package android.util; + public class SparseArray<E> implements Cloneable {} + """ + ).indented(), + ) + + // Substitutes "backslash + new line" with an empty string to imitate line continuation + private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "") +} diff --git a/tools/lint/global/Android.bp b/tools/lint/global/Android.bp new file mode 100644 index 000000000000..bedb7bd78a29 --- /dev/null +++ b/tools/lint/global/Android.bp @@ -0,0 +1,65 @@ +// Copyright (C) 2022 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. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_library_host { + name: "AndroidGlobalLintChecker", + srcs: ["checks/src/main/java/**/*.kt"], + plugins: ["auto_service_plugin"], + libs: [ + "auto_service_annotations", + "lint_api", + ], + static_libs: ["AndroidCommonLint"], + kotlincflags: ["-Xjvm-default=all"], + dist: { + targets: ["droid"], + }, +} + +java_test_host { + name: "AndroidGlobalLintCheckerTest", + srcs: ["checks/src/test/java/**/*.kt"], + static_libs: [ + "AndroidGlobalLintChecker", + "junit", + "lint", + "lint_tests", + ], + test_options: { + unit_test: true, + tradefed_options: [ + { + // lint bundles in some classes that were built with older versions + // of libraries, and no longer load. Since tradefed tries to load + // all classes in the jar to look for tests, it crashes loading them. + // Exclude these classes from tradefed's search. + name: "exclude-paths", + value: "org/apache", + }, + { + name: "exclude-paths", + value: "META-INF", + }, + ], + }, +} diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt index a6fd9bba6192..a20266a9b140 100644 --- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 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. @@ -19,21 +19,19 @@ package com.google.android.lint import com.android.tools.lint.client.api.IssueRegistry import com.android.tools.lint.client.api.Vendor import com.android.tools.lint.detector.api.CURRENT_API +import com.google.android.lint.aidl.EnforcePermissionDetector +import com.google.android.lint.aidl.EnforcePermissionHelperDetector +import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector import com.google.auto.service.AutoService @AutoService(IssueRegistry::class) @Suppress("UnstableApiUsage") -class AndroidFrameworkIssueRegistry : IssueRegistry() { +class AndroidGlobalIssueRegistry : IssueRegistry() { override val issues = listOf( - CallingIdentityTokenDetector.ISSUE_UNUSED_TOKEN, - CallingIdentityTokenDetector.ISSUE_NON_FINAL_TOKEN, - CallingIdentityTokenDetector.ISSUE_NESTED_CLEAR_IDENTITY_CALLS, - CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK, - CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY, - CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY, - CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED, EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION, - EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION + EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION, + EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER, + SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT, ) override val api: Int @@ -45,6 +43,6 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() { override val vendor: Vendor = Vendor( vendorName = "Android", feedbackUrl = "http://b/issues/new?component=315013", - contact = "brufino@google.com" + contact = "repsonsible-apis@google.com" ) -} +}
\ No newline at end of file diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt new file mode 100644 index 000000000000..ab6d871d6ea6 --- /dev/null +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.client.api.UElementHandler +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.SourceCodeScanner +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UMethod + +/** + * Abstract class for detectors that look for methods implementing + * generated AIDL interface stubs + */ +abstract class AidlImplementationDetector : Detector(), SourceCodeScanner { + override fun getApplicableUastTypes(): List<Class<out UElement?>> = + listOf(UMethod::class.java) + + override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context) + + private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() { + override fun visitMethod(node: UMethod) { + val interfaceName = getContainingAidlInterface(context, node) + .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return + val body = (node.uastBody as? UBlockExpression) ?: return + visitAidlMethod(context, node, interfaceName, body) + } + } + + abstract fun visitAidlMethod( + context: JavaContext, + node: UMethod, + interfaceName: String, + body: UBlockExpression, + ) +} diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt new file mode 100644 index 000000000000..dcfbe953f955 --- /dev/null +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.aidl + +const val ANNOTATION_ENFORCE_PERMISSION = "android.annotation.EnforcePermission" +const val ANNOTATION_REQUIRES_NO_PERMISSION = "android.annotation.RequiresNoPermission" +const val ANNOTATION_PERMISSION_MANUALLY_ENFORCED = "android.annotation.PermissionManuallyEnforced" + +val AIDL_PERMISSION_ANNOTATIONS = listOf( + ANNOTATION_ENFORCE_PERMISSION, + ANNOTATION_REQUIRES_NO_PERMISSION, + ANNOTATION_PERMISSION_MANUALLY_ENFORCED +) + +const val BINDER_CLASS = "android.os.Binder" +const val IINTERFACE_INTERFACE = "android.os.IInterface" + +const val AIDL_PERMISSION_HELPER_SUFFIX = "_enforcePermission" + +/** + * If a non java (e.g. c++) backend is enabled, the @EnforcePermission + * annotation cannot be used. At time of writing, the mechanism + * is not implemented for non java backends. + * TODO: b/242564874 (have lint know which interfaces have the c++ backend enabled) + * rather than hard coding this list? + */ +val EXCLUDED_CPP_INTERFACES = listOf( + "AdbTransportType", + "FingerprintAndPairDevice", + "IAdbCallback", + "IAdbManager", + "PairDevice", + "IStatsBootstrapAtomService", + "StatsBootstrapAtom", + "StatsBootstrapAtomValue", + "FixedSizeArrayExample", + "PlaybackTrackMetadata", + "RecordTrackMetadata", + "SinkMetadata", + "SourceMetadata", + "IUpdateEngineStable", + "IUpdateEngineStableCallback", + "AudioCapabilities", + "ConfidenceLevel", + "ModelParameter", + "ModelParameterRange", + "Phrase", + "PhraseRecognitionEvent", + "PhraseRecognitionExtra", + "PhraseSoundModel", + "Properties", + "RecognitionConfig", + "RecognitionEvent", + "RecognitionMode", + "RecognitionStatus", + "SoundModel", + "SoundModelType", + "Status", + "IThermalService", + "IPowerManager", + "ITunerResourceManager" +) diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt new file mode 100644 index 000000000000..0baac2c7aacf --- /dev/null +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.client.api.UElementHandler +import com.android.tools.lint.detector.api.AnnotationInfo +import com.android.tools.lint.detector.api.AnnotationOrigin +import com.android.tools.lint.detector.api.AnnotationUsageInfo +import com.android.tools.lint.detector.api.AnnotationUsageType +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.ConstantEvaluator +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiArrayInitializerMemberValue +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import org.jetbrains.uast.UAnnotation +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.toUElement + +/** + * Lint Detector that ensures that any method overriding a method annotated + * with @EnforcePermission is also annotated with the exact same annotation. + * The intent is to surface the effective permission checks to the service + * implementations. + * + * This is done with 2 mechanisms: + * 1. Visit any annotation usage, to ensure that any derived class will have + * the correct annotation on each methods. This is for the top to bottom + * propagation. + * 2. Visit any annotation, to ensure that if a method is annotated, it has + * its ancestor also annotated. This is to avoid having an annotation on a + * Java method without the corresponding annotation on the AIDL interface. + */ +class EnforcePermissionDetector : Detector(), SourceCodeScanner { + + override fun applicableAnnotations(): List<String> { + return listOf(ANNOTATION_ENFORCE_PERMISSION) + } + + override fun getApplicableUastTypes(): List<Class<out UElement>> { + return listOf(UAnnotation::class.java) + } + + private fun annotationValueGetChildren(elem: PsiElement): Array<PsiElement> { + if (elem is PsiArrayInitializerMemberValue) + return elem.getInitializers().map { it as PsiElement }.toTypedArray() + return elem.getChildren() + } + + private fun areAnnotationsEquivalent( + context: JavaContext, + anno1: PsiAnnotation, + anno2: PsiAnnotation + ): Boolean { + if (anno1.qualifiedName != anno2.qualifiedName) { + return false + } + val attr1 = anno1.parameterList.attributes + val attr2 = anno2.parameterList.attributes + if (attr1.size != attr2.size) { + return false + } + for (i in attr1.indices) { + if (attr1[i].name != attr2[i].name) { + return false + } + val value1 = attr1[i].value ?: return false + val value2 = attr2[i].value ?: return false + // Try to compare values directly with each other. + val v1 = ConstantEvaluator.evaluate(context, value1) + val v2 = ConstantEvaluator.evaluate(context, value2) + if (v1 != null && v2 != null) { + if (v1 != v2) { + return false + } + } else { + val children1 = annotationValueGetChildren(value1) + val children2 = annotationValueGetChildren(value2) + if (children1.size != children2.size) { + return false + } + for (j in children1.indices) { + val c1 = ConstantEvaluator.evaluate(context, children1[j]) + val c2 = ConstantEvaluator.evaluate(context, children2[j]) + if (c1 != c2) { + return false + } + } + } + } + return true + } + + private fun compareMethods( + context: JavaContext, + element: UElement, + overridingMethod: PsiMethod, + overriddenMethod: PsiMethod, + checkEquivalence: Boolean = true + ) { + // If method is not from a Stub subclass, this method shouldn't use @EP at all. + // This is handled by EnforcePermissionHelperDetector. + if (!isContainedInSubclassOfStub(context, overridingMethod.toUElement() as? UMethod)) { + return + } + val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION) + val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION) + val location = context.getLocation(element) + val overridingClass = overridingMethod.parent as PsiClass + val overriddenClass = overriddenMethod.parent as PsiClass + val overridingName = "${overridingClass.name}.${overridingMethod.name}" + val overriddenName = "${overriddenClass.name}.${overriddenMethod.name}" + if (overridingAnnotation == null) { + val msg = "The method $overridingName overrides the method $overriddenName which " + + "is annotated with @EnforcePermission. The same annotation must be used " + + "on $overridingName" + context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg) + } else if (overriddenAnnotation == null) { + val msg = "The method $overridingName overrides the method $overriddenName which " + + "is not annotated with @EnforcePermission. The same annotation must be " + + "used on $overriddenName. Did you forget to annotate the AIDL definition?" + context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg) + } else if (checkEquivalence && !areAnnotationsEquivalent( + context, overridingAnnotation, overriddenAnnotation)) { + val msg = "The method $overridingName is annotated with " + + "${overridingAnnotation.text} which differs from the overridden " + + "method $overriddenName: ${overriddenAnnotation.text}. The same " + + "annotation must be used for both methods." + context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg) + } + } + + override fun visitAnnotationUsage( + context: JavaContext, + element: UElement, + annotationInfo: AnnotationInfo, + usageInfo: AnnotationUsageInfo + ) { + if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE && + annotationInfo.origin == AnnotationOrigin.METHOD) { + val overridingMethod = element.sourcePsi as PsiMethod + val overriddenMethod = usageInfo.referenced as PsiMethod + compareMethods(context, element, overridingMethod, overriddenMethod) + } + } + + override fun createUastHandler(context: JavaContext): UElementHandler { + return object : UElementHandler() { + override fun visitAnnotation(node: UAnnotation) { + if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) { + return + } + val method = node.uastParent as? UMethod ?: return + val overridingMethod = method as PsiMethod + val parents = overridingMethod.findSuperMethods() + for (overriddenMethod in parents) { + // The equivalence check can be skipped, if both methods are + // annotated, it will be verified by visitAnnotationUsage. + compareMethods(context, method, overridingMethod, + overriddenMethod, checkEquivalence = false) + } + } + } + } + + companion object { + val EXPLANATION = """ + The @EnforcePermission annotation is used to indicate that the underlying binder code + has already verified the caller's permissions before calling the appropriate method. The + verification code is usually generated by the AIDL compiler, which also takes care of + annotating the generated Java code. + + In order to surface that information to platform developers, the same annotation must be + used on the implementation class or methods. + """ + + val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create( + id = "MissingEnforcePermissionAnnotation", + briefDescription = "Missing @EnforcePermission annotation on Binder method", + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 6, + severity = Severity.ERROR, + implementation = Implementation( + EnforcePermissionDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + val ISSUE_MISMATCHING_ENFORCE_PERMISSION: Issue = Issue.create( + id = "MismatchingEnforcePermissionAnnotation", + briefDescription = "Incorrect @EnforcePermission annotation on Binder method", + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 6, + severity = Severity.ERROR, + implementation = Implementation( + EnforcePermissionDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + } +} diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt new file mode 100644 index 000000000000..25d208db14ec --- /dev/null +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.LintFix +import com.android.tools.lint.detector.api.Location +import com.android.tools.lint.detector.api.UastLintUtils.Companion.getAnnotationBooleanValue +import com.android.tools.lint.detector.api.UastLintUtils.Companion.getAnnotationStringValues +import com.android.tools.lint.detector.api.findSelector +import com.android.tools.lint.detector.api.getUMethod +import com.google.android.lint.findCallExpression +import com.google.android.lint.getPermissionMethodAnnotation +import com.google.android.lint.hasPermissionNameAnnotation +import com.google.android.lint.isPermissionMethodCall +import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiType +import org.jetbrains.kotlin.psi.psiUtil.parameterIndex +import org.jetbrains.uast.UBinaryExpression +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UExpression +import org.jetbrains.uast.UExpressionList +import org.jetbrains.uast.UIfExpression +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.UThrowExpression +import org.jetbrains.uast.UastBinaryOperator +import org.jetbrains.uast.evaluateString +import org.jetbrains.uast.skipParenthesizedExprDown +import org.jetbrains.uast.visitor.AbstractUastVisitor + +/** + * Helper class that facilitates the creation of lint auto fixes + */ +data class EnforcePermissionFix( + val manualCheckLocations: List<Location>, + val permissionNames: List<String>, + val errorLevel: Boolean, + val anyOf: Boolean, +) { + fun toLintFix(context: JavaContext, node: UMethod): LintFix { + val methodLocation = context.getLocation(node) + val replaceOrRemoveFixes = manualCheckLocations.mapIndexed { index, manualCheckLocation -> + if (index == 0) { + // Replace the first manual check with a call to the helper method + getHelperMethodFix(node, manualCheckLocation, false) + } else { + // Remove all subsequent manual checks + LintFix.create() + .replace() + .reformat(true) + .range(manualCheckLocation) + .with("") + .autoFix() + .build() + } + } + + // Annotate the method with @EnforcePermission(...) + val annotateFix = LintFix.create() + .annotate(annotation) + .range(methodLocation) + .autoFix() + .build() + + return LintFix.create().composite(annotateFix, *replaceOrRemoveFixes.toTypedArray()) + } + + private val annotation: String + get() { + val quotedPermissions = permissionNames.joinToString(", ") { """"$it"""" } + + val attributeName = + if (permissionNames.size > 1) { + if (anyOf) "anyOf" else "allOf" + } else null + + val annotationParameter = + if (attributeName != null) "$attributeName={$quotedPermissions}" + else quotedPermissions + + return "@$ANNOTATION_ENFORCE_PERMISSION($annotationParameter)" + } + + companion object { + /** + * Walks the expressions in a block, looking for simple permission checks. + * + * As soon as something other than a permission check is encountered, stop looking, + * as some other business logic is happening that prevents an automated fix. + */ + fun fromBlockExpression( + context: JavaContext, + blockExpression: UBlockExpression + ): EnforcePermissionFix? { + try { + val singleFixes = mutableListOf<EnforcePermissionFix>() + for (expression in blockExpression.expressions) { + val fix = fromExpression(context, expression) ?: break + singleFixes.add(fix) + } + return compose(singleFixes) + } catch (e: AnyOfAllOfException) { + return null + } + } + + /** + * Conditionally constructs EnforcePermissionFix from any UExpression + * + * @return EnforcePermissionFix if the expression boils down to a permission check, + * else null + */ + fun fromExpression( + context: JavaContext, + expression: UExpression + ): EnforcePermissionFix? { + val trimmedExpression = expression.skipParenthesizedExprDown() + if (trimmedExpression is UIfExpression) { + return fromIfExpression(context, trimmedExpression) + } + findCallExpression(trimmedExpression)?.let { + return fromCallExpression(context, it) + } + return null + } + + /** + * Conditionally constructs EnforcePermissionFix from a UCallExpression + * + * @return EnforcePermissionFix if the called method is annotated with @PermissionMethod, else null + */ + fun fromCallExpression( + context: JavaContext, + callExpression: UCallExpression + ): EnforcePermissionFix? { + val method = callExpression.resolve()?.getUMethod() ?: return null + val annotation = getPermissionMethodAnnotation(method) ?: return null + val returnsVoid = method.returnType == PsiType.VOID + val orSelf = getAnnotationBooleanValue(annotation, "orSelf") ?: false + val anyOf = getAnnotationBooleanValue(annotation, "anyOf") ?: false + return EnforcePermissionFix( + listOf(getPermissionCheckLocation(context, callExpression)), + getPermissionCheckValues(callExpression), + errorLevel = isErrorLevel(throws = returnsVoid, orSelf = orSelf), + anyOf, + ) + } + + /** + * Conditionally constructs EnforcePermissionFix from a UCallExpression + * + * @return EnforcePermissionFix IF AND ONLY IF: + * * The condition of the if statement compares the return value of a + * PermissionMethod to one of the PackageManager.PermissionResult values + * * The expression inside the if statement does nothing but throw SecurityException + */ + fun fromIfExpression( + context: JavaContext, + ifExpression: UIfExpression + ): EnforcePermissionFix? { + val condition = ifExpression.condition.skipParenthesizedExprDown() + if (condition !is UBinaryExpression) return null + + val maybeLeftCall = findCallExpression(condition.leftOperand) + val maybeRightCall = findCallExpression(condition.rightOperand) + + val (callExpression, comparison) = + if (maybeLeftCall is UCallExpression) { + Pair(maybeLeftCall, condition.rightOperand) + } else if (maybeRightCall is UCallExpression) { + Pair(maybeRightCall, condition.leftOperand) + } else return null + + val permissionMethodAnnotation = getPermissionMethodAnnotation( + callExpression.resolve()?.getUMethod()) ?: return null + + val equalityCheck = + when (comparison.findSelector().asSourceString() + .filterNot(Char::isWhitespace)) { + "PERMISSION_GRANTED" -> UastBinaryOperator.IDENTITY_NOT_EQUALS + "PERMISSION_DENIED" -> UastBinaryOperator.IDENTITY_EQUALS + else -> return null + } + + if (condition.operator != equalityCheck) return null + + val throwExpression: UThrowExpression? = + ifExpression.thenExpression as? UThrowExpression + ?: (ifExpression.thenExpression as? UBlockExpression) + ?.expressions?.firstOrNull() + as? UThrowExpression + + + val thrownClass = (throwExpression?.thrownExpression?.getExpressionType() + as? PsiClassType)?.resolve() ?: return null + if (!context.evaluator.inheritsFrom( + thrownClass, "java.lang.SecurityException")){ + return null + } + + val orSelf = getAnnotationBooleanValue(permissionMethodAnnotation, "orSelf") ?: false + val anyOf = getAnnotationBooleanValue(permissionMethodAnnotation, "anyOf") ?: false + + return EnforcePermissionFix( + listOf(context.getLocation(ifExpression)), + getPermissionCheckValues(callExpression), + errorLevel = isErrorLevel(throws = true, orSelf = orSelf), + anyOf = anyOf + ) + } + + + fun compose(individuals: List<EnforcePermissionFix>): EnforcePermissionFix? { + if (individuals.isEmpty()) return null + val anyOfs = individuals.filter(EnforcePermissionFix::anyOf) + // anyOf/allOf should be consistent. If we encounter some @PermissionMethods that are anyOf + // and others that aren't, we don't know what to do. + if (anyOfs.isNotEmpty() && anyOfs.size < individuals.size) { + throw AnyOfAllOfException() + } + return EnforcePermissionFix( + individuals.flatMap(EnforcePermissionFix::manualCheckLocations), + individuals.flatMap(EnforcePermissionFix::permissionNames), + errorLevel = individuals.all(EnforcePermissionFix::errorLevel), + anyOf = anyOfs.isNotEmpty() + ) + } + + /** + * Given a permission check, get its proper location + * so that a lint fix can remove the entire expression + */ + private fun getPermissionCheckLocation( + context: JavaContext, + callExpression: UCallExpression + ): + Location { + val javaPsi = callExpression.javaPsi!! + return Location.create( + context.file, + javaPsi.containingFile?.text, + javaPsi.textRange.startOffset, + // unfortunately the element doesn't include the ending semicolon + javaPsi.textRange.endOffset + 1 + ) + } + + /** + * Given a @PermissionMethod, find arguments annotated with @PermissionName + * and pull out the permission value(s) being used. Also evaluates nested calls + * to @PermissionMethod(s) in the given method's body. + */ + @Throws(AnyOfAllOfException::class) + private fun getPermissionCheckValues( + callExpression: UCallExpression + ): List<String> { + if (!isPermissionMethodCall(callExpression)) return emptyList() + + val result = mutableSetOf<String>() // protect against duplicate permission values + val visitedCalls = mutableSetOf<UCallExpression>() // don't visit the same call twice + val bfsQueue = ArrayDeque(listOf(callExpression)) + + var anyOfAllOfState: AnyOfAllOfState = AnyOfAllOfState.INITIAL + + // Bread First Search - evaluating nested @PermissionMethod(s) in the available + // source code for @PermissionName(s). + while (bfsQueue.isNotEmpty()) { + val currentCallExpression = bfsQueue.removeFirst() + visitedCalls.add(currentCallExpression) + val currentPermissions = findPermissions(currentCallExpression) + result.addAll(currentPermissions) + + val currentAnnotation = getPermissionMethodAnnotation( + currentCallExpression.resolve()?.getUMethod()) + val currentAnyOf = getAnnotationBooleanValue(currentAnnotation, "anyOf") ?: false + + // anyOf/allOf should be consistent. If we encounter a nesting of @PermissionMethods + // where we start in an anyOf state and switch to allOf, or vice versa, + // we don't know what to do. + if (anyOfAllOfState == AnyOfAllOfState.INITIAL) { + if (currentAnyOf) anyOfAllOfState = AnyOfAllOfState.ANY_OF + else if (result.isNotEmpty()) anyOfAllOfState = AnyOfAllOfState.ALL_OF + } + + if (anyOfAllOfState == AnyOfAllOfState.ALL_OF && currentAnyOf) { + throw AnyOfAllOfException() + } + + if (anyOfAllOfState == AnyOfAllOfState.ANY_OF && + !currentAnyOf && currentPermissions.size > 1) { + throw AnyOfAllOfException() + } + + currentCallExpression.resolve()?.getUMethod() + ?.accept(PermissionCheckValuesVisitor(visitedCalls, bfsQueue)) + } + + return result.toList() + } + + private enum class AnyOfAllOfState { + INITIAL, + ANY_OF, + ALL_OF + } + + /** + * Adds visited permission method calls to the provided + * queue in support of the BFS traversal happening while + * this is used + */ + private class PermissionCheckValuesVisitor( + val visitedCalls: Set<UCallExpression>, + val bfsQueue: ArrayDeque<UCallExpression> + ) : AbstractUastVisitor() { + override fun visitCallExpression(node: UCallExpression): Boolean { + if (isPermissionMethodCall(node) && node !in visitedCalls) { + bfsQueue.add(node) + } + return false + } + } + + private fun findPermissions( + callExpression: UCallExpression, + ): List<String> { + val annotation = getPermissionMethodAnnotation(callExpression.resolve()?.getUMethod()) + + val hardCodedPermissions = (getAnnotationStringValues(annotation, "value") + ?: emptyArray()) + .toList() + + val indices = callExpression.resolve()?.getUMethod() + ?.uastParameters + ?.filter(::hasPermissionNameAnnotation) + ?.mapNotNull { it.sourcePsi?.parameterIndex() } + ?: emptyList() + + val argPermissions = indices + .flatMap { i -> + when (val argument = callExpression.getArgumentForParameter(i)) { + null -> listOf(null) + is UExpressionList -> // varargs e.g. someMethod(String...) + argument.expressions.map(UExpression::evaluateString) + else -> listOf(argument.evaluateString()) + } + } + .filterNotNull() + + return hardCodedPermissions + argPermissions + } + + /** + * If we detect that the PermissionMethod enforces that permission is granted, + * AND is of the "orSelf" variety, we are very confident that this is a behavior + * preserving migration to @EnforcePermission. Thus, the incident should be ERROR + * level. + */ + private fun isErrorLevel(throws: Boolean, orSelf: Boolean): Boolean = throws && orSelf + } +} +/** + * anyOf/allOf @PermissionMethods must be consistent to apply @EnforcePermission - + * meaning if we encounter some @PermissionMethods that are anyOf, and others are allOf, + * we don't know which to apply. + */ +class AnyOfAllOfException : Exception() { + override val message: String = "anyOf/allOf permission methods cannot be mixed" +} diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt new file mode 100644 index 000000000000..df13af516514 --- /dev/null +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.client.api.UElementHandler +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.google.android.lint.findCallExpression +import com.intellij.psi.PsiElement +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UDeclarationsExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UExpression +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.skipParenthesizedExprDown + +class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner { + override fun getApplicableUastTypes(): List<Class<out UElement?>> = + listOf(UMethod::class.java) + + override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context) + + private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() { + override fun visitMethod(node: UMethod) { + if (context.evaluator.isAbstract(node)) return + if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return + + if (!isContainedInSubclassOfStub(context, node)) { + context.report( + ISSUE_MISUSING_ENFORCE_PERMISSION, + node, + context.getLocation(node), + "The class of ${node.name} does not inherit from an AIDL generated Stub class" + ) + return + } + + val targetExpression = getHelperMethodCallSourceString(node) + val message = + "Method must start with $targetExpression or super.${node.name}(), if applicable" + + val firstExpression = (node.uastBody as? UBlockExpression) + ?.expressions?.firstOrNull() + + if (firstExpression == null) { + context.report( + ISSUE_ENFORCE_PERMISSION_HELPER, + context.getLocation(node), + message, + ) + return + } + + val firstExpressionSource = firstExpression.skipParenthesizedExprDown() + .asSourceString() + .filterNot(Char::isWhitespace) + + if (firstExpressionSource != targetExpression && + firstExpressionSource != "super.$targetExpression") { + // calling super.<methodName>() is also legal + val directSuper = context.evaluator.getSuperMethod(node) + val firstCall = findCallExpression(firstExpression)?.resolve() + if (directSuper != null && firstCall == directSuper) return + + val locationTarget = getLocationTarget(firstExpression) + val expressionLocation = context.getLocation(locationTarget) + + context.report( + ISSUE_ENFORCE_PERMISSION_HELPER, + context.getLocation(node), + message, + getHelperMethodFix(node, expressionLocation), + ) + } + } + } + + companion object { + private const val HELPER_SUFFIX = "_enforcePermission" + + private const val EXPLANATION = """ + The @EnforcePermission annotation can only be used on methods whose class extends from + the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the + AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX. + + yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can + either call it directly, or call it indirectly via super.yourMethodName(). + """ + + val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create( + id = "MissingEnforcePermissionHelper", + briefDescription = """Missing permission-enforcing method call in AIDL method + |annotated with @EnforcePermission""".trimMargin(), + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 6, + severity = Severity.ERROR, + implementation = Implementation( + EnforcePermissionHelperDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create( + id = "MisusingEnforcePermissionAnnotation", + briefDescription = "@EnforcePermission cannot be used here", + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 6, + severity = Severity.ERROR, + implementation = Implementation( + EnforcePermissionDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + /** + * handles an edge case with UDeclarationsExpression, where sourcePsi is null, + * resulting in an incorrect Location if used directly + */ + private fun getLocationTarget(firstExpression: UExpression): PsiElement? { + if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi + if (firstExpression is UDeclarationsExpression) { + return firstExpression.declarations.firstOrNull()?.sourcePsi + } + return null + } + } +} diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt new file mode 100644 index 000000000000..d41fee3fc0dc --- /dev/null +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.LintFix +import com.android.tools.lint.detector.api.Location +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiReferenceList +import org.jetbrains.uast.UMethod + +/** + * Given a UMethod, determine if this method is + * the entrypoint to an interface generated by AIDL, + * returning the interface name if so, otherwise returning null + */ +fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? { + if (!isContainedInSubclassOfStub(context, node)) return null + for (superMethod in node.findSuperMethods()) { + for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements + ?: continue) { + if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) { + return superMethod.containingClass?.name + } + } + } + return null +} + +fun isContainedInSubclassOfStub(context: JavaContext, node: UMethod?): Boolean { + var superClass = node?.containingClass?.superClass + while (superClass != null) { + if (isStub(context, superClass)) return true + superClass = superClass.superClass + } + return false +} + +fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean { + if (psiClass == null) return false + if (psiClass.name != "Stub") return false + if (!context.evaluator.isStatic(psiClass)) return false + if (!context.evaluator.isAbstract(psiClass)) return false + + if (!hasSingleAncestor(psiClass.extendsList, BINDER_CLASS)) return false + + val parent = psiClass.parent as? PsiClass ?: return false + if (!hasSingleAncestor(parent.extendsList, IINTERFACE_INTERFACE)) return false + + val parentName = parent.qualifiedName ?: return false + if (!hasSingleAncestor(psiClass.implementsList, parentName)) return false + + return true +} + +private fun hasSingleAncestor(references: PsiReferenceList?, qualifiedName: String) = + references != null && + references.referenceElements.size == 1 && + references.referenceElements[0].qualifiedName == qualifiedName + +fun getHelperMethodCallSourceString(node: UMethod) = "${node.name}$AIDL_PERMISSION_HELPER_SUFFIX()" + +fun getHelperMethodFix( + node: UMethod, + manualCheckLocation: Location, + prepend: Boolean = true +): LintFix { + val helperMethodSource = getHelperMethodCallSourceString(node) + val indent = " ".repeat(manualCheckLocation.start?.column ?: 0) + val newText = "$helperMethodSource;${if (prepend) "\n\n$indent" else ""}" + + val fix = LintFix.create() + .replace() + .range(manualCheckLocation) + .with(newText) + .reformat(true) + .autoFix() + + if (prepend) fix.beginning() + + return fix.build() +} diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt new file mode 100644 index 000000000000..c7be36efd991 --- /dev/null +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetector.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Incident +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UMethod + +/** + * Looks for methods implementing generated AIDL interface stubs + * that can have simple permission checks migrated to + * @EnforcePermission annotations + */ +@Suppress("UnstableApiUsage") +class SimpleManualPermissionEnforcementDetector : AidlImplementationDetector() { + override fun visitAidlMethod( + context: JavaContext, + node: UMethod, + interfaceName: String, + body: UBlockExpression + ) { + val enforcePermissionFix = EnforcePermissionFix.fromBlockExpression(context, body) ?: return + val lintFix = enforcePermissionFix.toLintFix(context, node) + val message = + "$interfaceName permission check ${ + if (enforcePermissionFix.errorLevel) "should" else "can" + } be converted to @EnforcePermission annotation" + + val incident = Incident( + ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT, + enforcePermissionFix.manualCheckLocations.last(), + message, + lintFix + ) + + // TODO(b/265014041): turn on errors once all code that would cause one is fixed + // if (enforcePermissionFix.errorLevel) { + // incident.overrideSeverity(Severity.ERROR) + // } + + context.report(incident) + } + + companion object { + + private val EXPLANATION = """ + Whenever possible, method implementations of AIDL interfaces should use the @EnforcePermission + annotation to declare the permissions to be enforced. The verification code is then + generated by the AIDL compiler, which also takes care of annotating the generated java + code. + + This reduces the risk of bugs around these permission checks (that often become vulnerabilities). + It also enables easier auditing and review. + + Please migrate to an @EnforcePermission annotation. (See: go/aidl-enforce-howto) + """.trimIndent() + + @JvmField + val ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT = Issue.create( + id = "SimpleManualPermissionEnforcement", + briefDescription = "Manual permission check can be @EnforcePermission annotation", + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 5, + severity = Severity.WARNING, + implementation = Implementation( + SimpleManualPermissionEnforcementDetector::class.java, + Scope.JAVA_FILE_SCOPE + ), + ) + } +} diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorCodegenTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorCodegenTest.kt new file mode 100644 index 000000000000..f2930d9faac7 --- /dev/null +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorCodegenTest.kt @@ -0,0 +1,557 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class EnforcePermissionDetectorCodegenTest : LintDetectorTest() { + override fun getDetector(): Detector = EnforcePermissionDetector() + + override fun getIssues(): List<Issue> = listOf( + EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION, + EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + fun test_generated_IProtected() { + lint().files( + java( + """ + /* + * This file is auto-generated. DO NOT MODIFY. + */ + package android.aidl.tests.permission; + public interface IProtected extends android.os.IInterface + { + /** Default implementation for IProtected. */ + public static class Default implements android.aidl.tests.permission.IProtected + { + @Override public void PermissionProtected() throws android.os.RemoteException + { + } + @Override public void MultiplePermissionsAll() throws android.os.RemoteException + { + } + @Override public void MultiplePermissionsAny() throws android.os.RemoteException + { + } + @Override public void NonManifestPermission() throws android.os.RemoteException + { + } + // Used by the integration tests to dynamically set permissions that are considered granted. + @Override public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException + { + } + @Override + public android.os.IBinder asBinder() { + return null; + } + } + /** Local-side IPC implementation stub class. */ + public static abstract class Stub extends android.os.Binder implements android.aidl.tests.permission.IProtected + { + private final android.os.PermissionEnforcer mEnforcer; + /** Construct the stub using the Enforcer provided. */ + public Stub(android.os.PermissionEnforcer enforcer) + { + this.attachInterface(this, DESCRIPTOR); + if (enforcer == null) { + throw new IllegalArgumentException("enforcer cannot be null"); + } + mEnforcer = enforcer; + } + @Deprecated + /** Default constructor. */ + public Stub() { + this(android.os.PermissionEnforcer.fromContext( + android.app.ActivityThread.currentActivityThread().getSystemContext())); + } + /** + * Cast an IBinder object into an android.aidl.tests.permission.IProtected interface, + * generating a proxy if needed. + */ + public static android.aidl.tests.permission.IProtected asInterface(android.os.IBinder obj) + { + if ((obj==null)) { + return null; + } + android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); + if (((iin!=null)&&(iin instanceof android.aidl.tests.permission.IProtected))) { + return ((android.aidl.tests.permission.IProtected)iin); + } + return new android.aidl.tests.permission.IProtected.Stub.Proxy(obj); + } + @Override public android.os.IBinder asBinder() + { + return this; + } + /** @hide */ + public static java.lang.String getDefaultTransactionName(int transactionCode) + { + switch (transactionCode) + { + case TRANSACTION_PermissionProtected: + { + return "PermissionProtected"; + } + case TRANSACTION_MultiplePermissionsAll: + { + return "MultiplePermissionsAll"; + } + case TRANSACTION_MultiplePermissionsAny: + { + return "MultiplePermissionsAny"; + } + case TRANSACTION_NonManifestPermission: + { + return "NonManifestPermission"; + } + case TRANSACTION_SetGranted: + { + return "SetGranted"; + } + default: + { + return null; + } + } + } + /** @hide */ + public java.lang.String getTransactionName(int transactionCode) + { + return this.getDefaultTransactionName(transactionCode); + } + @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException + { + java.lang.String descriptor = DESCRIPTOR; + if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) { + data.enforceInterface(descriptor); + } + switch (code) + { + case INTERFACE_TRANSACTION: + { + reply.writeString(descriptor); + return true; + } + } + switch (code) + { + case TRANSACTION_PermissionProtected: + { + this.PermissionProtected(); + reply.writeNoException(); + break; + } + case TRANSACTION_MultiplePermissionsAll: + { + this.MultiplePermissionsAll(); + reply.writeNoException(); + break; + } + case TRANSACTION_MultiplePermissionsAny: + { + this.MultiplePermissionsAny(); + reply.writeNoException(); + break; + } + case TRANSACTION_NonManifestPermission: + { + this.NonManifestPermission(); + reply.writeNoException(); + break; + } + case TRANSACTION_SetGranted: + { + java.util.List<java.lang.String> _arg0; + _arg0 = data.createStringArrayList(); + data.enforceNoDataAvail(); + this.SetGranted(_arg0); + reply.writeNoException(); + break; + } + default: + { + return super.onTransact(code, data, reply, flags); + } + } + return true; + } + private static class Proxy implements android.aidl.tests.permission.IProtected + { + private android.os.IBinder mRemote; + Proxy(android.os.IBinder remote) + { + mRemote = remote; + } + @Override public android.os.IBinder asBinder() + { + return mRemote; + } + public java.lang.String getInterfaceDescriptor() + { + return DESCRIPTOR; + } + @Override public void PermissionProtected() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_PermissionProtected, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void MultiplePermissionsAll() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_MultiplePermissionsAll, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void MultiplePermissionsAny() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_MultiplePermissionsAny, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void NonManifestPermission() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_NonManifestPermission, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + // Used by the integration tests to dynamically set permissions that are considered granted. + @Override public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeStringList(permissions); + boolean _status = mRemote.transact(Stub.TRANSACTION_SetGranted, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + } + static final int TRANSACTION_PermissionProtected = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); + /** Helper method to enforce permissions for PermissionProtected */ + protected void PermissionProtected_enforcePermission() throws SecurityException { + android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null); + mEnforcer.enforcePermission(android.Manifest.permission.READ_PHONE_STATE, source); + } + static final int TRANSACTION_MultiplePermissionsAll = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); + /** Helper method to enforce permissions for MultiplePermissionsAll */ + protected void MultiplePermissionsAll_enforcePermission() throws SecurityException { + android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null); + mEnforcer.enforcePermissionAllOf(new String[]{android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}, source); + } + static final int TRANSACTION_MultiplePermissionsAny = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); + /** Helper method to enforce permissions for MultiplePermissionsAny */ + protected void MultiplePermissionsAny_enforcePermission() throws SecurityException { + android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null); + mEnforcer.enforcePermissionAnyOf(new String[]{android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}, source); + } + static final int TRANSACTION_NonManifestPermission = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); + /** Helper method to enforce permissions for NonManifestPermission */ + protected void NonManifestPermission_enforcePermission() throws SecurityException { + android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null); + mEnforcer.enforcePermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, source); + } + static final int TRANSACTION_SetGranted = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4); + /** @hide */ + public int getMaxTransactionId() + { + return 4; + } + } + + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void PermissionProtected() throws android.os.RemoteException; + @android.annotation.EnforcePermission(allOf = {android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}) + public void MultiplePermissionsAll() throws android.os.RemoteException; + @android.annotation.EnforcePermission(anyOf = {android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}) + public void MultiplePermissionsAny() throws android.os.RemoteException; + @android.annotation.EnforcePermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + public void NonManifestPermission() throws android.os.RemoteException; + // Used by the integration tests to dynamically set permissions that are considered granted. + @android.annotation.RequiresNoPermission + public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException; + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun test_generated_IProtectedInterface() { + lint().files( + java( + """ + /* + * This file is auto-generated. DO NOT MODIFY. + */ + package android.aidl.tests.permission; + public interface IProtectedInterface extends android.os.IInterface + { + /** Default implementation for IProtectedInterface. */ + public static class Default implements android.aidl.tests.permission.IProtectedInterface + { + @Override public void Method1() throws android.os.RemoteException + { + } + @Override public void Method2() throws android.os.RemoteException + { + } + @Override + public android.os.IBinder asBinder() { + return null; + } + } + /** Local-side IPC implementation stub class. */ + public static abstract class Stub extends android.os.Binder implements android.aidl.tests.permission.IProtectedInterface + { + private final android.os.PermissionEnforcer mEnforcer; + /** Construct the stub using the Enforcer provided. */ + public Stub(android.os.PermissionEnforcer enforcer) + { + this.attachInterface(this, DESCRIPTOR); + if (enforcer == null) { + throw new IllegalArgumentException("enforcer cannot be null"); + } + mEnforcer = enforcer; + } + @Deprecated + /** Default constructor. */ + public Stub() { + this(android.os.PermissionEnforcer.fromContext( + android.app.ActivityThread.currentActivityThread().getSystemContext())); + } + /** + * Cast an IBinder object into an android.aidl.tests.permission.IProtectedInterface interface, + * generating a proxy if needed. + */ + public static android.aidl.tests.permission.IProtectedInterface asInterface(android.os.IBinder obj) + { + if ((obj==null)) { + return null; + } + android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); + if (((iin!=null)&&(iin instanceof android.aidl.tests.permission.IProtectedInterface))) { + return ((android.aidl.tests.permission.IProtectedInterface)iin); + } + return new android.aidl.tests.permission.IProtectedInterface.Stub.Proxy(obj); + } + @Override public android.os.IBinder asBinder() + { + return this; + } + /** @hide */ + public static java.lang.String getDefaultTransactionName(int transactionCode) + { + switch (transactionCode) + { + case TRANSACTION_Method1: + { + return "Method1"; + } + case TRANSACTION_Method2: + { + return "Method2"; + } + default: + { + return null; + } + } + } + /** @hide */ + public java.lang.String getTransactionName(int transactionCode) + { + return this.getDefaultTransactionName(transactionCode); + } + @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException + { + java.lang.String descriptor = DESCRIPTOR; + if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) { + data.enforceInterface(descriptor); + } + switch (code) + { + case INTERFACE_TRANSACTION: + { + reply.writeString(descriptor); + return true; + } + } + switch (code) + { + case TRANSACTION_Method1: + { + this.Method1(); + reply.writeNoException(); + break; + } + case TRANSACTION_Method2: + { + this.Method2(); + reply.writeNoException(); + break; + } + default: + { + return super.onTransact(code, data, reply, flags); + } + } + return true; + } + private static class Proxy implements android.aidl.tests.permission.IProtectedInterface + { + private android.os.IBinder mRemote; + Proxy(android.os.IBinder remote) + { + mRemote = remote; + } + @Override public android.os.IBinder asBinder() + { + return mRemote; + } + public java.lang.String getInterfaceDescriptor() + { + return DESCRIPTOR; + } + @Override public void Method1() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_Method1, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void Method2() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_Method2, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + } + static final int TRANSACTION_Method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); + /** Helper method to enforce permissions for Method1 */ + protected void Method1_enforcePermission() throws SecurityException { + android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null); + mEnforcer.enforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION, source); + } + static final int TRANSACTION_Method2 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); + /** Helper method to enforce permissions for Method2 */ + protected void Method2_enforcePermission() throws SecurityException { + android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null); + mEnforcer.enforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION, source); + } + /** @hide */ + public int getMaxTransactionId() + { + return 1; + } + } + + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION) + public void Method1() throws android.os.RemoteException; + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION) + public void Method2() throws android.os.RemoteException; + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + /* Stubs */ + + private val manifestPermissionStub: TestFile = java( + """ + package android.Manifest; + class permission { + public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"; + public static final String INTERNET = "android.permission.INTERNET"; + } + """ + ).indented() + + private val enforcePermissionAnnotationStub: TestFile = java( + """ + package android.annotation; + public @interface EnforcePermission {} + """ + ).indented() + + private val stubs = arrayOf(manifestPermissionStub, enforcePermissionAnnotationStub) +} diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt new file mode 100644 index 000000000000..75b00737a168 --- /dev/null +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class EnforcePermissionDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = EnforcePermissionDetector() + + override fun getIssues(): List<Issue> = listOf( + EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION, + EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + import android.annotation.EnforcePermission; + public class TestClass2 extends IFooMethod.Stub { + @Override + @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testDoesNotDetectIssuesCorrectAnnotationAllOnMethod() { + lint().files(java( + """ + package test.pkg; + import android.annotation.EnforcePermission; + public class TestClass11 extends IFooMethod.Stub { + @Override + @EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}) + public void testMethodAll() {} + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testDoesNotDetectIssuesCorrectAnnotationAllLiteralOnMethod() { + lint().files(java( + """ + package test.pkg; + import android.annotation.EnforcePermission; + public class TestClass111 extends IFooMethod.Stub { + @Override + @EnforcePermission(allOf={"android.permission.INTERNET", android.Manifest.permission.READ_PHONE_STATE}) + public void testMethodAllLiteral() {} + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testDoesNotDetectIssuesCorrectAnnotationAnyOnMethod() { + lint().files(java( + """ + package test.pkg; + import android.annotation.EnforcePermission; + public class TestClass12 extends IFooMethod.Stub { + @Override + @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}) + public void testMethodAny() {} + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testDoesNotDetectIssuesCorrectAnnotationAnyLiteralOnMethod() { + lint().files(java( + """ + package test.pkg; + import android.annotation.EnforcePermission; + public class TestClass121 extends IFooMethod.Stub { + @Override + @EnforcePermission(anyOf={"android.permission.INTERNET", android.Manifest.permission.READ_PHONE_STATE}) + public void testMethodAnyLiteral() {} + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testDetectIssuesMismatchingAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass4 extends IFooMethod.Stub { + @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect(""" + src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \ + which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \ + The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethod() {} + ~~~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation()) + } + + fun testDetectIssuesEmptyAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass41 extends IFooMethod.Stub { + @android.annotation.EnforcePermission + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect(""" + src/test/pkg/TestClass41.java:4: Error: The method TestClass41.testMethod is annotated with @android.annotation.EnforcePermission \ + which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \ + The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethod() {} + ~~~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation()) + } + + fun testDetectIssuesMismatchingAnyAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass9 extends IFooMethod.Stub { + @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) + public void testMethodAny() {} + } + """).indented(), + *stubs + ) + .run() + .expect(""" + src/test/pkg/TestClass9.java:4: Error: The method TestClass9.testMethodAny is annotated with \ + @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) \ + which differs from the overridden method Stub.testMethodAny: \ + @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}). \ + The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethodAny() {} + ~~~~~~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation()) + } + + fun testDetectIssuesMismatchingAnyLiteralAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass91 extends IFooMethod.Stub { + @android.annotation.EnforcePermission(anyOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) + public void testMethodAnyLiteral() {} + } + """).indented(), + *stubs + ) + .run() + .expect(""" + src/test/pkg/TestClass91.java:4: Error: The method TestClass91.testMethodAnyLiteral is annotated with \ + @android.annotation.EnforcePermission(anyOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) \ + which differs from the overridden method Stub.testMethodAnyLiteral: \ + @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \ + The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethodAnyLiteral() {} + ~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation()) + } + + fun testDetectIssuesMismatchingAllAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass10 extends IFooMethod.Stub { + @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) + public void testMethodAll() {} + } + """).indented(), + *stubs + ) + .run() + .expect(""" + src/test/pkg/TestClass10.java:4: Error: The method TestClass10.testMethodAll is annotated with \ + @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) \ + which differs from the overridden method Stub.testMethodAll: \ + @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}). \ + The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethodAll() {} + ~~~~~~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation()) + } + + fun testDetectIssuesMismatchingAllLiteralAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass101 extends IFooMethod.Stub { + @android.annotation.EnforcePermission(allOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) + public void testMethodAllLiteral() {} + } + """).indented(), + *stubs + ) + .run() + .expect(""" + src/test/pkg/TestClass101.java:4: Error: The method TestClass101.testMethodAllLiteral is annotated with \ + @android.annotation.EnforcePermission(allOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) \ + which differs from the overridden method Stub.testMethodAllLiteral: \ + @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \ + The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethodAllLiteral() {} + ~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation()) + } + + fun testDetectIssuesMissingAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass6 extends IFooMethod.Stub { + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect(""" + src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod overrides the method Stub.testMethod which is annotated with @EnforcePermission. \ + The same annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation] + public void testMethod() {} + ~~~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation()) + } + + fun testDetectIssuesExtraAnnotationMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass7 extends IBar.Stub { + @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect(""" + src/test/pkg/TestClass7.java:4: Error: The method TestClass7.testMethod overrides the method Stub.testMethod which is not annotated with @EnforcePermission. \ + The same annotation must be used on Stub.testMethod. Did you forget to annotate the AIDL definition? [MissingEnforcePermissionAnnotation] + public void testMethod() {} + ~~~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation()) + } + + fun testDetectIssuesMissingAnnotationOnMethodWhenClassIsCalledDefault() { + lint().files(java( + """ + package test.pkg; + public class Default extends IFooMethod.Stub { + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect( + """ + src/test/pkg/Default.java:3: Error: The method Default.testMethod \ + overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same annotation must be used on Default.testMethod [MissingEnforcePermissionAnnotation] + public void testMethod() {} + ~~~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation() + ) + } + + fun testDoesDetectIssuesShortStringsNotAllowed() { + lint().files(java( + """ + package test.pkg; + import android.annotation.EnforcePermission; + public class TestClass121 extends IFooMethod.Stub { + @Override + @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) + public void testMethodAnyLiteral() {} + } + """).indented(), + *stubs + ) + .run() + .expect( + """ + src/test/pkg/TestClass121.java:6: Error: The method \ + TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) \ + which differs from the overridden method Stub.testMethodAnyLiteral: \ + @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \ + The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethodAnyLiteral() {} + ~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation() + ) + } + + /* Stubs */ + + // A service with permission annotation on the method. + private val interfaceIFooMethodStub: TestFile = java( + """ + public interface IFooMethod extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IFooMethod { + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod() {} + @Override + @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}) + public void testMethodAny() {} + @Override + @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}) + public void testMethodAnyLiteral() {} + @Override + @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}) + public void testMethodAll() {} + @Override + @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}) + public void testMethodAllLiteral() {} + } + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod(); + @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}) + public void testMethodAny() {} + @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}) + public void testMethodAnyLiteral() {} + @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}) + public void testMethodAll() {} + @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}) + public void testMethodAllLiteral() {} + } + """ + ).indented() + + // A service without any permission annotation. + private val interfaceIBarStub: TestFile = java( + """ + public interface IBar extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IBar { + @Override + public void testMethod() {} + } + public void testMethod(); + } + """ + ).indented() + + private val manifestPermissionStub: TestFile = java( + """ + package android.Manifest; + class permission { + public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"; + public static final String NFC = "android.permission.NFC"; + public static final String INTERNET = "android.permission.INTERNET"; + } + """ + ).indented() + + private val enforcePermissionAnnotationStub: TestFile = java( + """ + package android.annotation; + public @interface EnforcePermission {} + """ + ).indented() + + private val stubs = arrayOf(interfaceIFooMethodStub, interfaceIBarStub, + manifestPermissionStub, enforcePermissionAnnotationStub) + + // Substitutes "backslash + new line" with an empty string to imitate line continuation + private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "") +} diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt new file mode 100644 index 000000000000..5a63bb4084d2 --- /dev/null +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt @@ -0,0 +1,557 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.checks.infrastructure.TestMode +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class EnforcePermissionHelperDetectorCodegenTest : LintDetectorTest() { + override fun getDetector(): Detector = EnforcePermissionHelperDetector() + + override fun getIssues(): List<Issue> = listOf( + EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + fun test_generated_IProtected() { + lint().testModes(TestMode.DEFAULT).files( + java( + """ + /* + * This file is auto-generated. DO NOT MODIFY. + */ + package android.aidl.tests.permission; + public interface IProtected extends android.os.IInterface + { + /** Default implementation for IProtected. */ + public static class Default implements android.aidl.tests.permission.IProtected + { + @Override public void PermissionProtected() throws android.os.RemoteException + { + } + @Override public void MultiplePermissionsAll() throws android.os.RemoteException + { + } + @Override public void MultiplePermissionsAny() throws android.os.RemoteException + { + } + @Override public void NonManifestPermission() throws android.os.RemoteException + { + } + // Used by the integration tests to dynamically set permissions that are considered granted. + @Override public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException + { + } + @Override + public android.os.IBinder asBinder() { + return null; + } + } + /** Local-side IPC implementation stub class. */ + public static abstract class Stub extends android.os.Binder implements android.aidl.tests.permission.IProtected + { + private final android.os.PermissionEnforcer mEnforcer; + /** Construct the stub using the Enforcer provided. */ + public Stub(android.os.PermissionEnforcer enforcer) + { + this.attachInterface(this, DESCRIPTOR); + if (enforcer == null) { + throw new IllegalArgumentException("enforcer cannot be null"); + } + mEnforcer = enforcer; + } + @Deprecated + /** Default constructor. */ + public Stub() { + this(android.os.PermissionEnforcer.fromContext( + android.app.ActivityThread.currentActivityThread().getSystemContext())); + } + /** + * Cast an IBinder object into an android.aidl.tests.permission.IProtected interface, + * generating a proxy if needed. + */ + public static android.aidl.tests.permission.IProtected asInterface(android.os.IBinder obj) + { + if ((obj==null)) { + return null; + } + android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); + if (((iin!=null)&&(iin instanceof android.aidl.tests.permission.IProtected))) { + return ((android.aidl.tests.permission.IProtected)iin); + } + return new android.aidl.tests.permission.IProtected.Stub.Proxy(obj); + } + @Override public android.os.IBinder asBinder() + { + return this; + } + /** @hide */ + public static java.lang.String getDefaultTransactionName(int transactionCode) + { + switch (transactionCode) + { + case TRANSACTION_PermissionProtected: + { + return "PermissionProtected"; + } + case TRANSACTION_MultiplePermissionsAll: + { + return "MultiplePermissionsAll"; + } + case TRANSACTION_MultiplePermissionsAny: + { + return "MultiplePermissionsAny"; + } + case TRANSACTION_NonManifestPermission: + { + return "NonManifestPermission"; + } + case TRANSACTION_SetGranted: + { + return "SetGranted"; + } + default: + { + return null; + } + } + } + /** @hide */ + public java.lang.String getTransactionName(int transactionCode) + { + return this.getDefaultTransactionName(transactionCode); + } + @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException + { + java.lang.String descriptor = DESCRIPTOR; + if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) { + data.enforceInterface(descriptor); + } + switch (code) + { + case INTERFACE_TRANSACTION: + { + reply.writeString(descriptor); + return true; + } + } + switch (code) + { + case TRANSACTION_PermissionProtected: + { + this.PermissionProtected(); + reply.writeNoException(); + break; + } + case TRANSACTION_MultiplePermissionsAll: + { + this.MultiplePermissionsAll(); + reply.writeNoException(); + break; + } + case TRANSACTION_MultiplePermissionsAny: + { + this.MultiplePermissionsAny(); + reply.writeNoException(); + break; + } + case TRANSACTION_NonManifestPermission: + { + this.NonManifestPermission(); + reply.writeNoException(); + break; + } + case TRANSACTION_SetGranted: + { + java.util.List<java.lang.String> _arg0; + _arg0 = data.createStringArrayList(); + data.enforceNoDataAvail(); + this.SetGranted(_arg0); + reply.writeNoException(); + break; + } + default: + { + return super.onTransact(code, data, reply, flags); + } + } + return true; + } + private static class Proxy implements android.aidl.tests.permission.IProtected + { + private android.os.IBinder mRemote; + Proxy(android.os.IBinder remote) + { + mRemote = remote; + } + @Override public android.os.IBinder asBinder() + { + return mRemote; + } + public java.lang.String getInterfaceDescriptor() + { + return DESCRIPTOR; + } + @Override public void PermissionProtected() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_PermissionProtected, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void MultiplePermissionsAll() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_MultiplePermissionsAll, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void MultiplePermissionsAny() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_MultiplePermissionsAny, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void NonManifestPermission() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_NonManifestPermission, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + // Used by the integration tests to dynamically set permissions that are considered granted. + @Override public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeStringList(permissions); + boolean _status = mRemote.transact(Stub.TRANSACTION_SetGranted, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + } + static final int TRANSACTION_PermissionProtected = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); + /** Helper method to enforce permissions for PermissionProtected */ + protected void PermissionProtected_enforcePermission() throws SecurityException { + android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null); + mEnforcer.enforcePermission(android.Manifest.permission.READ_PHONE_STATE, source); + } + static final int TRANSACTION_MultiplePermissionsAll = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); + /** Helper method to enforce permissions for MultiplePermissionsAll */ + protected void MultiplePermissionsAll_enforcePermission() throws SecurityException { + android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null); + mEnforcer.enforcePermissionAllOf(new String[]{android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}, source); + } + static final int TRANSACTION_MultiplePermissionsAny = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); + /** Helper method to enforce permissions for MultiplePermissionsAny */ + protected void MultiplePermissionsAny_enforcePermission() throws SecurityException { + android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null); + mEnforcer.enforcePermissionAnyOf(new String[]{android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}, source); + } + static final int TRANSACTION_NonManifestPermission = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); + /** Helper method to enforce permissions for NonManifestPermission */ + protected void NonManifestPermission_enforcePermission() throws SecurityException { + android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null); + mEnforcer.enforcePermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, source); + } + static final int TRANSACTION_SetGranted = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4); + /** @hide */ + public int getMaxTransactionId() + { + return 4; + } + } + + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void PermissionProtected() throws android.os.RemoteException; + @android.annotation.EnforcePermission(allOf = {android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}) + public void MultiplePermissionsAll() throws android.os.RemoteException; + @android.annotation.EnforcePermission(anyOf = {android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}) + public void MultiplePermissionsAny() throws android.os.RemoteException; + @android.annotation.EnforcePermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + public void NonManifestPermission() throws android.os.RemoteException; + // Used by the integration tests to dynamically set permissions that are considered granted. + @android.annotation.RequiresNoPermission + public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException; + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun test_generated_IProtectedInterface() { + lint().files( + java( + """ + /* + * This file is auto-generated. DO NOT MODIFY. + */ + package android.aidl.tests.permission; + public interface IProtectedInterface extends android.os.IInterface + { + /** Default implementation for IProtectedInterface. */ + public static class Default implements android.aidl.tests.permission.IProtectedInterface + { + @Override public void Method1() throws android.os.RemoteException + { + } + @Override public void Method2() throws android.os.RemoteException + { + } + @Override + public android.os.IBinder asBinder() { + return null; + } + } + /** Local-side IPC implementation stub class. */ + public static abstract class Stub extends android.os.Binder implements android.aidl.tests.permission.IProtectedInterface + { + private final android.os.PermissionEnforcer mEnforcer; + /** Construct the stub using the Enforcer provided. */ + public Stub(android.os.PermissionEnforcer enforcer) + { + this.attachInterface(this, DESCRIPTOR); + if (enforcer == null) { + throw new IllegalArgumentException("enforcer cannot be null"); + } + mEnforcer = enforcer; + } + @Deprecated + /** Default constructor. */ + public Stub() { + this(android.os.PermissionEnforcer.fromContext( + android.app.ActivityThread.currentActivityThread().getSystemContext())); + } + /** + * Cast an IBinder object into an android.aidl.tests.permission.IProtectedInterface interface, + * generating a proxy if needed. + */ + public static android.aidl.tests.permission.IProtectedInterface asInterface(android.os.IBinder obj) + { + if ((obj==null)) { + return null; + } + android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); + if (((iin!=null)&&(iin instanceof android.aidl.tests.permission.IProtectedInterface))) { + return ((android.aidl.tests.permission.IProtectedInterface)iin); + } + return new android.aidl.tests.permission.IProtectedInterface.Stub.Proxy(obj); + } + @Override public android.os.IBinder asBinder() + { + return this; + } + /** @hide */ + public static java.lang.String getDefaultTransactionName(int transactionCode) + { + switch (transactionCode) + { + case TRANSACTION_Method1: + { + return "Method1"; + } + case TRANSACTION_Method2: + { + return "Method2"; + } + default: + { + return null; + } + } + } + /** @hide */ + public java.lang.String getTransactionName(int transactionCode) + { + return this.getDefaultTransactionName(transactionCode); + } + @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException + { + java.lang.String descriptor = DESCRIPTOR; + if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) { + data.enforceInterface(descriptor); + } + switch (code) + { + case INTERFACE_TRANSACTION: + { + reply.writeString(descriptor); + return true; + } + } + switch (code) + { + case TRANSACTION_Method1: + { + this.Method1(); + reply.writeNoException(); + break; + } + case TRANSACTION_Method2: + { + this.Method2(); + reply.writeNoException(); + break; + } + default: + { + return super.onTransact(code, data, reply, flags); + } + } + return true; + } + private static class Proxy implements android.aidl.tests.permission.IProtectedInterface + { + private android.os.IBinder mRemote; + Proxy(android.os.IBinder remote) + { + mRemote = remote; + } + @Override public android.os.IBinder asBinder() + { + return mRemote; + } + public java.lang.String getInterfaceDescriptor() + { + return DESCRIPTOR; + } + @Override public void Method1() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_Method1, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void Method2() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(asBinder()); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_Method2, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + } + static final int TRANSACTION_Method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); + /** Helper method to enforce permissions for Method1 */ + protected void Method1_enforcePermission() throws SecurityException { + android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null); + mEnforcer.enforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION, source); + } + static final int TRANSACTION_Method2 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); + /** Helper method to enforce permissions for Method2 */ + protected void Method2_enforcePermission() throws SecurityException { + android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null); + mEnforcer.enforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION, source); + } + /** @hide */ + public int getMaxTransactionId() + { + return 1; + } + } + + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION) + public void Method1() throws android.os.RemoteException; + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION) + public void Method2() throws android.os.RemoteException; + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + /* Stubs */ + + private val manifestPermissionStub: TestFile = java( + """ + package android.Manifest; + class permission { + public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"; + public static final String INTERNET = "android.permission.INTERNET"; + } + """ + ).indented() + + private val enforcePermissionAnnotationStub: TestFile = java( + """ + package android.annotation; + public @interface EnforcePermission {} + """ + ).indented() + + private val stubs = arrayOf(manifestPermissionStub, enforcePermissionAnnotationStub) +} diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt new file mode 100644 index 000000000000..10a6e1da91dc --- /dev/null +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt @@ -0,0 +1,443 @@ +/* +* Copyright (C) 2022 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. +*/ + +package com.google.android.lint.aidl + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestLintTask + +class EnforcePermissionHelperDetectorTest : LintDetectorTest() { + override fun getDetector() = EnforcePermissionHelperDetector() + override fun getIssues() = listOf( + EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER, + EnforcePermissionHelperDetector.ISSUE_MISUSING_ENFORCE_PERMISSION + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk() + + fun testFirstExpressionIsFunctionCall() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + Binder.getCallingUid(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:5: Error: Method must start with test_enforcePermission() or super.test(), if applicable [MissingEnforcePermissionHelper] + @Override + ^ + 1 errors, 0 warnings + """ + ) + .expectFixDiffs( + """ + Autofix for src/Foo.java line 5: Replace with test_enforcePermission();...: + @@ -8 +8 + + test_enforcePermission(); + + + """ + ) + } + + fun testFirstExpressionIsVariableDeclaration() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + String foo = "bar"; + Binder.getCallingUid(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:5: Error: Method must start with test_enforcePermission() or super.test(), if applicable [MissingEnforcePermissionHelper] + @Override + ^ + 1 errors, 0 warnings + """ + ) + .expectFixDiffs( + """ + Autofix for src/Foo.java line 5: Replace with test_enforcePermission();...: + @@ -8 +8 + + test_enforcePermission(); + + + """ + ) + } + + fun testMethodIsEmpty() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException {} + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:5: Error: Method must start with test_enforcePermission() or super.test(), if applicable [MissingEnforcePermissionHelper] + @Override + ^ + 1 errors, 0 warnings + """ + ) + } + + fun testOkay() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + super.test_enforcePermission(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testHelperWithoutSuperPrefix_Okay() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + test_enforcePermission(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testInterfaceDefaultMethod_notStubAncestor_error() { + lint().files( + java( + """ + public interface IProtected extends android.os.IInterface { + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + default void PermissionProtected() throws android.os.RemoteException { + String foo = "bar"; + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/IProtected.java:2: Error: The class of PermissionProtected does not inherit from an AIDL generated Stub class [MisusingEnforcePermissionAnnotation] + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + ^ + 1 errors, 0 warnings + """ + ) + } + + fun testInheritance_callSuper_okay() { + lint().files( + java( + """ + package test; + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + super.test_enforcePermission(); + } + } + """ + ).indented(), + java( + """ + package test; + import test.Foo; + public class Bar extends Foo { + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + super.test(); + } + } + """ + ).indented(), + java( + """ + package test; + import test.Bar; + public class Baz extends Bar { + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + super.test(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testInheritance_callHelper_okay() { + lint().files( + java( + """ + package test; + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + super.test_enforcePermission(); + } + } + """ + ).indented(), + java( + """ + package test; + import test.Foo; + public class Bar extends Foo { + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + super.test(); + } + } + """ + ).indented(), + java( + """ + package test; + import test.Bar; + public class Baz extends Bar { + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + super.test_enforcePermission(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testInheritance_missingCallInChain_error() { + lint().files( + java( + """ + package test; + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + super.test_enforcePermission(); + } + } + """ + ).indented(), + java( + """ + package test; + import test.Foo; + public class Bar extends Foo { + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + doStuff(); + } + } + """ + ).indented(), + java( + """ + package test; + import test.Bar; + public class Baz extends Bar { + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + super.test(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/test/Bar.java:4: Error: Method must start with test_enforcePermission() or super.test(), if applicable [MissingEnforcePermissionHelper] + @Override + ^ + 1 errors, 0 warnings + """ + ) + } + + fun testInheritance_missingCall_error() { + lint().files( + java( + """ + package test; + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + super.test_enforcePermission(); + } + } + """ + ).indented(), + java( + """ + package test; + import test.Foo; + public class Bar extends Foo { + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + super.test(); + } + } + """ + ).indented(), + java( + """ + package test; + import test.Bar; + public class Baz extends Bar { + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void test() throws android.os.RemoteException { + doStuff(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/test/Baz.java:4: Error: Method must start with test_enforcePermission() or super.test(), if applicable [MissingEnforcePermissionHelper] + @Override + ^ + 1 errors, 0 warnings + """ + ) + } + + fun testRandomClass_notStubAncestor_error() { + lint().files( + java( + """ + public class Foo { + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + void PermissionProtected() throws android.os.RemoteException { + String foo = "bar"; + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:2: Error: The class of PermissionProtected does not inherit from an AIDL generated Stub class [MisusingEnforcePermissionAnnotation] + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + ^ + 1 errors, 0 warnings + """ + ) + } + + companion object { + val stubs = arrayOf(aidlStub, contextStub, binderStub) + } +} + + + diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt new file mode 100644 index 000000000000..6b8e72cf9222 --- /dev/null +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/SimpleManualPermissionEnforcementDetectorTest.kt @@ -0,0 +1,843 @@ +/* + * Copyright (C) 2022 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. + */ + +package com.google.android.lint.aidl + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class SimpleManualPermissionEnforcementDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = SimpleManualPermissionEnforcementDetector() + override fun getIssues(): List<Issue> = listOf( + SimpleManualPermissionEnforcementDetector + .ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk() + + fun testClass() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + public void test() throws android.os.RemoteException { + mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:7: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 7: Annotate with @EnforcePermission: + @@ -5 +5 + + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS") + @@ -7 +8 + - mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); + + test_enforcePermission(); + """ + ) + } + + fun testClass_orSelfFalse_warning() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + public void test() throws android.os.RemoteException { + mContext.enforceCallingPermission("android.permission.READ_CONTACTS", "foo"); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + mContext.enforceCallingPermission("android.permission.READ_CONTACTS", "foo"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 7: Annotate with @EnforcePermission: + @@ -5 +5 + + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS") + @@ -7 +8 + - mContext.enforceCallingPermission("android.permission.READ_CONTACTS", "foo"); + + test_enforcePermission(); + """ + ) + } + + fun testClass_enforcesFalse_warning() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + public void test() throws android.os.RemoteException { + mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 7: Annotate with @EnforcePermission: + @@ -5 +5 + + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS") + @@ -7 +8 + - mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); + + test_enforcePermission(); + """ + ) + } + + fun testAnonClass() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo { + private Context mContext; + private ITest itest = new ITest.Stub() { + @Override + public void test() throws android.os.RemoteException { + mContext.enforceCallingOrSelfPermission( + "android.permission.READ_CONTACTS", "foo"); + } + }; + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:8: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + mContext.enforceCallingOrSelfPermission( + ^ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 8: Annotate with @EnforcePermission: + @@ -6 +6 + + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS") + @@ -8 +9 + - mContext.enforceCallingOrSelfPermission( + - "android.permission.READ_CONTACTS", "foo"); + + test_enforcePermission(); + """ + ) + } + + fun testConstantEvaluation() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + + public class Foo extends ITest.Stub { + private Context mContext; + @Override + public void test() throws android.os.RemoteException { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo"); + } + } + """ + ).indented(), + *stubs, + manifestStub + ) + .run() + .expect( + """ + src/Foo.java:8: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 8: Annotate with @EnforcePermission: + @@ -6 +6 + + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS") + @@ -8 +9 + - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo"); + + test_enforcePermission(); + """ + ) + } + + fun testAllOf() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo { + private Context mContext; + private ITest itest = new ITest.Stub() { + @Override + public void test() throws android.os.RemoteException { + mContext.enforceCallingOrSelfPermission( + "android.permission.READ_CONTACTS", "foo"); + mContext.enforceCallingOrSelfPermission( + "android.permission.WRITE_CONTACTS", "foo"); + } + }; + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:10: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + mContext.enforceCallingOrSelfPermission( + ^ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 10: Annotate with @EnforcePermission: + @@ -6 +6 + + @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"}) + @@ -8 +9 + - mContext.enforceCallingOrSelfPermission( + - "android.permission.READ_CONTACTS", "foo"); + - mContext.enforceCallingOrSelfPermission( + - "android.permission.WRITE_CONTACTS", "foo"); + + test_enforcePermission(); + """ + ) + } + + fun testAllOf_mixedOrSelf_warning() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo { + private Context mContext; + private ITest itest = new ITest.Stub() { + @Override + public void test() throws android.os.RemoteException { + mContext.enforceCallingOrSelfPermission( + "android.permission.READ_CONTACTS", "foo"); + mContext.enforceCallingPermission( + "android.permission.WRITE_CONTACTS", "foo"); + } + }; + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + mContext.enforceCallingPermission( + ^ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 10: Annotate with @EnforcePermission: + @@ -6 +6 + + @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"}) + @@ -8 +9 + - mContext.enforceCallingOrSelfPermission( + - "android.permission.READ_CONTACTS", "foo"); + - mContext.enforceCallingPermission( + - "android.permission.WRITE_CONTACTS", "foo"); + + test_enforcePermission(); + """ + ) + } + + fun testAllOf_mixedEnforces_warning() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo { + private Context mContext; + private ITest itest = new ITest.Stub() { + @Override + public void test() throws android.os.RemoteException { + mContext.enforceCallingOrSelfPermission( + "android.permission.READ_CONTACTS", "foo"); + mContext.checkCallingOrSelfPermission( + "android.permission.WRITE_CONTACTS", "foo"); + } + }; + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + mContext.checkCallingOrSelfPermission( + ^ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 10: Annotate with @EnforcePermission: + @@ -6 +6 + + @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"}) + @@ -8 +9 + - mContext.enforceCallingOrSelfPermission( + - "android.permission.READ_CONTACTS", "foo"); + - mContext.checkCallingOrSelfPermission( + - "android.permission.WRITE_CONTACTS", "foo"); + + test_enforcePermission(); + """ + ) + } + + fun testPrecedingExpressions() { + lint().files( + java( + """ + import android.os.Binder; + import android.test.ITest; + public class Foo extends ITest.Stub { + private mContext Context; + @Override + public void test() throws android.os.RemoteException { + long uid = Binder.getCallingUid(); + mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testPermissionHelper() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + + public class Foo extends ITest.Stub { + private Context mContext; + + @android.annotation.PermissionMethod(orSelf = true) + private void helper() { + mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); + } + + @Override + public void test() throws android.os.RemoteException { + helper(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:14: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + helper(); + ~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 14: Annotate with @EnforcePermission: + @@ -12 +12 + + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS") + @@ -14 +15 + - helper(); + + test_enforcePermission(); + """ + ) + } + + fun testPermissionHelper_orSelfNotBubbledUp_warning() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + + public class Foo extends ITest.Stub { + private Context mContext; + + @android.annotation.PermissionMethod + private void helper() { + mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); + } + + @Override + public void test() throws android.os.RemoteException { + helper(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + helper(); + ~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 14: Annotate with @EnforcePermission: + @@ -12 +12 + + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS") + @@ -14 +15 + - helper(); + + test_enforcePermission(); + """ + ) + } + + fun testPermissionHelperAllOf() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + + public class Foo extends ITest.Stub { + private Context mContext; + + @android.annotation.PermissionMethod(orSelf = true) + private void helper() { + mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); + mContext.enforceCallingOrSelfPermission("android.permission.WRITE_CONTACTS", "foo"); + } + + @Override + public void test() throws android.os.RemoteException { + helper(); + mContext.enforceCallingOrSelfPermission("FOO", "foo"); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:16: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + mContext.enforceCallingOrSelfPermission("FOO", "foo"); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 16: Annotate with @EnforcePermission: + @@ -13 +13 + + @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS", "FOO"}) + @@ -15 +16 + - helper(); + - mContext.enforceCallingOrSelfPermission("FOO", "foo"); + + test_enforcePermission(); + """ + ) + } + + + fun testPermissionHelperNested() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + + public class Foo extends ITest.Stub { + private Context mContext; + + @android.annotation.PermissionMethod(orSelf = true) + private void helperHelper() { + helper("android.permission.WRITE_CONTACTS"); + } + + @android.annotation.PermissionMethod(orSelf = true) + private void helper(@android.annotation.PermissionName String extraPermission) { + mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo"); + } + + @Override + public void test() throws android.os.RemoteException { + helperHelper(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:19: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + helperHelper(); + ~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 19: Annotate with @EnforcePermission: + @@ -17 +17 + + @android.annotation.EnforcePermission(allOf={"android.permission.WRITE_CONTACTS", "android.permission.READ_CONTACTS"}) + @@ -19 +20 + - helperHelper(); + + test_enforcePermission(); + """ + ) + } + + fun testIfExpression() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + public void test() throws android.os.RemoteException { + if (mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo") + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("yikes!"); + } + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:7: Warning: ITest permission check should be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + if (mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo") + ^ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 7: Annotate with @EnforcePermission: + @@ -5 +5 + + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS") + @@ -7 +8 + - if (mContext.checkCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo") + - != PackageManager.PERMISSION_GRANTED) { + - throw new SecurityException("yikes!"); + - } + + test_enforcePermission(); + """ + ) + } + + fun testIfExpression_orSelfFalse_warning() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + public void test() throws android.os.RemoteException { + if (mContext.checkCallingPermission("android.permission.READ_CONTACTS", "foo") + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("yikes!"); + } + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + if (mContext.checkCallingPermission("android.permission.READ_CONTACTS", "foo") + ^ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 7: Annotate with @EnforcePermission: + @@ -5 +5 + + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS") + @@ -7 +8 + - if (mContext.checkCallingPermission("android.permission.READ_CONTACTS", "foo") + - != PackageManager.PERMISSION_GRANTED) { + - throw new SecurityException("yikes!"); + - } + + test_enforcePermission(); + """ + ) + } + + fun testIfExpression_otherSideEffect_ignored() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + public void test() throws android.os.RemoteException { + if (mContext.checkCallingPermission("android.permission.READ_CONTACTS", "foo") + != PackageManager.PERMISSION_GRANTED) { + doSomethingElse(); + throw new SecurityException("yikes!"); + } + } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testIfExpression_inlinedWithSideEffect_ignored() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + public class Foo extends ITest.Stub { + private Context mContext; + @Override + public void test() throws android.os.RemoteException { + if (somethingElse() && mContext.checkCallingPermission("android.permission.READ_CONTACTS", "foo") + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("yikes!"); + } + } + + private boolean somethingElse() { + return true; + } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testAnyOf_hardCodedAndVarArgs() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + + public class Foo extends ITest.Stub { + private Context mContext; + + @android.annotation.PermissionMethod(anyOf = true) + private void helperHelper() { + helper("FOO", "BAR"); + } + + @android.annotation.PermissionMethod(anyOf = true, value = {"BAZ", "BUZZ"}) + private void helper(@android.annotation.PermissionName String... extraPermissions) {} + + @Override + public void test() throws android.os.RemoteException { + helperHelper(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Foo.java:17: Warning: ITest permission check can be converted to @EnforcePermission annotation [SimpleManualPermissionEnforcement] + helperHelper(); + ~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """ + ) + .expectFixDiffs( + """ + Fix for src/Foo.java line 17: Annotate with @EnforcePermission: + @@ -15 +15 + + @android.annotation.EnforcePermission(anyOf={"BAZ", "BUZZ", "FOO", "BAR"}) + @@ -17 +18 + - helperHelper(); + + test_enforcePermission(); + """ + ) + } + + + fun testAnyOfAllOf_mixedConsecutiveCalls_ignored() { + lint().files( + java( + """ + import android.content.Context; + import android.test.ITest; + + public class Foo extends ITest.Stub { + private Context mContext; + + @android.annotation.PermissionMethod + private void allOfhelper() { + mContext.enforceCallingOrSelfPermission("FOO"); + mContext.enforceCallingOrSelfPermission("BAR"); + } + + @android.annotation.PermissionMethod(anyOf = true, permissions = {"BAZ", "BUZZ"}) + private void anyOfHelper() {} + + @Override + public void test() throws android.os.RemoteException { + allOfhelper(); + anyOfHelper(); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testAnyOfAllOf_mixedNestedCalls_ignored() { + lint().files( + java( + """ + import android.content.Context; + import android.annotation.PermissionName; + import android.test.ITest; + + public class Foo extends ITest.Stub { + private Context mContext; + + @android.annotation.PermissionMethod(anyOf = true) + private void anyOfCheck(@PermissionName String... permissions) { + allOfCheck("BAZ", "BUZZ"); + } + + @android.annotation.PermissionMethod + private void allOfCheck(@PermissionName String... permissions) {} + + @Override + public void test() throws android.os.RemoteException { + anyOfCheck("FOO", "BAR"); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + companion object { + val stubs = arrayOf( + aidlStub, + contextStub, + binderStub, + permissionMethodStub, + permissionNameStub + ) + } +} diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt new file mode 100644 index 000000000000..2ec8fddbb4e9 --- /dev/null +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt @@ -0,0 +1,88 @@ +package com.google.android.lint.aidl + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java +import com.android.tools.lint.checks.infrastructure.TestFile + +val aidlStub: TestFile = java( + """ + package android.test; + public interface ITest extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements android.test.ITest { + protected void test_enforcePermission() throws SecurityException {} + } + public void test() throws android.os.RemoteException; + } + """ +).indented() + +val contextStub: TestFile = java( + """ + package android.content; + public class Context { + @android.annotation.PermissionMethod(orSelf = true) + public void enforceCallingOrSelfPermission(@android.annotation.PermissionName String permission, String message) {} + @android.annotation.PermissionMethod + public void enforceCallingPermission(@android.annotation.PermissionName String permission, String message) {} + @android.annotation.PermissionMethod(orSelf = true) + public int checkCallingOrSelfPermission(@android.annotation.PermissionName String permission, String message) {} + @android.annotation.PermissionMethod + public int checkCallingPermission(@android.annotation.PermissionName String permission, String message) {} + } + """ +).indented() + +val binderStub: TestFile = java( + """ + package android.os; + public class Binder { + public static int getCallingUid() {} + } + """ +).indented() + +val permissionMethodStub: TestFile = java( + """ + package android.annotation; + + import static java.lang.annotation.ElementType.METHOD; + import static java.lang.annotation.RetentionPolicy.CLASS; + + import java.lang.annotation.Retention; + import java.lang.annotation.Target; + + @Retention(CLASS) + @Target({METHOD}) + public @interface PermissionMethod {} + """ +).indented() + +val permissionNameStub: TestFile = java( + """ + package android.annotation; + + import static java.lang.annotation.ElementType.FIELD; + import static java.lang.annotation.ElementType.LOCAL_VARIABLE; + import static java.lang.annotation.ElementType.METHOD; + import static java.lang.annotation.ElementType.PARAMETER; + import static java.lang.annotation.RetentionPolicy.CLASS; + + import java.lang.annotation.Retention; + import java.lang.annotation.Target; + + @Retention(CLASS) + @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD}) + public @interface PermissionName {} + """ +).indented() + +val manifestStub: TestFile = java( + """ + package android; + + public final class Manifest { + public static final class permission { + public static final String READ_CONTACTS="android.permission.READ_CONTACTS"; + } + } + """.trimIndent() +)
\ No newline at end of file diff --git a/tools/localedata/OWNERS b/tools/localedata/OWNERS new file mode 100644 index 000000000000..2501679784d6 --- /dev/null +++ b/tools/localedata/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 24949 +include platform/external/icu:/OWNERS diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py index ca1847af7d06..81ac897deae0 100755 --- a/tools/localedata/extract_icu_data.py +++ b/tools/localedata/extract_icu_data.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Copyright 2016 The Android Open Source Project. All Rights Reserved. # @@ -61,7 +61,7 @@ def read_likely_subtags(input_file_name): # would be chosen.) } for line in input_file: - line = unicode(line, 'UTF-8').strip(u' \n\uFEFF').encode('UTF-8') + line = line.strip(u' \n\uFEFF') if line.startswith('//'): continue if '{' in line and '}' in line: @@ -118,26 +118,26 @@ def pack_to_uint32(locale): def dump_script_codes(all_scripts): """Dump the SCRIPT_CODES table.""" - print 'const char SCRIPT_CODES[][4] = {' + print('const char SCRIPT_CODES[][4] = {') for index, script in enumerate(all_scripts): - print " /* %-2d */ {'%c', '%c', '%c', '%c'}," % ( - index, script[0], script[1], script[2], script[3]) - print '};' - print + print(" /* %-2d */ {'%c', '%c', '%c', '%c'}," % ( + index, script[0], script[1], script[2], script[3])) + print('};') + print() def dump_script_data(likely_script_dict, all_scripts): """Dump the script data.""" - print - print 'const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({' + print() + print('const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({') for locale in sorted(likely_script_dict.keys()): script = likely_script_dict[locale] - print ' {0x%08Xu, %2du}, // %s -> %s' % ( + print(' {0x%08Xu, %2du}, // %s -> %s' % ( pack_to_uint32(locale), all_scripts.index(script), locale.replace('_', '-'), - script) - print '});' + script)) + print('});') def pack_to_uint64(locale): @@ -152,13 +152,13 @@ def pack_to_uint64(locale): def dump_representative_locales(representative_locales): """Dump the set of representative locales.""" - print - print 'std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({' + print() + print('std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({') for locale in sorted(representative_locales): - print ' 0x%08XLLU, // %s' % ( + print(' 0x%08XLLU, // %s' % ( pack_to_uint64(locale), - locale) - print '});' + locale)) + print('});') def read_and_dump_likely_data(icu_data_dir): @@ -220,30 +220,30 @@ def get_likely_script(locale, likely_script_dict): def dump_parent_data(script_organized_dict): """Dump information for parents of locales.""" sorted_scripts = sorted(script_organized_dict.keys()) - print + print() for script in sorted_scripts: parent_dict = script_organized_dict[script] print ('const std::unordered_map<uint32_t, uint32_t> %s_PARENTS({' % escape_script_variable_name(script.upper())) for locale in sorted(parent_dict.keys()): parent = parent_dict[locale] - print ' {0x%08Xu, 0x%08Xu}, // %s -> %s' % ( + print(' {0x%08Xu, 0x%08Xu}, // %s -> %s' % ( pack_to_uint32(locale), pack_to_uint32(parent), locale.replace('_', '-'), - parent.replace('_', '-')) - print '});' - print - - print 'const struct {' - print ' const char script[4];' - print ' const std::unordered_map<uint32_t, uint32_t>* map;' - print '} SCRIPT_PARENTS[] = {' + parent.replace('_', '-'))) + print('});') + print() + + print('const struct {') + print(' const char script[4];') + print(' const std::unordered_map<uint32_t, uint32_t>* map;') + print('} SCRIPT_PARENTS[] = {') for script in sorted_scripts: - print " {{'%c', '%c', '%c', '%c'}, &%s_PARENTS}," % ( + print(" {{'%c', '%c', '%c', '%c'}, &%s_PARENTS}," % ( script[0], script[1], script[2], script[3], - escape_script_variable_name(script.upper())) - print '};' + escape_script_variable_name(script.upper()))) + print('};') def dump_parent_tree_depth(parent_dict): @@ -256,8 +256,8 @@ def dump_parent_tree_depth(parent_dict): depth += 1 max_depth = max(max_depth, depth) assert max_depth < 5 # Our algorithms assume small max_depth - print - print 'const size_t MAX_PARENT_DEPTH = %d;' % max_depth + print() + print('const size_t MAX_PARENT_DEPTH = %d;' % max_depth) def read_and_dump_parent_data(icu_data_dir, likely_script_dict): @@ -281,8 +281,8 @@ def main(): source_root, 'external', 'icu', 'icu4c', 'source', 'data') - print '// Auto-generated by %s' % sys.argv[0] - print + print('// Auto-generated by %s' % sys.argv[0]) + print() likely_script_dict = read_and_dump_likely_data(icu_data_dir) read_and_dump_parent_data(icu_data_dir, likely_script_dict) diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp index 6efd1f64d8fe..954b816a52bf 100644 --- a/tools/locked_region_code_injection/Android.bp +++ b/tools/locked_region_code_injection/Android.bp @@ -12,10 +12,27 @@ java_binary_host { manifest: "manifest.txt", srcs: ["src/**/*.java"], static_libs: [ - "asm-9.2", - "asm-commons-9.2", - "asm-tree-9.2", - "asm-analysis-9.2", - "guava-21.0", + "guava", + "ow2-asm", + "ow2-asm-analysis", + "ow2-asm-commons", + "ow2-asm-tree", + ], +} + +java_library_host { + name: "lockedregioncodeinjection_input", + manifest: "test/manifest.txt", + srcs: ["test/*/*.java"], + static_libs: [ + "guava", + "ow2-asm", + "ow2-asm-analysis", + "ow2-asm-commons", + "ow2-asm-tree", + "hamcrest-library", + "hamcrest", + "platform-test-annotations", + "junit", ], } diff --git a/tools/locked_region_code_injection/OWNERS b/tools/locked_region_code_injection/OWNERS new file mode 100644 index 000000000000..bd43f1736ca5 --- /dev/null +++ b/tools/locked_region_code_injection/OWNERS @@ -0,0 +1,4 @@ +# Everyone in frameworks/base is included by default +shayba@google.com +shombert@google.com +timmurray@google.com diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java index 81a077324e6c..2067bb4ef2fe 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java @@ -13,37 +13,51 @@ */ package lockedregioncodeinjection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.TryCatchBlockSorter; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LineNumberNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.TryCatchBlockNode; import org.objectweb.asm.tree.analysis.Analyzer; import org.objectweb.asm.tree.analysis.AnalyzerException; import org.objectweb.asm.tree.analysis.BasicValue; import org.objectweb.asm.tree.analysis.Frame; -import static com.google.common.base.Preconditions.checkElementIndex; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; /** - * This visitor does two things: + * This visitor operates on two kinds of targets. For a legacy target, it does the following: * - * 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and insert the corresponding pre + * 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and inserts the corresponding pre * and post methods calls should it matches one of the given target type in the Configuration. * * 2. Find all methods that are synchronized and insert pre method calls in the beginning and post * method calls just before all return instructions. + * + * For a scoped target, it does the following: + * + * 1. Finds all the MONITOR_ENTER instructions in the byte code. If the target of the opcode is + * named in a --scope switch, then the pre method is invoked ON THE TARGET immediately after + * MONITOR_ENTER opcode completes. + * + * 2. Finds all the MONITOR_EXIT instructions in the byte code. If the target of the opcode is + * named in a --scope switch, then the post method is invoked ON THE TARGET immediately before + * MONITOR_EXIT opcode completes. */ class LockFindingClassVisitor extends ClassVisitor { private String className = null; @@ -73,12 +87,16 @@ class LockFindingClassVisitor extends ClassVisitor { class LockFindingMethodVisitor extends MethodVisitor { private String owner; private MethodVisitor chain; + private final String className; + private final String methodName; public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) { super(Utils.ASM_VERSION, mn); assert owner != null; this.owner = owner; this.chain = chain; + className = owner; + methodName = mn.name; } @SuppressWarnings("unchecked") @@ -93,6 +111,12 @@ class LockFindingClassVisitor extends ClassVisitor { for (LockTarget t : targets) { if (t.getTargetDesc().equals("L" + owner + ";")) { ownerMonitor = t; + if (ownerMonitor.getScoped()) { + final String emsg = String.format( + "scoped targets do not support synchronized methods in %s.%s()", + className, methodName); + throw new RuntimeException(emsg); + } } } } @@ -118,9 +142,11 @@ class LockFindingClassVisitor extends ClassVisitor { AbstractInsnNode s = instructions.getFirst(); MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPreOwner(), ownerMonitor.getPreMethod(), "()V", false); - insertMethodCallBefore(mn, frameMap, handlersMap, s, 0, call); + insertMethodCallBeforeSync(mn, frameMap, handlersMap, s, 0, call); } + boolean anyDup = false; + for (int i = 0; i < instructions.size(); i++) { AbstractInsnNode s = instructions.get(i); @@ -131,9 +157,15 @@ class LockFindingClassVisitor extends ClassVisitor { LockTargetState state = (LockTargetState) operand; for (int j = 0; j < state.getTargets().size(); j++) { LockTarget target = state.getTargets().get(j); - MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, - target.getPreOwner(), target.getPreMethod(), "()V", false); - insertMethodCallAfter(mn, frameMap, handlersMap, s, i, call); + MethodInsnNode call = methodCall(target, true); + if (target.getScoped()) { + TypeInsnNode cast = typeCast(target); + i += insertInvokeAcquire(mn, frameMap, handlersMap, s, i, + call, cast); + anyDup = true; + } else { + i += insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call); + } } } } @@ -144,8 +176,9 @@ class LockFindingClassVisitor extends ClassVisitor { if (operand instanceof LockTargetState) { LockTargetState state = (LockTargetState) operand; for (int j = 0; j < state.getTargets().size(); j++) { - // The instruction after a monitor_exit should be a label for the end of the implicit - // catch block that surrounds the synchronized block to call monitor_exit when an exception + // The instruction after a monitor_exit should be a label for + // the end of the implicit catch block that surrounds the + // synchronized block to call monitor_exit when an exception // occurs. checkState(instructions.get(i + 1).getType() == AbstractInsnNode.LABEL, "Expected to find label after monitor exit"); @@ -161,9 +194,16 @@ class LockFindingClassVisitor extends ClassVisitor { "Expected label to be the end of monitor exit's try block"); LockTarget target = state.getTargets().get(j); - MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, - target.getPostOwner(), target.getPostMethod(), "()V", false); - insertMethodCallAfter(mn, frameMap, handlersMap, label, labelIndex, call); + MethodInsnNode call = methodCall(target, false); + if (target.getScoped()) { + TypeInsnNode cast = typeCast(target); + i += insertInvokeRelease(mn, frameMap, handlersMap, s, i, + call, cast); + anyDup = true; + } else { + insertMethodCallAfter(mn, frameMap, handlersMap, label, + labelIndex, call); + } } } } @@ -174,16 +214,116 @@ class LockFindingClassVisitor extends ClassVisitor { MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPostOwner(), ownerMonitor.getPostMethod(), "()V", false); - insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call); + insertMethodCallBeforeSync(mn, frameMap, handlersMap, s, i, call); i++; // Skip ahead. Otherwise, we will revisit this instruction again. } } + + if (anyDup) { + mn.maxStack++; + } + super.visitEnd(); mn.accept(chain); } + + // Insert a call to a monitor pre handler. The node and the index identify the + // monitorenter call itself. Insert DUP immediately prior to the MONITORENTER. + // Insert the typecast and call (in that order) after the MONITORENTER. + public int insertInvokeAcquire(MethodNode mn, List<Frame> frameMap, + List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index, + MethodInsnNode call, TypeInsnNode cast) { + InsnList instructions = mn.instructions; + + // Insert a DUP right before MONITORENTER, to capture the object being locked. + // Note that the object will be typed as java.lang.Object. + instructions.insertBefore(node, new InsnNode(Opcodes.DUP)); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + // Insert the call right after the MONITORENTER. These entries are pushed after + // MONITORENTER so they are inserted in reverse order. MONITORENTER should be + // the target of a try/catch block, which means it must be immediately + // followed by a label (which is part of the try/catch block definition). + // Move forward past the label so the invocation in inside the proper block. + // Throw an error if the next instruction is not a label. + node = node.getNext(); + if (!(node instanceof LabelNode)) { + throw new RuntimeException(String.format("invalid bytecode sequence in %s.%s()", + className, methodName)); + } + node = node.getNext(); + index = instructions.indexOf(node); + + instructions.insertBefore(node, cast); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + instructions.insertBefore(node, call); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + return 3; + } + + // Insert instructions completely before the current opcode. This is slightly + // different from insertMethodCallBefore(), which inserts the call before MONITOREXIT + // but inserts the start and end labels after MONITOREXIT. + public int insertInvokeRelease(MethodNode mn, List<Frame> frameMap, + List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index, + MethodInsnNode call, TypeInsnNode cast) { + InsnList instructions = mn.instructions; + + instructions.insertBefore(node, new InsnNode(Opcodes.DUP)); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + instructions.insertBefore(node, cast); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + instructions.insertBefore(node, call); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + return 3; + } + } + + public static MethodInsnNode methodCall(LockTarget target, boolean pre) { + String spec = "()V"; + if (!target.getScoped()) { + if (pre) { + return new MethodInsnNode( + Opcodes.INVOKESTATIC, target.getPreOwner(), target.getPreMethod(), spec); + } else { + return new MethodInsnNode( + Opcodes.INVOKESTATIC, target.getPostOwner(), target.getPostMethod(), spec); + } + } else { + if (pre) { + return new MethodInsnNode( + Opcodes.INVOKEVIRTUAL, target.getPreOwner(), target.getPreMethod(), spec); + } else { + return new MethodInsnNode( + Opcodes.INVOKEVIRTUAL, target.getPostOwner(), target.getPostMethod(), spec); + } + } } - public static void insertMethodCallBefore(MethodNode mn, List<Frame> frameMap, + public static TypeInsnNode typeCast(LockTarget target) { + if (!target.getScoped()) { + return null; + } else { + // preOwner and postOwner return the same string for scoped targets. + return new TypeInsnNode(Opcodes.CHECKCAST, target.getPreOwner()); + } + } + + /** + * Insert a method call before the beginning or end of a synchronized method. + */ + public static void insertMethodCallBeforeSync(MethodNode mn, List<Frame> frameMap, List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index, MethodInsnNode call) { List<TryCatchBlockNode> handlers = handlersMap.get(index); @@ -226,6 +366,22 @@ class LockFindingClassVisitor extends ClassVisitor { updateCatchHandler(mn, handlers, start, end, handlersMap); } + // Insert instructions completely before the current opcode. This is slightly different from + // insertMethodCallBeforeSync(), which inserts the call before MONITOREXIT but inserts the + // start and end labels after MONITOREXIT. + public int insertMethodCallBefore(MethodNode mn, List<Frame> frameMap, + List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index, + MethodInsnNode call) { + InsnList instructions = mn.instructions; + + instructions.insertBefore(node, call); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + return 1; + } + + @SuppressWarnings("unchecked") public static void updateCatchHandler(MethodNode mn, List<TryCatchBlockNode> handlers, LabelNode start, LabelNode end, List<List<TryCatchBlockNode>> handlersMap) { diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java index c5e59e3dc64d..5f6240327a7f 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java @@ -21,14 +21,28 @@ package lockedregioncodeinjection; public class LockTarget { public static final LockTarget NO_TARGET = new LockTarget("", null, null); + // The lock which must be instrumented, in Java internal form (L<path>;). private final String targetDesc; + // The methods to be called when the lock is taken (released). For non-scoped locks, + // these are fully qualified static methods. For scoped locks, these are the + // unqualified names of a member method of the target lock. private final String pre; private final String post; + // If true, the pre and post methods are virtual on the target class. The pre and post methods + // are both called while the lock is held. If this field is false then the pre and post methods + // take no parameters and the post method is called after the lock is released. This is legacy + // behavior. + private final boolean scoped; - public LockTarget(String targetDesc, String pre, String post) { + public LockTarget(String targetDesc, String pre, String post, boolean scoped) { this.targetDesc = targetDesc; this.pre = pre; this.post = post; + this.scoped = scoped; + } + + public LockTarget(String targetDesc, String pre, String post) { + this(targetDesc, pre, post, false); } public String getTargetDesc() { @@ -40,7 +54,11 @@ public class LockTarget { } public String getPreOwner() { - return pre.substring(0, pre.lastIndexOf('.')); + if (scoped) { + return targetDesc.substring(1, targetDesc.length() - 1); + } else { + return pre.substring(0, pre.lastIndexOf('.')); + } } public String getPreMethod() { @@ -52,10 +70,23 @@ public class LockTarget { } public String getPostOwner() { - return post.substring(0, post.lastIndexOf('.')); + if (scoped) { + return targetDesc.substring(1, targetDesc.length() - 1); + } else { + return post.substring(0, post.lastIndexOf('.')); + } } public String getPostMethod() { return post.substring(post.lastIndexOf('.') + 1); } + + public boolean getScoped() { + return scoped; + } + + @Override + public String toString() { + return targetDesc + ":" + pre + ":" + post; + } } diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java index 99d841829132..5df0160abd8c 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java @@ -13,10 +13,11 @@ */ package lockedregioncodeinjection; -import java.util.List; import org.objectweb.asm.Type; import org.objectweb.asm.tree.analysis.BasicValue; +import java.util.List; + public class LockTargetState extends BasicValue { private final List<LockTarget> lockTargets; @@ -31,4 +32,10 @@ public class LockTargetState extends BasicValue { public List<LockTarget> getTargets() { return lockTargets; } + + @Override + public String toString() { + return "LockTargetState(" + getType().getDescriptor() + + ", " + lockTargets.size() + ")"; + } } diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java index 828cce72dda9..d22ea2338ff7 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java @@ -21,7 +21,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.Collections; +import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; @@ -36,6 +36,7 @@ public class Main { String legacyTargets = null; String legacyPreMethods = null; String legacyPostMethods = null; + List<LockTarget> targets = new ArrayList<>(); for (int i = 0; i < args.length; i++) { if ("-i".equals(args[i].trim())) { i++; @@ -52,23 +53,25 @@ public class Main { } else if ("--post".equals(args[i].trim())) { i++; legacyPostMethods = args[i].trim(); + } else if ("--scoped".equals(args[i].trim())) { + i++; + targets.add(Utils.getScopedTarget(args[i].trim())); } - } - // TODO(acleung): Better help message than asserts. - assert inJar != null; - assert outJar != null; + if (inJar == null) { + throw new RuntimeException("missing input jar path"); + } + if (outJar == null) { + throw new RuntimeException("missing output jar path"); + } assert legacyTargets == null || (legacyPreMethods != null && legacyPostMethods != null); ZipFile zipSrc = new ZipFile(inJar); ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outJar)); - List<LockTarget> targets = null; if (legacyTargets != null) { - targets = Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods, - legacyPostMethods); - } else { - targets = Collections.emptyList(); + targets.addAll(Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods, + legacyPostMethods)); } Enumeration<? extends ZipEntry> srcEntries = zipSrc.entries(); diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java index f1e84b1d8a33..bfef10611e6c 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java @@ -20,7 +20,7 @@ import java.util.List; public class Utils { - public static final int ASM_VERSION = Opcodes.ASM7; + public static final int ASM_VERSION = Opcodes.ASM9; /** * Reads a comma separated configuration similar to the Jack definition. @@ -44,4 +44,27 @@ public class Utils { return config; } + + /** + * Returns a single {@link LockTarget} from a string. The target is a comma-separated list of + * the target class, the request method, the release method, and a boolean which is true if this + * is a scoped target and false if this is a legacy target. The boolean is optional and + * defaults to true. + */ + public static LockTarget getScopedTarget(String arg) { + String[] c = arg.split(","); + if (c.length == 3) { + return new LockTarget(c[0], c[1], c[2], true); + } else if (c.length == 4) { + if (c[3].equals("true")) { + return new LockTarget(c[0], c[1], c[2], true); + } else if (c[3].equals("false")) { + return new LockTarget(c[0], c[1], c[2], false); + } else { + System.err.println("illegal target parameter \"" + c[3] + "\""); + } + } + // Fall through + throw new RuntimeException("invalid scoped target format"); + } } diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java index 31fa0bf63416..28f00b9c897a 100644 --- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java +++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java @@ -17,7 +17,10 @@ import org.junit.Assert; import org.junit.Test; /** - * To run the unit tests: + * To run the unit tests, first build the two necessary artifacts. Do this explicitly as they are + * not generally retained by a normal "build all". After lunching a target: + * m lockedregioncodeinjection + * m lockedregioncodeinjection_input * * <pre> * <code> @@ -29,31 +32,25 @@ import org.junit.Test; * mkdir -p out * rm -fr out/* * - * # Make booster - * javac -cp lib/asm-7.0_BETA.jar:lib/asm-commons-7.0_BETA.jar:lib/asm-tree-7.0_BETA.jar:lib/asm-analysis-7.0_BETA.jar:lib/guava-21.0.jar src/*/*.java -d out/ - * pushd out - * jar cfe lockedregioncodeinjection.jar lockedregioncodeinjection.Main */*.class - * popd - * - * # Make unit tests. - * javac -cp lib/junit-4.12.jar test/*/*.java -d out/ - * - * pushd out - * jar cfe test_input.jar lockedregioncodeinjection.Test */*.class - * popd + * # Paths to the build artifacts. These assume linux-x86; YMMV. + * ROOT=$TOP/out/host/linux-x86 + * EXE=$ROOT/bin/lockedregioncodeinjection + * INPUT=$ROOT/frameworkd/lockedregioncodeinjection_input.jar * * # Run tool on unit tests. - * java -ea -cp lib/asm-7.0_BETA.jar:lib/asm-commons-7.0_BETA.jar:lib/asm-tree-7.0_BETA.jar:lib/asm-analysis-7.0_BETA.jar:lib/guava-21.0.jar:out/lockedregioncodeinjection.jar \ - * lockedregioncodeinjection.Main \ - * -i out/test_input.jar -o out/test_output.jar \ + * $EXE -i $INPUT -o out/test_output.jar \ * --targets 'Llockedregioncodeinjection/TestTarget;' \ * --pre 'lockedregioncodeinjection/TestTarget.boost' \ * --post 'lockedregioncodeinjection/TestTarget.unboost' * * # Run unit tests. - * java -ea -cp lib/hamcrest-core-1.3.jar:lib/junit-4.12.jar:out/test_output.jar \ + * java -ea -cp out/test_output.jar \ * org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain * </code> + * OR + * <code> + * bash test/unit-test.sh + * </code> * </pre> */ public class TestMain { @@ -64,7 +61,9 @@ public class TestMain { Assert.assertEquals(TestTarget.boostCount, 0); Assert.assertEquals(TestTarget.unboostCount, 0); - Assert.assertEquals(TestTarget.unboostCount, 0); + Assert.assertEquals(TestTarget.invokeCount, 0); + Assert.assertEquals(TestTarget.boostCountLocked, 0); + Assert.assertEquals(TestTarget.unboostCountLocked, 0); synchronized (t) { Assert.assertEquals(TestTarget.boostCount, 1); @@ -75,6 +74,8 @@ public class TestMain { Assert.assertEquals(TestTarget.boostCount, 1); Assert.assertEquals(TestTarget.unboostCount, 1); Assert.assertEquals(TestTarget.invokeCount, 1); + Assert.assertEquals(TestTarget.boostCountLocked, 0); + Assert.assertEquals(TestTarget.unboostCountLocked, 0); } @Test @@ -84,12 +85,16 @@ public class TestMain { Assert.assertEquals(TestTarget.boostCount, 0); Assert.assertEquals(TestTarget.unboostCount, 0); + Assert.assertEquals(TestTarget.boostCountLocked, 0); + Assert.assertEquals(TestTarget.unboostCountLocked, 0); t.synchronizedCall(); Assert.assertEquals(TestTarget.boostCount, 1); Assert.assertEquals(TestTarget.unboostCount, 1); Assert.assertEquals(TestTarget.invokeCount, 1); + Assert.assertEquals(TestTarget.boostCountLocked, 0); + Assert.assertEquals(TestTarget.unboostCountLocked, 0); } @Test @@ -99,12 +104,16 @@ public class TestMain { Assert.assertEquals(TestTarget.boostCount, 0); Assert.assertEquals(TestTarget.unboostCount, 0); + Assert.assertEquals(TestTarget.boostCountLocked, 0); + Assert.assertEquals(TestTarget.unboostCountLocked, 0); t.synchronizedCallReturnInt(); Assert.assertEquals(TestTarget.boostCount, 1); Assert.assertEquals(TestTarget.unboostCount, 1); Assert.assertEquals(TestTarget.invokeCount, 1); + Assert.assertEquals(TestTarget.boostCountLocked, 0); + Assert.assertEquals(TestTarget.unboostCountLocked, 0); } @Test @@ -253,4 +262,125 @@ public class TestMain { Assert.assertEquals(TestTarget.invokeCount, 1); } + @Test + public void testScopedTarget() { + TestScopedTarget target = new TestScopedTarget(); + Assert.assertEquals(0, target.scopedLock().mLevel); + + synchronized (target.scopedLock()) { + Assert.assertEquals(1, target.scopedLock().mLevel); + } + Assert.assertEquals(0, target.scopedLock().mLevel); + + synchronized (target.scopedLock()) { + synchronized (target.scopedLock()) { + Assert.assertEquals(2, target.scopedLock().mLevel); + } + } + Assert.assertEquals(0, target.scopedLock().mLevel); + } + + @Test + public void testScopedExceptionHandling() { + TestScopedTarget target = new TestScopedTarget(); + Assert.assertEquals(0, target.scopedLock().mLevel); + + boolean handled; + + // 1: an exception inside the block properly releases the lock. + handled = false; + try { + synchronized (target.scopedLock()) { + Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); + Assert.assertEquals(1, target.scopedLock().mLevel); + throw new RuntimeException(); + } + } catch (RuntimeException e) { + Assert.assertEquals(0, target.scopedLock().mLevel); + handled = true; + } + Assert.assertEquals(0, target.scopedLock().mLevel); + Assert.assertEquals(true, handled); + // Just verify that the lock can still be taken + Assert.assertEquals(false, Thread.holdsLock(target.scopedLock())); + + // 2: An exception inside the monitor enter function + handled = false; + target.throwOnEnter(true); + try { + synchronized (target.scopedLock()) { + // The exception was thrown inside monitorEnter(), so the code should + // never reach this point. + Assert.assertEquals(0, 1); + } + } catch (RuntimeException e) { + Assert.assertEquals(0, target.scopedLock().mLevel); + handled = true; + } + Assert.assertEquals(0, target.scopedLock().mLevel); + Assert.assertEquals(true, handled); + // Just verify that the lock can still be taken + Assert.assertEquals(false, Thread.holdsLock(target.scopedLock())); + + // 3: An exception inside the monitor exit function + handled = false; + target.throwOnEnter(true); + try { + synchronized (target.scopedLock()) { + Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); + Assert.assertEquals(1, target.scopedLock().mLevel); + } + } catch (RuntimeException e) { + Assert.assertEquals(0, target.scopedLock().mLevel); + handled = true; + } + Assert.assertEquals(0, target.scopedLock().mLevel); + Assert.assertEquals(true, handled); + // Just verify that the lock can still be taken + Assert.assertEquals(false, Thread.holdsLock(target.scopedLock())); + } + + // Provide an in-class type conversion for the scoped target. + private Object untypedLock(TestScopedTarget target) { + return target.scopedLock(); + } + + @Test + public void testScopedLockTyping() { + TestScopedTarget target = new TestScopedTarget(); + Assert.assertEquals(target.scopedLock().mLevel, 0); + + // Scoped lock injection works on the static type of an object. In general, it is + // a very bad idea to do type conversion on scoped locks, but the general rule is + // that conversions within a single method are recognized by the lock injection + // tool and injection occurs. Conversions outside a single method are not + // recognized and injection does not occur. + + // 1. Conversion occurs outside the class. The visible type of the lock is Object + // in this block, so no injection takes place on 'untypedLock', even though the + // dynamic type is TestScopedLock. + synchronized (target.untypedLock()) { + Assert.assertEquals(0, target.scopedLock().mLevel); + Assert.assertEquals(true, target.untypedLock() instanceof TestScopedLock); + Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); + } + + // 2. Conversion occurs inside the class but in another method. The visible type + // of the lock is Object in this block, so no injection takes place on + // 'untypedLock', even though the dynamic type is TestScopedLock. + synchronized (untypedLock(target)) { + Assert.assertEquals(0, target.scopedLock().mLevel); + Assert.assertEquals(true, target.untypedLock() instanceof TestScopedLock); + Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); + } + + // 3. Conversion occurs inside the method. The compiler can determine the type of + // the lock within a single function, so injection does take place here. + Object untypedLock = target.scopedLock(); + synchronized (untypedLock) { + Assert.assertEquals(1, target.scopedLock().mLevel); + Assert.assertEquals(true, untypedLock instanceof TestScopedLock); + Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); + } + } } diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java new file mode 100644 index 000000000000..7441d2b1da48 --- /dev/null +++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java @@ -0,0 +1,38 @@ +/* + * 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. + */ +package lockedregioncodeinjection; + +public class TestScopedLock { + public int mEntered = 0; + public int mExited = 0; + + public int mLevel = 0; + private final TestScopedTarget mTarget; + + TestScopedLock(TestScopedTarget target) { + mTarget = target; + } + + void monitorEnter() { + mLevel++; + mEntered++; + mTarget.enter(mLevel); + } + + void monitorExit() { + mLevel--; + mExited++; + mTarget.exit(mLevel); + } +} diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java new file mode 100644 index 000000000000..c80975e9c6b3 --- /dev/null +++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java @@ -0,0 +1,56 @@ +/* + * 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. + */ +package lockedregioncodeinjection; + +public class TestScopedTarget { + + public final TestScopedLock mLock;; + + private boolean mNextEnterThrows = false; + private boolean mNextExitThrows = false; + + TestScopedTarget() { + mLock = new TestScopedLock(this); + } + + TestScopedLock scopedLock() { + return mLock; + } + + Object untypedLock() { + return mLock; + } + + void enter(int level) { + if (mNextEnterThrows) { + mNextEnterThrows = false; + throw new RuntimeException(); + } + } + + void exit(int level) { + if (mNextExitThrows) { + mNextExitThrows = false; + throw new RuntimeException(); + } + } + + void throwOnEnter(boolean b) { + mNextEnterThrows = b; + } + + void throwOnExit(boolean b) { + mNextExitThrows = b; + } +} diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java index d1c8f340a598..e3ba6a77e3b7 100644 --- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java +++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java @@ -19,8 +19,17 @@ public class TestTarget { public static int invokeCount = 0; public static boolean nextUnboostThrows = false; + // If this is not null, then this is the lock under test. The lock must not be held when boost() + // or unboost() are called. + public static Object mLock = null; + public static int boostCountLocked = 0; + public static int unboostCountLocked = 0; + public static void boost() { boostCount++; + if (mLock != null && Thread.currentThread().holdsLock(mLock)) { + boostCountLocked++; + } } public static void unboost() { @@ -29,6 +38,9 @@ public class TestTarget { throw new RuntimeException(); } unboostCount++; + if (mLock != null && Thread.currentThread().holdsLock(mLock)) { + unboostCountLocked++; + } } public static void invoke() { diff --git a/tools/locked_region_code_injection/test/manifest.txt b/tools/locked_region_code_injection/test/manifest.txt new file mode 100644 index 000000000000..2314c188721c --- /dev/null +++ b/tools/locked_region_code_injection/test/manifest.txt @@ -0,0 +1 @@ +Main-Class: org.junit.runner.JUnitCore diff --git a/tools/locked_region_code_injection/test/unit-test.sh b/tools/locked_region_code_injection/test/unit-test.sh new file mode 100755 index 000000000000..9fa6f39af14a --- /dev/null +++ b/tools/locked_region_code_injection/test/unit-test.sh @@ -0,0 +1,98 @@ +#! /bin/bash +# + +# Copyright (C) 2023 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. + +# This script runs the tests for the lockedregioninjectioncode. See +# TestMain.java for the invocation. The script expects that a full build has +# already been done and artifacts are in $TOP/out. + +# Compute the default top of the workspace. The following code copies the +# strategy of croot. (croot cannot be usd directly because it is a function and +# functions are not carried over into subshells.) This gives the correct answer +# if run from inside a workspace. If run from outside a workspace, supply TOP +# on the command line. +TOPFILE=build/make/core/envsetup.mk +TOP=$(dirname $(realpath $0)) +while [[ ! $TOP = / && ! -f $TOP/$TOPFILE ]]; do + TOP=$(dirname $TOP) +done +# TOP is "/" if this script is located outside a workspace. + +# If the user supplied a top directory, use it instead +if [[ -n $1 ]]; then + TOP=$1 + shift +fi +if [[ -z $TOP || $TOP = / ]]; then + echo "usage: $0 <workspace-root>" + exit 1 +elif [[ ! -d $TOP ]]; then + echo "$TOP is not a directory" + exit 1 +elif [[ ! -d $TOP/prebuilts/misc/common ]]; then + echo "$TOP does not look like w workspace" + exit 1 +fi +echo "Using workspace $TOP" + +# Pick up the current java compiler. The lunch target is not very important, +# since most, if not all, will use the same host binaries. +pushd $TOP > /dev/null +. build/envsetup.sh > /dev/null 2>&1 +lunch redfin-userdebug > /dev/null 2>&1 +popd > /dev/null + +# Bail on any error +set -o pipefail +trap 'exit 1' ERR + +# Create the two sources +pushd $TOP > /dev/null +m lockedregioncodeinjection +m lockedregioncodeinjection_input +popd > /dev/null + +# Create a temporary directory outside of the workspace. +OUT=$TOP/out/host/test/lockedregioncodeinjection +echo + +# Clean the directory +if [[ -d $OUT ]]; then rm -r $OUT; fi +mkdir -p $OUT + +ROOT=$TOP/out/host/linux-x86 +EXE=$ROOT/bin/lockedregioncodeinjection +INP=$ROOT/framework/lockedregioncodeinjection_input.jar + +# Run tool on unit tests. +$EXE \ + -i $INP -o $OUT/test_output.jar \ + --targets 'Llockedregioncodeinjection/TestTarget;' \ + --pre 'lockedregioncodeinjection/TestTarget.boost' \ + --post 'lockedregioncodeinjection/TestTarget.unboost' \ + --scoped 'Llockedregioncodeinjection/TestScopedLock;,monitorEnter,monitorExit' + +# Run unit tests. +java -ea -cp $OUT/test_output.jar \ + org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain + +# Extract the class files and decompile them for possible post-analysis. +pushd $OUT > /dev/null +jar -x --file test_output.jar lockedregioncodeinjection +for class in lockedregioncodeinjection/*.class; do + javap -c -v $class > ${class%.class}.asm +done +popd > /dev/null + +echo "artifacts are in $OUT" diff --git a/tools/preload-check/AndroidTest.xml b/tools/preload-check/AndroidTest.xml index a0645d5b1051..d486f0f914c6 100644 --- a/tools/preload-check/AndroidTest.xml +++ b/tools/preload-check/AndroidTest.xml @@ -14,7 +14,7 @@ limitations under the License. --> <configuration description="Config for PreloadCheck"> - <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> <option name="cleanup" value="true" /> <option name="push" value="preload-check-device.jar->/data/local/tmp/preload-check-device.jar" /> </target_preparer> diff --git a/tools/preload-check/OWNERS b/tools/preload-check/OWNERS new file mode 100644 index 000000000000..e71c7332ca90 --- /dev/null +++ b/tools/preload-check/OWNERS @@ -0,0 +1 @@ +include /ZYGOTE_OWNERS diff --git a/tools/preload-check/device/src/com/android/preload/check/Util.java b/tools/preload-check/device/src/com/android/preload/check/Util.java index fccea0a0c107..f521c95839f1 100644 --- a/tools/preload-check/device/src/com/android/preload/check/Util.java +++ b/tools/preload-check/device/src/com/android/preload/check/Util.java @@ -25,8 +25,8 @@ import java.io.FileOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; +import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedList; import java.util.List; public class Util { @@ -48,7 +48,7 @@ public class Util { Class<?> vmClassLoaderClass = Class.forName("java.lang.VMClassLoader"); Method getResources = vmClassLoaderClass.getDeclaredMethod("getResources", String.class); getResources.setAccessible(true); - LinkedList<DexFile> res = new LinkedList<>(); + ArrayList<DexFile> res = new ArrayList<>(); for (int i = 1;; i++) { try { String name = "classes" + (i > 1 ? String.valueOf(i) : "") + ".dex"; diff --git a/tools/preload/loadclass/LoadClass.java b/tools/preload/loadclass/LoadClass.java index a71b6a8b145e..3f6658ab8c65 100644 --- a/tools/preload/loadclass/LoadClass.java +++ b/tools/preload/loadclass/LoadClass.java @@ -14,8 +14,8 @@ * limitations under the License. */ -import android.util.Log; import android.os.Debug; +import android.util.Log; /** * Loads a class, runs the garbage collector, and prints showmap output. @@ -28,7 +28,7 @@ class LoadClass { System.loadLibrary("android_runtime"); if (registerNatives() < 0) { - throw new RuntimeException("Error registering natives."); + throw new RuntimeException("Error registering natives."); } Debug.startAllocCounting(); @@ -46,7 +46,7 @@ class LoadClass { } } - System.gc(); + Runtime.getRuntime().gc(); int allocCount = Debug.getGlobalAllocCount(); int allocSize = Debug.getGlobalAllocSize(); @@ -73,7 +73,7 @@ class LoadClass { response.append(',').append(freedCount); response.append(',').append(freedSize); response.append(',').append(nativeHeapSize); - + System.out.println(response.toString()); } diff --git a/tools/processors/immutability/Android.bp b/tools/processors/immutability/Android.bp new file mode 100644 index 000000000000..a7d69039fcb0 --- /dev/null +++ b/tools/processors/immutability/Android.bp @@ -0,0 +1,80 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_library_host { + name: "ImmutabilityAnnotationProcessorHostLibrary", + srcs: [ + "src/**/*.kt", + "src/**/*.java", + ], + use_tools_jar: true, + javacflags: [ + "--add-modules=jdk.compiler", + "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], +} + +java_plugin { + name: "ImmutabilityAnnotationProcessor", + processor_class: "android.processor.immutability.ImmutabilityProcessor", + static_libs: ["ImmutabilityAnnotationProcessorHostLibrary"], + javacflags: [ + "--add-modules=jdk.compiler", + "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], +} + +java_library { + name: "ImmutabilityAnnotation", + srcs: ["src/**/Immutable.java"], + sdk_version: "core_current", + host_supported: true, +} + +java_test_host { + name: "ImmutabilityAnnotationProcessorUnitTests", + + srcs: ["test/**/*.kt"], + + static_libs: [ + "compile-testing-prebuilt", + "truth-prebuilt", + "junit", + "kotlin-reflect", + "ImmutabilityAnnotationProcessorHostLibrary", + ], + + // Bundle the source file so it can be loaded into the test compiler + java_resources: [":ImmutabilityAnnotationJavaSource"], + + test_suites: ["general-tests"], + test_options: { + unit_test: true, + }, + javacflags: [ + "--add-modules=jdk.compiler", + "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], + test_config_template: "AndroidTestTemplate.xml", +} + +filegroup { + name: "ImmutabilityAnnotationJavaSource", + srcs: ["src/android/processor/immutability/Immutable.java"], + path: "src/android/processor/immutability/", +} diff --git a/tools/processors/immutability/AndroidTestTemplate.xml b/tools/processors/immutability/AndroidTestTemplate.xml new file mode 100644 index 000000000000..b9cf62f5ca5b --- /dev/null +++ b/tools/processors/immutability/AndroidTestTemplate.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 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. +--> +<!-- This test config file is auto-generated. --> +<configuration description="Runs {MODULE}"> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-unit-tests" /> + <option name="config-descriptor:metadata" key="component" value="{MODULE}" /> + + {EXTRA_CONFIGS} + + <test class="com.android.tradefed.testtype.IsolatedHostTest" > + <option name="jar" value="{MODULE}.jar" /> + <option name="java-flags" value="--add-modules=jdk.compiler"/> + <option name="java-flags" value="--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED"/> + <option name="java-flags" value="--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED"/> + <option name="java-flags" value="--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED"/> + <option name="java-flags" value="--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"/> + </test> +</configuration> diff --git a/tools/processors/immutability/OWNERS b/tools/processors/immutability/OWNERS new file mode 100644 index 000000000000..86ae5818e91c --- /dev/null +++ b/tools/processors/immutability/OWNERS @@ -0,0 +1 @@ +include /PACKAGE_MANAGER_OWNERS diff --git a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt new file mode 100644 index 000000000000..c6f6d45215fe --- /dev/null +++ b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt @@ -0,0 +1,424 @@ +/* + * Copyright (C) 2022 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. + */ + +@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE") + +package android.processor.immutability + +import com.sun.tools.javac.code.Symbol +import com.sun.tools.javac.code.Type +import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.ProcessingEnvironment +import javax.annotation.processing.RoundEnvironment +import javax.lang.model.SourceVersion +import javax.lang.model.element.Element +import javax.lang.model.element.ElementKind +import javax.lang.model.element.Modifier +import javax.lang.model.element.TypeElement +import javax.lang.model.type.TypeKind +import javax.lang.model.type.TypeMirror +import javax.tools.Diagnostic + +val IMMUTABLE_ANNOTATION_NAME = Immutable::class.qualifiedName + +class ImmutabilityProcessor : AbstractProcessor() { + + companion object { + + /** + * Types that are already immutable. Will also ignore subclasses. + */ + private val IGNORED_SUPER_TYPES = listOf( + "java.io.File", + "java.lang.Boolean", + "java.lang.Byte", + "java.lang.CharSequence", + "java.lang.Character", + "java.lang.Double", + "java.lang.Float", + "java.lang.Integer", + "java.lang.Long", + "java.lang.Short", + "java.lang.String", + "java.lang.Void", + "java.util.UUID", + "android.os.Parcelable.Creator", + ) + + /** + * Types that are already immutable. Must be an exact match, does not include any super + * or sub classes. + */ + private val IGNORED_EXACT_TYPES = listOf( + "java.lang.Class", + "java.lang.Object", + ) + + private val IGNORED_METHODS = listOf( + "writeToParcel", + ) + } + + private lateinit var collectionType: TypeMirror + private lateinit var mapType: TypeMirror + + private lateinit var ignoredSuperTypes: List<TypeMirror> + private lateinit var ignoredExactTypes: List<TypeMirror> + + private val seenTypesByPolicy = mutableMapOf<Set<Immutable.Policy.Exception>, Set<Type>>() + + override fun getSupportedSourceVersion() = SourceVersion.latest()!! + + override fun getSupportedAnnotationTypes() = setOf(Immutable::class.qualifiedName) + + override fun init(processingEnv: ProcessingEnvironment) { + super.init(processingEnv) + collectionType = processingEnv.erasedType("java.util.Collection")!! + mapType = processingEnv.erasedType("java.util.Map")!! + ignoredSuperTypes = IGNORED_SUPER_TYPES.mapNotNull { processingEnv.erasedType(it) } + ignoredExactTypes = IGNORED_EXACT_TYPES.mapNotNull { processingEnv.erasedType(it) } + } + + override fun process( + annotations: MutableSet<out TypeElement>, + roundEnvironment: RoundEnvironment + ): Boolean { + annotations.find { + it.qualifiedName.toString() == IMMUTABLE_ANNOTATION_NAME + } ?: return false + roundEnvironment.getElementsAnnotatedWith(Immutable::class.java) + .forEach { + visitClass( + parentChain = emptyList(), + seenTypesByPolicy = seenTypesByPolicy, + elementToPrint = it, + classType = it as Symbol.TypeSymbol, + parentPolicyExceptions = emptySet() + ) + } + return true + } + + /** + * @return true if any error was encountered at this level or any child level + */ + private fun visitClass( + parentChain: List<String>, + seenTypesByPolicy: MutableMap<Set<Immutable.Policy.Exception>, Set<Type>>, + elementToPrint: Element, + classType: Symbol.TypeSymbol, + parentPolicyExceptions: Set<Immutable.Policy.Exception>, + ): Boolean { + if (isIgnored(classType)) return false + + val policyAnnotation = classType.getAnnotation(Immutable.Policy::class.java) + val newPolicyExceptions = parentPolicyExceptions + policyAnnotation?.exceptions.orEmpty() + + // If already seen this type with the same policies applied, skip it + val seenTypes = seenTypesByPolicy[newPolicyExceptions] + val type = classType.asType() + if (seenTypes?.contains(type) == true) return false + seenTypesByPolicy[newPolicyExceptions] = seenTypes.orEmpty() + type + + val allowFinalClassesFinalFields = + newPolicyExceptions.contains(Immutable.Policy.Exception.FINAL_CLASSES_WITH_FINAL_FIELDS) + + val filteredElements = classType.enclosedElements + .filterNot(::isIgnored) + + val hasFieldError = filteredElements + .filter { it.getKind() == ElementKind.FIELD } + .fold(false) { anyError, field -> + if (field.isStatic) { + if (!field.isPrivate) { + val finalityError = !field.modifiers.contains(Modifier.FINAL) + if (finalityError) { + printError(parentChain, field, MessageUtils.staticNonFinalFailure()) + } + + // Must call visitType first so it doesn't get short circuited by the || + visitType( + parentChain = parentChain, + seenTypesByPolicy = seenTypesByPolicy, + symbol = field, + type = field.type, + parentPolicyExceptions = parentPolicyExceptions + ) || anyError || finalityError + } + return@fold anyError + } else { + val isFinal = field.modifiers.contains(Modifier.FINAL) + if (!isFinal || !allowFinalClassesFinalFields) { + printError(parentChain, field, MessageUtils.memberNotMethodFailure()) + return@fold true + } + + return@fold anyError + } + } + + // Scan inner classes before methods so that any violations isolated to the file prints + // the error on the class declaration rather than on the method that returns the type. + // Although it doesn't matter too much either way. + val hasClassError = filteredElements + .filter { it.getKind() == ElementKind.CLASS } + .map { it as Symbol.ClassSymbol } + .fold(false) { anyError, innerClass -> + // Must call visitClass first so it doesn't get short circuited by the || + visitClass( + parentChain, + seenTypesByPolicy, + innerClass, + innerClass, + newPolicyExceptions + ) || anyError + } + + val newChain = parentChain + "$classType" + + val hasMethodError = filteredElements + .asSequence() + .filter { it.getKind() == ElementKind.METHOD } + .map { it as Symbol.MethodSymbol } + .filterNot { it.isStatic } + .filterNot { IGNORED_METHODS.contains(it.name.toString()) } + .fold(false) { anyError, method -> + // Must call visitMethod first so it doesn't get short circuited by the || + visitMethod(newChain, seenTypesByPolicy, method, newPolicyExceptions) || anyError + } + + val className = classType.simpleName.toString() + val isRegularClass = classType.getKind() == ElementKind.CLASS + + var anyError = hasFieldError || hasClassError || hasMethodError + + // If final classes are not considered OR there's a non-field failure, also check for + // interface/@Immutable, assuming the class is malformed + if ((isRegularClass && !allowFinalClassesFinalFields) || hasMethodError || hasClassError) { + if (classType.getAnnotation(Immutable::class.java) == null) { + printError( + parentChain, + elementToPrint, + MessageUtils.classNotImmutableFailure(className) + ) + anyError = true + } + + if (classType.getKind() != ElementKind.INTERFACE) { + printError(parentChain, elementToPrint, MessageUtils.nonInterfaceClassFailure()) + anyError = true + } + } + + // Check all of the super classes, since methods in those classes are also accessible + (classType as? Symbol.ClassSymbol)?.run { + (interfaces + superclass).forEach { + val element = it.asElement() ?: return@forEach + visitClass(parentChain, seenTypesByPolicy, element, element, newPolicyExceptions) + } + } + + if (isRegularClass && !anyError && allowFinalClassesFinalFields && + !classType.modifiers.contains(Modifier.FINAL) + ) { + printError(parentChain, elementToPrint, MessageUtils.classNotFinalFailure(className)) + return true + } + + return anyError + } + + /** + * @return true if any error was encountered at this level or any child level + */ + private fun visitMethod( + parentChain: List<String>, + seenTypesByPolicy: MutableMap<Set<Immutable.Policy.Exception>, Set<Type>>, + method: Symbol.MethodSymbol, + parentPolicyExceptions: Set<Immutable.Policy.Exception>, + ): Boolean { + val returnType = method.returnType + val typeName = returnType.toString() + when (returnType.kind) { + TypeKind.BOOLEAN, + TypeKind.BYTE, + TypeKind.SHORT, + TypeKind.INT, + TypeKind.LONG, + TypeKind.CHAR, + TypeKind.FLOAT, + TypeKind.DOUBLE, + TypeKind.NONE, + TypeKind.NULL -> { + // Do nothing + } + TypeKind.VOID -> { + if (!method.isConstructor) { + printError(parentChain, method, MessageUtils.voidReturnFailure()) + return true + } + } + TypeKind.ARRAY -> { + printError(parentChain, method, MessageUtils.arrayFailure()) + return true + } + TypeKind.DECLARED -> { + return visitType( + parentChain, + seenTypesByPolicy, + method, + method.returnType, + parentPolicyExceptions + ) + } + TypeKind.ERROR, + TypeKind.TYPEVAR, + TypeKind.WILDCARD, + TypeKind.PACKAGE, + TypeKind.EXECUTABLE, + TypeKind.OTHER, + TypeKind.UNION, + TypeKind.INTERSECTION, + // Java 9+ + // TypeKind.MODULE, + null -> { + printError( + parentChain, method, + MessageUtils.genericTypeKindFailure(typeName = typeName) + ) + return true + } + else -> { + printError( + parentChain, method, + MessageUtils.genericTypeKindFailure(typeName = typeName) + ) + return true + } + } + + return false + } + + /** + * @return true if any error was encountered at this level or any child level + */ + private fun visitType( + parentChain: List<String>, + seenTypesByPolicy: MutableMap<Set<Immutable.Policy.Exception>, Set<Type>>, + symbol: Symbol, + type: Type, + parentPolicyExceptions: Set<Immutable.Policy.Exception>, + nonInterfaceClassFailure: () -> String = { MessageUtils.nonInterfaceReturnFailure() }, + ): Boolean { + // Skip if the symbol being considered is itself ignored + if (isIgnored(symbol)) return false + + // Skip if the type being checked, like for a typeArg or return type, is ignored + if (isIgnored(type)) return false + + // Skip if that typeArg is itself ignored when inspected at the class header level + if (isIgnored(type.asElement())) return false + + if (type.isPrimitive) return false + if (type.isPrimitiveOrVoid) { + printError(parentChain, symbol, MessageUtils.voidReturnFailure()) + return true + } + + val policyAnnotation = symbol.getAnnotation(Immutable.Policy::class.java) + val newPolicyExceptions = parentPolicyExceptions + policyAnnotation?.exceptions.orEmpty() + + // Collection (and Map) types are ignored for the interface check as they have immutability + // enforced through a runtime exception which must be verified in a separate runtime test + val isMap = processingEnv.typeUtils.isAssignable(type, mapType) + if (!processingEnv.typeUtils.isAssignable(type, collectionType) && !isMap) { + if (!type.isInterface && !newPolicyExceptions + .contains(Immutable.Policy.Exception.FINAL_CLASSES_WITH_FINAL_FIELDS) + ) { + printError(parentChain, symbol, nonInterfaceClassFailure()) + return true + } else { + return visitClass( + parentChain, seenTypesByPolicy, symbol, + processingEnv.typeUtils.asElement(type) as Symbol.TypeSymbol, + newPolicyExceptions, + ) + } + } + + var anyError = false + + type.typeArguments.forEachIndexed { index, typeArg -> + if (isIgnored(typeArg.asElement())) return@forEachIndexed + + val argError = + visitType(parentChain, seenTypesByPolicy, symbol, typeArg, newPolicyExceptions) { + MessageUtils.nonInterfaceReturnFailure( + prefix = when { + !isMap -> "" + index == 0 -> "Key " + typeArg.asElement().simpleName + else -> "Value " + typeArg.asElement().simpleName + }, index = index + ) + } + anyError = anyError || argError + } + + return anyError + } + + private fun printError( + parentChain: List<String>, + element: Element, + message: String, + ) = processingEnv.messager.printMessage( + Diagnostic.Kind.ERROR, + parentChain.plus(element.simpleName).joinToString() + "\n\t " + message, + element, + ) + + private fun ProcessingEnvironment.erasedType(typeName: String) = + elementUtils.getTypeElement(typeName)?.asType()?.let(typeUtils::erasure) + + private fun isIgnored(type: Type) = + (type.getAnnotation(Immutable.Ignore::class.java) != null) + || (ignoredSuperTypes.any { type.isAssignable(it) }) + || (ignoredExactTypes.any { type.isSameType(it) }) + + private fun isIgnored(symbol: Symbol) = when { + // Anything annotated as @Ignore is always ignored + symbol.getAnnotation(Immutable.Ignore::class.java) != null -> true + // Then ignore exact types, regardless of what kind they are + ignoredExactTypes.any { symbol.type.isSameType(it) } -> true + // Then only allow methods through, since other types (fields) are usually a failure + symbol.getKind() != ElementKind.METHOD -> false + // Finally, check for any ignored super types + else -> ignoredSuperTypes.any { symbol.type.isAssignable(it) } + } + + private fun TypeMirror.isAssignable(type: TypeMirror) = try { + processingEnv.typeUtils.isAssignable(this, type) + } catch (ignored: Exception) { + false + } + + private fun TypeMirror.isSameType(type: TypeMirror) = try { + processingEnv.typeUtils.isSameType(this, type) + } catch (ignored: Exception) { + false + } +} diff --git a/tools/processors/immutability/src/android/processor/immutability/Immutable.java b/tools/processors/immutability/src/android/processor/immutability/Immutable.java new file mode 100644 index 000000000000..ba822ba562ee --- /dev/null +++ b/tools/processors/immutability/src/android/processor/immutability/Immutable.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 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. + */ + +package android.processor.immutability; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Collection; +import java.util.Map; + +/** + * Marks a class as immutable. When used with the Immutability processor, verifies at compile that + * the class is truly immutable. Immutable is defined as: + * <ul> + * <li>Only exposes methods and/or static final constants</li> + * <li>Every exposed type is an @Immutable interface or otherwise immutable class</li> + * <ul> + * <li>Implicitly immutable types like {@link String} are ignored</li> + * <li>{@link Collection} and {@link Map} and their subclasses where immutability is + * enforced at runtime are ignored</li> + * </ul> + * <li>Every method must return a type (no void methods allowed)</li> + * <li>All inner classes must be @Immutable interfaces</li> + * </ul> + */ +public @interface Immutable { + + /** + * Marks a specific class, field, or method as ignored for immutability validation. + */ + @Retention(RetentionPolicy.CLASS) // Not SOURCE as that isn't retained for some reason + @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) + @interface Ignore { + String reason() default ""; + } + + /** + * Marks an element and its reachable children with a specific policy. + */ + @Retention(RetentionPolicy.CLASS) + @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) + @interface Policy { + Exception[] exceptions() default {}; + + enum Exception { + /** + * Allow final classes with only final fields. By default these are not allowed because + * direct field access disallows hard removal of APIs (by having their getters return + * mocks/stubs) and also prevents field compaction, which can occur with booleans + * stuffed into a number as flags. + * + * This exception is allowed though because several framework classes are built around + * the final field access model and it would be unnecessarily difficult to migrate or + * wrap each type. + */ + FINAL_CLASSES_WITH_FINAL_FIELDS, + } + } +} diff --git a/tools/processors/immutability/src/android/processor/immutability/MessageUtils.kt b/tools/processors/immutability/src/android/processor/immutability/MessageUtils.kt new file mode 100644 index 000000000000..7fa2fb56deb1 --- /dev/null +++ b/tools/processors/immutability/src/android/processor/immutability/MessageUtils.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 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. + */ + +package android.processor.immutability + +object MessageUtils { + + fun classNotImmutableFailure(className: String) = "$className should be marked @Immutable" + + fun classNotFinalFailure(className: String) = "$className should be marked final" + + fun memberNotMethodFailure() = "Member must be a method" + + fun nonInterfaceClassFailure() = "Class was not an interface" + + fun nonInterfaceReturnFailure(prefix: String, index: Int = -1) = + if (prefix.isEmpty()) { + "Type at index $index was not an interface" + } else { + "$prefix was not an interface" + } + + fun genericTypeKindFailure(typeName: CharSequence) = "TypeKind $typeName unsupported" + + fun arrayFailure() = "Array types are not supported as they can be mutated by callers" + + fun nonInterfaceReturnFailure() = "Must return an interface" + + fun voidReturnFailure() = "Cannot return void" + + fun staticNonFinalFailure() = "Static member must be final" +}
\ No newline at end of file diff --git a/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt b/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt new file mode 100644 index 000000000000..43caa456a093 --- /dev/null +++ b/tools/processors/immutability/test/android/processor/ImmutabilityProcessorTest.kt @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2022 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. + */ + +package android.processor + +import android.processor.immutability.IMMUTABLE_ANNOTATION_NAME +import android.processor.immutability.ImmutabilityProcessor +import android.processor.immutability.MessageUtils +import com.google.common.truth.Expect +import com.google.testing.compile.CompilationSubject.assertThat +import com.google.testing.compile.Compiler.javac +import com.google.testing.compile.JavaFileObjects +import org.junit.Rule +import org.junit.Test +import java.util.* +import javax.tools.JavaFileObject + +class ImmutabilityProcessorTest { + + companion object { + private const val PACKAGE_PREFIX = "android.processor.immutability" + private const val DATA_CLASS_NAME = "DataClass" + private val ANNOTATION = JavaFileObjects.forResource("Immutable.java") + + private val FINAL_CLASSES = listOf( + JavaFileObjects.forSourceString( + "$PACKAGE_PREFIX.NonFinalClassFinalFields", + /* language=JAVA */ """ + package $PACKAGE_PREFIX; + + public class NonFinalClassFinalFields { + private final String finalField; + public NonFinalClassFinalFields(String value) { + this.finalField = value; + } + } + """.trimIndent() + ), + JavaFileObjects.forSourceString( + "$PACKAGE_PREFIX.NonFinalClassNonFinalFields", + /* language=JAVA */ """ + package $PACKAGE_PREFIX; + + public class NonFinalClassNonFinalFields { + private String nonFinalField; + } + """.trimIndent() + ), + JavaFileObjects.forSourceString( + "$PACKAGE_PREFIX.FinalClassFinalFields", + /* language=JAVA */ """ + package $PACKAGE_PREFIX; + + public final class FinalClassFinalFields { + private final String finalField; + public FinalClassFinalFields(String value) { + this.finalField = value; + } + } + """.trimIndent() + ), + JavaFileObjects.forSourceString( + "$PACKAGE_PREFIX.FinalClassNonFinalFields", + /* language=JAVA */ """ + package $PACKAGE_PREFIX; + + public final class FinalClassNonFinalFields { + private String nonFinalField; + } + """.trimIndent() + ) + ) + } + + @get:Rule + val expect = Expect.create() + + @Test + fun validInterface() = test( + source = JavaFileObjects.forSourceString( + "$PACKAGE_PREFIX.$DATA_CLASS_NAME", + /* language=JAVA */ """ + package $PACKAGE_PREFIX; + + import $IMMUTABLE_ANNOTATION_NAME; + import java.util.ArrayList; + import java.util.Collections; + import java.util.List; + + @Immutable + public interface $DATA_CLASS_NAME { + InnerInterface DEFAULT = new InnerInterface() { + @Override + public String getValue() { + return ""; + } + @Override + public List<String> getArray() { + return Collections.emptyList(); + } + }; + + String getValue(); + ArrayList<String> getArray(); + InnerInterface getInnerInterface(); + + @Immutable + interface InnerInterface { + String getValue(); + List<String> getArray(); + } + } + """.trimIndent() + ), errors = emptyList() + ) + + @Test + fun abstractClass() = test( + JavaFileObjects.forSourceString( + "$PACKAGE_PREFIX.$DATA_CLASS_NAME", + /* language=JAVA */ """ + package $PACKAGE_PREFIX; + + import $IMMUTABLE_ANNOTATION_NAME; + import java.util.Map; + + @Immutable + public abstract class $DATA_CLASS_NAME { + public static final String IMMUTABLE = ""; + public static final InnerClass NOT_IMMUTABLE = null; + public static InnerClass NOT_FINAL = null; + + // Field finality doesn't matter, methods are always enforced so that future + // field compaction or deprecation is possible + private final String fieldFinal = ""; + private String fieldNonFinal; + public abstract void sideEffect(); + public abstract String[] getArray(); + public abstract InnerClass getInnerClassOne(); + public abstract InnerClass getInnerClassTwo(); + @Immutable.Ignore + public abstract InnerClass getIgnored(); + public abstract InnerInterface getInnerInterface(); + + public abstract Map<String, String> getValidMap(); + public abstract Map<InnerClass, InnerClass> getInvalidMap(); + + public static final class InnerClass { + public String innerField; + public String[] getArray() { return null; } + } + + public interface InnerInterface { + String[] getArray(); + InnerClass getInnerClass(); + } + } + """.trimIndent() + ), errors = listOf( + nonInterfaceClassFailure(line = 7), + nonInterfaceReturnFailure(line = 9), + staticNonFinalFailure(line = 10), + nonInterfaceReturnFailure(line = 10), + memberNotMethodFailure(line = 14), + memberNotMethodFailure(line = 15), + voidReturnFailure(line = 16), + arrayFailure(line = 17), + nonInterfaceReturnFailure(line = 18), + nonInterfaceReturnFailure(line = 19), + classNotImmutableFailure(line = 22, className = "InnerInterface"), + nonInterfaceReturnFailure(line = 25, prefix = "Key InnerClass"), + nonInterfaceReturnFailure(line = 25, prefix = "Value InnerClass"), + classNotImmutableFailure(line = 27, className = "InnerClass"), + nonInterfaceClassFailure(line = 27), + memberNotMethodFailure(line = 28), + arrayFailure(line = 29), + arrayFailure(line = 33), + nonInterfaceReturnFailure(line = 34), + ) + ) + + @Test + fun finalClasses() = test( + JavaFileObjects.forSourceString( + "$PACKAGE_PREFIX.$DATA_CLASS_NAME", + /* language=JAVA */ """ + package $PACKAGE_PREFIX; + + import java.util.List; + + @Immutable + public interface $DATA_CLASS_NAME { + NonFinalClassFinalFields getNonFinalFinal(); + List<NonFinalClassNonFinalFields> getNonFinalNonFinal(); + FinalClassFinalFields getFinalFinal(); + List<FinalClassNonFinalFields> getFinalNonFinal(); + + @Immutable.Policy(exceptions = {Immutable.Policy.Exception.FINAL_CLASSES_WITH_FINAL_FIELDS}) + NonFinalClassFinalFields getPolicyNonFinalFinal(); + + @Immutable.Policy(exceptions = {Immutable.Policy.Exception.FINAL_CLASSES_WITH_FINAL_FIELDS}) + List<NonFinalClassNonFinalFields> getPolicyNonFinalNonFinal(); + + @Immutable.Policy(exceptions = {Immutable.Policy.Exception.FINAL_CLASSES_WITH_FINAL_FIELDS}) + FinalClassFinalFields getPolicyFinalFinal(); + + @Immutable.Policy(exceptions = {Immutable.Policy.Exception.FINAL_CLASSES_WITH_FINAL_FIELDS}) + List<FinalClassNonFinalFields> getPolicyFinalNonFinal(); + } + """.trimIndent() + ), errors = listOf( + nonInterfaceReturnFailure(line = 7), + nonInterfaceReturnFailure(line = 8, index = 0), + nonInterfaceReturnFailure(line = 9), + nonInterfaceReturnFailure(line = 10, index = 0), + classNotFinalFailure(line = 13, "NonFinalClassFinalFields"), + ), otherErrors = mapOf( + FINAL_CLASSES[1] to listOf( + memberNotMethodFailure(line = 4), + ), + FINAL_CLASSES[3] to listOf( + memberNotMethodFailure(line = 4), + ), + ) + ) + + @Test + fun superClass() { + val superClass = JavaFileObjects.forSourceString( + "$PACKAGE_PREFIX.SuperClass", + /* language=JAVA */ """ + package $PACKAGE_PREFIX; + + import java.util.List; + + public interface SuperClass { + InnerClass getInnerClassOne(); + + final class InnerClass { + public String innerField; + } + } + """.trimIndent() + ) + + val dataClass = JavaFileObjects.forSourceString( + "$PACKAGE_PREFIX.$DATA_CLASS_NAME", + /* language=JAVA */ """ + package $PACKAGE_PREFIX; + + import java.util.List; + + @Immutable + public interface $DATA_CLASS_NAME extends SuperClass { + String[] getArray(); + } + """.trimIndent() + ) + + test( + sources = arrayOf(superClass, dataClass), + fileToErrors = mapOf( + superClass to listOf( + classNotImmutableFailure(line = 5, className = "SuperClass"), + nonInterfaceReturnFailure(line = 6), + nonInterfaceClassFailure(8), + classNotImmutableFailure(line = 8, className = "InnerClass"), + memberNotMethodFailure(line = 9), + ), + dataClass to listOf( + arrayFailure(line = 7), + ) + ) + ) + } + + @Test + fun ignoredClass() = test( + JavaFileObjects.forSourceString( + "$PACKAGE_PREFIX.$DATA_CLASS_NAME", + /* language=JAVA */ """ + package $PACKAGE_PREFIX; + + import java.util.List; + import java.util.Map; + + @Immutable + public interface $DATA_CLASS_NAME { + IgnoredClass getInnerClassOne(); + NotIgnoredClass getInnerClassTwo(); + Map<String, IgnoredClass> getInnerClassThree(); + Map<String, NotIgnoredClass> getInnerClassFour(); + + @Immutable.Ignore + final class IgnoredClass { + public String innerField; + } + + final class NotIgnoredClass { + public String innerField; + } + } + """.trimIndent() + ), errors = listOf( + nonInterfaceReturnFailure(line = 9), + nonInterfaceReturnFailure(line = 11, prefix = "Value NotIgnoredClass"), + classNotImmutableFailure(line = 18, className = "NotIgnoredClass"), + nonInterfaceClassFailure(line = 18), + memberNotMethodFailure(line = 19), + ) + ) + + private fun test( + source: JavaFileObject, + errors: List<CompilationError>, + otherErrors: Map<JavaFileObject, List<CompilationError>> = emptyMap(), + ) = test( + sources = arrayOf(source), + fileToErrors = otherErrors + (source to errors), + ) + + private fun test( + vararg sources: JavaFileObject, + fileToErrors: Map<JavaFileObject, List<CompilationError>> = emptyMap(), + ) { + val compilation = javac() + .withProcessors(ImmutabilityProcessor()) + .compile(FINAL_CLASSES + ANNOTATION + sources) + + fileToErrors.forEach { (file, errors) -> + errors.forEach { error -> + try { + assertThat(compilation) + .hadErrorContaining(error.message) + .inFile(file) + .onLine(error.line) + } catch (e: AssertionError) { + // Wrap the exception so that the line number is logged + val wrapped = AssertionError("Expected $error, ${e.message}").apply { + stackTrace = e.stackTrace + } + + // Wrap again with Expect so that all errors are reported. This is very bad code + // but can only be fixed by updating compile-testing with a better Truth Subject + // implementation. + expect.that(wrapped).isNull() + } + } + } + + expect.that(compilation.errors().size).isEqualTo(fileToErrors.values.sumOf { it.size }) + + if (expect.hasFailures()) { + expect.withMessage( + compilation.errors() + .sortedBy { it.lineNumber } + .joinToString(separator = "\n") { + "${it.lineNumber}: ${it.getMessage(Locale.ENGLISH)?.trim()}" + } + ).fail() + } + } + + private fun classNotImmutableFailure(line: Long, className: String) = + CompilationError(line = line, message = MessageUtils.classNotImmutableFailure(className)) + + private fun nonInterfaceClassFailure(line: Long) = + CompilationError(line = line, message = MessageUtils.nonInterfaceClassFailure()) + + private fun nonInterfaceReturnFailure(line: Long) = + CompilationError(line = line, message = MessageUtils.nonInterfaceReturnFailure()) + + private fun nonInterfaceReturnFailure(line: Long, prefix: String = "", index: Int = -1) = + CompilationError( + line = line, + message = MessageUtils.nonInterfaceReturnFailure(prefix = prefix, index = index) + ) + + private fun memberNotMethodFailure(line: Long) = + CompilationError(line = line, message = MessageUtils.memberNotMethodFailure()) + + private fun voidReturnFailure(line: Long) = + CompilationError(line = line, message = MessageUtils.voidReturnFailure()) + + private fun staticNonFinalFailure(line: Long) = + CompilationError(line = line, message = MessageUtils.staticNonFinalFailure()) + + private fun arrayFailure(line: Long) = + CompilationError(line = line, message = MessageUtils.arrayFailure()) + + private fun classNotFinalFailure(line: Long, className: String) = + CompilationError(line = line, message = MessageUtils.classNotFinalFailure(className)) + + data class CompilationError( + val line: Long, + val message: String, + ) +} diff --git a/tools/processors/intdef_mappings/Android.bp b/tools/processors/intdef_mappings/Android.bp index 82a5dac21160..7059c52ddc76 100644 --- a/tools/processors/intdef_mappings/Android.bp +++ b/tools/processors/intdef_mappings/Android.bp @@ -7,27 +7,33 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -java_plugin { - name: "intdef-annotation-processor", - - processor_class: "android.processor.IntDefProcessor", +java_library_host { + name: "libintdef-annotation-processor", srcs: [ ":framework-annotations", "src/**/*.java", - "src/**/*.kt" + "src/**/*.kt", ], use_tools_jar: true, } +java_plugin { + name: "intdef-annotation-processor", + + processor_class: "android.processor.IntDefProcessor", + + static_libs: ["libintdef-annotation-processor"], +} + java_test_host { name: "intdef-annotation-processor-test", srcs: [ "test/**/*.java", - "test/**/*.kt" - ], + "test/**/*.kt", + ], java_resource_dirs: ["test/resources"], static_libs: [ @@ -35,7 +41,7 @@ java_test_host { "truth-prebuilt", "junit", "guava", - "intdef-annotation-processor" + "libintdef-annotation-processor", ], test_suites: ["general-tests"], diff --git a/tools/processors/staledataclass/Android.bp b/tools/processors/staledataclass/Android.bp index 1e5097662a0a..2169c49a91a5 100644 --- a/tools/processors/staledataclass/Android.bp +++ b/tools/processors/staledataclass/Android.bp @@ -22,17 +22,13 @@ java_plugin { static_libs: [ "codegen-version-info", ], - // The --add-modules/exports flags below don't work for kotlinc yet, so pin this module to Java language level 8 (see b/139342589): - java_version: "1.8", - openjdk9: { - javacflags: [ - "--add-modules=jdk.compiler", - "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", - ], - }, + javacflags: [ + "--add-modules=jdk.compiler", + "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], use_tools_jar: true, } diff --git a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt index 2e60f64b21e8..1cef5b0c8dfb 100644 --- a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt +++ b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE") package android.processor.staledataclass @@ -97,7 +98,7 @@ class StaleDataclassProcessor: AbstractProcessor() { private fun elemToString(elem: Element): String { return buildString { - append(elem.modifiers.joinToString(" ") { it.name.toLowerCase() }) + append(elem.modifiers.joinToString(" ") { it.name.lowercase() }) append(" ") append(elem.annotationMirrors.joinToString(" ", transform = { annotationToString(it) })) append(" ") diff --git a/tools/processors/view_inspector/Android.bp b/tools/processors/view_inspector/Android.bp index ea9974f06a64..877a1d2b5fb3 100644 --- a/tools/processors/view_inspector/Android.bp +++ b/tools/processors/view_inspector/Android.bp @@ -7,10 +7,8 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -java_plugin { - name: "view-inspector-annotation-processor", - - processor_class: "android.processor.view.inspector.PlatformInspectableProcessor", +java_library_host { + name: "libview-inspector-annotation-processor", srcs: ["src/java/**/*.java"], java_resource_dirs: ["src/resources"], @@ -23,6 +21,16 @@ java_plugin { use_tools_jar: true, } +java_plugin { + name: "view-inspector-annotation-processor", + + processor_class: "android.processor.view.inspector.PlatformInspectableProcessor", + + static_libs: [ + "libview-inspector-annotation-processor", + ], +} + java_test_host { name: "view-inspector-annotation-processor-test", @@ -32,7 +40,7 @@ java_test_host { static_libs: [ "junit", "guava", - "view-inspector-annotation-processor" + "libview-inspector-annotation-processor", ], test_suites: ["general-tests"], diff --git a/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt index a52c8042582b..451e514b8b33 100644 --- a/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt +++ b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt @@ -29,7 +29,7 @@ object CodeUtils { */ fun hash(position: String, messageString: String, logLevel: LogLevel, logGroup: LogGroup): Int { return (position + messageString + logLevel.name + logGroup.name) - .map { c -> c.toInt() }.reduce { h, c -> h * 31 + c } + .map { c -> c.code }.reduce { h, c -> h * 31 + c } } fun checkWildcardStaticImported(code: CompilationUnit, className: String, fileName: String) { diff --git a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt index 67a31da87081..512d90c725fe 100644 --- a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt @@ -84,8 +84,8 @@ class LogParserTest { @Test fun parse_formatting() { - config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" + - " %x %e %g %s %f", "ERROR", "WindowManager") + config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %%" + + " %x %s %f", "ERROR", "WindowManager") val logBuilder = ProtoLogFileProto.newBuilder() val logMessageBuilder = ProtoLogMessage.newBuilder() @@ -93,21 +93,21 @@ class LogParserTest { .setMessageHash(123) .setElapsedRealtimeNanos(0) .addBooleanParams(true) - .addAllSint64Params(listOf(1000, 20000, 300000)) - .addAllDoubleParams(listOf(0.1, 0.00001, 1000.1)) + .addAllSint64Params(listOf(1000, 20000)) + .addDoubleParams(1000.1) .addStrParams("test") logBuilder.addLog(logMessageBuilder.build()) parser.parse(buildProtoInput(logBuilder), getConfigDummyStream(), printStream) assertEquals("${testDate(0)} ERROR WindowManager: Test completed successfully: " + - "true 1000 % 47040 493e0 1.000000e-01 1.00000e-05 test 1000.100000\n", + "true 1000 % 4e20 test 1000.100000\n", outStream.toString()) } @Test fun parse_invalidParamsTooMany() { - config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o", + config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %%", "ERROR", "WindowManager") val logBuilder = ProtoLogFileProto.newBuilder() @@ -129,8 +129,8 @@ class LogParserTest { @Test fun parse_invalidParamsNotEnough() { - config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" + - " %x %e %g %s %f", "ERROR", "WindowManager") + config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %%" + + " %x %s %f", "ERROR", "WindowManager") val logBuilder = ProtoLogFileProto.newBuilder() val logMessageBuilder = ProtoLogMessage.newBuilder() diff --git a/tools/sdkparcelables/Android.bp b/tools/sdkparcelables/Android.bp index 6ebacd8a0b14..6503a1f3a5f8 100644 --- a/tools/sdkparcelables/Android.bp +++ b/tools/sdkparcelables/Android.bp @@ -14,7 +14,7 @@ java_binary_host { "src/**/*.kt", ], static_libs: [ - "asm-9.2", + "ow2-asm", ], } diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt index 0fb062f280e3..0b619488c49c 100644 --- a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt @@ -39,7 +39,7 @@ fun main(args: Array<String>) { kotlin.system.exitProcess(2) } - val ancestorCollector = AncestorCollector(Opcodes.ASM7, null) + val ancestorCollector = AncestorCollector(Opcodes.ASM9, null) for (entry in zipFile.entries()) { if (entry.name.endsWith(".class")) { diff --git a/tools/stringslint/stringslint.py b/tools/stringslint/stringslint.py deleted file mode 100644 index 15088fc81e88..000000000000 --- a/tools/stringslint/stringslint.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/env python3 -#-*- coding: utf-8 -*- - -# 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. - -""" -Enforces common Android string best-practices. It ignores lint messages from -a previous strings file, if provided. - -Usage: stringslint.py strings.xml -Usage: stringslint.py strings.xml old_strings.xml - -In general: -* Errors signal issues that must be fixed before submitting, and are only - used when there are no false-positives. -* Warnings signal issues that might need to be fixed, but need manual - inspection due to risk of false-positives. -* Info signal issues that should be fixed to match best-practices, such - as providing comments to aid translation. -""" - -import re, sys, codecs -import lxml.etree as ET - -BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) - -def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False): - # manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes - codes = [] - if reset: codes.append("0") - else: - if not fg is None: codes.append("3%d" % (fg)) - if not bg is None: - if not bright: codes.append("4%d" % (bg)) - else: codes.append("10%d" % (bg)) - if bold: codes.append("1") - elif dim: codes.append("2") - else: codes.append("22") - return "\033[%sm" % (";".join(codes)) - -warnings = None - -def warn(tag, msg, actual, expected, color=YELLOW): - global warnings - key = "%s:%d" % (tag.attrib["name"], hash(msg)) - value = "%sLine %d: '%s':%s %s" % (format(fg=color, bold=True), - tag.sourceline, - tag.attrib["name"], - format(reset=True), - msg) - if not actual is None: value += "\n\tActual: %s%s%s" % (format(dim=True), - actual, - format(reset=True)) - if not expected is None: value += "\n\tExample: %s%s%s" % (format(dim=True), - expected, - format(reset=True)) - warnings[key] = value - - -def error(tag, msg, actual, expected): - warn(tag, msg, actual, expected, RED) - -def info(tag, msg, actual, expected): - warn(tag, msg, actual, expected, CYAN) - -# Escaping logic borrowed from https://stackoverflow.com/a/24519338 -ESCAPE_SEQUENCE_RE = re.compile(r''' - ( \\U........ # 8-digit hex escapes - | \\u.... # 4-digit hex escapes - | \\x.. # 2-digit hex escapes - | \\[0-7]{1,3} # Octal escapes - | \\N\{[^}]+\} # Unicode characters by name - | \\[\\'"abfnrtv] # Single-character escapes - )''', re.UNICODE | re.VERBOSE) - -def decode_escapes(s): - def decode_match(match): - return codecs.decode(match.group(0), 'unicode-escape') - - s = re.sub(r"\n\s*", " ", s) - s = ESCAPE_SEQUENCE_RE.sub(decode_match, s) - s = re.sub(r"%(\d+\$)?[a-z]", "____", s) - s = re.sub(r"\^\d+", "____", s) - s = re.sub(r"<br/?>", "\n", s) - s = re.sub(r"</?[a-z]+>", "", s) - return s - -def sample_iter(tag): - if not isinstance(tag, ET._Comment) and re.match("{.*xliff.*}g", tag.tag) and "example" in tag.attrib: - yield tag.attrib["example"] - elif tag.text: - yield decode_escapes(tag.text) - for e in tag: - for v in sample_iter(e): - yield v - if e.tail: - yield decode_escapes(e.tail) - -def lint(path): - global warnings - warnings = {} - - with open(path) as f: - raw = f.read() - if len(raw.strip()) == 0: - return warnings - tree = ET.fromstring(bytes(raw, encoding='utf-8')) - root = tree #tree.getroot() - - last_comment = None - for child in root: - # TODO: handle plurals - if isinstance(child, ET._Comment): - last_comment = child - elif child.tag == "string": - # We always consume comment - comment = last_comment - last_comment = None - - # Prepare string for analysis - text = "".join(child.itertext()) - sample = "".join(sample_iter(child)).strip().strip("'\"") - - # Validate comment - if comment is None: - info(child, "Missing string comment to aid translation", - None, None) - continue - if "do not translate" in comment.text.lower(): - continue - if "translatable" in child.attrib and child.attrib["translatable"].lower() == "false": - continue - - misspelled_attributes = [ - ("translateable", "translatable"), - ] - for misspelling, expected in misspelled_attributes: - if misspelling in child.attrib: - error(child, "Misspelled <string> attribute.", misspelling, expected) - - limit = re.search("CHAR[ _-]LIMIT=(\d+|NONE|none)", comment.text) - if limit is None: - info(child, "Missing CHAR LIMIT to aid translation", - repr(comment), "<!-- Description of string [CHAR LIMIT=32] -->") - elif re.match("\d+", limit.group(1)): - limit = int(limit.group(1)) - if len(sample) > limit: - warn(child, "Expanded string length is larger than CHAR LIMIT", - sample, None) - - # Look for common mistakes/substitutions - if "'" in text: - error(child, "Turned quotation mark glyphs are more polished", - text, "This doesn\u2019t need to \u2018happen\u2019 today") - if '"' in text and not text.startswith('"') and text.endswith('"'): - error(child, "Turned quotation mark glyphs are more polished", - text, "This needs to \u201chappen\u201d today") - if "..." in text: - error(child, "Ellipsis glyph is more polished", - text, "Loading\u2026") - if "wi-fi" in text.lower(): - error(child, "Non-breaking glyph is more polished", - text, "Wi\u2011Fi") - if "wifi" in text.lower(): - error(child, "Using non-standard spelling", - text, "Wi\u2011Fi") - if re.search("\d-\d", text): - warn(child, "Ranges should use en dash glyph", - text, "You will find this material in chapters 8\u201312") - if "--" in text: - warn(child, "Phrases should use em dash glyph", - text, "Upon discovering errors\u2014all 124 of them\u2014they recalled.") - if ". " in text: - warn(child, "Only use single space between sentences", - text, "First idea. Second idea.") - if re.match(r"^[A-Z\s]{5,}$", text): - warn(child, "Actions should use android:textAllCaps in layout; ignore if acronym", - text, "Refresh data") - if " phone " in text and "product" not in child.attrib: - warn(child, "Strings mentioning phones should have variants for tablets", - text, None) - - # When more than one substitution, require indexes - if len(re.findall("%[^%]", text)) > 1: - if len(re.findall("%[^\d]", text)) > 0: - error(child, "Substitutions must be indexed", - text, "Add %1$s to %2$s") - - # Require xliff substitutions - for gc in child.iter(): - badsub = False - if gc.tail and re.search("%[^%]", gc.tail): badsub = True - if re.match("{.*xliff.*}g", gc.tag): - if "id" not in gc.attrib: - error(child, "Substitutions must define id attribute", - None, "<xliff:g id=\"domain\" example=\"example.com\">%1$s</xliff:g>") - if "example" not in gc.attrib: - error(child, "Substitutions must define example attribute", - None, "<xliff:g id=\"domain\" example=\"example.com\">%1$s</xliff:g>") - else: - if gc.text and re.search("%[^%]", gc.text): badsub = True - if badsub: - error(child, "Substitutions must be inside xliff tags", - text, "<xliff:g id=\"domain\" example=\"example.com\">%1$s</xliff:g>") - - return warnings - -if len(sys.argv) > 2: - before = lint(sys.argv[2]) -else: - before = {} -after = lint(sys.argv[1]) - -for b in before: - if b in after: - del after[b] - -if len(after) > 0: - for a in sorted(after.keys()): - print(after[a]) - print() - sys.exit(1) diff --git a/tools/stringslint/stringslint_sha.sh b/tools/stringslint/stringslint_sha.sh index bd0569873197..009a1f284bf0 100755 --- a/tools/stringslint/stringslint_sha.sh +++ b/tools/stringslint/stringslint_sha.sh @@ -1,5 +1,3 @@ #!/bin/bash -LOCAL_DIR="$( dirname ${BASH_SOURCE} )" -git show --name-only --pretty=format: $1 | grep values/strings.xml | while read file; do - python3 $LOCAL_DIR/stringslint.py <(git show $1:$file) <(git show $1^:$file) -done + +# NOTE: this script has been deprecated and replaced by AyeAye checks directly in Gerrit diff --git a/tools/traceinjection/Android.bp b/tools/traceinjection/Android.bp index 39d1b1c2defd..d627fb99d882 100644 --- a/tools/traceinjection/Android.bp +++ b/tools/traceinjection/Android.bp @@ -12,11 +12,11 @@ java_binary_host { manifest: "manifest.txt", srcs: ["src/**/*.java"], static_libs: [ - "asm-9.2", - "asm-commons-9.2", - "asm-tree-9.2", - "asm-analysis-9.2", - "guava-21.0", + "ow2-asm", + "ow2-asm-commons", + "ow2-asm-tree", + "ow2-asm-analysis", + "guava", ], } diff --git a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java index 863f976b8aff..67c5561f348d 100644 --- a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java +++ b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java @@ -28,7 +28,7 @@ public class TraceInjectionClassVisitor extends ClassVisitor { private final TraceInjectionConfiguration mParams; public TraceInjectionClassVisitor(ClassVisitor classVisitor, TraceInjectionConfiguration params) { - super(Opcodes.ASM7, classVisitor); + super(Opcodes.ASM9, classVisitor); mParams = params; } diff --git a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java index c2bbddcb5668..91e987dc72ee 100644 --- a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java +++ b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java @@ -61,7 +61,7 @@ public class TraceInjectionMethodAdapter extends AdviceAdapter { public TraceInjectionMethodAdapter(MethodVisitor methodVisitor, int access, String name, String descriptor, TraceInjectionConfiguration params) { - super(Opcodes.ASM7, methodVisitor, access, name, descriptor); + super(Opcodes.ASM9, methodVisitor, access, name, descriptor); mParams = params; mIsConstructor = "<init>".equals(name); } @@ -157,7 +157,7 @@ public class TraceInjectionMethodAdapter extends AdviceAdapter { class TracingAnnotationVisitor extends AnnotationVisitor { TracingAnnotationVisitor(AnnotationVisitor annotationVisitor) { - super(Opcodes.ASM7, annotationVisitor); + super(Opcodes.ASM9, annotationVisitor); } @Override diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp index 25373f9e9e2f..554f64e73b74 100644 --- a/tools/validatekeymaps/Android.bp +++ b/tools/validatekeymaps/Android.bp @@ -15,7 +15,7 @@ package { cc_binary_host { name: "validatekeymaps", - + cpp_std: "c++20", srcs: ["Main.cpp"], cflags: [ diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp index 0d7d5f949a08..0fa13b81c4ea 100644 --- a/tools/validatekeymaps/Main.cpp +++ b/tools/validatekeymaps/Main.cpp @@ -167,8 +167,8 @@ static bool validateFile(const char* filename) { android::base::Result<std::unique_ptr<PropertyMap>> propertyMap = PropertyMap::load(String8(filename)); if (!propertyMap.ok()) { - error("Error %d parsing input device configuration file.\n\n", - propertyMap.error().code()); + error("Error parsing input device configuration file: %s.\n\n", + propertyMap.error().message().c_str()); return false; } break; diff --git a/tools/validatekeymaps/OWNERS b/tools/validatekeymaps/OWNERS index 0313a40f7270..4c20c4dc9d35 100644 --- a/tools/validatekeymaps/OWNERS +++ b/tools/validatekeymaps/OWNERS @@ -1,2 +1 @@ -michaelwr@google.com -svv@google.com +include /INPUT_OWNERS |