diff options
Diffstat (limited to 'tools')
146 files changed, 4748 insertions, 1594 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index 672731cc2939..899d26818548 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -319,12 +319,10 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta // The second subtag can either be a script or a region code. // If its size is 4, it's a script code, else it's a region code. - bool hasRegion = false; if (subtags[1].size() == 4) { setScript(subtags[1]); } else if (subtags[1].size() == 2 || subtags[1].size() == 3) { setRegion(subtags[1]); - hasRegion = true; } else { fprintf(stderr, "ERROR: Invalid BCP 47 tag in directory name %s\n", part.string()); return -1; diff --git a/tools/aapt/Android.bp b/tools/aapt/Android.bp index a19d183d617b..cc10db9e1523 100644 --- a/tools/aapt/Android.bp +++ b/tools/aapt/Android.bp @@ -50,7 +50,6 @@ cc_defaults { "libbase", "libz", ], - group_static_libs: true, cflags: [ "-Wall", @@ -62,16 +61,6 @@ cc_defaults { enabled: true, }, }, - - // This tool is prebuilt if we're doing an app-only build. - product_variables: { - pdk: { - enabled: false, - }, - unbundled_build: { - enabled: false, - }, - }, } // ========================================================== diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 812e2087f36b..fecc7b3cbf37 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -769,7 +769,7 @@ int doDump(Bundle* bundle) config.country[1] = 'S'; config.orientation = ResTable_config::ORIENTATION_PORT; config.density = ResTable_config::DENSITY_MEDIUM; - config.sdkVersion = 10000; // Very high. + config.sdkVersion = SDK_CUR_DEVELOPMENT; // Very high. config.screenWidthDp = 320; config.screenHeightDp = 480; config.smallestScreenWidthDp = 320; @@ -1306,16 +1306,30 @@ int doDump(Bundle* bundle) splitName.string()).string()); } + // For 'platformBuildVersionName', using both string and int type as a fallback + // since it may be the code name of Android or the API level. String8 platformBuildVersionName = AaptXml::getAttribute(tree, NULL, "platformBuildVersionName"); + int32_t platformBuildVersionNameInt = + AaptXml::getIntegerAttribute(tree, NULL, "platformBuildVersionName", 0, + NULL); if (platformBuildVersionName != "") { printf(" platformBuildVersionName='%s'", platformBuildVersionName.string()); + } else if (platformBuildVersionNameInt > 0) { + printf(" platformBuildVersionName='%d'", platformBuildVersionNameInt); } + // For 'platformBuildVersionCode', using both string and int type as a fallback + // since it may be the code name of Android or the API level. String8 platformBuildVersionCode = AaptXml::getAttribute(tree, NULL, "platformBuildVersionCode"); + int32_t platformBuildVersionCodeInt = + AaptXml::getIntegerAttribute(tree, NULL, "platformBuildVersionCode", 0, + NULL); if (platformBuildVersionCode != "") { printf(" platformBuildVersionCode='%s'", platformBuildVersionCode.string()); + } else if (platformBuildVersionCodeInt > 0) { + printf(" platformBuildVersionCode='%d'", platformBuildVersionCodeInt); } int32_t compileSdkVersion = AaptXml::getIntegerAttribute(tree, @@ -1490,7 +1504,7 @@ int doDump(Bundle* bundle) error.string()); goto bail; } - if (name == "Donut") targetSdk = 4; + if (name == "Donut") targetSdk = SDK_DONUT; printf("sdkVersion:'%s'\n", ResTable::normalizeForOutput(name.string()).string()); } else if (code != -1) { @@ -1512,7 +1526,12 @@ int doDump(Bundle* bundle) error.string()); goto bail; } - if (name == "Donut" && targetSdk < 4) targetSdk = 4; + if (name == "Donut" && targetSdk < SDK_DONUT) { + targetSdk = SDK_DONUT; + } else if (name != "" && targetSdk == 0) { + // Bump to current development version + targetSdk = SDK_CUR_DEVELOPMENT; + } printf("targetSdkVersion:'%s'\n", ResTable::normalizeForOutput(name.string()).string()); } else if (code != -1) { @@ -2122,7 +2141,7 @@ int doDump(Bundle* bundle) } // Pre-1.6 implicitly granted permission compatibility logic - if (targetSdk < 4) { + if (targetSdk < SDK_DONUT) { if (!hasWriteExternalStoragePermission) { printUsesPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE")); printUsesImpliedPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"), @@ -2149,7 +2168,7 @@ int doDump(Bundle* bundle) } // Pre-JellyBean call log permission compatibility. - if (targetSdk < 16) { + if (targetSdk < SDK_JELLY_BEAN) { if (!hasReadCallLogPermission && hasReadContactsPermission) { printUsesPermission(String8("android.permission.READ_CALL_LOG")); printUsesImpliedPermission(String8("android.permission.READ_CALL_LOG"), @@ -2291,21 +2310,23 @@ int doDump(Bundle* bundle) // the screen size support was introduced, so all default to // enabled. if (smallScreen > 0) { - smallScreen = targetSdk >= 4 ? -1 : 0; + smallScreen = targetSdk >= SDK_DONUT ? -1 : 0; } if (normalScreen > 0) { normalScreen = -1; } if (largeScreen > 0) { - largeScreen = targetSdk >= 4 ? -1 : 0; + largeScreen = targetSdk >= SDK_DONUT ? -1 : 0; } if (xlargeScreen > 0) { // Introduced in Gingerbread. - xlargeScreen = targetSdk >= 9 ? -1 : 0; + xlargeScreen = targetSdk >= SDK_GINGERBREAD ? -1 : 0; } if (anyDensity > 0) { - anyDensity = (targetSdk >= 4 || requiresSmallestWidthDp > 0 - || compatibleWidthLimitDp > 0) ? -1 : 0; + anyDensity = (targetSdk >= SDK_DONUT || requiresSmallestWidthDp > 0 || + compatibleWidthLimitDp > 0) + ? -1 + : 0; } printf("supports-screens:"); if (smallScreen != 0) { diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 257e96b6e51a..b9de11b0026b 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -2475,11 +2475,10 @@ void ResourceTable::reportError(void* accessorCookie, const char* fmt, ...) { if (accessorCookie != NULL && fmt != NULL) { AccessorCookie* ac = (AccessorCookie*)accessorCookie; - int retval=0; char buf[1024]; va_list ap; va_start(ap, fmt); - retval = vsnprintf(buf, sizeof(buf), fmt, ap); + vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); ac->sourcePos.error("Error: %s (at '%s' with value '%s').\n", buf, ac->attr.string(), ac->value.string()); diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h index daf91b032638..a146466402f6 100644 --- a/tools/aapt/SdkConstants.h +++ b/tools/aapt/SdkConstants.h @@ -48,6 +48,8 @@ enum { SDK_R = 30, SDK_S = 31, SDK_S_V2 = 32, + SDK_TIRAMISU = 33, + SDK_CUR_DEVELOPMENT = 10000, }; #endif // H_AAPT_SDK_CONSTANTS diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 12dc156f75be..bfb32854a374 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -72,7 +72,6 @@ cc_defaults { "libidmap2_policies", ], stl: "libc++_static", - group_static_libs: true, } // ========================================================== @@ -166,21 +165,12 @@ cc_library_host_static { ], proto: { export_proto_headers: true, + type: "full", }, defaults: ["aapt2_defaults"], } // ========================================================== -// Build the host shared library: aapt2_jni -// ========================================================== -cc_library_host_shared { - name: "libaapt2_jni", - srcs: toolSources + ["jni/aapt2_jni.cpp"], - static_libs: ["libaapt2"], - defaults: ["aapt2_defaults"], -} - -// ========================================================== // Build the host tests: aapt2_tests // ========================================================== cc_test_host { diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index b165c6bed220..7b94e718fd0e 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -15,6 +15,8 @@ $(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)) + aapt2_results := include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h index d3ca357b0305..cabbe7ea7446 100644 --- a/tools/aapt2/AppInfo.h +++ b/tools/aapt2/AppInfo.h @@ -17,11 +17,10 @@ #ifndef AAPT_APP_INFO_H #define AAPT_APP_INFO_H +#include <optional> #include <set> #include <string> -#include "util/Maybe.h" - namespace aapt { // Information relevant to building an app, parsed from the app's AndroidManifest.xml. @@ -30,19 +29,19 @@ struct AppInfo { std::string package; // The app's minimum SDK version, if it is defined. - Maybe<int> min_sdk_version; + std::optional<int> min_sdk_version; // The app's version code (the lower 32 bits of the long version code), if it is defined. - Maybe<uint32_t> version_code; + std::optional<uint32_t> version_code; // The app's version code major (the upper 32 bits of the long version code), if it is defined. - Maybe<uint32_t> version_code_major; + std::optional<uint32_t> version_code_major; // The app's revision code, if it is defined. - Maybe<uint32_t> revision_code; + std::optional<uint32_t> revision_code; // The app's split name, if it is a split. - Maybe<std::string> split_name; + std::optional<std::string> split_name; // The split names that this split depends on. std::set<std::string> split_name_dependencies; diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index ef3a62f4efcc..f47d66ea5e87 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -16,6 +16,9 @@ #include "Debug.h" +#include <androidfw/TypeWrappers.h> +#include <format/binary/ResChunkPullParser.h> + #include <algorithm> #include <map> #include <memory> @@ -23,17 +26,16 @@ #include <set> #include <vector> -#include "android-base/logging.h" -#include "android-base/stringprintf.h" - #include "ResourceTable.h" +#include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" +#include "android-base/logging.h" +#include "android-base/stringprintf.h" +#include "idmap2/Policies.h" #include "text/Printer.h" #include "util/Util.h" -#include "idmap2/Policies.h" - using ::aapt::text::Printer; using ::android::StringPiece; using ::android::base::StringPrintf; @@ -78,7 +80,7 @@ class ValueHeadlinePrinter : public ConstValueVisitor { printer_->Print(parent_name.package); printer_->Print(":"); } - printer_->Print(to_string(parent_name.type)); + printer_->Print(parent_name.type.to_string()); printer_->Print("/"); printer_->Print(parent_name.entry); if (parent_ref.id) { @@ -280,8 +282,7 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& printer->Indent(); for (const ResourceTableEntryView& entry : type.entries) { printer->Print("resource "); - printer->Print(ResourceId(package.id.value_or_default(0), type.id.value_or_default(0), - entry.id.value_or_default(0)) + printer->Print(ResourceId(package.id.value_or(0), type.id.value_or(0), entry.id.value_or(0)) .to_string()); printer->Print(" "); @@ -362,7 +363,7 @@ void Debug::PrintStyleGraph(ResourceTable* table, const ResourceName& target_sty continue; } - Maybe<ResourceTable::SearchResult> result = table->FindResource(style_name); + std::optional<ResourceTable::SearchResult> result = table->FindResource(style_name); if (result) { ResourceEntry* entry = result.value().entry; for (const auto& value : entry->values) { @@ -482,8 +483,7 @@ class XmlPrinter : public xml::ConstVisitor { if (attr.compiled_attribute) { printer_->Print("("); - printer_->Print( - attr.compiled_attribute.value().id.value_or_default(ResourceId(0)).to_string()); + printer_->Print(attr.compiled_attribute.value().id.value_or(ResourceId(0)).to_string()); printer_->Print(")"); } printer_->Print("="); @@ -586,4 +586,260 @@ void Debug::DumpOverlayable(const ResourceTable& table, text::Printer* printer) } } +namespace { + +using namespace android; + +class ChunkPrinter { + public: + ChunkPrinter(const void* data, size_t len, Printer* printer, IDiagnostics* diag) + : data_(data), data_len_(len), printer_(printer), diag_(diag) { + } + + void PrintChunkHeader(const ResChunk_header* chunk) { + switch (util::DeviceToHost16(chunk->type)) { + case RES_STRING_POOL_TYPE: + printer_->Print("[RES_STRING_POOL_TYPE]"); + break; + case RES_TABLE_LIBRARY_TYPE: + printer_->Print("[RES_TABLE_LIBRARY_TYPE]"); + break; + case RES_TABLE_TYPE: + printer_->Print("[ResTable_header]"); + break; + case RES_TABLE_PACKAGE_TYPE: + printer_->Print("[ResTable_package]"); + break; + case RES_TABLE_TYPE_TYPE: + printer_->Print("[ResTable_type]"); + break; + case RES_TABLE_TYPE_SPEC_TYPE: + printer_->Print("[RES_TABLE_TYPE_SPEC_TYPE]"); + break; + default: + break; + } + + printer_->Print(StringPrintf(" chunkSize: %u", util::DeviceToHost32(chunk->size))); + printer_->Print(StringPrintf(" headerSize: %u", util::DeviceToHost32(chunk->headerSize))); + } + + bool PrintTable(const ResTable_header* chunk) { + printer_->Print( + StringPrintf(" Package count: %u\n", util::DeviceToHost32(chunk->packageCount))); + + // Print the chunks contained within the table + printer_->Indent(); + bool success = PrintChunk( + ResChunkPullParser(GetChunkData(&chunk->header), GetChunkDataLen(&chunk->header))); + printer_->Undent(); + return success; + } + + 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))); + + if (type) { + auto item = + ResourceUtils::ParseBinaryResValue(*type, config, value_pool_, *value, &out_pool_); + printer_->Print(" ("); + item->PrettyPrint(printer_); + printer_->Print(")"); + } + + printer_->Print("\n"); + } + + bool PrintTableType(const ResTable_type* chunk) { + printer_->Print(StringPrintf(" id: 0x%02x", 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))); + + 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)); + + printer_->Indent(); + + TypeVariant tv(chunk); + for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) { + const ResTable_entry* entry = *it; + if (!entry) { + continue; + } + + printer_->Print((entry->flags & ResTable_entry::FLAG_COMPLEX) ? "[ResTable_map_entry]" + : "[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))); + + 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))); + + // 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++) { + 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))); + } + } 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); + } + + printer_->Undent(); + } + + printer_->Undent(); + return true; + } + + void PrintStringPool(const ResStringPool_header* chunk) { + // Initialize the string pools + + ResStringPool* pool; + if (value_pool_.getError() == NO_INIT) { + pool = &value_pool_; + } else if (type_pool_.getError() == NO_INIT) { + pool = &type_pool_; + } else if (key_pool_.getError() == NO_INIT) { + pool = &key_pool_; + } else { + return; + } + + pool->setTo(chunk, + 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())); + } + } + + bool PrintPackage(const ResTable_package* chunk) { + printer_->Print(StringPrintf(" id: 0x%02x", 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]); + } + + 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))); + + // Print the chunks contained within the table + printer_->Indent(); + bool success = PrintChunk( + ResChunkPullParser(GetChunkData(&chunk->header), GetChunkDataLen(&chunk->header))); + printer_->Undent(); + return success; + } + + bool PrintChunk(ResChunkPullParser&& parser) { + while (ResChunkPullParser::IsGoodEvent(parser.Next())) { + auto chunk = parser.chunk(); + PrintChunkHeader(chunk); + + switch (util::DeviceToHost16(chunk->type)) { + case RES_STRING_POOL_TYPE: + PrintStringPool(reinterpret_cast<const ResStringPool_header*>(chunk)); + break; + + case RES_TABLE_TYPE: + PrintTable(reinterpret_cast<const ResTable_header*>(chunk)); + break; + + case RES_TABLE_PACKAGE_TYPE: + type_pool_.uninit(); + key_pool_.uninit(); + PrintPackage(reinterpret_cast<const ResTable_package*>(chunk)); + break; + + case RES_TABLE_TYPE_TYPE: + PrintTableType(reinterpret_cast<const ResTable_type*>(chunk)); + break; + + default: + printer_->Print("\n"); + break; + } + } + + if (parser.event() == ResChunkPullParser::Event::kBadDocument) { + diag_->Error(DiagMessage(source_) << "corrupt resource table: " << parser.error()); + return false; + } + + return true; + } + + void Print() { + PrintChunk(ResChunkPullParser(data_, data_len_)); + printer_->Print("[End]\n"); + } + + private: + const Source source_; + const void* data_; + const size_t data_len_; + Printer* printer_; + IDiagnostics* diag_; + + // The standard value string pool for resource values. + ResStringPool value_pool_; + + // The string pool that holds the names of the types defined + // in this table. + ResStringPool type_pool_; + + // The string pool that holds the names of the entries defined + // in this table. + ResStringPool key_pool_; + + StringPool out_pool_; +}; + +} // namespace + +void Debug::DumpChunks(const void* data, size_t len, Printer* printer, IDiagnostics* diag) { + ChunkPrinter chunk_printer(data, len, printer, diag); + chunk_printer.Print(); +} + } // namespace aapt diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h index 9443d606d7e5..4da92044cf2a 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -40,6 +40,7 @@ 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); }; } // namespace aapt diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 8a43bb4ede35..b249c6c128e1 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -147,7 +147,7 @@ class DaemonCommand : public Command { private: io::FileOutputStream* out_; IDiagnostics* diagnostics_; - Maybe<std::string> trace_folder_; + std::optional<std::string> trace_folder_; }; } // namespace aapt diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h index f1aad29a5c58..0b4905253d20 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -21,7 +21,6 @@ #include <string> #include "Resource.h" -#include "util/Maybe.h" namespace aapt { @@ -44,7 +43,7 @@ class NameMangler { public: explicit NameMangler(NameManglerPolicy policy) : policy_(policy) {} - Maybe<ResourceName> MangleName(const ResourceName& name) { + std::optional<ResourceName> MangleName(const ResourceName& name) { if (policy_.target_package_name == name.package || policy_.packages_to_mangle.count(name.package) == 0) { return {}; diff --git a/tools/aapt2/OWNERS b/tools/aapt2/OWNERS index 69dfcc98340d..4f655e54a7c2 100644 --- a/tools/aapt2/OWNERS +++ b/tools/aapt2/OWNERS @@ -1,4 +1,4 @@ set noparent toddke@google.com -rtmitchell@google.com +zyy@google.com patb@google.com
\ No newline at end of file diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index 6364ccdd09e5..0bb330e26e6f 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -134,6 +134,24 @@ static const std::map<StringPiece, ResourceType> sResourceTypeMap{ {"xml", ResourceType::kXml}, }; +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(), ':'); + const ResourceType* parsedType; + if (colon != s.end() && colon != std::prev(s.end())) { + parsedType = ParseResourceType(s.substr(s.begin(), colon)); + } else { + parsedType = ParseResourceType(s); + } + if (parsedType == nullptr) { + return std::nullopt; + } + return ResourceNamedTypeRef(s, *parsedType); +} + const ResourceType* ParseResourceType(const StringPiece& str) { auto iter = sResourceTypeMap.find(str); if (iter == std::end(sResourceTypeMap)) { diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 307c21d9dc96..b41d8514230b 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -19,22 +19,21 @@ #include <iomanip> #include <limits> +#include <optional> #include <sstream> #include <string> #include <tuple> #include <vector> +#include "Source.h" #include "androidfw/ConfigDescription.h" #include "androidfw/StringPiece.h" #include "utils/JenkinsHash.h" -#include "Source.h" - namespace aapt { /** - * The various types of resource types available. Corresponds - * to the 'type' in package:type/entry. + * The various types of resource types available. */ enum class ResourceType { kAnim, @@ -78,15 +77,63 @@ android::StringPiece to_string(ResourceType type); const ResourceType* ParseResourceType(const android::StringPiece& str); /** + * Pair of type name as in ResourceTable and actual resource type. + * Corresponds to the 'type' in package:type/entry. + * + * This is to support resource types with custom names inside resource tables. + */ +struct ResourceNamedType { + std::string name; + ResourceType type = ResourceType::kRaw; + + ResourceNamedType() = default; + ResourceNamedType(const android::StringPiece& n, ResourceType t); + + int compare(const ResourceNamedType& other) const; + + const std::string& to_string() const; +}; + +/** + * Same as ResourceNamedType, but uses StringPieces instead. + * Use this if you need to avoid copying and know that + * the lifetime of this object is shorter than that + * of the original string. + */ +struct ResourceNamedTypeRef { + android::StringPiece name; + ResourceType type = ResourceType::kRaw; + + ResourceNamedTypeRef() = default; + ResourceNamedTypeRef(const ResourceNamedTypeRef&) = default; + ResourceNamedTypeRef(ResourceNamedTypeRef&&) = default; + ResourceNamedTypeRef(const ResourceNamedType& rhs); // NOLINT(google-explicit-constructor) + ResourceNamedTypeRef(const 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; +}; + +ResourceNamedTypeRef ResourceNamedTypeWithDefaultName(ResourceType t); + +std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s); + +/** * A resource's name. This can uniquely identify * a resource in the ResourceTable. */ struct ResourceName { std::string package; - ResourceType type = ResourceType::kRaw; + ResourceNamedType type; 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); int compare(const ResourceName& other) const; @@ -103,13 +150,15 @@ struct ResourceName { */ struct ResourceNameRef { android::StringPiece package; - ResourceType type = ResourceType::kRaw; + ResourceNamedTypeRef type; android::StringPiece entry; ResourceNameRef() = default; 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& operator=(const ResourceNameRef& rhs) = default; ResourceNameRef& operator=(ResourceNameRef&& rhs) = default; @@ -295,17 +344,98 @@ 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 int ResourceNamedType::compare(const ResourceNamedType& other) const { + int cmp = static_cast<int>(type) - static_cast<int>(other.type); + if (cmp != 0) return cmp; + cmp = name.compare(other.name); + return cmp; +} + +inline const std::string& ResourceNamedType::to_string() const { + return name; +} + +inline bool operator<(const ResourceNamedType& lhs, const ResourceNamedType& rhs) { + return lhs.compare(rhs) < 0; +} + +inline bool operator==(const ResourceNamedType& lhs, const ResourceNamedType& rhs) { + return lhs.compare(rhs) == 0; +} + +inline bool operator!=(const ResourceNamedType& lhs, const ResourceNamedType& rhs) { + return lhs.compare(rhs) != 0; +} + +inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNamedType& val) { + return out << val.to_string(); +} + +// +// ResourceNamedTypeRef implementation. +// +inline ResourceNamedTypeRef::ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t) + : name(n), type(t) { +} + +inline ResourceNamedTypeRef::ResourceNamedTypeRef(const ResourceNamedType& rhs) + : name(rhs.name), type(rhs.type) { +} + +inline ResourceNamedTypeRef& ResourceNamedTypeRef::operator=(const ResourceNamedType& rhs) { + name = rhs.name; + type = rhs.type; + return *this; +} + +inline ResourceNamedType ResourceNamedTypeRef::ToResourceNamedType() const { + return ResourceNamedType(name, type); +} + +inline std::string ResourceNamedTypeRef::to_string() const { + return name.to_string(); +} + +inline bool operator<(const ResourceNamedTypeRef& lhs, const ResourceNamedTypeRef& rhs) { + return std::tie(lhs.type, lhs.name) < std::tie(rhs.type, rhs.name); +} + +inline bool operator==(const ResourceNamedTypeRef& lhs, const ResourceNamedTypeRef& rhs) { + return std::tie(lhs.type, lhs.name) == std::tie(rhs.type, rhs.name); +} + +inline bool operator!=(const ResourceNamedTypeRef& lhs, const ResourceNamedTypeRef& rhs) { + return std::tie(lhs.type, lhs.name) != std::tie(rhs.type, rhs.name); +} + +inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNamedTypeRef& val) { + return out << val.name; +} + +// // 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(const android::StringPiece& p, ResourceType t, const android::StringPiece& e) - : package(p.to_string()), type(t), entry(e.to_string()) {} + : ResourceName(p, ResourceNamedTypeWithDefaultName(t), e) { +} inline int ResourceName::compare(const ResourceName& other) const { int cmp = package.compare(other.package); if (cmp != 0) return cmp; - cmp = static_cast<int>(type) - static_cast<int>(other.type); + cmp = type.compare(other.type); if (cmp != 0) return cmp; cmp = entry.compare(other.entry); return cmp; @@ -341,9 +471,16 @@ 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) + : package(p), type(t), entry(e) { +} + inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p, ResourceType t, const android::StringPiece& e) - : package(p), type(t), entry(e) {} + : ResourceNameRef(p, ResourceNamedTypeWithDefaultName(t), e) { +} inline ResourceNameRef& ResourceNameRef::operator=(const ResourceName& rhs) { package = rhs.package; @@ -400,7 +537,7 @@ struct hash<aapt::ResourceName> { size_t operator()(const aapt::ResourceName& name) const { android::hash_t h = 0; h = android::JenkinsHashMix(h, static_cast<uint32_t>(hash<string>()(name.package))); - h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type)); + h = android::JenkinsHashMix(h, static_cast<uint32_t>(hash<string>()(name.type.name))); h = android::JenkinsHashMix(h, static_cast<uint32_t>(hash<string>()(name.entry))); return static_cast<size_t>(h); } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index f1e2da9f41e2..8d35eeec2a93 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -20,7 +20,8 @@ #include <limits> #include <sstream> -#include "android-base/logging.h" +#include <android-base/logging.h> +#include <idmap2/Policies.h> #include "ResourceTable.h" #include "ResourceUtils.h" @@ -28,12 +29,10 @@ #include "ValueVisitor.h" #include "text/Utf8Iterator.h" #include "util/ImmutableMap.h" -#include "util/Maybe.h" + #include "util/Util.h" #include "xml/XmlPullParser.h" -#include "idmap2/Policies.h" - using ::aapt::ResourceUtils::StringBuilder; using ::aapt::text::Utf8Iterator; using ::android::ConfigDescription; @@ -109,8 +108,8 @@ struct ParsedResource { Visibility::Level visibility_level = Visibility::Level::kUndefined; bool staged_api = false; bool allow_new = false; - Maybe<OverlayableItem> overlayable_item; - Maybe<StagedId> staged_alias; + std::optional<OverlayableItem> overlayable_item; + std::optional<StagedId> staged_alias; std::string comment; std::unique_ptr<Value> value; @@ -252,7 +251,7 @@ bool ResourceParser::FlattenXmlSubtree( std::string current_text; // The first occurrence of a <xliff:g> tag. Nested <xliff:g> tags are illegal. - Maybe<size_t> untranslatable_start_depth; + std::optional<size_t> untranslatable_start_depth; Node root; std::vector<Node*> node_stack; @@ -342,7 +341,7 @@ bool ResourceParser::FlattenXmlSubtree( } node_stack.pop_back(); - if (untranslatable_start_depth == make_value(depth)) { + if (untranslatable_start_depth == depth) { // This is the end of an untranslatable section. untranslatable_start_depth = {}; } @@ -461,14 +460,14 @@ bool ResourceParser::ParseResources(xml::XmlPullParser* parser) { ParsedResource parsed_resource; parsed_resource.config = config_; parsed_resource.source = source_.WithLine(parser->line_number()); - // NOLINTNEXTLINE(bugprone-use-after-move) move+reset comment parsed_resource.comment = std::move(comment); + comment.clear(); if (options_.visibility) { parsed_resource.visibility_level = options_.visibility.value(); } // Extract the product name if it exists. - if (Maybe<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) { + if (std::optional<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) { parsed_resource.product = maybe_product.value().to_string(); } @@ -560,7 +559,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, resource_format = android::ResTable_map::TYPE_ANY; // Items have their type encoded in the type attribute. - if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { + if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { resource_type = maybe_type.value().to_string(); } else { diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) @@ -568,7 +567,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, return false; } - if (Maybe<StringPiece> maybe_format = xml::FindNonEmptyAttribute(parser, "format")) { + if (std::optional<StringPiece> maybe_format = xml::FindNonEmptyAttribute(parser, "format")) { // An explicit format for this resource was specified. The resource will // retain its type in its name, but the accepted value for this type is // overridden. @@ -584,7 +583,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, can_be_item = false; // Bags have their type encoded in the type attribute. - if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { + if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { resource_type = maybe_type.value().to_string(); } else { diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) @@ -595,7 +594,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, // Get the name of the resource. This will be checked later, because not all // XML elements require a name. - Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); + std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); if (resource_type == "id") { if (!maybe_name) { @@ -605,7 +604,8 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, return false; } - out_resource->name.type = ResourceType::kId; + out_resource->name.type = + ResourceNamedTypeWithDefaultName(ResourceType::kId).ToResourceNamedType(); out_resource->name.entry = maybe_name.value().to_string(); // Ids either represent a unique resource id or reference another resource id @@ -624,7 +624,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, // A null reference also means there is no inner element when ids are in the form: // <id name="name"/> out_resource->value = util::make_unique<Id>(); - } else if (!ref || ref->name.value().type != ResourceType::kId) { + } 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() @@ -641,7 +641,8 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, return false; } - out_resource->name.type = ResourceType::kMacro; + out_resource->name.type = + ResourceNamedTypeWithDefaultName(ResourceType::kMacro).ToResourceNamedType(); out_resource->name.entry = maybe_name.value().to_string(); return ParseMacro(parser, out_resource); } @@ -657,7 +658,8 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, return false; } - out_resource->name.type = item_iter->second.type; + out_resource->name.type = + ResourceNamedTypeWithDefaultName(item_iter->second.type).ToResourceNamedType(); out_resource->name.entry = maybe_name.value().to_string(); // Only use the implied format of the type when there is no explicit format. @@ -700,7 +702,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, if (can_be_item) { // Try parsing the elementName (or type) as a resource. These shall only be // resources like 'layout' or 'xml' and they can only be references. - const ResourceType* parsed_type = ParseResourceType(resource_type); + std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(resource_type); if (parsed_type) { if (!maybe_name) { diag_->Error(DiagMessage(out_resource->source) @@ -709,7 +711,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, return false; } - out_resource->name.type = *parsed_type; + out_resource->name.type = parsed_type->ToResourceNamedType(); out_resource->name.entry = maybe_name.value().to_string(); out_resource->value = ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString); if (!out_resource->value) { @@ -835,10 +837,8 @@ std::unique_ptr<Item> ResourceParser::ParseXml(const FlattenedXmlSubTree& xmlsub bool ResourceParser::ParseString(xml::XmlPullParser* parser, ParsedResource* out_resource) { bool formatted = true; - if (Maybe<StringPiece> formatted_attr = - xml::FindAttribute(parser, "formatted")) { - Maybe<bool> maybe_formatted = - ResourceUtils::ParseBool(formatted_attr.value()); + 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) << "invalid value for 'formatted'. Must be a boolean"); @@ -848,8 +848,8 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser, } bool translatable = options_.translatable; - if (Maybe<StringPiece> translatable_attr = xml::FindAttribute(parser, "translatable")) { - Maybe<bool> maybe_translatable = ResourceUtils::ParseBool(translatable_attr.value()); + 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) << "invalid value for 'translatable'. Must be a boolean"); @@ -929,14 +929,14 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out << "ignoring configuration '" << out_resource->config << "' for <public> tag"); } - Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); + std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { diag_->Error(DiagMessage(out_resource->source) << "<public> must have a 'type' attribute"); return false; } - const ResourceType* parsed_type = ParseResourceType(maybe_type.value()); + std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(maybe_type.value()); if (!parsed_type) { diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '" << maybe_type.value() @@ -944,10 +944,10 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out return false; } - out_resource->name.type = *parsed_type; + out_resource->name.type = parsed_type->ToResourceNamedType(); - if (Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "id")) { - Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value()); + 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) << "invalid resource ID '" << maybe_id_str.value() << "' in <public>"); @@ -956,7 +956,7 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out out_resource->id = maybe_id.value(); } - if (*parsed_type == ResourceType::kId) { + if (parsed_type->type == ResourceType::kId) { // An ID marked as public is also the definition of an ID. out_resource->value = util::make_unique<Id>(); } @@ -974,28 +974,28 @@ bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resou << "> tag"); } - Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); + std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { diag->Error(DiagMessage(out_resource->source) << "<" << tag_name << "> must have a 'type' attribute"); return false; } - const ResourceType* parsed_type = ParseResourceType(maybe_type.value()); + 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 <" << tag_name << ">"); return false; } - Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "first-id"); + std::optional<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "first-id"); if (!maybe_id_str) { diag->Error(DiagMessage(out_resource->source) << "<" << tag_name << "> must have a 'first-id' attribute"); return false; } - Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value()); + std::optional<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value()); if (!maybe_id) { diag->Error(DiagMessage(out_resource->source) << "invalid resource ID '" << maybe_id_str.value() << "' in <" << tag_name << ">"); @@ -1038,11 +1038,19 @@ bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resou continue; } + if (maybe_name.value().substr(0, std::strlen("removed_")) == "removed_") { + // Skip resources that have been removed from the framework, but leave a hole so that + // other staged resources don't shift and break apps previously compiled against them + next_id.id++; + continue; + } + ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{ .name = ResourceName{{}, *parsed_type, maybe_name.value().to_string()}, .source = item_source, .comment = std::move(comment), }); + comment.clear(); // Execute group specific code. func(entry_res, next_id); @@ -1090,7 +1098,7 @@ bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource) { - Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); + std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { diag_->Error(DiagMessage(out_resource->source) << "<" << parser->element_name() @@ -1098,7 +1106,7 @@ bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser, return false; } - const ResourceType* parsed_type = ParseResourceType(maybe_type.value()); + 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 <" @@ -1106,7 +1114,7 @@ bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser, return false; } - out_resource->name.type = *parsed_type; + out_resource->name.type = parsed_type->ToResourceNamedType(); return true; } @@ -1137,7 +1145,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource << "' for <overlayable> tag"); } - Maybe<StringPiece> overlayable_name = xml::FindNonEmptyAttribute(parser, "name"); + 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"); @@ -1146,7 +1154,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource const std::string kActorUriScheme = android::base::StringPrintf("%s://", Overlayable::kActorScheme); - Maybe<StringPiece> overlayable_actor = xml::FindNonEmptyAttribute(parser, "actor"); + std::optional<StringPiece> overlayable_actor = xml::FindNonEmptyAttribute(parser, "actor"); if (overlayable_actor && !util::StartsWith(overlayable_actor.value(), kActorUriScheme)) { diag_->Error(DiagMessage(out_resource->source) << "specified <overlayable> tag 'actor' attribute must use the scheme '" @@ -1194,7 +1202,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource } // Items specify the name and type of resource that should be overlayable - Maybe<StringPiece> item_name = xml::FindNonEmptyAttribute(parser, "name"); + std::optional<StringPiece> item_name = xml::FindNonEmptyAttribute(parser, "name"); if (!item_name) { diag_->Error(DiagMessage(element_source) << "<item> within an <overlayable> must have a 'name' attribute"); @@ -1202,7 +1210,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource continue; } - Maybe<StringPiece> item_type = xml::FindNonEmptyAttribute(parser, "type"); + std::optional<StringPiece> item_type = xml::FindNonEmptyAttribute(parser, "type"); if (!item_type) { diag_->Error(DiagMessage(element_source) << "<item> within an <overlayable> must have a 'type' attribute"); @@ -1210,8 +1218,8 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource continue; } - const ResourceType* type = ParseResourceType(item_type.value()); - if (type == nullptr) { + std::optional<ResourceNamedTypeRef> type = ParseResourceNamedType(item_type.value()); + if (!type) { diag_->Error(DiagMessage(element_source) << "invalid resource type '" << item_type.value() << "' in <item> within an <overlayable>"); @@ -1225,7 +1233,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource overlayable_item.source = element_source; ParsedResource child_resource{}; - child_resource.name.type = *type; + child_resource.name.type = type->ToResourceNamedType(); child_resource.name.entry = item_name.value().to_string(); child_resource.overlayable_item = overlayable_item; out_resource->child_resources.push_back(std::move(child_resource)); @@ -1236,7 +1244,8 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource diag_->Error(DiagMessage(element_source) << "<policy> blocks cannot be recursively nested"); error = true; break; - } else if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { + } 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(), '|')) { @@ -1290,7 +1299,8 @@ bool ResourceParser::ParseAttr(xml::XmlPullParser* parser, bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak) { - out_resource->name.type = ResourceType::kAttr; + out_resource->name.type = + ResourceNamedTypeWithDefaultName(ResourceType::kAttr).ToResourceNamedType(); // Attributes only end up in default configuration. if (out_resource->config != ConfigDescription::DefaultConfig()) { @@ -1302,7 +1312,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, uint32_t type_mask = 0; - Maybe<StringPiece> maybe_format = xml::FindAttribute(parser, "format"); + std::optional<StringPiece> maybe_format = xml::FindAttribute(parser, "format"); if (maybe_format) { type_mask = ParseFormatAttribute(maybe_format.value()); if (type_mask == 0) { @@ -1312,9 +1322,9 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, } } - Maybe<int32_t> maybe_min, maybe_max; + std::optional<int32_t> maybe_min, maybe_max; - if (Maybe<StringPiece> maybe_min_str = xml::FindAttribute(parser, "min")) { + 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); @@ -1331,7 +1341,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, } } - if (Maybe<StringPiece> maybe_max_str = xml::FindAttribute(parser, "max")) { + 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); @@ -1398,8 +1408,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, type_mask |= android::ResTable_map::TYPE_FLAGS; } - if (Maybe<Attribute::Symbol> s = - ParseEnumOrFlagItem(parser, element_name)) { + if (std::optional<Attribute::Symbol> s = ParseEnumOrFlagItem(parser, element_name)) { Attribute::Symbol& symbol = s.value(); ParsedResource child_resource; child_resource.name = symbol.symbol.name.value(); @@ -1443,24 +1452,24 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, type_mask ? type_mask : uint32_t{android::ResTable_map::TYPE_ANY}); attr->SetWeak(weak); attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end()); - attr->min_int = maybe_min.value_or_default(std::numeric_limits<int32_t>::min()); - attr->max_int = maybe_max.value_or_default(std::numeric_limits<int32_t>::max()); + attr->min_int = maybe_min.value_or(std::numeric_limits<int32_t>::min()); + attr->max_int = maybe_max.value_or(std::numeric_limits<int32_t>::max()); out_resource->value = std::move(attr); return true; } -Maybe<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem( - xml::XmlPullParser* parser, const StringPiece& tag) { +std::optional<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(xml::XmlPullParser* parser, + const StringPiece& tag) { const Source source = source_.WithLine(parser->line_number()); - Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); + std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); if (!maybe_name) { diag_->Error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">"); return {}; } - Maybe<StringPiece> maybe_value = xml::FindNonEmptyAttribute(parser, "value"); + std::optional<StringPiece> maybe_value = xml::FindNonEmptyAttribute(parser, "value"); if (!maybe_value) { diag_->Error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">"); @@ -1477,20 +1486,21 @@ Maybe<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem( } return Attribute::Symbol{ - Reference(ResourceNameRef({}, ResourceType::kId, maybe_name.value())), + Reference(ResourceNameRef({}, ResourceNamedTypeWithDefaultName(ResourceType::kId), + maybe_name.value())), val.data, val.dataType}; } bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) { const Source source = source_.WithLine(parser->line_number()); - Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); + std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); if (!maybe_name) { diag_->Error(DiagMessage(source) << "<item> must have a 'name' attribute"); return false; } - Maybe<Reference> maybe_key = ResourceUtils::ParseXmlAttributeName(maybe_name.value()); + std::optional<Reference> maybe_key = ResourceUtils::ParseXmlAttributeName(maybe_name.value()); if (!maybe_key) { diag_->Error(DiagMessage(source) << "invalid attribute name '" << maybe_name.value() << "'"); return false; @@ -1511,11 +1521,11 @@ bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) { bool ResourceParser::ParseStyle(const ResourceType type, xml::XmlPullParser* parser, ParsedResource* out_resource) { - out_resource->name.type = type; + out_resource->name.type = ResourceNamedTypeWithDefaultName(type).ToResourceNamedType(); std::unique_ptr<Style> style = util::make_unique<Style>(); - Maybe<StringPiece> maybe_parent = xml::FindAttribute(parser, "parent"); + std::optional<StringPiece> maybe_parent = xml::FindAttribute(parser, "parent"); if (maybe_parent) { // If the parent is empty, we don't have a parent, but we also don't infer either. if (!maybe_parent.value().empty()) { @@ -1537,7 +1547,8 @@ bool ResourceParser::ParseStyle(const ResourceType type, xml::XmlPullParser* par size_t pos = style_name.find_last_of(u'.'); if (pos != std::string::npos) { style->parent_inferred = true; - style->parent = Reference(ResourceName({}, ResourceType::kStyle, style_name.substr(0, pos))); + style->parent = Reference(ResourceName( + {}, ResourceNamedTypeWithDefaultName(ResourceType::kStyle), style_name.substr(0, pos))); } } @@ -1571,7 +1582,7 @@ bool ResourceParser::ParseStyle(const ResourceType type, xml::XmlPullParser* par bool ResourceParser::ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource) { uint32_t resource_format = android::ResTable_map::TYPE_ANY; - if (Maybe<StringPiece> format_attr = xml::FindNonEmptyAttribute(parser, "format")) { + 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())) @@ -1593,13 +1604,14 @@ bool ResourceParser::ParseStringArray(xml::XmlPullParser* parser, ParsedResource bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, const uint32_t typeMask) { - out_resource->name.type = ResourceType::kArray; + out_resource->name.type = + ResourceNamedTypeWithDefaultName(ResourceType::kArray).ToResourceNamedType(); std::unique_ptr<Array> array = util::make_unique<Array>(); bool translatable = options_.translatable; - if (Maybe<StringPiece> translatable_attr = xml::FindAttribute(parser, "translatable")) { - Maybe<bool> maybe_translatable = ResourceUtils::ParseBool(translatable_attr.value()); + 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) << "invalid value for 'translatable'. Must be a boolean"); @@ -1648,7 +1660,8 @@ bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser, bool ResourceParser::ParsePlural(xml::XmlPullParser* parser, ParsedResource* out_resource) { - out_resource->name.type = ResourceType::kPlurals; + out_resource->name.type = + ResourceNamedTypeWithDefaultName(ResourceType::kPlurals).ToResourceNamedType(); std::unique_ptr<Plural> plural = util::make_unique<Plural>(); @@ -1664,8 +1677,7 @@ bool ResourceParser::ParsePlural(xml::XmlPullParser* parser, const std::string& element_namespace = parser->element_namespace(); const std::string& element_name = parser->element_name(); if (element_namespace.empty() && element_name == "item") { - Maybe<StringPiece> maybe_quantity = - xml::FindNonEmptyAttribute(parser, "quantity"); + std::optional<StringPiece> maybe_quantity = xml::FindNonEmptyAttribute(parser, "quantity"); if (!maybe_quantity) { diag_->Error(DiagMessage(item_source) << "<item> in <plurals> requires attribute " @@ -1730,7 +1742,8 @@ bool ResourceParser::ParsePlural(xml::XmlPullParser* parser, bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource) { - out_resource->name.type = ResourceType::kStyleable; + out_resource->name.type = + ResourceNamedTypeWithDefaultName(ResourceType::kStyleable).ToResourceNamedType(); if (!options_.preserve_visibility_of_styleables) { // This was added in change Idd21b5de4d20be06c6f8c8eb5a22ccd68afc4927 to mimic aapt1, but no one @@ -1767,7 +1780,7 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, const std::string& element_namespace = parser->element_namespace(); const std::string& element_name = parser->element_name(); if (element_namespace.empty() && element_name == "attr") { - Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); + std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); if (!maybe_name) { diag_->Error(DiagMessage(item_source) << "<attr> tag must have a 'name' attribute"); error = true; @@ -1777,7 +1790,7 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, // If this is a declaration, the package name may be in the name. Separate // these out. // Eg. <attr name="android:text" /> - Maybe<Reference> maybe_ref = ResourceUtils::ParseXmlAttributeName(maybe_name.value()); + 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() << "'"); @@ -1792,8 +1805,8 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource child_resource; child_resource.name = child_ref.name.value(); child_resource.source = item_source; - // NOLINTNEXTLINE(bugprone-use-after-move) move+reset comment child_resource.comment = std::move(comment); + comment.clear(); if (options_.visibility) { child_resource.visibility_level = options_.visibility.value(); } diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 261499781638..548f5f9531fd 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -18,6 +18,7 @@ #define AAPT_RESOURCE_PARSER_H #include <memory> +#include <optional> #include "android-base/macros.h" #include "androidfw/ConfigDescription.h" @@ -27,7 +28,6 @@ #include "ResourceTable.h" #include "ResourceValues.h" #include "StringPool.h" -#include "util/Maybe.h" #include "xml/XmlPullParser.h" namespace aapt { @@ -54,7 +54,7 @@ struct ResourceParserOptions { // If visibility was forced, we need to use it when creating a new resource and also error if we // try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags. - Maybe<Visibility::Level> visibility; + std::optional<Visibility::Level> visibility; }; struct FlattenedXmlSubTree { @@ -122,8 +122,8 @@ class ResourceParser { bool ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak); - Maybe<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser, - const android::StringPiece& tag); + std::optional<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser, + const 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); diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 279ebcba2f71..556ffa221db5 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -567,12 +567,12 @@ TEST_F(ResourceParserTest, ParseStyle) { Style* style = test::GetValue<Style>(&table_, "style/foo"); ASSERT_THAT(style, NotNull()); ASSERT_TRUE(style->parent); - EXPECT_THAT(style->parent.value().name, Eq(make_value(test::ParseNameOrDie("style/fu")))); + EXPECT_THAT(style->parent.value().name, Eq(test::ParseNameOrDie("style/fu"))); ASSERT_THAT(style->entries, SizeIs(3)); - EXPECT_THAT(style->entries[0].key.name, Eq(make_value(test::ParseNameOrDie("attr/bar")))); - EXPECT_THAT(style->entries[1].key.name, Eq(make_value(test::ParseNameOrDie("attr/bat")))); - EXPECT_THAT(style->entries[2].key.name, Eq(make_value(test::ParseNameOrDie("attr/baz")))); + EXPECT_THAT(style->entries[0].key.name, Eq(test::ParseNameOrDie("attr/bar"))); + EXPECT_THAT(style->entries[1].key.name, Eq(test::ParseNameOrDie("attr/bat"))); + EXPECT_THAT(style->entries[2].key.name, Eq(test::ParseNameOrDie("attr/baz"))); } TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { @@ -581,7 +581,7 @@ TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { Style* style = test::GetValue<Style>(&table_, "style/foo"); ASSERT_THAT(style, NotNull()); ASSERT_TRUE(style->parent); - EXPECT_THAT(style->parent.value().name, Eq(make_value(test::ParseNameOrDie("com.app:style/Theme")))); + EXPECT_THAT(style->parent.value().name, Eq(test::ParseNameOrDie("com.app:style/Theme"))); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { @@ -594,7 +594,7 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { ASSERT_THAT(style, NotNull()); ASSERT_TRUE(style->parent); ASSERT_TRUE(style->parent.value().name); - EXPECT_THAT(style->parent.value().name, Eq(make_value(test::ParseNameOrDie("android:style/Theme")))); + EXPECT_THAT(style->parent.value().name, Eq(test::ParseNameOrDie("android:style/Theme"))); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { @@ -607,7 +607,7 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { Style* style = test::GetValue<Style>(&table_, "style/foo"); ASSERT_THAT(style, NotNull()); ASSERT_THAT(style->entries, SizeIs(1)); - EXPECT_THAT(style->entries[0].key.name, Eq(make_value(test::ParseNameOrDie("android:attr/bar")))); + EXPECT_THAT(style->entries[0].key.name, Eq(test::ParseNameOrDie("android:attr/bar"))); } TEST_F(ResourceParserTest, ParseStyleWithRawStringItem) { @@ -634,7 +634,7 @@ TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { Style* style = test::GetValue<Style>(&table_, "style/foo.bar"); ASSERT_THAT(style, NotNull()); ASSERT_TRUE(style->parent); - EXPECT_THAT(style->parent.value().name, Eq(make_value(test::ParseNameOrDie("style/foo")))); + EXPECT_THAT(style->parent.value().name, Eq(test::ParseNameOrDie("style/foo"))); EXPECT_TRUE(style->parent_inferred); } @@ -672,7 +672,7 @@ TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { </declare-styleable>)"; ASSERT_TRUE(TestParse(input)); - Maybe<ResourceTable::SearchResult> result = + std::optional<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("styleable/foo")); ASSERT_TRUE(result); EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); @@ -695,9 +695,9 @@ TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { ASSERT_THAT(styleable, NotNull()); ASSERT_THAT(styleable->entries, SizeIs(3)); - EXPECT_THAT(styleable->entries[0].name, Eq(make_value(test::ParseNameOrDie("attr/bar")))); - EXPECT_THAT(styleable->entries[1].name, Eq(make_value(test::ParseNameOrDie("attr/bat")))); - EXPECT_THAT(styleable->entries[2].name, Eq(make_value(test::ParseNameOrDie("attr/baz")))); + EXPECT_THAT(styleable->entries[0].name, Eq(test::ParseNameOrDie("attr/bar"))); + EXPECT_THAT(styleable->entries[1].name, Eq(test::ParseNameOrDie("attr/bat"))); + EXPECT_THAT(styleable->entries[2].name, Eq(test::ParseNameOrDie("attr/baz"))); } TEST_F(ResourceParserTest, ParseDeclareStyleablePreservingVisibility) { @@ -913,7 +913,8 @@ TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { </public-group>)"; ASSERT_TRUE(TestParse(input)); - Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("attr/foo")); + std::optional<ResourceTable::SearchResult> result = + table_.FindResource(test::ParseNameOrDie("attr/foo")); ASSERT_TRUE(result); ASSERT_TRUE(result.value().entry->id); EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01010040))); @@ -932,7 +933,8 @@ TEST_F(ResourceParserTest, StagingPublicGroup) { </staging-public-group>)"; ASSERT_TRUE(TestParse(input)); - Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("attr/foo")); + std::optional<ResourceTable::SearchResult> result = + table_.FindResource(test::ParseNameOrDie("attr/foo")); ASSERT_TRUE(result); ASSERT_TRUE(result.value().entry->id); @@ -959,7 +961,7 @@ TEST_F(ResourceParserTest, StrongestSymbolVisibilityWins) { <java-symbol type="string" name="foo" />)"; ASSERT_TRUE(TestParse(input)); - Maybe<ResourceTable::SearchResult> result = + std::optional<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("string/foo")); ASSERT_TRUE(result); @@ -977,7 +979,7 @@ TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) { TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) { ASSERT_TRUE(TestParse(R"(<add-resource name="bar" type="string" />)")); - Maybe<ResourceTable::SearchResult> result = + std::optional<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("string/bar")); ASSERT_TRUE(result); const ResourceEntry* entry = result.value().entry; diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 8ab1493c6ab3..98cce268e213 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -18,6 +18,7 @@ #include <algorithm> #include <memory> +#include <optional> #include <tuple> #include "android-base/logging.h" @@ -68,7 +69,7 @@ struct NameEqualRange { template <typename T, typename U> bool less_than_struct_with_name_and_id(const T& lhs, - const std::pair<std::string_view, Maybe<U>>& rhs) { + const std::pair<std::string_view, std::optional<U>>& rhs) { if (lhs.id != rhs.second) { return lhs.id < rhs.second; } @@ -341,20 +342,20 @@ struct EntryViewComparer { void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePackage* package, const ResourceTableType* type, const std::string& entry_name, - const Maybe<ResourceId>& id, const Visibility& visibility, - const Maybe<AllowNew>& allow_new, - const Maybe<OverlayableItem>& overlayable_item, - const Maybe<StagedId>& staged_id, + const std::optional<ResourceId>& id, const Visibility& visibility, + const std::optional<AllowNew>& allow_new, + const std::optional<OverlayableItem>& overlayable_item, + const std::optional<StagedId>& staged_id, const std::vector<std::unique_ptr<ResourceConfigValue>>& values) { SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter; SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter; SortedVectorInserter<ResourceTableEntryView, EntryViewComparer> entry_inserter; ResourceTablePackageView new_package{package->name, - id ? id.value().package_id() : Maybe<uint8_t>{}}; + 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() : Maybe<uint8_t>{}}; + ResourceTableTypeView new_type{type->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) { @@ -363,7 +364,7 @@ void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePacka } ResourceTableEntryView new_entry{.name = entry_name, - .id = id ? id.value().entry_id() : Maybe<uint16_t>{}, + .id = id ? id.value().entry_id() : std::optional<uint16_t>{}, .visibility = visibility, .allow_new = allow_new, .overlayable_item = overlayable_item, @@ -435,11 +436,11 @@ ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptio const size_t index = type_index_iter->second; if (new_packages.size() == index) { new_packages.emplace_back(ResourceTablePackageView{package.name, package.id}); - type_new_package_index[type.type] = index + 1; } // Move the type into a new package auto& other_package = new_packages[index]; + type_new_package_index[type.type] = index + 1; type_inserter.Insert(other_package.types, std::move(type)); type_it = package.types.erase(type_it); } @@ -472,7 +473,7 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) { } auto package = FindOrCreatePackage(res.name.package); - auto type = package->FindOrCreateType(res.name.type); + auto type = package->FindOrCreateType(res.name.type.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); @@ -585,13 +586,14 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) { return true; } -Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) const { +std::optional<ResourceTable::SearchResult> ResourceTable::FindResource( + const ResourceNameRef& name) const { ResourceTablePackage* package = FindPackage(name.package); if (package == nullptr) { return {}; } - ResourceTableType* type = package->FindType(name.type); + ResourceTableType* type = package->FindType(name.type.type); if (type == nullptr) { return {}; } @@ -603,14 +605,14 @@ Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNam return SearchResult{package, type, entry}; } -Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name, - ResourceId id) const { +std::optional<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name, + ResourceId id) const { ResourceTablePackage* package = FindPackage(name.package); if (package == nullptr) { return {}; } - ResourceTableType* type = package->FindType(name.type); + ResourceTableType* type = package->FindType(name.type.type); if (type == nullptr) { return {}; } @@ -631,7 +633,7 @@ bool ResourceTable::RemoveResource(const ResourceNameRef& name, ResourceId id) c return {}; } - ResourceTableType* type = package->FindType(name.type); + ResourceTableType* type = package->FindType(name.type.type); if (type == nullptr) { return {}; } diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index bae1d827a841..2e17659b0679 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -120,18 +120,18 @@ class ResourceEntry { const std::string name; // The entry ID for this resource (the EEEE in 0xPPTTEEEE). - Maybe<ResourceId> id; + std::optional<ResourceId> id; // Whether this resource is public (and must maintain the same entry ID across builds). Visibility visibility; - Maybe<AllowNew> allow_new; + std::optional<AllowNew> allow_new; // The declarations of this resource as overlayable for RROs - Maybe<OverlayableItem> overlayable_item; + std::optional<OverlayableItem> overlayable_item; // The staged resource id for a finalized resource. - Maybe<StagedId> staged_id; + std::optional<StagedId> staged_id; // The resource's values for each configuration. std::vector<std::unique_ptr<ResourceConfigValue>> values; @@ -205,11 +205,11 @@ class ResourceTablePackage { struct ResourceTableEntryView { std::string name; - Maybe<uint16_t> id; + std::optional<uint16_t> id; Visibility visibility; - Maybe<AllowNew> allow_new; - Maybe<OverlayableItem> overlayable_item; - Maybe<StagedId> staged_id; + std::optional<AllowNew> allow_new; + std::optional<OverlayableItem> overlayable_item; + std::optional<StagedId> staged_id; std::vector<const ResourceConfigValue*> values; const ResourceConfigValue* FindValue(const android::ConfigDescription& config, @@ -218,7 +218,7 @@ struct ResourceTableEntryView { struct ResourceTableTypeView { ResourceType type; - Maybe<uint8_t> id; + std::optional<uint8_t> id; Visibility::Level visibility_level = Visibility::Level::kUndefined; // Entries sorted in ascending entry id order. If ids have not been assigned, the entries are @@ -228,7 +228,7 @@ struct ResourceTableTypeView { struct ResourceTablePackageView { std::string name; - Maybe<uint8_t> id; + std::optional<uint8_t> id; // Types sorted in ascending type id order. If ids have not been assigned, the types are sorted by // their declaration order in the ResourceType enum. std::vector<ResourceTableTypeView> types; @@ -309,8 +309,8 @@ class ResourceTable { ResourceEntry* entry; }; - Maybe<SearchResult> FindResource(const ResourceNameRef& name) const; - Maybe<SearchResult> FindResource(const ResourceNameRef& name, ResourceId id) const; + std::optional<SearchResult> FindResource(const ResourceNameRef& name) const; + std::optional<SearchResult> FindResource(const ResourceNameRef& name, ResourceId id) const; bool RemoveResource(const ResourceNameRef& name, ResourceId id) const; // Returns the package struct with the given name, or nullptr if such a package does not diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 38391c99f55a..de73d2c203e4 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -162,7 +162,7 @@ TEST(ResourceTableTest, ProductVaryingValues) { EXPECT_THAT(test::GetValueForConfigAndProduct<Id>(&table, "android:string/foo",test::ParseConfigOrDie("land"), "tablet"), NotNull()); EXPECT_THAT(test::GetValueForConfigAndProduct<Id>(&table, "android:string/foo",test::ParseConfigOrDie("land"), "phone"), NotNull()); - Maybe<ResourceTable::SearchResult> sr = + std::optional<ResourceTable::SearchResult> sr = table.FindResource(test::ParseNameOrDie("android:string/foo")); ASSERT_TRUE(sr); std::vector<ResourceConfigValue*> values = @@ -187,7 +187,7 @@ static ::testing::AssertionResult VisibilityOfResource(const ResourceTable& tabl const ResourceNameRef& name, Visibility::Level level, const StringPiece& comment) { - Maybe<ResourceTable::SearchResult> result = table.FindResource(name); + std::optional<ResourceTable::SearchResult> result = table.FindResource(name); if (!result) { return ::testing::AssertionFailure() << "no resource '" << name << "' found in table"; } @@ -242,7 +242,7 @@ TEST(ResourceTableTest, SetAllowNew) { const ResourceName name = test::ParseNameOrDie("android:string/foo"); AllowNew allow_new; - Maybe<ResourceTable::SearchResult> result; + std::optional<ResourceTable::SearchResult> result; allow_new.comment = "first"; ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetAllowNew(allow_new).Build(), @@ -274,7 +274,7 @@ TEST(ResourceTableTest, SetOverlayable) { const ResourceName name = test::ParseNameOrDie("android:string/foo"); ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item).Build(), test::GetDiagnostics())); - Maybe<ResourceTable::SearchResult> search_result = table.FindResource(name); + std::optional<ResourceTable::SearchResult> search_result = table.FindResource(name); ASSERT_TRUE(search_result); ASSERT_TRUE(search_result.value().entry->overlayable_item); diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index e0e80ac02dea..23f6c88aad91 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -40,8 +40,7 @@ using ::android::base::StringPrintf; namespace aapt { namespace ResourceUtils { -Maybe<ResourceName> ToResourceName( - const android::ResTable::resource_name& name_in) { +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; if (!name_in.package) { @@ -51,12 +50,11 @@ Maybe<ResourceName> ToResourceName( name_out.package = util::Utf16ToUtf8(StringPiece16(name_in.package, name_in.packageLen)); - const ResourceType* type; + std::optional<ResourceNamedTypeRef> type; if (name_in.type) { - type = ParseResourceType( - util::Utf16ToUtf8(StringPiece16(name_in.type, name_in.typeLen))); + type = ParseResourceNamedType(util::Utf16ToUtf8(StringPiece16(name_in.type, name_in.typeLen))); } else if (name_in.type8) { - type = ParseResourceType(StringPiece(name_in.type8, name_in.typeLen)); + type = ParseResourceNamedType(StringPiece(name_in.type8, name_in.typeLen)); } else { return {}; } @@ -65,7 +63,7 @@ Maybe<ResourceName> ToResourceName( return {}; } - name_out.type = *type; + name_out.type = type->ToResourceNamedType(); if (name_in.name) { name_out.entry = @@ -78,7 +76,7 @@ Maybe<ResourceName> ToResourceName( return name_out; } -Maybe<ResourceName> ToResourceName(const android::AssetManager2::ResourceName& name_in) { +std::optional<ResourceName> ToResourceName(const android::AssetManager2::ResourceName& name_in) { ResourceName name_out; if (!name_in.package) { return {}; @@ -86,12 +84,12 @@ Maybe<ResourceName> ToResourceName(const android::AssetManager2::ResourceName& n name_out.package = std::string(name_in.package, name_in.package_len); - const ResourceType* type; + std::optional<ResourceNamedTypeRef> type; if (name_in.type16) { - type = ParseResourceType( - util::Utf16ToUtf8(StringPiece16(name_in.type16, name_in.type_len))); + type = + ParseResourceNamedType(util::Utf16ToUtf8(StringPiece16(name_in.type16, name_in.type_len))); } else if (name_in.type) { - type = ParseResourceType(StringPiece(name_in.type, name_in.type_len)); + type = ParseResourceNamedType(StringPiece(name_in.type, name_in.type_len)); } else { return {}; } @@ -100,7 +98,7 @@ Maybe<ResourceName> ToResourceName(const android::AssetManager2::ResourceName& n return {}; } - name_out.type = *type; + name_out.type = type->ToResourceNamedType(); if (name_in.entry16) { name_out.entry = @@ -134,7 +132,7 @@ bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref, return false; } - const ResourceType* parsed_type = ParseResourceType(type); + std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(type); if (!parsed_type) { return false; } @@ -182,7 +180,7 @@ bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref, return false; } - if (create && name.type != ResourceType::kId) { + if (create && name.type.type != ResourceType::kId) { return false; } @@ -231,7 +229,7 @@ bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) { if (out_ref) { out_ref->package = package; - out_ref->type = ResourceType::kAttr; + out_ref->type = ResourceNamedTypeWithDefaultName(ResourceType::kAttr); out_ref->entry = entry; } return true; @@ -251,8 +249,7 @@ bool IsAttributeReference(const StringPiece& str) { * <[*]package>:[style/]<entry> * [[*]package:style/]<entry> */ -Maybe<Reference> ParseStyleParentReference(const StringPiece& str, - std::string* out_error) { +std::optional<Reference> ParseStyleParentReference(const StringPiece& str, std::string* out_error) { if (str.empty()) { return {}; } @@ -274,7 +271,7 @@ Maybe<Reference> ParseStyleParentReference(const StringPiece& str, } ResourceNameRef ref; - ref.type = ResourceType::kStyle; + ref.type = ResourceNamedTypeWithDefaultName(ResourceType::kStyle); StringPiece type_str; android::ExtractResourceName(name, &ref.package, &type_str, &ref.entry); @@ -301,7 +298,7 @@ Maybe<Reference> ParseStyleParentReference(const StringPiece& str, return result; } -Maybe<Reference> ParseXmlAttributeName(const StringPiece& str) { +std::optional<Reference> ParseXmlAttributeName(const StringPiece& str) { StringPiece trimmed_str = util::TrimWhitespace(str); const char* start = trimmed_str.data(); const char* const end = start + trimmed_str.size(); @@ -325,8 +322,9 @@ Maybe<Reference> ParseXmlAttributeName(const StringPiece& str) { p++; } - ref.name = ResourceName(package, ResourceType::kAttr, name.empty() ? trimmed_str : name); - return Maybe<Reference>(std::move(ref)); + ref.name = ResourceName(package, ResourceNamedTypeWithDefaultName(ResourceType::kAttr), + name.empty() ? trimmed_str : name); + return std::optional<Reference>(std::move(ref)); } std::unique_ptr<Reference> TryParseReference(const StringPiece& str, @@ -488,18 +486,18 @@ std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) { : util::make_unique<BinaryPrimitive>(value); } -Maybe<bool> ParseBool(const StringPiece& str) { +std::optional<bool> ParseBool(const StringPiece& str) { StringPiece trimmed_str(util::TrimWhitespace(str)); if (trimmed_str == "true" || trimmed_str == "TRUE" || trimmed_str == "True") { - return Maybe<bool>(true); + return std::optional<bool>(true); } else if (trimmed_str == "false" || trimmed_str == "FALSE" || trimmed_str == "False") { - return Maybe<bool>(false); + return std::optional<bool>(false); } return {}; } -Maybe<uint32_t> ParseInt(const StringPiece& str) { +std::optional<uint32_t> ParseInt(const StringPiece& str) { std::u16string str16 = util::Utf8ToUtf16(str); android::Res_value value; if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { @@ -508,7 +506,7 @@ Maybe<uint32_t> ParseInt(const StringPiece& str) { return {}; } -Maybe<ResourceId> ParseResourceId(const StringPiece& str) { +std::optional<ResourceId> ParseResourceId(const StringPiece& str) { StringPiece trimmed_str(util::TrimWhitespace(str)); std::u16string str16 = util::Utf8ToUtf16(trimmed_str); @@ -524,7 +522,7 @@ Maybe<ResourceId> ParseResourceId(const StringPiece& str) { return {}; } -Maybe<int> ParseSdkVersion(const StringPiece& str) { +std::optional<int> ParseSdkVersion(const StringPiece& str) { StringPiece trimmed_str(util::TrimWhitespace(str)); std::u16string str16 = util::Utf8ToUtf16(trimmed_str); @@ -534,7 +532,7 @@ Maybe<int> ParseSdkVersion(const StringPiece& str) { } // Try parsing the code name. - Maybe<int> entry = GetDevelopmentSdkCodeNameVersion(trimmed_str); + std::optional<int> entry = GetDevelopmentSdkCodeNameVersion(trimmed_str); if (entry) { return entry.value(); } @@ -551,7 +549,7 @@ Maybe<int> ParseSdkVersion(const StringPiece& str) { } std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str) { - if (Maybe<bool> maybe_result = ParseBool(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); } diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index be493db8cee0..fe450a834dfa 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -75,35 +75,33 @@ bool IsAttributeReference(const android::StringPiece& str); /** * Convert an android::ResTable::resource_name to an aapt::ResourceName struct. */ -Maybe<ResourceName> ToResourceName( - const android::ResTable::resource_name& name); +std::optional<ResourceName> ToResourceName(const android::ResTable::resource_name& name); /** * Convert an android::AssetManager2::ResourceName to an aapt::ResourceName struct. */ -Maybe<ResourceName> ToResourceName( - const android::AssetManager2::ResourceName& name_in); +std::optional<ResourceName> ToResourceName(const android::AssetManager2::ResourceName& name_in); /** * Returns a boolean value if the string is equal to TRUE, true, True, FALSE, * false, or False. */ -Maybe<bool> ParseBool(const android::StringPiece& str); +std::optional<bool> ParseBool(const android::StringPiece& str); /** * Returns a uint32_t if the string is an integer. */ -Maybe<uint32_t> ParseInt(const android::StringPiece& str); +std::optional<uint32_t> ParseInt(const android::StringPiece& str); /** * Returns an ID if it the string represented a valid ID. */ -Maybe<ResourceId> ParseResourceId(const android::StringPiece& str); +std::optional<ResourceId> ParseResourceId(const android::StringPiece& str); /** * Parses an SDK version, which can be an integer, or a letter from A-Z. */ -Maybe<int> ParseSdkVersion(const android::StringPiece& str); +std::optional<int> ParseSdkVersion(const android::StringPiece& str); /* * Returns a Reference, or None Maybe instance if the string `str` was parsed as @@ -116,7 +114,8 @@ Maybe<int> ParseSdkVersion(const android::StringPiece& str); * ?[package:]style/<entry> or * <package>:[style/]<entry> */ -Maybe<Reference> ParseStyleParentReference(const android::StringPiece& str, std::string* out_error); +std::optional<Reference> ParseStyleParentReference(const android::StringPiece& str, + std::string* out_error); /* * Returns a Reference if the string `str` was parsed as a valid XML attribute @@ -125,7 +124,7 @@ Maybe<Reference> ParseStyleParentReference(const android::StringPiece& str, std: * * package:entry */ -Maybe<Reference> ParseXmlAttributeName(const android::StringPiece& str); +std::optional<Reference> ParseXmlAttributeName(const android::StringPiece& str); /* * Returns a Reference object if the string was parsed as a resource or diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index b08bf9a1ff17..1aaa34deee79 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -30,15 +30,15 @@ using ::testing::Pointee; namespace aapt { TEST(ResourceUtilsTest, ParseBool) { - EXPECT_THAT(ResourceUtils::ParseBool("true"), Eq(Maybe<bool>(true))); - EXPECT_THAT(ResourceUtils::ParseBool("TRUE"), Eq(Maybe<bool>(true))); - EXPECT_THAT(ResourceUtils::ParseBool("True"), Eq(Maybe<bool>(true))); + EXPECT_THAT(ResourceUtils::ParseBool("true"), Eq(std::optional<bool>(true))); + EXPECT_THAT(ResourceUtils::ParseBool("TRUE"), Eq(std::optional<bool>(true))); + EXPECT_THAT(ResourceUtils::ParseBool("True"), Eq(std::optional<bool>(true))); - EXPECT_THAT(ResourceUtils::ParseBool("false"), Eq(Maybe<bool>(false))); - EXPECT_THAT(ResourceUtils::ParseBool("FALSE"), Eq(Maybe<bool>(false))); - EXPECT_THAT(ResourceUtils::ParseBool("False"), Eq(Maybe<bool>(false))); + EXPECT_THAT(ResourceUtils::ParseBool("false"), Eq(std::optional<bool>(false))); + EXPECT_THAT(ResourceUtils::ParseBool("FALSE"), Eq(std::optional<bool>(false))); + EXPECT_THAT(ResourceUtils::ParseBool("False"), Eq(std::optional<bool>(false))); - EXPECT_THAT(ResourceUtils::ParseBool(" False\n "), Eq(Maybe<bool>(false))); + EXPECT_THAT(ResourceUtils::ParseBool(" False\n "), Eq(std::optional<bool>(false))); } TEST(ResourceUtilsTest, ParseResourceName) { @@ -155,41 +155,42 @@ TEST(ResourceUtilsTest, ParseStyleParentReference) { const ResourceName kStyleFooName({}, ResourceType::kStyle, "foo"); std::string err_str; - Maybe<Reference> ref = ResourceUtils::ParseStyleParentReference("@android:style/foo", &err_str); + std::optional<Reference> ref = + ResourceUtils::ParseStyleParentReference("@android:style/foo", &err_str); ASSERT_TRUE(ref); - EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); + EXPECT_THAT(ref.value().name, Eq(kAndroidStyleFooName)); ref = ResourceUtils::ParseStyleParentReference("@style/foo", &err_str); ASSERT_TRUE(ref); - EXPECT_THAT(ref.value().name, Eq(make_value(kStyleFooName))); + EXPECT_THAT(ref.value().name, Eq(kStyleFooName)); ref = ResourceUtils::ParseStyleParentReference("?android:style/foo", &err_str); ASSERT_TRUE(ref); - EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); + EXPECT_THAT(ref.value().name, Eq(kAndroidStyleFooName)); ref = ResourceUtils::ParseStyleParentReference("?style/foo", &err_str); ASSERT_TRUE(ref); - EXPECT_THAT(ref.value().name, Eq(make_value(kStyleFooName))); + EXPECT_THAT(ref.value().name, Eq(kStyleFooName)); ref = ResourceUtils::ParseStyleParentReference("android:style/foo", &err_str); ASSERT_TRUE(ref); - EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); + EXPECT_THAT(ref.value().name, Eq(kAndroidStyleFooName)); ref = ResourceUtils::ParseStyleParentReference("android:foo", &err_str); ASSERT_TRUE(ref); - EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); + EXPECT_THAT(ref.value().name, Eq(kAndroidStyleFooName)); ref = ResourceUtils::ParseStyleParentReference("@android:foo", &err_str); ASSERT_TRUE(ref); - EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); + EXPECT_THAT(ref.value().name, Eq(kAndroidStyleFooName)); ref = ResourceUtils::ParseStyleParentReference("foo", &err_str); ASSERT_TRUE(ref); - EXPECT_THAT(ref.value().name, Eq(make_value(kStyleFooName))); + EXPECT_THAT(ref.value().name, Eq(kStyleFooName)); ref = ResourceUtils::ParseStyleParentReference("*android:style/foo", &err_str); ASSERT_TRUE(ref); - EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); + EXPECT_THAT(ref.value().name, Eq(kAndroidStyleFooName)); EXPECT_TRUE(ref.value().private_reference); } @@ -228,15 +229,11 @@ TEST(ResourceUtilsTest, ItemsWithWhitespaceAreParsedCorrectly) { } TEST(ResourceUtilsTest, ParseSdkVersionWithCodename) { - EXPECT_THAT(ResourceUtils::ParseSdkVersion("Q"), Eq(Maybe<int>(10000))); - EXPECT_THAT( - ResourceUtils::ParseSdkVersion("Q.fingerprint"), - Eq(Maybe<int>(10000))); - - EXPECT_THAT(ResourceUtils::ParseSdkVersion("R"), Eq(Maybe<int>(10000))); - EXPECT_THAT( - ResourceUtils::ParseSdkVersion("R.fingerprint"), - Eq(Maybe<int>(10000))); + EXPECT_THAT(ResourceUtils::ParseSdkVersion("Q"), Eq(std::optional<int>(10000))); + EXPECT_THAT(ResourceUtils::ParseSdkVersion("Q.fingerprint"), Eq(std::optional<int>(10000))); + + EXPECT_THAT(ResourceUtils::ParseSdkVersion("R"), Eq(std::optional<int>(10000))); + EXPECT_THAT(ResourceUtils::ParseSdkVersion("R.fingerprint"), Eq(std::optional<int>(10000))); } TEST(ResourceUtilsTest, StringBuilderWhitespaceRemoval) { diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 2a90f267f185..b796eb07f076 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -116,11 +116,11 @@ bool Reference::Equals(const Value* value) const { } bool Reference::Flatten(android::Res_value* out_value) const { - if (name && name.value().type == ResourceType::kMacro) { + if (name && name.value().type.type == ResourceType::kMacro) { return false; } - const ResourceId resid = id.value_or_default(ResourceId(0)); + const ResourceId resid = id.value_or(ResourceId(0)); const bool dynamic = resid.is_valid() && is_dynamic; if (reference_type == Reference::Type::kResource) { @@ -192,7 +192,7 @@ static void PrettyPrintReferenceImpl(const Reference& ref, bool print_package, P if (print_package) { printer->Print(name.to_string()); } else { - printer->Print(to_string(name.type)); + printer->Print(name.type.to_string()); printer->Print("/"); printer->Print(name.entry); } @@ -1040,7 +1040,7 @@ void Macro::Print(std::ostream* out) const { } bool operator<(const Reference& a, const Reference& b) { - int cmp = a.name.value_or_default({}).compare(b.name.value_or_default({})); + int cmp = a.name.value_or(ResourceName{}).compare(b.name.value_or(ResourceName{})); if (cmp != 0) return cmp < 0; return a.id < b.id; } diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index d903b7e1b8b3..1694d6b6fe4a 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -31,7 +31,6 @@ #include "ValueTransformer.h" #include "io/File.h" #include "text/Printer.h" -#include "util/Maybe.h" namespace aapt { @@ -159,8 +158,8 @@ struct Reference : public TransformableItem<Reference, BaseItem<Reference>> { kAttribute, }; - Maybe<ResourceName> name; - Maybe<ResourceId> id; + std::optional<ResourceName> name; + std::optional<ResourceId> id; std::optional<uint32_t> type_flags; Reference::Type reference_type; bool private_reference = false; @@ -327,7 +326,7 @@ struct Style : public TransformableValue<Style, BaseValue<Style>> { friend std::ostream& operator<<(std::ostream& out, const Entry& entry); }; - Maybe<Reference> parent; + std::optional<Reference> parent; // If set to true, the parent was auto inferred from the style's name. bool parent_inferred = false; diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp index c557f3c77654..2c55d1d548db 100644 --- a/tools/aapt2/Resource_test.cpp +++ b/tools/aapt2/Resource_test.cpp @@ -18,6 +18,9 @@ #include "test/Test.h" +using ::testing::Eq; +using ::testing::Optional; + namespace aapt { TEST(ResourceTypeTest, ParseResourceTypes) { @@ -125,4 +128,104 @@ TEST(ResourceTypeTest, ParseResourceTypes) { EXPECT_EQ(type, nullptr); } +TEST(ResourceTypeTest, ParseResourceNamedType) { + auto type = ParseResourceNamedType("anim"); + EXPECT_THAT(type, Optional(Eq(ResourceNamedType("anim", ResourceType::kAnim)))); + + 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:another"); + EXPECT_THAT(type, Optional(Eq(ResourceNamedType("layout:another", ResourceType::kLayout)))); + + type = ParseResourceNamedType("layout:"); + EXPECT_THAT(type, Eq(std::nullopt)); + + type = ParseResourceNamedType("layout2"); + EXPECT_THAT(type, Eq(std::nullopt)); + + type = ParseResourceNamedType("blahaha"); + EXPECT_THAT(type, Eq(std::nullopt)); +} + +TEST(ResourceTypeTest, ResourceNamedTypeWithDefaultName) { + auto type = ResourceNamedTypeWithDefaultName(ResourceType::kAnim); + EXPECT_THAT(type, Eq(ResourceNamedType("anim", ResourceType::kAnim))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kAnimator); + EXPECT_THAT(type, Eq(ResourceNamedType("animator", ResourceType::kAnimator))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kArray); + EXPECT_THAT(type, Eq(ResourceNamedType("array", ResourceType::kArray))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kAttr); + EXPECT_THAT(type, Eq(ResourceNamedType("attr", ResourceType::kAttr))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kAttrPrivate); + EXPECT_THAT(type, Eq(ResourceNamedType("^attr-private", ResourceType::kAttrPrivate))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kBool); + EXPECT_THAT(type, Eq(ResourceNamedType("bool", ResourceType::kBool))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kColor); + EXPECT_THAT(type, Eq(ResourceNamedType("color", ResourceType::kColor))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kConfigVarying); + EXPECT_THAT(type, Eq(ResourceNamedType("configVarying", ResourceType::kConfigVarying))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kDimen); + EXPECT_THAT(type, Eq(ResourceNamedType("dimen", ResourceType::kDimen))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kDrawable); + EXPECT_THAT(type, Eq(ResourceNamedType("drawable", ResourceType::kDrawable))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kFont); + EXPECT_THAT(type, Eq(ResourceNamedType("font", ResourceType::kFont))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kFraction); + EXPECT_THAT(type, Eq(ResourceNamedType("fraction", ResourceType::kFraction))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kId); + EXPECT_THAT(type, Eq(ResourceNamedType("id", ResourceType::kId))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kInteger); + EXPECT_THAT(type, Eq(ResourceNamedType("integer", ResourceType::kInteger))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kInterpolator); + EXPECT_THAT(type, Eq(ResourceNamedType("interpolator", ResourceType::kInterpolator))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kLayout); + EXPECT_THAT(type, Eq(ResourceNamedType("layout", ResourceType::kLayout))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kMenu); + EXPECT_THAT(type, Eq(ResourceNamedType("menu", ResourceType::kMenu))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kMipmap); + EXPECT_THAT(type, Eq(ResourceNamedType("mipmap", ResourceType::kMipmap))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kNavigation); + EXPECT_THAT(type, Eq(ResourceNamedType("navigation", ResourceType::kNavigation))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kPlurals); + EXPECT_THAT(type, Eq(ResourceNamedType("plurals", ResourceType::kPlurals))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kRaw); + EXPECT_THAT(type, Eq(ResourceNamedType("raw", ResourceType::kRaw))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kString); + EXPECT_THAT(type, Eq(ResourceNamedType("string", ResourceType::kString))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kStyle); + EXPECT_THAT(type, Eq(ResourceNamedType("style", ResourceType::kStyle))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kTransition); + EXPECT_THAT(type, Eq(ResourceNamedType("transition", ResourceType::kTransition))); + + type = ResourceNamedTypeWithDefaultName(ResourceType::kXml); + EXPECT_THAT(type, Eq(ResourceNamedType("xml", ResourceType::kXml))); +} + } // namespace aapt diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index f8e448f0f676..34e8edb0a47f 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -26,9 +26,8 @@ using android::StringPiece; namespace aapt { static ApiVersion sDevelopmentSdkLevel = 10000; -static const auto sDevelopmentSdkCodeNames = std::unordered_set<StringPiece>({ - "Q", "R", "S", "Sv2" -}); +static const auto sDevelopmentSdkCodeNames = + std::unordered_set<StringPiece>({"Q", "R", "S", "Sv2", "Tiramisu", "UpsideDownCake"}); static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = { {0x021c, 1}, @@ -78,9 +77,10 @@ ApiVersion FindAttributeSdkLevel(const ResourceId& id) { return iter->second; } -Maybe<ApiVersion> GetDevelopmentSdkCodeNameVersion(const StringPiece& code_name) { +std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const StringPiece& code_name) { return (sDevelopmentSdkCodeNames.find(code_name) == sDevelopmentSdkCodeNames.end()) - ? Maybe<ApiVersion>() : sDevelopmentSdkLevel; + ? std::optional<ApiVersion>() + : sDevelopmentSdkLevel; } } // namespace aapt diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index 0f27ac50b3bf..0bd61c04f2b2 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -58,10 +58,12 @@ enum : ApiVersion { SDK_R = 30, SDK_S = 31, SDK_S_V2 = 32, + SDK_TIRAMISU = 33, + SDK_CUR_DEVELOPMENT = 10000, }; ApiVersion FindAttributeSdkLevel(const ResourceId& id); -Maybe<ApiVersion> GetDevelopmentSdkCodeNameVersion(const android::StringPiece& code_name); +std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const android::StringPiece& code_name); } // namespace aapt diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h index 92934c343960..4f9369a5d517 100644 --- a/tools/aapt2/Source.h +++ b/tools/aapt2/Source.h @@ -17,21 +17,20 @@ #ifndef AAPT_SOURCE_H #define AAPT_SOURCE_H +#include <optional> #include <ostream> #include <string> #include "android-base/stringprintf.h" #include "androidfw/StringPiece.h" -#include "util/Maybe.h" - namespace aapt { // Represents a file on disk. Used for logging and showing errors. struct Source { std::string path; - Maybe<size_t> line; - Maybe<std::string> archive; + std::optional<size_t> line; + std::optional<std::string> archive; Source() = default; diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp index 919b4c98fa8f..b1452fad0e8f 100644 --- a/tools/aapt2/cmd/Command.cpp +++ b/tools/aapt2/cmd/Command.cpp @@ -72,7 +72,7 @@ void Command::AddRequiredFlagList(const StringPiece& name, const StringPiece& de } void Command::AddOptionalFlag(const StringPiece& name, const StringPiece& description, - Maybe<std::string>* value, uint32_t flags) { + 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(); return true; diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h index d21571d530d2..8678cda59856 100644 --- a/tools/aapt2/cmd/Command.h +++ b/tools/aapt2/cmd/Command.h @@ -18,6 +18,7 @@ #define AAPT_COMMAND_H #include <functional> +#include <optional> #include <ostream> #include <string> #include <unordered_set> @@ -25,19 +26,20 @@ #include "androidfw/StringPiece.h" -#include "util/Maybe.h" - namespace aapt { class Command { public: - explicit Command(const android::StringPiece& name) : name_(name.to_string()), - short_name_(""), - full_subcommand_name_(name.to_string()) {} + explicit Command(const android::StringPiece& name) + : name_(name.to_string()), full_subcommand_name_(name.to_string()){}; 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()) {} + : name_(name.to_string()), + short_name_(short_name.to_string()), + full_subcommand_name_(name.to_string()){}; + + Command(Command&&) = default; + Command& operator=(Command&&) = default; virtual ~Command() = default; @@ -58,7 +60,7 @@ class Command { uint32_t flags = 0); void AddOptionalFlag(const android::StringPiece& name, const android::StringPiece& description, - Maybe<std::string>* value, uint32_t flags = 0); + std::optional<std::string>* value, uint32_t flags = 0); void AddOptionalFlagList(const android::StringPiece& name, const android::StringPiece& description, std::vector<std::string>* value, @@ -87,8 +89,6 @@ class Command { virtual int Action(const std::vector<std::string>& args) = 0; private: - DISALLOW_COPY_AND_ASSIGN(Command); - struct Flag { explicit Flag(const android::StringPiece& name, const android::StringPiece& description, const bool is_required, const size_t num_args, @@ -104,8 +104,8 @@ class Command { bool found = false; }; - const std::string name_; - const std::string short_name_; + std::string name_; + std::string short_name_; std::string description_ = ""; std::string full_subcommand_name_; diff --git a/tools/aapt2/cmd/Command_test.cpp b/tools/aapt2/cmd/Command_test.cpp index 65608fdf64a7..7aa1aa017f7b 100644 --- a/tools/aapt2/cmd/Command_test.cpp +++ b/tools/aapt2/cmd/Command_test.cpp @@ -38,7 +38,7 @@ TEST(CommandTest, LongFullyQualifiedPathWindows) { TestCommand command; std::string required_flag; command.AddRequiredFlag("--rflag", "", &required_flag, Command::kPath); - Maybe<std::string> optional_flag; + std::optional<std::string> optional_flag; command.AddOptionalFlag("--oflag", "", &optional_flag, Command::kPath); std::vector<std::string> required_flag_list; command.AddRequiredFlagList("--rlflag", "", &required_flag_list, Command::kPath); diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index cd5015e81203..fe560180bd48 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -47,7 +47,6 @@ #include "io/ZipArchive.h" #include "trace/TraceBuffer.h" #include "util/Files.h" -#include "util/Maybe.h" #include "util/Util.h" #include "xml/XmlDom.h" #include "xml/XmlPullParser.h" @@ -75,10 +74,10 @@ struct ResourcePathData { }; // Resource file paths are expected to look like: [--/res/]type[-config]/name -static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, - const char dir_sep, - std::string* out_error, - const CompileOptions& options) { +static std::optional<ResourcePathData> ExtractResourcePathData(const std::string& path, + const char dir_sep, + std::string* out_error, + const CompileOptions& options) { std::vector<std::string> parts = util::Split(path, dir_sep); if (parts.size() < 2) { if (out_error) *out_error = "bad resource path"; @@ -337,7 +336,7 @@ static bool IsValidFile(IAaptContext* context, const std::string& input_path) { if (file_type == file::FileType::kDirectory) { context->GetDiagnostics()->Error(DiagMessage(input_path) << "resource file cannot be a directory"); - } else if (file_type == file::FileType::kNonexistant) { + } else if (file_type == file::FileType::kNonExistant) { context->GetDiagnostics()->Error(DiagMessage(input_path) << "file not found"); } else { context->GetDiagnostics()->Error(DiagMessage(input_path) diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h index 1bc1f6651f85..bd2e3d72a551 100644 --- a/tools/aapt2/cmd/Compile.h +++ b/tools/aapt2/cmd/Compile.h @@ -17,7 +17,10 @@ #ifndef AAPT2_COMPILE_H #define AAPT2_COMPILE_H -#include "androidfw/StringPiece.h" +#include <optional> + +#include <androidfw/StringPiece.h> + #include "format/Archive.h" #include "process/IResourceTableConsumer.h" #include "Command.h" @@ -28,11 +31,11 @@ namespace aapt { struct CompileOptions { std::string output_path; - Maybe<std::string> source_path; - Maybe<std::string> res_dir; - Maybe<std::string> res_zip; - Maybe<std::string> generate_text_symbols_path; - Maybe<Visibility::Level> visibility; + std::optional<std::string> source_path; + std::optional<std::string> res_dir; + std::optional<std::string> res_zip; + std::optional<std::string> generate_text_symbols_path; + std::optional<Visibility::Level> visibility; bool pseudolocalize = false; bool no_png_crunch = false; bool legacy_mode = false; @@ -80,8 +83,8 @@ class CompileCommand : public Command { private: IDiagnostics* diagnostic_; CompileOptions options_; - Maybe<std::string> visibility_; - Maybe<std::string> trace_folder_; + std::optional<std::string> visibility_; + std::optional<std::string> trace_folder_; }; int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* output_writer, diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index 89757134f3a7..fbfbf68b30bb 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -19,12 +19,11 @@ #include "android-base/file.h" #include "android-base/stringprintf.h" #include "android-base/utf8.h" - +#include "format/proto/ProtoDeserialize.h" #include "io/StringStream.h" #include "io/ZipArchive.h" #include "java/AnnotationProcessor.h" #include "test/Test.h" -#include "format/proto/ProtoDeserialize.h" namespace aapt { @@ -59,55 +58,56 @@ TEST_F(CompilerTest, MultiplePeriods) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests", "CompileTest", "res"}); + const std::string kOutDir = testing::TempDir(); // Resource files without periods in the file name should not throw errors const std::string path0 = BuildPath({kResDir, "values", "values.xml"}); - const std::string path0_out = BuildPath({kResDir, "values_values.arsc.flat"}); + const std::string path0_out = BuildPath({kOutDir, "values_values.arsc.flat"}); ::android::base::utf8::unlink(path0_out.c_str()); - ASSERT_EQ(TestCompile(path0, kResDir, /** legacy */ false, diag), 0); + ASSERT_EQ(TestCompile(path0, kOutDir, /** legacy */ false, diag), 0); ASSERT_EQ(::android::base::utf8::unlink(path0_out.c_str()), 0); - ASSERT_EQ(TestCompile(path0, kResDir, /** legacy */ true, diag), 0); + ASSERT_EQ(TestCompile(path0, kOutDir, /** legacy */ true, diag), 0); ASSERT_EQ(::android::base::utf8::unlink(path0_out.c_str()), 0); const std::string path1 = BuildPath({kResDir, "drawable", "image.png"}); - const std::string path1_out = BuildPath({kResDir, "drawable_image.png.flat"}); + const std::string path1_out = BuildPath({kOutDir, "drawable_image.png.flat"}); ::android::base::utf8::unlink(path1_out.c_str()); - ASSERT_EQ(TestCompile(path1, kResDir, /** legacy */ false, diag), 0); + ASSERT_EQ(TestCompile(path1, kOutDir, /** legacy */ false, diag), 0); ASSERT_EQ(::android::base::utf8::unlink(path1_out.c_str()), 0); - ASSERT_EQ(TestCompile(path1, kResDir, /** legacy */ true, diag), 0); + ASSERT_EQ(TestCompile(path1, kOutDir, /** legacy */ true, diag), 0); ASSERT_EQ(::android::base::utf8::unlink(path1_out.c_str()), 0); const std::string path2 = BuildPath({kResDir, "drawable", "image.9.png"}); - const std::string path2_out = BuildPath({kResDir, "drawable_image.9.png.flat"}); + const std::string path2_out = BuildPath({kOutDir, "drawable_image.9.png.flat"}); ::android::base::utf8::unlink(path2_out.c_str()); - ASSERT_EQ(TestCompile(path2, kResDir, /** legacy */ false, diag), 0); + ASSERT_EQ(TestCompile(path2, kOutDir, /** legacy */ false, diag), 0); ASSERT_EQ(::android::base::utf8::unlink(path2_out.c_str()), 0); - ASSERT_EQ(TestCompile(path2, kResDir, /** legacy */ true, diag), 0); + ASSERT_EQ(TestCompile(path2, kOutDir, /** legacy */ true, diag), 0); ASSERT_EQ(::android::base::utf8::unlink(path2_out.c_str()), 0); // Resource files with periods in the file name should fail on non-legacy compilations const std::string path3 = BuildPath({kResDir, "values", "values.all.xml"}); - const std::string path3_out = BuildPath({kResDir, "values_values.all.arsc.flat"}); + const std::string path3_out = BuildPath({kOutDir, "values_values.all.arsc.flat"}); ::android::base::utf8::unlink(path3_out.c_str()); - ASSERT_NE(TestCompile(path3, kResDir, /** legacy */ false, diag), 0); + ASSERT_NE(TestCompile(path3, kOutDir, /** legacy */ false, diag), 0); ASSERT_NE(::android::base::utf8::unlink(path3_out.c_str()), 0); - ASSERT_EQ(TestCompile(path3, kResDir, /** legacy */ true, diag), 0); + ASSERT_EQ(TestCompile(path3, kOutDir, /** legacy */ true, diag), 0); ASSERT_EQ(::android::base::utf8::unlink(path3_out.c_str()), 0); const std::string path4 = BuildPath({kResDir, "drawable", "image.small.png"}); - const std::string path4_out = BuildPath({kResDir, "drawable_image.small.png.flat"}); + const std::string path4_out = BuildPath({kOutDir, "drawable_image.small.png.flat"}); ::android::base::utf8::unlink(path4_out.c_str()); - ASSERT_NE(TestCompile(path4, kResDir, /** legacy */ false, diag), 0); + ASSERT_NE(TestCompile(path4, kOutDir, /** legacy */ false, diag), 0); ASSERT_NE(::android::base::utf8::unlink(path4_out.c_str()), 0); - ASSERT_EQ(TestCompile(path4, kResDir, /** legacy */ true, diag), 0); + ASSERT_EQ(TestCompile(path4, kOutDir, /** legacy */ true, diag), 0); ASSERT_EQ(::android::base::utf8::unlink(path4_out.c_str()), 0); const std::string path5 = BuildPath({kResDir, "drawable", "image.small.9.png"}); - const std::string path5_out = BuildPath({kResDir, "drawable_image.small.9.png.flat"}); + const std::string path5_out = BuildPath({kOutDir, "drawable_image.small.9.png.flat"}); ::android::base::utf8::unlink(path5_out.c_str()); - ASSERT_NE(TestCompile(path5, kResDir, /** legacy */ false, diag), 0); + ASSERT_NE(TestCompile(path5, kOutDir, /** legacy */ false, diag), 0); ASSERT_NE(::android::base::utf8::unlink(path5_out.c_str()), 0); - ASSERT_EQ(TestCompile(path5, kResDir, /** legacy */ true, diag), 0); + ASSERT_EQ(TestCompile(path5, kOutDir, /** legacy */ true, diag), 0); ASSERT_EQ(::android::base::utf8::unlink(path5_out.c_str()), 0); } @@ -116,9 +116,7 @@ TEST_F(CompilerTest, DirInput) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const std::string kResDir = BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests", "CompileTest", "DirInput", "res"}); - const std::string kOutputFlata = - BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests", - "CompileTest", "DirInput", "compiled.flata"}); + const std::string kOutputFlata = BuildPath({testing::TempDir(), "compiled.flata"}); ::android::base::utf8::unlink(kOutputFlata.c_str()); std::vector<android::StringPiece> args; @@ -147,9 +145,7 @@ TEST_F(CompilerTest, ZipInput) { const std::string kResZip = BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests", "CompileTest", "ZipInput", "res.zip"}); - const std::string kOutputFlata = - BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests", - "CompileTest", "ZipInput", "compiled.flata"}); + const std::string kOutputFlata = BuildPath({testing::TempDir(), "compiled.flata"}); ::android::base::utf8::unlink(kOutputFlata.c_str()); @@ -257,9 +253,9 @@ TEST_F(CompilerTest, DoNotTranslateTest) { TEST_F(CompilerTest, RelativePathTest) { StdErrDiagnostics diag; - const std::string res_path = BuildPath( - {android::base::Dirname(android::base::GetExecutablePath()), - "integration-tests", "CompileTest", "res"}); + const std::string res_path = + BuildPath({android::base::Dirname(android::base::GetExecutablePath()), "integration-tests", + "CompileTest", "res"}); const std::string path_values_colors = GetTestPath("values/colors.xml"); WriteFile(path_values_colors, "<resources>" @@ -272,9 +268,8 @@ TEST_F(CompilerTest, RelativePathTest) { "<TextBox android:id=\"@+id/text_one\" android:background=\"@color/color_one\"/>" "</LinearLayout>"); - const std::string compiled_files_dir = BuildPath( - {android::base::Dirname(android::base::GetExecutablePath()), - "integration-tests", "CompileTest", "compiled"}); + const std::string compiled_files_dir = + BuildPath({testing::TempDir(), "integration-tests", "CompileTest", "compiled"}); CHECK(file::mkdirs(compiled_files_dir.data())); const std::string path_values_colors_out = @@ -283,9 +278,8 @@ TEST_F(CompilerTest, RelativePathTest) { BuildPath({compiled_files_dir, "layout_layout_one.flat"}); ::android::base::utf8::unlink(path_values_colors_out.c_str()); ::android::base::utf8::unlink(path_layout_layout_one_out.c_str()); - const std::string apk_path = BuildPath( - {android::base::Dirname(android::base::GetExecutablePath()), - "integration-tests", "CompileTest", "out.apk"}); + const std::string apk_path = + BuildPath({testing::TempDir(), "integration-tests", "CompileTest", "out.apk"}); const std::string source_set_res = BuildPath({"main", "res"}); const std::string relative_path_values_colors = diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp index 22bcd8589ce9..3b097e09e09d 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -367,8 +367,7 @@ int ConvertCommand::Action(const std::vector<std::string>& args) { return 1; } - Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), - context.GetDiagnostics()); + auto app_info = ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics()); if (!app_info) { return 1; } diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h index 7e2029dfc4d2..2cdb0c87310f 100644 --- a/tools/aapt2/cmd/Convert.h +++ b/tools/aapt2/cmd/Convert.h @@ -17,6 +17,8 @@ #ifndef AAPT2_CONVERT_H #define AAPT2_CONVERT_H +#include <optional> + #include "Command.h" #include "LoadedApk.h" #include "format/binary/TableFlattener.h" @@ -52,7 +54,7 @@ class ConvertCommand : public Command { TableFlattenerOptions table_flattener_options_; XmlFlattenerOptions xml_flattener_options_; std::string output_path_; - Maybe<std::string> output_format_; + std::optional<std::string> output_format_; bool verbose_ = false; }; diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp index 3950f337b575..d9e8c921dbc5 100644 --- a/tools/aapt2/cmd/Diff.cpp +++ b/tools/aapt2/cmd/Diff.cpp @@ -87,8 +87,8 @@ static bool IsSymbolVisibilityDifferent(const Visibility& vis_a, const Visibilit } template <typename Id> -static bool IsIdDiff(const Visibility::Level& level_a, const Maybe<Id>& id_a, - const Visibility::Level& level_b, const Maybe<Id>& id_b) { +static bool IsIdDiff(const Visibility::Level& level_a, const std::optional<Id>& id_a, + const Visibility::Level& level_b, const std::optional<Id>& id_b) { if (level_a == Visibility::Level::kPublic || level_b == Visibility::Level::kPublic) { return id_a != id_b; } diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp index 3982d12f6036..bcce3e5a800f 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -257,12 +257,11 @@ int DumpConfigsCommand::Dump(LoadedApk* apk) { } int DumpPackageNameCommand::Dump(LoadedApk* apk) { - Maybe<std::string> package_name = GetPackageName(apk); - if (!package_name) { + auto package_name = GetPackageName(apk); + if (!package_name.has_value()) { return 1; } - - GetPrinter()->Println(package_name.value()); + GetPrinter()->Println(*package_name); return 0; } @@ -283,12 +282,12 @@ int DumpStringsCommand::Dump(LoadedApk* apk) { } int DumpStyleParentCommand::Dump(LoadedApk* apk) { - Maybe<std::string> package_name = GetPackageName(apk); - if (!package_name) { + auto package_name = GetPackageName(apk); + if (!package_name.has_value()) { return 1; } - const auto target_style = ResourceName(package_name.value(), ResourceType::kStyle, style_); + const auto target_style = ResourceName(*package_name, ResourceType::kStyle, style_); const auto table = apk->GetResourceTable(); if (!table) { @@ -296,7 +295,7 @@ int DumpStyleParentCommand::Dump(LoadedApk* apk) { return 1; } - Maybe<ResourceTable::SearchResult> target = table->FindResource(target_style); + std::optional<ResourceTable::SearchResult> target = table->FindResource(target_style); if (!target) { GetDiagnostics()->Error( DiagMessage() << "Target style \"" << target_style.entry << "\" does not exist"); @@ -561,4 +560,21 @@ const char DumpBadgerCommand::kBadgerData[2925] = { 32, 32, 32, 32, 32, 32, 32, 32, 32, 46, 32, 32, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 10}; +int DumpChunks::Dump(LoadedApk* apk) { + auto file = apk->GetFileCollection()->FindFile("resources.arsc"); + if (!file) { + GetDiagnostics()->Error(DiagMessage() << "Failed to find resources.arsc in APK"); + return 1; + } + + auto data = file->OpenAsData(); + if (!data) { + GetDiagnostics()->Error(DiagMessage() << "Failed to open resources.arsc "); + return 1; + } + + Debug::DumpChunks(data->data(), data->size(), GetPrinter(), GetDiagnostics()); + return 0; +} + } // namespace aapt diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h index cd51f7a7718c..ec320ecd2a32 100644 --- a/tools/aapt2/cmd/Dump.h +++ b/tools/aapt2/cmd/Dump.h @@ -17,6 +17,9 @@ #ifndef AAPT2_DUMP_H #define AAPT2_DUMP_H +#include <io/FileStream.h> +#include <io/ZipArchive.h> + #include "Command.h" #include "Debug.h" #include "LoadedApk.h" @@ -43,17 +46,17 @@ class DumpApkCommand : public Command { return diag_; } - Maybe<std::string> GetPackageName(LoadedApk* apk) { + std::optional<std::string> GetPackageName(LoadedApk* apk) { xml::Element* manifest_el = apk->GetManifest()->root.get(); if (!manifest_el) { GetDiagnostics()->Error(DiagMessage() << "No AndroidManifest."); - return Maybe<std::string>(); + return {}; } xml::Attribute* attr = manifest_el->FindAttribute({}, "package"); if (!attr) { GetDiagnostics()->Error(DiagMessage() << "No package name."); - return Maybe<std::string>(); + return {}; } return attr->value; } @@ -226,6 +229,16 @@ class DumpXmlStringsCommand : public DumpApkCommand { std::vector<std::string> files_; }; +class DumpChunks : public DumpApkCommand { + public: + DumpChunks(text::Printer* printer, IDiagnostics* diag) : DumpApkCommand("chunks", printer, diag) { + SetDescription("Print the chunk information of the compiled resources.arsc in the APK."); + } + + int Dump(LoadedApk* apk) override; +}; + +/** Prints the tree of a compiled xml in an APK. */ class DumpXmlTreeCommand : public DumpApkCommand { public: explicit DumpXmlTreeCommand(text::Printer* printer, IDiagnostics* diag) @@ -263,6 +276,7 @@ class DumpCommand : public Command { AddOptionalSubcommand(util::make_unique<DumpStringsCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpStyleParentCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpTableCommand>(printer, diag_)); + AddOptionalSubcommand(util::make_unique<DumpChunks>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpOverlayableCommand>(printer, diag_)); diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index e4d0f3b6bd23..790f2b34c58b 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -207,7 +207,7 @@ class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate { } // Check to see if this is an 'id' with the target package. - if (name.type == ResourceType::kId && symbol->id) { + if (name.type.type == ResourceType::kId && symbol->id) { ResourceId* id = &symbol->id.value(); if (id->package_id() > kAppPackageId) { // Rewrite the resource ID to be compatible pre-O. @@ -299,7 +299,7 @@ struct ResourceFileFlattenerOptions { bool do_not_fail_on_missing_resources = false; OutputFormat output_format = OutputFormat::kApk; std::unordered_set<std::string> extensions_to_not_compress; - Maybe<std::regex> regex_to_not_compress; + std::optional<std::regex> regex_to_not_compress; }; // A sampling of public framework resource IDs. @@ -741,7 +741,7 @@ static bool LoadStableIdMap(IDiagnostics* diag, const std::string& path, const size_t res_id_str_len = line.size() - res_id_start_idx; StringPiece res_id_str = util::TrimWhitespace(line.substr(res_id_start_idx, res_id_str_len)); - Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(res_id_str); + 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 << "'"); @@ -793,7 +793,7 @@ class Linker { if (!options_.manifest_fixer_options.compile_sdk_version) { xml::Attribute* attr = manifest_xml->root->FindAttribute(xml::kSchemaAndroid, "versionCode"); if (attr != nullptr) { - Maybe<std::string>& compile_sdk_version = options_.manifest_fixer_options.compile_sdk_version; + auto& compile_sdk_version = options_.manifest_fixer_options.compile_sdk_version; if (BinaryPrimitive* prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get())) { switch (prim->value.dataType) { case Res_value::TYPE_INT_DEC: @@ -816,7 +816,7 @@ class Linker { if (!options_.manifest_fixer_options.compile_sdk_version_codename) { xml::Attribute* attr = manifest_xml->root->FindAttribute(xml::kSchemaAndroid, "versionName"); if (attr != nullptr) { - Maybe<std::string>& compile_sdk_version_codename = + std::optional<std::string>& compile_sdk_version_codename = options_.manifest_fixer_options.compile_sdk_version_codename; if (String* str = ValueCast<String>(attr->compiled_value.get())) { compile_sdk_version_codename = *str->value; @@ -912,7 +912,7 @@ class Linker { return true; } - Maybe<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res, IDiagnostics* diag) { + std::optional<AppInfo> ExtractAppInfoFromManifest(xml::XmlResource* xml_res, IDiagnostics* diag) { TRACE_CALL(); // Make sure the first element is <manifest> with package attribute. xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get()); @@ -937,7 +937,7 @@ class Linker { if (xml::Attribute* version_code_attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) { - Maybe<uint32_t> maybe_code = ResourceUtils::ParseInt(version_code_attr->value); + 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)) << "invalid android:versionCode '" << version_code_attr->value << "'"); @@ -948,7 +948,7 @@ class Linker { if (xml::Attribute* version_code_major_attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor")) { - Maybe<uint32_t> maybe_code = ResourceUtils::ParseInt(version_code_major_attr->value); + 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 '" @@ -960,7 +960,7 @@ class Linker { if (xml::Attribute* revision_code_attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) { - Maybe<uint32_t> maybe_code = ResourceUtils::ParseInt(revision_code_attr->value); + 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)) << "invalid android:revisionCode '" << revision_code_attr->value << "'"); @@ -1094,7 +1094,7 @@ class Linker { bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate, const StringPiece& out_package, const JavaClassGeneratorOptions& java_options, - const Maybe<std::string>& out_text_symbols_path = {}) { + const std::optional<std::string>& out_text_symbols_path = {}) { if (!options_.generate_java_class_path && !out_text_symbols_path) { return true; } @@ -1251,7 +1251,7 @@ class Linker { } const std::string package_utf8 = - options_.custom_java_package.value_or_default(context_->GetCompilationPackage()); + options_.custom_java_package.value_or(context_->GetCompilationPackage()); std::string out_path = options_.generate_java_class_path.value(); file::AppendPath(&out_path, file::PackageToPath(package_utf8)); @@ -1283,7 +1283,7 @@ class Linker { return true; } - bool WriteProguardFile(const Maybe<std::string>& out, const proguard::KeepSet& keep_set) { + bool WriteProguardFile(const std::optional<std::string>& out, const proguard::KeepSet& keep_set) { TRACE_CALL(); if (!out) { return true; @@ -1374,7 +1374,7 @@ class Linker { res_name.package = context_->GetCompilationPackage(); } - Maybe<ResourceName> mangled_name = context_->GetNameMangler()->MangleName(res_name); + std::optional<ResourceName> mangled_name = context_->GetNameMangler()->MangleName(res_name); if (mangled_name) { res_name = mangled_name.value(); } @@ -1550,7 +1550,7 @@ class Linker { bool CopyAssetsDirsToApk(IArchiveWriter* writer) { std::map<std::string, std::unique_ptr<io::RegularFile>> merged_assets; for (const std::string& assets_dir : options_.assets_dirs) { - Maybe<std::vector<std::string>> files = + std::optional<std::vector<std::string>> files = file::FindFiles(assets_dir, context_->GetDiagnostics(), nullptr); if (!files) { return false; @@ -1783,7 +1783,7 @@ class Linker { package_to_rewrite = table->packages.back().get(); std::string new_package_name = StringPrintf("%s.%s", package_to_rewrite->name.c_str(), - app_info_.split_name.value_or_default("feature").c_str()); + app_info_.split_name.value_or("feature").c_str()); if (context_->IsVerbose()) { context_->GetDiagnostics()->Note( @@ -1823,7 +1823,7 @@ class Linker { } // First extract the Package name without modifying it (via --rename-manifest-package). - if (Maybe<AppInfo> maybe_app_info = + if (std::optional<AppInfo> maybe_app_info = ExtractAppInfoFromManifest(manifest_xml.get(), context_->GetDiagnostics())) { const AppInfo& app_info = maybe_app_info.value(); context_->SetCompilationPackage(app_info.package); @@ -1850,14 +1850,14 @@ class Linker { return 1; } - Maybe<AppInfo> maybe_app_info = + std::optional<AppInfo> maybe_app_info = ExtractAppInfoFromManifest(manifest_xml.get(), context_->GetDiagnostics()); if (!maybe_app_info) { return 1; } app_info_ = maybe_app_info.value(); - context_->SetMinSdkVersion(app_info_.min_sdk_version.value_or_default(0)); + context_->SetMinSdkVersion(app_info_.min_sdk_version.value_or(0)); context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()}); context_->SetSplitNameDependencies(app_info_.split_name_dependencies); @@ -2231,7 +2231,7 @@ class Linker { std::map<size_t, std::string> shared_libs_; // The package name of the base application, if it is included. - Maybe<std::string> included_feature_base_; + std::optional<std::string> included_feature_base_; }; int LinkCommand::Action(const std::vector<std::string>& args) { @@ -2315,7 +2315,8 @@ int LinkCommand::Action(const std::vector<std::string>& args) { return 1; } - const Maybe<uint32_t> maybe_package_id_int = ResourceUtils::ParseInt(package_id_.value()); + 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"); @@ -2360,7 +2361,7 @@ int LinkCommand::Action(const std::vector<std::string>& args) { } if (preferred_density_) { - Maybe<uint16_t> density = + std::optional<uint16_t> density = ParseTargetDensityParameter(preferred_density_.value(), context.GetDiagnostics()); if (!density) { return 1; diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index 768b4b2c7bfd..d8c76e297ec5 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -45,21 +45,21 @@ struct LinkOptions { bool auto_add_overlay = false; bool override_styles_instead_of_overlaying = false; OutputFormat output_format = OutputFormat::kApk; - Maybe<std::string> rename_resources_package; + std::optional<std::string> rename_resources_package; // Java/Proguard options. - Maybe<std::string> generate_java_class_path; - Maybe<std::string> custom_java_package; + std::optional<std::string> generate_java_class_path; + std::optional<std::string> custom_java_package; std::set<std::string> extra_java_packages; - Maybe<std::string> generate_text_symbols_path; - Maybe<std::string> generate_proguard_rules_path; - Maybe<std::string> generate_main_dex_proguard_rules_path; + std::optional<std::string> generate_text_symbols_path; + std::optional<std::string> generate_proguard_rules_path; + std::optional<std::string> generate_main_dex_proguard_rules_path; bool generate_conditional_proguard_rules = false; bool generate_minimal_proguard_rules = false; bool generate_non_final_ids = false; bool no_proguard_location_reference = false; std::vector<std::string> javadoc_annotations; - Maybe<std::string> private_symbols; + std::optional<std::string> private_symbols; // Optimizations/features. bool no_auto_version = false; @@ -70,7 +70,7 @@ struct LinkOptions { bool no_xml_namespaces = false; bool do_not_compress_anything = false; std::unordered_set<std::string> extensions_to_not_compress; - Maybe<std::regex> regex_to_not_compress; + std::optional<std::regex> regex_to_not_compress; // Static lib options. bool no_static_lib_packages = false; @@ -97,7 +97,7 @@ struct LinkOptions { // Stable ID options. std::unordered_map<ResourceName, ResourceId> stable_id_map; - Maybe<std::string> resource_id_map_path; + std::optional<std::string> resource_id_map_path; // When 'true', allow reserved package IDs to be used for applications. Pre-O, the platform // treats negative resource IDs [those with a package ID of 0x80 or higher] as invalid. @@ -321,20 +321,20 @@ class LinkCommand : public Command { std::vector<std::string> overlay_arg_list_; std::vector<std::string> extra_java_packages_; - Maybe<std::string> package_id_; + std::optional<std::string> package_id_; std::vector<std::string> configs_; - Maybe<std::string> preferred_density_; - Maybe<std::string> product_list_; - Maybe<std::string> no_compress_regex; + std::optional<std::string> preferred_density_; + std::optional<std::string> product_list_; + std::optional<std::string> no_compress_regex; bool legacy_x_flag_ = false; bool require_localization_ = false; bool verbose_ = false; bool shared_lib_ = false; bool static_lib_ = false; bool proto_format_ = false; - Maybe<std::string> stable_id_file_path_; + std::optional<std::string> stable_id_file_path_; std::vector<std::string> split_args_; - Maybe<std::string> trace_folder_; + std::optional<std::string> trace_folder_; }; }// namespace aapt diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 5b18a3789d76..caa3e60d6af1 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -354,7 +354,7 @@ bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk, return false; } - Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics()); + auto app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics()); if (!app_info) { context->GetDiagnostics()->Error(DiagMessage() << "failed to extract data from AndroidManifest.xml"); @@ -362,7 +362,7 @@ bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk, } out_options->app_info = std::move(app_info.value()); - context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0)); + context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or(0)); return true; } @@ -380,7 +380,7 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { if (config_path_) { std::string& path = config_path_.value(); - Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path); + std::optional<ConfigurationParser> for_path = ConfigurationParser::ForPath(path); if (for_path) { options_.apk_artifacts = for_path.value().WithDiagnostics(diag).Parse(apk_path); if (!options_.apk_artifacts) { @@ -427,7 +427,7 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { if (target_densities_) { // Parse the target screen densities. for (const StringPiece& config_str : util::Tokenize(target_densities_.value(), ',')) { - Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag); + 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 3afc46b04af6..ff63e8dd76d4 100644 --- a/tools/aapt2/cmd/Optimize.h +++ b/tools/aapt2/cmd/Optimize.h @@ -29,9 +29,9 @@ struct OptimizeOptions { friend class OptimizeCommand; // Path to the output APK. - Maybe<std::string> output_path; + std::optional<std::string> output_path; // Path to the output APK directory for splits. - Maybe<std::string> output_dir; + std::optional<std::string> output_dir; // Details of the app extracted from the AndroidManifest.xml AppInfo app_info; @@ -50,7 +50,7 @@ struct OptimizeOptions { TableFlattenerOptions table_flattener_options; - Maybe<std::vector<aapt::configuration::OutputArtifact>> apk_artifacts; + std::optional<std::vector<aapt::configuration::OutputArtifact>> apk_artifacts; // Set of artifacts to keep when generating multi-APK splits. If the list is empty, all artifacts // are kept and will be written as output. @@ -60,7 +60,7 @@ struct OptimizeOptions { bool shorten_resource_paths = false; // Path to the output map of original resource paths to shortened paths. - Maybe<std::string> shortened_paths_map_path; + std::optional<std::string> shortened_paths_map_path; }; class OptimizeCommand : public Command { @@ -122,9 +122,9 @@ class OptimizeCommand : public Command { bool WriteObfuscatedPathsMap(const std::map<std::string, std::string> &path_map, const std::string &file_path); - Maybe<std::string> config_path_; - Maybe<std::string> resources_config_path_; - Maybe<std::string> target_densities_; + std::optional<std::string> config_path_; + std::optional<std::string> resources_config_path_; + std::optional<std::string> target_densities_; std::vector<std::string> configs_; std::vector<std::string> split_args_; std::unordered_set<std::string> kept_artifacts_; diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index 7214f1a68d2c..3244fb83fa4b 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -21,11 +21,10 @@ #include "android-base/logging.h" #include "androidfw/ConfigDescription.h" #include "androidfw/Locale.h" - #include "ResourceUtils.h" #include "ValueVisitor.h" #include "split/TableSplitter.h" -#include "util/Maybe.h" + #include "util/Util.h" using ::android::ConfigDescription; @@ -35,7 +34,7 @@ using ::android::base::StringPrintf; namespace aapt { -Maybe<uint16_t> ParseTargetDensityParameter(const StringPiece& arg, IDiagnostics* diag) { +std::optional<uint16_t> ParseTargetDensityParameter(const StringPiece& arg, IDiagnostics* diag) { ConfigDescription preferred_density_config; if (!ConfigDescription::Parse(arg, &preferred_density_config)) { diag->Error(DiagMessage() << "invalid density '" << arg << "' for --preferred-density option"); @@ -245,8 +244,8 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, return doc; } -static Maybe<std::string> ExtractCompiledString(const xml::Attribute& attr, - std::string* out_error) { +static std::optional<std::string> ExtractCompiledString(const xml::Attribute& attr, + std::string* out_error) { if (attr.compiled_value != nullptr) { const String* compiled_str = ValueCast<String>(attr.compiled_value.get()); if (compiled_str != nullptr) { @@ -269,7 +268,8 @@ static Maybe<std::string> ExtractCompiledString(const xml::Attribute& attr, return {}; } -static Maybe<uint32_t> ExtractCompiledInt(const xml::Attribute& attr, std::string* out_error) { +static std::optional<uint32_t> ExtractCompiledInt(const xml::Attribute& attr, + std::string* out_error) { if (attr.compiled_value != nullptr) { const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get()); if (compiled_prim != nullptr) { @@ -283,7 +283,7 @@ static Maybe<uint32_t> ExtractCompiledInt(const xml::Attribute& attr, std::strin } // Fallback to the plain text value if there is one. - Maybe<uint32_t> integer = ResourceUtils::ParseInt(attr.value); + std::optional<uint32_t> integer = ResourceUtils::ParseInt(attr.value); if (integer) { return integer; } @@ -293,7 +293,7 @@ static Maybe<uint32_t> ExtractCompiledInt(const xml::Attribute& attr, std::strin return {}; } -static Maybe<int> ExtractSdkVersion(const xml::Attribute& attr, std::string* out_error) { +static std::optional<int> ExtractSdkVersion(const xml::Attribute& attr, std::string* out_error) { if (attr.compiled_value != nullptr) { const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get()); if (compiled_prim != nullptr) { @@ -307,7 +307,7 @@ static Maybe<int> ExtractSdkVersion(const xml::Attribute& attr, std::string* out const String* compiled_str = ValueCast<String>(attr.compiled_value.get()); if (compiled_str != nullptr) { - Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(*compiled_str->value); + std::optional<int> sdk_version = ResourceUtils::ParseSdkVersion(*compiled_str->value); if (sdk_version) { return sdk_version; } @@ -320,7 +320,7 @@ static Maybe<int> ExtractSdkVersion(const xml::Attribute& attr, std::string* out } // Fallback to the plain text value if there is one. - Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(attr.value); + std::optional<int> sdk_version = ResourceUtils::ParseSdkVersion(attr.value); if (sdk_version) { return sdk_version; } @@ -330,8 +330,8 @@ static Maybe<int> ExtractSdkVersion(const xml::Attribute& attr, std::string* out return {}; } -Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, - IDiagnostics* diag) { +std::optional<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, + 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) { @@ -352,7 +352,7 @@ Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, } std::string error_msg; - Maybe<std::string> maybe_package = ExtractCompiledString(*package_attr, &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)) << "invalid package name: " << error_msg); @@ -362,7 +362,7 @@ Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, if (const xml::Attribute* version_code_attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) { - Maybe<uint32_t> maybe_code = ExtractCompiledInt(*version_code_attr, &error_msg); + 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)) << "invalid android:versionCode: " << error_msg); @@ -373,7 +373,7 @@ Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, if (const xml::Attribute* version_code_major_attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCodeMajor")) { - Maybe<uint32_t> maybe_code = ExtractCompiledInt(*version_code_major_attr, &error_msg); + 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); @@ -384,7 +384,7 @@ Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, if (const xml::Attribute* revision_code_attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) { - Maybe<uint32_t> maybe_code = ExtractCompiledInt(*revision_code_attr, &error_msg); + 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)) << "invalid android:revisionCode: " << error_msg); @@ -394,7 +394,8 @@ Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, } if (const xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) { - Maybe<std::string> maybe_split_name = ExtractCompiledString(*split_name_attr, &error_msg); + 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)) << "invalid split name: " << error_msg); @@ -406,7 +407,7 @@ Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, if (const xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) { if (const xml::Attribute* min_sdk = uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) { - Maybe<int> maybe_sdk = ExtractSdkVersion(*min_sdk, &error_msg); + 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)) << "invalid android:minSdkVersion: " << error_msg); diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h index 2a7c62eef678..1b98eb468700 100644 --- a/tools/aapt2/cmd/Util.h +++ b/tools/aapt2/cmd/Util.h @@ -26,14 +26,14 @@ #include "SdkConstants.h" #include "filter/ConfigFilter.h" #include "split/TableSplitter.h" -#include "util/Maybe.h" #include "xml/XmlDom.h" 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. -Maybe<uint16_t> ParseTargetDensityParameter(const android::StringPiece& arg, IDiagnostics* diag); +std::optional<uint16_t> ParseTargetDensityParameter(const android::StringPiece& arg, + 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. @@ -59,8 +59,8 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, const SplitConstraints& constraints); // Extracts relevant info from the AndroidManifest.xml. -Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, - IDiagnostics* diag); +std::optional<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res, + 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. diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp index 339b8af5d536..fa816be43be3 100644 --- a/tools/aapt2/compile/IdAssigner.cpp +++ b/tools/aapt2/compile/IdAssigner.cpp @@ -275,7 +275,7 @@ bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id, return false; } - auto key = ResourceTypeKey{name.type, id.type_id()}; + auto key = ResourceTypeKey{name.type.type, id.type_id()}; auto type = types_.find(key); if (type == types_.end()) { // The type has not been assigned an id yet. Ensure that the specified id is not being used by @@ -291,7 +291,7 @@ bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id, if (!visibility.staged_api) { // Ensure that non-staged resources can only exist in one type ID. - auto non_staged_type = non_staged_type_ids_.emplace(name.type, id.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 @@ -316,14 +316,14 @@ std::optional<ResourceId> IdAssignerContext::NextId(const ResourceName& name, ID CHECK(name.package.empty() || name.package == package_name_); // Find the type id for non-staged resources of this type. - auto non_staged_type = non_staged_type_ids_.find(name.type); + auto non_staged_type = non_staged_type_ids_.find(name.type.type); if (non_staged_type == non_staged_type_ids_.end()) { auto next_type_id = type_id_finder_.NextId(); CHECK(next_type_id.has_value()) << "resource type IDs allocated have exceeded maximum (256)"; - non_staged_type = non_staged_type_ids_.emplace(name.type, *next_type_id).first; + non_staged_type = non_staged_type_ids_.emplace(name.type.type, *next_type_id).first; } - ResourceTypeKey key{name.type, non_staged_type->second}; + ResourceTypeKey key{name.type.type, non_staged_type->second}; auto type = types_.find(key); if (type == types_.end()) { type = types_.emplace(key, TypeGroup(package_id_, key.id)).first; diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp index 663776645990..d3575716ae4f 100644 --- a/tools/aapt2/compile/IdAssigner_test.cpp +++ b/tools/aapt2/compile/IdAssigner_test.cpp @@ -57,18 +57,18 @@ TEST_F(IdAssignerTests, AssignIdsWithReservedIds) { ASSERT_TRUE(assigner.Consume(context.get(), table.get())); ASSERT_TRUE(VerifyIds(table.get())); - Maybe<ResourceTable::SearchResult> maybe_result; + std::optional<ResourceTable::SearchResult> maybe_result; // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX. maybe_result = table->FindResource(test::ParseNameOrDie("android:dimen/two")); ASSERT_TRUE(maybe_result); - EXPECT_EQ(make_value<ResourceId>(0x01020000), maybe_result.value().entry->id); + EXPECT_EQ(0x01020000, maybe_result.value().entry->id); maybe_result = table->FindResource(test::ParseNameOrDie("android:integer/three")); ASSERT_TRUE(maybe_result); - EXPECT_EQ(make_value<ResourceId>(0x01030000), maybe_result.value().entry->id); + EXPECT_EQ(0x01030000, maybe_result.value().entry->id); // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX // IDs. @@ -76,17 +76,17 @@ TEST_F(IdAssignerTests, AssignIdsWithReservedIds) { maybe_result = table->FindResource(test::ParseNameOrDie("android:string/five")); ASSERT_TRUE(maybe_result); - EXPECT_EQ(make_value<ResourceId>(0x01050000), maybe_result.value().entry->id); + EXPECT_EQ(0x01050000, maybe_result.value().entry->id); // Expect to fill in the gaps between 0x01040000 and 0x01040006. maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/bar")); ASSERT_TRUE(maybe_result); - EXPECT_EQ(make_value<ResourceId>(0x01040001), maybe_result.value().entry->id); + EXPECT_EQ(0x01040001, maybe_result.value().entry->id); maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/baz")); ASSERT_TRUE(maybe_result); - EXPECT_EQ(make_value<ResourceId>(0x01040002), maybe_result.value().entry->id); + EXPECT_EQ(0x01040002, maybe_result.value().entry->id); } TEST_F(IdAssignerTests, FailWhenNonUniqueIdsAssigned) { @@ -143,7 +143,7 @@ TEST_F(IdAssignerTests, AssignIdsWithIdMap) { ASSERT_TRUE(result); const ResourceTable::SearchResult& search_result = result.value(); - EXPECT_EQ(make_value<ResourceId>(0x01010002), search_result.entry->id); + EXPECT_EQ(0x01010002, search_result.entry->id); } TEST_F(IdAssignerTests, UseAllEntryIds) { diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp index 79b0933a089f..de1c3bb3dd7e 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser.cpp +++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp @@ -56,7 +56,7 @@ class Visitor : public xml::PackageAwareVisitor { return; } - Maybe<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value); + std::optional<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value); if (!ref) { context_->GetDiagnostics()->Error(DiagMessage(src) << "invalid XML attribute '" << attr->value << "'"); @@ -65,7 +65,7 @@ class Visitor : public xml::PackageAwareVisitor { } const ResourceName& name = ref.value().name.value(); - Maybe<xml::ExtractedPackage> maybe_pkg = TransformPackageAlias(name.package); + std::optional<xml::ExtractedPackage> maybe_pkg = TransformPackageAlias(name.package); if (!maybe_pkg) { context_->GetDiagnostics()->Error(DiagMessage(src) << "invalid namespace prefix '" << name.package << "'"); diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index 3f574ee8e897..2461438c49b6 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -33,7 +33,7 @@ namespace aapt { // The struct that represents both Span objects and UntranslatableSections. struct UnifiedSpan { // Only present for Span objects. If not present, this was an UntranslatableSection. - Maybe<std::string> tag; + std::optional<std::string> tag; // The UTF-16 index into the string where this span starts. uint32_t first_char; diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp index 50541152f802..bb72159f9e77 100644 --- a/tools/aapt2/compile/XmlIdCollector.cpp +++ b/tools/aapt2/compile/XmlIdCollector.cpp @@ -46,7 +46,7 @@ struct IdCollector : public xml::Visitor { ResourceNameRef name; bool create = false; if (ResourceUtils::ParseReference(attr.value, &name, &create, nullptr)) { - if (create && name.type == ResourceType::kId) { + 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"); diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp index dd06b38f6c01..e7a45851e239 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -34,7 +34,6 @@ #include "io/FileSystem.h" #include "io/StringStream.h" #include "util/Files.h" -#include "util/Maybe.h" #include "util/Util.h" #include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" @@ -113,7 +112,7 @@ std::string GetLabel(const Element* element, IDiagnostics* diag) { } /** Returns the value of the version-code-order attribute for a given element. */ -Maybe<int32_t> GetVersionCodeOrder(const Element* element, IDiagnostics* diag) { +std::optional<int32_t> GetVersionCodeOrder(const Element* element, IDiagnostics* diag) { const xml::Attribute* version = element->FindAttribute("", "version-code-order"); if (version == nullptr) { std::string label = GetLabel(element, diag); @@ -135,7 +134,7 @@ class NamespaceVisitor : public xml::Visitor { /** Copies the values referenced in a configuration group to the target list. */ template <typename T> -bool CopyXmlReferences(const Maybe<std::string>& name, const Group<T>& groups, +bool CopyXmlReferences(const std::optional<std::string>& name, const Group<T>& groups, std::vector<T>* target) { // If there was no item configured, there is nothing to do and no error. if (!name) { @@ -159,7 +158,7 @@ bool CopyXmlReferences(const Maybe<std::string>& name, const Group<T>& groups, * 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 Maybe<StringPiece>& value, +bool ReplacePlaceholder(const StringPiece& placeholder, const std::optional<StringPiece>& value, std::string* name, IDiagnostics* diag) { size_t offset = name->find(placeholder.data()); bool found = (offset != std::string::npos); @@ -207,17 +206,17 @@ xml::XmlNodeAction::ActionFuncWithDiag Bind(configuration::PostProcessingConfigu } /** Converts a ConfiguredArtifact into an OutputArtifact. */ -Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact, - const std::string& apk_name, - const PostProcessingConfiguration& config, - IDiagnostics* diag) { +std::optional<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact, + const std::string& apk_name, + const PostProcessingConfiguration& config, + IDiagnostics* diag) { if (!artifact.name && !config.artifact_format) { diag->Error( DiagMessage() << "Artifact does not have a name and no global name template defined"); return {}; } - Maybe<std::string> artifact_name = + std::optional<std::string> artifact_name = (artifact.name) ? artifact.Name(apk_name, diag) : artifact.ToArtifactName(config.artifact_format.value(), apk_name, diag); @@ -287,9 +286,9 @@ Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact, namespace configuration { /** Returns the binary reprasentation of the XML configuration. */ -Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents, - const std::string& config_path, - IDiagnostics* diag) { +std::optional<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents, + const std::string& config_path, + IDiagnostics* diag) { StringInputStream in(contents); std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source(config_path)); if (!doc) { @@ -351,7 +350,8 @@ const StringPiece& AbiToString(Abi abi) { /** * Returns the common artifact base name from a template string. */ -Maybe<std::string> ToBaseName(std::string result, const StringPiece& apk_name, IDiagnostics* diag) { +std::optional<std::string> ToBaseName(std::string result, const StringPiece& apk_name, + IDiagnostics* diag) { const StringPiece ext = file::GetExtension(apk_name); size_t end_index = apk_name.to_string().rfind(ext.to_string()); const std::string base_name = @@ -359,8 +359,8 @@ Maybe<std::string> ToBaseName(std::string result, const StringPiece& apk_name, I // Base name is optional. if (result.find("${basename}") != std::string::npos) { - Maybe<StringPiece> maybe_base_name = - base_name.empty() ? Maybe<StringPiece>{} : Maybe<StringPiece>{base_name}; + auto maybe_base_name = base_name.empty() ? std::nullopt + : std::optional<StringPiece>{base_name}; if (!ReplacePlaceholder("${basename}", maybe_base_name, &result, diag)) { return {}; } @@ -383,10 +383,10 @@ Maybe<std::string> ToBaseName(std::string result, const StringPiece& apk_name, I return result; } -Maybe<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format, - const StringPiece& apk_name, - IDiagnostics* diag) const { - Maybe<std::string> base = ToBaseName(format.to_string(), apk_name, diag); +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); if (!base) { return {}; } @@ -419,7 +419,8 @@ Maybe<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format, return result; } -Maybe<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name, IDiagnostics* diag) const { +std::optional<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name, + IDiagnostics* diag) const { if (!name) { return {}; } @@ -430,7 +431,7 @@ Maybe<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name, IDiagno } // namespace configuration /** Returns a ConfigurationParser for the file located at the provided path. */ -Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) { +std::optional<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) { std::string contents; if (!ReadFileToString(path, &contents, true)) { return {}; @@ -442,9 +443,9 @@ ConfigurationParser::ConfigurationParser(std::string contents, const std::string : contents_(std::move(contents)), config_path_(config_path), diag_(&noop_) { } -Maybe<std::vector<OutputArtifact>> ConfigurationParser::Parse( +std::optional<std::vector<OutputArtifact>> ConfigurationParser::Parse( const android::StringPiece& apk_path) { - Maybe<PostProcessingConfiguration> maybe_config = + std::optional<PostProcessingConfiguration> maybe_config = ExtractConfiguration(contents_, config_path_, diag_); if (!maybe_config) { return {}; @@ -460,7 +461,8 @@ Maybe<std::vector<OutputArtifact>> ConfigurationParser::Parse( int version = 1; for (const ConfiguredArtifact& artifact : config.artifacts) { - Maybe<OutputArtifact> output_artifact = ToOutputArtifact(artifact, apk_name, config, diag_); + std::optional<OutputArtifact> output_artifact = + ToOutputArtifact(artifact, apk_name, config, diag_); if (!output_artifact) { // Defer return an error condition so that all errors are reported. valid = false; @@ -538,7 +540,7 @@ bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_eleme bool valid = true; OrderedEntry<Abi>& entry = config->abi_groups[label]; - Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); + std::optional<int32_t> order = GetVersionCodeOrder(root_element, diag); if (!order) { valid = false; } else { @@ -589,7 +591,7 @@ bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* bool valid = true; OrderedEntry<ConfigDescription>& entry = config->screen_density_groups[label]; - Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); + std::optional<int32_t> order = GetVersionCodeOrder(root_element, diag); if (!order) { valid = false; } else { @@ -656,7 +658,7 @@ bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_el bool valid = true; OrderedEntry<ConfigDescription>& entry = config->locale_groups[label]; - Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); + std::optional<int32_t> order = GetVersionCodeOrder(root_element, diag); if (!order) { valid = false; } else { @@ -724,19 +726,19 @@ bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_ele entry.label = attr.value; valid_attr = true; } else if (attr.name == "minSdkVersion") { - Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value); + std::optional<int> version = ResourceUtils::ParseSdkVersion(attr.value); if (version) { valid_attr = true; entry.min_sdk_version = version.value(); } } else if (attr.name == "targetSdkVersion") { - Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value); + std::optional<int> version = ResourceUtils::ParseSdkVersion(attr.value); if (version) { valid_attr = true; entry.target_sdk_version = version; } } else if (attr.name == "maxSdkVersion") { - Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value); + std::optional<int> version = ResourceUtils::ParseSdkVersion(attr.value); if (version) { valid_attr = true; entry.max_sdk_version = version; @@ -778,7 +780,7 @@ bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root bool valid = true; OrderedEntry<GlTexture>& entry = config->gl_texture_groups[label]; - Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); + std::optional<int32_t> order = GetVersionCodeOrder(root_element, diag); if (!order) { valid = false; } else { @@ -828,7 +830,7 @@ bool DeviceFeatureGroupTagHandler(PostProcessingConfiguration* config, Element* bool valid = true; OrderedEntry<DeviceFeature>& entry = config->device_feature_groups[label]; - Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); + std::optional<int32_t> order = GetVersionCodeOrder(root_element, diag); if (!order) { valid = false; } else { diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h index b9e3be9393c9..195b4baac319 100644 --- a/tools/aapt2/configuration/ConfigurationParser.h +++ b/tools/aapt2/configuration/ConfigurationParser.h @@ -17,6 +17,7 @@ #ifndef AAPT2_CONFIGURATION_H #define AAPT2_CONFIGURATION_H +#include <optional> #include <set> #include <string> #include <unordered_map> @@ -25,7 +26,6 @@ #include "androidfw/ConfigDescription.h" #include "Diagnostics.h" -#include "util/Maybe.h" namespace aapt { @@ -55,9 +55,9 @@ const android::StringPiece& AbiToString(Abi abi); */ struct Locale { /** The ISO<?> standard locale language code. */ - Maybe<std::string> lang; + std::optional<std::string> lang; /** The ISO<?> standard locale region code. */ - Maybe<std::string> region; + std::optional<std::string> region; inline friend bool operator==(const Locale& lhs, const Locale& rhs) { return lhs.lang == rhs.lang && lhs.region == rhs.region; @@ -74,9 +74,9 @@ struct AndroidManifest { struct AndroidSdk { std::string label; int min_sdk_version; // min_sdk_version is mandatory if splitting by SDK. - Maybe<int> target_sdk_version; - Maybe<int> max_sdk_version; - Maybe<AndroidManifest> manifest; + std::optional<int> target_sdk_version; + std::optional<int> max_sdk_version; + std::optional<AndroidManifest> manifest; static AndroidSdk ForMinSdk(int min_sdk) { AndroidSdk sdk; @@ -112,7 +112,7 @@ struct OutputArtifact { std::vector<Abi> abis; std::vector<android::ConfigDescription> screen_densities; std::vector<android::ConfigDescription> locales; - Maybe<AndroidSdk> android_sdk; + std::optional<AndroidSdk> android_sdk; std::vector<DeviceFeature> features; std::vector<GlTexture> textures; @@ -136,7 +136,7 @@ class ConfigurationParser { public: /** Returns a ConfigurationParser for the file located at the provided path. */ - static Maybe<ConfigurationParser> ForPath(const std::string& path); + static std::optional<ConfigurationParser> ForPath(const std::string& path); /** Returns a ConfigurationParser for the configuration in the provided file contents. */ static ConfigurationParser ForContents(const std::string& contents, const std::string& path) { @@ -154,7 +154,8 @@ 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. */ - Maybe<std::vector<configuration::OutputArtifact>> Parse(const android::StringPiece& apk_path); + std::optional<std::vector<configuration::OutputArtifact>> Parse( + const android::StringPiece& apk_path); protected: /** diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h index c541688bc018..42ef51591d4f 100644 --- a/tools/aapt2/configuration/ConfigurationParser.internal.h +++ b/tools/aapt2/configuration/ConfigurationParser.internal.h @@ -84,8 +84,8 @@ class ComparisonChain { * have not been able to determine the sort order with the previous comparisons. */ template <typename T> - ComparisonChain& Add(const Group<T>& groups, const Maybe<std::string>& lhs, - const Maybe<std::string>& rhs) { + ComparisonChain& Add(const Group<T>& groups, const std::optional<std::string>& lhs, + const std::optional<std::string>& rhs) { return Add(GetGroupOrder(groups, lhs), GetGroupOrder(groups, rhs)); } @@ -108,7 +108,7 @@ class ComparisonChain { private: template <typename T> - inline size_t GetGroupOrder(const Entry<T>& groups, const Maybe<std::string>& label) { + inline size_t GetGroupOrder(const Entry<T>& groups, const std::optional<std::string>& label) { if (!label) { return std::numeric_limits<size_t>::max(); } @@ -122,32 +122,33 @@ class ComparisonChain { /** Output artifact configuration options. */ struct ConfiguredArtifact { /** Name to use for output of processing foo.apk -> foo.<name>.apk. */ - Maybe<std::string> name; + std::optional<std::string> name; /** If present, uses the ABI group with this name. */ - Maybe<std::string> abi_group; + std::optional<std::string> abi_group; /** If present, uses the screen density group with this name. */ - Maybe<std::string> screen_density_group; + std::optional<std::string> screen_density_group; /** If present, uses the locale group with this name. */ - Maybe<std::string> locale_group; + std::optional<std::string> locale_group; /** If present, uses the Android SDK with this name. */ - Maybe<std::string> android_sdk; + std::optional<std::string> android_sdk; /** If present, uses the device feature group with this name. */ - Maybe<std::string> device_feature_group; + std::optional<std::string> device_feature_group; /** If present, uses the OpenGL texture group with this name. */ - Maybe<std::string> gl_texture_group; + std::optional<std::string> gl_texture_group; /** Convert an artifact name template into a name string based on configuration contents. */ - Maybe<std::string> ToArtifactName(const android::StringPiece& format, - const android::StringPiece& apk_name, IDiagnostics* diag) const; + std::optional<std::string> ToArtifactName(const android::StringPiece& format, + const android::StringPiece& apk_name, + IDiagnostics* diag) const; /** Convert an artifact name template into a name string based on configuration contents. */ - Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const; + std::optional<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const; }; /** AAPT2 XML configuration file binary representation. */ struct PostProcessingConfiguration { std::vector<ConfiguredArtifact> artifacts; - Maybe<std::string> artifact_format; + std::optional<std::string> artifact_format; Group<Abi> abi_groups; Group<android::ConfigDescription> screen_density_groups; @@ -212,9 +213,9 @@ struct PostProcessingConfiguration { }; /** Parses the provided XML document returning the post processing configuration. */ -Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents, - const std::string& config_path, - IDiagnostics* diag); +std::optional<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents, + const std::string& config_path, + IDiagnostics* diag); namespace handler { diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp index e5b3107877cb..e5eacccb682e 100644 --- a/tools/aapt2/configuration/ConfigurationParser_test.cpp +++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp @@ -31,11 +31,6 @@ using ::android::ConfigDescription; namespace aapt { namespace configuration { -void PrintTo(const AndroidSdk& sdk, std::ostream* os) { - *os << "SDK: min=" << sdk.min_sdk_version - << ", target=" << sdk.target_sdk_version.value_or_default(-1) - << ", max=" << sdk.max_sdk_version.value_or_default(-1); -} bool operator==(const ConfiguredArtifact& lhs, const ConfiguredArtifact& rhs) { return lhs.name == rhs.name && lhs.abi_group == rhs.abi_group && @@ -45,20 +40,6 @@ bool operator==(const ConfiguredArtifact& lhs, const ConfiguredArtifact& rhs) { lhs.gl_texture_group == rhs.gl_texture_group; } -std::ostream& operator<<(std::ostream& out, const Maybe<std::string>& value) { - PrintTo(value, &out); - return out; -} - -void PrintTo(const ConfiguredArtifact& artifact, std::ostream* os) { - *os << "\n{" - << "\n name: " << artifact.name << "\n sdk: " << artifact.android_sdk - << "\n abi: " << artifact.abi_group << "\n density: " << artifact.screen_density_group - << "\n locale: " << artifact.locale_group - << "\n features: " << artifact.device_feature_group - << "\n textures: " << artifact.gl_texture_group << "\n}\n"; -} - namespace handler { namespace { @@ -186,7 +167,7 @@ TEST_F(ConfigurationParserTest, ForPath_NoFile) { } TEST_F(ConfigurationParserTest, ExtractConfiguration) { - Maybe<PostProcessingConfiguration> maybe_config = + std::optional<PostProcessingConfiguration> maybe_config = ExtractConfiguration(kValidConfig, "fake.xml", &diag_); PostProcessingConfiguration config = maybe_config.value(); @@ -928,7 +909,8 @@ TEST(ArtifactTest, Nesting) { EXPECT_FALSE(x86.ToArtifactName("something.${abi${density}}.apk", "", &diag)); - const Maybe<std::string>& name = x86.ToArtifactName("something.${abi${abi}}.apk", "", &diag); + const std::optional<std::string>& name = + x86.ToArtifactName("something.${abi${abi}}.apk", "", &diag); ASSERT_TRUE(name); EXPECT_EQ(name.value(), "something.${abix86}.apk"); } diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index 145d7f873ccd..9828b97982ed 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -92,7 +92,6 @@ enum { }; const std::string& kAndroidNamespace = "http://schemas.android.com/apk/res/android"; -constexpr int kCurrentDevelopmentVersion = 10000; constexpr int kNeverForLocation = 0x00010000; /** Retrieves the attribute of the element with the specified attribute resource id. */ @@ -331,7 +330,7 @@ class ManifestExtractor { ConfigDescription config; config.orientation = android::ResTable_config::ORIENTATION_PORT; config.density = android::ResTable_config::DENSITY_MEDIUM; - config.sdkVersion = kCurrentDevelopmentVersion; // Very high. + config.sdkVersion = SDK_CUR_DEVELOPMENT; // Very high. config.screenWidthDp = 320; config.screenHeightDp = 480; config.smallestScreenWidthDp = 320; @@ -621,7 +620,7 @@ class UsesSdkBadging : public ManifestExtractor::Element { // Detect the target sdk of the element if ((min_sdk_name && *min_sdk_name == "Donut") || (target_sdk_name && *target_sdk_name == "Donut")) { - extractor()->RaiseTargetSdk(4); + extractor()->RaiseTargetSdk(SDK_DONUT); } if (min_sdk) { extractor()->RaiseTargetSdk(*min_sdk); @@ -629,7 +628,7 @@ class UsesSdkBadging : public ManifestExtractor::Element { if (target_sdk) { extractor()->RaiseTargetSdk(*target_sdk); } else if (target_sdk_name) { - extractor()->RaiseTargetSdk(kCurrentDevelopmentVersion); + extractor()->RaiseTargetSdk(SDK_CUR_DEVELOPMENT); } } @@ -746,21 +745,23 @@ class SupportsScreen : public ManifestExtractor::Element { // the screen size support was introduced, so all default to // enabled. if (small_screen_temp > 0) { - small_screen_temp = target_sdk >= 4 ? -1 : 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 >= 4 ? -1 : 0; + large_screen_temp = target_sdk >= SDK_DONUT ? -1 : 0; } if (xlarge_screen_temp > 0) { // Introduced in Gingerbread. - xlarge_screen_temp = target_sdk >= 9 ? -1 : 0; + xlarge_screen_temp = target_sdk >= SDK_GINGERBREAD ? -1 : 0; } if (any_density_temp > 0) { - any_density_temp = (target_sdk >= 4 || requires_smallest_width_dp > 0 - || compatible_width_limit_dp > 0) ? -1 : 0; + any_density_temp = (target_sdk >= SDK_DONUT || requires_smallest_width_dp > 0 || + compatible_width_limit_dp > 0) + ? -1 + : 0; } // Print the formatted screen info @@ -1460,6 +1461,64 @@ class UsesStaticLibrary : public ManifestExtractor::Element { } }; +/** Represents <sdk-library> elements. **/ +class SdkLibrary : public ManifestExtractor::Element { + public: + SdkLibrary() = default; + std::string name; + 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); + } + } + + void Print(text::Printer* printer) override { + printer->Print( + StringPrintf("sdk-library: name='%s' versionMajor='%d'\n", name.data(), versionMajor)); + } +}; + +/** Represents <uses-sdk-library> elements. **/ +class UsesSdkLibrary : public ManifestExtractor::Element { + public: + UsesSdkLibrary() = default; + std::string name; + int versionMajor; + 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); + } + } + + void AddCertDigest(xml::Element* element) { + std::string digest = GetAttributeStringDefault(FindAttribute(element, CERT_DIGEST_ATTR), ""); + // We allow ":" delimiters in the SHA declaration as this is the format + // emitted by the certtool making it easy for developers to copy/paste. + digest.erase(std::remove(digest.begin(), digest.end(), ':'), digest.end()); + if (!digest.empty()) { + certDigests.push_back(digest); + } + } + + void Print(text::Printer* printer) override { + printer->Print( + StringPrintf("uses-sdk-library: name='%s' versionMajor='%d'", name.data(), versionMajor)); + for (size_t i = 0; i < certDigests.size(); i++) { + printer->Print(StringPrintf(" certDigest='%s'", certDigests[i].data())); + } + printer->Print("\n"); + } +}; + /** Represents <uses-native-library> elements. **/ class UsesNativeLibrary : public ManifestExtractor::Element { public: @@ -2030,7 +2089,7 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { auto write_external_permission = ElementCast<UsesPermission>( FindPermission(root.get(), "android.permission.WRITE_EXTERNAL_STORAGE")); - if (target_sdk() < 4) { + if (target_sdk() < SDK_DONUT) { if (!write_external_permission) { PrintPermission("android.permission.WRITE_EXTERNAL_STORAGE", "targetSdkVersion < 4", -1); insert_write_external = true; @@ -2053,7 +2112,7 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { } // Pre-JellyBean call log permission compatibility. - if (target_sdk() < 16) { + 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", @@ -2366,6 +2425,7 @@ T* ElementCast(ManifestExtractor::Element* element) { {"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}, @@ -2378,6 +2438,7 @@ T* ElementCast(ManifestExtractor::Element* element) { {"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}, }; @@ -2420,6 +2481,7 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( {"required-not-feature", &CreateType<RequiredNotFeature>}, {"screen", &CreateType<Screen>}, {"service", &CreateType<Service>}, + {"sdk-library", &CreateType<SdkLibrary>}, {"static-library", &CreateType<StaticLibrary>}, {"supports-gl-texture", &CreateType<SupportsGlTexture>}, {"supports-input", &CreateType<SupportsInput>}, @@ -2432,6 +2494,7 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( {"uses-permission", &CreateType<UsesPermission>}, {"uses-permission-sdk-23", &CreateType<UsesPermissionSdk23>}, {"uses-sdk", &CreateType<UsesSdkBadging>}, + {"uses-sdk-library", &CreateType<UsesSdkLibrary>}, {"uses-static-library", &CreateType<UsesStaticLibrary>}, }; diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp index 41f01a01ed7c..c20b053c37b1 100644 --- a/tools/aapt2/format/Archive.cpp +++ b/tools/aapt2/format/Archive.cpp @@ -43,7 +43,7 @@ class DirectoryWriter : public IArchiveWriter { bool Open(const StringPiece& out_dir) { dir_ = out_dir.to_string(); file::FileType type = file::GetFileType(dir_); - if (type == file::FileType::kNonexistant) { + if (type == file::FileType::kNonExistant) { error_ = "directory does not exist"; return false; } else if (type != file::FileType::kDirectory) { diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index a8845ef96bc3..c65c55024bab 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -556,8 +556,8 @@ bool BinaryResourceParser::ParseStagedAliases(const ResChunk_header* chunk) { std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& name, const ConfigDescription& config, const android::Res_value& value) { - std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(name.type, config, value_pool_, - value, &table_->string_pool); + std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue( + name.type.type, config, value_pool_, value, &table_->string_pool); if (files_ != nullptr) { FileReference* file_ref = ValueCast<FileReference>(item.get()); if (file_ref != nullptr) { @@ -575,8 +575,10 @@ std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& na std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { - switch (name.type) { + switch (name.type.type) { case ResourceType::kStyle: + // fallthrough + case ResourceType::kConfigVarying: // legacy thing used in tests return ParseStyle(name, config, map); case ResourceType::kAttrPrivate: // fallthrough @@ -592,8 +594,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 '" << to_string(name.type) << "' (" - << (int)name.type << ")"); + diag_->Error(DiagMessage() << "illegal map type '" << name.type << "' (" + << (int)name.type.type << ")"); break; } return {}; diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index 8139d7385092..cd1c0af702cf 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -123,7 +123,7 @@ class TableFlattenerTest : public ::testing::Test { return ::testing::AssertionFailure() << "failed to find resource name"; } - Maybe<ResourceName> resName = ResourceUtils::ToResourceName(actual_name); + std::optional<ResourceName> resName = ResourceUtils::ToResourceName(actual_name); if (!resName) { return ::testing::AssertionFailure() << "expected name '" << expected_res_name << "' but got '" @@ -423,7 +423,7 @@ TEST_F(TableFlattenerTest, FlattenSharedLibrary) { ResourceTable result; ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); - Maybe<ResourceTable::SearchResult> search_result = + std::optional<ResourceTable::SearchResult> search_result = result.FindResource(test::ParseNameOrDie("lib:id/foo")); ASSERT_TRUE(search_result); EXPECT_EQ(0x00u, search_result.value().entry->id.value().package_id()); @@ -454,7 +454,7 @@ TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) { ResourceTable result; ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); - Maybe<ResourceTable::SearchResult> search_result = + std::optional<ResourceTable::SearchResult> search_result = result.FindResource(test::ParseNameOrDie("lib:style/Theme")); ASSERT_TRUE(search_result); EXPECT_EQ(0x00030001u, search_result.value().entry->id.value()); diff --git a/tools/aapt2/format/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp index afbaae4ee2a8..cdbe8828b29b 100644 --- a/tools/aapt2/format/binary/XmlFlattener.cpp +++ b/tools/aapt2/format/binary/XmlFlattener.cpp @@ -264,7 +264,7 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { } std::string processed_str; - Maybe<StringPiece> compiled_text; + std::optional<StringPiece> compiled_text; if (xml_attr->compiled_value != nullptr) { // Make sure we're not flattening a String. A String can be referencing a string from // a different StringPool than we're using here to build the binary XML. diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index 6042ba89bf8a..f3b7f758e170 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -438,7 +438,7 @@ static pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type) { } static void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref) { - pb_ref->set_id(ref.id.value_or_default(ResourceId(0x0)).id); + pb_ref->set_id(ref.id.value_or(ResourceId(0x0)).id); if (ref.name) { pb_ref->set_name(ref.name.value().to_string()); @@ -759,13 +759,13 @@ void SerializeXmlToPb(const xml::Element& el, pb::XmlNode* out_node, pb_attr->set_namespace_uri(attr.namespace_uri); pb_attr->set_value(attr.value); if (attr.compiled_attribute) { - const ResourceId attr_id = attr.compiled_attribute.value().id.value_or_default({}); + const ResourceId attr_id = attr.compiled_attribute.value().id.value_or(ResourceId{}); pb_attr->set_resource_id(attr_id.id); } if (attr.compiled_value != nullptr) { SerializeItemToPb(*attr.compiled_value, pb_attr->mutable_compiled_item()); pb::SourcePosition* pb_src = pb_attr->mutable_source(); - pb_src->set_line_number(attr.compiled_value->GetSource().line.value_or_default(0)); + pb_src->set_line_number(attr.compiled_value->GetSource().line.value_or(0)); } } diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index 38c811fe3619..d1d72e012b31 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -189,7 +189,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { ASSERT_THAT(new_id, NotNull()); EXPECT_THAT(new_id->IsWeak(), Eq(id->IsWeak())); - Maybe<ResourceTable::SearchResult> result = + std::optional<ResourceTable::SearchResult> result = new_table.FindResource(test::ParseNameOrDie("com.app.a:layout/main")); ASSERT_TRUE(result); @@ -234,7 +234,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u)); EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u)); - Maybe<ResourceTable::SearchResult> search_result = + std::optional<ResourceTable::SearchResult> search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:integer/overlayable")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); @@ -637,7 +637,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)); EXPECT_THAT(error, IsEmpty()); - Maybe<ResourceTable::SearchResult> search_result = + std::optional<ResourceTable::SearchResult> search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo")); ASSERT_TRUE(search_result); ASSERT_TRUE(search_result.value().entry->overlayable_item); diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp index e15f935cad27..fc2e45e74b4d 100644 --- a/tools/aapt2/io/FileSystem.cpp +++ b/tools/aapt2/io/FileSystem.cpp @@ -21,11 +21,10 @@ #include "android-base/errors.h" #include "androidfw/StringPiece.h" #include "utils/FileMap.h" - #include "Source.h" #include "io/FileStream.h" #include "util/Files.h" -#include "util/Maybe.h" + #include "util/Util.h" using ::android::StringPiece; @@ -38,7 +37,7 @@ RegularFile::RegularFile(const Source& source) : source_(source) {} std::unique_ptr<IData> RegularFile::OpenAsData() { android::FileMap map; - if (Maybe<android::FileMap> map = file::MmapPath(source_.path, nullptr)) { + if (std::optional<android::FileMap> map = file::MmapPath(source_.path, nullptr)) { if (map.value().getDataPtr() && map.value().getDataLength() > 0) { return util::make_unique<MmappedData>(std::move(map.value())); } diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index de6524dc7027..a963d9893f2f 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -204,7 +204,7 @@ bool JavaClassGenerator::SkipSymbol(Visibility::Level level) { } // Whether or not to skip writing this symbol. -bool JavaClassGenerator::SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol) { +bool JavaClassGenerator::SkipSymbol(const std::optional<SymbolTable::Symbol>& symbol) { return !symbol || (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic && !symbol.value().is_public); } @@ -212,12 +212,12 @@ bool JavaClassGenerator::SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol) { struct StyleableAttr { const Reference* attr_ref = nullptr; std::string field_name; - Maybe<SymbolTable::Symbol> symbol; + std::optional<SymbolTable::Symbol> symbol; }; static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) { - const ResourceId lhs_id = lhs.attr_ref->id.value_or_default(ResourceId(0)); - const ResourceId rhs_id = rhs.attr_ref->id.value_or_default(ResourceId(0)); + const ResourceId lhs_id = lhs.attr_ref->id.value_or(ResourceId(0)); + const ResourceId rhs_id = rhs.attr_ref->id.value_or(ResourceId(0)); if (lhs_id == rhs_id) { return lhs.attr_ref->name.value() < rhs.attr_ref->name.value(); } @@ -229,8 +229,8 @@ static FieldReference GetRFieldReference(const ResourceName& name, const std::string package_name = name.package.empty() ? fallback_package_name.to_string() : name.package; const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry); - return FieldReference( - StringPrintf("%s.R.%s.%s", package_name.c_str(), to_string(name.type).data(), entry.c_str())); + return FieldReference(StringPrintf("%s.R.%s.%s", package_name.c_str(), + name.type.to_string().data(), entry.c_str())); } bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, @@ -362,7 +362,7 @@ bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res array_def->AddElement(field_name); r_txt_contents = field_name.ref; } else { - const ResourceId attr_id = attr.attr_ref->id.value_or_default(ResourceId(0)); + const ResourceId attr_id = attr.attr_ref->id.value_or(ResourceId(0)); array_def->AddElement(attr_id); r_txt_contents = to_string(attr_id); } @@ -451,7 +451,7 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso MethodDefinition* out_rewrite_method, text::Printer* r_txt_printer) { ResourceId real_id = id; - if (context_->GetMinSdkVersion() < SDK_O && name.type == ResourceType::kId && + if (context_->GetMinSdkVersion() < SDK_O && name.type.type == ResourceType::kId && id.package_id() > kAppPackageId) { // Workaround for feature splits using package IDs > 0x7F. // See b/37498913. @@ -489,7 +489,7 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso if (r_txt_printer != nullptr) { r_txt_printer->Print("int ") - .Print(to_string(name.type)) + .Print(name.type.to_string()) .Print(" ") .Print(field_name) .Print(" ") @@ -497,16 +497,16 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso } if (out_rewrite_method != nullptr) { - const StringPiece& type_str = to_string(name.type); + const std::string 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())); } } -Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name, - const StringPiece& package_name_to_generate, - const ResourceEntry& entry) { +std::optional<std::string> JavaClassGenerator::UnmangleResource( + const StringPiece& package_name, const StringPiece& package_name_to_generate, + const ResourceEntry& entry) { if (SkipSymbol(entry.visibility.level)) { return {}; } @@ -535,7 +535,7 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate MethodDefinition* out_rewrite_method_def, Printer* r_txt_printer) { for (const auto& entry : type.entries) { - const Maybe<std::string> unmangled_name = + const std::optional<std::string> unmangled_name = UnmangleResource(package.name, package_name_to_generate, *entry); if (!unmangled_name) { continue; @@ -561,7 +561,7 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate return false; } - if (resource_name.type == ResourceType::kStyleable) { + if (resource_name.type.type == ResourceType::kStyleable) { CHECK(!entry->values.empty()); const auto styleable = reinterpret_cast<const Styleable*>(entry->values.front()->value.get()); if (!ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h index d9d1b39805f9..b45a2f12db35 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -46,7 +46,7 @@ struct JavaClassGeneratorOptions { // If set, generates code to rewrite the package ID of resources. // Implies use_final == true. Default is unset. - Maybe<OnResourcesLoadedCallbackOptions> rewrite_callback_options; + std::optional<OnResourcesLoadedCallbackOptions> rewrite_callback_options; enum class SymbolTypes { kAll, @@ -83,13 +83,13 @@ class JavaClassGenerator { private: bool SkipSymbol(Visibility::Level state); - bool SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol); + bool SkipSymbol(const std::optional<SymbolTable::Symbol>& symbol); // 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. - Maybe<std::string> UnmangleResource(const android::StringPiece& package_name, - const android::StringPiece& package_name_to_generate, - const ResourceEntry& entry); + std::optional<std::string> UnmangleResource(const android::StringPiece& package_name, + const android::StringPiece& package_name_to_generate, + const ResourceEntry& entry); bool ProcessType(const android::StringPiece& package_name_to_generate, const ResourceTablePackage& package, const ResourceTableType& type, diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index 09ea03b23c9a..a0db41baecb4 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -19,19 +19,17 @@ #include <algorithm> #include "Source.h" -#include "java/AnnotationProcessor.h" #include "java/ClassDefinition.h" #include "java/JavaClassGenerator.h" #include "text/Unicode.h" -#include "util/Maybe.h" #include "xml/XmlDom.h" using ::aapt::text::IsJavaIdentifier; namespace aapt { -static Maybe<std::string> ExtractJavaIdentifier(IDiagnostics* diag, const Source& source, - const std::string& value) { +static std::optional<std::string> ExtractJavaIdentifier(IDiagnostics* diag, const Source& source, + const std::string& value) { std::string result = value; size_t pos = value.rfind('.'); if (pos != std::string::npos) { @@ -63,7 +61,7 @@ static bool WriteSymbol(const Source& source, IDiagnostics* diag, xml::Element* return false; } - Maybe<std::string> result = + std::optional<std::string> result = ExtractJavaIdentifier(diag, source.WithLine(el->line_number), attr->value); if (!result) { return false; diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index d9a4caa34e0d..e53e22070b62 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -48,7 +48,7 @@ class BaseVisitor : public xml::Visitor { void Visit(xml::Element* node) override { if (!node->namespace_uri.empty()) { - Maybe<xml::ExtractedPackage> maybe_package = + std::optional<xml::ExtractedPackage> maybe_package = xml::ExtractPackageFromNamespace(node->namespace_uri); if (maybe_package) { // This is a custom view, let's figure out the class name from this. @@ -270,14 +270,16 @@ class ManifestVisitor : public BaseVisitor { get_name = true; xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "backupAgent"); if (attr) { - Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value); + std::optional<std::string> result = + util::GetFullyQualifiedClassName(package_, attr->value); if (result) { AddClass(node->line_number, result.value(), ""); } } attr = node->FindAttribute(xml::kSchemaAndroid, "appComponentFactory"); if (attr) { - Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value); + std::optional<std::string> result = + util::GetFullyQualifiedClassName(package_, attr->value); if (result) { AddClass(node->line_number, result.value(), ""); } @@ -285,7 +287,8 @@ class ManifestVisitor : public BaseVisitor { attr = node->FindAttribute(xml::kSchemaAndroid, "zygotePreloadName"); if (attr) { - Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value); + std::optional<std::string> result = + util::GetFullyQualifiedClassName(package_, attr->value); if (result) { AddClass(node->line_number, result.value(), ""); } @@ -308,7 +311,7 @@ class ManifestVisitor : public BaseVisitor { component_process ? component_process->value : default_process_; get_name = !process.empty() && process[0] != ':'; } - } else if (node->name == "instrumentation") { + } else if (node->name == "instrumentation" || node->name == "process") { get_name = true; } @@ -317,7 +320,8 @@ class ManifestVisitor : public BaseVisitor { get_name = attr != nullptr; if (get_name) { - Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value); + std::optional<std::string> result = + util::GetFullyQualifiedClassName(package_, attr->value); if (result) { AddClass(node->line_number, result.value(), ""); } @@ -354,7 +358,7 @@ bool CollectProguardRules(IAaptContext* context_, xml::XmlResource* res, KeepSet return false; } - switch (res->file.name.type) { + switch (res->file.name.type.type) { case ResourceType::kLayout: { LayoutVisitor visitor(res->file, keep_set); res->root->Accept(&visitor); @@ -461,7 +465,7 @@ bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set, locations->insert(location); // TODO: allow for more reference types if we can determine its safe. - if (location.name.type != ResourceType::kLayout) { + if (location.name.type.type != ResourceType::kLayout) { return false; } diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp index e1040666e410..466b7d97da00 100644 --- a/tools/aapt2/java/ProguardRules_test.cpp +++ b/tools/aapt2/java/ProguardRules_test.cpp @@ -44,6 +44,9 @@ TEST(ProguardRulesTest, ManifestRuleDefaultConstructorOnly) { android:name="com.foo.BarApplication" android:zygotePreloadName="com.foo.BarZygotePreload" > + <processes> + <process android:process=":sub" android:name="com.foo.BazApplication" /> + </processes> <activity android:name="com.foo.BarActivity"/> <service android:name="com.foo.BarService"/> <receiver android:name="com.foo.BarReceiver"/> @@ -59,6 +62,7 @@ TEST(ProguardRulesTest, ManifestRuleDefaultConstructorOnly) { EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarAppComponentFactory { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarBackupAgent { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarApplication { <init>(); }")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BazApplication { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarActivity { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarService { <init>(); }")); EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarReceiver { <init>(); }")); diff --git a/tools/aapt2/jni/ScopedUtfChars.h b/tools/aapt2/jni/ScopedUtfChars.h deleted file mode 100644 index a8c4b136dcf6..000000000000 --- a/tools/aapt2/jni/ScopedUtfChars.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2010 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 SCOPED_UTF_CHARS_H_included -#define SCOPED_UTF_CHARS_H_included - -#include <string.h> -#include <jni.h> - -#include "android-base/logging.h" - -// This file was copied with some minor modifications from libnativehelper. -// As soon as libnativehelper can be compiled for Windows, this file should be -// replaced with libnativehelper's implementation. -class ScopedUtfChars { - public: - ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) { - CHECK(s != nullptr); - utf_chars_ = env->GetStringUTFChars(s, nullptr); - } - - ScopedUtfChars(ScopedUtfChars&& rhs) : - env_(rhs.env_), string_(rhs.string_), utf_chars_(rhs.utf_chars_) { - rhs.env_ = nullptr; - rhs.string_ = nullptr; - rhs.utf_chars_ = nullptr; - } - - ~ScopedUtfChars() { - if (utf_chars_) { - env_->ReleaseStringUTFChars(string_, utf_chars_); - } - } - - ScopedUtfChars& operator=(ScopedUtfChars&& rhs) { - if (this != &rhs) { - // Delete the currently owned UTF chars. - this->~ScopedUtfChars(); - - // Move the rhs ScopedUtfChars and zero it out. - env_ = rhs.env_; - string_ = rhs.string_; - utf_chars_ = rhs.utf_chars_; - rhs.env_ = nullptr; - rhs.string_ = nullptr; - rhs.utf_chars_ = nullptr; - } - return *this; - } - - const char* c_str() const { - return utf_chars_; - } - - size_t size() const { - return strlen(utf_chars_); - } - - const char& operator[](size_t n) const { - return utf_chars_[n]; - } - - private: - JNIEnv* env_; - jstring string_; - const char* utf_chars_; - - DISALLOW_COPY_AND_ASSIGN(ScopedUtfChars); -}; - -#endif // SCOPED_UTF_CHARS_H_included diff --git a/tools/aapt2/jni/aapt2_jni.cpp b/tools/aapt2/jni/aapt2_jni.cpp deleted file mode 100644 index ec3c5431c7a3..000000000000 --- a/tools/aapt2/jni/aapt2_jni.cpp +++ /dev/null @@ -1,143 +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 "com_android_tools_aapt2_Aapt2Jni.h" - -#include <algorithm> -#include <memory> -#include <utility> -#include <vector> - -#include "android-base/logging.h" -#include "ScopedUtfChars.h" - -#include "Diagnostics.h" -#include "cmd/Compile.h" -#include "cmd/Link.h" -#include "util/Util.h" - -using android::StringPiece; - -/* - * Converts a java List<String> into C++ vector<ScopedUtfChars>. - */ -static std::vector<ScopedUtfChars> list_to_utfchars(JNIEnv *env, jobject obj) { - std::vector<ScopedUtfChars> converted; - - // Call size() method on the list to know how many elements there are. - jclass list_cls = env->GetObjectClass(obj); - jmethodID size_method_id = env->GetMethodID(list_cls, "size", "()I"); - CHECK(size_method_id != 0); - jint size = env->CallIntMethod(obj, size_method_id); - CHECK(size >= 0); - - // Now, iterate all strings in the list - // (note: generic erasure means get() return an Object) - jmethodID get_method_id = env->GetMethodID(list_cls, "get", "(I)Ljava/lang/Object;"); - CHECK(get_method_id != 0); - for (jint i = 0; i < size; i++) { - // Call get(i) to get the string in the ith position. - jobject string_obj_uncast = env->CallObjectMethod(obj, get_method_id, i); - CHECK(string_obj_uncast != nullptr); - jstring string_obj = static_cast<jstring>(string_obj_uncast); - converted.push_back(ScopedUtfChars(env, string_obj)); - } - - return converted; -} - -/* - * Extracts all StringPiece from the ScopedUtfChars instances. - * - * The returned pieces can only be used while the original ones have not been - * destroyed. - */ -static std::vector<StringPiece> extract_pieces(const std::vector<ScopedUtfChars> &strings) { - std::vector<StringPiece> pieces; - - std::for_each( - strings.begin(), strings.end(), - [&pieces](const ScopedUtfChars &p) { pieces.push_back(p.c_str()); }); - - return pieces; -} - -class JniDiagnostics : public aapt::IDiagnostics { - public: - JniDiagnostics(JNIEnv* env, jobject diagnostics_obj) - : env_(env), diagnostics_obj_(diagnostics_obj) { - mid_ = NULL; - } - - void Log(Level level, aapt::DiagMessageActual& actual_msg) override { - jint level_value; - switch (level) { - case Level::Error: - level_value = 3; - break; - - case Level::Warn: - level_value = 2; - break; - - case Level::Note: - level_value = 1; - break; - } - jstring message = env_->NewStringUTF(actual_msg.message.c_str()); - jstring path = env_->NewStringUTF(actual_msg.source.path.c_str()); - jlong line = -1; - if (actual_msg.source.line) { - line = actual_msg.source.line.value(); - } - if (!mid_) { - jclass diagnostics_cls = env_->GetObjectClass(diagnostics_obj_); - mid_ = env_->GetMethodID(diagnostics_cls, "log", "(ILjava/lang/String;JLjava/lang/String;)V"); - } - env_->CallVoidMethod(diagnostics_obj_, mid_, level_value, path, line, message); - } - - private: - JNIEnv* env_; - jobject diagnostics_obj_; - jmethodID mid_; - DISALLOW_COPY_AND_ASSIGN(JniDiagnostics); -}; - -JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeCompile( - JNIEnv* env, jclass aapt_obj, jobject arguments_obj, jobject diagnostics_obj) { - std::vector<ScopedUtfChars> compile_args_jni = - list_to_utfchars(env, arguments_obj); - std::vector<StringPiece> compile_args = extract_pieces(compile_args_jni); - JniDiagnostics diagnostics(env, diagnostics_obj); - return aapt::CompileCommand(&diagnostics).Execute(compile_args, &std::cerr); -} - -JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink(JNIEnv* env, - jclass aapt_obj, - jobject arguments_obj, - jobject diagnostics_obj) { - std::vector<ScopedUtfChars> link_args_jni = - list_to_utfchars(env, arguments_obj); - std::vector<StringPiece> link_args = extract_pieces(link_args_jni); - JniDiagnostics diagnostics(env, diagnostics_obj); - return aapt::LinkCommand(&diagnostics).Execute(link_args, &std::cerr); -} - -JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_ping( - JNIEnv *env, jclass aapt_obj) { - // This is just a no-op method to see if the library has been loaded. -} diff --git a/tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h b/tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h deleted file mode 100644 index 3cd98658fab2..000000000000 --- a/tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h +++ /dev/null @@ -1,37 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include <jni.h> -/* Header for class com_android_tools_aapt2_Aapt2Jni */ - -#ifndef _Included_com_android_tools_aapt2_Aapt2Jni -#define _Included_com_android_tools_aapt2_Aapt2Jni -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: com_android_tools_aapt2_Aapt2Jni - * Method: ping - * Signature: ()V - */ -JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_ping - (JNIEnv *, jclass); - -/* - * Class: com_android_tools_aapt2_Aapt2Jni - * Method: nativeCompile - * Signature: (Ljava/util/List;Lcom/android/tools/aapt2/Aapt2JniDiagnostics;)I - */ -JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeCompile(JNIEnv*, jclass, jobject, - jobject); - -/* - * Class: com_android_tools_aapt2_Aapt2Jni - * Method: nativeLink - * Signature: (Ljava/util/List;Lcom/android/tools/aapt2/Aapt2JniDiagnostics;)I - */ -JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink(JNIEnv*, jclass, jobject, - jobject); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp index 876494e617a6..328ac97090a8 100644 --- a/tools/aapt2/link/AutoVersioner.cpp +++ b/tools/aapt2/link/AutoVersioner.cpp @@ -90,7 +90,7 @@ bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) { } if (Style* style = ValueCast<Style>(config_value->value.get())) { - Maybe<ApiVersion> min_sdk_stripped; + std::optional<ApiVersion> min_sdk_stripped; std::vector<Style::Entry> stripped; auto iter = style->entries.begin(); diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp index 02fd00bba439..8179d46145ca 100644 --- a/tools/aapt2/link/AutoVersioner_test.cpp +++ b/tools/aapt2/link/AutoVersioner_test.cpp @@ -87,25 +87,27 @@ TEST(AutoVersionerTest, VersionStylesForTable) { Style* style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", test::ParseConfigOrDie("v4")); ASSERT_THAT(style, NotNull()); ASSERT_EQ(style->entries.size(), 1u); - EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/onClick")), style->entries.front().key.name); + EXPECT_EQ(test::ParseNameOrDie("android:attr/onClick"), style->entries.front().key.name); style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", test::ParseConfigOrDie("v13")); ASSERT_THAT(style, NotNull()); ASSERT_EQ(style->entries.size(), 2u); - EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/onClick")),style->entries[0].key.name); - EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/requiresSmallestWidthDp")), style->entries[1].key.name); + EXPECT_EQ(test::ParseNameOrDie("android:attr/onClick"), style->entries[0].key.name); + EXPECT_EQ(test::ParseNameOrDie("android:attr/requiresSmallestWidthDp"), + style->entries[1].key.name); style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", test::ParseConfigOrDie("v17")); ASSERT_THAT(style, NotNull()); ASSERT_EQ(style->entries.size(), 3u); - EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/onClick")), style->entries[0].key.name); - EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/requiresSmallestWidthDp")), style->entries[1].key.name); - EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/paddingStart")), style->entries[2].key.name); + EXPECT_EQ(test::ParseNameOrDie("android:attr/onClick"), style->entries[0].key.name); + EXPECT_EQ(test::ParseNameOrDie("android:attr/requiresSmallestWidthDp"), + style->entries[1].key.name); + EXPECT_EQ(test::ParseNameOrDie("android:attr/paddingStart"), style->entries[2].key.name); style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", test::ParseConfigOrDie("v21")); ASSERT_THAT(style, NotNull()); ASSERT_EQ(1u, style->entries.size()); - EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/paddingEnd")), style->entries.front().key.name); + EXPECT_EQ(test::ParseNameOrDie("android:attr/paddingEnd"), style->entries.front().key.name); } } // namespace aapt diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 8abd9dec56be..d432341a8cde 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -52,7 +52,7 @@ static bool NameIsJavaClassName(xml::Element* el, xml::Attribute* attr, // 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. - Maybe<std::string> fully_qualified_class_name = + std::optional<std::string> fully_qualified_class_name = util::GetFullyQualifiedClassName("a", attr->value); StringPiece qualified_class_name = fully_qualified_class_name @@ -146,7 +146,7 @@ static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics* // Now inject the android:isFeatureSplit="true" attribute. xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, kIsFeatureSplit); if (attr != nullptr) { - if (!ResourceUtils::ParseBool(attr->value).value_or_default(false)) { + 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) @@ -163,6 +163,31 @@ static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics* return true; } +static bool AutoGenerateIsSplitRequired(xml::Element* el, SourcePathDiagnostics* diag) { + constexpr const char* kRequiredSplitTypes = "requiredSplitTypes"; + constexpr const char* kIsSplitRequired = "isSplitRequired"; + + xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, kRequiredSplitTypes); + if (attr != nullptr) { + // Now inject the android:isSplitRequired="true" attribute. + xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, kIsSplitRequired); + if (attr != nullptr) { + 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) + << "attribute 'requiredSplitTypes' used in <manifest> but " + "'android:isSplitRequired' is not 'true'"); + return false; + } + // The attribute is already there and set to true, nothing to do. + } else { + el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, kIsSplitRequired, "true"}); + } + } + return true; +} + static bool VerifyManifest(xml::Element* el, xml::XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag) { xml::Attribute* attr = el->FindAttribute({}, "package"); @@ -329,6 +354,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, // Manifest actions. xml::XmlNodeAction& manifest_action = (*executor)["manifest"]; manifest_action.Action(AutoGenerateIsFeatureSplit); + manifest_action.Action(AutoGenerateIsSplitRequired); manifest_action.Action(VerifyManifest); manifest_action.Action(FixCoreAppAttribute); manifest_action.Action([&](xml::Element* el) -> bool { @@ -482,6 +508,16 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, uses_static_library_action.Action(RequiredAndroidAttribute("certDigest")); uses_static_library_action["additional-certificate"]; + xml::XmlNodeAction& sdk_library_action = application_action["sdk-library"]; + sdk_library_action.Action(RequiredNameIsJavaPackage); + sdk_library_action.Action(RequiredAndroidAttribute("versionMajor")); + + xml::XmlNodeAction& uses_sdk_library_action = application_action["uses-sdk-library"]; + uses_sdk_library_action.Action(RequiredNameIsJavaPackage); + uses_sdk_library_action.Action(RequiredAndroidAttribute("versionMajor")); + uses_sdk_library_action.Action(RequiredAndroidAttribute("certDigest")); + uses_sdk_library_action["additional-certificate"]; + xml::XmlNodeAction& uses_package_action = application_action["uses-package"]; uses_package_action.Action(RequiredNameIsJavaPackage); uses_package_action["additional-certificate"]; @@ -508,6 +544,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, application_action["activity-alias"] = component_action; application_action["service"] = component_action; application_action["receiver"] = component_action; + application_action["apex-system-service"] = component_action; // Provider actions. application_action["provider"] = component_action; @@ -523,7 +560,8 @@ static void FullyQualifyClassName(const StringPiece& package, const StringPiece& const StringPiece& attr_name, xml::Element* el) { xml::Attribute* attr = el->FindAttribute(attr_ns, attr_name); if (attr != nullptr) { - if (Maybe<std::string> new_value = util::GetFullyQualifiedClassName(package, attr->value)) { + if (std::optional<std::string> new_value = + util::GetFullyQualifiedClassName(package, attr->value)) { attr->value = std::move(new_value.value()); } } @@ -550,10 +588,21 @@ static bool RenameManifestPackage(const StringPiece& package_override, xml::Elem child_el->name == "provider" || child_el->name == "receiver" || child_el->name == "service") { FullyQualifyClassName(original_package, xml::kSchemaAndroid, "name", child_el); + continue; } if (child_el->name == "activity-alias") { FullyQualifyClassName(original_package, xml::kSchemaAndroid, "targetActivity", child_el); + continue; + } + + if (child_el->name == "processes") { + for (xml::Element* grand_child_el : child_el->GetChildElements()) { + if (grand_child_el->name == "process") { + FullyQualifyClassName(original_package, xml::kSchemaAndroid, "name", grand_child_el); + } + } + continue; } } } diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h index 34ad8d586df1..d5d1d1770e1c 100644 --- a/tools/aapt2/link/ManifestFixer.h +++ b/tools/aapt2/link/ManifestFixer.h @@ -22,7 +22,7 @@ #include "android-base/macros.h" #include "process/IResourceTableConsumer.h" -#include "util/Maybe.h" + #include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" @@ -30,47 +30,47 @@ namespace aapt { struct ManifestFixerOptions { // The minimum SDK version to set if no 'android:minSdkVersion' is defined in a <uses-sdk> tag. - Maybe<std::string> min_sdk_version_default; + std::optional<std::string> min_sdk_version_default; // The target SDK version to set if no 'android:targetSdkVersion' is defined in a <uses-sdk> tag. - Maybe<std::string> target_sdk_version_default; + std::optional<std::string> target_sdk_version_default; // The Android package to use instead of the one defined in 'package' in <manifest>. // This also renames all relative package/class names in the manifest to fully qualified // Java names. - Maybe<std::string> rename_manifest_package; + std::optional<std::string> rename_manifest_package; // The Android package to use instead of the one defined in 'android:targetPackage' in // <instrumentation>. - Maybe<std::string> rename_instrumentation_target_package; + std::optional<std::string> rename_instrumentation_target_package; // The Android package to use instead of the one defined in 'android:targetPackage' in // <overlay>. - Maybe<std::string> rename_overlay_target_package; + std::optional<std::string> rename_overlay_target_package; // The version name to set if 'android:versionName' is not defined in <manifest> or if // replace_version is set. - Maybe<std::string> version_name_default; + std::optional<std::string> version_name_default; // The version code to set if 'android:versionCode' is not defined in <manifest> or if // replace_version is set. - Maybe<std::string> version_code_default; + std::optional<std::string> version_code_default; // The version code to set if 'android:versionCodeMajor' is not defined in <manifest> or if // replace_version is set. - Maybe<std::string> version_code_major_default; + std::optional<std::string> version_code_major_default; // The revision code to set if 'android:revisionCode' is not defined in <manifest> or if // replace_version is set. - Maybe<std::string> revision_code_default; + std::optional<std::string> revision_code_default; // The version of the framework being compiled against to set for 'android:compileSdkVersion' in // the <manifest> tag. - Maybe<std::string> compile_sdk_version; + 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. - Maybe<std::string> compile_sdk_version_codename; + std::optional<std::string> compile_sdk_version_codename; // 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 diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 4ac25bd6a0e0..5372cf243951 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -190,7 +190,8 @@ class EmptyDeclStack : public xml::IPackageDeclStack { public: EmptyDeclStack() = default; - Maybe<xml::ExtractedPackage> TransformPackageAlias(const StringPiece& alias) const override { + std::optional<xml::ExtractedPackage> TransformPackageAlias( + const StringPiece& alias) const override { if (alias.empty()) { return xml::ExtractedPackage{{}, true /*private*/}; } @@ -206,7 +207,8 @@ struct MacroDeclStack : public xml::IPackageDeclStack { : alias_namespaces_(std::move(namespaces)) { } - Maybe<xml::ExtractedPackage> TransformPackageAlias(const StringPiece& alias) const override { + std::optional<xml::ExtractedPackage> TransformPackageAlias( + const StringPiece& alias) const override { if (alias.empty()) { return xml::ExtractedPackage{{}, true /*private*/}; } @@ -322,11 +324,11 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility( return symbol; } -Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference& reference, - const CallSite& callsite, - IAaptContext* context, - SymbolTable* symbols, - std::string* out_error) { +std::optional<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference& reference, + const CallSite& callsite, + IAaptContext* context, + SymbolTable* symbols, + std::string* out_error) { const SymbolTable::Symbol* symbol = ResolveAttributeCheckVisibility(reference, callsite, context, symbols, out_error); if (!symbol) { @@ -350,7 +352,7 @@ void ReferenceLinker::WriteAttributeName(const Reference& ref, const CallSite& c } const ResourceName& ref_name = ref.name.value(); - CHECK_EQ(ref_name.type, ResourceType::kAttr); + CHECK_EQ(ref_name.type.type, ResourceType::kAttr); if (!ref_name.package.empty()) { *out_msg << ref_name.package << ":"; @@ -383,7 +385,7 @@ std::unique_ptr<Item> ReferenceLinker::LinkReference(const CallSite& callsite, Reference transformed_reference = reference; xml::ResolvePackage(decls, &transformed_reference); - if (transformed_reference.name.value().type == ResourceType::kMacro) { + if (transformed_reference.name.value().type.type == ResourceType::kMacro) { if (transformed_reference.name.value().package.empty()) { transformed_reference.name.value().package = callsite.package; } diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h index 770f1e500ac0..b46085397c52 100644 --- a/tools/aapt2/link/ReferenceLinker.h +++ b/tools/aapt2/link/ReferenceLinker.h @@ -97,11 +97,11 @@ class ReferenceLinker : public IResourceTableConsumer { // Resolves the attribute reference and returns an xml::AaptAttribute if successful. // If resolution fails, outError holds the error message. - static Maybe<xml::AaptAttribute> CompileXmlAttribute(const Reference& reference, - const CallSite& callsite, - IAaptContext* context, - SymbolTable* symbols, - std::string* out_error); + static std::optional<xml::AaptAttribute> CompileXmlAttribute(const Reference& reference, + const CallSite& callsite, + IAaptContext* context, + SymbolTable* symbols, + std::string* out_error); // Writes the resource name to the DiagMessage, using the // "orig_name (aka <transformed_name>)" syntax. diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp index 2d8f0d39053f..97bdd3ebae66 100644 --- a/tools/aapt2/link/ReferenceLinker_test.cpp +++ b/tools/aapt2/link/ReferenceLinker_test.cpp @@ -317,12 +317,12 @@ TEST(ReferenceLinkerTest, ReferenceWithNoPackageUsesCallSitePackage) { CallSite{"com.app.test"}, context.get(), &table); ASSERT_THAT(s, NotNull()); - EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x7f010000))); + EXPECT_THAT(s->id, Eq(0x7f010000)); s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), CallSite{"com.app.lib"}, context.get(), &table); ASSERT_THAT(s, NotNull()); - EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x7f010001))); + EXPECT_THAT(s->id, Eq(0x7f010001)); EXPECT_THAT(ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), CallSite{"com.app.bad"}, context.get(), &table), @@ -348,7 +348,7 @@ TEST(ReferenceLinkerTest, ReferenceSymbolFromOtherSplit) { CallSite{"com.app.test"}, context.get(), &table); ASSERT_THAT(s, NotNull()); - EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x80010000))); + EXPECT_THAT(s->id, Eq(0x80010000)); s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), CallSite{"com.app.lib"}, context.get(), &table); diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 22f4d18dc3ca..d78f0ac17f67 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -303,8 +303,8 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, dst_config_value->value = std::move(new_file_ref); } else { - Maybe<std::string> original_comment = (dst_config_value->value) - ? dst_config_value->value->GetComment() : Maybe<std::string>(); + auto original_comment = (dst_config_value->value) + ? dst_config_value->value->GetComment() : std::optional<std::string>(); dst_config_value->value = src_config_value->value->Transform(cloner); @@ -349,7 +349,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) + pkg->FindOrCreateType(file_desc.name.type.type) ->FindOrCreateEntry(file_desc.name.entry) ->FindOrCreateValue(file_desc.config, {}) ->value = std::move(file_ref); diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index 4358fb565a7d..4cbf2d3a826c 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -409,8 +409,7 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) { const auto expected = ResourceUtils::MakeBool(true); EXPECT_THAT(style->entries, Contains(Field(&Style::Entry::value, Pointee(ValueEq(*expected))))); - EXPECT_THAT(style->parent, - Eq(make_value(Reference(test::ParseNameOrDie("com.app.a:style/OverlayParent"))))); + EXPECT_THAT(style->parent, Reference(test::ParseNameOrDie("com.app.a:style/OverlayParent"))); } TEST_F(TableMergerTest, OverrideStyleInsteadOfOverlaying) { @@ -483,7 +482,7 @@ TEST_F(TableMergerTest, SetOverlayable) { ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/)); const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo"); - Maybe<ResourceTable::SearchResult> search_result = final_table.FindResource(name); + std::optional<ResourceTable::SearchResult> search_result = final_table.FindResource(name); ASSERT_TRUE(search_result); ASSERT_TRUE(search_result.value().entry->overlayable_item); OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); @@ -517,7 +516,7 @@ TEST_F(TableMergerTest, SetOverlayableLater) { ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/)); const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo"); - Maybe<ResourceTable::SearchResult> search_result = final_table.FindResource(name); + std::optional<ResourceTable::SearchResult> search_result = final_table.FindResource(name); ASSERT_TRUE(search_result); ASSERT_TRUE(search_result.value().entry->overlayable_item); OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index aaa085e2eb15..1f8548b5de75 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -68,7 +68,7 @@ class XmlVisitor : public xml::PackageAwareVisitor { const Attribute* attribute = &default_attribute; - if (Maybe<xml::ExtractedPackage> maybe_package = + if (std::optional<xml::ExtractedPackage> maybe_package = xml::ExtractPackageFromNamespace(attr.namespace_uri)) { // There is a valid package name for this attribute. We will look this up. Reference attr_ref( diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp index ddf5b9a22c2f..6d96cf1cf502 100644 --- a/tools/aapt2/link/XmlReferenceLinker_test.cpp +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -100,18 +100,18 @@ TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "layout_width"); ASSERT_THAT(xml_attr, NotNull()); ASSERT_TRUE(xml_attr->compiled_attribute); - EXPECT_EQ(make_value(ResourceId(0x01010000)), xml_attr->compiled_attribute.value().id); + EXPECT_EQ(ResourceId(0x01010000), xml_attr->compiled_attribute.value().id); EXPECT_THAT(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()), NotNull()); xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "background"); ASSERT_THAT(xml_attr, NotNull()); ASSERT_TRUE(xml_attr->compiled_attribute); - EXPECT_EQ(make_value(ResourceId(0x01010001)), xml_attr->compiled_attribute.value().id); + EXPECT_EQ(ResourceId(0x01010001), xml_attr->compiled_attribute.value().id); Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); ASSERT_THAT(ref, NotNull()); - EXPECT_EQ(make_value(test::ParseNameOrDie("color/green")), ref->name); // Make sure the name - // didn't change. - EXPECT_EQ(make_value(ResourceId(0x7f020000)), ref->id); + EXPECT_EQ(test::ParseNameOrDie("color/green"), ref->name); // Make sure the name + // didn't change. + EXPECT_EQ(ResourceId(0x7f020000), ref->id); xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "text"); ASSERT_THAT(xml_attr, NotNull()); @@ -172,7 +172,7 @@ TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { view_el->FindAttribute(xml::BuildPackageNamespace("com.android.support"), "colorAccent"); ASSERT_THAT(xml_attr, NotNull()); ASSERT_TRUE(xml_attr->compiled_attribute); - EXPECT_EQ(make_value(ResourceId(0x7f010001)), xml_attr->compiled_attribute.value().id); + EXPECT_EQ(ResourceId(0x7f010001), xml_attr->compiled_attribute.value().id); EXPECT_THAT(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()), NotNull()); } @@ -190,11 +190,11 @@ TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAuto, "colorAccent"); ASSERT_THAT(xml_attr, NotNull()); ASSERT_TRUE(xml_attr->compiled_attribute); - EXPECT_EQ(make_value(ResourceId(0x7f010000)), xml_attr->compiled_attribute.value().id); + EXPECT_EQ(ResourceId(0x7f010000), xml_attr->compiled_attribute.value().id); Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); ASSERT_THAT(ref, NotNull()); ASSERT_TRUE(ref->name); - EXPECT_EQ(make_value(ResourceId(0x7f020001)), ref->id); + EXPECT_EQ(ResourceId(0x7f020001), ref->id); } TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { @@ -214,10 +214,10 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "attr"); ASSERT_THAT(xml_attr, NotNull()); ASSERT_TRUE(xml_attr->compiled_attribute); - EXPECT_EQ(make_value(ResourceId(0x01010002)), xml_attr->compiled_attribute.value().id); + EXPECT_EQ(ResourceId(0x01010002), xml_attr->compiled_attribute.value().id); Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); ASSERT_THAT(ref, NotNull()); - EXPECT_EQ(make_value(ResourceId(0x01030000)), ref->id); + EXPECT_EQ(ResourceId(0x01030000), ref->id); ASSERT_FALSE(view_el->GetChildElements().empty()); view_el = view_el->GetChildElements().front(); @@ -228,10 +228,10 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { xml_attr = view_el->FindAttribute(xml::BuildPackageNamespace("com.app.test"), "attr"); ASSERT_THAT(xml_attr, NotNull()); ASSERT_TRUE(xml_attr->compiled_attribute); - EXPECT_EQ(make_value(ResourceId(0x7f010002)), xml_attr->compiled_attribute.value().id); + EXPECT_EQ(ResourceId(0x7f010002), xml_attr->compiled_attribute.value().id); ref = ValueCast<Reference>(xml_attr->compiled_value.get()); ASSERT_THAT(ref, NotNull()); - EXPECT_EQ(make_value(ResourceId(0x7f030000)), ref->id); + EXPECT_EQ(ResourceId(0x7f030000), ref->id); } TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { @@ -250,10 +250,10 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { xml::Attribute* xml_attr = view_el->FindAttribute(xml::BuildPackageNamespace("com.app.test"), "attr"); ASSERT_THAT(xml_attr, NotNull()); ASSERT_TRUE(xml_attr->compiled_attribute); - EXPECT_EQ(make_value(ResourceId(0x7f010002)), xml_attr->compiled_attribute.value().id); + EXPECT_EQ(ResourceId(0x7f010002), xml_attr->compiled_attribute.value().id); Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); ASSERT_THAT(ref, NotNull()); - EXPECT_EQ(make_value(ResourceId(0x7f030000)), ref->id); + EXPECT_EQ(ResourceId(0x7f030000), ref->id); } @@ -270,7 +270,7 @@ TEST_F(XmlReferenceLinkerTest, AddAngleOnGradientForAndroidQ) { xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle"); ASSERT_THAT(xml_attr, NotNull()); ASSERT_TRUE(xml_attr->compiled_attribute); - EXPECT_EQ(make_value(ResourceId(0x01010004)), xml_attr->compiled_attribute.value().id); + EXPECT_EQ(ResourceId(0x01010004), xml_attr->compiled_attribute.value().id); BinaryPrimitive* value = ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()); ASSERT_THAT(value, NotNull()); @@ -292,7 +292,7 @@ TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForAndroidQ) { xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle"); ASSERT_THAT(xml_attr, NotNull()); ASSERT_TRUE(xml_attr->compiled_attribute); - EXPECT_EQ(make_value(ResourceId(0x01010004)), xml_attr->compiled_attribute.value().id); + EXPECT_EQ(ResourceId(0x01010004), xml_attr->compiled_attribute.value().id); BinaryPrimitive* value = ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()); ASSERT_THAT(value, NotNull()); diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index d385267fe5ed..92b45c397eed 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -75,8 +75,8 @@ const SymbolTable::Symbol* SymbolTable::FindByName(const ResourceName& name) { // Fill in the package name if necessary. // If there is no package in `name`, we will need to copy the ResourceName - // and store it somewhere; we use the Maybe<> class to reserve storage. - Maybe<ResourceName> name_with_package_impl; + // and store it somewhere; we use the std::optional<> class to reserve storage. + std::optional<ResourceName> name_with_package_impl; if (name.package.empty()) { name_with_package_impl = ResourceName(mangler_->GetTargetPackageName(), name.type, name.entry); name_with_package = &name_with_package_impl.value(); @@ -88,9 +88,9 @@ const SymbolTable::Symbol* SymbolTable::FindByName(const ResourceName& name) { } // The name was not found in the cache. Mangle it (if necessary) and find it in our sources. - // Again, here we use a Maybe<> object to reserve storage if we need to mangle. + // Again, here we use a std::optional<> object to reserve storage if we need to mangle. const ResourceName* mangled_name = name_with_package; - Maybe<ResourceName> mangled_name_impl; + std::optional<ResourceName> mangled_name_impl; if (mangler_->ShouldMangle(name_with_package->package)) { mangled_name_impl = mangler_->MangleName(*name_with_package); mangled_name = &mangled_name_impl.value(); @@ -183,9 +183,9 @@ std::unique_ptr<SymbolTable::Symbol> DefaultSymbolTableDelegate::FindById( std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName( const ResourceName& name) { - Maybe<ResourceTable::SearchResult> result = table_->FindResource(name); + std::optional<ResourceTable::SearchResult> result = table_->FindResource(name); if (!result) { - if (name.type == ResourceType::kAttr) { + if (name.type.type == ResourceType::kAttr) { // Recurse and try looking up a private attribute. return FindByName(ResourceName(name.package, ResourceType::kAttrPrivate, name.entry)); } @@ -203,7 +203,7 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName( (sr.entry->id.value().package_id() == 0) || sr.entry->visibility.staged_api; } - if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) { + if (name.type.type == ResourceType::kAttr || name.type.type == ResourceType::kAttrPrivate) { const ConfigDescription kDefaultConfig; ResourceConfigValue* config_value = sr.entry->FindValue(kDefaultConfig); if (config_value) { @@ -306,7 +306,7 @@ static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable( return nullptr; } - Maybe<ResourceName> parsed_name = ResourceUtils::ToResourceName(*name); + std::optional<ResourceName> parsed_name = ResourceUtils::ToResourceName(*name); if (!parsed_name) { return nullptr; } @@ -366,7 +366,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( } std::unique_ptr<SymbolTable::Symbol> s; - if (real_name.type == ResourceType::kAttr) { + if (real_name.type.type == ResourceType::kAttr) { s = LookupAttributeInTable(asset_manager_, res_id); } else { s = util::make_unique<SymbolTable::Symbol>(); @@ -382,8 +382,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( return {}; } -static Maybe<ResourceName> GetResourceName(android::AssetManager2& am, - ResourceId id) { +static std::optional<ResourceName> GetResourceName(android::AssetManager2& am, ResourceId id) { auto name = am.GetResourceName(id.id); if (!name.has_value()) { return {}; @@ -402,7 +401,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( return {}; } - Maybe<ResourceName> maybe_name = GetResourceName(asset_manager_, id); + std::optional<ResourceName> maybe_name = GetResourceName(asset_manager_, id); if (!maybe_name) { return {}; } @@ -414,7 +413,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( ResourceName& name = maybe_name.value(); std::unique_ptr<SymbolTable::Symbol> s; - if (name.type == ResourceType::kAttr) { + if (name.type.type == ResourceType::kAttr) { s = LookupAttributeInTable(asset_manager_, id); } else { s = util::make_unique<SymbolTable::Symbol>(); diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index 06eaf63ad442..c17837c224ab 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -38,7 +38,7 @@ inline android::hash_t hash_type(const ResourceName& name) { std::hash<std::string> str_hash; android::hash_t hash = 0; hash = android::JenkinsHashMix(hash, (uint32_t)str_hash(name.package)); - hash = android::JenkinsHashMix(hash, (uint32_t)name.type); + hash = android::JenkinsHashMix(hash, (uint32_t)str_hash(name.type.name)); hash = android::JenkinsHashMix(hash, (uint32_t)str_hash(name.entry)); return hash; } @@ -56,7 +56,7 @@ class SymbolTable { struct Symbol { Symbol() = default; - explicit Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr = {}, + explicit Symbol(const std::optional<ResourceId>& i, const std::shared_ptr<Attribute>& attr = {}, bool pub = false) : id(i), attribute(attr), is_public(pub) { } @@ -66,7 +66,7 @@ class SymbolTable { Symbol& operator=(const Symbol&) = default; Symbol& operator=(Symbol&&) = default; - Maybe<ResourceId> id; + std::optional<ResourceId> id; std::shared_ptr<Attribute> attribute; bool is_public = false; bool is_dynamic = false; diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp index 4816596da487..23331de02df7 100644 --- a/tools/aapt2/test/Builders.cpp +++ b/tools/aapt2/test/Builders.cpp @@ -159,7 +159,8 @@ std::unique_ptr<ResourceTable> ResourceTableBuilder::Build() { return std::move(table_); } -std::unique_ptr<Reference> BuildReference(const StringPiece& ref, const Maybe<ResourceId>& id) { +std::unique_ptr<Reference> BuildReference(const StringPiece& ref, + const std::optional<ResourceId>& id) { std::unique_ptr<Reference> reference = util::make_unique<Reference>(ParseNameOrDie(ref)); reference->id = id; return reference; @@ -218,7 +219,8 @@ std::unique_ptr<Style> StyleBuilder::Build() { return std::move(style_); } -StyleableBuilder& StyleableBuilder::AddItem(const StringPiece& str, const Maybe<ResourceId>& id) { +StyleableBuilder& StyleableBuilder::AddItem(const StringPiece& str, + const std::optional<ResourceId>& id) { styleable_->entries.push_back(Reference(ParseNameOrDie(str))); styleable_->entries.back().id = id; return *this; diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 3ff955d65f24..55778aea40af 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -29,7 +29,6 @@ #include "configuration/ConfigurationParser.internal.h" #include "process/IResourceTableConsumer.h" #include "test/Common.h" -#include "util/Maybe.h" #include "xml/XmlDom.h" namespace aapt { @@ -86,7 +85,7 @@ class ResourceTableBuilder { }; std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref, - const Maybe<ResourceId>& id = {}); + const std::optional<ResourceId>& id = {}); std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data); template <typename T> @@ -149,7 +148,8 @@ class StyleBuilder { class StyleableBuilder { public: StyleableBuilder() = default; - StyleableBuilder& AddItem(const android::StringPiece& str, const Maybe<ResourceId>& id = {}); + StyleableBuilder& AddItem(const android::StringPiece& str, + const std::optional<ResourceId>& id = {}); std::unique_ptr<Styleable> Build(); private: diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp index 23c22185a53f..e029d025b366 100644 --- a/tools/aapt2/test/Common.cpp +++ b/tools/aapt2/test/Common.cpp @@ -48,7 +48,7 @@ Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, const android::StringPiece& res_name, const ConfigDescription& config, const android::StringPiece& product) { - Maybe<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name)); + std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name)); if (result) { ResourceConfigValue* config_value = result.value().entry->FindValue(config, product); if (config_value) { diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 777ca5c72619..7006964d6f88 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -55,7 +55,7 @@ template <typename T = Value> T* GetValueForConfigAndProduct(ResourceTable* table, const android::StringPiece& res_name, const android::ConfigDescription& config, const android::StringPiece& product) { - Maybe<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name)); + std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name)); if (result) { ResourceConfigValue* config_value = result.value().entry->FindValue(config, product); if (config_value) { @@ -130,7 +130,7 @@ template std::ostream& operator<<<Plural>(std::ostream&, const Plural&); // Add a print method to Maybe. template <typename T> -void PrintTo(const Maybe<T>& value, std::ostream* out) { +void PrintTo(const std::optional<T>& value, std::ostream* out) { if (value) { *out << ::testing::PrintToString(value.value()); } else { diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h index 5d8ded39e654..e1b8dd5687ff 100644 --- a/tools/aapt2/test/Context.h +++ b/tools/aapt2/test/Context.h @@ -95,8 +95,8 @@ class Context : public IAaptContext { friend class ContextBuilder; PackageType package_type_ = PackageType::kApp; - Maybe<std::string> compilation_package_; - Maybe<uint8_t> package_id_; + std::optional<std::string> compilation_package_; + std::optional<uint8_t> package_id_; StdErrDiagnostics diagnostics_; NameMangler name_mangler_; SymbolTable symbols_; diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp index 285e5a11b4c0..ddc1853ca13c 100644 --- a/tools/aapt2/test/Fixture.cpp +++ b/tools/aapt2/test/Fixture.cpp @@ -67,8 +67,7 @@ void ClearDirectory(const android::StringPiece& path) { } void TestDirectoryFixture::SetUp() { - temp_dir_ = file::BuildPath({android::base::GetExecutableDirectory(), - "_temp", + temp_dir_ = file::BuildPath({testing::TempDir(), "_temp", testing::UnitTest::GetInstance()->current_test_case()->name(), testing::UnitTest::GetInstance()->current_test_info()->name()}); ASSERT_TRUE(file::mkdirs(temp_dir_)); @@ -126,7 +125,7 @@ bool CommandTestFixture::Link(const std::vector<std::string>& args, link_args.insert(link_args.end(), {"-I", android_sdk}); // Add the files from the compiled resources directory to the link file arguments - Maybe<std::vector<std::string>> compiled_files = file::FindFiles(flat_dir, diag); + std::optional<std::vector<std::string>> compiled_files = file::FindFiles(flat_dir, diag); if (compiled_files) { for (std::string& compile_file : compiled_files.value()) { compile_file = file::BuildPath({flat_dir, compile_file}); @@ -236,4 +235,4 @@ std::vector<std::string> LinkCommandBuilder::Build(const std::string& out_apk) { return args_; } -} // namespace aapt
\ No newline at end of file +} // namespace aapt diff --git a/tools/aapt2/tools/finalize_res.py b/tools/aapt2/tools/finalize_res.py new file mode 100755 index 000000000000..0e4d865bc890 --- /dev/null +++ b/tools/aapt2/tools/finalize_res.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +# -*- coding: 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. +# +# 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. + +""" +Finalize resource values in <staging-public-group> tags +and convert those to <staging-public-group-final> + +Usage: $ANDROID_BUILD_TOP/frameworks/base/tools/aapt2/tools/finalize_res.py \ + $ANDROID_BUILD_TOP/frameworks/base/core/res/res/values/public-staging.xml \ + $ANDROID_BUILD_TOP/frameworks/base/core/res/res/values/public-final.xml +""" + +import re +import sys + +resTypes = ["attr", "id", "style", "string", "dimen", "color", "array", "drawable", "layout", + "anim", "animator", "interpolator", "mipmap", "integer", "transition", "raw", "bool", + "fraction"] + +_type_ids = {} +_type = "" + +_lowest_staging_first_id = 0x01FFFFFF + +""" + Created finalized <public> declarations for staging resources, ignoring them if they've been + prefixed with removed_. The IDs are assigned without holes starting from the last ID for that + type currently finalized in public-final.xml. +""" +def finalize_item(raw): + name = raw.group(1) + if re.match(r'_*removed.+', name): + return "" + id = _type_ids[_type] + _type_ids[_type] += 1 + return ' <public type="%s" name="%s" id="%s" />\n' % (_type, name, '0x{0:0{1}x}'.format(id, 8)) + + +""" + Finalizes staging-public-groups if they have any entries in them. Also keeps track of the + lowest first-id of the non-empty groups so that the next release's staging-public-groups can + be assigned the next down shifted first-id. +""" +def finalize_group(raw): + global _type, _lowest_staging_first_id + _type = raw.group(1) + id = int(raw.group(2), 16) + _type_ids[_type] = _type_ids.get(_type, id) + (res, count) = re.subn(' {0,4}<public name="(.+?)" */>\n', finalize_item, raw.group(3)) + if count > 0: + res = raw.group(0).replace("staging-public-group", + "staging-public-group-final") + '\n' + res + _lowest_staging_first_id = min(id, _lowest_staging_first_id) + return res + +""" + Collects the max ID for each resType so that the new IDs can be assigned afterwards +""" +def collect_ids(raw): + for m in re.finditer(r'<public type="(.+?)" name=".+?" id="(.+?)" />', raw): + type = m.group(1) + id = int(m.group(2), 16) + _type_ids[type] = max(id + 1, _type_ids.get(type, 0)) + + +with open(sys.argv[1], "r+") as stagingFile: + with open(sys.argv[2], "r+") as finalFile: + existing = finalFile.read() + # Cut out the closing resources tag so that it can be concatenated easily later + existing = "\n".join(existing.rsplit("</resources>", 1)) + + # Collect the IDs from the existing already finalized resources + collect_ids(existing) + + staging = stagingFile.read() + stagingSplit = staging.rsplit("<resources>") + staging = stagingSplit[1] + staging = re.sub( + r'<staging-public-group type="(.+?)" first-id="(.+?)">(.+?)</staging-public-group>', + finalize_group, staging, flags=re.DOTALL) + staging = re.sub(r' *\n', '\n', staging) + staging = re.sub(r'\n{3,}', '\n\n', staging) + + # First write the existing finalized declarations and then append the new stuff + finalFile.seek(0) + finalFile.write(existing.strip("\n")) + finalFile.write("\n\n") + finalFile.write(staging.strip("\n")) + finalFile.write("\n") + finalFile.truncate() + + stagingFile.seek(0) + # Include the documentation from public-staging.xml that was previously split out + stagingFile.write(stagingSplit[0]) + # Write the next platform header + stagingFile.write("<resources>\n\n") + stagingFile.write(" <!-- ===============================================================\n") + stagingFile.write(" Resources added in version NEXT of the platform\n\n") + stagingFile.write(" NOTE: After this version of the platform is forked, changes cannot be made to the root\n") + stagingFile.write(" branch's groups for that release. Only merge changes to the forked platform branch.\n") + stagingFile.write(" =============================================================== -->\n") + stagingFile.write(" <eat-comment/>\n\n") + + # Seed the next release's staging-public-groups as empty declarations, + # so its easy for another developer to expose a new public resource + nextId = _lowest_staging_first_id - 0x00010000 + for resType in resTypes: + stagingFile.write(' <staging-public-group type="%s" first-id="%s">\n' + ' </staging-public-group>\n\n' % + (resType, '0x{0:0{1}x}'.format(nextId, 8))) + nextId -= 0x00010000 + + # Close the resources tag and truncate, since the file will be shorter than the previous + stagingFile.write("</resources>\n") + stagingFile.truncate() diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp index 5d57de6a9fb1..3285d8bafa4d 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -50,12 +50,12 @@ namespace file { FileType GetFileType(const std::string& path) { std::wstring path_utf16; if (!::android::base::UTF8PathToWindowsLongPath(path.c_str(), &path_utf16)) { - return FileType::kNonexistant; + return FileType::kNonExistant; } DWORD result = GetFileAttributesW(path_utf16.c_str()); if (result == INVALID_FILE_ATTRIBUTES) { - return FileType::kNonexistant; + return FileType::kNonExistant; } if (result & FILE_ATTRIBUTE_DIRECTORY) { @@ -72,7 +72,7 @@ FileType GetFileType(const std::string& path) { if (result == -1) { if (errno == ENOENT || errno == ENOTDIR) { - return FileType::kNonexistant; + return FileType::kNonExistant; } return FileType::kUnknown; } @@ -154,7 +154,7 @@ StringPiece GetFilename(const StringPiece& path) { const char* end = path.end(); const char* last_dir_sep = path.begin(); for (const char* c = path.begin(); c != end; ++c) { - if (*c == sDirSep) { + if (*c == sDirSep || *c == sInvariantDirSep) { last_dir_sep = c + 1; } } @@ -208,7 +208,7 @@ std::string PackageToPath(const StringPiece& package) { return out_path; } -Maybe<FileMap> MmapPath(const std::string& path, std::string* out_error) { +std::optional<FileMap> MmapPath(const std::string& path, std::string* out_error) { int flags = O_RDONLY | O_CLOEXEC | O_BINARY; unique_fd fd(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), flags))); if (fd == -1) { @@ -344,12 +344,12 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const { return true; } -Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDiagnostics* diag, - const FileFilter* filter) { +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::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir); if (!d) { - diag->Error(DiagMessage() << SystemErrorCodeToString(errno)); + diag->Error(DiagMessage() << SystemErrorCodeToString(errno) << ": " << root_dir); return {}; } @@ -382,7 +382,7 @@ Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDia for (const std::string& subdir : subdirs) { std::string full_subdir = root_dir; AppendPath(&full_subdir, subdir); - Maybe<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter); + std::optional<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter); if (!subfiles) { return {}; } diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h index 481a4cdb6ad0..a2b1b58e5d4f 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -18,6 +18,7 @@ #define AAPT_FILES_H #include <memory> +#include <optional> #include <string> #include <unordered_set> #include <vector> @@ -27,7 +28,6 @@ #include "utils/FileMap.h" #include "Diagnostics.h" -#include "Maybe.h" #include "Source.h" namespace aapt { @@ -41,9 +41,11 @@ constexpr const char sDirSep = '/'; constexpr const char sPathSep = ':'; #endif +constexpr const char sInvariantDirSep = '/'; + enum class FileType { kUnknown = 0, - kNonexistant, + kNonExistant, kRegular, kDirectory, kCharDev, @@ -81,7 +83,7 @@ bool IsHidden(const android::StringPiece& path); std::string PackageToPath(const android::StringPiece& package); // Creates a FileMap for the file at path. -Maybe<android::FileMap> MmapPath(const std::string& path, std::string* out_error); +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, @@ -124,8 +126,9 @@ class FileFilter { // Returns a list of files relative to the directory identified by `path`. // An optional FileFilter filters out any files that don't pass. -Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDiagnostics* diag, - const FileFilter* filter = nullptr); +std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path, + IDiagnostics* diag, + const FileFilter* filter = nullptr); } // namespace file } // namespace aapt diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h deleted file mode 100644 index 047e1a581330..000000000000 --- a/tools/aapt2/util/Maybe.h +++ /dev/null @@ -1,324 +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_MAYBE_H -#define AAPT_MAYBE_H - -#include <type_traits> -#include <utility> - -#include "android-base/logging.h" - -#include "util/TypeTraits.h" - -namespace aapt { - -/** - * Either holds a valid value of type T, or holds Nothing. - * The value is stored inline in this structure, so no - * heap memory is used when creating a Maybe<T> object. - */ -template <typename T> -class Maybe { - public: - /** - * Construct Nothing. - */ - Maybe(); - - ~Maybe(); - - Maybe(const Maybe& rhs); - - template <typename U> - Maybe(const Maybe<U>& rhs); // NOLINT(google-explicit-constructor) - - Maybe(Maybe&& rhs) noexcept; - - template <typename U> - Maybe(Maybe<U>&& rhs); // NOLINT(google-explicit-constructor) - - Maybe& operator=(const Maybe& rhs); - - template <typename U> - Maybe& operator=(const Maybe<U>& rhs); - - Maybe& operator=(Maybe&& rhs) noexcept; - - template <typename U> - Maybe& operator=(Maybe<U>&& rhs); - - /** - * Construct a Maybe holding a value. - */ - Maybe(const T& value); // NOLINT(google-explicit-constructor) - - /** - * Construct a Maybe holding a value. - */ - Maybe(T&& value); // NOLINT(google-explicit-constructor) - - /** - * True if this holds a value, false if - * it holds Nothing. - */ - explicit operator bool() const; - - /** - * Gets the value if one exists, or else - * panics. - */ - T& value(); - - /** - * Gets the value if one exists, or else - * panics. - */ - const T& value() const; - - T value_or_default(const T& def) const; - - private: - template <typename U> - friend class Maybe; - - template <typename U> - Maybe& copy(const Maybe<U>& rhs); - - template <typename U> - Maybe& move(Maybe<U>&& rhs); - - void destroy(); - - bool nothing_; - - typename std::aligned_storage<sizeof(T), alignof(T)>::type storage_; -}; - -template <typename T> -Maybe<T>::Maybe() : nothing_(true) {} - -template <typename T> -Maybe<T>::~Maybe() { - if (!nothing_) { - destroy(); - } -} - -template <typename T> -Maybe<T>::Maybe(const Maybe& rhs) : nothing_(rhs.nothing_) { - if (!rhs.nothing_) { - new (&storage_) T(reinterpret_cast<const T&>(rhs.storage_)); - } -} - -template <typename T> -template <typename U> -Maybe<T>::Maybe(const Maybe<U>& rhs) : nothing_(rhs.nothing_) { - if (!rhs.nothing_) { - new (&storage_) T(reinterpret_cast<const U&>(rhs.storage_)); - } -} - -template <typename T> -Maybe<T>::Maybe(Maybe&& rhs) noexcept : nothing_(rhs.nothing_) { - if (!rhs.nothing_) { - rhs.nothing_ = true; - - // Move the value from rhs. - new (&storage_) T(std::move(reinterpret_cast<T&>(rhs.storage_))); - rhs.destroy(); - } -} - -template <typename T> -template <typename U> -Maybe<T>::Maybe(Maybe<U>&& rhs) : nothing_(rhs.nothing_) { - if (!rhs.nothing_) { - rhs.nothing_ = true; - - // Move the value from rhs. - new (&storage_) T(std::move(reinterpret_cast<U&>(rhs.storage_))); - rhs.destroy(); - } -} - -template <typename T> -inline Maybe<T>& Maybe<T>::operator=(const Maybe& rhs) { - // Delegate to the actual assignment. - return copy(rhs); -} - -template <typename T> -template <typename U> -inline Maybe<T>& Maybe<T>::operator=(const Maybe<U>& rhs) { - return copy(rhs); -} - -template <typename T> -template <typename U> -Maybe<T>& Maybe<T>::copy(const Maybe<U>& rhs) { - if (nothing_ && rhs.nothing_) { - // Both are nothing, nothing to do. - return *this; - } else if (!nothing_ && !rhs.nothing_) { - // We both are something, so assign rhs to us. - reinterpret_cast<T&>(storage_) = reinterpret_cast<const U&>(rhs.storage_); - } else if (nothing_) { - // We are nothing but rhs is something. - nothing_ = rhs.nothing_; - - // Copy the value from rhs. - new (&storage_) T(reinterpret_cast<const U&>(rhs.storage_)); - } else { - // We are something but rhs is nothing, so destroy our value. - nothing_ = rhs.nothing_; - destroy(); - } - return *this; -} - -template <typename T> -inline Maybe<T>& Maybe<T>::operator=(Maybe&& rhs) noexcept { - // Delegate to the actual assignment. - return move(std::forward<Maybe<T>>(rhs)); -} - -template <typename T> -template <typename U> -inline Maybe<T>& Maybe<T>::operator=(Maybe<U>&& rhs) { - return move(std::forward<Maybe<U>>(rhs)); -} - -template <typename T> -template <typename U> -Maybe<T>& Maybe<T>::move(Maybe<U>&& rhs) { - if (nothing_ && rhs.nothing_) { - // Both are nothing, nothing to do. - return *this; - } else if (!nothing_ && !rhs.nothing_) { - // We both are something, so move assign rhs to us. - rhs.nothing_ = true; - reinterpret_cast<T&>(storage_) = - std::move(reinterpret_cast<U&>(rhs.storage_)); - rhs.destroy(); - } else if (nothing_) { - // We are nothing but rhs is something. - nothing_ = false; - rhs.nothing_ = true; - - // Move the value from rhs. - new (&storage_) T(std::move(reinterpret_cast<U&>(rhs.storage_))); - rhs.destroy(); - } else { - // We are something but rhs is nothing, so destroy our value. - nothing_ = true; - destroy(); - } - return *this; -} - -template <typename T> -Maybe<T>::Maybe(const T& value) : nothing_(false) { - new (&storage_) T(value); -} - -template <typename T> -Maybe<T>::Maybe(T&& value) : nothing_(false) { - new (&storage_) T(std::forward<T>(value)); -} - -template <typename T> -Maybe<T>::operator bool() const { - return !nothing_; -} - -template <typename T> -T& Maybe<T>::value() { - CHECK(!nothing_) << "Maybe<T>::value() called on Nothing"; - return reinterpret_cast<T&>(storage_); -} - -template <typename T> -const T& Maybe<T>::value() const { - CHECK(!nothing_) << "Maybe<T>::value() called on Nothing"; - return reinterpret_cast<const T&>(storage_); -} - -template <typename T> -T Maybe<T>::value_or_default(const T& def) const { - if (nothing_) { - return def; - } - return reinterpret_cast<const T&>(storage_); -} - -template <typename T> -void Maybe<T>::destroy() { - reinterpret_cast<T&>(storage_).~T(); -} - -template <typename T> -inline Maybe<typename std::remove_reference<T>::type> make_value(T&& value) { - return Maybe<typename std::remove_reference<T>::type>(std::forward<T>(value)); -} - -template <typename T> -inline Maybe<T> make_nothing() { - return Maybe<T>(); -} - -// Define the == operator between Maybe<T> and Maybe<U> only if the operator T == U is defined. -// That way the compiler will show an error at the callsite when comparing two Maybe<> objects -// whose inner types can't be compared. -template <typename T, typename U> -typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(const Maybe<T>& a, - const Maybe<U>& b) { - if (a && b) { - return a.value() == b.value(); - } else if (!a && !b) { - return true; - } - return false; -} - -template <typename T, typename U> -typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(const Maybe<T>& a, - const U& b) { - return a ? a.value() == b : false; -} - -// Same as operator== but negated. -template <typename T, typename U> -typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=(const Maybe<T>& a, - const Maybe<U>& b) { - return !(a == b); -} - -template <typename T, typename U> -typename std::enable_if<has_lt_op<T, U>::value, bool>::type operator<(const Maybe<T>& a, - const Maybe<U>& b) { - if (a && b) { - return a.value() < b.value(); - } else if (!a && !b) { - return false; - } - return !a; -} - -} // namespace aapt - -#endif // AAPT_MAYBE_H diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp deleted file mode 100644 index 4c921f13a3ca..000000000000 --- a/tools/aapt2/util/Maybe_test.cpp +++ /dev/null @@ -1,129 +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/Maybe.h" - -#include <string> - -#include "test/Test.h" - -namespace aapt { - -struct Fake { - Fake() { - data = new int; - *data = 1; - std::cerr << "Construct Fake{0x" << (void*)this << "} with data=0x" - << (void*)data << std::endl; - } - - Fake(const Fake& rhs) { - data = nullptr; - if (rhs.data) { - data = new int; - *data = *rhs.data; - } - std::cerr << "CopyConstruct Fake{0x" << (void*)this << "} from Fake{0x" - << (const void*)&rhs << "}" << std::endl; - } - - Fake(Fake&& rhs) { - data = rhs.data; - rhs.data = nullptr; - std::cerr << "MoveConstruct Fake{0x" << (void*)this << "} from Fake{0x" - << (const void*)&rhs << "}" << std::endl; - } - - Fake& operator=(const Fake& rhs) { - delete data; - data = nullptr; - - if (rhs.data) { - data = new int; - *data = *rhs.data; - } - std::cerr << "CopyAssign Fake{0x" << (void*)this << "} from Fake{0x" - << (const void*)&rhs << "}" << std::endl; - return *this; - } - - Fake& operator=(Fake&& rhs) { - delete data; - data = rhs.data; - rhs.data = nullptr; - std::cerr << "MoveAssign Fake{0x" << (void*)this << "} from Fake{0x" - << (const void*)&rhs << "}" << std::endl; - return *this; - } - - ~Fake() { - std::cerr << "Destruct Fake{0x" << (void*)this << "} with data=0x" - << (void*)data << std::endl; - delete data; - } - - int* data; -}; - -TEST(MaybeTest, MakeNothing) { - Maybe<int> val = make_nothing<int>(); - EXPECT_FALSE(val); - - Maybe<std::string> val2 = make_nothing<std::string>(); - EXPECT_FALSE(val2); - - val2 = make_nothing<std::string>(); - EXPECT_FALSE(val2); -} - -TEST(MaybeTest, MakeSomething) { - Maybe<int> val = make_value(23); - ASSERT_TRUE(val); - EXPECT_EQ(23, val.value()); - - Maybe<std::string> val2 = make_value(std::string("hey")); - ASSERT_TRUE(val2); - EXPECT_EQ(std::string("hey"), val2.value()); -} - -TEST(MaybeTest, Lifecycle) { - Maybe<Fake> val = make_nothing<Fake>(); - - Maybe<Fake> val2 = make_value(Fake()); -} - -TEST(MaybeTest, MoveAssign) { - Maybe<Fake> val; - { - Maybe<Fake> val2 = Fake(); - val = std::move(val2); - } -} - -TEST(MaybeTest, Equality) { - Maybe<int> a = 1; - Maybe<int> b = 1; - Maybe<int> c; - - Maybe<int> emptyA, emptyB; - - EXPECT_EQ(a, b); - EXPECT_EQ(b, a); - EXPECT_NE(a, c); - EXPECT_EQ(emptyA, emptyB); -} - -} // namespace aapt diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index d7a8e6fe6ada..efbbf8ebe013 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -22,13 +22,12 @@ #include <vector> #include "android-base/stringprintf.h" +#include "android-base/strings.h" #include "androidfw/StringPiece.h" #include "build/version.h" - #include "text/Unicode.h" #include "text/Utf8Iterator.h" #include "util/BigBuffer.h" -#include "util/Maybe.h" #include "utils/Unicode.h" using ::aapt::text::Utf8Iterator; @@ -193,8 +192,8 @@ bool IsAndroidSplitName(const StringPiece& str) { return IsAndroidNameImpl(str) > 0; } -Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package, - const StringPiece& classname) { +std::optional<std::string> GetFullyQualifiedClassName(const StringPiece& package, + const StringPiece& classname) { if (classname.empty()) { return {}; } @@ -232,7 +231,14 @@ std::string GetToolFingerprint() { static const char* const sMinorVersion = "19"; // The build id of aapt2 binary. - static const std::string sBuildId = android::build::GetBuildNumber(); + static std::string sBuildId = android::build::GetBuildNumber(); + + if (android::base::StartsWith(sBuildId, "eng.")) { + time_t now = time(0); + tm* ltm = localtime(&now); + + sBuildId = android::base::StringPrintf("eng.%d%d", 1900 + ltm->tm_year, 1 + ltm->tm_mon); + } return android::base::StringPrintf("%s.%s-%s", sMajorVersion, sMinorVersion, sBuildId.c_str()); } diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index c77aca31a810..c3efe6a63feb 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -28,7 +28,6 @@ #include "utils/ByteOrder.h" #include "util/BigBuffer.h" -#include "util/Maybe.h" #ifdef _WIN32 // TODO(adamlesinski): remove once http://b/32447322 is resolved. @@ -105,8 +104,8 @@ bool IsAndroidSharedUserId(const android::StringPiece& package_name, // .asdf --> package.asdf // .a.b --> package.a.b // asdf.adsf --> asdf.adsf -Maybe<std::string> GetFullyQualifiedClassName(const android::StringPiece& package, - const android::StringPiece& class_name); +std::optional<std::string> GetFullyQualifiedClassName(const android::StringPiece& package, + const android::StringPiece& class_name); // Retrieves the formatted name of aapt2. const char* GetToolName(); diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index 2cdcfe45b50e..8b7eadf9fac9 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -545,7 +545,7 @@ void Text::Accept(ConstVisitor* visitor) const { void PackageAwareVisitor::BeforeVisitElement(Element* el) { std::vector<PackageDecl> decls; for (const NamespaceDecl& decl : el->namespace_decls) { - if (Maybe<ExtractedPackage> maybe_package = ExtractPackageFromNamespace(decl.uri)) { + if (std::optional<ExtractedPackage> maybe_package = ExtractPackageFromNamespace(decl.uri)) { decls.push_back(PackageDecl{decl.prefix, std::move(maybe_package.value())}); } } @@ -556,7 +556,8 @@ void PackageAwareVisitor::AfterVisitElement(Element* el) { package_decls_.pop_back(); } -Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias(const StringPiece& alias) const { +std::optional<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias( + 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 a5b2d10fc9e0..5d31804d43b7 100644 --- a/tools/aapt2/xml/XmlDom.h +++ b/tools/aapt2/xml/XmlDom.h @@ -65,12 +65,12 @@ struct NamespaceDecl { }; struct AaptAttribute { - explicit AaptAttribute(const ::aapt::Attribute& attr, const Maybe<ResourceId>& resid = {}) + explicit AaptAttribute(const ::aapt::Attribute& attr, const std::optional<ResourceId>& resid = {}) : attribute(attr), id(resid) { } aapt::Attribute attribute; - Maybe<ResourceId> id; + std::optional<ResourceId> id; }; // An XML attribute. @@ -79,7 +79,7 @@ struct Attribute { std::string name; std::string value; - Maybe<AaptAttribute> compiled_attribute; + std::optional<AaptAttribute> compiled_attribute; std::unique_ptr<Item> compiled_value; }; @@ -235,7 +235,8 @@ class PackageAwareVisitor : public Visitor, public IPackageDeclStack { public: using Visitor::Visit; - Maybe<ExtractedPackage> TransformPackageAlias(const android::StringPiece& alias) const override; + std::optional<ExtractedPackage> TransformPackageAlias( + const 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 ca46d539fe3c..6c717dcd84c8 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -98,7 +98,7 @@ TEST(XmlDomTest, BinaryInflate) { // the Attribute accepts (eg: string|reference). ASSERT_TRUE(new_doc->root->attributes[0].compiled_attribute); EXPECT_THAT(new_doc->root->attributes[0].compiled_attribute.value().id, - Eq(make_value(ResourceId(0x01010001u)))); + Eq(ResourceId(0x01010001u))); EXPECT_THAT(new_doc->root->attributes[0].value, StrEq("@string/foo")); EXPECT_THAT(new_doc->root->attributes[0].compiled_value, @@ -145,21 +145,19 @@ class TestVisitor : public PackageAwareVisitor { void Visit(Element* el) override { if (el->name == "View1") { - EXPECT_THAT(TransformPackageAlias("one"), Eq(make_value(ExtractedPackage{"com.one", false}))); + EXPECT_THAT(TransformPackageAlias("one"), Eq(ExtractedPackage{"com.one", false})); } else if (el->name == "View2") { - EXPECT_THAT(TransformPackageAlias("one"), Eq(make_value(ExtractedPackage{"com.one", false}))); - EXPECT_THAT(TransformPackageAlias("two"), Eq(make_value(ExtractedPackage{"com.two", false}))); + EXPECT_THAT(TransformPackageAlias("one"), Eq(ExtractedPackage{"com.one", false})); + EXPECT_THAT(TransformPackageAlias("two"), Eq(ExtractedPackage{"com.two", false})); } else if (el->name == "View3") { - EXPECT_THAT(TransformPackageAlias("one"), Eq(make_value(ExtractedPackage{"com.one", false}))); - EXPECT_THAT(TransformPackageAlias("two"), Eq(make_value(ExtractedPackage{"com.two", false}))); - EXPECT_THAT(TransformPackageAlias("three"), - Eq(make_value(ExtractedPackage{"com.three", false}))); + EXPECT_THAT(TransformPackageAlias("one"), Eq(ExtractedPackage{"com.one", false})); + EXPECT_THAT(TransformPackageAlias("two"), Eq(ExtractedPackage{"com.two", false})); + EXPECT_THAT(TransformPackageAlias("three"), Eq(ExtractedPackage{"com.three", false})); } else if (el->name == "View4") { - EXPECT_THAT(TransformPackageAlias("one"), Eq(make_value(ExtractedPackage{"com.one", false}))); - EXPECT_THAT(TransformPackageAlias("two"), Eq(make_value(ExtractedPackage{"com.two", false}))); - EXPECT_THAT(TransformPackageAlias("three"), - Eq(make_value(ExtractedPackage{"com.three", false}))); - EXPECT_THAT(TransformPackageAlias("four"), Eq(make_value(ExtractedPackage{"", true}))); + EXPECT_THAT(TransformPackageAlias("one"), Eq(ExtractedPackage{"com.one", false})); + EXPECT_THAT(TransformPackageAlias("two"), Eq(ExtractedPackage{"com.two", false})); + EXPECT_THAT(TransformPackageAlias("three"), Eq(ExtractedPackage{"com.three", false})); + EXPECT_THAT(TransformPackageAlias("four"), Eq(ExtractedPackage{"", true})); } } }; diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp index 182203d397c3..bfa07490b9c0 100644 --- a/tools/aapt2/xml/XmlPullParser.cpp +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -17,7 +17,6 @@ #include <iostream> #include <string> -#include "util/Maybe.h" #include "util/Util.h" #include "xml/XmlPullParser.h" #include "xml/XmlUtil.h" @@ -84,8 +83,7 @@ XmlPullParser::Event XmlPullParser::Next() { // handling of references that use namespace aliases. if (next_event == Event::kStartNamespace || next_event == Event::kEndNamespace) { - Maybe<ExtractedPackage> result = - ExtractPackageFromNamespace(namespace_uri()); + std::optional<ExtractedPackage> result = ExtractPackageFromNamespace(namespace_uri()); if (next_event == Event::kStartNamespace) { if (result) { package_aliases_.emplace_back( @@ -142,7 +140,8 @@ const std::string& XmlPullParser::namespace_uri() const { return event_queue_.front().data2; } -Maybe<ExtractedPackage> XmlPullParser::TransformPackageAlias(const StringPiece& alias) const { +std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias( + const StringPiece& alias) const { if (alias.empty()) { return ExtractedPackage{{}, false /*private*/}; } @@ -308,8 +307,7 @@ void XMLCALL XmlPullParser::EndCdataSectionHandler(void* user_data) { parser->depth_ }); } -Maybe<StringPiece> FindAttribute(const XmlPullParser* parser, - const StringPiece& name) { +std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, const StringPiece& name) { auto iter = parser->FindAttribute("", name); if (iter != parser->end_attributes()) { return StringPiece(util::TrimWhitespace(iter->value)); @@ -317,8 +315,8 @@ Maybe<StringPiece> FindAttribute(const XmlPullParser* parser, return {}; } -Maybe<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, - const StringPiece& name) { +std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, + const 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 5da2d4b10a4b..ab347728ae4b 100644 --- a/tools/aapt2/xml/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -33,7 +33,6 @@ #include "Resource.h" #include "io/Io.h" #include "process/IResourceTableConsumer.h" -#include "util/Maybe.h" #include "xml/XmlUtil.h" namespace aapt { @@ -121,7 +120,8 @@ class XmlPullParser : public IPackageDeclStack { * If xmlns:app="http://schemas.android.com/apk/res-auto", then * 'package' will be set to 'defaultPackage'. */ - Maybe<ExtractedPackage> TransformPackageAlias(const android::StringPiece& alias) const override; + std::optional<ExtractedPackage> TransformPackageAlias( + const android::StringPiece& alias) const override; struct PackageDecl { std::string prefix; @@ -193,16 +193,16 @@ class XmlPullParser : public IPackageDeclStack { /** * Finds the attribute in the current element within the global namespace. */ -Maybe<android::StringPiece> FindAttribute(const XmlPullParser* parser, - const android::StringPiece& name); +std::optional<android::StringPiece> FindAttribute(const XmlPullParser* parser, + const android::StringPiece& name); /** * Finds the attribute in the current element within the global namespace. The * attribute's value * must not be the empty string. */ -Maybe<android::StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, - const android::StringPiece& name); +std::optional<android::StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, + const android::StringPiece& name); // // Implementation diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp index 0a622b2bd336..114b5ba7ab1a 100644 --- a/tools/aapt2/xml/XmlUtil.cpp +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -19,7 +19,6 @@ #include <algorithm> #include <string> -#include "util/Maybe.h" #include "util/Util.h" #include "xml/XmlDom.h" @@ -34,8 +33,7 @@ std::string BuildPackageNamespace(const StringPiece& package, bool private_refer return result; } -Maybe<ExtractedPackage> ExtractPackageFromNamespace( - const std::string& namespace_uri) { +std::optional<ExtractedPackage> ExtractPackageFromNamespace(const std::string& namespace_uri) { if (util::StartsWith(namespace_uri, kSchemaPublicPrefix)) { StringPiece schema_prefix = kSchemaPublicPrefix; StringPiece package = namespace_uri; @@ -62,7 +60,7 @@ Maybe<ExtractedPackage> ExtractPackageFromNamespace( void ResolvePackage(const IPackageDeclStack* decl_stack, Reference* in_ref) { if (in_ref->name) { - if (Maybe<ExtractedPackage> transformed_package = + if (std::optional<ExtractedPackage> transformed_package = decl_stack->TransformPackageAlias(in_ref->name.value().package)) { ExtractedPackage& extracted_package = transformed_package.value(); in_ref->name.value().package = std::move(extracted_package.package); diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h index 592a604f87a6..1ab05a93d314 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -20,7 +20,6 @@ #include <string> #include "ResourceValues.h" -#include "util/Maybe.h" namespace aapt { namespace xml { @@ -53,7 +52,7 @@ struct ExtractedPackage { // // Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, returns an empty // package name. -Maybe<ExtractedPackage> ExtractPackageFromNamespace(const std::string& namespace_uri); +std::optional<ExtractedPackage> ExtractPackageFromNamespace(const std::string& namespace_uri); // Returns an XML Android namespace for the given package of the form: // http://schemas.android.com/apk/res/<package> @@ -69,7 +68,7 @@ struct IPackageDeclStack { virtual ~IPackageDeclStack() = default; // Returns an ExtractedPackage struct if the alias given corresponds with a package declaration. - virtual Maybe<ExtractedPackage> TransformPackageAlias( + virtual std::optional<ExtractedPackage> TransformPackageAlias( const android::StringPiece& alias) const = 0; }; diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp index cbded8ffac8e..7b6ce9e96689 100644 --- a/tools/aapt2/xml/XmlUtil_test.cpp +++ b/tools/aapt2/xml/XmlUtil_test.cpp @@ -27,7 +27,7 @@ TEST(XmlUtilTest, ExtractPackageFromNamespace) { ASSERT_FALSE(xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/")); ASSERT_FALSE(xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/prv/res/")); - Maybe<xml::ExtractedPackage> p = + std::optional<xml::ExtractedPackage> p = xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/a"); ASSERT_TRUE(p); EXPECT_EQ(std::string("a"), p.value().package); diff --git a/tools/aosp/aosp_sha.sh b/tools/aosp/aosp_sha.sh index 81d35efaf29f..95b43cdf253d 100755 --- a/tools/aosp/aosp_sha.sh +++ b/tools/aosp/aosp_sha.sh @@ -1,14 +1,28 @@ #!/bin/bash LOCAL_DIR="$( dirname "${BASH_SOURCE}" )" -if git branch -vv | grep -q -P "^\*[^\[]+\[aosp/"; then +if git log -n 1 --format='%D' HEAD@{upstream} | grep -q aosp/; then # Change appears to be in AOSP exit 0 elif git log -n 1 --format='%B' $1 | grep -q -E "^Ignore-AOSP-First: .+" ; then # Change is explicitly marked as ok to skip AOSP exit 0 else - # Change appears to be non-AOSP; search for files + # Change appears to be non-AOSP. + + # If this is a cherry-pick, then allow it. + cherrypick=0 + while read -r line ; do + if [[ $line =~ cherry\ picked\ from ]] ; then + (( cherrypick++ )) + fi + done < <(git show $1) + if (( cherrypick != 0 )); then + # This is a cherry-pick, so allow it. + exit 0 + fi + + # See if any files are affected. count=0 while read -r file ; do if (( count == 0 )); then @@ -21,6 +35,7 @@ else echo echo "If your change contains no confidential details (such as security fixes), please" echo "upload and merge this change at https://android-review.googlesource.com/." + echo "Else add a tag 'Ignore-AOSP-First:' with the reason to bypass AOSP." echo exit 1 fi diff --git a/tools/apilint/deprecated_at_birth.py b/tools/apilint/deprecated_at_birth.py index 297d9c3bcca0..d53c12734d23 100644..100755 --- a/tools/apilint/deprecated_at_birth.py +++ b/tools/apilint/deprecated_at_birth.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright (C) 2021 The Android Open Source Project # @@ -44,6 +44,7 @@ def ident(raw): can be used to identify members across API levels.""" raw = raw.replace(" deprecated ", " ") raw = raw.replace(" synchronized ", " ") + raw = raw.replace(" abstract ", " ") raw = raw.replace(" final ", " ") raw = re.sub("<.+?>", "", raw) raw = re.sub("@[A-Za-z]+ ", "", raw) @@ -208,17 +209,17 @@ def _parse_stream(f, api={}): def _parse_stream_path(path): api = {} - print "Parsing", path + print("Parsing %s" % path) for f in os.listdir(path): f = os.path.join(path, f) if not os.path.isfile(f): continue if not f.endswith(".txt"): continue if f.endswith("removed.txt"): continue - print "\t", f + print("\t%s" % f) with open(f) as s: api = _parse_stream(s, api) - print "Parsed", len(api), "APIs" - print + print("Parsed %d APIs" % len(api)) + print() return api @@ -306,8 +307,8 @@ if __name__ == "__main__": if "@Deprecated " in i.raw: error(clazz, i, None, "Found API deprecation at birth " + i.ident) - print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), - format(reset=True))) + print("%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), + format(reset=True)))) for f in sorted(failures): - print failures[f] - print + print(failures[f]) + print() diff --git a/tools/bit/command.cpp b/tools/bit/command.cpp index f95ea117a96e..6c68e0b0ff6b 100644 --- a/tools/bit/command.cpp +++ b/tools/bit/command.cpp @@ -192,10 +192,11 @@ exec_with_path_search(const char* prog, char const* const* argv, char const* con if (strchr(prog, '/') != NULL) { return execve(prog, (char*const*)argv, (char*const*)envp); } else { - char* pathEnv = strdup(getenv("PATH")); - if (pathEnv == NULL) { + const char* pathEnvRaw = getenv("PATH"); + if (pathEnvRaw == NULL) { return 1; } + char* pathEnv = strdup(pathEnvRaw); char* dir = pathEnv; while (dir) { char* next = strchr(dir, ':'); diff --git a/tools/bit/print.cpp b/tools/bit/print.cpp index 35feda11ec29..8bc6f167bd7f 100644 --- a/tools/bit/print.cpp +++ b/tools/bit/print.cpp @@ -17,6 +17,7 @@ #include "print.h" #include <sys/ioctl.h> +#include <stdarg.h> #include <stdio.h> #include <unistd.h> diff --git a/tools/bit/util.h b/tools/bit/util.h index 7ccdab103d9a..8c66911b3c48 100644 --- a/tools/bit/util.h +++ b/tools/bit/util.h @@ -17,6 +17,8 @@ #ifndef UTIL_H #define UTIL_H +#include <sys/types.h> + #include <map> #include <string> #include <vector> diff --git a/tools/codegen/OWNERS b/tools/codegen/OWNERS index da723b3b67da..c9bd260ca7ae 100644 --- a/tools/codegen/OWNERS +++ b/tools/codegen/OWNERS @@ -1 +1 @@ -eugenesusla@google.com
\ No newline at end of file +chiuwinson@google.com diff --git a/tools/codegen/src/com/android/codegen/ImportsProvider.kt b/tools/codegen/src/com/android/codegen/ImportsProvider.kt index 27dd9587db25..46df2739e59f 100644 --- a/tools/codegen/src/com/android/codegen/ImportsProvider.kt +++ b/tools/codegen/src/com/android/codegen/ImportsProvider.kt @@ -53,10 +53,25 @@ interface ImportsProvider { * Optionally shortens a class reference if there's a corresponding import present */ fun classRef(fullName: String): String { - val pkg = fullName.substringBeforeLast(".") val simpleName = fullName.substringAfterLast(".") - if (fileAst.imports.any { imprt -> + val imports = fileAst.imports + + // If an import of the same class name is available, + // use it instead of the internal Android package variants. + if (fullName.startsWith("com.android.internal.util.") + && imports.any { + it.nameAsString.endsWith(fullName.removePrefix("com.android.internal.util.")) + } + ) { + return fullName.removePrefix("com.android.internal.util.") + } else if (fullName.startsWith("android.annotation") + && imports.any { it.nameAsString.endsWith(simpleName) } + ) { + return simpleName + } + + if (imports.any { imprt -> imprt.nameAsString == fullName || (imprt.isAsterisk && imprt.nameAsString == pkg) }) { @@ -89,4 +104,4 @@ interface ImportsProvider { /** @see classRef */ inline fun <reified T : Any> ImportsProvider.classRef(): String { return classRef(T::class.java.name) -}
\ No newline at end of file +} diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt index 7cfa7847fcff..9ceb2042d74e 100644 --- a/tools/codegen/src/com/android/codegen/Utils.kt +++ b/tools/codegen/src/com/android/codegen/Utils.kt @@ -43,8 +43,8 @@ inline infix fun Int.times(action: () -> Unit) { * cccc dd */ fun Iterable<Pair<String, String>>.columnize(separator: String = " | "): String { - val col1w = map { (a, _) -> a.length }.max()!! - val col2w = map { (_, b) -> b.length }.max()!! + val col1w = map { (a, _) -> a.length }.maxOrNull()!! + val col2w = map { (_, b) -> b.length }.maxOrNull()!! return map { it.first.padEnd(col1w) + separator + it.second.padEnd(col2w) }.joinToString("\n") } diff --git a/tools/finalize_res/finalize_res.py b/tools/finalize_res/finalize_res.py deleted file mode 100755 index 724443c01852..000000000000 --- a/tools/finalize_res/finalize_res.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python3 -#-*- coding: utf-8 -*- - -# 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. - -""" -Finalize resource values in <staging-public-group> tags -and convert those to <staging-public-group-final> - -Usage: finalize_res.py core/res/res/values/public.xml public_finalized.xml -""" - -import re, sys, codecs - -def finalize_item(raw): - global _type_ids, _type - id = _type_ids[_type] - _type_ids[_type] += 1 - name = raw.group(1) - val = '<public type="%s" name="%s" id="%s" />' % (_type, name, '0x{0:0{1}x}'.format(id,8)) - if re.match(r'_*removed.+', name): - val = '<!-- ' + val.replace('<public', '< public') + ' -->' - return val - -def finalize_group(raw): - global _type_ids, _type - _type = raw.group(1) - id = int(raw.group(2), 16) - _type_ids[_type] = _type_ids.get(_type, id) - (res, count) = re.subn(r' {0,2}<public name="(.+?)" */>', finalize_item, raw.group(3)) - if count > 0: - res = raw.group(0).replace("staging-public-group", "staging-public-group-final") + '\n' + res - return res - -def collect_ids(raw): - global _type_ids - for m in re.finditer(r'<public type="(.+?)" name=".+?" id="(.+?)" />', raw): - type = m.group(1) - id = int(m.group(2), 16) - _type_ids[type] = max(id + 1, _type_ids.get(type, 0)) - -with open(sys.argv[1]) as f: - global _type_ids, _type - _type_ids = {} - raw = f.read() - collect_ids(raw) - raw = re.sub(r'<staging-public-group type="(.+?)" first-id="(.+?)">(.+?)</staging-public-group>', finalize_group, raw, flags=re.DOTALL) - raw = re.sub(r' *\n', '\n', raw) - raw = re.sub(r'\n{3,}', '\n\n', raw) - with open(sys.argv[2], "w") as f: - f.write(raw) - diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index e181c62c67e5..35a0ce60e359 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -13,41 +13,56 @@ from fontTools import ttLib EMOJI_VS = 0xFE0F LANG_TO_SCRIPT = { + 'af': 'Latn', 'as': 'Beng', + 'am': 'Latn', 'be': 'Cyrl', 'bg': 'Cyrl', 'bn': 'Beng', + 'cs': 'Latn', 'cu': 'Cyrl', 'cy': 'Latn', 'da': 'Latn', 'de': 'Latn', + 'el': 'Latn', 'en': 'Latn', 'es': 'Latn', 'et': 'Latn', 'eu': 'Latn', 'fr': 'Latn', 'ga': 'Latn', + 'gl': 'Latn', 'gu': 'Gujr', 'hi': 'Deva', 'hr': 'Latn', 'hu': 'Latn', 'hy': 'Armn', + 'it': 'Latn', 'ja': 'Jpan', + 'ka': 'Latn', 'kn': 'Knda', 'ko': 'Kore', 'la': 'Latn', + 'lt': 'Latn', + 'lv': 'Latn', 'ml': 'Mlym', 'mn': 'Cyrl', 'mr': 'Deva', 'nb': 'Latn', + 'nl': 'Latn', 'nn': 'Latn', 'or': 'Orya', 'pa': 'Guru', 'pt': 'Latn', + 'ru': 'Latn', + 'sk': 'Latn', 'sl': 'Latn', + 'sq': 'Latn', + 'sv': 'Latn', 'ta': 'Taml', 'te': 'Telu', 'tk': 'Latn', + 'uk': 'Latn', } def lang_to_script(lang_code): @@ -228,6 +243,8 @@ def parse_fonts_xml(fonts_xml_path): name = family.get('name') variant = family.get('variant') langs = family.get('lang') + ignoreAttr = family.get('ignore') + if name: assert variant is None, ( 'No variant expected for LGC font %s.' % name) @@ -244,6 +261,11 @@ def parse_fonts_xml(fonts_xml_path): name = family.get('name') variant = family.get('variant') langs = family.get('lang') + ignoreAttr = family.get('ignore') + ignore = ignoreAttr == 'true' or ignoreAttr == '1' + + if ignore: + continue if langs: langs = langs.split() @@ -318,29 +340,104 @@ def check_emoji_coverage(all_emoji, equivalent_emoji): def get_emoji_fonts(): return [ record.font for record in _all_fonts if 'Zsye' in record.scripts ] +def seq_any(sequence, pred): + if type(sequence) is tuple: + return any([pred(x) for x in sequence]) + else: + return pred(sequence) + +def seq_all(sequence, pred): + if type(sequence) is tuple: + return all([pred(x) for x in sequence]) + else: + return pred(sequence) + +def is_regional_indicator(x): + # regional indicator A..Z + return 0x1F1E6 <= x <= 0x1F1FF + +def is_tag(x): + # tag block + return 0xE0000 <= x <= 0xE007F + def is_pua(x): return 0xE000 <= x <= 0xF8FF or 0xF0000 <= x <= 0xFFFFD or 0x100000 <= x <= 0x10FFFD def contains_pua(sequence): - if type(sequence) is tuple: - return any([is_pua(x) for x in sequence]) - else: - return is_pua(sequence) + return seq_any(sequence, is_pua) + +def contains_regional_indicator(sequence): + return seq_any(sequence, is_regional_indicator) + +def only_tags(sequence): + return seq_all(sequence, is_tag) def get_psname(ttf): return str(next(x for x in ttf['name'].names if x.platformID == 3 and x.platEncID == 1 and x.nameID == 6)) -def check_emoji_compat(): +def hex_strs(sequence): + if type(sequence) is tuple: + return tuple(f"{s:X}" for s in sequence) + return hex(sequence) + +def check_plausible_compat_pua(coverage, all_emoji, equivalent_emoji): + # A PUA should point to every RGI emoji and that PUA should be unique to the + # set of equivalent sequences for the emoji. + problems = [] + for seq in all_emoji: + # We're looking to match not-PUA with PUA so filter out existing PUA + if contains_pua(seq): + continue + + # Filter out non-RGI things that end up in all_emoji + if only_tags(seq) or seq in {ZWJ, COMBINING_KEYCAP, EMPTY_FLAG_SEQUENCE}: + continue + + equivalents = [seq] + if seq in equivalent_emoji: + equivalents.append(equivalent_emoji[seq]) + + # If there are problems the hex code is much more useful + log_equivalents = [hex_strs(s) for s in equivalents] + + # The system compat font should NOT include regional indicators as these have been split out + if contains_regional_indicator(seq): + assert not any(s in coverage for s in equivalents), f"Regional indicators not expected in compat font, found {log_equivalents}" + continue + + glyph = {coverage[e] for e in equivalents} + if len(glyph) != 1: + problems.append(f"{log_equivalents} should all point to the same glyph") + continue + glyph = next(iter(glyph)) + + pua = {s for s, g in coverage.items() if contains_pua(s) and g == glyph} + if not pua: + problems.append(f"Expected PUA for {log_equivalents} but none exist") + continue + + assert not problems, "\n".join(sorted(problems)) + f"\n{len(problems)} PUA problems" + +def check_emoji_compat(all_emoji, equivalent_emoji): + compat_psnames = set() for emoji_font in get_emoji_fonts(): ttf = open_font(emoji_font) psname = get_psname(ttf) - # If the font file is NotoColorEmoji, it must be Compat font. - if psname == 'NotoColorEmoji': - meta = ttf['meta'] - assert meta, 'Compat font must have meta table' - assert 'Emji' in meta.data, 'meta table should have \'Emji\' data.' + is_compat_font = "meta" in ttf and 'Emji' in ttf["meta"].data + if not is_compat_font: + continue + compat_psnames.add(psname) + + # If the font has compat metadata it should have PUAs for emoji sequences + coverage = get_emoji_map(emoji_font) + check_plausible_compat_pua(coverage, all_emoji, equivalent_emoji) + + + # NotoColorEmoji must be a Compat font. + assert 'NotoColorEmoji' in compat_psnames, 'NotoColorEmoji MUST be a compat font' + def check_emoji_font_coverage(emoji_fonts, all_emoji, equivalent_emoji): coverages = [] @@ -589,6 +686,8 @@ SAME_FLAG_MAPPINGS = [ ZWJ = 0x200D +EMPTY_FLAG_SEQUENCE = (0x1F3F4, 0xE007F) + def is_fitzpatrick_modifier(cp): return 0x1F3FB <= cp <= 0x1F3FF @@ -614,29 +713,17 @@ def compute_expected_emoji(): adjusted_emoji_zwj_sequences.update(_emoji_zwj_sequences) # Add empty flag tag sequence that is supported as fallback - _emoji_sequences[(0x1F3F4, 0xE007F)] = 'Emoji_Tag_Sequence' + _emoji_sequences[EMPTY_FLAG_SEQUENCE] = 'Emoji_Tag_Sequence' for sequence in _emoji_sequences.keys(): sequence = tuple(ch for ch in sequence if ch != EMOJI_VS) all_sequences.add(sequence) sequence_pieces.update(sequence) - if _emoji_sequences.get(sequence, None) == 'Emoji_Tag_Sequence': - # Add reverse of all emoji ZWJ sequences, which are added to the - # fonts as a workaround to get the sequences work in RTL text. - # TODO: test if these are actually needed by Minikin/HarfBuzz. - reversed_seq = reverse_emoji(sequence) - all_sequences.add(reversed_seq) - equivalent_emoji[reversed_seq] = sequence for sequence in adjusted_emoji_zwj_sequences.keys(): sequence = tuple(ch for ch in sequence if ch != EMOJI_VS) all_sequences.add(sequence) sequence_pieces.update(sequence) - # Add reverse of all emoji ZWJ sequences, which are added to the fonts - # as a workaround to get the sequences work in RTL text. - reversed_seq = reverse_emoji(sequence) - all_sequences.add(reversed_seq) - equivalent_emoji[reversed_seq] = sequence for first, second in SAME_FLAG_MAPPINGS: equivalent_emoji[first] = second @@ -741,6 +828,7 @@ def main(): _fonts_dir = path.join(target_out, 'fonts') fonts_xml_path = path.join(target_out, 'etc', 'fonts.xml') + parse_fonts_xml(fonts_xml_path) check_compact_only_fallback() @@ -759,7 +847,7 @@ def main(): ucd_path = sys.argv[3] parse_ucd(ucd_path) all_emoji, default_emoji, equivalent_emoji = compute_expected_emoji() - check_emoji_compat() + check_emoji_compat(all_emoji, equivalent_emoji) check_emoji_coverage(all_emoji, equivalent_emoji) check_emoji_defaults(default_emoji) diff --git a/tools/lint/Android.bp b/tools/lint/Android.bp new file mode 100644 index 000000000000..17547ef8b561 --- /dev/null +++ b/tools/lint/Android.bp @@ -0,0 +1,46 @@ +// 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 { + // 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: "AndroidFrameworkLintChecker", + srcs: ["checks/src/main/java/**/*.kt"], + plugins: ["auto_service_plugin"], + libs: [ + "auto_service_annotations", + "lint_api", + ], +} + +java_test_host { + name: "AndroidFrameworkLintCheckerTest", + srcs: ["checks/src/test/java/**/*.kt"], + static_libs: [ + "AndroidFrameworkLintChecker", + "junit", + "lint", + "lint_tests", + ], + test_options: { + unit_test: true, + }, +} diff --git a/tools/lint/OWNERS b/tools/lint/OWNERS new file mode 100644 index 000000000000..7c0451900e32 --- /dev/null +++ b/tools/lint/OWNERS @@ -0,0 +1,5 @@ +brufino@google.com +jsharkey@google.com + +per-file *CallingSettingsNonUserGetterMethods* = file:/packages/SettingsProvider/OWNERS + diff --git a/tools/lint/README.md b/tools/lint/README.md new file mode 100644 index 000000000000..b534b62cb395 --- /dev/null +++ b/tools/lint/README.md @@ -0,0 +1,84 @@ +# Android Framework Lint Checker + +Custom lint 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 + +1. Write your detector with its issues and put it into + `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`. +4. Done! Your lint checks should be applied in lint report builds for modules that include + `AndroidFrameworkLintChecker`. + +## How to run lint against your module + +1. Add the following `lint` attribute to the module definition, e.g. `services.autofill`: +``` +java_library_static { + name: "services.autofill", + ... + lint: { + extra_check_modules: ["AndroidFrameworkLintChecker"], + }, +} +``` +2. Run the following command to verify that the report is being correctly built: +``` +m out/soong/.intermediates/frameworks/base/services/autofill/services.autofill/android_common/lint/lint-report.html +``` + (Lint report can be found in the same path, i.e. `out/../lint-report.html`) + +3. Now lint issues should appear on gerrit! + +**Notes:** + +- Lint report will not be produced if you just build the module, i.e. `m services.autofill` will not + build the lint report. +- If you want to build lint reports for more than 1 module and they include a common module in their + `defaults` field, e.g. `platform_service_defaults`, you can add the `lint` property to that common + module instead of adding it in every module. +- If you want to run a single lint type, use the `ANDROID_LINT_CHECK` + environment variable with the id of the lint. For example: + `ANDROID_LINT_CHECK=UnusedTokenOfOriginalCallingIdentity m out/[...]/lint-report.html` + +## 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 +automatically use it. You can override the file using lint properties too. + +``` +java_library { + lint: { + baseline_filename: "my-baseline.xml", // default: lint-baseline.xml; + } +} +``` + +When using soong to create a lint report (as described above), it also creates a reference +baseline file. This contains all lint errors and warnings that were found. So the next time +you run lint, if you use this baseline file, there should be 0 findings. + +After the previous invocation, you can find the baseline here: + +``` +out/soong/.intermediates/frameworks/base/services/autofill/services.autofill/android_common/lint/lint-baseline.xml +``` + +As noted above, this baseline file contains warnings too, which might be undesirable. For example, +CI tools might surface these warnings in code reviews. In order to create this file without +warnings, we need to pass another flag to lint: `--nowarn`. The easiest way to do this is to +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 + +- [Android Lint Docs](https://googlesamples.github.io/android-custom-lint-rules/) +- [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) +- [IntelliJ plugin for viewing PSI tree of files](https://plugins.jetbrains.com/plugin/227-psiviewer) diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt new file mode 100644 index 000000000000..a6fd9bba6192 --- /dev/null +++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt @@ -0,0 +1,50 @@ +/* + * 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.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, + CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED, + EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION, + EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION + ) + + 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/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt new file mode 100644 index 000000000000..930378b168b2 --- /dev/null +++ b/tools/lint/checks/src/main/java/com/google/android/lint/CallingIdentityTokenDetector.kt @@ -0,0 +1,577 @@ +/* + * 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.UElementHandler +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.JavaContext +import com.android.tools.lint.detector.api.Location +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.search.PsiSearchScopeUtil +import com.intellij.psi.search.SearchScope +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.UDeclarationsExpression +import org.jetbrains.uast.UElement +import org.jetbrains.uast.ULocalVariable +import org.jetbrains.uast.USimpleNameReferenceExpression +import org.jetbrains.uast.UTryExpression +import org.jetbrains.uast.getParentOfType +import org.jetbrains.uast.getQualifiedParentOrThis +import org.jetbrains.uast.getUCallExpression +import org.jetbrains.uast.skipParenthesizedExprDown +import org.jetbrains.uast.skipParenthesizedExprUp + +/** + * Lint Detector that finds issues with improper usages of the token returned by + * Binder.clearCallingIdentity() + */ +@Suppress("UnstableApiUsage") +class CallingIdentityTokenDetector : Detector(), SourceCodeScanner { + /** Map of <Token variable name, Token object> */ + private val tokensMap = mutableMapOf<String, Token>() + + override fun getApplicableUastTypes(): List<Class<out UElement?>> = + listOf(ULocalVariable::class.java, UCallExpression::class.java) + + override fun createUastHandler(context: JavaContext): UElementHandler = + TokenUastHandler(context) + + /** File analysis starts with a clear map */ + override fun beforeCheckFile(context: Context) { + tokensMap.clear() + } + + /** + * - If tokensMap has tokens after checking the file -> reports all locations as unused token + * issue incidents + * - File analysis ends with a clear map + */ + override fun afterCheckFile(context: Context) { + for (token in tokensMap.values) { + context.report( + ISSUE_UNUSED_TOKEN, + token.location, + getIncidentMessageUnusedToken(token.variableName) + ) + } + tokensMap.clear() + } + + /** UAST handler that analyses elements and reports incidents */ + private inner class TokenUastHandler(val context: JavaContext) : UElementHandler() { + /** + * For every variable initialization with Binder.clearCallingIdentity(): + * - Checks for non-final token issue + * - Checks for unused token issue within different scopes + * - Checks for nested calls of clearCallingIdentity() issue + * - Checks for clearCallingIdentity() not followed by try-finally issue + * - Stores token variable name, scope in the file, location and finally block in tokensMap + */ + override fun visitLocalVariable(node: ULocalVariable) { + val initializer = node.uastInitializer?.skipParenthesizedExprDown() + val rhsExpression = initializer?.getUCallExpression() ?: return + if (!isMethodCall(rhsExpression, Method.BINDER_CLEAR_CALLING_IDENTITY)) return + val location = context.getLocation(node as UElement) + val variableName = node.getName() + if (!node.isFinal) { + context.report( + ISSUE_NON_FINAL_TOKEN, + location, + getIncidentMessageNonFinalToken(variableName) + ) + } + // If there exists an unused variable with the same name in the map, we can imply that + // we left the scope of the previous declaration, so we need to report the unused token + val oldToken = tokensMap[variableName] + if (oldToken != null) { + context.report( + 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 + // clearCallingIdentity() has been called at least twice without immediate restoration + // of identity, so we need to report the nested call of clearCallingIdentity() + val firstCallToken = findFirstTokenInScope(node) + if (firstCallToken != null) { + context.report( + 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 + if (finallyClause == null) { + context.report( + ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY, + location, + getIncidentMessageClearIdentityCallNotFollowedByTryFinally(variableName) + ) + } + tokensMap[variableName] = Token( + 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) { + val token = findFirstTokenInScope(node) + if (isCallerAwareMethod(node) && token != null) { + context.report( + 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 + val variableName = arg.identifier + val originalScope = tokensMap[variableName]?.scope ?: return + val psi = arg.sourcePsi ?: return + // Checks if Binder.restoreCallingIdentity(token) is called within the scope of the + // 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 + // 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) { + context.report( + ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK, + context.getLocation(node), + getIncidentMessageRestoreIdentityCallNotInFinallyBlock(variableName) + ) + } + tokensMap.remove(variableName) + } + + private fun isCallerAwareMethod(expression: UCallExpression): Boolean = + callerAwareMethods.any { method -> isMethodCall(expression, method) } + + private fun isMethodCall( + expression: UCallExpression, + method: Method + ): Boolean { + val psiMethod = expression.resolve() ?: return false + return psiMethod.getName() == method.methodName && + context.evaluator.methodMatches( + psiMethod, + method.className, + /* allowInherit */ true, + *method.args + ) + } + + /** + * ULocalVariable in the file tree: + * + * UBlockExpression + * UDeclarationsExpression + * ULocalVariable + * ULocalVariable + * UTryStatement + * etc. + * + * To get the next statement of ULocalVariable: + * - If there exists a next sibling in UDeclarationsExpression, return the sibling + * - If there exists a next sibling of UDeclarationsExpression in UBlockExpression, return + * the sibling + * - Otherwise, return null + * + * Example 1 - the next sibling is in UDeclarationsExpression: + * Code: + * { + * int num1 = 0, num2 = methodThatThrowsException(); + * } + * Returns: num2 = methodThatThrowsException() + * + * Example 2 - the next sibling is in UBlockExpression: + * Code: + * { + * int num1 = 0; + * methodThatThrowsException(); + * } + * Returns: methodThatThrowsException() + * + * Example 3 - no next sibling; + * Code: + * { + * int num1 = 0; + * } + * Returns: null + */ + private fun getNextStatementOfLocalVariable(node: ULocalVariable): UElement? { + val declarationsExpression = node.uastParent as? UDeclarationsExpression ?: return null + val declarations = declarationsExpression.declarations + val indexInDeclarations = declarations.indexOf(node) + if (indexInDeclarations != -1 && declarations.size > indexInDeclarations + 1) { + return declarations[indexInDeclarations + 1] + } + val enclosingBlock = node + .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) + } + } + + private fun findFirstTokenInScope(node: UElement): Token? { + val psi = node.sourcePsi ?: return null + for (token in tokensMap.values) { + if (token.scope != null && PsiSearchScopeUtil.isInScope(token.scope, psi)) { + return token + } + } + return null + } + + /** + * Creates a new instance of the primary location with the secondary location + * + * Here, secondary location is the helper location that shows where the issue originated + * + * The detector reports locations as objects, so when we add a secondary location to a location + * that has multiple issues, the secondary location gets displayed every time a location is + * referenced. + * + * Example: + * 1: final long token1 = Binder.clearCallingIdentity(); + * 2: long token2 = Binder.clearCallingIdentity(); + * 3: Binder.restoreCallingIdentity(token1); + * 4: Binder.restoreCallingIdentity(token2); + * + * Explanation: + * token2 has 2 issues: NonFinal and NestedCalls + * + * Lint report without cloning Lint report with cloning + * line 2: [NonFinalIssue] line 2: [NonFinalIssue] + * line 1: [NestedCallsIssue] + * line 2: [NestedCallsIssue] line 2: [NestedCallsIssue] + * line 1: [NestedCallsIssue] line 1: [NestedCallsIssue] + */ + private fun createNestedLocation( + firstCallToken: Token, + secondCallTokenLocation: Location + ): Location { + return cloneLocation(secondCallTokenLocation) + .withSecondary( + cloneLocation(firstCallToken.location), + getIncidentMessageNestedClearIdentityCallsSecondary( + firstCallToken.variableName + ) + ) + } + + private fun cloneLocation(location: Location): Location { + // smart cast of location.start to 'Position' is impossible, because 'location.start' is a + // public API property declared in different module + val locationStart = location.start + return if (locationStart == null) { + Location.create(location.file) + } else { + Location.create(location.file, locationStart, location.end) + } + } + + private enum class Method( + val className: String, + val methodName: String, + val args: Array<String> + ) { + BINDER_CLEAR_CALLING_IDENTITY(CLASS_BINDER, "clearCallingIdentity", emptyArray()), + BINDER_RESTORE_CALLING_IDENTITY(CLASS_BINDER, "restoreCallingIdentity", arrayOf("long")), + BINDER_GET_CALLING_PID(CLASS_BINDER, "getCallingPid", emptyArray()), + BINDER_GET_CALLING_UID(CLASS_BINDER, "getCallingUid", emptyArray()), + BINDER_GET_CALLING_UID_OR_THROW(CLASS_BINDER, "getCallingUidOrThrow", emptyArray()), + BINDER_GET_CALLING_USER_HANDLE(CLASS_BINDER, "getCallingUserHandle", emptyArray()), + USER_HANDLE_GET_CALLING_APP_ID(CLASS_USER_HANDLE, "getCallingAppId", emptyArray()), + USER_HANDLE_GET_CALLING_USER_ID(CLASS_USER_HANDLE, "getCallingUserId", emptyArray()) + } + + private data class Token( + val variableName: String, + val scope: SearchScope?, + val location: Location, + val finallyBlock: UElement? + ) + + companion object { + const val CLASS_BINDER = "android.os.Binder" + 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 + ) + + /** Issue: unused token from Binder.clearCallingIdentity() */ + @JvmField + val ISSUE_UNUSED_TOKEN: Issue = Issue.create( + 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. + + Call `Binder.restoreCallingIdentity(token)` in the `finally` block, at the end \ + of the method or when you need to restore the identity. + + `token` is the result of `Binder.clearCallingIdentity()` + """, + 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`." + + /** 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 = """ + You cleared the original calling identity with \ + `Binder.clearCallingIdentity()`, but have not made the returned token `final`. + + The token should be `final` in order to prevent it from being overwritten, \ + 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 + ) + ) + + private fun getIncidentMessageNonFinalToken(variableName: String) = "`$variableName` is " + + "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 = """ + 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 + ) + ) + + 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)`." + + private fun getIncidentMessageNestedClearIdentityCallsSecondary( + firstCallVariableName: String + ): String = "Location of the `$firstCallVariableName` declaration." + + /** 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 = """ + You cleared the original calling identity with \ + `Binder.clearCallingIdentity()`, but the next statement is not a `try` \ + statement. + + 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); + } + ``` + + Any calls/operations between `Binder.clearCallingIdentity()` and `try` \ + statement risk throwing an exception without doing a safe and unconditional \ + restore of the identity with `Binder.restoreCallingIdentity()` as an immediate \ + child of the `finally` block. If you do not follow the pattern, 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 + ) + ) + + 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." + + /** 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 = """ + 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. + + 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); + } + ``` + + If you do not surround the code using your identity with the `try` statement \ + and call `Binder.restoreCallingIdentity()` as an immediate child of the \ + `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 + ) + ) + + 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." + + /** 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 = """ + 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 \ + the caller's identity, so if this is expected replace them with methods that \ + explicitly query your own identity such as `Process.myUid()`, \ + `Process.myPid()` and `UserHandle.myUserId()`, otherwise move those methods \ + out of the `Binder.clearCallingIdentity()` / `Binder.restoreCallingIdentity()` \ + section. + + ``` + Binder.getCallingPid() + Binder.getCallingUid() + Binder.getCallingUidOrThrow() + Binder.getCallingUserHandle() + UserHandle.getCallingAppId() + UserHandle.getCallingUserId() + ``` + """, + 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)`." + } +} diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt new file mode 100644 index 000000000000..fe567da7c017 --- /dev/null +++ b/tools/lint/checks/src/main/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsDetector.kt @@ -0,0 +1,83 @@ +/* + * 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.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.PsiMethod +import org.jetbrains.uast.UCallExpression + +/** + * Lint Detector that finds issues with improper usages of the non-user getter methods of Settings + */ +@Suppress("UnstableApiUsage") +class CallingSettingsNonUserGetterMethodsDetector : Detector(), SourceCodeScanner { + override fun getApplicableMethodNames(): List<String> = listOf( + "getString", + "getInt", + "getLong", + "getFloat" + ) + + override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { + val evaluator = context.evaluator + if (evaluator.isMemberInClass(method, "android.provider.Settings.Secure") || + evaluator.isMemberInClass(method, "android.provider.Settings.System") + ) { + val message = getIncidentMessageNonUserGetterMethods(getMethodSignature(method)) + context.report(ISSUE_NON_USER_GETTER_CALLED, node, context.getNameLocation(node), + message) + } + } + + private fun getMethodSignature(method: PsiMethod) = + method.containingClass + ?.qualifiedName + ?.let { "$it#${method.name}" } + ?: method.name + + companion object { + @JvmField + val ISSUE_NON_USER_GETTER_CALLED: Issue = Issue.create( + id = "NonUserGetterCalled", + briefDescription = "Non-ForUser Getter Method called to Settings", + explanation = """ + System process should not call the non-ForUser getter methods of \ + `Settings.Secure` or `Settings.System`. For example, instead of \ + `Settings.Secure.getInt()`, use `Settings.Secure.getIntForUser()` instead. \ + This will make sure that the correct Settings value is retrieved. + """, + category = Category.CORRECTNESS, + priority = 6, + severity = Severity.ERROR, + implementation = Implementation( + CallingSettingsNonUserGetterMethodsDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + fun getIncidentMessageNonUserGetterMethods(methodSignature: String) = + "`$methodSignature()` called from system process. " + + "Please call `${methodSignature}ForUser()` instead. " + } +} 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 new file mode 100644 index 000000000000..8011b36c9a8f --- /dev/null +++ b/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt @@ -0,0 +1,169 @@ +/* + * 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/CallingIdentityTokenDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt new file mode 100644 index 000000000000..e1a5c613dee1 --- /dev/null +++ b/tools/lint/checks/src/test/java/com/google/android/lint/CallingIdentityTokenDetectorTest.kt @@ -0,0 +1,814 @@ +/* + * 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.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 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 + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + /** No issue scenario */ + + fun testDoesNotDetectIssuesInCorrectScenario() { + lint().files( + java( + """ + package test.pkg; + import android.os.Binder; + public class TestClass1 extends Binder { + private void testMethod() { + final long token1 = Binder.clearCallingIdentity(); + try { + } finally { + Binder.restoreCallingIdentity(token1); + } + final long token2 = android.os.Binder.clearCallingIdentity(); + try { + } finally { + android.os.Binder.restoreCallingIdentity(token2); + } + final long token3 = clearCallingIdentity(); + try { + } finally { + restoreCallingIdentity(token3); + } + } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + /** Unused token issue tests */ + + fun testDetectsUnusedTokens() { + lint().files( + java( + """ + package test.pkg; + import android.os.Binder; + public class TestClass1 extends Binder { + private void testMethodImported() { + final long token1 = Binder.clearCallingIdentity(); + try { + } finally { + } + } + private void testMethodFullClass() { + final long token2 = android.os.Binder.clearCallingIdentity(); + try { + } finally { + } + } + private void testMethodChildOfBinder() { + final long token3 = clearCallingIdentity(); + try { + } finally { + } + } + } + """ + ).indented(), + *stubs + ) + .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 \ + remove token1. [UnusedTokenOfOriginalCallingIdentity] + final long token1 = Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:11: Warning: token2 has not been used to \ + restore the calling identity. Introduce a try-finally after the \ + declaration and call Binder.restoreCallingIdentity(token2) in finally or \ + remove token2. [UnusedTokenOfOriginalCallingIdentity] + final long token2 = android.os.Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:17: Warning: token3 has not been used to \ + restore the calling identity. Introduce a try-finally after the \ + declaration and call Binder.restoreCallingIdentity(token3) in finally or \ + remove token3. [UnusedTokenOfOriginalCallingIdentity] + final long token3 = clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 3 warnings + """.addLineContinuation() + ) + } + + fun testDetectsUnusedTokensInScopes() { + lint().files( + java( + """ + package test.pkg; + import android.os.Binder; + public class TestClass1 { + private void testMethodTokenFromClearIdentity() { + final long token = Binder.clearCallingIdentity(); + try { + } finally { + } + } + private void testMethodTokenNotFromClearIdentity() { + long token = 0; + try { + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + """ + ).indented(), + *stubs + ) + .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 \ + remove token. [UnusedTokenOfOriginalCallingIdentity] + final long token = Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 1 warnings + """.addLineContinuation() + ) + } + + fun testDoesNotDetectUsedTokensInScopes() { + lint().files( + java( + """ + package test.pkg; + import android.os.Binder; + public class TestClass1 { + private void testMethodTokenFromClearIdentity() { + final long token = Binder.clearCallingIdentity(); + try { + } finally { + Binder.restoreCallingIdentity(token); + } + } + private void testMethodTokenNotFromClearIdentity() { + long token = 0; + try { + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testDetectsUnusedTokensWithSimilarNamesInScopes() { + lint().files( + java( + """ + package test.pkg; + import android.os.Binder; + public class TestClass1 { + private void testMethod1() { + final long token = Binder.clearCallingIdentity(); + try { + } finally { + } + } + private void testMethod2() { + final long token = Binder.clearCallingIdentity(); + try { + } finally { + } + } + } + """ + ).indented(), + *stubs + ) + .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 \ + remove token. [UnusedTokenOfOriginalCallingIdentity] + final long token = Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:11: 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 \ + remove token. [UnusedTokenOfOriginalCallingIdentity] + final long token = Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 2 warnings + """.addLineContinuation() + ) + } + + /** Non-final token issue tests */ + + fun testDetectsNonFinalTokens() { + lint().files( + java( + """ + package test.pkg; + import android.os.Binder; + public class TestClass1 extends Binder { + private void testMethod() { + long token1 = Binder.clearCallingIdentity(); + try { + } finally { + Binder.restoreCallingIdentity(token1); + } + long token2 = android.os.Binder.clearCallingIdentity(); + try { + } finally { + android.os.Binder.restoreCallingIdentity(token2); + } + long token3 = clearCallingIdentity(); + try { + } finally { + restoreCallingIdentity(token3); + } + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/test/pkg/TestClass1.java:5: Warning: token1 is a non-final token from \ + Binder.clearCallingIdentity(). Add final keyword to token1. \ + [NonFinalTokenOfOriginalCallingIdentity] + long token1 = Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:10: Warning: token2 is a non-final token from \ + Binder.clearCallingIdentity(). Add final keyword to token2. \ + [NonFinalTokenOfOriginalCallingIdentity] + long token2 = android.os.Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:15: Warning: token3 is a non-final token from \ + Binder.clearCallingIdentity(). Add final keyword to token3. \ + [NonFinalTokenOfOriginalCallingIdentity] + long token3 = clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 3 warnings + """.addLineContinuation() + ) + } + + /** Nested clearCallingIdentity() calls issue tests */ + + fun testDetectsNestedClearCallingIdentityCalls() { + // Pattern: clear - clear - clear - restore - restore - restore + lint().files( + java( + """ + package test.pkg; + import android.os.Binder; + public class TestClass1 extends Binder { + private void testMethod() { + final long token1 = Binder.clearCallingIdentity(); + try { + final long token2 = android.os.Binder.clearCallingIdentity(); + try { + final long token3 = clearCallingIdentity(); + try { + } finally { + restoreCallingIdentity(token3); + } + } finally { + android.os.Binder.restoreCallingIdentity(token2); + } + } finally { + Binder.restoreCallingIdentity(token1); + } + } + } + """ + ).indented(), + *stubs + ) + .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). \ + [NestedClearCallingIdentityCalls] + final long token2 = android.os.Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:5: Location of the token1 declaration. + src/test/pkg/TestClass1.java:9: Warning: The calling identity has already \ + been cleared and returned into token1. Move token3 declaration after \ + restoring the calling identity with Binder.restoreCallingIdentity(token1). \ + [NestedClearCallingIdentityCalls] + final long token3 = clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 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( + """ + package test.pkg; + import android.os.Binder; + public class TestClass1 extends Binder{ + private void testMethodNoTry() { + final long token = Binder.clearCallingIdentity(); + Binder.restoreCallingIdentity(token); + } + private void testMethodSomethingBetweenClearAndTry() { + final long token = Binder.clearCallingIdentity(); + int pid = 0; + try { + } finally { + Binder.restoreCallingIdentity(token); + } + } + private void testMethodLocalVariableBetweenClearAndTry() { + final long token = clearCallingIdentity(), num = 0; + try { + } finally { + restoreCallingIdentity(token); + } + } + private void testMethodTryCatch() { + final long token = android.os.Binder.clearCallingIdentity(); + try { + } catch (Exception e) { + } + Binder.restoreCallingIdentity(token); + } + private void testMethodTryCatchInScopes() { + final long token = android.os.Binder.clearCallingIdentity(); + { + try { + } catch (Exception e) { + } + } + Binder.restoreCallingIdentity(token); + } + } + """ + ).indented(), + *stubs + ) + .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 \ + to ensure a safe restore of the calling identity by calling \ + Binder.restoreCallingIdentity(token) and making it an immediate child of \ + the finally block. [ClearIdentityCallNotFollowedByTryFinally] + final long token = Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:9: 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 \ + to ensure a safe restore of the calling identity by calling \ + Binder.restoreCallingIdentity(token) and making it an immediate child of \ + the finally block. [ClearIdentityCallNotFollowedByTryFinally] + final long token = Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:17: 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 \ + to ensure a safe restore of the calling identity by calling \ + Binder.restoreCallingIdentity(token) and making it an immediate child of \ + the finally block. [ClearIdentityCallNotFollowedByTryFinally] + final long token = clearCallingIdentity(), num = 0; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:24: 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 \ + to ensure a safe restore of the calling identity by calling \ + Binder.restoreCallingIdentity(token) and making it an immediate child of \ + the finally block. [ClearIdentityCallNotFollowedByTryFinally] + final long token = android.os.Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:31: 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 \ + to ensure a safe restore of the calling identity by calling \ + Binder.restoreCallingIdentity(token) and making it an immediate child of \ + the finally block. [ClearIdentityCallNotFollowedByTryFinally] + final long token = android.os.Binder.clearCallingIdentity(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 5 warnings + """.addLineContinuation() + ) + } + + /** restoreCallingIdentity() call not in finally block issue tests */ + + fun testDetectsRestoreCallingIdentityCallNotInFinally() { + lint().files( + java( + """ + package test.pkg; + import android.os.Binder; + public class TestClass1 extends Binder { + private void testMethodImported() { + final long token = Binder.clearCallingIdentity(); + try { + } catch (Exception e) { + } finally { + } + Binder.restoreCallingIdentity(token); + } + private void testMethodFullClass() { + final long token = android.os.Binder.clearCallingIdentity(); + try { + } finally { + } + android.os.Binder.restoreCallingIdentity(token); + } + private void testMethodRestoreInCatch() { + final long token = clearCallingIdentity(); + try { + } catch (Exception e) { + restoreCallingIdentity(token); + } finally { + } + } + } + """ + ).indented(), + *stubs + ) + .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\ + all with finally block and call it unconditionally. \ + [RestoreIdentityCallNotInFinallyBlock] + Binder.restoreCallingIdentity(token); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:17: Warning: \ + Binder.restoreCallingIdentity(token) is not an immediate child of the \ + finally block of the try statement after token declaration. Surround the c\ + all with finally block and call it unconditionally. \ + [RestoreIdentityCallNotInFinallyBlock] + android.os.Binder.restoreCallingIdentity(token); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:23: Warning: \ + Binder.restoreCallingIdentity(token) is not an immediate child of the \ + finally block of the try statement after token declaration. Surround the c\ + all with finally block and call it unconditionally. \ + [RestoreIdentityCallNotInFinallyBlock] + restoreCallingIdentity(token); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 3 warnings + """.addLineContinuation() + ) + } + + fun testDetectsRestoreCallingIdentityCallNotInFinallyInScopes() { + lint().files( + java( + """ + package test.pkg; + import android.os.Binder; + public class TestClass1 extends Binder { + private void testMethodOutsideFinally() { + final long token1 = Binder.clearCallingIdentity(); + try { + } catch (Exception e) { + } finally { + } + { + Binder.restoreCallingIdentity(token1); + } + final long token2 = android.os.Binder.clearCallingIdentity(); + try { + } finally { + } + { + { + { + android.os.Binder.restoreCallingIdentity(token2); + } + } + } + } + private void testMethodInsideFinallyInScopes() { + final long token1 = Binder.clearCallingIdentity(); + try { + } finally { + { + { + Binder.restoreCallingIdentity(token1); + } + } + } + final long token2 = clearCallingIdentity(); + try { + } finally { + if (true) restoreCallingIdentity(token2); + } + } + } + """ + ).indented(), + *stubs + ) + .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 \ + call with finally block and call it unconditionally. \ + [RestoreIdentityCallNotInFinallyBlock] + Binder.restoreCallingIdentity(token1); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:20: Warning: \ + Binder.restoreCallingIdentity(token2) is not an immediate child of the \ + finally block of the try statement after token2 declaration. Surround the \ + call with finally block and call it unconditionally. \ + [RestoreIdentityCallNotInFinallyBlock] + android.os.Binder.restoreCallingIdentity(token2); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:31: Warning: \ + Binder.restoreCallingIdentity(token1) is not an immediate child of the \ + finally block of the try statement after token1 declaration. Surround the \ + call with finally block and call it unconditionally. \ + [RestoreIdentityCallNotInFinallyBlock] + Binder.restoreCallingIdentity(token1); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:38: Warning: \ + Binder.restoreCallingIdentity(token2) is not an immediate child of the \ + finally block of the try statement after token2 declaration. Surround the \ + call with finally block and call it unconditionally. \ + [RestoreIdentityCallNotInFinallyBlock] + if (true) restoreCallingIdentity(token2); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 4 warnings + """.addLineContinuation() + ) + } + + /** Use of caller-aware methods after clearCallingIdentity() issue tests */ + + fun testDetectsUseOfCallerAwareMethodsWithClearedIdentityIssuesInScopes() { + lint().files( + java( + """ + package test.pkg; + import android.os.Binder; + import android.os.UserHandle; + public class TestClass1 { + private void testMethod() { + final long token = Binder.clearCallingIdentity(); + try { + int pid1 = Binder.getCallingPid(); + int pid2 = android.os.Binder.getCallingPid(); + int uid1 = Binder.getCallingUid(); + int uid2 = android.os.Binder.getCallingUid(); + int uid3 = Binder.getCallingUidOrThrow(); + int uid4 = android.os.Binder.getCallingUidOrThrow(); + UserHandle uh1 = Binder.getCallingUserHandle(); + UserHandle uh2 = android.os.Binder.getCallingUserHandle(); + { + int appId1 = UserHandle.getCallingAppId(); + int appId2 = android.os.UserHandle.getCallingAppId(); + int userId1 = UserHandle.getCallingUserId(); + int userId2 = android.os.UserHandle.getCallingUserId(); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + """ + ).indented(), + *stubs + ) + .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 \ + caller's. Either explicitly query your own identity or move it after \ + restoring the identity with Binder.restoreCallingIdentity(token). \ + [UseOfCallerAwareMethodsWithClearedIdentity] + int pid1 = Binder.getCallingPid(); + ~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:9: Warning: You cleared the original identity \ + with Binder.clearCallingIdentity() and returned into token, so \ + getCallingPid() 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(token). \ + [UseOfCallerAwareMethodsWithClearedIdentity] + int pid2 = android.os.Binder.getCallingPid(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:10: Warning: You cleared the original \ + identity with Binder.clearCallingIdentity() and returned into token, so \ + getCallingUid() 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(token). \ + [UseOfCallerAwareMethodsWithClearedIdentity] + int uid1 = Binder.getCallingUid(); + ~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:11: Warning: You cleared the original \ + identity with Binder.clearCallingIdentity() and returned into token, so \ + getCallingUid() 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(token). \ + [UseOfCallerAwareMethodsWithClearedIdentity] + int uid2 = android.os.Binder.getCallingUid(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:12: Warning: You cleared the original \ + identity with Binder.clearCallingIdentity() and returned into token, so \ + getCallingUidOrThrow() 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(token). \ + [UseOfCallerAwareMethodsWithClearedIdentity] + int uid3 = Binder.getCallingUidOrThrow(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:13: Warning: You cleared the original \ + identity with Binder.clearCallingIdentity() and returned into token, so \ + getCallingUidOrThrow() 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(token). \ + [UseOfCallerAwareMethodsWithClearedIdentity] + int uid4 = android.os.Binder.getCallingUidOrThrow(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:14: Warning: You cleared the original \ + identity with Binder.clearCallingIdentity() and returned into token, so \ + getCallingUserHandle() 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(token). \ + [UseOfCallerAwareMethodsWithClearedIdentity] + UserHandle uh1 = Binder.getCallingUserHandle(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:15: Warning: You cleared the original \ + identity with Binder.clearCallingIdentity() and returned into token, so \ + getCallingUserHandle() 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(token). \ + [UseOfCallerAwareMethodsWithClearedIdentity] + UserHandle uh2 = android.os.Binder.getCallingUserHandle(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:17: Warning: You cleared the original \ + identity with Binder.clearCallingIdentity() and returned into token, so \ + getCallingAppId() 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(token). \ + [UseOfCallerAwareMethodsWithClearedIdentity] + int appId1 = UserHandle.getCallingAppId(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:18: Warning: You cleared the original \ + identity with Binder.clearCallingIdentity() and returned into token, so \ + getCallingAppId() 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(token). \ + [UseOfCallerAwareMethodsWithClearedIdentity] + int appId2 = android.os.UserHandle.getCallingAppId(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:19: Warning: You cleared the original \ + identity with Binder.clearCallingIdentity() and returned into token, so \ + getCallingUserId() 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(token). \ + [UseOfCallerAwareMethodsWithClearedIdentity] + int userId1 = UserHandle.getCallingUserId(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + src/test/pkg/TestClass1.java:20: Warning: You cleared the original \ + identity with Binder.clearCallingIdentity() and returned into token, so \ + getCallingUserId() 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(token). \ + [UseOfCallerAwareMethodsWithClearedIdentity] + int userId2 = android.os.UserHandle.getCallingUserId(); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0 errors, 12 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() { + return 0; + } + public static final native void restoreCallingIdentity(long token) { + } + public static final native int getCallingPid() { + return 0; + } + public static final native int getCallingUid() { + return 0; + } + public static final int getCallingUidOrThrow() { + return 0; + } + public static final @NonNull UserHandle getCallingUserHandle() { + return UserHandle.of(UserHandle.getUserId(getCallingUid())); + } + } + """ + ).indented() + + private val userHandleStub: TestFile = java( + """ + package android.os; + import android.annotation.AppIdInt; + import android.annotation.UserIdInt; + public class UserHandle { + public static @AppIdInt int getCallingAppId() { + return getAppId(Binder.getCallingUid()); + } + public static @UserIdInt int getCallingUserId() { + return getUserId(Binder.getCallingUid()); + } + public static @UserIdInt int getUserId(int uid) { + return 0; + } + public static @AppIdInt int getAppId(int uid) { + return 0; + } + public static UserHandle of(@UserIdInt int userId) { + return new UserHandle(); + } + } + """ + ).indented() + + private val userIdIntStub: TestFile = java( + """ + package android.annotation; + public @interface UserIdInt { + } + """ + ).indented() + + private val appIdIntStub: TestFile = java( + """ + package android.annotation; + public @interface AppIdInt { + } + """ + ).indented() + + private val stubs = arrayOf(binderStub, userHandleStub, userIdIntStub, appIdIntStub) + + // 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/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt new file mode 100644 index 000000000000..e72f38416310 --- /dev/null +++ b/tools/lint/checks/src/test/java/com/google/android/lint/CallingSettingsNonUserGetterMethodsIssueDetectorTest.kt @@ -0,0 +1,217 @@ +/* + * 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.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 CallingSettingsNonUserGetterMethodsIssueDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = CallingSettingsNonUserGetterMethodsDetector() + + override fun getIssues(): List<Issue> = listOf( + CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + fun testDoesNotDetectIssues() { + lint().files( + java( + """ + package test.pkg; + import android.provider.Settings.Secure; + public class TestClass1 { + private void testMethod(Context context) { + final int value = Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.KEY1, 0, 0); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testDetectsNonUserGetterCalledFromSecure() { + lint().files( + java( + """ + package test.pkg; + import android.provider.Settings.Secure; + public class TestClass1 { + private void testMethod(Context context) { + final int value = Secure.getInt(context.getContentResolver(), + Settings.Secure.KEY1); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/test/pkg/TestClass1.java:5: Error: \ + android.provider.Settings.Secure#getInt() called from system process. \ + Please call android.provider.Settings.Secure#getIntForUser() instead. \ + [NonUserGetterCalled] + final int value = Secure.getInt(context.getContentResolver(), + ~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation() + ) + } + fun testDetectsNonUserGetterCalledFromSystem() { + lint().files( + java( + """ + package test.pkg; + import android.provider.Settings.System; + public class TestClass1 { + private void testMethod(Context context) { + final float value = System.getFloat(context.getContentResolver(), + Settings.System.KEY1); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/test/pkg/TestClass1.java:5: Error: \ + android.provider.Settings.System#getFloat() called from system process. \ + Please call android.provider.Settings.System#getFloatForUser() instead. \ + [NonUserGetterCalled] + final float value = System.getFloat(context.getContentResolver(), + ~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation() + ) + } + + fun testDetectsNonUserGetterCalledFromSettings() { + lint().files( + java( + """ + package test.pkg; + import android.provider.Settings; + public class TestClass1 { + private void testMethod(Context context) { + float value = Settings.System.getFloat(context.getContentResolver(), + Settings.System.KEY1); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/test/pkg/TestClass1.java:5: Error: \ + android.provider.Settings.System#getFloat() called from system process. \ + Please call android.provider.Settings.System#getFloatForUser() instead. \ + [NonUserGetterCalled] + float value = Settings.System.getFloat(context.getContentResolver(), + ~~~~~~~~ + 1 errors, 0 warnings + """.addLineContinuation() + ) + } + + fun testDetectsNonUserGettersCalledFromSystemAndSecure() { + lint().files( + java( + """ + package test.pkg; + import android.provider.Settings.Secure; + import android.provider.Settings.System; + public class TestClass1 { + private void testMethod(Context context) { + final long value1 = Secure.getLong(context.getContentResolver(), + Settings.Secure.KEY1, 0); + final String value2 = System.getString(context.getContentResolver(), + Settings.System.KEY2); + } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/test/pkg/TestClass1.java:6: Error: \ + android.provider.Settings.Secure#getLong() called from system process. \ + Please call android.provider.Settings.Secure#getLongForUser() instead. \ + [NonUserGetterCalled] + final long value1 = Secure.getLong(context.getContentResolver(), + ~~~~~~~ + src/test/pkg/TestClass1.java:8: Error: \ + android.provider.Settings.System#getString() called from system process. \ + Please call android.provider.Settings.System#getStringForUser() instead. \ + [NonUserGetterCalled] + final String value2 = System.getString(context.getContentResolver(), + ~~~~~~~~~ + 2 errors, 0 warnings + """.addLineContinuation() + ) + } + + private val SettingsStub: TestFile = java( + """ + package android.provider; + public class Settings { + public class Secure { + float getFloat(ContentResolver cr, String key) { + return 0.0f; + } + long getLong(ContentResolver cr, String key) { + return 0l; + } + int getInt(ContentResolver cr, String key) { + return 0; + } + } + public class System { + float getFloat(ContentResolver cr, String key) { + return 0.0f; + } + long getLong(ContentResolver cr, String key) { + return 0l; + } + String getString(ContentResolver cr, String key) { + return null; + } + } + } + """ + ).indented() + + private val stubs = arrayOf(SettingsStub) + + // 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/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt new file mode 100644 index 000000000000..f5f4ebee24e0 --- /dev/null +++ b/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt @@ -0,0 +1,202 @@ +/* + * 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/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp index 98c0e69cbf8e..6efd1f64d8fe 100644 --- a/tools/locked_region_code_injection/Android.bp +++ b/tools/locked_region_code_injection/Android.bp @@ -12,10 +12,10 @@ java_binary_host { manifest: "manifest.txt", srcs: ["src/**/*.java"], static_libs: [ - "asm-6.0", - "asm-commons-6.0", - "asm-tree-6.0", - "asm-analysis-6.0", + "asm-9.2", + "asm-commons-9.2", + "asm-tree-9.2", + "asm-analysis-9.2", "guava-21.0", ], } diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java index 1002c88e9281..335b3f82c533 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java @@ -13,8 +13,6 @@ */ package lockedregioncodeinjection; -import java.util.ArrayList; -import java.util.List; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; @@ -22,6 +20,9 @@ import org.objectweb.asm.tree.analysis.AnalyzerException; import org.objectweb.asm.tree.analysis.BasicInterpreter; import org.objectweb.asm.tree.analysis.BasicValue; +import java.util.ArrayList; +import java.util.List; + /** * A simple dataflow analysis to determine if the operands on the stack must be one of target lock * class type. @@ -31,6 +32,7 @@ public class LockTargetStateAnalysis extends BasicInterpreter { private final List<LockTarget> targetLocks; public LockTargetStateAnalysis(List<LockTarget> targetLocks) { + super(Utils.ASM_VERSION); this.targetLocks = targetLocks; } diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java index 219c2b3a2fec..f1e84b1d8a33 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java @@ -13,13 +13,14 @@ */ package lockedregioncodeinjection; +import org.objectweb.asm.Opcodes; + import java.util.ArrayList; import java.util.List; -import org.objectweb.asm.Opcodes; public class Utils { - public static final int ASM_VERSION = Opcodes.ASM6; + public static final int ASM_VERSION = Opcodes.ASM7; /** * Reads a comma separated configuration similar to the Jack definition. diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java index c408b9e99c32..31fa0bf63416 100644 --- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java +++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java @@ -30,7 +30,7 @@ import org.junit.Test; * rm -fr out/* * * # Make booster - * javac -cp lib/asm-6.0_BETA.jar:lib/asm-commons-6.0_BETA.jar:lib/asm-tree-6.0_BETA.jar:lib/asm-analysis-6.0_BETA.jar:lib/guava-21.0.jar src/*/*.java -d out/ + * 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 @@ -43,7 +43,7 @@ import org.junit.Test; * popd * * # Run tool on unit tests. - * java -ea -cp lib/asm-6.0_BETA.jar:lib/asm-commons-6.0_BETA.jar:lib/asm-tree-6.0_BETA.jar:lib/asm-analysis-6.0_BETA.jar:lib/guava-21.0.jar:out/lockedregioncodeinjection.jar \ + * 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 \ * --targets 'Llockedregioncodeinjection/TestTarget;' \ diff --git a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt index 1aec9b812e61..2e60f64b21e8 100644 --- a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt +++ b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt @@ -21,8 +21,6 @@ import com.android.codegen.BASE_BUILDER_CLASS import com.android.codegen.CANONICAL_BUILDER_CLASS import com.android.codegen.CODEGEN_NAME import com.android.codegen.CODEGEN_VERSION -import com.sun.tools.javac.code.Symbol -import com.sun.tools.javac.code.Type import java.io.File import java.io.FileNotFoundException import javax.annotation.processing.AbstractProcessor @@ -33,6 +31,7 @@ import javax.lang.model.element.AnnotationMirror import javax.lang.model.element.Element import javax.lang.model.element.ElementKind import javax.lang.model.element.TypeElement +import javax.lang.model.type.ExecutableType import javax.tools.Diagnostic private const val STALE_FILE_THRESHOLD_MS = 1000 @@ -102,14 +101,13 @@ class StaleDataclassProcessor: AbstractProcessor() { append(" ") append(elem.annotationMirrors.joinToString(" ", transform = { annotationToString(it) })) append(" ") - if (elem is Symbol) { - if (elem.type is Type.MethodType) { - append((elem.type as Type.MethodType).returnType) - } else { - append(elem.type) - } - append(" ") + val type = elem.asType() + if (type is ExecutableType) { + append(type.returnType) + } else { + append(type) } + append(" ") append(elem) } } @@ -234,4 +232,4 @@ class StaleDataclassProcessor: AbstractProcessor() { override fun getSupportedSourceVersion(): SourceVersion { return SourceVersion.latest() } -}
\ No newline at end of file +} diff --git a/tools/sdkparcelables/Android.bp b/tools/sdkparcelables/Android.bp index 9d773e44faea..6ebacd8a0b14 100644 --- a/tools/sdkparcelables/Android.bp +++ b/tools/sdkparcelables/Android.bp @@ -14,7 +14,7 @@ java_binary_host { "src/**/*.kt", ], static_libs: [ - "asm-6.0", + "asm-9.2", ], } diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt index 22e8d781335b..0fb062f280e3 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.ASM6, null) + val ancestorCollector = AncestorCollector(Opcodes.ASM7, null) for (entry in zipFile.entries()) { if (entry.name.endsWith(".class")) { diff --git a/tools/split-select/Android.bp b/tools/split-select/Android.bp index c12fc6a7f253..540265793de6 100644 --- a/tools/split-select/Android.bp +++ b/tools/split-select/Android.bp @@ -48,7 +48,6 @@ cc_defaults { "libbase", "libz", ], - group_static_libs: true, target: { windows: { diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp index 1ec83a360048..b18bdff7263f 100644 --- a/tools/streaming_proto/Android.bp +++ b/tools/streaming_proto/Android.bp @@ -69,7 +69,6 @@ java_library { "test/**/*.proto", ], proto: { - plugin: "javastream", + type: "stream", }, - static_libs: ["libprotobuf-java-lite"], } diff --git a/tools/streaming_proto/Errors.cpp b/tools/streaming_proto/Errors.cpp index 0cd9037dcb55..6890d99b104c 100644 --- a/tools/streaming_proto/Errors.cpp +++ b/tools/streaming_proto/Errors.cpp @@ -1,5 +1,6 @@ #include "Errors.h" +#include <stdarg.h> #include <stdlib.h> namespace android { diff --git a/tools/traceinjection/Android.bp b/tools/traceinjection/Android.bp new file mode 100644 index 000000000000..39d1b1c2defd --- /dev/null +++ b/tools/traceinjection/Android.bp @@ -0,0 +1,49 @@ +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_binary_host { + name: "traceinjection", + 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", + ], +} + +java_library_host { + name: "TraceInjectionTests-Uninjected", + srcs: ["test/**/*.java"], + static_libs: [ + "junit", + ], +} + +java_genrule_host { + name: "TraceInjectionTests-Injected", + srcs: [":TraceInjectionTests-Uninjected"], + tools: ["traceinjection"], + cmd: "$(location traceinjection) " + + " --annotation \"com/android/traceinjection/Trace\"" + + " --start \"com/android/traceinjection/InjectionTests.traceStart\"" + + " --end \"com/android/traceinjection/InjectionTests.traceEnd\"" + + " -o $(out) " + + " -i $(in)", + out: ["TraceInjectionTests-Injected.jar"], +} + +java_test_host { + name: "TraceInjectionTests", + static_libs: [ + "TraceInjectionTests-Injected", + ], +} diff --git a/tools/traceinjection/manifest.txt b/tools/traceinjection/manifest.txt new file mode 100644 index 000000000000..7f4ee1d617fa --- /dev/null +++ b/tools/traceinjection/manifest.txt @@ -0,0 +1 @@ +Main-Class: com.android.traceinjection.Main diff --git a/tools/traceinjection/src/com/android/traceinjection/Main.java b/tools/traceinjection/src/com/android/traceinjection/Main.java new file mode 100644 index 000000000000..190df819dd64 --- /dev/null +++ b/tools/traceinjection/src/com/android/traceinjection/Main.java @@ -0,0 +1,121 @@ +/* + * 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.android.traceinjection; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; + +import java.io.BufferedInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public class Main { + public static void main(String[] args) throws IOException { + String inJar = null; + String outJar = null; + String annotation = null; + String traceStart = null; + String traceEnd = null; + + // All arguments require a value currently, so just make sure we have an even number and + // then process them all two at a time. + if (args.length % 2 != 0) { + throw new IllegalArgumentException("Argument is missing corresponding value"); + } + for (int i = 0; i < args.length - 1; i += 2) { + final String arg = args[i].trim(); + final String argValue = args[i + 1].trim(); + if ("-i".equals(arg)) { + inJar = argValue; + } else if ("-o".equals(arg)) { + outJar = argValue; + } else if ("--annotation".equals(arg)) { + annotation = argValue; + } else if ("--start".equals(arg)) { + traceStart = argValue; + } else if ("--end".equals(arg)) { + traceEnd = argValue; + } else { + throw new IllegalArgumentException("Unknown argument: " + arg); + } + } + + if (inJar == null) { + throw new IllegalArgumentException("input jar is required"); + } + + if (outJar == null) { + throw new IllegalArgumentException("output jar is required"); + } + + if (annotation == null) { + throw new IllegalArgumentException("trace annotation is required"); + } + + if (traceStart == null) { + throw new IllegalArgumentException("start trace method is required"); + } + + if (traceEnd == null) { + throw new IllegalArgumentException("end trace method is required"); + } + + TraceInjectionConfiguration params = + new TraceInjectionConfiguration(annotation, traceStart, traceEnd); + + try ( + ZipFile zipSrc = new ZipFile(inJar); + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outJar)); + ) { + Enumeration<? extends ZipEntry> srcEntries = zipSrc.entries(); + while (srcEntries.hasMoreElements()) { + ZipEntry entry = srcEntries.nextElement(); + ZipEntry newEntry = new ZipEntry(entry.getName()); + newEntry.setTime(entry.getTime()); + zos.putNextEntry(newEntry); + BufferedInputStream bis = new BufferedInputStream(zipSrc.getInputStream(entry)); + + if (entry.getName().endsWith(".class")) { + convert(bis, zos, params); + } else { + while (bis.available() > 0) { + zos.write(bis.read()); + } + zos.closeEntry(); + bis.close(); + } + } + zos.finish(); + } + } + + private static void convert(InputStream in, OutputStream out, + TraceInjectionConfiguration params) throws IOException { + ClassReader cr = new ClassReader(in); + ClassWriter cw = new ClassWriter(0); + TraceInjectionClassVisitor cv = new TraceInjectionClassVisitor(cw, params); + cr.accept(cv, ClassReader.EXPAND_FRAMES); + byte[] data = cw.toByteArray(); + out.write(data); + } +} diff --git a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java new file mode 100644 index 000000000000..863f976b8aff --- /dev/null +++ b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionClassVisitor.java @@ -0,0 +1,41 @@ +/* + * 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.android.traceinjection; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * {@link ClassVisitor} that injects tracing code to methods annotated with the configured + * annotation. + */ +public class TraceInjectionClassVisitor extends ClassVisitor { + private final TraceInjectionConfiguration mParams; + public TraceInjectionClassVisitor(ClassVisitor classVisitor, + TraceInjectionConfiguration params) { + super(Opcodes.ASM7, classVisitor); + mParams = params; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, + String[] exceptions) { + MethodVisitor chain = super.visitMethod(access, name, desc, signature, exceptions); + return new TraceInjectionMethodAdapter(chain, access, name, desc, mParams); + } +} diff --git a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionConfiguration.java b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionConfiguration.java new file mode 100644 index 000000000000..f9595bdad9cf --- /dev/null +++ b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionConfiguration.java @@ -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.android.traceinjection; + +/** + * Configuration data for trace method injection. + */ +public class TraceInjectionConfiguration { + public final String annotation; + public final String startMethodClass; + public final String startMethodName; + public final String endMethodClass; + public final String endMethodName; + + public TraceInjectionConfiguration(String annotation, String startMethod, String endMethod) { + this.annotation = annotation; + String[] startMethodComponents = parseMethod(startMethod); + String[] endMethodComponents = parseMethod(endMethod); + startMethodClass = startMethodComponents[0]; + startMethodName = startMethodComponents[1]; + endMethodClass = endMethodComponents[0]; + endMethodName = endMethodComponents[1]; + } + + public String toString() { + return "TraceInjectionParams{annotation=" + annotation + + ", startMethod=" + startMethodClass + "." + startMethodName + + ", endMethod=" + endMethodClass + "." + endMethodName + "}"; + } + + private static String[] parseMethod(String method) { + String[] methodComponents = method.split("\\."); + if (methodComponents.length != 2) { + throw new IllegalArgumentException("Invalid method descriptor: " + method); + } + return methodComponents; + } +} diff --git a/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java new file mode 100644 index 000000000000..c2bbddcb5668 --- /dev/null +++ b/tools/traceinjection/src/com/android/traceinjection/TraceInjectionMethodAdapter.java @@ -0,0 +1,183 @@ +/* + * 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.android.traceinjection; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.AdviceAdapter; +import org.objectweb.asm.commons.Method; + +/** + * Adapter that injects tracing code to methods annotated with the configured annotation. + * + * Assuming the configured annotation is {@code @Trace} and the configured methods are + * {@code Tracing.begin()} and {@code Tracing.end()}, it effectively transforms: + * + * <pre>{@code + * @Trace + * void method() { + * doStuff(); + * } + * }</pre> + * + * into: + * <pre>{@code + * @Trace + * void method() { + * Tracing.begin(); + * try { + * doStuff(); + * } finally { + * Tracing.end(); + * } + * } + * }</pre> + */ +public class TraceInjectionMethodAdapter extends AdviceAdapter { + private final TraceInjectionConfiguration mParams; + private final Label mStartFinally = newLabel(); + private final boolean mIsConstructor; + + private boolean mShouldTrace; + private long mTraceId; + private String mTraceLabel; + + public TraceInjectionMethodAdapter(MethodVisitor methodVisitor, int access, + String name, String descriptor, TraceInjectionConfiguration params) { + super(Opcodes.ASM7, methodVisitor, access, name, descriptor); + mParams = params; + mIsConstructor = "<init>".equals(name); + } + + @Override + public void visitCode() { + super.visitCode(); + if (mShouldTrace) { + visitLabel(mStartFinally); + } + } + + @Override + protected void onMethodEnter() { + if (!mShouldTrace) { + return; + } + Type type = Type.getType(toJavaSpecifier(mParams.startMethodClass)); + Method trace = Method.getMethod("void " + mParams.startMethodName + " (long, String)"); + push(mTraceId); + push(getTraceLabel()); + invokeStatic(type, trace); + } + + private String getTraceLabel() { + return !isEmpty(mTraceLabel) ? mTraceLabel : getName(); + } + + @Override + protected void onMethodExit(int opCode) { + // Any ATHROW exits will be caught as part of our exception-handling block, so putting it + // here would cause us to call the end trace method multiple times. + if (opCode != ATHROW) { + onFinally(); + } + } + + private void onFinally() { + if (!mShouldTrace) { + return; + } + Type type = Type.getType(toJavaSpecifier(mParams.endMethodClass)); + Method trace = Method.getMethod("void " + mParams.endMethodName + " (long)"); + push(mTraceId); + invokeStatic(type, trace); + } + + @Override + public void visitMaxs(int maxStack, int maxLocals) { + final int minStackSize; + if (mShouldTrace) { + Label endFinally = newLabel(); + visitLabel(endFinally); + catchException(mStartFinally, endFinally, null); + // The stack will always contain exactly one element: the exception we caught + final Object[] stack = new Object[]{ "java/lang/Throwable"}; + // Because we use EXPAND_FRAMES, the frame type must always be F_NEW. + visitFrame(F_NEW, /* numLocal= */ 0, /* local= */ null, stack.length, stack); + onFinally(); + // Rethrow the exception that we caught in the finally block. + throwException(); + + // Make sure we have at least enough stack space to push the trace arguments + // (long, String) + minStackSize = Type.LONG_TYPE.getSize() + Type.getType(String.class).getSize(); + } else { + // We didn't inject anything, so no need for additional stack space. + minStackSize = 0; + } + + super.visitMaxs(Math.max(minStackSize, maxStack), maxLocals); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + AnnotationVisitor av = super.visitAnnotation(descriptor, visible); + if (descriptor.equals(toJavaSpecifier(mParams.annotation))) { + if (mIsConstructor) { + // TODO: Support constructor tracing. At the moment, constructors aren't supported + // because you can't put an exception handler around a super() call within the + // constructor itself. + throw new IllegalStateException("Cannot trace constructors"); + } + av = new TracingAnnotationVisitor(av); + } + return av; + } + + /** + * An AnnotationVisitor that pulls the trace ID and label information from the configured + * annotation. + */ + class TracingAnnotationVisitor extends AnnotationVisitor { + + TracingAnnotationVisitor(AnnotationVisitor annotationVisitor) { + super(Opcodes.ASM7, annotationVisitor); + } + + @Override + public void visit(String name, Object value) { + if ("tag".equals(name)) { + mTraceId = (long) value; + // If we have a trace annotation and ID, then we have everything we need to trace + mShouldTrace = true; + } else if ("label".equals(name)) { + mTraceLabel = (String) value; + } + super.visit(name, value); + } + } + + private static String toJavaSpecifier(String klass) { + return "L" + klass + ";"; + } + + private static boolean isEmpty(String str) { + return str == null || "".equals(str); + } +} diff --git a/tools/traceinjection/test/com/android/traceinjection/InjectionTests.java b/tools/traceinjection/test/com/android/traceinjection/InjectionTests.java new file mode 100644 index 000000000000..81bf235fe0a6 --- /dev/null +++ b/tools/traceinjection/test/com/android/traceinjection/InjectionTests.java @@ -0,0 +1,246 @@ +/* + * 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.android.traceinjection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RunWith(JUnit4.class) +public class InjectionTests { + public static final int TRACE_TAG = 42; + public static final String CUSTOM_TRACE_NAME = "Custom"; + + public static final TraceTracker TRACKER = new TraceTracker(); + + @After + public void tearDown() { + TRACKER.reset(); + } + + @Test + public void testDefaultLabel() { + assertTraces(this::tracedMethod, "tracedMethod"); + tracedMethodThrowsAndCatches(); + } + + @Test + public void testCustomLabel() { + assertTraces(this::tracedMethodHasCustomName, CUSTOM_TRACE_NAME); + } + + @Test + public void testTracedMethodsStillThrow() { + assertTraces(() -> assertThrows(IllegalArgumentException.class, this::tracedMethodThrows), + "tracedMethodThrows"); + // Also test that we rethrow exceptions from method calls. This is slightly different from + // the previous case because the ATHROW instruction is not actually present at all in the + // bytecode of the instrumented method. + TRACKER.reset(); + assertTraces(() -> assertThrows(NullPointerException.class, + this::tracedMethodCallsThrowingMethod), + "tracedMethodCallsThrowingMethod"); + } + + @Test + public void testNestedTracedMethods() { + assertTraces(this::outerTracedMethod, "outerTracedMethod", "innerTracedMethod"); + } + + @Test + public void testTracedMethodWithCatchBlock() { + assertTraces(this::tracedMethodThrowsAndCatches, "tracedMethodThrowsAndCatches"); + } + + @Test + public void testTracedMethodWithFinallyBlock() { + assertTraces(() -> assertThrows(IllegalArgumentException.class, + this::tracedMethodThrowWithFinally), "tracedMethodThrowWithFinally"); + } + + @Test + public void testNonVoidMethod() { + assertTraces(this::tracedNonVoidMethod, "tracedNonVoidMethod"); + } + + @Test + public void testNonVoidMethodReturnsWithinCatches() { + assertTraces(this::tracedNonVoidMethodReturnsWithinCatches, + "tracedNonVoidMethodReturnsWithinCatches"); + } + + @Test + public void testNonVoidMethodReturnsWithinFinally() { + assertTraces(this::tracedNonVoidMethodReturnsWithinFinally, + "tracedNonVoidMethodReturnsWithinFinally"); + } + + @Test + public void testTracedStaticMethod() { + assertTraces(InjectionTests::tracedStaticMethod, "tracedStaticMethod"); + } + + @Trace(tag = TRACE_TAG) + public void tracedMethod() { + assertEquals(1, TRACKER.getTraceCount(TRACE_TAG)); + } + + @Trace(tag = TRACE_TAG) + public void tracedMethodThrows() { + throw new IllegalArgumentException(); + } + + @Trace(tag = TRACE_TAG) + public void tracedMethodCallsThrowingMethod() { + throwingMethod(); + } + + private void throwingMethod() { + throw new NullPointerException(); + } + + + @Trace(tag = TRACE_TAG) + public void tracedMethodThrowsAndCatches() { + try { + throw new IllegalArgumentException(); + } catch (IllegalArgumentException ignored) { + assertEquals(1, TRACKER.getTraceCount(TRACE_TAG)); + } + } + + @Trace(tag = TRACE_TAG) + public void tracedMethodThrowWithFinally() { + try { + throw new IllegalArgumentException(); + } finally { + assertEquals(1, TRACKER.getTraceCount(TRACE_TAG)); + } + } + + @Trace(tag = TRACE_TAG, label = CUSTOM_TRACE_NAME) + public void tracedMethodHasCustomName() { + } + + @Trace(tag = TRACE_TAG) + public void outerTracedMethod() { + innerTracedMethod(); + assertEquals(1, TRACKER.getTraceCount(TRACE_TAG)); + } + + @Trace(tag = TRACE_TAG) + public void innerTracedMethod() { + assertEquals(2, TRACKER.getTraceCount(TRACE_TAG)); + } + + @Trace(tag = TRACE_TAG) + public int tracedNonVoidMethod() { + assertEquals(1, TRACKER.getTraceCount(TRACE_TAG)); + return 0; + } + + @Trace(tag = TRACE_TAG) + public int tracedNonVoidMethodReturnsWithinCatches() { + try { + throw new IllegalArgumentException(); + } catch (IllegalArgumentException ignored) { + assertEquals(1, TRACKER.getTraceCount(TRACE_TAG)); + return 0; + } + } + + @Trace(tag = TRACE_TAG) + public int tracedNonVoidMethodReturnsWithinFinally() { + try { + throw new IllegalArgumentException(); + } finally { + assertEquals(1, TRACKER.getTraceCount(TRACE_TAG)); + return 0; + } + } + + @Trace(tag = TRACE_TAG) + public static void tracedStaticMethod() { + assertEquals(1, TRACKER.getTraceCount(TRACE_TAG)); + } + + public void assertTraces(Runnable r, String... traceLabels) { + r.run(); + assertEquals(Arrays.asList(traceLabels), TRACKER.getTraceLabels(TRACE_TAG)); + TRACKER.assertAllTracesClosed(); + } + + public static void traceStart(long tag, String name) { + TRACKER.onTraceStart(tag, name); + } + + public static void traceEnd(long tag) { + TRACKER.onTraceEnd(tag); + } + + static class TraceTracker { + private final Map<Long, List<String>> mTraceLabelsByTag = new HashMap<>(); + private final Map<Long, Integer> mTraceCountsByTag = new HashMap<>(); + + public void onTraceStart(long tag, String name) { + getTraceLabels(tag).add(name); + mTraceCountsByTag.put(tag, mTraceCountsByTag.getOrDefault(tag, 0) + 1); + } + + public void onTraceEnd(long tag) { + final int newCount = getTraceCount(tag) - 1; + if (newCount < 0) { + throw new IllegalStateException("Trace count has gone negative for tag " + tag); + } + mTraceCountsByTag.put(tag, newCount); + } + + public void reset() { + mTraceLabelsByTag.clear(); + mTraceCountsByTag.clear(); + } + + public List<String> getTraceLabels(long tag) { + if (!mTraceLabelsByTag.containsKey(tag)) { + mTraceLabelsByTag.put(tag, new ArrayList<>()); + } + return mTraceLabelsByTag.get(tag); + } + + public int getTraceCount(long tag) { + return mTraceCountsByTag.getOrDefault(tag, 0); + } + + public void assertAllTracesClosed() { + for (Map.Entry<Long, Integer> count: mTraceCountsByTag.entrySet()) { + final String errorMsg = "Tag " + count.getKey() + " is not fully closed (count=" + + count.getValue() + ")"; + assertEquals(errorMsg, 0, (int) count.getValue()); + } + } + } +} diff --git a/tools/traceinjection/test/com/android/traceinjection/Trace.java b/tools/traceinjection/test/com/android/traceinjection/Trace.java new file mode 100644 index 000000000000..9e1c545673e8 --- /dev/null +++ b/tools/traceinjection/test/com/android/traceinjection/Trace.java @@ -0,0 +1,22 @@ +/* + * 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.android.traceinjection; + +public @interface Trace { + long tag(); + String label() default ""; +} diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp index 0423b7abd685..ff24d160b917 100644 --- a/tools/validatekeymaps/Android.bp +++ b/tools/validatekeymaps/Android.bp @@ -32,7 +32,7 @@ cc_binary_host { "libui-types", ], target: { - linux_glibc: { + host_linux: { static_libs: [ // libbinder is only available for linux "libbinder", diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp index 991b28071515..817effd24a2d 100644 --- a/tools/validatekeymaps/Main.cpp +++ b/tools/validatekeymaps/Main.cpp @@ -29,6 +29,17 @@ using namespace android; static const char* PROG_NAME = "validatekeymaps"; static bool gQuiet = false; +/** + * Return true if 'str' contains 'substr', ignoring case. + */ +static bool containsSubstringCaseInsensitive(std::string_view str, std::string_view substr) { + auto it = std::search(str.begin(), str.end(), substr.begin(), substr.end(), + [](char left, char right) { + return std::tolower(left) == std::tolower(right); + }); + return it != str.end(); +} + enum class FileType { UNKNOWN, KEY_LAYOUT, @@ -85,6 +96,36 @@ static FileType getFileType(const char* filename) { return FileType::UNKNOWN; } +/** + * Return true if the filename is allowed, false otherwise. + */ +static bool validateKeyLayoutFileName(const std::string& filename) { + static const std::string kMicrosoftReason = + "Microsoft's controllers are designed to work with Generic.kl. Please check with " + "Microsoft prior to adding these layouts. See b/194334400"; + static const std::vector<std::pair<std::string, std::string>> kBannedDevices{ + std::make_pair("Vendor_0a5c_Product_8502", + "This vendorId/productId combination conflicts with 'SnakeByte " + "iDroid:con', 'BT23BK keyboard', and other keyboards. Instead, consider " + "matching these specific devices by name. See b/36976285, b/191720859"), + std::make_pair("Vendor_045e_Product_0b05", kMicrosoftReason), + std::make_pair("Vendor_045e_Product_0b20", kMicrosoftReason), + std::make_pair("Vendor_045e_Product_0b21", kMicrosoftReason), + std::make_pair("Vendor_045e_Product_0b22", kMicrosoftReason), + }; + + for (const auto& [filenameSubstr, reason] : kBannedDevices) { + if (containsSubstringCaseInsensitive(filename, filenameSubstr)) { + error("You are trying to add a key layout %s, which matches %s. ", filename.c_str(), + filenameSubstr.c_str()); + error("This would cause some devices to function incorrectly. "); + error("%s. ", reason.c_str()); + return false; + } + } + return true; +} + static bool validateFile(const char* filename) { log("Validating file '%s'...\n", filename); @@ -95,6 +136,9 @@ static bool validateFile(const char* filename) { return false; case FileType::KEY_LAYOUT: { + if (!validateKeyLayoutFileName(filename)) { + return false; + } base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(filename); if (!ret.ok()) { error("Error %s parsing key layout file.\n\n", ret.error().message().c_str()); |