diff options
212 files changed, 4836 insertions, 1557 deletions
diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h index 521156a319..87bf1c4d43 100644 --- a/cmdline/cmdline_types.h +++ b/cmdline/cmdline_types.h @@ -290,26 +290,42 @@ struct CmdlineType<double> : CmdlineTypeParser<double> { static const char* Name() { return "double"; } }; +template <typename T> +static inline CmdlineParseResult<T> ParseNumeric(const std::string& str) { + static_assert(sizeof(T) < sizeof(long long int), // NOLINT [runtime/int] [4] + "Current support is restricted."); + + const char* begin = str.c_str(); + char* end; + + // Parse into a larger type (long long) because we can't use strtoul + // since it silently converts negative values into unsigned long and doesn't set errno. + errno = 0; + long long int result = strtoll(begin, &end, 10); // NOLINT [runtime/int] [4] + if (begin == end || *end != '\0' || errno == EINVAL) { + return CmdlineParseResult<T>::Failure("Failed to parse integer from " + str); + } else if ((errno == ERANGE) || // NOLINT [runtime/int] [4] + result < std::numeric_limits<T>::min() || result > std::numeric_limits<T>::max()) { + return CmdlineParseResult<T>::OutOfRange( + "Failed to parse integer from " + str + "; out of range"); + } + + return CmdlineParseResult<T>::Success(static_cast<T>(result)); +} + template <> struct CmdlineType<unsigned int> : CmdlineTypeParser<unsigned int> { Result Parse(const std::string& str) { - const char* begin = str.c_str(); - char* end; + return ParseNumeric<unsigned int>(str); + } - // Parse into a larger type (long long) because we can't use strtoul - // since it silently converts negative values into unsigned long and doesn't set errno. - errno = 0; - long long int result = strtoll(begin, &end, 10); // NOLINT [runtime/int] [4] - if (begin == end || *end != '\0' || errno == EINVAL) { - return Result::Failure("Failed to parse integer from " + str); - } else if ((errno == ERANGE) || // NOLINT [runtime/int] [4] - result < std::numeric_limits<int>::min() - || result > std::numeric_limits<unsigned int>::max() || result < 0) { - return Result::OutOfRange( - "Failed to parse integer from " + str + "; out of unsigned int range"); - } + static const char* Name() { return "unsigned integer"; } +}; - return Result::Success(static_cast<unsigned int>(result)); +template <> +struct CmdlineType<int> : CmdlineTypeParser<int> { + Result Parse(const std::string& str) { + return ParseNumeric<int>(str); } static const char* Name() { return "unsigned integer"; } diff --git a/compiler/Android.bp b/compiler/Android.bp index 59ca4c7abf..1e4cdf2bd5 100644 --- a/compiler/Android.bp +++ b/compiler/Android.bp @@ -193,6 +193,10 @@ art_cc_defaults { "liblzma", ], include_dirs: ["art/disassembler"], + header_libs: [ + "art_cmdlineparser_headers", // For compiler_options. + ], + export_include_dirs: ["."], } diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc index 538845de19..b6cedff28a 100644 --- a/compiler/driver/compiler_options.cc +++ b/compiler/driver/compiler_options.cc @@ -18,7 +18,13 @@ #include <fstream> +#include "android-base/stringprintf.h" + +#include "base/variant_map.h" +#include "cmdline_parser.h" +#include "compiler_options_map-inl.h" #include "runtime.h" +#include "simple_compiler_options_map.h" namespace art { @@ -71,115 +77,50 @@ bool CompilerOptions::EmitRunTimeChecksInDebugMode() const { (kIsTargetBuild || IsCoreImage() || Runtime::Current()->UseJitCompilation()); } -void CompilerOptions::ParseHugeMethodMax(const StringPiece& option, UsageFn Usage) { - ParseUintOption(option, "--huge-method-max", &huge_method_threshold_, Usage); -} - -void CompilerOptions::ParseLargeMethodMax(const StringPiece& option, UsageFn Usage) { - ParseUintOption(option, "--large-method-max", &large_method_threshold_, Usage); -} - -void CompilerOptions::ParseSmallMethodMax(const StringPiece& option, UsageFn Usage) { - ParseUintOption(option, "--small-method-max", &small_method_threshold_, Usage); -} - -void CompilerOptions::ParseTinyMethodMax(const StringPiece& option, UsageFn Usage) { - ParseUintOption(option, "--tiny-method-max", &tiny_method_threshold_, Usage); -} - -void CompilerOptions::ParseNumDexMethods(const StringPiece& option, UsageFn Usage) { - ParseUintOption(option, "--num-dex-methods", &num_dex_methods_threshold_, Usage); -} - -void CompilerOptions::ParseInlineMaxCodeUnits(const StringPiece& option, UsageFn Usage) { - ParseUintOption(option, "--inline-max-code-units", &inline_max_code_units_, Usage); -} - -void CompilerOptions::ParseDumpInitFailures(const StringPiece& option, - UsageFn Usage ATTRIBUTE_UNUSED) { - DCHECK(option.starts_with("--dump-init-failures=")); - std::string file_name = option.substr(strlen("--dump-init-failures=")).data(); - init_failure_output_.reset(new std::ofstream(file_name)); +bool CompilerOptions::ParseDumpInitFailures(const std::string& option, std::string* error_msg) { + init_failure_output_.reset(new std::ofstream(option)); if (init_failure_output_.get() == nullptr) { - LOG(ERROR) << "Failed to allocate ofstream"; + *error_msg = "Failed to construct std::ofstream"; + return false; } else if (init_failure_output_->fail()) { - LOG(ERROR) << "Failed to open " << file_name << " for writing the initialization " - << "failures."; + *error_msg = android::base::StringPrintf( + "Failed to open %s for writing the initialization failures.", option.c_str()); init_failure_output_.reset(); + return false; } + return true; } -void CompilerOptions::ParseRegisterAllocationStrategy(const StringPiece& option, - UsageFn Usage) { - DCHECK(option.starts_with("--register-allocation-strategy=")); - StringPiece choice = option.substr(strlen("--register-allocation-strategy=")).data(); - if (choice == "linear-scan") { +bool CompilerOptions::ParseRegisterAllocationStrategy(const std::string& option, + std::string* error_msg) { + if (option == "linear-scan") { register_allocation_strategy_ = RegisterAllocator::Strategy::kRegisterAllocatorLinearScan; - } else if (choice == "graph-color") { + } else if (option == "graph-color") { register_allocation_strategy_ = RegisterAllocator::Strategy::kRegisterAllocatorGraphColor; } else { - Usage("Unrecognized register allocation strategy. Try linear-scan, or graph-color."); + *error_msg = "Unrecognized register allocation strategy. Try linear-scan, or graph-color."; + return false; } + return true; } -bool CompilerOptions::ParseCompilerOption(const StringPiece& option, UsageFn Usage) { - if (option.starts_with("--compiler-filter=")) { - const char* compiler_filter_string = option.substr(strlen("--compiler-filter=")).data(); - if (!CompilerFilter::ParseCompilerFilter(compiler_filter_string, &compiler_filter_)) { - Usage("Unknown --compiler-filter value %s", compiler_filter_string); - } - } else if (option == "--compile-pic") { - compile_pic_ = true; - } else if (option.starts_with("--huge-method-max=")) { - ParseHugeMethodMax(option, Usage); - } else if (option.starts_with("--large-method-max=")) { - ParseLargeMethodMax(option, Usage); - } else if (option.starts_with("--small-method-max=")) { - ParseSmallMethodMax(option, Usage); - } else if (option.starts_with("--tiny-method-max=")) { - ParseTinyMethodMax(option, Usage); - } else if (option.starts_with("--num-dex-methods=")) { - ParseNumDexMethods(option, Usage); - } else if (option.starts_with("--inline-max-code-units=")) { - ParseInlineMaxCodeUnits(option, Usage); - } else if (option == "--generate-debug-info" || option == "-g") { - generate_debug_info_ = true; - } else if (option == "--no-generate-debug-info") { - generate_debug_info_ = false; - } else if (option == "--generate-mini-debug-info") { - generate_mini_debug_info_ = true; - } else if (option == "--no-generate-mini-debug-info") { - generate_mini_debug_info_ = false; - } else if (option == "--generate-build-id") { - generate_build_id_ = true; - } else if (option == "--no-generate-build-id") { - generate_build_id_ = false; - } else if (option == "--debuggable") { - debuggable_ = true; - } else if (option.starts_with("--top-k-profile-threshold=")) { - ParseDouble(option.data(), '=', 0.0, 100.0, &top_k_profile_threshold_, Usage); - } else if (option == "--abort-on-hard-verifier-error") { - abort_on_hard_verifier_failure_ = true; - } else if (option == "--no-abort-on-hard-verifier-error") { - abort_on_hard_verifier_failure_ = false; - } else if (option.starts_with("--dump-init-failures=")) { - ParseDumpInitFailures(option, Usage); - } else if (option.starts_with("--dump-cfg=")) { - dump_cfg_file_name_ = option.substr(strlen("--dump-cfg=")).data(); - } else if (option == "--dump-cfg-append") { - dump_cfg_append_ = true; - } else if (option.starts_with("--register-allocation-strategy=")) { - ParseRegisterAllocationStrategy(option, Usage); - } else if (option.starts_with("--verbose-methods=")) { - // TODO: rather than switch off compiler logging, make all VLOG(compiler) messages - // conditional on having verbose methods. - gLogVerbosity.compiler = false; - Split(option.substr(strlen("--verbose-methods=")).ToString(), ',', &verbose_methods_); - } else { - // Option not recognized. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wframe-larger-than=" + +bool CompilerOptions::ParseCompilerOptions(const std::vector<std::string>& options, + bool ignore_unrecognized, + std::string* error_msg) { + auto parser = CreateSimpleParser(ignore_unrecognized); + CmdlineResult parse_result = parser.Parse(options); + if (!parse_result.IsSuccess()) { + *error_msg = parse_result.GetMessage(); return false; } - return true; + + SimpleParseArgumentMap args = parser.ReleaseArgumentsMap(); + return ReadCompilerOptions(args, this, error_msg); } +#pragma GCC diagnostic pop + } // namespace art diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h index a9372c4844..311dbd569e 100644 --- a/compiler/driver/compiler_options.h +++ b/compiler/driver/compiler_options.h @@ -231,7 +231,9 @@ class CompilerOptions FINAL { return no_inline_from_; } - bool ParseCompilerOption(const StringPiece& option, UsageFn Usage); + bool ParseCompilerOptions(const std::vector<std::string>& options, + bool ignore_unrecognized, + std::string* error_msg); void SetNonPic() { compile_pic_ = false; @@ -258,7 +260,7 @@ class CompilerOptions FINAL { } private: - void ParseDumpInitFailures(const StringPiece& option, UsageFn Usage); + bool ParseDumpInitFailures(const std::string& option, std::string* error_msg); void ParseDumpCfgPasses(const StringPiece& option, UsageFn Usage); void ParseInlineMaxCodeUnits(const StringPiece& option, UsageFn Usage); void ParseNumDexMethods(const StringPiece& option, UsageFn Usage); @@ -266,7 +268,7 @@ class CompilerOptions FINAL { void ParseSmallMethodMax(const StringPiece& option, UsageFn Usage); void ParseLargeMethodMax(const StringPiece& option, UsageFn Usage); void ParseHugeMethodMax(const StringPiece& option, UsageFn Usage); - void ParseRegisterAllocationStrategy(const StringPiece& option, UsageFn Usage); + bool ParseRegisterAllocationStrategy(const std::string& option, std::string* error_msg); CompilerFilter::Filter compiler_filter_; size_t huge_method_threshold_; @@ -327,6 +329,9 @@ class CompilerOptions FINAL { friend class CommonCompilerTest; friend class verifier::VerifierDepsTest; + template <class Base> + friend bool ReadCompilerOptions(Base& map, CompilerOptions* options, std::string* error_msg); + DISALLOW_COPY_AND_ASSIGN(CompilerOptions); }; diff --git a/compiler/driver/compiler_options_map-inl.h b/compiler/driver/compiler_options_map-inl.h new file mode 100644 index 0000000000..9cb818a270 --- /dev/null +++ b/compiler/driver/compiler_options_map-inl.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DRIVER_COMPILER_OPTIONS_MAP_INL_H_ +#define ART_COMPILER_DRIVER_COMPILER_OPTIONS_MAP_INL_H_ + +#include "compiler_options_map.h" + +#include <memory> + +#include "android-base/logging.h" +#include "android-base/macros.h" +#include "android-base/stringprintf.h" + +#include "base/macros.h" +#include "cmdline_parser.h" +#include "compiler_options.h" + +namespace art { + +template <class Base> +inline bool ReadCompilerOptions(Base& map, CompilerOptions* options, std::string* error_msg) { + if (map.Exists(Base::CompilerFilter)) { + CompilerFilter::Filter compiler_filter; + if (!CompilerFilter::ParseCompilerFilter(map.Get(Base::CompilerFilter)->c_str(), + &compiler_filter)) { + *error_msg = android::base::StringPrintf("Unknown --compiler-filter value %s", + map.Get(Base::CompilerFilter)->c_str()); + return false; + } + options->SetCompilerFilter(compiler_filter); + } + if (map.Exists(Base::PIC)) { + options->compile_pic_ = true; + } + map.AssignIfExists(Base::HugeMethodMaxThreshold, &options->huge_method_threshold_); + map.AssignIfExists(Base::LargeMethodMaxThreshold, &options->large_method_threshold_); + map.AssignIfExists(Base::SmallMethodMaxThreshold, &options->small_method_threshold_); + map.AssignIfExists(Base::TinyMethodMaxThreshold, &options->tiny_method_threshold_); + map.AssignIfExists(Base::NumDexMethodsThreshold, &options->num_dex_methods_threshold_); + map.AssignIfExists(Base::InlineMaxCodeUnitsThreshold, &options->inline_max_code_units_); + map.AssignIfExists(Base::GenerateDebugInfo, &options->generate_debug_info_); + map.AssignIfExists(Base::GenerateMiniDebugInfo, &options->generate_mini_debug_info_); + map.AssignIfExists(Base::GenerateBuildID, &options->generate_build_id_); + if (map.Exists(Base::Debuggable)) { + options->debuggable_ = true; + } + map.AssignIfExists(Base::TopKProfileThreshold, &options->top_k_profile_threshold_); + map.AssignIfExists(Base::AbortOnHardVerifierFailure, &options->abort_on_hard_verifier_failure_); + if (map.Exists(Base::DumpInitFailures)) { + if (!options->ParseDumpInitFailures(*map.Get(Base::DumpInitFailures), error_msg)) { + return false; + } + } + map.AssignIfExists(Base::DumpCFG, &options->dump_cfg_file_name_); + if (map.Exists(Base::DumpCFGAppend)) { + options->dump_cfg_append_ = true; + } + if (map.Exists(Base::RegisterAllocationStrategy)) { + if (!options->ParseRegisterAllocationStrategy(*map.Get(Base::DumpInitFailures), error_msg)) { + return false; + } + } + map.AssignIfExists(Base::VerboseMethods, &options->verbose_methods_); + + return true; +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wframe-larger-than=" + +template <typename Map, typename Builder> +inline void AddCompilerOptionsArgumentParserOptions(Builder& b) { + b. + Define("--compiler-filter=_") + .template WithType<std::string>() + .IntoKey(Map::CompilerFilter) + + .Define("--compile-pic") + .IntoKey(Map::PIC) + + .Define("--huge-method-max=_") + .template WithType<unsigned int>() + .IntoKey(Map::HugeMethodMaxThreshold) + .Define("--large-method-max=_") + .template WithType<unsigned int>() + .IntoKey(Map::LargeMethodMaxThreshold) + .Define("--small-method-max=_") + .template WithType<unsigned int>() + .IntoKey(Map::SmallMethodMaxThreshold) + .Define("--tiny-method-max=_") + .template WithType<unsigned int>() + .IntoKey(Map::TinyMethodMaxThreshold) + .Define("--num-dex-methods=_") + .template WithType<unsigned int>() + .IntoKey(Map::NumDexMethodsThreshold) + .Define("--inline-max-code-units=_") + .template WithType<unsigned int>() + .IntoKey(Map::InlineMaxCodeUnitsThreshold) + + .Define({"--generate-debug-info", "-g", "--no-generate-debug-info"}) + .WithValues({true, true, false}) + .IntoKey(Map::GenerateDebugInfo) + .Define({"--generate-mini-debug-info", "--no-generate-mini-debug-info"}) + .WithValues({true, false}) + .IntoKey(Map::GenerateMiniDebugInfo) + + .Define({"--generate-build-id", "--no-generate-build-id"}) + .WithValues({true, false}) + .IntoKey(Map::GenerateBuildID) + + .Define("--debuggable") + .IntoKey(Map::Debuggable) + + .Define("--top-k-profile-threshold=_") + .template WithType<double>().WithRange(0.0, 100.0) + .IntoKey(Map::TopKProfileThreshold) + + .Define({"--abort-on-hard-verifier-error", "--no-abort-on-hard-verifier-error"}) + .WithValues({true, false}) + .IntoKey(Map::AbortOnHardVerifierFailure) + + .Define("--dump-init-failures=_") + .template WithType<std::string>() + .IntoKey(Map::DumpInitFailures) + + .Define("--dump-cfg=_") + .template WithType<std::string>() + .IntoKey(Map::DumpCFG) + .Define("--dump-cfg-append") + .IntoKey(Map::DumpCFGAppend) + + .Define("--register-allocation-strategy=_") + .template WithType<std::string>() + .IntoKey(Map::RegisterAllocationStrategy) + + .Define("--verbose-methods=_") + .template WithType<ParseStringList<','>>() + .IntoKey(Map::VerboseMethods); +} + +#pragma GCC diagnostic pop + +} // namespace art + +#endif // ART_COMPILER_DRIVER_COMPILER_OPTIONS_MAP_INL_H_ diff --git a/compiler/driver/compiler_options_map-storage.h b/compiler/driver/compiler_options_map-storage.h new file mode 100644 index 0000000000..756598de05 --- /dev/null +++ b/compiler/driver/compiler_options_map-storage.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DRIVER_COMPILER_OPTIONS_MAP_STORAGE_H_ +#define ART_COMPILER_DRIVER_COMPILER_OPTIONS_MAP_STORAGE_H_ + +// Assumes: +// * #include "compiler_options_map.h" +// * namespace art +// +// Usage: +// #define COMPILER_OPTIONS_MAP_TYPE TheTypeOfTheMap +// #define COMPILER_OPTIONS_MAP_KEY_TYPE TheTypeOfTheMapsKey +// #include "driver/compiler_options_map-storage.h + +#ifndef COMPILER_OPTIONS_MAP_TYPE +#error "Expected COMPILER_OPTIONS_MAP_TYPE" +#endif + +#ifndef COMPILER_OPTIONS_MAP_KEY_TYPE +#error "Expected COMPILER_OPTIONS_MAP_KEY_TYPE" +#endif + +#define COMPILER_OPTIONS_KEY(Type, Name, ...) \ + template <typename Base, template <typename TV> class KeyType> \ + const KeyType<Type> CompilerOptionsMap<Base, KeyType>::Name {__VA_ARGS__}; // NOLINT [readability/braces] [4] +#include <driver/compiler_options_map.def> + +template struct CompilerOptionsMap<COMPILER_OPTIONS_MAP_TYPE, COMPILER_OPTIONS_MAP_KEY_TYPE>; + +#undef COMPILER_OPTIONS_MAP_TYPE +#undef COMPILER_OPTIONS_MAP_KEY_TYPE + +#endif // ART_COMPILER_DRIVER_COMPILER_OPTIONS_MAP_STORAGE_H_ +#undef ART_COMPILER_DRIVER_COMPILER_OPTIONS_MAP_STORAGE_H_ // Guard is only for cpplint diff --git a/compiler/driver/compiler_options_map.def b/compiler/driver/compiler_options_map.def new file mode 100644 index 0000000000..570bc5aca7 --- /dev/null +++ b/compiler/driver/compiler_options_map.def @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef COMPILER_OPTIONS_KEY +#error "Please #define COMPILER_OPTIONS_KEY before #including this file" +#define COMPILER_OPTIONS_KEY(...) // Don't display errors in this file in IDEs. +#endif + +// This file defines the list of keys for CompilerOptionsMap. +// These can be used with CompilerOptionsMap.Get/Set/etc, once that template class has been +// instantiated. +// +// Column Descriptions: +// <<Type>> <<Key Name>> (<<Default Value>>) +// +// Default values are only used by Map::GetOrDefault(K<T>). +// If a default value is omitted here, T{} is used as the default value, which is +// almost-always the value of the type as if it was memset to all 0. +// +// Please keep the columns aligned if possible when adding new rows. +// + +// Parse-able keys from the command line. + +// TODO: Add type parser. +COMPILER_OPTIONS_KEY (std::string, CompilerFilter) +COMPILER_OPTIONS_KEY (Unit, PIC) +COMPILER_OPTIONS_KEY (unsigned int, HugeMethodMaxThreshold) +COMPILER_OPTIONS_KEY (unsigned int, LargeMethodMaxThreshold) +COMPILER_OPTIONS_KEY (unsigned int, SmallMethodMaxThreshold) +COMPILER_OPTIONS_KEY (unsigned int, TinyMethodMaxThreshold) +COMPILER_OPTIONS_KEY (unsigned int, NumDexMethodsThreshold) +COMPILER_OPTIONS_KEY (unsigned int, InlineMaxCodeUnitsThreshold) +COMPILER_OPTIONS_KEY (bool, GenerateDebugInfo) +COMPILER_OPTIONS_KEY (bool, GenerateMiniDebugInfo) +COMPILER_OPTIONS_KEY (bool, GenerateBuildID) +COMPILER_OPTIONS_KEY (Unit, Debuggable) +COMPILER_OPTIONS_KEY (double, TopKProfileThreshold) +COMPILER_OPTIONS_KEY (bool, AbortOnHardVerifierFailure) +COMPILER_OPTIONS_KEY (std::string, DumpInitFailures) +COMPILER_OPTIONS_KEY (std::string, DumpCFG) +COMPILER_OPTIONS_KEY (Unit, DumpCFGAppend) +// TODO: Add type parser. +COMPILER_OPTIONS_KEY (std::string, RegisterAllocationStrategy) +COMPILER_OPTIONS_KEY (ParseStringList<','>, VerboseMethods) + +#undef COMPILER_OPTIONS_KEY diff --git a/compiler/driver/compiler_options_map.h b/compiler/driver/compiler_options_map.h new file mode 100644 index 0000000000..b9bc8b6ea1 --- /dev/null +++ b/compiler/driver/compiler_options_map.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_COMPILER_DRIVER_COMPILER_OPTIONS_MAP_H_ +#define ART_COMPILER_DRIVER_COMPILER_OPTIONS_MAP_H_ + +#include <string> +#include <vector> + +#include "base/variant_map.h" +#include "cmdline_types.h" + +namespace art { + +// Defines a type-safe heterogeneous key->value map. This is to be used as the base for +// an extended map. +template <typename Base, template <typename TV> class KeyType> +struct CompilerOptionsMap : VariantMap<Base, KeyType> { + // Make the next many usages of Key slightly shorter to type. + template <typename TValue> + using Key = KeyType<TValue>; + + // List of key declarations, shorthand for 'static const Key<T> Name' +#define COMPILER_OPTIONS_KEY(Type, Name, ...) static const Key<Type> (Name); +#include "compiler_options_map.def" +}; + +#undef DECLARE_KEY + +} // namespace art + +#endif // ART_COMPILER_DRIVER_COMPILER_OPTIONS_MAP_H_ diff --git a/compiler/driver/simple_compiler_options_map.h b/compiler/driver/simple_compiler_options_map.h new file mode 100644 index 0000000000..3860da9f66 --- /dev/null +++ b/compiler/driver/simple_compiler_options_map.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This file declares a completion of the CompilerOptionsMap and should be included into a +// .cc file, only. + +#ifndef ART_COMPILER_DRIVER_SIMPLE_COMPILER_OPTIONS_MAP_H_ +#define ART_COMPILER_DRIVER_SIMPLE_COMPILER_OPTIONS_MAP_H_ + +#include <memory> + +#include "compiler_options_map-inl.h" +#include "base/variant_map.h" + +namespace art { + +template <typename TValue> +struct SimpleParseArgumentMapKey : VariantMapKey<TValue> { + SimpleParseArgumentMapKey() {} + explicit SimpleParseArgumentMapKey(TValue default_value) + : VariantMapKey<TValue>(std::move(default_value)) {} + // Don't ODR-use constexpr default values, which means that Struct::Fields + // that are declared 'static constexpr T Name = Value' don't need to have a matching definition. +}; + +struct SimpleParseArgumentMap : CompilerOptionsMap<SimpleParseArgumentMap, + SimpleParseArgumentMapKey> { + // This 'using' line is necessary to inherit the variadic constructor. + using CompilerOptionsMap<SimpleParseArgumentMap, SimpleParseArgumentMapKey>::CompilerOptionsMap; +}; + +#define COMPILER_OPTIONS_MAP_TYPE SimpleParseArgumentMap +#define COMPILER_OPTIONS_MAP_KEY_TYPE SimpleParseArgumentMapKey +#include "compiler_options_map-storage.h" + +using Parser = CmdlineParser<SimpleParseArgumentMap, SimpleParseArgumentMapKey>; + +static inline Parser CreateSimpleParser(bool ignore_unrecognized) { + std::unique_ptr<Parser::Builder> parser_builder = + std::unique_ptr<Parser::Builder>(new Parser::Builder()); + + AddCompilerOptionsArgumentParserOptions<SimpleParseArgumentMap>(*parser_builder); + + parser_builder->IgnoreUnrecognized(ignore_unrecognized); + + return parser_builder->Build(); +} + +} // namespace art + +#endif // ART_COMPILER_DRIVER_SIMPLE_COMPILER_OPTIONS_MAP_H_ diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index 511a44af04..5c89869e00 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -78,21 +78,16 @@ extern "C" void jit_types_loaded(void* handle, mirror::Class** types, size_t cou } } -// Callers of this method assume it has NO_RETURN. -NO_RETURN static void Usage(const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - std::string error; - android::base::StringAppendV(&error, fmt, ap); - LOG(FATAL) << error; - va_end(ap); - exit(EXIT_FAILURE); -} - JitCompiler::JitCompiler() { compiler_options_.reset(new CompilerOptions()); - for (const std::string& argument : Runtime::Current()->GetCompilerOptions()) { - compiler_options_->ParseCompilerOption(argument, Usage); + { + std::string error_msg; + if (!compiler_options_->ParseCompilerOptions(Runtime::Current()->GetCompilerOptions(), + true /* ignore_unrecognized */, + &error_msg)) { + LOG(FATAL) << error_msg; + UNREACHABLE(); + } } // JIT is never PIC, no matter what the runtime compiler options specify. compiler_options_->SetNonPic(); diff --git a/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc b/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc index edb3292ea7..0bae4d4b69 100644 --- a/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc +++ b/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc @@ -164,6 +164,21 @@ void ArmVIXLJNIMacroAssembler::RemoveFrame(size_t frame_size, // AAPCS calling convention. DCHECK_NE(core_spill_mask & (1 << MR), 0) << "core_spill_mask should contain Marking Register R" << MR; + + // The following condition is a compile-time one, so it does not have a run-time cost. + if (kIsDebugBuild) { + // The following condition is a run-time one; it is executed after the + // previous compile-time test, to avoid penalizing non-debug builds. + if (emit_run_time_checks_in_debug_mode_) { + // Emit a run-time check verifying that the Marking Register is up-to-date. + UseScratchRegisterScope temps(asm_.GetVIXLAssembler()); + vixl32::Register temp = temps.Acquire(); + // Ensure we are not clobbering a callee-save register that was restored before. + DCHECK_EQ(core_spill_mask & (1 << temp.GetCode()), 0) + << "core_spill_mask hould not contain scratch register R" << temp.GetCode(); + asm_.GenerateMarkingRegisterCheck(temp); + } + } } } diff --git a/compiler/utils/arm64/jni_macro_assembler_arm64.cc b/compiler/utils/arm64/jni_macro_assembler_arm64.cc index 43c0eff8fc..573bb6d4be 100644 --- a/compiler/utils/arm64/jni_macro_assembler_arm64.cc +++ b/compiler/utils/arm64/jni_macro_assembler_arm64.cc @@ -788,6 +788,21 @@ void Arm64JNIMacroAssembler::RemoveFrame(size_t frame_size, // AAPCS64 calling convention. DCHECK(core_reg_list.IncludesAliasOf(mr)) << "core_reg_list should contain Marking Register X" << mr.GetCode(); + + // The following condition is a compile-time one, so it does not have a run-time cost. + if (kIsDebugBuild) { + // The following condition is a run-time one; it is executed after the + // previous compile-time test, to avoid penalizing non-debug builds. + if (emit_run_time_checks_in_debug_mode_) { + // Emit a run-time check verifying that the Marking Register is up-to-date. + UseScratchRegisterScope temps(asm_.GetVIXLAssembler()); + Register temp = temps.AcquireW(); + // Ensure we are not clobbering a callee-save register that was restored before. + DCHECK(!core_reg_list.IncludesAliasOf(temp.X())) + << "core_reg_list should not contain scratch register X" << temp.GetCode(); + asm_.GenerateMarkingRegisterCheck(temp); + } + } } } diff --git a/compiler/utils/test_dex_file_builder.h b/compiler/utils/test_dex_file_builder.h index 9ba3903033..e6501e0b83 100644 --- a/compiler/utils/test_dex_file_builder.h +++ b/compiler/utils/test_dex_file_builder.h @@ -26,7 +26,8 @@ #include "base/bit_utils.h" #include "base/logging.h" -#include "dex_file.h" +#include "dex_file_loader.h" +#include "native_dex_file.h" namespace art { @@ -88,8 +89,8 @@ class TestDexFileBuilder { } header_data; std::memset(header_data.data, 0, sizeof(header_data.data)); DexFile::Header* header = reinterpret_cast<DexFile::Header*>(&header_data.data); - std::copy_n(DexFile::kDexMagic, 4u, header->magic_); - std::copy_n(DexFile::kDexMagicVersions[0], 4u, header->magic_ + 4u); + std::copy_n(NativeDexFile::kDexMagic, 4u, header->magic_); + std::copy_n(NativeDexFile::kDexMagicVersions[0], 4u, header->magic_ + 4u); header->header_size_ = sizeof(DexFile::Header); header->endian_tag_ = DexFile::kDexEndianConstant; header->link_size_ = 0u; // Unused. @@ -231,7 +232,7 @@ class TestDexFileBuilder { static constexpr bool kVerify = false; static constexpr bool kVerifyChecksum = false; std::string error_msg; - std::unique_ptr<const DexFile> dex_file(DexFile::Open( + std::unique_ptr<const DexFile> dex_file(DexFileLoader::Open( &dex_file_data_[0], dex_file_data_.size(), dex_location, diff --git a/dex2oat/Android.bp b/dex2oat/Android.bp index c9125df8f4..a93b0e7f0c 100644 --- a/dex2oat/Android.bp +++ b/dex2oat/Android.bp @@ -91,7 +91,10 @@ cc_defaults { name: "dex2oat-defaults", host_supported: true, defaults: ["art_defaults"], - srcs: ["dex2oat.cc"], + srcs: [ + "dex2oat_options.cc", + "dex2oat.cc", + ], target: { android: { diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 7b4653107f..528cf3a0a7 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -20,11 +20,13 @@ #include <sys/stat.h> #include "base/memory_tool.h" +#include <forward_list> #include <fstream> #include <iostream> #include <limits> #include <sstream> #include <string> +#include <type_traits> #include <unordered_set> #include <vector> @@ -50,16 +52,19 @@ #include "base/unix_file/fd_file.h" #include "class_linker.h" #include "class_loader_context.h" +#include "cmdline_parser.h" #include "compiler.h" #include "compiler_callbacks.h" #include "debug/elf_debug_writer.h" #include "debug/method_debug_info.h" #include "dex/quick_compiler_callbacks.h" #include "dex/verification_results.h" +#include "dex2oat_options.h" #include "dex2oat_return_codes.h" #include "dex_file-inl.h" #include "driver/compiler_driver.h" #include "driver/compiler_options.h" +#include "driver/compiler_options_map-inl.h" #include "elf_file.h" #include "gc/space/image_space.h" #include "gc/space/space-inl.h" @@ -235,6 +240,13 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(" --oat-fd=<number>: specifies the oat output destination via a file descriptor."); UsageError(" Example: --oat-fd=6"); UsageError(""); + UsageError(" --input-vdex-fd=<number>: specifies the vdex input source via a file descriptor."); + UsageError(" Example: --input-vdex-fd=6"); + UsageError(""); + UsageError(" --output-vdex-fd=<number>: specifies the vdex output destination via a file"); + UsageError(" descriptor."); + UsageError(" Example: --output-vdex-fd=6"); + UsageError(""); UsageError(" --oat-location=<oat-name>: specifies a symbolic name for the file corresponding"); UsageError(" to the file descriptor specified by --oat-fd."); UsageError(" Example: --oat-location=/data/dalvik-cache/system@app@Calculator.apk.oat"); @@ -659,76 +671,27 @@ class Dex2Oat FINAL { std::string error_msg; }; - void ParseZipFd(const StringPiece& option) { - ParseUintOption(option, "--zip-fd", &zip_fd_, Usage); - } - - void ParseInputVdexFd(const StringPiece& option) { - // Note that the input vdex fd might be -1. - ParseIntOption(option, "--input-vdex-fd", &input_vdex_fd_, Usage); - } - - void ParseOutputVdexFd(const StringPiece& option) { - ParseUintOption(option, "--output-vdex-fd", &output_vdex_fd_, Usage); - } - - void ParseOatFd(const StringPiece& option) { - ParseUintOption(option, "--oat-fd", &oat_fd_, Usage); - } - - void ParseFdForCollection(const StringPiece& option, - const char* arg_name, - std::vector<uint32_t>* fds) { - uint32_t fd; - ParseUintOption(option, arg_name, &fd, Usage); - fds->push_back(fd); - } - - void ParseJ(const StringPiece& option) { - ParseUintOption(option, "-j", &thread_count_, Usage, /* is_long_option */ false); - } - - void ParseBase(const StringPiece& option) { - DCHECK(option.starts_with("--base=")); - const char* image_base_str = option.substr(strlen("--base=")).data(); + void ParseBase(const std::string& option) { char* end; - image_base_ = strtoul(image_base_str, &end, 16); - if (end == image_base_str || *end != '\0') { + image_base_ = strtoul(option.c_str(), &end, 16); + if (end == option.c_str() || *end != '\0') { Usage("Failed to parse hexadecimal value for option %s", option.data()); } } - void ParseInstructionSet(const StringPiece& option) { - DCHECK(option.starts_with("--instruction-set=")); - StringPiece instruction_set_str = option.substr(strlen("--instruction-set=")).data(); - // StringPiece is not necessarily zero-terminated, so need to make a copy and ensure it. - std::unique_ptr<char[]> buf(new char[instruction_set_str.length() + 1]); - strncpy(buf.get(), instruction_set_str.data(), instruction_set_str.length()); - buf.get()[instruction_set_str.length()] = 0; - instruction_set_ = GetInstructionSetFromString(buf.get()); - // arm actually means thumb2. - if (instruction_set_ == InstructionSet::kArm) { - instruction_set_ = InstructionSet::kThumb2; - } - } - bool VerifyProfileData() { return profile_compilation_info_->VerifyProfileData(dex_files_); } - void ParseInstructionSetVariant(const StringPiece& option, ParserOptions* parser_options) { - DCHECK(option.starts_with("--instruction-set-variant=")); - StringPiece str = option.substr(strlen("--instruction-set-variant=")).data(); + void ParseInstructionSetVariant(const std::string& option, ParserOptions* parser_options) { instruction_set_features_ = InstructionSetFeatures::FromVariant( - instruction_set_, str.as_string(), &parser_options->error_msg); + instruction_set_, option, &parser_options->error_msg); if (instruction_set_features_.get() == nullptr) { Usage("%s", parser_options->error_msg.c_str()); } } - void ParseInstructionSetFeatures(const StringPiece& option, ParserOptions* parser_options) { - DCHECK(option.starts_with("--instruction-set-features=")); - StringPiece str = option.substr(strlen("--instruction-set-features=")).data(); + void ParseInstructionSetFeatures(const std::string& option, ParserOptions* parser_options) { if (instruction_set_features_ == nullptr) { instruction_set_features_ = InstructionSetFeatures::FromVariant( instruction_set_, "default", &parser_options->error_msg); @@ -738,38 +701,9 @@ class Dex2Oat FINAL { } } instruction_set_features_ = - instruction_set_features_->AddFeaturesFromString(str.as_string(), - &parser_options->error_msg); + instruction_set_features_->AddFeaturesFromString(option, &parser_options->error_msg); if (instruction_set_features_ == nullptr) { - Usage("Error parsing '%s': %s", option.data(), parser_options->error_msg.c_str()); - } - } - - void ParseCompilerBackend(const StringPiece& option, ParserOptions* parser_options) { - DCHECK(option.starts_with("--compiler-backend=")); - parser_options->requested_specific_compiler = true; - StringPiece backend_str = option.substr(strlen("--compiler-backend=")).data(); - if (backend_str == "Quick") { - compiler_kind_ = Compiler::kQuick; - } else if (backend_str == "Optimizing") { - compiler_kind_ = Compiler::kOptimizing; - } else { - Usage("Unknown compiler backend: %s", backend_str.data()); - } - } - - void ParseImageFormat(const StringPiece& option) { - const StringPiece substr("--image-format="); - DCHECK(option.starts_with(substr)); - const StringPiece format_str = option.substr(substr.length()); - if (format_str == "lz4") { - image_storage_mode_ = ImageHeader::kStorageModeLZ4; - } else if (format_str == "lz4hc") { - image_storage_mode_ = ImageHeader::kStorageModeLZ4HC; - } else if (format_str == "uncompressed") { - image_storage_mode_ = ImageHeader::kStorageModeUncompressed; - } else { - Usage("Unknown image format: %s", format_str.data()); + Usage("Error parsing '%s': %s", option.c_str(), parser_options->error_msg.c_str()); } } @@ -1092,23 +1026,20 @@ class Dex2Oat FINAL { base_symbol_oat = base_symbol_oat.substr(0, last_symbol_oat_slash + 1); } - const size_t num_expanded_files = 2 + (base_symbol_oat.empty() ? 0 : 1); - char_backing_storage_.reserve((dex_locations_.size() - 1) * num_expanded_files); - // Now create the other names. Use a counted loop to skip the first one. for (size_t i = 1; i < dex_locations_.size(); ++i) { // TODO: Make everything properly std::string. std::string image_name = CreateMultiImageName(dex_locations_[i], prefix, infix, ".art"); - char_backing_storage_.push_back(base_img + image_name); - image_filenames_.push_back((char_backing_storage_.end() - 1)->c_str()); + char_backing_storage_.push_front(base_img + image_name); + image_filenames_.push_back(char_backing_storage_.front().c_str()); std::string oat_name = CreateMultiImageName(dex_locations_[i], prefix, infix, ".oat"); - char_backing_storage_.push_back(base_oat + oat_name); - oat_filenames_.push_back((char_backing_storage_.end() - 1)->c_str()); + char_backing_storage_.push_front(base_oat + oat_name); + oat_filenames_.push_back(char_backing_storage_.front().c_str()); if (!base_symbol_oat.empty()) { - char_backing_storage_.push_back(base_symbol_oat + oat_name); - oat_unstripped_.push_back((char_backing_storage_.end() - 1)->c_str()); + char_backing_storage_.push_front(base_symbol_oat + oat_name); + oat_unstripped_.push_back(char_backing_storage_.front().c_str()); } } } @@ -1173,6 +1104,43 @@ class Dex2Oat FINAL { kUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue); } + // This simple forward is here so the string specializations below don't look out of place. + template <typename T, typename U> + void AssignIfExists(Dex2oatArgumentMap& map, + const Dex2oatArgumentMap::Key<T>& key, + U* out) { + map.AssignIfExists(key, out); + } + + // Specializations to handle const char* vs std::string. + void AssignIfExists(Dex2oatArgumentMap& map, + const Dex2oatArgumentMap::Key<std::string>& key, + const char** out) { + if (map.Exists(key)) { + char_backing_storage_.push_front(std::move(*map.Get(key))); + *out = char_backing_storage_.front().c_str(); + } + } + void AssignIfExists(Dex2oatArgumentMap& map, + const Dex2oatArgumentMap::Key<std::vector<std::string>>& key, + std::vector<const char*>* out) { + if (map.Exists(key)) { + for (auto& val : *map.Get(key)) { + char_backing_storage_.push_front(std::move(val)); + out->push_back(char_backing_storage_.front().c_str()); + } + } + } + + template <typename T> + void AssignTrueIfExists(Dex2oatArgumentMap& map, + const Dex2oatArgumentMap::Key<T>& key, + bool* out) { + if (map.Exists(key)) { + *out = true; + } + } + // Parse the arguments from the command line. In case of an unrecognized option or impossible // values/combinations, a usage error will be displayed and exit() is called. Thus, if the method // returns, arguments have been successfully parsed. @@ -1182,159 +1150,104 @@ class Dex2Oat FINAL { InitLogging(argv, Runtime::Abort); - // Skip over argv[0]. - argv++; - argc--; + compiler_options_.reset(new CompilerOptions()); - if (argc == 0) { - Usage("No arguments specified"); + using M = Dex2oatArgumentMap; + std::string error_msg; + std::unique_ptr<M> args_uptr = M::Parse(argc, const_cast<const char**>(argv), &error_msg); + if (args_uptr == nullptr) { + Usage("Failed to parse command line: %s", error_msg.c_str()); + UNREACHABLE(); } + M& args = *args_uptr; + std::unique_ptr<ParserOptions> parser_options(new ParserOptions()); - compiler_options_.reset(new CompilerOptions()); - for (int i = 0; i < argc; i++) { - const StringPiece option(argv[i]); - const bool log_options = false; - if (log_options) { - LOG(INFO) << "dex2oat: option[" << i << "]=" << argv[i]; - } - if (option.starts_with("--dex-file=")) { - dex_filenames_.push_back(option.substr(strlen("--dex-file=")).data()); - } else if (option.starts_with("--dex-location=")) { - dex_locations_.push_back(option.substr(strlen("--dex-location=")).data()); - } else if (option.starts_with("--zip-fd=")) { - ParseZipFd(option); - } else if (option.starts_with("--zip-location=")) { - zip_location_ = option.substr(strlen("--zip-location=")).data(); - } else if (option.starts_with("--input-vdex-fd=")) { - ParseInputVdexFd(option); - } else if (option.starts_with("--input-vdex=")) { - input_vdex_ = option.substr(strlen("--input-vdex=")).data(); - } else if (option.starts_with("--output-vdex=")) { - output_vdex_ = option.substr(strlen("--output-vdex=")).data(); - } else if (option.starts_with("--output-vdex-fd=")) { - ParseOutputVdexFd(option); - } else if (option.starts_with("--oat-file=")) { - oat_filenames_.push_back(option.substr(strlen("--oat-file=")).data()); - } else if (option.starts_with("--oat-symbols=")) { - parser_options->oat_symbols.push_back(option.substr(strlen("--oat-symbols=")).data()); - } else if (option.starts_with("--oat-fd=")) { - ParseOatFd(option); - } else if (option.starts_with("--oat-location=")) { - oat_location_ = option.substr(strlen("--oat-location=")).data(); - } else if (option == "--watch-dog") { - parser_options->watch_dog_enabled = true; - } else if (option == "--no-watch-dog") { - parser_options->watch_dog_enabled = false; - } else if (option.starts_with("--watchdog-timeout=")) { - ParseIntOption(option, - "--watchdog-timeout", - &parser_options->watch_dog_timeout_in_ms, - Usage); - } else if (option.starts_with("-j")) { - ParseJ(option); - } else if (option.starts_with("--image=")) { - image_filenames_.push_back(option.substr(strlen("--image=")).data()); - } else if (option.starts_with("--image-classes=")) { - image_classes_filename_ = option.substr(strlen("--image-classes=")).data(); - } else if (option.starts_with("--image-classes-zip=")) { - image_classes_zip_filename_ = option.substr(strlen("--image-classes-zip=")).data(); - } else if (option.starts_with("--image-format=")) { - ParseImageFormat(option); - } else if (option.starts_with("--compiled-classes=")) { - compiled_classes_filename_ = option.substr(strlen("--compiled-classes=")).data(); - } else if (option.starts_with("--compiled-classes-zip=")) { - compiled_classes_zip_filename_ = option.substr(strlen("--compiled-classes-zip=")).data(); - } else if (option.starts_with("--compiled-methods=")) { - compiled_methods_filename_ = option.substr(strlen("--compiled-methods=")).data(); - } else if (option.starts_with("--compiled-methods-zip=")) { - compiled_methods_zip_filename_ = option.substr(strlen("--compiled-methods-zip=")).data(); - } else if (option.starts_with("--run-passes=")) { - passes_to_run_filename_ = option.substr(strlen("--run-passes=")).data(); - } else if (option.starts_with("--base=")) { - ParseBase(option); - } else if (option.starts_with("--boot-image=")) { - parser_options->boot_image_filename = option.substr(strlen("--boot-image=")).data(); - } else if (option.starts_with("--android-root=")) { - android_root_ = option.substr(strlen("--android-root=")).data(); - } else if (option.starts_with("--instruction-set=")) { - ParseInstructionSet(option); - } else if (option.starts_with("--instruction-set-variant=")) { - ParseInstructionSetVariant(option, parser_options.get()); - } else if (option.starts_with("--instruction-set-features=")) { - ParseInstructionSetFeatures(option, parser_options.get()); - } else if (option.starts_with("--compiler-backend=")) { - ParseCompilerBackend(option, parser_options.get()); - } else if (option.starts_with("--profile-file=")) { - profile_file_ = option.substr(strlen("--profile-file=")).ToString(); - } else if (option.starts_with("--profile-file-fd=")) { - ParseUintOption(option, "--profile-file-fd", &profile_file_fd_, Usage); - } else if (option == "--host") { - is_host_ = true; - } else if (option == "--runtime-arg") { - if (++i >= argc) { - Usage("Missing required argument for --runtime-arg"); - } - if (log_options) { - LOG(INFO) << "dex2oat: option[" << i << "]=" << argv[i]; - } - runtime_args_.push_back(argv[i]); - } else if (option == "--dump-timing") { - dump_timing_ = true; - } else if (option == "--dump-passes") { - dump_passes_ = true; - } else if (option == "--dump-stats") { - dump_stats_ = true; - } else if (option == "--avoid-storing-invocation") { - avoid_storing_invocation_ = true; - } else if (option.starts_with("--swap-file=")) { - swap_file_name_ = option.substr(strlen("--swap-file=")).data(); - } else if (option.starts_with("--swap-fd=")) { - ParseUintOption(option, "--swap-fd", &swap_fd_, Usage); - } else if (option.starts_with("--swap-dex-size-threshold=")) { - ParseUintOption(option, - "--swap-dex-size-threshold", - &min_dex_file_cumulative_size_for_swap_, - Usage); - } else if (option.starts_with("--swap-dex-count-threshold=")) { - ParseUintOption(option, - "--swap-dex-count-threshold", - &min_dex_files_for_swap_, - Usage); - } else if (option.starts_with("--very-large-app-threshold=")) { - ParseUintOption(option, - "--very-large-app-threshold", - &very_large_threshold_, - Usage); - } else if (option.starts_with("--app-image-file=")) { - app_image_file_name_ = option.substr(strlen("--app-image-file=")).data(); - } else if (option.starts_with("--app-image-fd=")) { - ParseUintOption(option, "--app-image-fd", &app_image_fd_, Usage); - } else if (option == "--multi-image") { - multi_image_ = true; - } else if (option.starts_with("--no-inline-from=")) { - no_inline_from_string_ = option.substr(strlen("--no-inline-from=")).data(); - } else if (option == "--force-determinism") { - if (!SupportsDeterministicCompilation()) { - Usage("Option --force-determinism requires read barriers or a CMS/MS garbage collector"); - } - force_determinism_ = true; - } else if (option.starts_with("--classpath-dir=")) { - classpath_dir_ = option.substr(strlen("--classpath-dir=")).data(); - } else if (option.starts_with("--class-loader-context=")) { - class_loader_context_ = ClassLoaderContext::Create( - option.substr(strlen("--class-loader-context=")).data()); - if (class_loader_context_ == nullptr) { - Usage("Option --class-loader-context has an incorrect format: %s", option.data()); - } - } else if (option.starts_with("--dirty-image-objects=")) { - dirty_image_objects_filename_ = option.substr(strlen("--dirty-image-objects=")).data(); - } else if (!compiler_options_->ParseCompilerOption(option, Usage)) { - Usage("Unknown argument %s", option.data()); + AssignIfExists(args, M::DexFiles, &dex_filenames_); + AssignIfExists(args, M::DexLocations, &dex_locations_); + AssignIfExists(args, M::OatFiles, &oat_filenames_); + AssignIfExists(args, M::OatSymbols, &parser_options->oat_symbols); + AssignIfExists(args, M::ImageFilenames, &image_filenames_); + AssignIfExists(args, M::ZipFd, &zip_fd_); + AssignIfExists(args, M::ZipLocation, &zip_location_); + AssignIfExists(args, M::InputVdexFd, &input_vdex_fd_); + AssignIfExists(args, M::OutputVdexFd, &output_vdex_fd_); + AssignIfExists(args, M::InputVdex, &input_vdex_); + AssignIfExists(args, M::OutputVdex, &output_vdex_); + AssignIfExists(args, M::OatFd, &oat_fd_); + AssignIfExists(args, M::OatLocation, &oat_location_); + AssignIfExists(args, M::Watchdog, &parser_options->watch_dog_enabled); + AssignIfExists(args, M::WatchdogTimeout, &parser_options->watch_dog_timeout_in_ms); + AssignIfExists(args, M::Threads, &thread_count_); + AssignIfExists(args, M::ImageClasses, &image_classes_filename_); + AssignIfExists(args, M::ImageClassesZip, &image_classes_zip_filename_); + AssignIfExists(args, M::CompiledClasses, &compiled_classes_filename_); + AssignIfExists(args, M::CompiledClassesZip, &compiled_classes_zip_filename_); + AssignIfExists(args, M::CompiledMethods, &compiled_methods_filename_); + AssignIfExists(args, M::CompiledMethodsZip, &compiled_methods_zip_filename_); + AssignIfExists(args, M::Passes, &passes_to_run_filename_); + AssignIfExists(args, M::BootImage, &parser_options->boot_image_filename); + AssignIfExists(args, M::AndroidRoot, &android_root_); + AssignIfExists(args, M::Profile, &profile_file_); + AssignIfExists(args, M::ProfileFd, &profile_file_fd_); + AssignIfExists(args, M::RuntimeOptions, &runtime_args_); + AssignIfExists(args, M::SwapFile, &swap_file_name_); + AssignIfExists(args, M::SwapFileFd, &swap_fd_); + AssignIfExists(args, M::SwapDexSizeThreshold, &min_dex_file_cumulative_size_for_swap_); + AssignIfExists(args, M::SwapDexCountThreshold, &min_dex_files_for_swap_); + AssignIfExists(args, M::VeryLargeAppThreshold, &very_large_threshold_); + AssignIfExists(args, M::AppImageFile, &app_image_file_name_); + AssignIfExists(args, M::AppImageFileFd, &app_image_fd_); + AssignIfExists(args, M::NoInlineFrom, &no_inline_from_string_); + AssignIfExists(args, M::ClasspathDir, &classpath_dir_); + AssignIfExists(args, M::DirtyImageObjects, &dirty_image_objects_filename_); + AssignIfExists(args, M::ImageFormat, &image_storage_mode_); + + AssignIfExists(args, M::Backend, &compiler_kind_); + parser_options->requested_specific_compiler = args.Exists(M::Backend); + + AssignIfExists(args, M::TargetInstructionSet, &instruction_set_); + // arm actually means thumb2. + if (instruction_set_ == InstructionSet::kArm) { + instruction_set_ = InstructionSet::kThumb2; + } + + AssignTrueIfExists(args, M::Host, &is_host_); + AssignTrueIfExists(args, M::DumpTiming, &dump_timing_); + AssignTrueIfExists(args, M::DumpPasses, &dump_passes_); + AssignTrueIfExists(args, M::DumpStats, &dump_stats_); + AssignTrueIfExists(args, M::AvoidStoringInvocation, &avoid_storing_invocation_); + AssignTrueIfExists(args, M::MultiImage, &multi_image_); + + if (args.Exists(M::ForceDeterminism)) { + if (!SupportsDeterministicCompilation()) { + Usage("Option --force-determinism requires read barriers or a CMS/MS garbage collector"); + } + force_determinism_ = true; + } + + if (args.Exists(M::Base)) { + ParseBase(*args.Get(M::Base)); + } + if (args.Exists(M::TargetInstructionSetVariant)) { + ParseInstructionSetVariant(*args.Get(M::TargetInstructionSetVariant), parser_options.get()); + } + if (args.Exists(M::TargetInstructionSetFeatures)) { + ParseInstructionSetFeatures(*args.Get(M::TargetInstructionSetFeatures), parser_options.get()); + } + if (args.Exists(M::ClassLoaderContext)) { + class_loader_context_ = ClassLoaderContext::Create(*args.Get(M::ClassLoaderContext)); + if (class_loader_context_ == nullptr) { + Usage("Option --class-loader-context has an incorrect format: %s", + args.Get(M::ClassLoaderContext)->c_str()); } } + if (!ReadCompilerOptions(args, compiler_options_.get(), &error_msg)) { + Usage(error_msg.c_str()); + } + ProcessOptions(parser_options.get()); // Insert some compiler things. @@ -2931,7 +2844,7 @@ class Dex2Oat FINAL { std::unordered_map<const DexFile*, size_t> dex_file_oat_index_map_; // Backing storage. - std::vector<std::string> char_backing_storage_; + std::forward_list<std::string> char_backing_storage_; // See CompilerOptions.force_determinism_. bool force_determinism_; diff --git a/dex2oat/dex2oat_image_test.cc b/dex2oat/dex2oat_image_test.cc index 1f644c15dc..ae7ebe2da1 100644 --- a/dex2oat/dex2oat_image_test.cc +++ b/dex2oat/dex2oat_image_test.cc @@ -28,6 +28,7 @@ #include "base/macros.h" #include "base/unix_file/fd_file.h" #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "jit/profile_compilation_info.h" #include "method_reference.h" #include "runtime.h" @@ -62,7 +63,11 @@ class Dex2oatImageTest : public CommonRuntimeTest { for (const std::string& dex : GetLibCoreDexFileNames()) { std::vector<std::unique_ptr<const DexFile>> dex_files; std::string error_msg; - CHECK(DexFile::Open(dex.c_str(), dex, /*verify_checksum*/ false, &error_msg, &dex_files)) + CHECK(DexFileLoader::Open(dex.c_str(), + dex, + /*verify_checksum*/ false, + &error_msg, + &dex_files)) << error_msg; for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { for (size_t i = 0; i < dex_file->NumMethodIds(); ++i) { @@ -328,8 +333,8 @@ TEST_F(Dex2oatImageTest, TestModesAndFilters) { profile_file.Close(); std::cout << "Profile sizes " << profile_sizes << std::endl; // Since there is some difference between profile vs image + methods due to layout, check that - // the range is within expected margins (+-5%). - const double kRatio = 0.95; + // the range is within expected margins (+-10%). + const double kRatio = 0.90; EXPECT_LE(profile_sizes.art_size * kRatio, compiled_methods_sizes.art_size); // TODO(mathieuc): Find a reliable way to check compiled code. b/63746626 // EXPECT_LE(profile_sizes.oat_size * kRatio, compiled_methods_sizes.oat_size); diff --git a/dex2oat/dex2oat_options.cc b/dex2oat/dex2oat_options.cc new file mode 100644 index 0000000000..43e6c4d02f --- /dev/null +++ b/dex2oat/dex2oat_options.cc @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dex2oat_options.h" + +#include <memory> + +#include "cmdline_parser.h" +#include "driver/compiler_options_map-inl.h" + +namespace art { + +template<> +struct CmdlineType<InstructionSet> : CmdlineTypeParser<InstructionSet> { + Result Parse(const std::string& option) { + InstructionSet set = GetInstructionSetFromString(option.c_str()); + if (set == kNone) { + return Result::Failure(std::string("Not a valid instruction set: '") + option + "'"); + } + return Result::Success(set); + } + + static const char* Name() { return "InstructionSet"; } +}; + +#define COMPILER_OPTIONS_MAP_TYPE Dex2oatArgumentMap +#define COMPILER_OPTIONS_MAP_KEY_TYPE Dex2oatArgumentMapKey +#include "driver/compiler_options_map-storage.h" + +// Specify storage for the Dex2oatOptions keys. + +#define DEX2OAT_OPTIONS_KEY(Type, Name, ...) \ + const Dex2oatArgumentMap::Key<Type> Dex2oatArgumentMap::Name {__VA_ARGS__}; // NOLINT [readability/braces] [4] +#include "dex2oat_options.def" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wframe-larger-than=" + +using M = Dex2oatArgumentMap; +using Parser = CmdlineParser<Dex2oatArgumentMap, Dex2oatArgumentMap::Key>; +using Builder = Parser::Builder; + +static void AddInputMappings(Builder& builder) { + builder. + Define("--dex-file=_") + .WithType<std::vector<std::string>>().AppendValues() + .IntoKey(M::DexFiles) + .Define("--dex-location=_") + .WithType<std::vector<std::string>>().AppendValues() + .IntoKey(M::DexLocations) + .Define("--zip-fd=_") + .WithType<int>() + .IntoKey(M::ZipFd) + .Define("--zip-location=_") + .WithType<std::string>() + .IntoKey(M::ZipLocation) + .Define("--boot-image=_") + .WithType<std::string>() + .IntoKey(M::BootImage); +} + +static void AddGeneratedArtifactMappings(Builder& builder) { + builder. + Define("--input-vdex-fd=_") + .WithType<int>() + .IntoKey(M::InputVdexFd) + .Define("--input-vdex=_") + .WithType<std::string>() + .IntoKey(M::InputVdex) + .Define("--output-vdex-fd=_") + .WithType<int>() + .IntoKey(M::OutputVdexFd) + .Define("--output-vdex=_") + .WithType<std::string>() + .IntoKey(M::OutputVdex) + .Define("--oat-file=_") + .WithType<std::vector<std::string>>().AppendValues() + .IntoKey(M::OatFiles) + .Define("--oat-symbols=_") + .WithType<std::vector<std::string>>().AppendValues() + .IntoKey(M::OatSymbols) + .Define("--oat-fd=_") + .WithType<int>() + .IntoKey(M::OatFd) + .Define("--oat-location=_") + .WithType<std::string>() + .IntoKey(M::OatLocation); +} + +static void AddImageMappings(Builder& builder) { + builder. + Define("--image=_") + .WithType<std::vector<std::string>>().AppendValues() + .IntoKey(M::ImageFilenames) + .Define("--image-classes=_") + .WithType<std::string>() + .IntoKey(M::ImageClasses) + .Define("--image-classes-zip=_") + .WithType<std::string>() + .IntoKey(M::ImageClassesZip) + .Define("--base=_") + .WithType<std::string>() + .IntoKey(M::Base) + .Define("--app-image-file=_") + .WithType<std::string>() + .IntoKey(M::AppImageFile) + .Define("--app-image-fd=_") + .WithType<int>() + .IntoKey(M::AppImageFileFd) + .Define("--multi-image") + .IntoKey(M::MultiImage) + .Define("--dirty-image-objects=_") + .WithType<std::string>() + .IntoKey(M::DirtyImageObjects) + .Define("--image-format=_") + .WithType<ImageHeader::StorageMode>() + .WithValueMap({{"lz4", ImageHeader::kStorageModeLZ4}, + {"lz4hc", ImageHeader::kStorageModeLZ4HC}, + {"uncompressed", ImageHeader::kStorageModeUncompressed}}) + .IntoKey(M::ImageFormat); +} + +static void AddSwapMappings(Builder& builder) { + builder. + Define("--swap-file=_") + .WithType<std::string>() + .IntoKey(M::SwapFile) + .Define("--swap-fd=_") + .WithType<int>() + .IntoKey(M::SwapFileFd) + .Define("--swap-dex-size-threshold=_") + .WithType<unsigned int>() + .IntoKey(M::SwapDexSizeThreshold) + .Define("--swap-dex-count-threshold=_") + .WithType<unsigned int>() + .IntoKey(M::SwapDexCountThreshold); +} + +static void AddCompilerMappings(Builder& builder) { + builder. + Define("--compiled-classes=_") + .WithType<std::string>() + .IntoKey(M::CompiledClasses) + .Define("--compiled-classes-zip=_") + .WithType<std::string>() + .IntoKey(M::CompiledClassesZip) + .Define("--compiled-methods=_") + .WithType<std::string>() + .IntoKey(M::CompiledMethods) + .Define("--compiled-methods-zip=_") + .WithType<std::string>() + .IntoKey(M::CompiledMethodsZip) + .Define("--run-passes=_") + .WithType<std::string>() + .IntoKey(M::Passes) + .Define("--profile-file=_") + .WithType<std::string>() + .IntoKey(M::Profile) + .Define("--profile-file-fd=_") + .WithType<int>() + .IntoKey(M::ProfileFd) + .Define("--no-inline-from=_") + .WithType<std::string>() + .IntoKey(M::NoInlineFrom); +} + +static void AddTargetMappings(Builder& builder) { + builder. + Define("--instruction-set=_") + .WithType<InstructionSet>() + .IntoKey(M::TargetInstructionSet) + .Define("--instruction-set-variant=_") + .WithType<std::string>() + .IntoKey(M::TargetInstructionSetVariant) + .Define("--instruction-set-features=_") + .WithType<std::string>() + .IntoKey(M::TargetInstructionSetFeatures); +} + +static Parser CreateArgumentParser() { + std::unique_ptr<Builder> parser_builder = std::unique_ptr<Builder>(new Builder()); + + AddInputMappings(*parser_builder); + AddGeneratedArtifactMappings(*parser_builder); + AddImageMappings(*parser_builder); + AddSwapMappings(*parser_builder); + AddCompilerMappings(*parser_builder); + AddTargetMappings(*parser_builder); + + parser_builder-> + Define({"--watch-dog", "--no-watch-dog"}) + .WithValues({true, false}) + .IntoKey(M::Watchdog) + .Define("--watchdog-timeout=_") + .WithType<int>() + .IntoKey(M::WatchdogTimeout) + .Define("-j_") + .WithType<unsigned int>() + .IntoKey(M::Threads) + .Define("--android-root=_") + .WithType<std::string>() + .IntoKey(M::AndroidRoot) + .Define("--compiler-backend=_") + .WithType<Compiler::Kind>() + .WithValueMap({{"Quick", Compiler::Kind::kQuick}, + {"Optimizing", Compiler::Kind::kOptimizing}}) + .IntoKey(M::Backend) + .Define("--host") + .IntoKey(M::Host) + .Define("--dump-timing") + .IntoKey(M::DumpTiming) + .Define("--dump-passes") + .IntoKey(M::DumpPasses) + .Define("--dump-stats") + .IntoKey(M::DumpStats) + .Define("--avoid-storing-invocation") + .IntoKey(M::AvoidStoringInvocation) + .Define("--very-large-app-threshold=_") + .WithType<unsigned int>() + .IntoKey(M::VeryLargeAppThreshold) + .Define("--force-determinism") + .IntoKey(M::ForceDeterminism) + .Define("--classpath-dir=_") + .WithType<std::string>() + .IntoKey(M::ClasspathDir) + .Define("--class-loader-context=_") + .WithType<std::string>() + .IntoKey(M::ClassLoaderContext) + .Define("--runtime-arg _") + .WithType<std::vector<std::string>>().AppendValues() + .IntoKey(M::RuntimeOptions); + + AddCompilerOptionsArgumentParserOptions<Dex2oatArgumentMap>(*parser_builder); + + parser_builder->IgnoreUnrecognized(false); + + return parser_builder->Build(); +} + +#pragma GCC diagnostic pop + +std::unique_ptr<Dex2oatArgumentMap> Dex2oatArgumentMap::Parse(int argc, + const char** argv, + std::string* error_msg) { + Parser parser = CreateArgumentParser(); + CmdlineResult parse_result = parser.Parse(argv, argc); + if (!parse_result.IsSuccess()) { + *error_msg = parse_result.GetMessage(); + return nullptr; + } + + return std::unique_ptr<Dex2oatArgumentMap>(new Dex2oatArgumentMap(parser.ReleaseArgumentsMap())); +} + +} // namespace art diff --git a/dex2oat/dex2oat_options.def b/dex2oat/dex2oat_options.def new file mode 100644 index 0000000000..83a3035ed5 --- /dev/null +++ b/dex2oat/dex2oat_options.def @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License") + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DEX2OAT_OPTIONS_KEY +#error "Please #define DEX2OAT_OPTIONS_KEY before #including this file" +#define DEX2OAT_OPTIONS_KEY(...) // Don't display errors in this file in IDEs. +#endif + +// This file defines the list of keys for Dex2oatOptions. +// These can be used with Dex2oatOptions.Get/Set/etc, for example: +// Dex2oatOptions opt; bool* dex2oat_enabled = opt.Get(Dex2oatOptions::Dex2Oat); +// +// Column Descriptions: +// <<Type>> <<Key Name>> <<Default Value>> +// +// Default values are only used by Map::GetOrDefault(K<T>). +// If a default value is omitted here, T{} is used as the default value, which is +// almost-always the value of the type as if it was memset to all 0. +// +// Please keep the columns aligned if possible when adding new rows. +// + +// Parse-able keys from the command line. +DEX2OAT_OPTIONS_KEY (std::vector<std::string>, DexFiles) +DEX2OAT_OPTIONS_KEY (std::vector<std::string>, DexLocations) +DEX2OAT_OPTIONS_KEY (int, ZipFd) +DEX2OAT_OPTIONS_KEY (std::string, ZipLocation) +DEX2OAT_OPTIONS_KEY (int, InputVdexFd) +DEX2OAT_OPTIONS_KEY (std::string, InputVdex) +DEX2OAT_OPTIONS_KEY (int, OutputVdexFd) +DEX2OAT_OPTIONS_KEY (std::string, OutputVdex) +DEX2OAT_OPTIONS_KEY (std::vector<std::string>, OatFiles) +DEX2OAT_OPTIONS_KEY (std::vector<std::string>, OatSymbols) +DEX2OAT_OPTIONS_KEY (int, OatFd) +DEX2OAT_OPTIONS_KEY (std::string, OatLocation) +DEX2OAT_OPTIONS_KEY (bool, Watchdog) +DEX2OAT_OPTIONS_KEY (int, WatchdogTimeout) +DEX2OAT_OPTIONS_KEY (unsigned int, Threads) +DEX2OAT_OPTIONS_KEY (std::vector<std::string>, ImageFilenames) +DEX2OAT_OPTIONS_KEY (std::string, ImageClasses) +DEX2OAT_OPTIONS_KEY (std::string, ImageClassesZip) +DEX2OAT_OPTIONS_KEY (ImageHeader::StorageMode, ImageFormat) +DEX2OAT_OPTIONS_KEY (std::string, CompiledClasses) +DEX2OAT_OPTIONS_KEY (std::string, CompiledClassesZip) +DEX2OAT_OPTIONS_KEY (std::string, CompiledMethods) +DEX2OAT_OPTIONS_KEY (std::string, CompiledMethodsZip) +DEX2OAT_OPTIONS_KEY (std::string, Passes) +DEX2OAT_OPTIONS_KEY (std::string, Base) // TODO: Hex string parsing. +DEX2OAT_OPTIONS_KEY (std::string, BootImage) +DEX2OAT_OPTIONS_KEY (std::string, AndroidRoot) +DEX2OAT_OPTIONS_KEY (InstructionSet, TargetInstructionSet) +DEX2OAT_OPTIONS_KEY (std::string, TargetInstructionSetVariant) +DEX2OAT_OPTIONS_KEY (std::string, TargetInstructionSetFeatures) +DEX2OAT_OPTIONS_KEY (Compiler::Kind, Backend) +DEX2OAT_OPTIONS_KEY (std::string, Profile) +DEX2OAT_OPTIONS_KEY (int, ProfileFd) +DEX2OAT_OPTIONS_KEY (Unit, Host) +DEX2OAT_OPTIONS_KEY (Unit, DumpTiming) +DEX2OAT_OPTIONS_KEY (Unit, DumpPasses) +DEX2OAT_OPTIONS_KEY (Unit, DumpStats) +DEX2OAT_OPTIONS_KEY (Unit, AvoidStoringInvocation) +DEX2OAT_OPTIONS_KEY (std::string, SwapFile) +DEX2OAT_OPTIONS_KEY (int, SwapFileFd) +DEX2OAT_OPTIONS_KEY (unsigned int, SwapDexSizeThreshold) +DEX2OAT_OPTIONS_KEY (unsigned int, SwapDexCountThreshold) +DEX2OAT_OPTIONS_KEY (unsigned int, VeryLargeAppThreshold) +DEX2OAT_OPTIONS_KEY (std::string, AppImageFile) +DEX2OAT_OPTIONS_KEY (int, AppImageFileFd) +DEX2OAT_OPTIONS_KEY (Unit, MultiImage) +DEX2OAT_OPTIONS_KEY (std::string, NoInlineFrom) +DEX2OAT_OPTIONS_KEY (Unit, ForceDeterminism) +DEX2OAT_OPTIONS_KEY (std::string, ClasspathDir) +DEX2OAT_OPTIONS_KEY (std::string, ClassLoaderContext) +DEX2OAT_OPTIONS_KEY (std::string, DirtyImageObjects) +DEX2OAT_OPTIONS_KEY (std::vector<std::string>, RuntimeOptions) + +#undef DEX2OAT_OPTIONS_KEY diff --git a/dex2oat/dex2oat_options.h b/dex2oat/dex2oat_options.h new file mode 100644 index 0000000000..a4c718625f --- /dev/null +++ b/dex2oat/dex2oat_options.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_DEX2OAT_DEX2OAT_OPTIONS_H_ +#define ART_DEX2OAT_DEX2OAT_OPTIONS_H_ + +#include <cstdio> +#include <string> +#include <vector> + +#include "base/variant_map.h" +#include "cmdline_types.h" // TODO: don't need to include this file here +#include "compiler.h" +#include "driver/compiler_options_map.h" +#include "image.h" + +namespace art { + +template <typename TVariantMap, + template <typename TKeyValue> class TVariantMapKey> +struct CmdlineParser; + +// Define a key that is usable with a Dex2oatArgumentMap. +// This key will *not* work with other subtypes of VariantMap. +template <typename TValue> +struct Dex2oatArgumentMapKey : VariantMapKey<TValue> { + Dex2oatArgumentMapKey() {} + explicit Dex2oatArgumentMapKey(TValue default_value) + : VariantMapKey<TValue>(std::move(default_value)) {} + // Don't ODR-use constexpr default values, which means that Struct::Fields + // that are declared 'static constexpr T Name = Value' don't need to have a matching definition. +}; + +// Defines a type-safe heterogeneous key->value map. +// Use the VariantMap interface to look up or to store a Dex2oatArgumentMapKey,Value pair. +// +// Example: +// auto map = Dex2oatArgumentMap(); +// map.Set(Dex2oatArgumentMap::ZipFd, -1); +// int *target_utilization = map.Get(Dex2oatArgumentMap::ZipFd); +// +struct Dex2oatArgumentMap : CompilerOptionsMap<Dex2oatArgumentMap, Dex2oatArgumentMapKey> { + // This 'using' line is necessary to inherit the variadic constructor. + using CompilerOptionsMap<Dex2oatArgumentMap, Dex2oatArgumentMapKey>::CompilerOptionsMap; + + static std::unique_ptr<Dex2oatArgumentMap> Parse(int argc, + const char** argv, + std::string* error_msg); + + // Make the next many usages of Key slightly shorter to type. + template <typename TValue> + using Key = Dex2oatArgumentMapKey<TValue>; + + // List of key declarations, shorthand for 'static const Key<T> Name' +#define DEX2OAT_OPTIONS_KEY(Type, Name, ...) static const Key<Type> (Name); +#include "dex2oat_options.def" +}; + +extern template struct CompilerOptionsMap<Dex2oatArgumentMap, Dex2oatArgumentMapKey>; + +} // namespace art + +#endif // ART_DEX2OAT_DEX2OAT_OPTIONS_H_ diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc index 5bf35139cb..1b731fc7f6 100644 --- a/dex2oat/dex2oat_test.cc +++ b/dex2oat/dex2oat_test.cc @@ -33,6 +33,7 @@ #include "dex2oat_environment_test.h" #include "dex2oat_return_codes.h" #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "jit/profile_compilation_info.h" #include "oat.h" #include "oat_file.h" @@ -677,7 +678,7 @@ class Dex2oatLayoutTest : public Dex2oatTest { const char* location = dex_location.c_str(); std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files; - ASSERT_TRUE(DexFile::Open(location, location, true, &error_msg, &dex_files)); + ASSERT_TRUE(DexFileLoader::Open(location, location, true, &error_msg, &dex_files)); EXPECT_EQ(dex_files.size(), 1U); std::unique_ptr<const DexFile>& dex_file = dex_files[0]; GenerateProfile(profile_location, @@ -811,7 +812,7 @@ class Dex2oatLayoutTest : public Dex2oatTest { const char* location = dex_location.c_str(); std::vector<std::unique_ptr<const DexFile>> dex_files; - ASSERT_TRUE(DexFile::Open(location, location, true, &error_msg, &dex_files)); + ASSERT_TRUE(DexFileLoader::Open(location, location, true, &error_msg, &dex_files)); EXPECT_EQ(dex_files.size(), 1U); std::unique_ptr<const DexFile>& old_dex_file = dex_files[0]; diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc index dfbe31a548..05af442a3a 100644 --- a/dex2oat/linker/oat_writer.cc +++ b/dex2oat/linker/oat_writer.cc @@ -34,6 +34,7 @@ #include "debug/method_debug_info.h" #include "dex/verification_results.h" #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "dex_file_types.h" #include "dexlayout.h" #include "driver/compiler_driver-inl.h" @@ -52,6 +53,7 @@ #include "mirror/class_loader.h" #include "mirror/dex_cache-inl.h" #include "mirror/object-inl.h" +#include "native_dex_file.h" #include "oat_quick_method_header.h" #include "os.h" #include "safe_map.h" @@ -415,7 +417,7 @@ bool OatWriter::AddDexFileSource(const char* filename, if (fd.Fd() == -1) { PLOG(ERROR) << "Failed to read magic number from dex file: '" << filename << "'"; return false; - } else if (IsDexMagic(magic)) { + } else if (DexFileLoader::IsValidMagic(magic)) { // The file is open for reading, not writing, so it's OK to let the File destructor // close it without checking for explicit Close(), so pass checkUsage = false. raw_dex_files_.emplace_back(new File(fd.Release(), location, /* checkUsage */ false)); @@ -447,13 +449,13 @@ bool OatWriter::AddZippedDexFilesSource(File&& zip_fd, return false; } for (size_t i = 0; ; ++i) { - std::string entry_name = DexFile::GetMultiDexClassesDexName(i); + std::string entry_name = DexFileLoader::GetMultiDexClassesDexName(i); std::unique_ptr<ZipEntry> entry(zip_archive->Find(entry_name.c_str(), &error_msg)); if (entry == nullptr) { break; } zipped_dex_files_.push_back(std::move(entry)); - zipped_dex_file_locations_.push_back(DexFile::GetMultiDexLocation(i, location)); + zipped_dex_file_locations_.push_back(DexFileLoader::GetMultiDexLocation(i, location)); const char* full_location = zipped_dex_file_locations_.back().c_str(); oat_dex_files_.emplace_back(full_location, DexFileSource(zipped_dex_files_.back().get()), @@ -478,12 +480,13 @@ bool OatWriter::AddVdexDexFilesSource(const VdexFile& vdex_file, LOG(ERROR) << "Unexpected number of dex files in vdex " << location; return false; } - if (!DexFile::IsMagicValid(current_dex_data)) { + + if (!DexFileLoader::IsValidMagic(current_dex_data)) { LOG(ERROR) << "Invalid magic in vdex file created from " << location; return false; } // We used `zipped_dex_file_locations_` to keep the strings in memory. - zipped_dex_file_locations_.push_back(DexFile::GetMultiDexLocation(i, location)); + zipped_dex_file_locations_.push_back(DexFileLoader::GetMultiDexLocation(i, location)); const char* full_location = zipped_dex_file_locations_.back().c_str(); oat_dex_files_.emplace_back(full_location, DexFileSource(current_dex_data), @@ -3107,11 +3110,12 @@ bool OatWriter::ReadDexFileHeader(File* file, OatDexFile* oat_dex_file) { } bool OatWriter::ValidateDexFileHeader(const uint8_t* raw_header, const char* location) { - if (!DexFile::IsMagicValid(raw_header)) { + const bool valid_native_dex_magic = NativeDexFile::IsMagicValid(raw_header); + if (!valid_native_dex_magic) { LOG(ERROR) << "Invalid magic number in dex file header. " << " File: " << location; return false; } - if (!DexFile::IsVersionValid(raw_header)) { + if (!NativeDexFile::IsVersionValid(raw_header)) { LOG(ERROR) << "Invalid version number in dex file header. " << " File: " << location; return false; } @@ -3242,12 +3246,12 @@ bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_fil LOG(ERROR) << "Failed to extract dex file to mem map for layout: " << error_msg; return false; } - dex_file = DexFile::Open(location, - zip_entry->GetCrc32(), - std::move(mem_map), - /* verify */ true, - /* verify_checksum */ true, - &error_msg); + dex_file = DexFileLoader::Open(location, + zip_entry->GetCrc32(), + std::move(mem_map), + /* verify */ true, + /* verify_checksum */ true, + &error_msg); } else if (oat_dex_file->source_.IsRawFile()) { File* raw_file = oat_dex_file->source_.GetRawFile(); int dup_fd = dup(raw_file->Fd()); @@ -3255,7 +3259,7 @@ bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_fil PLOG(ERROR) << "Failed to dup dex file descriptor (" << raw_file->Fd() << ") at " << location; return false; } - dex_file = DexFile::OpenDex(dup_fd, location, /* verify_checksum */ true, &error_msg); + dex_file = DexFileLoader::OpenDex(dup_fd, location, /* verify_checksum */ true, &error_msg); } else { // The source data is a vdex file. CHECK(oat_dex_file->source_.IsRawData()) @@ -3267,14 +3271,14 @@ bool OatWriter::LayoutAndWriteDexFile(OutputStream* out, OatDexFile* oat_dex_fil DCHECK(ValidateDexFileHeader(raw_dex_file, oat_dex_file->GetLocation())); const UnalignedDexFileHeader* header = AsUnalignedDexFileHeader(raw_dex_file); // Since the source may have had its layout changed, or may be quickened, don't verify it. - dex_file = DexFile::Open(raw_dex_file, - header->file_size_, - location, - oat_dex_file->dex_file_location_checksum_, - nullptr, - /* verify */ false, - /* verify_checksum */ false, - &error_msg); + dex_file = DexFileLoader::Open(raw_dex_file, + header->file_size_, + location, + oat_dex_file->dex_file_location_checksum_, + nullptr, + /* verify */ false, + /* verify_checksum */ false, + &error_msg); } if (dex_file == nullptr) { LOG(ERROR) << "Failed to open dex file for layout: " << error_msg; @@ -3532,14 +3536,14 @@ bool OatWriter::OpenDexFiles( } // Now, open the dex file. - dex_files.emplace_back(DexFile::Open(raw_dex_file, - oat_dex_file.dex_file_size_, - oat_dex_file.GetLocation(), - oat_dex_file.dex_file_location_checksum_, - /* oat_dex_file */ nullptr, - verify, - verify, - &error_msg)); + dex_files.emplace_back(DexFileLoader::Open(raw_dex_file, + oat_dex_file.dex_file_size_, + oat_dex_file.GetLocation(), + oat_dex_file.dex_file_location_checksum_, + /* oat_dex_file */ nullptr, + verify, + verify, + &error_msg)); if (dex_files.back() == nullptr) { LOG(ERROR) << "Failed to open dex file from oat file. File: " << oat_dex_file.GetLocation() << " Error: " << error_msg; diff --git a/dex2oat/linker/oat_writer_test.cc b/dex2oat/linker/oat_writer_test.cc index d89d9f07b2..a19057a0ed 100644 --- a/dex2oat/linker/oat_writer_test.cc +++ b/dex2oat/linker/oat_writer_test.cc @@ -26,6 +26,7 @@ #include "compiled_method-inl.h" #include "compiler.h" #include "debug/method_debug_info.h" +#include "dex_file_loader.h" #include "dex/quick_compiler_callbacks.h" #include "dex/verification_results.h" #include "driver/compiler_driver.h" @@ -48,16 +49,6 @@ namespace art { namespace linker { -NO_RETURN static void Usage(const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - std::string error; - android::base::StringAppendV(&error, fmt, ap); - LOG(FATAL) << error; - va_end(ap); - UNREACHABLE(); -} - class OatTest : public CommonCompilerTest { protected: static const bool kCompile = false; // DISABLED_ due to the time to compile libcore @@ -101,8 +92,11 @@ class OatTest : public CommonCompilerTest { insn_features_ = InstructionSetFeatures::FromVariant(insn_set, "default", error_msg); ASSERT_TRUE(insn_features_ != nullptr) << *error_msg; compiler_options_.reset(new CompilerOptions); - for (const std::string& option : compiler_options) { - compiler_options_->ParseCompilerOption(option, Usage); + if (!compiler_options_->ParseCompilerOptions(compiler_options, + false /* ignore_unrecognized */, + error_msg)) { + LOG(FATAL) << *error_msg; + UNREACHABLE(); } verification_results_.reset(new VerificationResults(compiler_options_.get())); callbacks_.reset(new QuickCompilerCallbacks(CompilerCallbacks::CallbackMode::kCompileApp)); @@ -752,14 +746,14 @@ void OatTest::TestZipFileInput(bool verify) { ASSERT_EQ(0, memcmp(&dex_file1_data->GetHeader(), &opened_dex_file1->GetHeader(), dex_file1_data->GetHeader().file_size_)); - ASSERT_EQ(DexFile::GetMultiDexLocation(0, zip_file.GetFilename().c_str()), + ASSERT_EQ(DexFileLoader::GetMultiDexLocation(0, zip_file.GetFilename().c_str()), opened_dex_file1->GetLocation()); ASSERT_EQ(dex_file2_data->GetHeader().file_size_, opened_dex_file2->GetHeader().file_size_); ASSERT_EQ(0, memcmp(&dex_file2_data->GetHeader(), &opened_dex_file2->GetHeader(), dex_file2_data->GetHeader().file_size_)); - ASSERT_EQ(DexFile::GetMultiDexLocation(1, zip_file.GetFilename().c_str()), + ASSERT_EQ(DexFileLoader::GetMultiDexLocation(1, zip_file.GetFilename().c_str()), opened_dex_file2->GetLocation()); } } @@ -801,14 +795,14 @@ void OatTest::TestZipFileInput(bool verify) { ASSERT_EQ(0, memcmp(&dex_file1_data->GetHeader(), &opened_dex_file1->GetHeader(), dex_file1_data->GetHeader().file_size_)); - ASSERT_EQ(DexFile::GetMultiDexLocation(0, zip_file.GetFilename().c_str()), + ASSERT_EQ(DexFileLoader::GetMultiDexLocation(0, zip_file.GetFilename().c_str()), opened_dex_file1->GetLocation()); ASSERT_EQ(dex_file2_data->GetHeader().file_size_, opened_dex_file2->GetHeader().file_size_); ASSERT_EQ(0, memcmp(&dex_file2_data->GetHeader(), &opened_dex_file2->GetHeader(), dex_file2_data->GetHeader().file_size_)); - ASSERT_EQ(DexFile::GetMultiDexLocation(1, zip_file.GetFilename().c_str()), + ASSERT_EQ(DexFileLoader::GetMultiDexLocation(1, zip_file.GetFilename().c_str()), opened_dex_file2->GetLocation()); } } diff --git a/dexdump/dexdump.cc b/dexdump/dexdump.cc index 7599d230d2..3648a3edd0 100644 --- a/dexdump/dexdump.cc +++ b/dexdump/dexdump.cc @@ -45,6 +45,7 @@ #include "android-base/stringprintf.h" #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "dex_file_types.h" #include "dex_instruction-inl.h" #include "dexdump_cfg.h" @@ -1825,7 +1826,7 @@ static void processDexFile(const char* fileName, fputs("Opened '", gOutFile); fputs(fileName, gOutFile); if (n > 1) { - fprintf(gOutFile, ":%s", DexFile::GetMultiDexClassesDexName(i).c_str()); + fprintf(gOutFile, ":%s", DexFileLoader::GetMultiDexClassesDexName(i).c_str()); } fprintf(gOutFile, "', DEX version '%.3s'\n", pDexFile->GetHeader().magic_ + 4); } @@ -1882,7 +1883,7 @@ int processFile(const char* fileName) { const bool kVerifyChecksum = !gOptions.ignoreBadChecksum; std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files; - if (!DexFile::Open(fileName, fileName, kVerifyChecksum, &error_msg, &dex_files)) { + if (!DexFileLoader::Open(fileName, fileName, kVerifyChecksum, &error_msg, &dex_files)) { // Display returned error message to user. Note that this error behavior // differs from the error messages shown by the original Dalvik dexdump. fputs(error_msg.c_str(), stderr); diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc index 095c960bc0..ade00723fd 100644 --- a/dexlayout/dexlayout.cc +++ b/dexlayout/dexlayout.cc @@ -35,6 +35,7 @@ #include "dex_file-inl.h" #include "dex_file_layout.h" +#include "dex_file_loader.h" #include "dex_file_types.h" #include "dex_file_verifier.h" #include "dex_instruction-inl.h" @@ -1929,14 +1930,14 @@ void DexLayout::OutputDexFile(const DexFile* dex_file) { // Verify the output dex file's structure for debug builds. if (kIsDebugBuild) { std::string location = "memory mapped file for " + dex_file_location; - std::unique_ptr<const DexFile> output_dex_file(DexFile::Open(mem_map_->Begin(), - mem_map_->Size(), - location, - header_->Checksum(), - /*oat_dex_file*/ nullptr, - /*verify*/ true, - /*verify_checksum*/ false, - &error_msg)); + std::unique_ptr<const DexFile> output_dex_file(DexFileLoader::Open(mem_map_->Begin(), + mem_map_->Size(), + location, + header_->Checksum(), + /*oat_dex_file*/ nullptr, + /*verify*/ true, + /*verify_checksum*/ false, + &error_msg)); DCHECK(output_dex_file != nullptr) << "Failed to re-open output file:" << error_msg; } // Do IR-level comparison between input and output. This check ignores potential differences @@ -1998,7 +1999,7 @@ int DexLayout::ProcessFile(const char* file_name) { const bool verify_checksum = !options_.ignore_bad_checksum_; std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files; - if (!DexFile::Open(file_name, file_name, verify_checksum, &error_msg, &dex_files)) { + if (!DexFileLoader::Open(file_name, file_name, verify_checksum, &error_msg, &dex_files)) { // Display returned error message to user. Note that this error behavior // differs from the error messages shown by the original Dalvik dexdump. fputs(error_msg.c_str(), stderr); diff --git a/dexlayout/dexlayout_test.cc b/dexlayout/dexlayout_test.cc index 336eb5fbcc..f8fa893069 100644 --- a/dexlayout/dexlayout_test.cc +++ b/dexlayout/dexlayout_test.cc @@ -24,6 +24,7 @@ #include "base/unix_file/fd_file.h" #include "common_runtime_test.h" #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "exec_utils.h" #include "jit/profile_compilation_info.h" #include "utils.h" @@ -322,11 +323,11 @@ class DexLayoutTest : public CommonRuntimeTest { const std::string& dex_location) { std::vector<std::unique_ptr<const DexFile>> dex_files; std::string error_msg; - bool result = DexFile::Open(input_dex.c_str(), - input_dex, - false, - &error_msg, - &dex_files); + bool result = DexFileLoader::Open(input_dex.c_str(), + input_dex, + false, + &error_msg, + &dex_files); ASSERT_TRUE(result) << error_msg; ASSERT_GE(dex_files.size(), 1u); diff --git a/dexlist/dexlist.cc b/dexlist/dexlist.cc index 6a1e22a525..e5870522a3 100644 --- a/dexlist/dexlist.cc +++ b/dexlist/dexlist.cc @@ -27,6 +27,7 @@ #include <stdlib.h> #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "mem_map.h" #include "runtime.h" @@ -178,7 +179,7 @@ static int processFile(const char* fileName) { static constexpr bool kVerifyChecksum = true; std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files; - if (!DexFile::Open(fileName, fileName, kVerifyChecksum, &error_msg, &dex_files)) { + if (!DexFileLoader::Open(fileName, fileName, kVerifyChecksum, &error_msg, &dex_files)) { fputs(error_msg.c_str(), stderr); fputc('\n', stderr); return -1; diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc index 51a67ca45e..08d38d5925 100644 --- a/dexoptanalyzer/dexoptanalyzer.cc +++ b/dexoptanalyzer/dexoptanalyzer.cc @@ -97,6 +97,10 @@ NO_RETURN static void Usage(const char *fmt, ...) { UsageError(" --android-data=<directory>: optional, the directory which should be used as"); UsageError(" android-data. By default ANDROID_DATA env variable is used."); UsageError(""); + UsageError(" --oat-fd=number: file descriptor of the oat file which should be analyzed"); + UsageError(""); + UsageError(" --vdex-fd=number: file descriptor of the vdex file corresponding to the oat file"); + UsageError(""); UsageError(" --downgrade: optional, if the purpose of dexopt is to downgrade the dex file"); UsageError(" By default, dexopt considers upgrade case."); UsageError(""); @@ -167,6 +171,10 @@ class DexoptAnalyzer FINAL { setenv("ANDROID_DATA", new_android_data.c_str(), 1); } else if (option.starts_with("--downgrade")) { downgrade_ = true; + } else if (option.starts_with("--oat-fd")) { + oat_fd_ = std::stoi(option.substr(strlen("--oat-fd=")).ToString(), nullptr, 0); + } else if (option.starts_with("--vdex-fd")) { + vdex_fd_ = std::stoi(option.substr(strlen("--vdex-fd=")).ToString(), nullptr, 0); } else { Usage("Unknown argument '%s'", option.data()); } } @@ -181,6 +189,12 @@ class DexoptAnalyzer FINAL { Usage("--image unspecified and ANDROID_ROOT not set or image file does not exist."); } } + if (oat_fd_ > 0 && vdex_fd_ < 0) { + Usage("A valid --vdex-fd must also be provided with --oat-fd."); + } + if (oat_fd_ < 0 && vdex_fd_ > 0) { + Usage("A valid --oat-fd must also be provided with --vdex-fd."); + } } bool CreateRuntime() { @@ -223,15 +237,26 @@ class DexoptAnalyzer FINAL { } std::unique_ptr<Runtime> runtime(Runtime::Current()); - OatFileAssistant oat_file_assistant(dex_file_.c_str(), isa_, /*load_executable*/ false); + std::unique_ptr<OatFileAssistant> oat_file_assistant; + if (oat_fd_ != -1 && vdex_fd_ != -1) { + oat_file_assistant = std::make_unique<OatFileAssistant>(dex_file_.c_str(), + isa_, + false /*load_executable*/, + vdex_fd_, + oat_fd_); + } else { + oat_file_assistant = std::make_unique<OatFileAssistant>(dex_file_.c_str(), + isa_, + false /*load_executable*/); + } // Always treat elements of the bootclasspath as up-to-date. // TODO(calin): this check should be in OatFileAssistant. - if (oat_file_assistant.IsInBootClassPath()) { + if (oat_file_assistant->IsInBootClassPath()) { return kNoDexOptNeeded; } // TODO(calin): Pass the class loader context as an argument to dexoptanalyzer. b/62269291. - int dexoptNeeded = oat_file_assistant.GetDexOptNeeded( + int dexoptNeeded = oat_file_assistant->GetDexOptNeeded( compiler_filter_, assume_profile_changed_, downgrade_); // Convert OatFileAssitant codes to dexoptanalyzer codes. @@ -258,6 +283,8 @@ class DexoptAnalyzer FINAL { bool assume_profile_changed_; bool downgrade_; std::string image_; + int oat_fd_ = -1; + int vdex_fd_ = -1; }; static int dexoptAnalyze(int argc, char** argv) { diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc index 32dd69e0e6..0282fbce1f 100644 --- a/openjdkjvmti/events.cc +++ b/openjdkjvmti/events.cc @@ -842,6 +842,12 @@ void EventHandler::HandleLocalAccessCapabilityAdded() { bool operator()(art::ObjPtr<art::mirror::Class> klass) OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + if (!klass->IsLoaded()) { + // Skip classes that aren't loaded since they might not have fully allocated and initialized + // their methods. Furthemore since the jvmti-plugin must have been loaded by this point + // these methods will definitately be using debuggable code. + return true; + } for (auto& m : klass->GetMethods(art::kRuntimePointerSize)) { const void* code = m.GetEntryPointFromQuickCompiledCode(); if (m.IsNative() || m.IsProxyMethod()) { diff --git a/openjdkjvmti/fixed_up_dex_file.cc b/openjdkjvmti/fixed_up_dex_file.cc index 5bfa5ca491..c4988695f1 100644 --- a/openjdkjvmti/fixed_up_dex_file.cc +++ b/openjdkjvmti/fixed_up_dex_file.cc @@ -30,6 +30,7 @@ */ #include "fixed_up_dex_file.h" +#include "dex_file_loader.h" #include "dex_file-inl.h" // Runtime includes. @@ -68,7 +69,7 @@ std::unique_ptr<FixedUpDexFile> FixedUpDexFile::Create(const art::DexFile& origi data.resize(original.Size()); memcpy(data.data(), original.Begin(), original.Size()); std::string error; - std::unique_ptr<const art::DexFile> new_dex_file(art::DexFile::Open( + std::unique_ptr<const art::DexFile> new_dex_file(art::DexFileLoader::Open( data.data(), data.size(), /*location*/"Unquickening_dexfile.dex", diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc index daf4a8b7f2..5f29416134 100644 --- a/openjdkjvmti/ti_class.cc +++ b/openjdkjvmti/ti_class.cc @@ -43,6 +43,7 @@ #include "class_table-inl.h" #include "common_throws.h" #include "dex_file_annotations.h" +#include "dex_file_loader.h" #include "events-inl.h" #include "fixed_up_dex_file.h" #include "gc/heap-visit-objects-inl.h" @@ -106,12 +107,12 @@ static std::unique_ptr<const art::DexFile> MakeSingleDexFile(art::Thread* self, } uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_; std::string map_name = map->GetName(); - std::unique_ptr<const art::DexFile> dex_file(art::DexFile::Open(map_name, - checksum, - std::move(map), - /*verify*/true, - /*verify_checksum*/true, - &error_msg)); + std::unique_ptr<const art::DexFile> dex_file(art::DexFileLoader::Open(map_name, + checksum, + std::move(map), + /*verify*/true, + /*verify_checksum*/true, + &error_msg)); if (dex_file.get() == nullptr) { LOG(WARNING) << "Unable to load modified dex file for " << descriptor << ": " << error_msg; art::ThrowClassFormatError(nullptr, diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc index f05977a4b1..50402a04a9 100644 --- a/openjdkjvmti/ti_method.cc +++ b/openjdkjvmti/ti_method.cc @@ -572,8 +572,9 @@ class CommonLocalVariableClosure : public art::Closure { return; } art::ArtMethod* method = visitor.GetMethod(); - if (method->IsNative()) { - // TODO We really should support get/set for non-shadow frames. + // Native and 'art' proxy methods don't have registers. + if (method->IsNative() || method->IsProxyMethod()) { + // TODO It might be useful to fake up support for get at least on proxy frames. result_ = ERR(OPAQUE_FRAME); return; } else if (method->GetCodeItem()->registers_size_ <= slot_) { diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc index 815caeb9cb..53abfbca00 100644 --- a/openjdkjvmti/ti_redefine.cc +++ b/openjdkjvmti/ti_redefine.cc @@ -44,6 +44,7 @@ #include "class_linker-inl.h" #include "debugger.h" #include "dex_file.h" +#include "dex_file_loader.h" #include "dex_file_types.h" #include "events-inl.h" #include "gc/allocation_listener.h" @@ -425,12 +426,12 @@ jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition return ERR(INVALID_CLASS_FORMAT); } uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_; - std::unique_ptr<const art::DexFile> dex_file(art::DexFile::Open(map->GetName(), - checksum, - std::move(map), - /*verify*/true, - /*verify_checksum*/true, - error_msg_)); + std::unique_ptr<const art::DexFile> dex_file(art::DexFileLoader::Open(map->GetName(), + checksum, + std::move(map), + /*verify*/true, + /*verify_checksum*/true, + error_msg_)); if (dex_file.get() == nullptr) { os << "Unable to load modified dex file for " << def.GetName() << ": " << *error_msg_; *error_msg_ = os.str(); @@ -1403,7 +1404,9 @@ void Redefiner::ClassRedefinition::UpdateMethods(art::ObjPtr<art::mirror::Class> method.SetNotIntrinsic(); // Notify the jit that this method is redefined. art::jit::Jit* jit = driver_->runtime_->GetJit(); - if (jit != nullptr) { + // Non-invokable methods don't have any JIT data associated with them so we don't need to tell + // the jit about them. + if (jit != nullptr && method.IsInvokable()) { jit->GetCodeCache()->NotifyMethodRedefined(&method); } } diff --git a/openjdkjvmti/ti_search.cc b/openjdkjvmti/ti_search.cc index 25bc5d6eb3..bafc8552b1 100644 --- a/openjdkjvmti/ti_search.cc +++ b/openjdkjvmti/ti_search.cc @@ -39,6 +39,7 @@ #include "base/macros.h" #include "class_linker.h" #include "dex_file.h" +#include "dex_file_loader.h" #include "jni_internal.h" #include "mirror/class-inl.h" #include "mirror/object.h" @@ -226,7 +227,7 @@ jvmtiError SearchUtil::AddToBootstrapClassLoaderSearch(jvmtiEnv* env ATTRIBUTE_U std::string error_msg; std::vector<std::unique_ptr<const art::DexFile>> dex_files; - if (!art::DexFile::Open(segment, segment, true, &error_msg, &dex_files)) { + if (!art::DexFileLoader::Open(segment, segment, true, &error_msg, &dex_files)) { LOG(WARNING) << "Could not open " << segment << " for boot classpath extension: " << error_msg; return ERR(ILLEGAL_ARGUMENT); } diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc index d4cc42ae70..e0c139954d 100644 --- a/openjdkjvmti/ti_stack.cc +++ b/openjdkjvmti/ti_stack.cc @@ -789,7 +789,7 @@ jvmtiError StackUtil::GetFrameLocation(jvmtiEnv* env ATTRIBUTE_UNUSED, } *method_ptr = art::jni::EncodeArtMethod(closure.method); - if (closure.method->IsNative()) { + if (closure.method->IsNative() || closure.method->IsProxyMethod()) { *location_ptr = -1; } else { if (closure.dex_pc == art::dex::kDexNoIndex) { diff --git a/profman/profman.cc b/profman/profman.cc index 9b4f5794b7..8ccf7b4c1d 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -39,6 +39,7 @@ #include "boot_image_profile.h" #include "bytecode_utils.h" #include "dex_file.h" +#include "dex_file_loader.h" #include "dex_file_types.h" #include "jit/profile_compilation_info.h" #include "profile_assistant.h" @@ -328,21 +329,21 @@ class ProfMan FINAL { std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files_for_location; if (use_apk_fd_list) { - if (DexFile::OpenZip(apks_fd_[i], - dex_locations_[i], - kVerifyChecksum, - &error_msg, - &dex_files_for_location)) { + if (DexFileLoader::OpenZip(apks_fd_[i], + dex_locations_[i], + kVerifyChecksum, + &error_msg, + &dex_files_for_location)) { } else { LOG(WARNING) << "OpenZip failed for '" << dex_locations_[i] << "' " << error_msg; continue; } } else { - if (DexFile::Open(apk_files_[i].c_str(), - dex_locations_[i], - kVerifyChecksum, - &error_msg, - &dex_files_for_location)) { + if (DexFileLoader::Open(apk_files_[i].c_str(), + dex_locations_[i], + kVerifyChecksum, + &error_msg, + &dex_files_for_location)) { } else { LOG(WARNING) << "Open failed for '" << dex_locations_[i] << "' " << error_msg; continue; @@ -795,7 +796,7 @@ class ProfMan FINAL { const DexFile* dex_file = class_ref.dex_file; const auto& dex_resolved_classes = resolved_class_set.emplace( dex_file->GetLocation(), - dex_file->GetBaseLocation(), + DexFileLoader::GetBaseLocation(dex_file->GetLocation()), dex_file->GetLocationChecksum(), dex_file->NumMethodIds()); dex_resolved_classes.first->AddClass(class_ref.TypeIndex()); diff --git a/runtime/Android.bp b/runtime/Android.bp index 711bc65892..ed9906a5ec 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -55,6 +55,7 @@ cc_defaults { "compiler_filter.cc", "debugger.cc", "dex_file.cc", + "dex_file_loader.cc", "dex_file_annotations.cc", "dex_file_layout.cc", "dex_file_tracking_registrar.cc", @@ -152,6 +153,7 @@ cc_defaults { "mirror/throwable.cc", "monitor.cc", "native_bridge_art_interface.cc", + "native_dex_file.cc", "native_stack_dump.cc", "native/dalvik_system_DexFile.cc", "native/dalvik_system_VMDebug.cc", @@ -530,6 +532,7 @@ art_cc_test { "barrier_test.cc", "base/arena_allocator_test.cc", "base/bit_field_test.cc", + "base/bit_struct_test.cc", "base/bit_utils_test.cc", "base/bit_vector_test.cc", "base/hash_set_test.cc", diff --git a/runtime/base/bit_struct.h b/runtime/base/bit_struct.h new file mode 100644 index 0000000000..1f86ee1917 --- /dev/null +++ b/runtime/base/bit_struct.h @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_BASE_BIT_STRUCT_H_ +#define ART_RUNTIME_BASE_BIT_STRUCT_H_ + +#include "bit_struct_detail.h" +#include "bit_utils.h" + +// +// Zero-cost, type-safe, well-defined "structs" of bit fields. +// +// --------------------------------------------- +// Usage example: +// --------------------------------------------- +// +// // Definition for type 'Example' +// BITSTRUCT_DEFINE_START(Example, 10) +// BitStructUint<0, 2> u2; // Every field must be a BitStruct[*]. +// BitStructInt<2, 7> i7; +// BitStructUint<9, 1> i1; +// BITSTRUCT_DEFINE_END(Example); +// +// Would define a bit struct with this layout: +// <- 1 -> <-- 7 --> <- 2 -> +// +--------+---------------+-----+ +// | i1 | i7 | u2 + +// +--------+---------------+-----+ +// 10 9 2 0 +// +// // Read-write just like regular values. +// Example ex; +// ex.u2 = 3; +// ex.i7 = -25; +// ex.i1 = true; +// size_t u2 = ex.u2; +// int i7 = ex.i7; +// bool i1 = ex.i1; +// +// // It's packed down to the smallest # of machine words. +// assert(sizeof(Example) == 2); +// // The exact bit pattern is well-defined by the template parameters. +// uint16_t cast = *reinterpret_cast<uint16_t*>(ex); +// assert(cast == ((3) | (0b100111 << 2) | (true << 9); +// +// --------------------------------------------- +// Why not just use C++ bitfields? +// --------------------------------------------- +// +// The layout is implementation-defined. +// We do not know whether the fields are packed left-to-right or +// right-to-left, so it makes it useless when the memory layout needs to be +// precisely controlled. +// +// --------------------------------------------- +// More info: +// --------------------------------------------- +// Currently uintmax_t is the largest supported underlying storage type, +// all (kBitOffset + kBitWidth) must fit into BitSizeOf<uintmax_t>(); +// +// Using BitStruct[U]int will automatically select an underlying type +// that's the smallest to fit your (offset + bitwidth). +// +// BitStructNumber can be used to manually select an underlying type. +// +// BitStructField can be used with custom standard-layout structs, +// thus allowing for arbitrary nesting of bit structs. +// +namespace art { +// Zero-cost wrapper around a struct 'T', allowing it to be stored as a bitfield +// at offset 'kBitOffset' and width 'kBitWidth'. +// The storage is plain unsigned int, whose size is the smallest required to fit +// 'kBitOffset + kBitWidth'. All operations to this become BitFieldExtract/BitFieldInsert +// operations to the underlying uint. +// +// Field memory representation: +// +// MSB <-- width --> LSB +// +--------+------------+--------+ +// | ?????? | u bitfield | ?????? + +// +--------+------------+--------+ +// offset 0 +// +// Reading/writing the bitfield (un)packs it into a temporary T: +// +// MSB <-- width --> LSB +// +-----------------+------------+ +// | 0.............0 | T bitfield | +// +-----------------+------------+ +// 0 +// +// It's the responsibility of the StorageType to ensure the bit representation +// of T can be represented by kBitWidth. +template <typename T, + size_t kBitOffset, + size_t kBitWidth = BitStructSizeOf<T>(), + typename StorageType = typename detail::MinimumTypeUnsignedHelper<kBitOffset + kBitWidth>::type> +struct BitStructField { + static_assert(std::is_standard_layout<T>::value, "T must be standard layout"); + + operator T() const { + return Get(); + } + + // Exclude overload when T==StorageType. + template <typename _ = void, + typename = std::enable_if_t<std::is_same<T, StorageType>::value, _>> + explicit operator StorageType() const { + return GetStorage(); + } + + BitStructField& operator=(T value) { + return Assign(*this, value); + } + + static constexpr size_t BitStructSizeOf() { + return kBitWidth; + } + + protected: + template <typename T2> + T2& Assign(T2& what, T value) { + // Since C++ doesn't allow the type of operator= to change out + // in the subclass, reimplement operator= in each subclass + // manually and call this helper function. + static_assert(std::is_base_of<BitStructField, T2>::value, "T2 must inherit BitStructField"); + what.Set(value); + return what; + } + + T Get() const { + ValueStorage vs; + vs.pod_.val_ = GetStorage(); + return vs.value_; + } + + void Set(T value) { + ValueStorage value_as_storage; + value_as_storage.value_ = value; + + storage_.pod_.val_ = BitFieldInsert(storage_.pod_.val_, + value_as_storage.pod_.val_, + kBitOffset, + kBitWidth); + } + + private: + StorageType GetStorage() const { + return BitFieldExtract(storage_.pod_.val_, kBitOffset, kBitWidth); + } + + // Underlying value must be wrapped in a separate standard-layout struct. + // See below for more details. + struct PodWrapper { + StorageType val_; + }; + + union ValueStorage { + // Safely alias pod_ and value_ together. + // + // See C++ 9.5.1 [class.union]: + // If a standard-layout union contains several standard-layout structs that share a common + // initial sequence ... it is permitted to inspect the common initial sequence of any of + // standard-layout struct members. + PodWrapper pod_; + T value_; + } storage_; + + // Future work: In theory almost non-standard layout can be supported here, + // assuming they don't rely on the address of (this). + // We just have to use memcpy since the union-aliasing would not work. +}; + +// Base class for number-like BitStruct fields. +// T is the type to store in as a bit field. +// kBitOffset, kBitWidth define the position and length of the bitfield. +// +// (Common usage should be BitStructInt, BitStructUint -- this +// intermediate template allows a user-defined integer to be used.) +template <typename T, size_t kBitOffset, size_t kBitWidth> +struct BitStructNumber : public BitStructField<T, kBitOffset, kBitWidth, /*StorageType*/T> { + using StorageType = T; + + BitStructNumber& operator=(T value) { + return BaseType::Assign(*this, value); + } + + /*implicit*/ operator T() const { + return Get(); + } + + explicit operator bool() const { + return static_cast<bool>(Get()); + } + + BitStructNumber& operator++() { + *this = Get() + 1u; + return *this; + } + + StorageType operator++(int) { + return Get() + 1u; + } + + BitStructNumber& operator--() { + *this = Get() - 1u; + return *this; + } + + StorageType operator--(int) { + return Get() - 1u; + } + + private: + using BaseType = BitStructField<T, kBitOffset, kBitWidth, /*StorageType*/T>; + using BaseType::Get; +}; + +// Create a BitStruct field which uses the smallest underlying int storage type, +// in order to be large enough to fit (kBitOffset + kBitWidth). +// +// Values are sign-extended when they are read out. +template <size_t kBitOffset, size_t kBitWidth> +using BitStructInt = + BitStructNumber<typename detail::MinimumTypeHelper<int, kBitOffset + kBitWidth>::type, + kBitOffset, + kBitWidth>; + +// Create a BitStruct field which uses the smallest underlying uint storage type, +// in order to be large enough to fit (kBitOffset + kBitWidth). +// +// Values are zero-extended when they are read out. +template <size_t kBitOffset, size_t kBitWidth> +using BitStructUint = + BitStructNumber<typename detail::MinimumTypeHelper<unsigned int, kBitOffset + kBitWidth>::type, + kBitOffset, + kBitWidth>; + +// Start a definition for a bitstruct. +// A bitstruct is defined to be a union with a common initial subsequence +// that we call 'DefineBitStructSize<bitwidth>'. +// +// See top of file for usage example. +// +// This marker is required by the C++ standard in order to +// have a "common initial sequence". +// +// See C++ 9.5.1 [class.union]: +// If a standard-layout union contains several standard-layout structs that share a common +// initial sequence ... it is permitted to inspect the common initial sequence of any of +// standard-layout struct members. +#define BITSTRUCT_DEFINE_START(name, bitwidth) \ + union name { \ + art::detail::DefineBitStructSize<(bitwidth)> _; \ + static constexpr size_t BitStructSizeOf() { return (bitwidth); } + +// End the definition of a bitstruct, and insert a sanity check +// to ensure that the bitstruct did not exceed the specified size. +// +// See top of file for usage example. +#define BITSTRUCT_DEFINE_END(name) \ + }; /* NOLINT [readability/braces] [4] */ \ + static_assert(art::detail::ValidateBitStructSize<name>(), \ + #name "bitsize incorrect: " \ + "did you insert extra fields that weren't BitStructX, " \ + "and does the size match the sum of the field widths?") + +// Determine the minimal bit size for a user-defined type T. +// Used by BitStructField to determine how small a custom type is. +template <typename T> +static constexpr size_t BitStructSizeOf() { + return T::BitStructSizeOf(); +} + +} // namespace art + +#endif // ART_RUNTIME_BASE_BIT_STRUCT_H_ diff --git a/runtime/base/bit_struct_detail.h b/runtime/base/bit_struct_detail.h new file mode 100644 index 0000000000..9f629c0970 --- /dev/null +++ b/runtime/base/bit_struct_detail.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_BASE_BIT_STRUCT_DETAIL_H_ +#define ART_RUNTIME_BASE_BIT_STRUCT_DETAIL_H_ + +#include "bit_utils.h" +#include "globals.h" + +#include <type_traits> + +// Implementation details for bit_struct.h +// Not intended to be used stand-alone. + +namespace art { + +template <typename T> +static constexpr size_t BitStructSizeOf(); + +namespace detail { + // Select the smallest uintX_t that will fit kBitSize bits. + template <size_t kBitSize> + struct MinimumTypeUnsignedHelper { + using type = + typename std::conditional<kBitSize == 0, void, + typename std::conditional<kBitSize <= 8, uint8_t, + typename std::conditional<kBitSize <= 16, uint16_t, + typename std::conditional<kBitSize <= 32, uint32_t, + typename std::conditional<kBitSize <= 64, uint64_t, + typename std::conditional<kBitSize <= BitSizeOf<uintmax_t>(), uintmax_t, + void>::type>::type>::type>::type>::type>::type; + }; + + // Select the smallest [u]intX_t that will fit kBitSize bits. + // Automatically picks intX_t or uintX_t based on the sign-ness of T. + template <typename T, size_t kBitSize> + struct MinimumTypeHelper { + using type_unsigned = typename MinimumTypeUnsignedHelper<kBitSize>::type; + + using type = + typename std::conditional</* if */ std::is_signed<T>::value, + /* then */ typename std::make_signed<type_unsigned>::type, + /* else */ type_unsigned>::type; + }; + + // Ensure the minimal type storage for 'T' matches its declared BitStructSizeOf. + // Nominally used by the BITSTRUCT_DEFINE_END macro. + template <typename T> + static constexpr bool ValidateBitStructSize() { + const size_t kBitStructSizeOf = BitStructSizeOf<T>(); + const size_t kExpectedSize = (BitStructSizeOf<T>() < kBitsPerByte) + ? kBitsPerByte + : RoundUpToPowerOfTwo(kBitStructSizeOf); + + // Ensure no extra fields were added in between START/END. + const size_t kActualSize = sizeof(T) * kBitsPerByte; + return kExpectedSize == kActualSize; + } + + // Denotes the beginning of a bit struct. + // + // This marker is required by the C++ standard in order to + // have a "common initial sequence". + // + // See C++ 9.5.1 [class.union]: + // If a standard-layout union contains several standard-layout structs that share a common + // initial sequence ... it is permitted to inspect the common initial sequence of any of + // standard-layout struct members. + template <size_t kSize> + struct DefineBitStructSize { + private: + typename MinimumTypeUnsignedHelper<kSize>::type _; + }; +} // namespace detail +} // namespace art + +#endif // ART_RUNTIME_BASE_BIT_STRUCT_DETAIL_H_ diff --git a/runtime/base/bit_struct_test.cc b/runtime/base/bit_struct_test.cc new file mode 100644 index 0000000000..872ada324c --- /dev/null +++ b/runtime/base/bit_struct_test.cc @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bit_struct.h" + +#include "gtest/gtest.h" + +namespace art { + +// A copy of detail::ValidateBitStructSize that uses EXPECT for a more +// human-readable message. +template <typename T> +static constexpr bool ValidateBitStructSize(const char* name) { + const size_t kBitStructSizeOf = BitStructSizeOf<T>(); + const size_t kExpectedSize = (BitStructSizeOf<T>() < kBitsPerByte) + ? kBitsPerByte + : RoundUpToPowerOfTwo(kBitStructSizeOf); + + // Ensure no extra fields were added in between START/END. + const size_t kActualSize = sizeof(T) * kBitsPerByte; + EXPECT_EQ(kExpectedSize, kActualSize) << name; + return true; +} + +#define VALIDATE_BITSTRUCT_SIZE(type) ValidateBitStructSize<type>(#type) + +TEST(BitStructs, MinimumType) { + EXPECT_EQ(1u, sizeof(typename detail::MinimumTypeUnsignedHelper<1>::type)); + EXPECT_EQ(1u, sizeof(typename detail::MinimumTypeUnsignedHelper<2>::type)); + EXPECT_EQ(1u, sizeof(typename detail::MinimumTypeUnsignedHelper<3>::type)); + EXPECT_EQ(1u, sizeof(typename detail::MinimumTypeUnsignedHelper<8>::type)); + EXPECT_EQ(2u, sizeof(typename detail::MinimumTypeUnsignedHelper<9>::type)); + EXPECT_EQ(2u, sizeof(typename detail::MinimumTypeUnsignedHelper<10>::type)); + EXPECT_EQ(2u, sizeof(typename detail::MinimumTypeUnsignedHelper<15>::type)); + EXPECT_EQ(2u, sizeof(typename detail::MinimumTypeUnsignedHelper<16>::type)); + EXPECT_EQ(4u, sizeof(typename detail::MinimumTypeUnsignedHelper<17>::type)); + EXPECT_EQ(4u, sizeof(typename detail::MinimumTypeUnsignedHelper<32>::type)); + EXPECT_EQ(8u, sizeof(typename detail::MinimumTypeUnsignedHelper<33>::type)); + EXPECT_EQ(8u, sizeof(typename detail::MinimumTypeUnsignedHelper<64>::type)); +} + +template <typename T> +size_t AsUint(const T& value) { + size_t uint_value = 0; + memcpy(&uint_value, &value, sizeof(value)); + return uint_value; +} + +struct CustomBitStruct { + CustomBitStruct() = default; + explicit CustomBitStruct(int8_t data) : data(data) {} + + static constexpr size_t BitStructSizeOf() { + return 4; + } + + int8_t data; +}; + +template <typename T> +void ZeroInitialize(T& value) { + memset(&value, 0, sizeof(T)); + // TODO: replace with value initialization +} + +TEST(BitStructs, Custom) { + CustomBitStruct expected(0b1111); + + BitStructField<CustomBitStruct, /*lsb*/4, /*width*/4> f; + ZeroInitialize(f); + + EXPECT_EQ(1u, sizeof(f)); + + f = CustomBitStruct(0b1111); + + CustomBitStruct read_out = f; + EXPECT_EQ(read_out.data, 0b1111); + + EXPECT_EQ(AsUint(f), 0b11110000u); +} + +BITSTRUCT_DEFINE_START(TestTwoCustom, /* size */ 8) + BitStructField<CustomBitStruct, /*lsb*/0, /*width*/4> f4_a; + BitStructField<CustomBitStruct, /*lsb*/4, /*width*/4> f4_b; +BITSTRUCT_DEFINE_END(TestTwoCustom); + +TEST(BitStructs, TwoCustom) { + EXPECT_EQ(sizeof(TestTwoCustom), 1u); + + VALIDATE_BITSTRUCT_SIZE(TestTwoCustom); + + TestTwoCustom cst; + ZeroInitialize(cst); + + // Test the write to most-significant field doesn't clobber least-significant. + cst.f4_a = CustomBitStruct(0b0110); + cst.f4_b = CustomBitStruct(0b0101); + + int8_t read_out = static_cast<CustomBitStruct>(cst.f4_a).data; + int8_t read_out_b = static_cast<CustomBitStruct>(cst.f4_b).data; + + EXPECT_EQ(0b0110, static_cast<int>(read_out)); + EXPECT_EQ(0b0101, static_cast<int>(read_out_b)); + + EXPECT_EQ(AsUint(cst), 0b01010110u); + + // Test write to least-significant field doesn't clobber most-significant. + cst.f4_a = CustomBitStruct(0); + + read_out = static_cast<CustomBitStruct>(cst.f4_a).data; + read_out_b = static_cast<CustomBitStruct>(cst.f4_b).data; + + EXPECT_EQ(0b0, static_cast<int>(read_out)); + EXPECT_EQ(0b0101, static_cast<int>(read_out_b)); + + EXPECT_EQ(AsUint(cst), 0b01010000u); +} + +TEST(BitStructs, Number) { + BitStructNumber<uint16_t, /*lsb*/4, /*width*/4> bsn; + ZeroInitialize(bsn); + EXPECT_EQ(2u, sizeof(bsn)); + + bsn = 0b1111; + + uint32_t read_out = static_cast<uint32_t>(bsn); + uint32_t read_out_impl = bsn; + + EXPECT_EQ(read_out, read_out_impl); + EXPECT_EQ(read_out, 0b1111u); + EXPECT_EQ(AsUint(bsn), 0b11110000u); +} + +BITSTRUCT_DEFINE_START(TestBitStruct, /* size */ 8) + BitStructInt</*lsb*/0, /*width*/3> i3; + BitStructUint</*lsb*/3, /*width*/4> u4; + + BitStructUint</*lsb*/0, /*width*/7> alias_all; +BITSTRUCT_DEFINE_END(TestBitStruct); + +TEST(BitStructs, Test1) { + { + // Check minimal size selection is correct. + BitStructInt</*lsb*/0, /*width*/3> i3; + BitStructUint</*lsb*/3, /*width*/4> u4; + + BitStructUint</*lsb*/0, /*width*/7> alias_all; + + EXPECT_EQ(1u, sizeof(i3)); + EXPECT_EQ(1u, sizeof(u4)); + EXPECT_EQ(1u, sizeof(alias_all)); + } + TestBitStruct tst; + ZeroInitialize(tst); + + // Check minimal size selection is correct. + EXPECT_EQ(1u, sizeof(TestBitStruct)); + EXPECT_EQ(1u, sizeof(tst._)); + EXPECT_EQ(1u, sizeof(tst.i3)); + EXPECT_EQ(1u, sizeof(tst.u4)); + EXPECT_EQ(1u, sizeof(tst.alias_all)); + + // Check operator assignment. + tst.i3 = -1; + tst.u4 = 0b1010; + + // Check implicit operator conversion. + int8_t read_i3 = tst.i3; + uint8_t read_u4 = tst.u4; + + // Ensure read-out values were correct. + EXPECT_EQ(static_cast<int8_t>(-1), read_i3); + EXPECT_EQ(0b1010, read_u4); + + // Ensure aliasing is working. + EXPECT_EQ(0b1010111, static_cast<uint8_t>(tst.alias_all)); + + // Ensure the bit pattern is correct. + EXPECT_EQ(0b1010111u, AsUint(tst)); + + // Math operator checks + { + // In-place + ++tst.u4; + EXPECT_EQ(static_cast<uint8_t>(0b1011), static_cast<uint8_t>(tst.u4)); + --tst.u4; + EXPECT_EQ(static_cast<uint8_t>(0b1010), static_cast<uint8_t>(tst.u4)); + + // Copy + uint8_t read_and_convert = tst.u4++; + EXPECT_EQ(static_cast<uint8_t>(0b1011), read_and_convert); + EXPECT_EQ(static_cast<uint8_t>(0b1010), static_cast<uint8_t>(tst.u4)); + read_and_convert = tst.u4--; + EXPECT_EQ(static_cast<uint8_t>(0b1001), read_and_convert); + EXPECT_EQ(static_cast<uint8_t>(0b1010), static_cast<uint8_t>(tst.u4)); + + // Check boolean operator conversion. + tst.u4 = 0b1010; + EXPECT_TRUE(static_cast<bool>(tst.u4)); + bool succ = tst.u4 ? true : false; + EXPECT_TRUE(succ); + + tst.u4 = 0; + EXPECT_FALSE(static_cast<bool>(tst.u4)); + +/* + // Disabled: Overflow is caught by the BitFieldInsert DCHECKs. + // Check overflow for uint. + tst.u4 = 0b1111; + ++tst.u4; + EXPECT_EQ(static_cast<uint8_t>(0), static_cast<uint8_t>(tst.u4)); +*/ + } +} + +BITSTRUCT_DEFINE_START(MixedSizeBitStruct, /* size */ 32) + BitStructUint</*lsb*/0, /*width*/3> u3; + BitStructUint</*lsb*/3, /*width*/10> u10; + BitStructUint</*lsb*/13, /*width*/19> u19; + + BitStructUint</*lsb*/0, /*width*/32> alias_all; +BITSTRUCT_DEFINE_END(MixedSizeBitStruct); + +// static_assert(sizeof(MixedSizeBitStruct) == sizeof(uint32_t), "TestBitStructs#MixedSize"); + +TEST(BitStructs, Mixed) { + EXPECT_EQ(4u, sizeof(MixedSizeBitStruct)); + + MixedSizeBitStruct tst; + ZeroInitialize(tst); + + // Check operator assignment. + tst.u3 = 0b111u; + tst.u10 = 0b1111010100u; + tst.u19 = 0b1010101010101010101u; + + // Check implicit operator conversion. + uint8_t read_u3 = tst.u3; + uint16_t read_u10 = tst.u10; + uint32_t read_u19 = tst.u19; + + // Ensure read-out values were correct. + EXPECT_EQ(0b111u, read_u3); + EXPECT_EQ(0b1111010100u, read_u10); + EXPECT_EQ(0b1010101010101010101u, read_u19); + + uint32_t read_all = tst.alias_all; + + // Ensure aliasing is working. + EXPECT_EQ(0b10101010101010101011111010100111u, read_all); + + // Ensure the bit pattern is correct. + EXPECT_EQ(0b10101010101010101011111010100111u, AsUint(tst)); +} + +} // namespace art diff --git a/runtime/base/bit_utils.h b/runtime/base/bit_utils.h index 87dac0261e..da3c7048b6 100644 --- a/runtime/base/bit_utils.h +++ b/runtime/base/bit_utils.h @@ -371,6 +371,128 @@ inline static uint64_t ReverseBits64(uint64_t opnd) { return opnd; } +// Create a mask for the least significant "bits" +// The returned value is always unsigned to prevent undefined behavior for bitwise ops. +// +// Given 'bits', +// Returns: +// <--- bits ---> +// +-----------------+------------+ +// | 0 ............0 | 1.....1 | +// +-----------------+------------+ +// msb lsb +template <typename T = size_t> +inline static constexpr std::make_unsigned_t<T> MaskLeastSignificant(size_t bits) { + DCHECK_GE(BitSizeOf<T>(), bits) << "Bits out of range for type T"; + using unsigned_T = std::make_unsigned_t<T>; + if (bits >= BitSizeOf<T>()) { + return std::numeric_limits<unsigned_T>::max(); + } else { + return static_cast<unsigned_T>((1 << bits) - 1); + } +} + +// Clears the bitfield starting at the least significant bit "lsb" with a bitwidth of 'width'. +// (Equivalent of ARM BFC instruction). +// +// Given: +// <-- width --> +// +--------+------------+--------+ +// | ABC... | bitfield | XYZ... + +// +--------+------------+--------+ +// lsb 0 +// Returns: +// <-- width --> +// +--------+------------+--------+ +// | ABC... | 0........0 | XYZ... + +// +--------+------------+--------+ +// lsb 0 +template <typename T> +inline static constexpr T BitFieldClear(T value, size_t lsb, size_t width) { + DCHECK_GE(BitSizeOf(value), lsb + width) << "Bit field out of range for value"; + const auto val = static_cast<std::make_unsigned_t<T>>(value); + const auto mask = MaskLeastSignificant<T>(width); + + return static_cast<T>(val & ~(mask << lsb)); +} + +// Inserts the contents of 'data' into bitfield of 'value' starting +// at the least significant bit "lsb" with a bitwidth of 'width'. +// Note: data must be within range of [MinInt(width), MaxInt(width)]. +// (Equivalent of ARM BFI instruction). +// +// Given (data): +// <-- width --> +// +--------+------------+--------+ +// | ABC... | bitfield | XYZ... + +// +--------+------------+--------+ +// lsb 0 +// Returns: +// <-- width --> +// +--------+------------+--------+ +// | ABC... | 0...data | XYZ... + +// +--------+------------+--------+ +// lsb 0 + +template <typename T, typename T2> +inline static constexpr T BitFieldInsert(T value, T2 data, size_t lsb, size_t width) { + DCHECK_GE(BitSizeOf(value), lsb + width) << "Bit field out of range for value"; + if (width != 0u) { + DCHECK_GE(MaxInt<T2>(width), data) << "Data out of range [too large] for bitwidth"; + DCHECK_LE(MinInt<T2>(width), data) << "Data out of range [too small] for bitwidth"; + } else { + DCHECK_EQ(static_cast<T2>(0), data) << "Data out of range [nonzero] for bitwidth 0"; + } + const auto data_mask = MaskLeastSignificant<T2>(width); + const auto value_cleared = BitFieldClear(value, lsb, width); + + return static_cast<T>(value_cleared | ((data & data_mask) << lsb)); +} + +// Extracts the bitfield starting at the least significant bit "lsb" with a bitwidth of 'width'. +// Signed types are sign-extended during extraction. (Equivalent of ARM UBFX/SBFX instruction). +// +// Given: +// <-- width --> +// +--------+-------------+-------+ +// | | bitfield | + +// +--------+-------------+-------+ +// lsb 0 +// (Unsigned) Returns: +// <-- width --> +// +----------------+-------------+ +// | 0... 0 | bitfield | +// +----------------+-------------+ +// 0 +// (Signed) Returns: +// <-- width --> +// +----------------+-------------+ +// | S... S | bitfield | +// +----------------+-------------+ +// 0 +// where S is the highest bit in 'bitfield'. +template <typename T> +inline static constexpr T BitFieldExtract(T value, size_t lsb, size_t width) { + DCHECK_GE(BitSizeOf(value), lsb + width) << "Bit field out of range for value"; + const auto val = static_cast<std::make_unsigned_t<T>>(value); + + const T bitfield_unsigned = + static_cast<T>((val >> lsb) & MaskLeastSignificant<T>(width)); + if (std::is_signed<T>::value) { + // Perform sign extension + if (width == 0) { // Avoid underflow. + return static_cast<T>(0); + } else if (bitfield_unsigned & (1 << (width - 1))) { // Detect if sign bit was set. + // MSB <width> LSB + // 0b11111...100...000000 + const auto ones_negmask = ~MaskLeastSignificant<T>(width); + return static_cast<T>(bitfield_unsigned | ones_negmask); + } + } + // Skip sign extension. + return bitfield_unsigned; +} + } // namespace art #endif // ART_RUNTIME_BASE_BIT_UTILS_H_ diff --git a/runtime/base/bit_utils_test.cc b/runtime/base/bit_utils_test.cc index c96c6dc933..0276d8ded2 100644 --- a/runtime/base/bit_utils_test.cc +++ b/runtime/base/bit_utils_test.cc @@ -345,6 +345,97 @@ static_assert(IsAbsoluteUint<32, int64_t>(std::numeric_limits<uint32_t>::max()), "TestIsAbsoluteUint64#27"); static_assert(!IsAbsoluteUint<32, int64_t>(kUint32MaxPlus1), "TestIsAbsoluteUint64#28"); +static_assert(MaskLeastSignificant(0) == 0b0, "TestMaskLeastSignificant#1"); +static_assert(MaskLeastSignificant(1) == 0b1, "TestMaskLeastSignificant#2"); +static_assert(MaskLeastSignificant(2) == 0b11, "TestMaskLeastSignificant#3"); +static_assert(MaskLeastSignificant<uint8_t>(8) == 0xFF, "TestMaskLeastSignificant#4"); +static_assert(MaskLeastSignificant<int8_t>(8) == 0xFF, "TestMaskLeastSignificant#5"); + +static_assert(BitFieldClear(0xFF, /*lsb*/0, /*width*/0) == 0xFF, "TestBitFieldClear#1"); +static_assert(BitFieldClear(std::numeric_limits<uint32_t>::max(), /*lsb*/0, /*width*/32) == 0x0, + "TestBitFieldClear#2"); +static_assert(BitFieldClear(std::numeric_limits<int32_t>::max(), /*lsb*/0, /*width*/32) == 0x0, + "TestBitFieldClear#3"); +static_assert(BitFieldClear(0xFF, /*lsb*/0, /*width*/2) == 0b11111100, "TestBitFieldClear#4"); +static_assert(BitFieldClear(0xFF, /*lsb*/0, /*width*/3) == 0b11111000, "TestBitFieldClear#5"); +static_assert(BitFieldClear(0xFF, /*lsb*/1, /*width*/3) == 0b11110001, "TestBitFieldClear#6"); +static_assert(BitFieldClear(0xFF, /*lsb*/2, /*width*/3) == 0b11100011, "TestBitFieldClear#7"); + +static_assert(BitFieldExtract(0xFF, /*lsb*/0, /*width*/0) == 0x0, "TestBitFieldExtract#1"); +static_assert(BitFieldExtract(std::numeric_limits<uint32_t>::max(), /*lsb*/0, /*width*/32) + == std::numeric_limits<uint32_t>::max(), + "TestBitFieldExtract#2"); +static_assert(BitFieldExtract(std::numeric_limits<int32_t>::max(), /*lsb*/0, /*width*/32) + == std::numeric_limits<int32_t>::max(), + "TestBitFieldExtract#3"); +static_assert(BitFieldExtract(static_cast<uint32_t>(0xFF), /*lsb*/0, /*width*/2) == 0b00000011, + "TestBitFieldExtract#4"); +static_assert(BitFieldExtract(static_cast<uint32_t>(0xFF), /*lsb*/0, /*width*/3) == 0b00000111, + "TestBitFieldExtract#5"); +static_assert(BitFieldExtract(static_cast<uint32_t>(0xFF), /*lsb*/1, /*width*/3) == 0b00000111, + "TestBitFieldExtract#6"); +static_assert(BitFieldExtract(static_cast<uint32_t>(0xFF), /*lsb*/2, /*width*/3) == 0b00000111, + "TestBitFieldExtract#7"); +static_assert(BitFieldExtract(static_cast<uint32_t>(0xFF), /*lsb*/3, /*width*/3) == 0b00000111, + "TestBitFieldExtract#8"); +static_assert(BitFieldExtract(static_cast<uint32_t>(0xFF), /*lsb*/8, /*width*/3) == 0b00000000, + "TestBitFieldExtract#9"); +static_assert(BitFieldExtract(static_cast<uint32_t>(0xFF), /*lsb*/7, /*width*/3) == 0b00000001, + "TestBitFieldExtract#10"); +static_assert(BitFieldExtract(static_cast<uint32_t>(0xFF), /*lsb*/6, /*width*/3) == 0b00000011, + "TestBitFieldExtract#11"); +static_assert(BitFieldExtract(0xFF, /*lsb*/0, /*width*/2) == -1, "TestBitFieldExtract#12"); +static_assert(BitFieldExtract(0xFF, /*lsb*/0, /*width*/3) == -1, "TestBitFieldExtract#13"); +static_assert(BitFieldExtract(0xFF, /*lsb*/1, /*width*/3) == -1, "TestBitFieldExtract#14"); +static_assert(BitFieldExtract(0xFF, /*lsb*/2, /*width*/3) == -1, "TestBitFieldExtract#15"); +static_assert(BitFieldExtract(0xFF, /*lsb*/3, /*width*/3) == -1, "TestBitFieldExtract#16"); +static_assert(BitFieldExtract(0xFF, /*lsb*/8, /*width*/3) == 0b00000000, "TestBitFieldExtract#17"); +static_assert(BitFieldExtract(0xFF, /*lsb*/7, /*width*/3) == 0b00000001, "TestBitFieldExtract#18"); +static_assert(BitFieldExtract(0xFF, /*lsb*/6, /*width*/3) == 0b00000011, "TestBitFieldExtract#19"); +static_assert(BitFieldExtract(static_cast<uint8_t>(0b01101010), /*lsb*/2, /*width*/4) + == 0b00001010, + "TestBitFieldExtract#20"); +static_assert(BitFieldExtract(static_cast<int8_t>(0b01101010), /*lsb*/2, /*width*/4) + == static_cast<int8_t>(0b11111010), + "TestBitFieldExtract#21"); + +static_assert(BitFieldInsert(0xFF, /*data*/0x0, /*lsb*/0, /*width*/0) == 0xFF, + "TestBitFieldInsert#1"); +static_assert(BitFieldInsert(std::numeric_limits<uint32_t>::max(), + /*data*/std::numeric_limits<uint32_t>::max(), + /*lsb*/0, + /*width*/32) + == std::numeric_limits<uint32_t>::max(), + "TestBitFieldInsert#2"); +static_assert(BitFieldInsert(std::numeric_limits<int32_t>::max(), + /*data*/std::numeric_limits<uint32_t>::max(), + /*lsb*/0, + /*width*/32) + == std::numeric_limits<uint32_t>::max(), + "TestBitFieldInsert#3"); +static_assert(BitFieldInsert(0u, + /*data*/std::numeric_limits<uint32_t>::max(), + /*lsb*/0, + /*width*/32) + == std::numeric_limits<uint32_t>::max(), + "TestBitFieldInsert#4"); +static_assert(BitFieldInsert(-(-0), + /*data*/std::numeric_limits<uint32_t>::max(), + /*lsb*/0, + /*width*/32) + == std::numeric_limits<uint32_t>::max(), + "TestBitFieldInsert#5"); +static_assert(BitFieldInsert(0x00, /*data*/0b11u, /*lsb*/0, /*width*/2) == 0b00000011, + "TestBitFieldInsert#6"); +static_assert(BitFieldInsert(0x00, /*data*/0b111u, /*lsb*/0, /*width*/3) == 0b00000111, + "TestBitFieldInsert#7"); +static_assert(BitFieldInsert(0x00, /*data*/0b111u, /*lsb*/1, /*width*/3) == 0b00001110, + "TestBitFieldInsert#8"); +static_assert(BitFieldInsert(0x00, /*data*/0b111u, /*lsb*/2, /*width*/3) == 0b00011100, + "TestBitFieldInsert#9"); +static_assert(BitFieldInsert(0b01011100, /*data*/0b1101u, /*lsb*/4, /*width*/4) == 0b11011100, + "TestBitFieldInsert#10"); + template <typename Container> void CheckElements(const std::initializer_list<uint32_t>& expected, const Container& elements) { auto expected_it = expected.begin(); diff --git a/runtime/base/file_magic.cc b/runtime/base/file_magic.cc index 568a7ae5d6..30b4f0559d 100644 --- a/runtime/base/file_magic.cc +++ b/runtime/base/file_magic.cc @@ -55,8 +55,4 @@ bool IsZipMagic(uint32_t magic) { ('K' == ((magic >> 8) & 0xff))); } -bool IsDexMagic(uint32_t magic) { - return DexFile::IsMagicValid(reinterpret_cast<const uint8_t*>(&magic)); -} - } // namespace art diff --git a/runtime/base/file_magic.h b/runtime/base/file_magic.h index 4b5d2f5a48..1c9effdb50 100644 --- a/runtime/base/file_magic.h +++ b/runtime/base/file_magic.h @@ -29,7 +29,6 @@ File OpenAndReadMagic(const char* filename, uint32_t* magic, std::string* error_ // Check whether the given magic matches a known file type. bool IsZipMagic(uint32_t magic); -bool IsDexMagic(uint32_t magic); } // namespace art diff --git a/runtime/base/variant_map.h b/runtime/base/variant_map.h index d87df8710c..71a1018dea 100644 --- a/runtime/base/variant_map.h +++ b/runtime/base/variant_map.h @@ -237,6 +237,14 @@ struct VariantMap { return (ptr == nullptr) ? key.CreateDefaultValue() : *ptr; } + template <typename T, typename U> + void AssignIfExists(const TKey<T>& key, U* out) { + DCHECK(out != nullptr); + if (Exists(key)) { + *out = std::move(*Get(key)); + } + } + private: // TODO: move to detail, or make it more generic like a ScopeGuard(function) template <typename TValue> diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 8999e17cf1..fe91272ef7 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -51,6 +51,7 @@ #include "compiler_callbacks.h" #include "debugger.h" #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "entrypoints/entrypoint_utils.h" #include "entrypoints/runtime_asm_entrypoints.h" #include "experimental_flags.h" @@ -2866,6 +2867,11 @@ bool ClassLinker::ShouldUseInterpreterEntrypoint(ArtMethod* method, const void* return true; } + if (Thread::Current()->IsAsyncExceptionPending()) { + // Force use of interpreter to handle async-exceptions + return true; + } + if (runtime->IsJavaDebuggable()) { // For simplicity, we ignore precompiled code and go to the interpreter // assuming we don't already have jitted code. @@ -8709,10 +8715,11 @@ class GetResolvedClassesVisitor : public ClassVisitor { const DexFile& dex_file = klass->GetDexFile(); if (&dex_file != last_dex_file_) { last_dex_file_ = &dex_file; - DexCacheResolvedClasses resolved_classes(dex_file.GetLocation(), - dex_file.GetBaseLocation(), - dex_file.GetLocationChecksum(), - dex_file.NumMethodIds()); + DexCacheResolvedClasses resolved_classes( + dex_file.GetLocation(), + DexFileLoader::GetBaseLocation(dex_file.GetLocation()), + dex_file.GetLocationChecksum(), + dex_file.NumMethodIds()); last_resolved_classes_ = result_->find(resolved_classes); if (last_resolved_classes_ == result_->end()) { last_resolved_classes_ = result_->insert(resolved_classes).first; diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc index f887b8ed42..3d9fd59e0b 100644 --- a/runtime/class_linker_test.cc +++ b/runtime/class_linker_test.cc @@ -26,7 +26,7 @@ #include "base/enums.h" #include "class_linker-inl.h" #include "common_runtime_test.h" -#include "dex_file.h" +#include "native_dex_file.h" #include "dex_file_types.h" #include "entrypoints/entrypoint_utils-inl.h" #include "experimental_flags.h" @@ -1462,11 +1462,11 @@ TEST_F(ClassLinkerTest, RegisterDexFileName) { dex_cache->SetLocation(location.Get()); const DexFile* old_dex_file = dex_cache->GetDexFile(); - std::unique_ptr<DexFile> dex_file(new DexFile(old_dex_file->Begin(), - old_dex_file->Size(), - location->ToModifiedUtf8(), - 0u, - nullptr)); + std::unique_ptr<DexFile> dex_file(new NativeDexFile(old_dex_file->Begin(), + old_dex_file->Size(), + location->ToModifiedUtf8(), + 0u, + nullptr)); { WriterMutexLock mu(soa.Self(), *Locks::dex_lock_); // Check that inserting with a UTF16 name works. diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc index 2282da048f..167533d68a 100644 --- a/runtime/class_loader_context.cc +++ b/runtime/class_loader_context.cc @@ -25,6 +25,7 @@ #include "class_linker.h" #include "class_loader_utils.h" #include "dex_file.h" +#include "dex_file_loader.h" #include "handle_scope-inl.h" #include "jni_internal.h" #include "oat_file_assistant.h" @@ -227,11 +228,11 @@ bool ClassLoaderContext::OpenDexFiles(InstructionSet isa, const std::string& cla std::string error_msg; // When opening the dex files from the context we expect their checksum to match their // contents. So pass true to verify_checksum. - if (!DexFile::Open(location.c_str(), - location.c_str(), - /*verify_checksum*/ true, - &error_msg, - &info.opened_dex_files)) { + if (!DexFileLoader::Open(location.c_str(), + location.c_str(), + /*verify_checksum*/ true, + &error_msg, + &info.opened_dex_files)) { // If we fail to open the dex file because it's been stripped, try to open the dex file // from its corresponding oat file. // This could happen when we need to recompile a pre-build whose dex code has been stripped. @@ -282,7 +283,7 @@ bool ClassLoaderContext::RemoveLocationsFromClassPaths( std::set<std::string> canonical_locations; for (const std::string& location : locations) { - canonical_locations.insert(DexFile::GetDexCanonicalLocation(location.c_str())); + canonical_locations.insert(DexFileLoader::GetDexCanonicalLocation(location.c_str())); } bool removed_locations = false; for (ClassLoaderInfo& info : class_loader_chain_) { @@ -292,7 +293,7 @@ bool ClassLoaderContext::RemoveLocationsFromClassPaths( info.classpath.end(), [canonical_locations](const std::string& location) { return ContainsElement(canonical_locations, - DexFile::GetDexCanonicalLocation(location.c_str())); + DexFileLoader::GetDexCanonicalLocation(location.c_str())); }); info.classpath.erase(kept_it, info.classpath.end()); if (initial_size != info.classpath.size()) { @@ -340,7 +341,8 @@ std::string ClassLoaderContext::EncodeContext(const std::string& base_dir, if (for_dex2oat) { // dex2oat only needs the base location. It cannot accept multidex locations. // So ensure we only add each file once. - bool new_insert = seen_locations.insert(dex_file->GetBaseLocation()).second; + bool new_insert = seen_locations.insert( + DexFileLoader::GetBaseLocation(dex_file->GetLocation())).second; if (!new_insert) { continue; } diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc index ae3dcecb4a..be6acde4a9 100644 --- a/runtime/class_loader_context_test.cc +++ b/runtime/class_loader_context_test.cc @@ -100,12 +100,13 @@ class ClassLoaderContextTest : public CommonRuntimeTest { info.opened_dex_files[cur_open_dex_index++]; std::unique_ptr<const DexFile>& expected_dex_file = (*all_dex_files)[k]; - std::string expected_location = expected_dex_file->GetBaseLocation(); + std::string expected_location = + DexFileLoader::GetBaseLocation(expected_dex_file->GetLocation()); UniqueCPtr<const char[]> expected_real_location( realpath(expected_location.c_str(), nullptr)); ASSERT_TRUE(expected_real_location != nullptr) << expected_location; expected_location.assign(expected_real_location.get()); - expected_location += DexFile::GetMultiDexSuffix(expected_dex_file->GetLocation()); + expected_location += DexFileLoader::GetMultiDexSuffix(expected_dex_file->GetLocation()); ASSERT_EQ(expected_location, opened_dex_file->GetLocation()); ASSERT_EQ(expected_dex_file->GetLocationChecksum(), opened_dex_file->GetLocationChecksum()); diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index 29b376a21c..0c2e49010e 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -34,6 +34,7 @@ #include "class_linker.h" #include "compiler_callbacks.h" #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "gc/heap.h" #include "gc_root-inl.h" #include "gtest/gtest.h" @@ -372,7 +373,7 @@ std::unique_ptr<const DexFile> CommonRuntimeTestImpl::LoadExpectSingleDexFile( std::string error_msg; MemMap::Init(); static constexpr bool kVerifyChecksum = true; - if (!DexFile::Open(location, location, kVerifyChecksum, &error_msg, &dex_files)) { + if (!DexFileLoader::Open(location, location, kVerifyChecksum, &error_msg, &dex_files)) { LOG(FATAL) << "Could not open .dex file '" << location << "': " << error_msg << "\n"; UNREACHABLE(); } else { @@ -571,7 +572,7 @@ std::vector<std::unique_ptr<const DexFile>> CommonRuntimeTestImpl::OpenTestDexFi static constexpr bool kVerifyChecksum = true; std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files; - bool success = DexFile::Open( + bool success = DexFileLoader::Open( filename.c_str(), filename.c_str(), kVerifyChecksum, &error_msg, &dex_files); CHECK(success) << "Failed to open '" << filename << "': " << error_msg; for (auto& dex_file : dex_files) { diff --git a/runtime/dex2oat_environment_test.h b/runtime/dex2oat_environment_test.h index 93daa45519..a9bb95480e 100644 --- a/runtime/dex2oat_environment_test.h +++ b/runtime/dex2oat_environment_test.h @@ -26,6 +26,7 @@ #include "base/stl_util.h" #include "common_runtime_test.h" #include "compiler_callbacks.h" +#include "dex_file_loader.h" #include "exec_utils.h" #include "gc/heap.h" #include "gc/space/image_space.h" @@ -71,7 +72,8 @@ class Dex2oatEnvironmentTest : public CommonRuntimeTest { << "Expected dex file to be at: " << GetDexSrc1(); ASSERT_TRUE(OS::FileExists(GetStrippedDexSrc1().c_str())) << "Expected stripped dex file to be at: " << GetStrippedDexSrc1(); - ASSERT_FALSE(DexFile::GetMultiDexChecksums(GetStrippedDexSrc1().c_str(), &checksums, &error_msg)) + ASSERT_FALSE( + DexFileLoader::GetMultiDexChecksums(GetStrippedDexSrc1().c_str(), &checksums, &error_msg)) << "Expected stripped dex file to be stripped: " << GetStrippedDexSrc1(); ASSERT_TRUE(OS::FileExists(GetDexSrc2().c_str())) << "Expected dex file to be at: " << GetDexSrc2(); @@ -80,13 +82,19 @@ class Dex2oatEnvironmentTest : public CommonRuntimeTest { // GetMultiDexSrc1, but a different secondary dex checksum. static constexpr bool kVerifyChecksum = true; std::vector<std::unique_ptr<const DexFile>> multi1; - ASSERT_TRUE(DexFile::Open(GetMultiDexSrc1().c_str(), - GetMultiDexSrc1().c_str(), kVerifyChecksum, &error_msg, &multi1)) << error_msg; + ASSERT_TRUE(DexFileLoader::Open(GetMultiDexSrc1().c_str(), + GetMultiDexSrc1().c_str(), + kVerifyChecksum, + &error_msg, + &multi1)) << error_msg; ASSERT_GT(multi1.size(), 1u); std::vector<std::unique_ptr<const DexFile>> multi2; - ASSERT_TRUE(DexFile::Open(GetMultiDexSrc2().c_str(), - GetMultiDexSrc2().c_str(), kVerifyChecksum, &error_msg, &multi2)) << error_msg; + ASSERT_TRUE(DexFileLoader::Open(GetMultiDexSrc2().c_str(), + GetMultiDexSrc2().c_str(), + kVerifyChecksum, + &error_msg, + &multi2)) << error_msg; ASSERT_GT(multi2.size(), 1u); ASSERT_EQ(multi1[0]->GetLocationChecksum(), multi2[0]->GetLocationChecksum()); diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc index 2e776b0e61..f6b3428208 100644 --- a/runtime/dex_file.cc +++ b/runtime/dex_file.cc @@ -23,7 +23,6 @@ #include <string.h> #include <sys/file.h> #include <sys/mman.h> // For the PROT_* and MAP_* constants. -#include <sys/stat.h> #include <zlib.h> #include <memory> @@ -33,19 +32,17 @@ #include "android-base/stringprintf.h" #include "base/enums.h" -#include "base/file_magic.h" #include "base/logging.h" #include "base/stl_util.h" -#include "base/systrace.h" -#include "base/unix_file/fd_file.h" #include "dex_file-inl.h" -#include "dex_file_verifier.h" +#include "dex_file_loader.h" #include "jvalue.h" #include "leb128.h" +#include "mem_map.h" +#include "native_dex_file.h" #include "os.h" #include "utf-inl.h" #include "utils.h" -#include "zip_archive.h" namespace art { @@ -56,22 +53,6 @@ static_assert(std::is_trivially_copyable<dex::StringIndex>::value, "StringIndex static_assert(sizeof(dex::TypeIndex) == sizeof(uint16_t), "TypeIndex size is wrong"); static_assert(std::is_trivially_copyable<dex::TypeIndex>::value, "TypeIndex not trivial"); -static constexpr OatDexFile* kNoOatDexFile = nullptr; - -const char* DexFile::kClassesDex = "classes.dex"; - -const uint8_t DexFile::kDexMagic[] = { 'd', 'e', 'x', '\n' }; -const uint8_t DexFile::kDexMagicVersions[DexFile::kNumDexVersions][DexFile::kDexVersionLen] = { - {'0', '3', '5', '\0'}, - // Dex version 036 skipped because of an old dalvik bug on some versions of android where dex - // files with that version number would erroneously be accepted and run. - {'0', '3', '7', '\0'}, - // Dex version 038: Android "O". - {'0', '3', '8', '\0'}, - // Dex verion 039: Beyond Android "O". - {'0', '3', '9', '\0'}, -}; - uint32_t DexFile::CalculateChecksum() const { const uint32_t non_sum = OFFSETOF_MEMBER(DexFile::Header, signature_); const uint8_t* non_sum_ptr = Begin() + non_sum; @@ -83,55 +64,6 @@ struct DexFile::AnnotationValue { uint8_t type_; }; -bool DexFile::GetMultiDexChecksums(const char* filename, - std::vector<uint32_t>* checksums, - std::string* error_msg) { - CHECK(checksums != nullptr); - uint32_t magic; - - File fd = OpenAndReadMagic(filename, &magic, error_msg); - if (fd.Fd() == -1) { - DCHECK(!error_msg->empty()); - return false; - } - if (IsZipMagic(magic)) { - std::unique_ptr<ZipArchive> zip_archive( - ZipArchive::OpenFromFd(fd.Release(), filename, error_msg)); - if (zip_archive.get() == nullptr) { - *error_msg = StringPrintf("Failed to open zip archive '%s' (error msg: %s)", filename, - error_msg->c_str()); - return false; - } - - uint32_t i = 0; - std::string zip_entry_name = GetMultiDexClassesDexName(i++); - std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(zip_entry_name.c_str(), error_msg)); - if (zip_entry.get() == nullptr) { - *error_msg = StringPrintf("Zip archive '%s' doesn't contain %s (error msg: %s)", filename, - zip_entry_name.c_str(), error_msg->c_str()); - return false; - } - - do { - checksums->push_back(zip_entry->GetCrc32()); - zip_entry_name = DexFile::GetMultiDexClassesDexName(i++); - zip_entry.reset(zip_archive->Find(zip_entry_name.c_str(), error_msg)); - } while (zip_entry.get() != nullptr); - return true; - } - if (IsDexMagic(magic)) { - std::unique_ptr<const DexFile> dex_file( - DexFile::OpenFile(fd.Release(), filename, false, false, error_msg)); - if (dex_file.get() == nullptr) { - return false; - } - checksums->push_back(dex_file->GetHeader().checksum_); - return true; - } - *error_msg = StringPrintf("Expected valid zip or dex file: '%s'", filename); - return false; -} - int DexFile::GetPermissions() const { if (mem_map_.get() == nullptr) { return 0; @@ -162,367 +94,6 @@ bool DexFile::DisableWrite() const { } } - -std::unique_ptr<const DexFile> DexFile::Open(const uint8_t* base, - size_t size, - const std::string& location, - uint32_t location_checksum, - const OatDexFile* oat_dex_file, - bool verify, - bool verify_checksum, - std::string* error_msg) { - ScopedTrace trace(std::string("Open dex file from RAM ") + location); - return OpenCommon(base, - size, - location, - location_checksum, - oat_dex_file, - verify, - verify_checksum, - error_msg); -} - -std::unique_ptr<const DexFile> DexFile::Open(const std::string& location, - uint32_t location_checksum, - std::unique_ptr<MemMap> map, - bool verify, - bool verify_checksum, - std::string* error_msg) { - ScopedTrace trace(std::string("Open dex file from mapped-memory ") + location); - CHECK(map.get() != nullptr); - - if (map->Size() < sizeof(DexFile::Header)) { - *error_msg = StringPrintf( - "DexFile: failed to open dex file '%s' that is too short to have a header", - location.c_str()); - return nullptr; - } - - std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(), - map->Size(), - location, - location_checksum, - kNoOatDexFile, - verify, - verify_checksum, - error_msg); - if (dex_file != nullptr) { - dex_file->mem_map_ = std::move(map); - } - return dex_file; -} - -bool DexFile::Open(const char* filename, - const std::string& location, - bool verify_checksum, - std::string* error_msg, - std::vector<std::unique_ptr<const DexFile>>* dex_files) { - ScopedTrace trace(std::string("Open dex file ") + std::string(location)); - DCHECK(dex_files != nullptr) << "DexFile::Open: out-param is nullptr"; - uint32_t magic; - File fd = OpenAndReadMagic(filename, &magic, error_msg); - if (fd.Fd() == -1) { - DCHECK(!error_msg->empty()); - return false; - } - if (IsZipMagic(magic)) { - return DexFile::OpenZip(fd.Release(), location, verify_checksum, error_msg, dex_files); - } - if (IsDexMagic(magic)) { - std::unique_ptr<const DexFile> dex_file(DexFile::OpenFile(fd.Release(), - location, - /* verify */ true, - verify_checksum, - error_msg)); - if (dex_file.get() != nullptr) { - dex_files->push_back(std::move(dex_file)); - return true; - } else { - return false; - } - } - *error_msg = StringPrintf("Expected valid zip or dex file: '%s'", filename); - return false; -} - -std::unique_ptr<const DexFile> DexFile::OpenDex(int fd, - const std::string& location, - bool verify_checksum, - std::string* error_msg) { - ScopedTrace trace("Open dex file " + std::string(location)); - return OpenFile(fd, location, true /* verify */, verify_checksum, error_msg); -} - -bool DexFile::OpenZip(int fd, - const std::string& location, - bool verify_checksum, - std::string* error_msg, - std::vector<std::unique_ptr<const DexFile>>* dex_files) { - ScopedTrace trace("Dex file open Zip " + std::string(location)); - DCHECK(dex_files != nullptr) << "DexFile::OpenZip: out-param is nullptr"; - std::unique_ptr<ZipArchive> zip_archive(ZipArchive::OpenFromFd(fd, location.c_str(), error_msg)); - if (zip_archive.get() == nullptr) { - DCHECK(!error_msg->empty()); - return false; - } - return DexFile::OpenAllDexFilesFromZip(*zip_archive, - location, - verify_checksum, - error_msg, - dex_files); -} - -std::unique_ptr<const DexFile> DexFile::OpenFile(int fd, - const std::string& location, - bool verify, - bool verify_checksum, - std::string* error_msg) { - ScopedTrace trace(std::string("Open dex file ") + std::string(location)); - CHECK(!location.empty()); - std::unique_ptr<MemMap> map; - { - File delayed_close(fd, /* check_usage */ false); - struct stat sbuf; - memset(&sbuf, 0, sizeof(sbuf)); - if (fstat(fd, &sbuf) == -1) { - *error_msg = StringPrintf("DexFile: fstat '%s' failed: %s", location.c_str(), - strerror(errno)); - return nullptr; - } - if (S_ISDIR(sbuf.st_mode)) { - *error_msg = StringPrintf("Attempt to mmap directory '%s'", location.c_str()); - return nullptr; - } - size_t length = sbuf.st_size; - map.reset(MemMap::MapFile(length, - PROT_READ, - MAP_PRIVATE, - fd, - 0, - /*low_4gb*/false, - location.c_str(), - error_msg)); - if (map == nullptr) { - DCHECK(!error_msg->empty()); - return nullptr; - } - } - - if (map->Size() < sizeof(DexFile::Header)) { - *error_msg = StringPrintf( - "DexFile: failed to open dex file '%s' that is too short to have a header", - location.c_str()); - return nullptr; - } - - const Header* dex_header = reinterpret_cast<const Header*>(map->Begin()); - - std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(), - map->Size(), - location, - dex_header->checksum_, - kNoOatDexFile, - verify, - verify_checksum, - error_msg); - if (dex_file != nullptr) { - dex_file->mem_map_ = std::move(map); - } - - return dex_file; -} - -std::unique_ptr<const DexFile> DexFile::OpenOneDexFileFromZip(const ZipArchive& zip_archive, - const char* entry_name, - const std::string& location, - bool verify_checksum, - std::string* error_msg, - ZipOpenErrorCode* error_code) { - ScopedTrace trace("Dex file open from Zip Archive " + std::string(location)); - CHECK(!location.empty()); - std::unique_ptr<ZipEntry> zip_entry(zip_archive.Find(entry_name, error_msg)); - if (zip_entry == nullptr) { - *error_code = ZipOpenErrorCode::kEntryNotFound; - return nullptr; - } - if (zip_entry->GetUncompressedLength() == 0) { - *error_msg = StringPrintf("Dex file '%s' has zero length", location.c_str()); - *error_code = ZipOpenErrorCode::kDexFileError; - return nullptr; - } - - std::unique_ptr<MemMap> map; - if (zip_entry->IsUncompressed()) { - if (!zip_entry->IsAlignedTo(alignof(Header))) { - // Do not mmap unaligned ZIP entries because - // doing so would fail dex verification which requires 4 byte alignment. - LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " - << "please zipalign to " << alignof(Header) << " bytes. " - << "Falling back to extracting file."; - } else { - // Map uncompressed files within zip as file-backed to avoid a dirty copy. - map.reset(zip_entry->MapDirectlyFromFile(location.c_str(), /*out*/error_msg)); - if (map == nullptr) { - LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " - << "is your ZIP file corrupted? Falling back to extraction."; - // Try again with Extraction which still has a chance of recovery. - } - } - } - - if (map == nullptr) { - // Default path for compressed ZIP entries, - // and fallback for stored ZIP entries. - map.reset(zip_entry->ExtractToMemMap(location.c_str(), entry_name, error_msg)); - } - - if (map == nullptr) { - *error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", entry_name, location.c_str(), - error_msg->c_str()); - *error_code = ZipOpenErrorCode::kExtractToMemoryError; - return nullptr; - } - VerifyResult verify_result; - std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(), - map->Size(), - location, - zip_entry->GetCrc32(), - kNoOatDexFile, - /* verify */ true, - verify_checksum, - error_msg, - &verify_result); - if (dex_file == nullptr) { - if (verify_result == VerifyResult::kVerifyNotAttempted) { - *error_code = ZipOpenErrorCode::kDexFileError; - } else { - *error_code = ZipOpenErrorCode::kVerifyError; - } - return nullptr; - } - dex_file->mem_map_ = std::move(map); - if (!dex_file->DisableWrite()) { - *error_msg = StringPrintf("Failed to make dex file '%s' read only", location.c_str()); - *error_code = ZipOpenErrorCode::kMakeReadOnlyError; - return nullptr; - } - CHECK(dex_file->IsReadOnly()) << location; - if (verify_result != VerifyResult::kVerifySucceeded) { - *error_code = ZipOpenErrorCode::kVerifyError; - return nullptr; - } - *error_code = ZipOpenErrorCode::kNoError; - return dex_file; -} - -// Technically we do not have a limitation with respect to the number of dex files that can be in a -// multidex APK. However, it's bad practice, as each dex file requires its own tables for symbols -// (types, classes, methods, ...) and dex caches. So warn the user that we open a zip with what -// seems an excessive number. -static constexpr size_t kWarnOnManyDexFilesThreshold = 100; - -bool DexFile::OpenAllDexFilesFromZip(const ZipArchive& zip_archive, - const std::string& location, - bool verify_checksum, - std::string* error_msg, - std::vector<std::unique_ptr<const DexFile>>* dex_files) { - ScopedTrace trace("Dex file open from Zip " + std::string(location)); - DCHECK(dex_files != nullptr) << "DexFile::OpenFromZip: out-param is nullptr"; - ZipOpenErrorCode error_code; - std::unique_ptr<const DexFile> dex_file(OpenOneDexFileFromZip(zip_archive, - kClassesDex, - location, - verify_checksum, - error_msg, - &error_code)); - if (dex_file.get() == nullptr) { - return false; - } else { - // Had at least classes.dex. - dex_files->push_back(std::move(dex_file)); - - // Now try some more. - - // We could try to avoid std::string allocations by working on a char array directly. As we - // do not expect a lot of iterations, this seems too involved and brittle. - - for (size_t i = 1; ; ++i) { - std::string name = GetMultiDexClassesDexName(i); - std::string fake_location = GetMultiDexLocation(i, location.c_str()); - std::unique_ptr<const DexFile> next_dex_file(OpenOneDexFileFromZip(zip_archive, - name.c_str(), - fake_location, - verify_checksum, - error_msg, - &error_code)); - if (next_dex_file.get() == nullptr) { - if (error_code != ZipOpenErrorCode::kEntryNotFound) { - LOG(WARNING) << "Zip open failed: " << *error_msg; - } - break; - } else { - dex_files->push_back(std::move(next_dex_file)); - } - - if (i == kWarnOnManyDexFilesThreshold) { - LOG(WARNING) << location << " has in excess of " << kWarnOnManyDexFilesThreshold - << " dex files. Please consider coalescing and shrinking the number to " - " avoid runtime overhead."; - } - - if (i == std::numeric_limits<size_t>::max()) { - LOG(ERROR) << "Overflow in number of dex files!"; - break; - } - } - - return true; - } -} - -std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base, - size_t size, - const std::string& location, - uint32_t location_checksum, - const OatDexFile* oat_dex_file, - bool verify, - bool verify_checksum, - std::string* error_msg, - VerifyResult* verify_result) { - if (verify_result != nullptr) { - *verify_result = VerifyResult::kVerifyNotAttempted; - } - std::unique_ptr<DexFile> dex_file(new DexFile(base, - size, - location, - location_checksum, - oat_dex_file)); - if (dex_file == nullptr) { - *error_msg = StringPrintf("Failed to open dex file '%s' from memory: %s", location.c_str(), - error_msg->c_str()); - return nullptr; - } - if (!dex_file->Init(error_msg)) { - dex_file.reset(); - return nullptr; - } - if (verify && !DexFileVerifier::Verify(dex_file.get(), - dex_file->Begin(), - dex_file->Size(), - location.c_str(), - verify_checksum, - error_msg)) { - if (verify_result != nullptr) { - *verify_result = VerifyResult::kVerifyFailed; - } - return nullptr; - } - if (verify_result != nullptr) { - *verify_result = VerifyResult::kVerifySucceeded; - } - return dex_file; -} - DexFile::DexFile(const uint8_t* base, size_t size, const std::string& location, @@ -569,7 +140,7 @@ bool DexFile::Init(std::string* error_msg) { } bool DexFile::CheckMagicAndVersion(std::string* error_msg) const { - if (!IsMagicValid(header_->magic_)) { + if (!IsMagicValid()) { std::ostringstream oss; oss << "Unrecognized magic number in " << GetLocation() << ":" << " " << header_->magic_[0] @@ -579,7 +150,7 @@ bool DexFile::CheckMagicAndVersion(std::string* error_msg) const { *error_msg = oss.str(); return false; } - if (!IsVersionValid(header_->magic_)) { + if (!IsVersionValid()) { std::ostringstream oss; oss << "Unrecognized version number in " << GetLocation() << ":" << " " << header_->magic_[4] @@ -619,22 +190,8 @@ void DexFile::InitializeSectionsFromMapList() { } } -bool DexFile::IsMagicValid(const uint8_t* magic) { - return (memcmp(magic, kDexMagic, sizeof(kDexMagic)) == 0); -} - -bool DexFile::IsVersionValid(const uint8_t* magic) { - const uint8_t* version = &magic[sizeof(kDexMagic)]; - for (uint32_t i = 0; i < kNumDexVersions; i++) { - if (memcmp(version, kDexMagicVersions[i], kDexVersionLen) == 0) { - return true; - } - } - return false; -} - uint32_t DexFile::Header::GetVersion() const { - const char* version = reinterpret_cast<const char*>(&magic_[sizeof(kDexMagic)]); + const char* version = reinterpret_cast<const char*>(&magic_[kDexMagicSize]); return atoi(version); } @@ -1218,41 +775,6 @@ bool DexFile::LineNumForPcCb(void* raw_context, const PositionInfo& entry) { } } -bool DexFile::IsMultiDexLocation(const char* location) { - return strrchr(location, kMultiDexSeparator) != nullptr; -} - -std::string DexFile::GetMultiDexClassesDexName(size_t index) { - if (index == 0) { - return "classes.dex"; - } else { - return StringPrintf("classes%zu.dex", index + 1); - } -} - -std::string DexFile::GetMultiDexLocation(size_t index, const char* dex_location) { - if (index == 0) { - return dex_location; - } else { - return StringPrintf("%s" kMultiDexSeparatorString "classes%zu.dex", dex_location, index + 1); - } -} - -std::string DexFile::GetDexCanonicalLocation(const char* dex_location) { - CHECK_NE(dex_location, static_cast<const char*>(nullptr)); - std::string base_location = GetBaseLocation(dex_location); - const char* suffix = dex_location + base_location.size(); - DCHECK(suffix[0] == 0 || suffix[0] == kMultiDexSeparator); - UniqueCPtr<const char[]> path(realpath(base_location.c_str(), nullptr)); - if (path != nullptr && path.get() != base_location) { - return std::string(path.get()) + suffix; - } else if (suffix[0] == 0) { - return base_location; - } else { - return dex_location; - } -} - // Read a signed integer. "zwidth" is the zero-based byte count. int32_t DexFile::ReadSignedInt(const uint8_t* ptr, int zwidth) { int32_t val = 0; diff --git a/runtime/dex_file.h b/runtime/dex_file.h index 9c5fd10a36..5759684c55 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -39,34 +39,28 @@ class Signature; class StringPiece; class ZipArchive; +// Dex file is the API that exposes native dex files (ordinary dex files) and CompactDex. +// Originally, the dex file format used by ART was mostly the same as APKs. The only change was +// quickened opcodes and layout optimizations. +// Since ART needs to support both native dex files and CompactDex files, the DexFile interface +// provides an abstraction to facilitate this. class DexFile { public: + // Number of bytes in the dex file magic. + static constexpr size_t kDexMagicSize = 4; + static constexpr size_t kDexVersionLen = 4; + // First Dex format version supporting default methods. static const uint32_t kDefaultMethodsVersion = 37; // First Dex format version enforcing class definition ordering rules. static const uint32_t kClassDefinitionOrderEnforcedVersion = 37; - static const uint8_t kDexMagic[]; - static constexpr size_t kNumDexVersions = 4; - static constexpr size_t kDexVersionLen = 4; - static const uint8_t kDexMagicVersions[kNumDexVersions][kDexVersionLen]; - static constexpr size_t kSha1DigestSize = 20; static constexpr uint32_t kDexEndianConstant = 0x12345678; - // name of the DexFile entry within a zip archive - static const char* kClassesDex; - // The value of an invalid index. static const uint16_t kDexNoIndex16 = 0xFFFF; - // The separator character in MultiDex locations. - static constexpr char kMultiDexSeparator = '!'; - - // A string version of the previous. This is a define so that we can merge string literals in the - // preprocessor. - #define kMultiDexSeparatorString "!" - // Raw header_item. struct Header { uint8_t magic_[8]; @@ -433,57 +427,6 @@ class DexFile { struct AnnotationValue; - // Returns the checksums of a file for comparison with GetLocationChecksum(). - // For .dex files, this is the single header checksum. - // For zip files, this is the zip entry CRC32 checksum for classes.dex and - // each additional multidex entry classes2.dex, classes3.dex, etc. - // Return true if the checksums could be found, false otherwise. - static bool GetMultiDexChecksums(const char* filename, - std::vector<uint32_t>* checksums, - std::string* error_msg); - - // Check whether a location denotes a multidex dex file. This is a very simple check: returns - // whether the string contains the separator character. - static bool IsMultiDexLocation(const char* location); - - // Opens .dex file, backed by existing memory - static std::unique_ptr<const DexFile> Open(const uint8_t* base, - size_t size, - const std::string& location, - uint32_t location_checksum, - const OatDexFile* oat_dex_file, - bool verify, - bool verify_checksum, - std::string* error_msg); - - // Opens .dex file that has been memory-mapped by the caller. - static std::unique_ptr<const DexFile> Open(const std::string& location, - uint32_t location_checkum, - std::unique_ptr<MemMap> mem_map, - bool verify, - bool verify_checksum, - std::string* error_msg); - - // Opens all .dex files found in the file, guessing the container format based on file extension. - static bool Open(const char* filename, - const std::string& location, - bool verify_checksum, - std::string* error_msg, - std::vector<std::unique_ptr<const DexFile>>* dex_files); - - // Open a single dex file from an fd. This function closes the fd. - static std::unique_ptr<const DexFile> OpenDex(int fd, - const std::string& location, - bool verify_checksum, - std::string* error_msg); - - // Opens dex files from within a .jar, .zip, or .apk file - static bool OpenZip(int fd, - const std::string& location, - bool verify_checksum, - std::string* error_msg, - std::vector<std::unique_ptr<const DexFile>>* dex_files); - // Closes a .dex file. virtual ~DexFile(); @@ -491,38 +434,6 @@ class DexFile { return location_; } - // For normal dex files, location and base location coincide. If a dex file is part of a multidex - // archive, the base location is the name of the originating jar/apk, stripped of any internal - // classes*.dex path. - static std::string GetBaseLocation(const char* location) { - const char* pos = strrchr(location, kMultiDexSeparator); - if (pos == nullptr) { - return location; - } else { - return std::string(location, pos - location); - } - } - - static std::string GetBaseLocation(const std::string& location) { - return GetBaseLocation(location.c_str()); - } - - // Returns the '!classes*.dex' part of the dex location. Returns an empty - // string if there is no multidex suffix for the given location. - // The kMultiDexSeparator is included in the returned suffix. - static std::string GetMultiDexSuffix(const std::string& location) { - size_t pos = location.rfind(kMultiDexSeparator); - if (pos == std::string::npos) { - return ""; - } else { - return location.substr(pos); - } - } - - std::string GetBaseLocation() const { - return GetBaseLocation(location_); - } - // For DexFiles directly from .dex files, this is the checksum from the DexFile::Header. // For DexFiles opened from a zip files, this will be the ZipEntry CRC32 of classes.dex. uint32_t GetLocationChecksum() const { @@ -540,10 +451,10 @@ class DexFile { } // Returns true if the byte string points to the magic value. - static bool IsMagicValid(const uint8_t* magic); + virtual bool IsMagicValid() const = 0; // Returns true if the byte string after the magic is the correct value. - static bool IsVersionValid(const uint8_t* magic); + virtual bool IsVersionValid() const = 0; // Returns the number of string identifiers in the .dex file. size_t NumStringIds() const { @@ -733,11 +644,10 @@ class DexFile { const TypeList* GetInterfacesList(const ClassDef& class_def) const { if (class_def.interfaces_off_ == 0) { - return nullptr; - } else { - const uint8_t* addr = begin_ + class_def.interfaces_off_; - return reinterpret_cast<const TypeList*>(addr); + return nullptr; } + const uint8_t* addr = begin_ + class_def.interfaces_off_; + return reinterpret_cast<const TypeList*>(addr); } uint32_t NumMethodHandles() const { @@ -760,11 +670,7 @@ class DexFile { // Returns a pointer to the raw memory mapped class_data_item const uint8_t* GetClassData(const ClassDef& class_def) const { - if (class_def.class_data_off_ == 0) { - return nullptr; - } else { - return begin_ + class_def.class_data_off_; - } + return (class_def.class_data_off_ == 0) ? nullptr : begin_ + class_def.class_data_off_; } // @@ -772,10 +678,9 @@ class DexFile { DCHECK_LT(code_off, size_) << "Code item offset larger then maximum allowed offset"; if (code_off == 0) { return nullptr; // native or abstract method - } else { - const uint8_t* addr = begin_ + code_off; - return reinterpret_cast<const CodeItem*>(addr); } + const uint8_t* addr = begin_ + code_off; + return reinterpret_cast<const CodeItem*>(addr); } const char* GetReturnTypeDescriptor(const ProtoId& proto_id) const; @@ -820,20 +725,13 @@ class DexFile { const char* GetShorty(uint32_t proto_idx) const; const TypeList* GetProtoParameters(const ProtoId& proto_id) const { - if (proto_id.parameters_off_ == 0) { - return nullptr; - } else { - const uint8_t* addr = begin_ + proto_id.parameters_off_; - return reinterpret_cast<const TypeList*>(addr); - } + return (proto_id.parameters_off_ == 0) + ? nullptr + : reinterpret_cast<const TypeList*>(begin_ + proto_id.parameters_off_); } const uint8_t* GetEncodedStaticFieldValuesArray(const ClassDef& class_def) const { - if (class_def.static_values_off_ == 0) { - return 0; - } else { - return begin_ + class_def.static_values_off_; - } + return (class_def.static_values_off_ == 0) ? 0 : begin_ + class_def.static_values_off_; } const uint8_t* GetCallSiteEncodedValuesArray(const CallSiteIdItem& call_site_id) const { @@ -860,27 +758,18 @@ class DexFile { // Check that the offset is in bounds. // Note that although the specification says that 0 should be used if there // is no debug information, some applications incorrectly use 0xFFFFFFFF. - if (code_item->debug_info_off_ == 0 || code_item->debug_info_off_ >= size_) { - return nullptr; - } else { - return begin_ + code_item->debug_info_off_; - } + const uint32_t debug_info_off = code_item->debug_info_off_; + return (debug_info_off == 0 || debug_info_off >= size_) ? nullptr : begin_ + debug_info_off; } struct PositionInfo { - PositionInfo() - : address_(0), - line_(0), - source_file_(nullptr), - prologue_end_(false), - epilogue_begin_(false) { - } + PositionInfo() = default; - uint32_t address_; // In 16-bit code units. - uint32_t line_; // Source code line number starting at 1. - const char* source_file_; // nullptr if the file from ClassDef still applies. - bool prologue_end_; - bool epilogue_begin_; + uint32_t address_ = 0; // In 16-bit code units. + uint32_t line_ = 0; // Source code line number starting at 1. + const char* source_file_ = nullptr; // nullptr if the file from ClassDef still applies. + bool prologue_end_ = false; + bool epilogue_begin_ = false; }; // Callback for "new position table entry". @@ -888,23 +777,15 @@ class DexFile { typedef bool (*DexDebugNewPositionCb)(void* context, const PositionInfo& entry); struct LocalInfo { - LocalInfo() - : name_(nullptr), - descriptor_(nullptr), - signature_(nullptr), - start_address_(0), - end_address_(0), - reg_(0), - is_live_(false) { - } - - const char* name_; // E.g., list. It can be nullptr if unknown. - const char* descriptor_; // E.g., Ljava/util/LinkedList; - const char* signature_; // E.g., java.util.LinkedList<java.lang.Integer> - uint32_t start_address_; // PC location where the local is first defined. - uint32_t end_address_; // PC location where the local is no longer defined. - uint16_t reg_; // Dex register which stores the values. - bool is_live_; // Is the local defined and live. + LocalInfo() = default; + + const char* name_ = nullptr; // E.g., list. It can be nullptr if unknown. + const char* descriptor_ = nullptr; // E.g., Ljava/util/LinkedList; + const char* signature_ = nullptr; // E.g., java.util.LinkedList<java.lang.Integer> + uint32_t start_address_ = 0; // PC location where the local is first defined. + uint32_t end_address_ = 0; // PC location where the local is no longer defined. + uint16_t reg_ = 0; // Dex register which stores the values. + bool is_live_ = false; // Is the local defined and live. }; // Callback for "new locals table entry". @@ -913,98 +794,82 @@ class DexFile { static bool LineNumForPcCb(void* context, const PositionInfo& entry); const AnnotationsDirectoryItem* GetAnnotationsDirectory(const ClassDef& class_def) const { - if (class_def.annotations_off_ == 0) { - return nullptr; - } else { - return reinterpret_cast<const AnnotationsDirectoryItem*>(begin_ + class_def.annotations_off_); - } + return (class_def.annotations_off_ == 0) + ? nullptr + : reinterpret_cast<const AnnotationsDirectoryItem*>(begin_ + class_def.annotations_off_); } const AnnotationSetItem* GetClassAnnotationSet(const AnnotationsDirectoryItem* anno_dir) const { - if (anno_dir->class_annotations_off_ == 0) { - return nullptr; - } else { - return reinterpret_cast<const AnnotationSetItem*>(begin_ + anno_dir->class_annotations_off_); - } + return (anno_dir->class_annotations_off_ == 0) + ? nullptr + : reinterpret_cast<const AnnotationSetItem*>(begin_ + anno_dir->class_annotations_off_); } const FieldAnnotationsItem* GetFieldAnnotations(const AnnotationsDirectoryItem* anno_dir) const { - if (anno_dir->fields_size_ == 0) { - return nullptr; - } else { - return reinterpret_cast<const FieldAnnotationsItem*>(&anno_dir[1]); - } + return (anno_dir->fields_size_ == 0) + ? nullptr + : reinterpret_cast<const FieldAnnotationsItem*>(&anno_dir[1]); } const MethodAnnotationsItem* GetMethodAnnotations(const AnnotationsDirectoryItem* anno_dir) const { if (anno_dir->methods_size_ == 0) { return nullptr; - } else { - // Skip past the header and field annotations. - const uint8_t* addr = reinterpret_cast<const uint8_t*>(&anno_dir[1]); - addr += anno_dir->fields_size_ * sizeof(FieldAnnotationsItem); - return reinterpret_cast<const MethodAnnotationsItem*>(addr); } + // Skip past the header and field annotations. + const uint8_t* addr = reinterpret_cast<const uint8_t*>(&anno_dir[1]); + addr += anno_dir->fields_size_ * sizeof(FieldAnnotationsItem); + return reinterpret_cast<const MethodAnnotationsItem*>(addr); } const ParameterAnnotationsItem* GetParameterAnnotations(const AnnotationsDirectoryItem* anno_dir) const { if (anno_dir->parameters_size_ == 0) { return nullptr; - } else { - // Skip past the header, field annotations, and method annotations. - const uint8_t* addr = reinterpret_cast<const uint8_t*>(&anno_dir[1]); - addr += anno_dir->fields_size_ * sizeof(FieldAnnotationsItem); - addr += anno_dir->methods_size_ * sizeof(MethodAnnotationsItem); - return reinterpret_cast<const ParameterAnnotationsItem*>(addr); } + // Skip past the header, field annotations, and method annotations. + const uint8_t* addr = reinterpret_cast<const uint8_t*>(&anno_dir[1]); + addr += anno_dir->fields_size_ * sizeof(FieldAnnotationsItem); + addr += anno_dir->methods_size_ * sizeof(MethodAnnotationsItem); + return reinterpret_cast<const ParameterAnnotationsItem*>(addr); } const AnnotationSetItem* GetFieldAnnotationSetItem(const FieldAnnotationsItem& anno_item) const { uint32_t offset = anno_item.annotations_off_; - if (offset == 0) { - return nullptr; - } else { - return reinterpret_cast<const AnnotationSetItem*>(begin_ + offset); - } + return (offset == 0) + ? nullptr + : reinterpret_cast<const AnnotationSetItem*>(begin_ + offset); } const AnnotationSetItem* GetMethodAnnotationSetItem(const MethodAnnotationsItem& anno_item) const { uint32_t offset = anno_item.annotations_off_; - if (offset == 0) { - return nullptr; - } else { - return reinterpret_cast<const AnnotationSetItem*>(begin_ + offset); - } + return (offset == 0) + ? nullptr + : reinterpret_cast<const AnnotationSetItem*>(begin_ + offset); } const AnnotationSetRefList* GetParameterAnnotationSetRefList( const ParameterAnnotationsItem* anno_item) const { uint32_t offset = anno_item->annotations_off_; - if (offset == 0) { - return nullptr; - } - return reinterpret_cast<const AnnotationSetRefList*>(begin_ + offset); + return (offset == 0) + ? nullptr + : reinterpret_cast<const AnnotationSetRefList*>(begin_ + offset); } const AnnotationItem* GetAnnotationItem(const AnnotationSetItem* set_item, uint32_t index) const { DCHECK_LE(index, set_item->size_); uint32_t offset = set_item->entries_[index]; - if (offset == 0) { - return nullptr; - } else { - return reinterpret_cast<const AnnotationItem*>(begin_ + offset); - } + return (offset == 0) + ? nullptr + : reinterpret_cast<const AnnotationItem*>(begin_ + offset); } const AnnotationSetItem* GetSetRefItemItem(const AnnotationSetRefItem* anno_item) const { uint32_t offset = anno_item->annotations_off_; - if (offset == 0) { - return nullptr; - } - return reinterpret_cast<const AnnotationSetItem*>(begin_ + offset); + return (offset == 0) + ? nullptr + : reinterpret_cast<const AnnotationSetItem*>(begin_ + offset); } // Debug info opcodes and constants @@ -1065,29 +930,6 @@ class DexFile { return size_; } - // Return the name of the index-th classes.dex in a multidex zip file. This is classes.dex for - // index == 0, and classes{index + 1}.dex else. - static std::string GetMultiDexClassesDexName(size_t index); - - // Return the (possibly synthetic) dex location for a multidex entry. This is dex_location for - // index == 0, and dex_location + multi-dex-separator + GetMultiDexClassesDexName(index) else. - static std::string GetMultiDexLocation(size_t index, const char* dex_location); - - // Returns the canonical form of the given dex location. - // - // There are different flavors of "dex locations" as follows: - // the file name of a dex file: - // The actual file path that the dex file has on disk. - // dex_location: - // This acts as a key for the class linker to know which dex file to load. - // It may correspond to either an old odex file or a particular dex file - // inside an oat file. In the first case it will also match the file name - // of the dex file. In the second case (oat) it will include the file name - // and possibly some multidex annotation to uniquely identify it. - // canonical_dex_location: - // the dex_location where it's file name part has been made canonical. - static std::string GetDexCanonicalLocation(const char* dex_location); - const OatDexFile* GetOatDexFile() const { return oat_dex_file_; } @@ -1113,64 +955,7 @@ class DexFile { // Returns a human-readable form of the type at an index. std::string PrettyType(dex::TypeIndex type_idx) const; - private: - static std::unique_ptr<const DexFile> OpenFile(int fd, - const std::string& location, - bool verify, - bool verify_checksum, - std::string* error_msg); - - enum class ZipOpenErrorCode { // private - kNoError, - kEntryNotFound, - kExtractToMemoryError, - kDexFileError, - kMakeReadOnlyError, - kVerifyError - }; - - // Open all classesXXX.dex files from a zip archive. - static bool OpenAllDexFilesFromZip(const ZipArchive& zip_archive, - const std::string& location, - bool verify_checksum, - std::string* error_msg, - std::vector<std::unique_ptr<const DexFile>>* dex_files); - - // Opens .dex file from the entry_name in a zip archive. error_code is undefined when non-null - // return. - static std::unique_ptr<const DexFile> OpenOneDexFileFromZip(const ZipArchive& zip_archive, - const char* entry_name, - const std::string& location, - bool verify_checksum, - std::string* error_msg, - ZipOpenErrorCode* error_code); - - enum class VerifyResult { // private - kVerifyNotAttempted, - kVerifySucceeded, - kVerifyFailed - }; - - static std::unique_ptr<DexFile> OpenCommon(const uint8_t* base, - size_t size, - const std::string& location, - uint32_t location_checksum, - const OatDexFile* oat_dex_file, - bool verify, - bool verify_checksum, - std::string* error_msg, - VerifyResult* verify_result = nullptr); - - - // Opens a .dex file at the given address, optionally backed by a MemMap - static std::unique_ptr<const DexFile> OpenMemory(const uint8_t* dex_file, - size_t size, - const std::string& location, - uint32_t location_checksum, - std::unique_ptr<MemMap> mem_map, - const OatDexFile* oat_dex_file, - std::string* error_msg); - + protected: DexFile(const uint8_t* base, size_t size, const std::string& location, @@ -1241,9 +1026,9 @@ class DexFile { // null. mutable const OatDexFile* oat_dex_file_; + friend class DexFileLoader; friend class DexFileVerifierTest; friend class OatWriter; - ART_FRIEND_TEST(ClassLinkerTest, RegisterDexFileName); // for constructor }; std::ostream& operator<<(std::ostream& os, const DexFile& dex_file); @@ -1252,7 +1037,7 @@ std::ostream& operator<<(std::ostream& os, const DexFile& dex_file); class DexFileParameterIterator { public: DexFileParameterIterator(const DexFile& dex_file, const DexFile::ProtoId& proto_id) - : dex_file_(dex_file), size_(0), pos_(0) { + : dex_file_(dex_file) { type_list_ = dex_file_.GetProtoParameters(proto_id); if (type_list_ != nullptr) { size_ = type_list_->Size(); @@ -1269,9 +1054,9 @@ class DexFileParameterIterator { } private: const DexFile& dex_file_; - const DexFile::TypeList* type_list_; - uint32_t size_; - uint32_t pos_; + const DexFile::TypeList* type_list_ = nullptr; + uint32_t size_ = 0; + uint32_t pos_ = 0; DISALLOW_IMPLICIT_CONSTRUCTORS(DexFileParameterIterator); }; @@ -1298,13 +1083,12 @@ class Signature : public ValueObject { Signature(const DexFile* dex, const DexFile::ProtoId& proto) : dex_file_(dex), proto_id_(&proto) { } - Signature() : dex_file_(nullptr), proto_id_(nullptr) { - } + Signature() = default; friend class DexFile; - const DexFile* const dex_file_; - const DexFile::ProtoId* const proto_id_; + const DexFile* const dex_file_ = nullptr; + const DexFile::ProtoId* const proto_id_ = nullptr; }; std::ostream& operator<<(std::ostream& os, const Signature& sig); @@ -1583,44 +1367,44 @@ class CallSiteArrayValueIterator : public EncodedArrayValueIterator { std::ostream& operator<<(std::ostream& os, const CallSiteArrayValueIterator::ValueType& code); class CatchHandlerIterator { - public: - CatchHandlerIterator(const DexFile::CodeItem& code_item, uint32_t address); + public: + CatchHandlerIterator(const DexFile::CodeItem& code_item, uint32_t address); - CatchHandlerIterator(const DexFile::CodeItem& code_item, - const DexFile::TryItem& try_item); + CatchHandlerIterator(const DexFile::CodeItem& code_item, + const DexFile::TryItem& try_item); - explicit CatchHandlerIterator(const uint8_t* handler_data) { - Init(handler_data); - } + explicit CatchHandlerIterator(const uint8_t* handler_data) { + Init(handler_data); + } - dex::TypeIndex GetHandlerTypeIndex() const { - return handler_.type_idx_; - } - uint32_t GetHandlerAddress() const { - return handler_.address_; - } - void Next(); - bool HasNext() const { - return remaining_count_ != -1 || catch_all_; - } - // End of this set of catch blocks, convenience method to locate next set of catch blocks - const uint8_t* EndDataPointer() const { - CHECK(!HasNext()); - return current_data_; - } + dex::TypeIndex GetHandlerTypeIndex() const { + return handler_.type_idx_; + } + uint32_t GetHandlerAddress() const { + return handler_.address_; + } + void Next(); + bool HasNext() const { + return remaining_count_ != -1 || catch_all_; + } + // End of this set of catch blocks, convenience method to locate next set of catch blocks + const uint8_t* EndDataPointer() const { + CHECK(!HasNext()); + return current_data_; + } - private: - void Init(const DexFile::CodeItem& code_item, int32_t offset); - void Init(const uint8_t* handler_data); - - struct CatchHandlerItem { - dex::TypeIndex type_idx_; // type index of the caught exception type - uint32_t address_; // handler address - } handler_; - const uint8_t* current_data_; // the current handler in dex file. - int32_t remaining_count_; // number of handlers not read. - bool catch_all_; // is there a handler that will catch all exceptions in case - // that all typed handler does not match. + private: + void Init(const DexFile::CodeItem& code_item, int32_t offset); + void Init(const uint8_t* handler_data); + + struct CatchHandlerItem { + dex::TypeIndex type_idx_; // type index of the caught exception type + uint32_t address_; // handler address + } handler_; + const uint8_t* current_data_; // the current handler in dex file. + int32_t remaining_count_; // number of handlers not read. + bool catch_all_; // is there a handler that will catch all exceptions in case + // that all typed handler does not match. }; } // namespace art diff --git a/runtime/dex_file_loader.cc b/runtime/dex_file_loader.cc new file mode 100644 index 0000000000..3ccb755f58 --- /dev/null +++ b/runtime/dex_file_loader.cc @@ -0,0 +1,484 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dex_file_loader.h" + +#include <sys/mman.h> // For the PROT_* and MAP_* constants. +#include <sys/stat.h> + +#include "android-base/stringprintf.h" + +#include "base/file_magic.h" +#include "base/stl_util.h" +#include "base/systrace.h" +#include "base/unix_file/fd_file.h" +#include "dex_file.h" +#include "dex_file_verifier.h" +#include "native_dex_file.h" +#include "zip_archive.h" + +namespace art { + +using android::base::StringPrintf; + +static constexpr OatDexFile* kNoOatDexFile = nullptr; + + +bool DexFileLoader::IsValidMagic(uint32_t magic) { + return IsValidMagic(reinterpret_cast<uint8_t*>(&magic)); +} + +bool DexFileLoader::IsValidMagic(const uint8_t* magic) { + return NativeDexFile::IsMagicValid(magic); +} + +bool DexFileLoader::GetMultiDexChecksums(const char* filename, + std::vector<uint32_t>* checksums, + std::string* error_msg) { + CHECK(checksums != nullptr); + uint32_t magic; + + File fd = OpenAndReadMagic(filename, &magic, error_msg); + if (fd.Fd() == -1) { + DCHECK(!error_msg->empty()); + return false; + } + if (IsZipMagic(magic)) { + std::unique_ptr<ZipArchive> zip_archive( + ZipArchive::OpenFromFd(fd.Release(), filename, error_msg)); + if (zip_archive.get() == nullptr) { + *error_msg = StringPrintf("Failed to open zip archive '%s' (error msg: %s)", filename, + error_msg->c_str()); + return false; + } + + uint32_t i = 0; + std::string zip_entry_name = GetMultiDexClassesDexName(i++); + std::unique_ptr<ZipEntry> zip_entry(zip_archive->Find(zip_entry_name.c_str(), error_msg)); + if (zip_entry.get() == nullptr) { + *error_msg = StringPrintf("Zip archive '%s' doesn't contain %s (error msg: %s)", filename, + zip_entry_name.c_str(), error_msg->c_str()); + return false; + } + + do { + checksums->push_back(zip_entry->GetCrc32()); + zip_entry_name = GetMultiDexClassesDexName(i++); + zip_entry.reset(zip_archive->Find(zip_entry_name.c_str(), error_msg)); + } while (zip_entry.get() != nullptr); + return true; + } + if (IsValidMagic(magic)) { + std::unique_ptr<const DexFile> dex_file( + OpenFile(fd.Release(), filename, false, false, error_msg)); + if (dex_file == nullptr) { + return false; + } + checksums->push_back(dex_file->GetHeader().checksum_); + return true; + } + *error_msg = StringPrintf("Expected valid zip or dex file: '%s'", filename); + return false; +} + +bool DexFileLoader::IsMultiDexLocation(const char* location) { + return strrchr(location, kMultiDexSeparator) != nullptr; +} + +std::string DexFileLoader::GetMultiDexClassesDexName(size_t index) { + return (index == 0) ? "classes.dex" : StringPrintf("classes%zu.dex", index + 1); +} + +std::string DexFileLoader::GetMultiDexLocation(size_t index, const char* dex_location) { + return (index == 0) + ? dex_location + : StringPrintf("%s%cclasses%zu.dex", dex_location, kMultiDexSeparator, index + 1); +} + +std::string DexFileLoader::GetDexCanonicalLocation(const char* dex_location) { + CHECK_NE(dex_location, static_cast<const char*>(nullptr)); + std::string base_location = GetBaseLocation(dex_location); + const char* suffix = dex_location + base_location.size(); + DCHECK(suffix[0] == 0 || suffix[0] == kMultiDexSeparator); + UniqueCPtr<const char[]> path(realpath(base_location.c_str(), nullptr)); + if (path != nullptr && path.get() != base_location) { + return std::string(path.get()) + suffix; + } else if (suffix[0] == 0) { + return base_location; + } else { + return dex_location; + } +} + +std::unique_ptr<const DexFile> DexFileLoader::Open(const uint8_t* base, + size_t size, + const std::string& location, + uint32_t location_checksum, + const OatDexFile* oat_dex_file, + bool verify, + bool verify_checksum, + std::string* error_msg) { + ScopedTrace trace(std::string("Open dex file from RAM ") + location); + return OpenCommon(base, + size, + location, + location_checksum, + oat_dex_file, + verify, + verify_checksum, + error_msg); +} + +std::unique_ptr<const DexFile> DexFileLoader::Open(const std::string& location, + uint32_t location_checksum, + std::unique_ptr<MemMap> map, + bool verify, + bool verify_checksum, + std::string* error_msg) { + ScopedTrace trace(std::string("Open dex file from mapped-memory ") + location); + CHECK(map.get() != nullptr); + + if (map->Size() < sizeof(DexFile::Header)) { + *error_msg = StringPrintf( + "DexFile: failed to open dex file '%s' that is too short to have a header", + location.c_str()); + return nullptr; + } + + std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(), + map->Size(), + location, + location_checksum, + kNoOatDexFile, + verify, + verify_checksum, + error_msg); + if (dex_file != nullptr) { + dex_file->mem_map_ = std::move(map); + } + return dex_file; +} + +bool DexFileLoader::Open(const char* filename, + const std::string& location, + bool verify_checksum, + std::string* error_msg, + std::vector<std::unique_ptr<const DexFile>>* dex_files) { + ScopedTrace trace(std::string("Open dex file ") + std::string(location)); + DCHECK(dex_files != nullptr) << "DexFile::Open: out-param is nullptr"; + uint32_t magic; + File fd = OpenAndReadMagic(filename, &magic, error_msg); + if (fd.Fd() == -1) { + DCHECK(!error_msg->empty()); + return false; + } + if (IsZipMagic(magic)) { + return OpenZip(fd.Release(), location, verify_checksum, error_msg, dex_files); + } + if (IsValidMagic(magic)) { + std::unique_ptr<const DexFile> dex_file(OpenFile(fd.Release(), + location, + /* verify */ true, + verify_checksum, + error_msg)); + if (dex_file.get() != nullptr) { + dex_files->push_back(std::move(dex_file)); + return true; + } else { + return false; + } + } + *error_msg = StringPrintf("Expected valid zip or dex file: '%s'", filename); + return false; +} + +std::unique_ptr<const DexFile> DexFileLoader::OpenDex(int fd, + const std::string& location, + bool verify_checksum, + std::string* error_msg) { + ScopedTrace trace("Open dex file " + std::string(location)); + return OpenFile(fd, location, true /* verify */, verify_checksum, error_msg); +} + +bool DexFileLoader::OpenZip(int fd, + const std::string& location, + bool verify_checksum, + std::string* error_msg, + std::vector<std::unique_ptr<const DexFile>>* dex_files) { + ScopedTrace trace("Dex file open Zip " + std::string(location)); + DCHECK(dex_files != nullptr) << "DexFile::OpenZip: out-param is nullptr"; + std::unique_ptr<ZipArchive> zip_archive(ZipArchive::OpenFromFd(fd, location.c_str(), error_msg)); + if (zip_archive.get() == nullptr) { + DCHECK(!error_msg->empty()); + return false; + } + return OpenAllDexFilesFromZip(*zip_archive, location, verify_checksum, error_msg, dex_files); +} + +std::unique_ptr<const DexFile> DexFileLoader::OpenFile(int fd, + const std::string& location, + bool verify, + bool verify_checksum, + std::string* error_msg) { + ScopedTrace trace(std::string("Open dex file ") + std::string(location)); + CHECK(!location.empty()); + std::unique_ptr<MemMap> map; + { + File delayed_close(fd, /* check_usage */ false); + struct stat sbuf; + memset(&sbuf, 0, sizeof(sbuf)); + if (fstat(fd, &sbuf) == -1) { + *error_msg = StringPrintf("DexFile: fstat '%s' failed: %s", location.c_str(), + strerror(errno)); + return nullptr; + } + if (S_ISDIR(sbuf.st_mode)) { + *error_msg = StringPrintf("Attempt to mmap directory '%s'", location.c_str()); + return nullptr; + } + size_t length = sbuf.st_size; + map.reset(MemMap::MapFile(length, + PROT_READ, + MAP_PRIVATE, + fd, + 0, + /*low_4gb*/false, + location.c_str(), + error_msg)); + if (map == nullptr) { + DCHECK(!error_msg->empty()); + return nullptr; + } + } + + if (map->Size() < sizeof(DexFile::Header)) { + *error_msg = StringPrintf( + "DexFile: failed to open dex file '%s' that is too short to have a header", + location.c_str()); + return nullptr; + } + + const DexFile::Header* dex_header = reinterpret_cast<const DexFile::Header*>(map->Begin()); + + std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(), + map->Size(), + location, + dex_header->checksum_, + kNoOatDexFile, + verify, + verify_checksum, + error_msg); + if (dex_file != nullptr) { + dex_file->mem_map_ = std::move(map); + } + + return dex_file; +} + +std::unique_ptr<const DexFile> DexFileLoader::OpenOneDexFileFromZip( + const ZipArchive& zip_archive, + const char* entry_name, + const std::string& location, + bool verify_checksum, + std::string* error_msg, + ZipOpenErrorCode* error_code) { + ScopedTrace trace("Dex file open from Zip Archive " + std::string(location)); + CHECK(!location.empty()); + std::unique_ptr<ZipEntry> zip_entry(zip_archive.Find(entry_name, error_msg)); + if (zip_entry == nullptr) { + *error_code = ZipOpenErrorCode::kEntryNotFound; + return nullptr; + } + if (zip_entry->GetUncompressedLength() == 0) { + *error_msg = StringPrintf("Dex file '%s' has zero length", location.c_str()); + *error_code = ZipOpenErrorCode::kDexFileError; + return nullptr; + } + + std::unique_ptr<MemMap> map; + if (zip_entry->IsUncompressed()) { + if (!zip_entry->IsAlignedTo(alignof(DexFile::Header))) { + // Do not mmap unaligned ZIP entries because + // doing so would fail dex verification which requires 4 byte alignment. + LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " + << "please zipalign to " << alignof(DexFile::Header) << " bytes. " + << "Falling back to extracting file."; + } else { + // Map uncompressed files within zip as file-backed to avoid a dirty copy. + map.reset(zip_entry->MapDirectlyFromFile(location.c_str(), /*out*/error_msg)); + if (map == nullptr) { + LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " + << "is your ZIP file corrupted? Falling back to extraction."; + // Try again with Extraction which still has a chance of recovery. + } + } + } + + if (map == nullptr) { + // Default path for compressed ZIP entries, + // and fallback for stored ZIP entries. + map.reset(zip_entry->ExtractToMemMap(location.c_str(), entry_name, error_msg)); + } + + if (map == nullptr) { + *error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", entry_name, location.c_str(), + error_msg->c_str()); + *error_code = ZipOpenErrorCode::kExtractToMemoryError; + return nullptr; + } + VerifyResult verify_result; + std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(), + map->Size(), + location, + zip_entry->GetCrc32(), + kNoOatDexFile, + /* verify */ true, + verify_checksum, + error_msg, + &verify_result); + if (dex_file == nullptr) { + if (verify_result == VerifyResult::kVerifyNotAttempted) { + *error_code = ZipOpenErrorCode::kDexFileError; + } else { + *error_code = ZipOpenErrorCode::kVerifyError; + } + return nullptr; + } + dex_file->mem_map_ = std::move(map); + if (!dex_file->DisableWrite()) { + *error_msg = StringPrintf("Failed to make dex file '%s' read only", location.c_str()); + *error_code = ZipOpenErrorCode::kMakeReadOnlyError; + return nullptr; + } + CHECK(dex_file->IsReadOnly()) << location; + if (verify_result != VerifyResult::kVerifySucceeded) { + *error_code = ZipOpenErrorCode::kVerifyError; + return nullptr; + } + *error_code = ZipOpenErrorCode::kNoError; + return dex_file; +} + +// Technically we do not have a limitation with respect to the number of dex files that can be in a +// multidex APK. However, it's bad practice, as each dex file requires its own tables for symbols +// (types, classes, methods, ...) and dex caches. So warn the user that we open a zip with what +// seems an excessive number. +static constexpr size_t kWarnOnManyDexFilesThreshold = 100; + +bool DexFileLoader::OpenAllDexFilesFromZip(const ZipArchive& zip_archive, + const std::string& location, + bool verify_checksum, + std::string* error_msg, + std::vector<std::unique_ptr<const DexFile>>* dex_files) { + ScopedTrace trace("Dex file open from Zip " + std::string(location)); + DCHECK(dex_files != nullptr) << "DexFile::OpenFromZip: out-param is nullptr"; + ZipOpenErrorCode error_code; + std::unique_ptr<const DexFile> dex_file(OpenOneDexFileFromZip(zip_archive, + kClassesDex, + location, + verify_checksum, + error_msg, + &error_code)); + if (dex_file.get() == nullptr) { + return false; + } else { + // Had at least classes.dex. + dex_files->push_back(std::move(dex_file)); + + // Now try some more. + + // We could try to avoid std::string allocations by working on a char array directly. As we + // do not expect a lot of iterations, this seems too involved and brittle. + + for (size_t i = 1; ; ++i) { + std::string name = GetMultiDexClassesDexName(i); + std::string fake_location = GetMultiDexLocation(i, location.c_str()); + std::unique_ptr<const DexFile> next_dex_file(OpenOneDexFileFromZip(zip_archive, + name.c_str(), + fake_location, + verify_checksum, + error_msg, + &error_code)); + if (next_dex_file.get() == nullptr) { + if (error_code != ZipOpenErrorCode::kEntryNotFound) { + LOG(WARNING) << "Zip open failed: " << *error_msg; + } + break; + } else { + dex_files->push_back(std::move(next_dex_file)); + } + + if (i == kWarnOnManyDexFilesThreshold) { + LOG(WARNING) << location << " has in excess of " << kWarnOnManyDexFilesThreshold + << " dex files. Please consider coalescing and shrinking the number to " + " avoid runtime overhead."; + } + + if (i == std::numeric_limits<size_t>::max()) { + LOG(ERROR) << "Overflow in number of dex files!"; + break; + } + } + + return true; + } +} + +std::unique_ptr<DexFile> DexFileLoader::OpenCommon(const uint8_t* base, + size_t size, + const std::string& location, + uint32_t location_checksum, + const OatDexFile* oat_dex_file, + bool verify, + bool verify_checksum, + std::string* error_msg, + VerifyResult* verify_result) { + if (verify_result != nullptr) { + *verify_result = VerifyResult::kVerifyNotAttempted; + } + std::unique_ptr<DexFile> dex_file; + if (NativeDexFile::IsMagicValid(base)) { + dex_file.reset(new NativeDexFile(base, size, location, location_checksum, oat_dex_file)); + } else { + return nullptr; + } + if (dex_file == nullptr) { + *error_msg = StringPrintf("Failed to open dex file '%s' from memory: %s", location.c_str(), + error_msg->c_str()); + return nullptr; + } + if (!dex_file->Init(error_msg)) { + dex_file.reset(); + return nullptr; + } + if (verify && !DexFileVerifier::Verify(dex_file.get(), + dex_file->Begin(), + dex_file->Size(), + location.c_str(), + verify_checksum, + error_msg)) { + if (verify_result != nullptr) { + *verify_result = VerifyResult::kVerifyFailed; + } + return nullptr; + } + if (verify_result != nullptr) { + *verify_result = VerifyResult::kVerifySucceeded; + } + return dex_file; +} + +} // namespace art diff --git a/runtime/dex_file_loader.h b/runtime/dex_file_loader.h new file mode 100644 index 0000000000..61b5c71726 --- /dev/null +++ b/runtime/dex_file_loader.h @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_DEX_FILE_LOADER_H_ +#define ART_RUNTIME_DEX_FILE_LOADER_H_ + +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +namespace art { + +class DexFile; +class MemMap; +class OatDexFile; +class ZipArchive; + +// Class that is used to open dex files and deal with corresponding multidex and location logic. +class DexFileLoader { + public: + // name of the DexFile entry within a zip archive + static constexpr const char* kClassesDex = "classes.dex"; + + // The separator character in MultiDex locations. + static constexpr char kMultiDexSeparator = '!'; + + // Return true if the magic is valid for dex or cdex. + static bool IsValidMagic(uint32_t magic); + static bool IsValidMagic(const uint8_t* magic); + + // Returns the checksums of a file for comparison with GetLocationChecksum(). + // For .dex files, this is the single header checksum. + // For zip files, this is the zip entry CRC32 checksum for classes.dex and + // each additional multidex entry classes2.dex, classes3.dex, etc. + // Return true if the checksums could be found, false otherwise. + static bool GetMultiDexChecksums(const char* filename, + std::vector<uint32_t>* checksums, + std::string* error_msg); + + // Check whether a location denotes a multidex dex file. This is a very simple check: returns + // whether the string contains the separator character. + static bool IsMultiDexLocation(const char* location); + + // Opens .dex file, backed by existing memory + static std::unique_ptr<const DexFile> Open(const uint8_t* base, + size_t size, + const std::string& location, + uint32_t location_checksum, + const OatDexFile* oat_dex_file, + bool verify, + bool verify_checksum, + std::string* error_msg); + + // Opens .dex file that has been memory-mapped by the caller. + static std::unique_ptr<const DexFile> Open(const std::string& location, + uint32_t location_checkum, + std::unique_ptr<MemMap> mem_map, + bool verify, + bool verify_checksum, + std::string* error_msg); + + // Opens all .dex files found in the file, guessing the container format based on file extension. + static bool Open(const char* filename, + const std::string& location, + bool verify_checksum, + std::string* error_msg, + std::vector<std::unique_ptr<const DexFile>>* dex_files); + + // Open a single dex file from an fd. This function closes the fd. + static std::unique_ptr<const DexFile> OpenDex(int fd, + const std::string& location, + bool verify_checksum, + std::string* error_msg); + + // Opens dex files from within a .jar, .zip, or .apk file + static bool OpenZip(int fd, + const std::string& location, + bool verify_checksum, + std::string* error_msg, + std::vector<std::unique_ptr<const DexFile>>* dex_files); + + // Return the name of the index-th classes.dex in a multidex zip file. This is classes.dex for + // index == 0, and classes{index + 1}.dex else. + static std::string GetMultiDexClassesDexName(size_t index); + + // Return the (possibly synthetic) dex location for a multidex entry. This is dex_location for + // index == 0, and dex_location + multi-dex-separator + GetMultiDexClassesDexName(index) else. + static std::string GetMultiDexLocation(size_t index, const char* dex_location); + + // Returns the canonical form of the given dex location. + // + // There are different flavors of "dex locations" as follows: + // the file name of a dex file: + // The actual file path that the dex file has on disk. + // dex_location: + // This acts as a key for the class linker to know which dex file to load. + // It may correspond to either an old odex file or a particular dex file + // inside an oat file. In the first case it will also match the file name + // of the dex file. In the second case (oat) it will include the file name + // and possibly some multidex annotation to uniquely identify it. + // canonical_dex_location: + // the dex_location where it's file name part has been made canonical. + static std::string GetDexCanonicalLocation(const char* dex_location); + + // For normal dex files, location and base location coincide. If a dex file is part of a multidex + // archive, the base location is the name of the originating jar/apk, stripped of any internal + // classes*.dex path. + static std::string GetBaseLocation(const char* location) { + const char* pos = strrchr(location, kMultiDexSeparator); + return (pos == nullptr) ? location : std::string(location, pos - location); + } + + static std::string GetBaseLocation(const std::string& location) { + return GetBaseLocation(location.c_str()); + } + + // Returns the '!classes*.dex' part of the dex location. Returns an empty + // string if there is no multidex suffix for the given location. + // The kMultiDexSeparator is included in the returned suffix. + static std::string GetMultiDexSuffix(const std::string& location) { + size_t pos = location.rfind(kMultiDexSeparator); + return (pos == std::string::npos) ? std::string() : location.substr(pos); + } + + private: + static std::unique_ptr<const DexFile> OpenFile(int fd, + const std::string& location, + bool verify, + bool verify_checksum, + std::string* error_msg); + + enum class ZipOpenErrorCode { + kNoError, + kEntryNotFound, + kExtractToMemoryError, + kDexFileError, + kMakeReadOnlyError, + kVerifyError + }; + + // Open all classesXXX.dex files from a zip archive. + static bool OpenAllDexFilesFromZip(const ZipArchive& zip_archive, + const std::string& location, + bool verify_checksum, + std::string* error_msg, + std::vector<std::unique_ptr<const DexFile>>* dex_files); + + // Opens .dex file from the entry_name in a zip archive. error_code is undefined when non-null + // return. + static std::unique_ptr<const DexFile> OpenOneDexFileFromZip(const ZipArchive& zip_archive, + const char* entry_name, + const std::string& location, + bool verify_checksum, + std::string* error_msg, + ZipOpenErrorCode* error_code); + + enum class VerifyResult { // private + kVerifyNotAttempted, + kVerifySucceeded, + kVerifyFailed + }; + + static std::unique_ptr<DexFile> OpenCommon(const uint8_t* base, + size_t size, + const std::string& location, + uint32_t location_checksum, + const OatDexFile* oat_dex_file, + bool verify, + bool verify_checksum, + std::string* error_msg, + VerifyResult* verify_result = nullptr); + + + // Opens a .dex file at the given address, optionally backed by a MemMap + static std::unique_ptr<const DexFile> OpenMemory(const uint8_t* dex_file, + size_t size, + const std::string& location, + uint32_t location_checksum, + std::unique_ptr<MemMap> mem_map, + const OatDexFile* oat_dex_file, + std::string* error_msg); +}; + +} // namespace art + +#endif // ART_RUNTIME_DEX_FILE_LOADER_H_ diff --git a/runtime/dex_file_test.cc b/runtime/dex_file_test.cc index 67cd42803d..b3011379c6 100644 --- a/runtime/dex_file_test.cc +++ b/runtime/dex_file_test.cc @@ -24,6 +24,7 @@ #include "base/unix_file/fd_file.h" #include "common_runtime_test.h" #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "mem_map.h" #include "os.h" #include "scoped_thread_state_change-inl.h" @@ -235,7 +236,7 @@ static bool OpenDexFilesBase64(const char* base64, ScopedObjectAccess soa(Thread::Current()); static constexpr bool kVerifyChecksum = true; std::vector<std::unique_ptr<const DexFile>> tmp; - bool success = DexFile::Open(location, location, kVerifyChecksum, error_msg, &tmp); + bool success = DexFileLoader::Open(location, location, kVerifyChecksum, error_msg, &tmp); if (success) { for (std::unique_ptr<const DexFile>& dex_file : tmp) { EXPECT_EQ(PROT_READ, dex_file->GetPermissions()); @@ -274,12 +275,12 @@ static std::unique_ptr<const DexFile> OpenDexFileInMemoryBase64(const char* base /* reuse */ false, &error_message)); memcpy(region->Begin(), dex_bytes.data(), dex_bytes.size()); - std::unique_ptr<const DexFile> dex_file(DexFile::Open(location, - location_checksum, - std::move(region), - /* verify */ true, - /* verify_checksum */ true, - &error_message)); + std::unique_ptr<const DexFile> dex_file(DexFileLoader::Open(location, + location_checksum, + std::move(region), + /* verify */ true, + /* verify_checksum */ true, + &error_message)); if (expect_success) { CHECK(dex_file != nullptr) << error_message; } else { @@ -365,7 +366,7 @@ TEST_F(DexFileTest, Version40Rejected) { static constexpr bool kVerifyChecksum = true; std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files; - ASSERT_FALSE(DexFile::Open(location, location, kVerifyChecksum, &error_msg, &dex_files)); + ASSERT_FALSE(DexFileLoader::Open(location, location, kVerifyChecksum, &error_msg, &dex_files)); } TEST_F(DexFileTest, Version41Rejected) { @@ -377,7 +378,7 @@ TEST_F(DexFileTest, Version41Rejected) { static constexpr bool kVerifyChecksum = true; std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files; - ASSERT_FALSE(DexFile::Open(location, location, kVerifyChecksum, &error_msg, &dex_files)); + ASSERT_FALSE(DexFileLoader::Open(location, location, kVerifyChecksum, &error_msg, &dex_files)); } TEST_F(DexFileTest, ZeroLengthDexRejected) { @@ -389,7 +390,7 @@ TEST_F(DexFileTest, ZeroLengthDexRejected) { static constexpr bool kVerifyChecksum = true; std::string error_msg; std::vector<std::unique_ptr<const DexFile>> dex_files; - ASSERT_FALSE(DexFile::Open(location, location, kVerifyChecksum, &error_msg, &dex_files)); + ASSERT_FALSE(DexFileLoader::Open(location, location, kVerifyChecksum, &error_msg, &dex_files)); } TEST_F(DexFileTest, GetLocationChecksum) { @@ -402,7 +403,9 @@ TEST_F(DexFileTest, GetChecksum) { std::vector<uint32_t> checksums; ScopedObjectAccess soa(Thread::Current()); std::string error_msg; - EXPECT_TRUE(DexFile::GetMultiDexChecksums(GetLibCoreDexFileNames()[0].c_str(), &checksums, &error_msg)) + EXPECT_TRUE(DexFileLoader::GetMultiDexChecksums(GetLibCoreDexFileNames()[0].c_str(), + &checksums, + &error_msg)) << error_msg; ASSERT_EQ(1U, checksums.size()); EXPECT_EQ(java_lang_dex_file_->GetLocationChecksum(), checksums[0]); @@ -412,18 +415,18 @@ TEST_F(DexFileTest, GetMultiDexChecksums) { std::string error_msg; std::vector<uint32_t> checksums; std::string multidex_file = GetTestDexFileName("MultiDex"); - EXPECT_TRUE(DexFile::GetMultiDexChecksums(multidex_file.c_str(), - &checksums, - &error_msg)) << error_msg; + EXPECT_TRUE(DexFileLoader::GetMultiDexChecksums(multidex_file.c_str(), + &checksums, + &error_msg)) << error_msg; std::vector<std::unique_ptr<const DexFile>> dexes = OpenTestDexFiles("MultiDex"); ASSERT_EQ(2U, dexes.size()); ASSERT_EQ(2U, checksums.size()); - EXPECT_EQ(dexes[0]->GetLocation(), DexFile::GetMultiDexLocation(0, multidex_file.c_str())); + EXPECT_EQ(dexes[0]->GetLocation(), DexFileLoader::GetMultiDexLocation(0, multidex_file.c_str())); EXPECT_EQ(dexes[0]->GetLocationChecksum(), checksums[0]); - EXPECT_EQ(dexes[1]->GetLocation(), DexFile::GetMultiDexLocation(1, multidex_file.c_str())); + EXPECT_EQ(dexes[1]->GetLocation(), DexFileLoader::GetMultiDexLocation(1, multidex_file.c_str())); EXPECT_EQ(dexes[1]->GetLocationChecksum(), checksums[1]); } @@ -625,20 +628,20 @@ TEST_F(DexFileTest, FindFieldId) { } TEST_F(DexFileTest, GetMultiDexClassesDexName) { - ASSERT_EQ("classes.dex", DexFile::GetMultiDexClassesDexName(0)); - ASSERT_EQ("classes2.dex", DexFile::GetMultiDexClassesDexName(1)); - ASSERT_EQ("classes3.dex", DexFile::GetMultiDexClassesDexName(2)); - ASSERT_EQ("classes100.dex", DexFile::GetMultiDexClassesDexName(99)); + ASSERT_EQ("classes.dex", DexFileLoader::GetMultiDexClassesDexName(0)); + ASSERT_EQ("classes2.dex", DexFileLoader::GetMultiDexClassesDexName(1)); + ASSERT_EQ("classes3.dex", DexFileLoader::GetMultiDexClassesDexName(2)); + ASSERT_EQ("classes100.dex", DexFileLoader::GetMultiDexClassesDexName(99)); } TEST_F(DexFileTest, GetMultiDexLocation) { std::string dex_location_str = "/system/app/framework.jar"; const char* dex_location = dex_location_str.c_str(); - ASSERT_EQ("/system/app/framework.jar", DexFile::GetMultiDexLocation(0, dex_location)); + ASSERT_EQ("/system/app/framework.jar", DexFileLoader::GetMultiDexLocation(0, dex_location)); ASSERT_EQ("/system/app/framework.jar!classes2.dex", - DexFile::GetMultiDexLocation(1, dex_location)); + DexFileLoader::GetMultiDexLocation(1, dex_location)); ASSERT_EQ("/system/app/framework.jar!classes101.dex", - DexFile::GetMultiDexLocation(100, dex_location)); + DexFileLoader::GetMultiDexLocation(100, dex_location)); } TEST_F(DexFileTest, GetDexCanonicalLocation) { @@ -646,28 +649,30 @@ TEST_F(DexFileTest, GetDexCanonicalLocation) { UniqueCPtr<const char[]> dex_location_real(realpath(file.GetFilename().c_str(), nullptr)); std::string dex_location(dex_location_real.get()); - ASSERT_EQ(dex_location, DexFile::GetDexCanonicalLocation(dex_location.c_str())); - std::string multidex_location = DexFile::GetMultiDexLocation(1, dex_location.c_str()); - ASSERT_EQ(multidex_location, DexFile::GetDexCanonicalLocation(multidex_location.c_str())); + ASSERT_EQ(dex_location, DexFileLoader::GetDexCanonicalLocation(dex_location.c_str())); + std::string multidex_location = DexFileLoader::GetMultiDexLocation(1, dex_location.c_str()); + ASSERT_EQ(multidex_location, DexFileLoader::GetDexCanonicalLocation(multidex_location.c_str())); std::string dex_location_sym = dex_location + "symlink"; ASSERT_EQ(0, symlink(dex_location.c_str(), dex_location_sym.c_str())); - ASSERT_EQ(dex_location, DexFile::GetDexCanonicalLocation(dex_location_sym.c_str())); + ASSERT_EQ(dex_location, DexFileLoader::GetDexCanonicalLocation(dex_location_sym.c_str())); - std::string multidex_location_sym = DexFile::GetMultiDexLocation(1, dex_location_sym.c_str()); - ASSERT_EQ(multidex_location, DexFile::GetDexCanonicalLocation(multidex_location_sym.c_str())); + std::string multidex_location_sym = DexFileLoader::GetMultiDexLocation( + 1, dex_location_sym.c_str()); + ASSERT_EQ(multidex_location, + DexFileLoader::GetDexCanonicalLocation(multidex_location_sym.c_str())); ASSERT_EQ(0, unlink(dex_location_sym.c_str())); } TEST(DexFileUtilsTest, GetBaseLocationAndMultiDexSuffix) { - EXPECT_EQ("/foo/bar/baz.jar", DexFile::GetBaseLocation("/foo/bar/baz.jar")); - EXPECT_EQ("/foo/bar/baz.jar", DexFile::GetBaseLocation("/foo/bar/baz.jar!classes2.dex")); - EXPECT_EQ("/foo/bar/baz.jar", DexFile::GetBaseLocation("/foo/bar/baz.jar!classes8.dex")); - EXPECT_EQ("", DexFile::GetMultiDexSuffix("/foo/bar/baz.jar")); - EXPECT_EQ("!classes2.dex", DexFile::GetMultiDexSuffix("/foo/bar/baz.jar!classes2.dex")); - EXPECT_EQ("!classes8.dex", DexFile::GetMultiDexSuffix("/foo/bar/baz.jar!classes8.dex")); + EXPECT_EQ("/foo/bar/baz.jar", DexFileLoader::GetBaseLocation("/foo/bar/baz.jar")); + EXPECT_EQ("/foo/bar/baz.jar", DexFileLoader::GetBaseLocation("/foo/bar/baz.jar!classes2.dex")); + EXPECT_EQ("/foo/bar/baz.jar", DexFileLoader::GetBaseLocation("/foo/bar/baz.jar!classes8.dex")); + EXPECT_EQ("", DexFileLoader::GetMultiDexSuffix("/foo/bar/baz.jar")); + EXPECT_EQ("!classes2.dex", DexFileLoader::GetMultiDexSuffix("/foo/bar/baz.jar!classes2.dex")); + EXPECT_EQ("!classes8.dex", DexFileLoader::GetMultiDexSuffix("/foo/bar/baz.jar!classes8.dex")); } TEST_F(DexFileTest, ZipOpenClassesPresent) { diff --git a/runtime/dex_file_verifier_test.cc b/runtime/dex_file_verifier_test.cc index 21de059797..af2d53585e 100644 --- a/runtime/dex_file_verifier_test.cc +++ b/runtime/dex_file_verifier_test.cc @@ -27,8 +27,10 @@ #include "base/unix_file/fd_file.h" #include "common_runtime_test.h" #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "dex_file_types.h" #include "leb128.h" +#include "native_dex_file.h" #include "scoped_thread_state_change-inl.h" #include "thread-current-inl.h" #include "utils.h" @@ -55,7 +57,7 @@ static void FixUpChecksum(uint8_t* dex_file) { class DexFileVerifierTest : public CommonRuntimeTest { protected: DexFile* GetDexFile(const uint8_t* dex_bytes, size_t length) { - return new DexFile(dex_bytes, length, "tmp", 0, nullptr); + return new NativeDexFile(dex_bytes, length, "tmp", 0, nullptr); } void VerifyModification(const char* dex_file_base64_content, @@ -112,7 +114,7 @@ static std::unique_ptr<const DexFile> OpenDexFileBase64(const char* base64, // read dex file ScopedObjectAccess soa(Thread::Current()); std::vector<std::unique_ptr<const DexFile>> tmp; - bool success = DexFile::Open(location, location, true, error_msg, &tmp); + bool success = DexFileLoader::Open(location, location, true, error_msg, &tmp); CHECK(success) << *error_msg; EXPECT_EQ(1U, tmp.size()); std::unique_ptr<const DexFile> dex_file = std::move(tmp[0]); diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index 813a264ed9..ea7a83c75e 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -2185,20 +2185,11 @@ extern "C" TwoWordReturn artQuickGenericJniTrampoline(Thread* self, ArtMethod** // Generic JNI trampoline at this stage; instead, method's // annotations' classes are looked up in the bootstrap class // loader's resolved types (which won't trigger an exception). + CHECK(!self->IsExceptionPending()); bool critical_native = called->IsAnnotatedWithCriticalNative(); - // ArtMethod::IsAnnotatedWithCriticalNative should not throw - // an exception; clear it if it happened anyway. - // TODO: Revisit this code path and turn this into a CHECK(!self->IsExceptionPending()). - if (self->IsExceptionPending()) { - self->ClearException(); - } + CHECK(!self->IsExceptionPending()); bool fast_native = called->IsAnnotatedWithFastNative(); - // ArtMethod::IsAnnotatedWithFastNative should not throw - // an exception; clear it if it happened anyway. - // TODO: Revisit this code path and turn this into a CHECK(!self->IsExceptionPending()). - if (self->IsExceptionPending()) { - self->ClearException(); - } + CHECK(!self->IsExceptionPending()); bool normal_native = !critical_native && !fast_native; // Restore the initial ArtMethod pointer at `*sp`. *sp = called; diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index 732c707670..f0eada3cb4 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -35,6 +35,7 @@ #include "base/stl_util.h" #include "base/systrace.h" #include "base/time_utils.h" +#include "dex_file_loader.h" #include "exec_utils.h" #include "gc/accounting/space_bitmap-inl.h" #include "image-inl.h" @@ -1829,12 +1830,12 @@ bool ImageSpace::ValidateOatFile(const OatFile& oat_file, std::string* error_msg // Skip multidex locations - These will be checked when we visit their // corresponding primary non-multidex location. - if (DexFile::IsMultiDexLocation(dex_file_location.c_str())) { + if (DexFileLoader::IsMultiDexLocation(dex_file_location.c_str())) { continue; } std::vector<uint32_t> checksums; - if (!DexFile::GetMultiDexChecksums(dex_file_location.c_str(), &checksums, error_msg)) { + if (!DexFileLoader::GetMultiDexChecksums(dex_file_location.c_str(), &checksums, error_msg)) { *error_msg = StringPrintf("ValidateOatFile failed to get checksums of dex file '%s' " "referenced by oat file %s: %s", dex_file_location.c_str(), @@ -1855,7 +1856,9 @@ bool ImageSpace::ValidateOatFile(const OatFile& oat_file, std::string* error_msg // Verify checksums for any related multidex entries. for (size_t i = 1; i < checksums.size(); i++) { - std::string multi_dex_location = DexFile::GetMultiDexLocation(i, dex_file_location.c_str()); + std::string multi_dex_location = DexFileLoader::GetMultiDexLocation( + i, + dex_file_location.c_str()); const OatFile::OatDexFile* multi_dex = oat_file.GetOatDexFile(multi_dex_location.c_str(), nullptr, error_msg); diff --git a/runtime/indirect_reference_table.cc b/runtime/indirect_reference_table.cc index 2dd4db3895..2c8ec47492 100644 --- a/runtime/indirect_reference_table.cc +++ b/runtime/indirect_reference_table.cc @@ -511,7 +511,7 @@ bool IndirectReferenceTable::EnsureFreeCapacity(size_t free_capacity, std::strin return true; } -size_t IndirectReferenceTable::FreeCapacity() { +size_t IndirectReferenceTable::FreeCapacity() const { return max_entries_ - segment_state_.top_index; } diff --git a/runtime/indirect_reference_table.h b/runtime/indirect_reference_table.h index 7daf01ce61..6675099523 100644 --- a/runtime/indirect_reference_table.h +++ b/runtime/indirect_reference_table.h @@ -293,7 +293,7 @@ class IndirectReferenceTable { REQUIRES_SHARED(Locks::mutator_lock_); // See implementation of EnsureFreeCapacity. We'll only state here how much is trivially free, // without recovering holes. Thus this is a conservative estimate. - size_t FreeCapacity() REQUIRES_SHARED(Locks::mutator_lock_); + size_t FreeCapacity() const; // Note IrtIterator does not have a read barrier as it's used to visit roots. IrtIterator begin() { diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc index 5a1605323e..73746e18ef 100644 --- a/runtime/java_vm_ext.cc +++ b/runtime/java_vm_ext.cc @@ -28,6 +28,8 @@ #include "check_jni.h" #include "dex_file-inl.h" #include "fault_handler.h" +#include "gc/allocation_record.h" +#include "gc/heap.h" #include "gc_root-inl.h" #include "indirect_reference_table-inl.h" #include "jni_internal.h" @@ -468,7 +470,11 @@ JavaVMExt::JavaVMExt(Runtime* runtime, weak_globals_add_condition_("weak globals add condition", (CHECK(Locks::jni_weak_globals_lock_ != nullptr), *Locks::jni_weak_globals_lock_)), - env_hooks_() { + env_hooks_(), + enable_allocation_tracking_delta_( + runtime_options.GetOrDefault(RuntimeArgumentMap::GlobalRefAllocStackTraceLimit)), + allocation_tracking_enabled_(false), + old_allocation_tracking_state_(false) { functions = unchecked_functions_; SetCheckJniEnabled(runtime_options.Exists(RuntimeArgumentMap::CheckJni)); } @@ -583,18 +589,55 @@ bool JavaVMExt::ShouldTrace(ArtMethod* method) { return true; } +void JavaVMExt::CheckGlobalRefAllocationTracking() { + if (LIKELY(enable_allocation_tracking_delta_ == 0)) { + return; + } + size_t simple_free_capacity = globals_.FreeCapacity(); + if (UNLIKELY(simple_free_capacity <= enable_allocation_tracking_delta_)) { + if (!allocation_tracking_enabled_) { + LOG(WARNING) << "Global reference storage appears close to exhaustion, program termination " + << "may be imminent. Enabling allocation tracking to improve abort diagnostics. " + << "This will result in program slow-down."; + + old_allocation_tracking_state_ = runtime_->GetHeap()->IsAllocTrackingEnabled(); + if (!old_allocation_tracking_state_) { + // Need to be guaranteed suspended. + ScopedObjectAccess soa(Thread::Current()); + ScopedThreadSuspension sts(soa.Self(), ThreadState::kNative); + gc::AllocRecordObjectMap::SetAllocTrackingEnabled(true); + } + allocation_tracking_enabled_ = true; + } + } else { + if (UNLIKELY(allocation_tracking_enabled_)) { + if (!old_allocation_tracking_state_) { + // Need to be guaranteed suspended. + ScopedObjectAccess soa(Thread::Current()); + ScopedThreadSuspension sts(soa.Self(), ThreadState::kNative); + gc::AllocRecordObjectMap::SetAllocTrackingEnabled(false); + } + allocation_tracking_enabled_ = false; + } + } +} + jobject JavaVMExt::AddGlobalRef(Thread* self, ObjPtr<mirror::Object> obj) { // Check for null after decoding the object to handle cleared weak globals. if (obj == nullptr) { return nullptr; } - WriterMutexLock mu(self, *Locks::jni_globals_lock_); + IndirectRef ref; std::string error_msg; - IndirectRef ref = globals_.Add(kIRTFirstSegment, obj, &error_msg); + { + WriterMutexLock mu(self, *Locks::jni_globals_lock_); + ref = globals_.Add(kIRTFirstSegment, obj, &error_msg); + } if (UNLIKELY(ref == nullptr)) { LOG(FATAL) << error_msg; UNREACHABLE(); } + CheckGlobalRefAllocationTracking(); return reinterpret_cast<jobject>(ref); } @@ -625,11 +668,14 @@ void JavaVMExt::DeleteGlobalRef(Thread* self, jobject obj) { if (obj == nullptr) { return; } - WriterMutexLock mu(self, *Locks::jni_globals_lock_); - if (!globals_.Remove(kIRTFirstSegment, obj)) { - LOG(WARNING) << "JNI WARNING: DeleteGlobalRef(" << obj << ") " - << "failed to find entry"; + { + WriterMutexLock mu(self, *Locks::jni_globals_lock_); + if (!globals_.Remove(kIRTFirstSegment, obj)) { + LOG(WARNING) << "JNI WARNING: DeleteGlobalRef(" << obj << ") " + << "failed to find entry"; + } } + CheckGlobalRefAllocationTracking(); } void JavaVMExt::DeleteWeakGlobalRef(Thread* self, jweak obj) { diff --git a/runtime/java_vm_ext.h b/runtime/java_vm_ext.h index b767b199f0..0510d6ab75 100644 --- a/runtime/java_vm_ext.h +++ b/runtime/java_vm_ext.h @@ -211,6 +211,8 @@ class JavaVMExt : public JavaVM { REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::jni_weak_globals_lock_); + void CheckGlobalRefAllocationTracking(); + Runtime* const runtime_; // Used for testing. By default, we'll LOG(FATAL) the reason. @@ -247,6 +249,10 @@ class JavaVMExt : public JavaVM { // TODO Maybe move this to Runtime. std::vector<GetEnvHook> env_hooks_; + size_t enable_allocation_tracking_delta_; + std::atomic<bool> allocation_tracking_enabled_; + std::atomic<bool> old_allocation_tracking_state_; + DISALLOW_COPY_AND_ASSIGN(JavaVMExt); }; diff --git a/runtime/java_vm_ext_test.cc b/runtime/java_vm_ext_test.cc index 2cbfa81b91..a15ec56274 100644 --- a/runtime/java_vm_ext_test.cc +++ b/runtime/java_vm_ext_test.cc @@ -19,6 +19,7 @@ #include <pthread.h> #include "common_runtime_test.h" +#include "gc/heap.h" #include "java_vm_ext.h" #include "runtime.h" @@ -134,4 +135,49 @@ TEST_F(JavaVmExtTest, DetachCurrentThread) { EXPECT_EQ(JNI_ERR, err); } +class JavaVmExtStackTraceTest : public JavaVmExtTest { + protected: + void SetUpRuntimeOptions(RuntimeOptions* options) OVERRIDE { + options->emplace_back("-XX:GlobalRefAllocStackTraceLimit=50000", nullptr); + } +}; + +TEST_F(JavaVmExtStackTraceTest, TestEnableDisable) { + ASSERT_FALSE(Runtime::Current()->GetHeap()->IsAllocTrackingEnabled()); + + JNIEnv* env; + jint ok = vm_->AttachCurrentThread(&env, nullptr); + ASSERT_EQ(JNI_OK, ok); + + std::vector<jobject> global_refs_; + jobject local_ref = env->NewStringUTF("Dummy"); + for (size_t i = 0; i < 2000; ++i) { + global_refs_.push_back(env->NewGlobalRef(local_ref)); + } + + EXPECT_TRUE(Runtime::Current()->GetHeap()->IsAllocTrackingEnabled()); + + for (jobject global_ref : global_refs_) { + env->DeleteGlobalRef(global_ref); + } + + EXPECT_FALSE(Runtime::Current()->GetHeap()->IsAllocTrackingEnabled()); + + global_refs_.clear(); + for (size_t i = 0; i < 2000; ++i) { + global_refs_.push_back(env->NewGlobalRef(local_ref)); + } + + EXPECT_TRUE(Runtime::Current()->GetHeap()->IsAllocTrackingEnabled()); + + for (jobject global_ref : global_refs_) { + env->DeleteGlobalRef(global_ref); + } + + EXPECT_FALSE(Runtime::Current()->GetHeap()->IsAllocTrackingEnabled()); + + ok = vm_->DetachCurrentThread(); + EXPECT_EQ(JNI_OK, ok); +} + } // namespace art diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index e122c6da20..47615f56fe 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -26,6 +26,7 @@ #include "base/time_utils.h" #include "cha.h" #include "debugger_interface.h" +#include "dex_file_loader.h" #include "entrypoints/runtime_asm_entrypoints.h" #include "gc/accounting/bitmap-inl.h" #include "gc/scoped_gc_critical_section.h" @@ -1350,7 +1351,8 @@ void JitCodeCache::GetProfiledMethods(const std::set<std::string>& dex_base_loca for (const ProfilingInfo* info : profiling_infos_) { ArtMethod* method = info->GetMethod(); const DexFile* dex_file = method->GetDexFile(); - if (!ContainsElement(dex_base_locations, dex_file->GetBaseLocation())) { + const std::string base_location = DexFileLoader::GetBaseLocation(dex_file->GetLocation()); + if (!ContainsElement(dex_base_locations, base_location)) { // Skip dex files which are not profiled. continue; } @@ -1404,7 +1406,8 @@ void JitCodeCache::GetProfiledMethods(const std::set<std::string>& dex_base_loca is_missing_types = true; continue; } - if (ContainsElement(dex_base_locations, class_dex_file->GetBaseLocation())) { + if (ContainsElement(dex_base_locations, + DexFileLoader::GetBaseLocation(class_dex_file->GetLocation()))) { // Only consider classes from the same apk (including multidex). profile_classes.emplace_back(/*ProfileMethodInfo::ProfileClassReference*/ class_dex_file, type_index); diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc index d14c94aafe..19501de81b 100644 --- a/runtime/jit/profile_compilation_info.cc +++ b/runtime/jit/profile_compilation_info.cc @@ -40,6 +40,7 @@ #include "base/systrace.h" #include "base/time_utils.h" #include "base/unix_file/fd_file.h" +#include "dex_file_loader.h" #include "jit/profiling_info.h" #include "os.h" #include "safe_map.h" @@ -1537,7 +1538,7 @@ std::string ProfileCompilationInfo::DumpInfo(const std::vector<const DexFile*>* os << dex_data->profile_key; } else { // Replace the (empty) multidex suffix of the first key with a substitute for easier reading. - std::string multidex_suffix = DexFile::GetMultiDexSuffix(dex_data->profile_key); + std::string multidex_suffix = DexFileLoader::GetMultiDexSuffix(dex_data->profile_key); os << (multidex_suffix.empty() ? kFirstDexFileKeySubstitute : multidex_suffix); } os << " [index=" << static_cast<uint32_t>(dex_data->profile_index) << "]"; @@ -1696,7 +1697,7 @@ bool ProfileCompilationInfo::GenerateTestProfile(int fd, const uint16_t kFavorSplit = 2; for (uint16_t i = 0; i < number_of_dex_files; i++) { - std::string dex_location = DexFile::GetMultiDexLocation(i, base_dex_location.c_str()); + std::string dex_location = DexFileLoader::GetMultiDexLocation(i, base_dex_location.c_str()); std::string profile_key = GetProfileDexFileKey(dex_location); for (uint16_t m = 0; m < number_of_methods; m++) { diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc index 2bf8d8b8f8..01853de403 100644 --- a/runtime/jit/profile_saver.cc +++ b/runtime/jit/profile_saver.cc @@ -31,6 +31,7 @@ #include "base/time_utils.h" #include "class_table-inl.h" #include "compiler_filter.h" +#include "dex_file_loader.h" #include "dex_reference_collection.h" #include "gc/collector_type.h" #include "gc/gc_cause.h" @@ -414,7 +415,8 @@ void ProfileSaver::FetchAndCacheResolvedClassesAndMethods(bool startup) { const std::set<std::string>& locations = it.second; for (const auto& pair : hot_methods.GetMap()) { const DexFile* const dex_file = pair.first; - if (locations.find(dex_file->GetBaseLocation()) != locations.end()) { + const std::string base_location = DexFileLoader::GetBaseLocation(dex_file->GetLocation()); + if (locations.find(base_location) != locations.end()) { const MethodReferenceCollection::IndexVector& indices = pair.second; uint8_t flags = Hotness::kFlagHot; flags |= startup ? Hotness::kFlagStartup : Hotness::kFlagPostStartup; @@ -427,7 +429,8 @@ void ProfileSaver::FetchAndCacheResolvedClassesAndMethods(bool startup) { } for (const auto& pair : sampled_methods.GetMap()) { const DexFile* const dex_file = pair.first; - if (locations.find(dex_file->GetBaseLocation()) != locations.end()) { + const std::string base_location = DexFileLoader::GetBaseLocation(dex_file->GetLocation()); + if (locations.find(base_location) != locations.end()) { const MethodReferenceCollection::IndexVector& indices = pair.second; cached_info->AddMethodsForDex(startup ? Hotness::kFlagStartup : Hotness::kFlagPostStartup, dex_file, @@ -437,14 +440,15 @@ void ProfileSaver::FetchAndCacheResolvedClassesAndMethods(bool startup) { } for (const auto& pair : resolved_classes.GetMap()) { const DexFile* const dex_file = pair.first; - if (locations.find(dex_file->GetBaseLocation()) != locations.end()) { + const std::string base_location = DexFileLoader::GetBaseLocation(dex_file->GetLocation()); + if (locations.find(base_location) != locations.end()) { const TypeReferenceCollection::IndexVector& classes = pair.second; VLOG(profiler) << "Added " << classes.size() << " classes for location " - << dex_file->GetBaseLocation() + << base_location << " (" << dex_file->GetLocation() << ")"; cached_info->AddClassesForDex(dex_file, classes.begin(), classes.end()); } else { - VLOG(profiler) << "Location not found " << dex_file->GetBaseLocation() + VLOG(profiler) << "Location not found " << base_location << " (" << dex_file->GetLocation() << ")"; } } diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc index d40e6d94c9..e75d097220 100644 --- a/runtime/native/dalvik_system_DexFile.cc +++ b/runtime/native/dalvik_system_DexFile.cc @@ -26,6 +26,7 @@ #include "common_throws.h" #include "compiler_filter.h" #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "jni_internal.h" #include "mirror/class_loader.h" #include "mirror/object-inl.h" @@ -185,12 +186,12 @@ static const DexFile* CreateDexFile(JNIEnv* env, std::unique_ptr<MemMap> dex_mem dex_mem_map->Begin(), dex_mem_map->End()); std::string error_message; - std::unique_ptr<const DexFile> dex_file(DexFile::Open(location, - 0, - std::move(dex_mem_map), - /* verify */ true, - /* verify_location */ true, - &error_message)); + std::unique_ptr<const DexFile> dex_file(DexFileLoader::Open(location, + 0, + std::move(dex_mem_map), + /* verify */ true, + /* verify_location */ true, + &error_message)); if (dex_file == nullptr) { ScopedObjectAccess soa(env); ThrowWrappedIOException("%s", error_message.c_str()); diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc index 3357fa7a45..70dd5cb56d 100644 --- a/runtime/native/dalvik_system_VMDebug.cc +++ b/runtime/native/dalvik_system_VMDebug.cc @@ -95,10 +95,10 @@ static void VMDebug_startMethodTracingDdmsImpl(JNIEnv*, jclass, jint bufferSize, } static void VMDebug_startMethodTracingFd(JNIEnv* env, jclass, jstring javaTraceFilename, - jobject javaFd, jint bufferSize, jint flags, + jint javaFd, jint bufferSize, jint flags, jboolean samplingEnabled, jint intervalUs, jboolean streamingOutput) { - int originalFd = jniGetFDFromFileDescriptor(env, javaFd); + int originalFd = javaFd; if (originalFd < 0) { return; } @@ -224,9 +224,9 @@ static jlong VMDebug_threadCpuTimeNanos(JNIEnv*, jclass) { * Cause "hprof" data to be dumped. We can throw an IOException if an * error occurs during file handling. */ -static void VMDebug_dumpHprofData(JNIEnv* env, jclass, jstring javaFilename, jobject javaFd) { +static void VMDebug_dumpHprofData(JNIEnv* env, jclass, jstring javaFilename, jint javaFd) { // Only one of these may be null. - if (javaFilename == nullptr && javaFd == nullptr) { + if (javaFilename == nullptr && javaFd < 0) { ScopedObjectAccess soa(env); ThrowNullPointerException("fileName == null && fd == null"); return; @@ -243,15 +243,7 @@ static void VMDebug_dumpHprofData(JNIEnv* env, jclass, jstring javaFilename, job filename = "[fd]"; } - int fd = -1; - if (javaFd != nullptr) { - fd = jniGetFDFromFileDescriptor(env, javaFd); - if (fd < 0) { - ScopedObjectAccess soa(env); - ThrowRuntimeException("Invalid file descriptor"); - return; - } - } + int fd = javaFd; hprof::DumpHeap(filename.c_str(), fd, false); } @@ -537,7 +529,7 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(VMDebug, countInstancesOfClass, "(Ljava/lang/Class;Z)J"), NATIVE_METHOD(VMDebug, countInstancesOfClasses, "([Ljava/lang/Class;Z)[J"), NATIVE_METHOD(VMDebug, crash, "()V"), - NATIVE_METHOD(VMDebug, dumpHprofData, "(Ljava/lang/String;Ljava/io/FileDescriptor;)V"), + NATIVE_METHOD(VMDebug, dumpHprofData, "(Ljava/lang/String;I)V"), NATIVE_METHOD(VMDebug, dumpHprofDataDdms, "()V"), NATIVE_METHOD(VMDebug, dumpReferenceTables, "()V"), NATIVE_METHOD(VMDebug, getAllocCount, "(I)I"), @@ -557,7 +549,7 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(VMDebug, startEmulatorTracing, "()V"), NATIVE_METHOD(VMDebug, startInstructionCounting, "()V"), NATIVE_METHOD(VMDebug, startMethodTracingDdmsImpl, "(IIZI)V"), - NATIVE_METHOD(VMDebug, startMethodTracingFd, "(Ljava/lang/String;Ljava/io/FileDescriptor;IIZIZ)V"), + NATIVE_METHOD(VMDebug, startMethodTracingFd, "(Ljava/lang/String;IIIZIZ)V"), NATIVE_METHOD(VMDebug, startMethodTracingFilename, "(Ljava/lang/String;IIZI)V"), NATIVE_METHOD(VMDebug, stopAllocCounting, "()V"), NATIVE_METHOD(VMDebug, stopEmulatorTracing, "()V"), diff --git a/runtime/native/java_lang_VMClassLoader.cc b/runtime/native/java_lang_VMClassLoader.cc index 4034e8c837..413149c510 100644 --- a/runtime/native/java_lang_VMClassLoader.cc +++ b/runtime/native/java_lang_VMClassLoader.cc @@ -17,6 +17,7 @@ #include "java_lang_VMClassLoader.h" #include "class_linker.h" +#include "dex_file_loader.h" #include "jni_internal.h" #include "mirror/class_loader.h" #include "mirror/object-inl.h" @@ -135,7 +136,7 @@ static jobjectArray VMClassLoader_getBootClassPathEntries(JNIEnv* env, jclass) { const DexFile* dex_file = path[i]; // For multidex locations, e.g., x.jar!classes2.dex, we want to look into x.jar. - const std::string& location(dex_file->GetBaseLocation()); + const std::string location(DexFileLoader::GetBaseLocation(dex_file->GetLocation())); ScopedLocalRef<jstring> javaPath(env, env->NewStringUTF(location.c_str())); if (javaPath.get() == nullptr) { diff --git a/runtime/native_dex_file.cc b/runtime/native_dex_file.cc new file mode 100644 index 0000000000..9a93696f1b --- /dev/null +++ b/runtime/native_dex_file.cc @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "native_dex_file.h" + +namespace art { + +const uint8_t NativeDexFile::kDexMagic[] = { 'd', 'e', 'x', '\n' }; +const uint8_t NativeDexFile::kDexMagicVersions[NativeDexFile::kNumDexVersions] + [NativeDexFile::kDexVersionLen] = { + {'0', '3', '5', '\0'}, + // Dex version 036 skipped because of an old dalvik bug on some versions of android where dex + // files with that version number would erroneously be accepted and run. + {'0', '3', '7', '\0'}, + // Dex version 038: Android "O" and beyond. + {'0', '3', '8', '\0'}, + // Dex verion 039: Beyond Android "O". + {'0', '3', '9', '\0'}, +}; + +bool NativeDexFile::IsMagicValid(const uint8_t* magic) { + return (memcmp(magic, kDexMagic, sizeof(kDexMagic)) == 0); +} + +bool NativeDexFile::IsVersionValid(const uint8_t* magic) { + const uint8_t* version = &magic[sizeof(kDexMagic)]; + for (uint32_t i = 0; i < kNumDexVersions; i++) { + if (memcmp(version, kDexMagicVersions[i], kDexVersionLen) == 0) { + return true; + } + } + return false; +} + +bool NativeDexFile::IsMagicValid() const { + return IsMagicValid(header_->magic_); +} + +bool NativeDexFile::IsVersionValid() const { + return IsVersionValid(header_->magic_); +} + +} // namespace art diff --git a/runtime/native_dex_file.h b/runtime/native_dex_file.h new file mode 100644 index 0000000000..8f09e6d7fc --- /dev/null +++ b/runtime/native_dex_file.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_RUNTIME_NATIVE_DEX_FILE_H_ +#define ART_RUNTIME_NATIVE_DEX_FILE_H_ + +#include <iosfwd> + +#include "dex_file.h" + +namespace art { + +class OatDexFile; + +// Native (ordinary) dex file. This is the format that is packaged in APKs and produced by tools. +class NativeDexFile : public DexFile { + public: + static const uint8_t kDexMagic[kDexMagicSize]; + static constexpr size_t kNumDexVersions = 4; + static const uint8_t kDexMagicVersions[kNumDexVersions][kDexVersionLen]; + + // Returns true if the byte string points to the magic value. + static bool IsMagicValid(const uint8_t* magic); + virtual bool IsMagicValid() const OVERRIDE; + + // Returns true if the byte string after the magic is the correct value. + static bool IsVersionValid(const uint8_t* magic); + virtual bool IsVersionValid() const OVERRIDE; + + private: + NativeDexFile(const uint8_t* base, + size_t size, + const std::string& location, + uint32_t location_checksum, + const OatDexFile* oat_dex_file) + : DexFile(base, size, location, location_checksum, oat_dex_file) {} + + friend class DexFileLoader; + friend class DexFileVerifierTest; + + ART_FRIEND_TEST(ClassLinkerTest, RegisterDexFileName); // for constructor + + DISALLOW_COPY_AND_ASSIGN(NativeDexFile); +}; + +} // namespace art + +#endif // ART_RUNTIME_NATIVE_DEX_FILE_H_ diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index 3f4cb942fc..ab820fb37f 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -26,6 +26,7 @@ #include <cstring> #include <sstream> #include <type_traits> +#include <sys/stat.h> // dlopen_ext support from bionic. #ifdef ART_TARGET_ANDROID @@ -41,6 +42,7 @@ #include "base/systrace.h" #include "base/unix_file/fd_file.h" #include "dex_file_types.h" +#include "dex_file_loader.h" #include "elf_file.h" #include "elf_utils.h" #include "gc_root.h" @@ -48,6 +50,7 @@ #include "mem_map.h" #include "mirror/class.h" #include "mirror/object-inl.h" +#include "native_dex_file.h" #include "oat.h" #include "oat_file-inl.h" #include "oat_file_manager.h" @@ -105,6 +108,19 @@ class OatFileBase : public OatFile { const char* abs_dex_location, std::string* error_msg); + template <typename kOatFileBaseSubType> + static OatFileBase* OpenOatFile(int vdex_fd, + int oat_fd, + const std::string& vdex_filename, + const std::string& oat_filename, + uint8_t* requested_base, + uint8_t* oat_file_begin, + bool writable, + bool executable, + bool low_4gb, + const char* abs_dex_location, + std::string* error_msg); + protected: OatFileBase(const std::string& filename, bool executable) : OatFile(filename, executable) {} @@ -118,6 +134,12 @@ class OatFileBase : public OatFile { bool low_4gb, std::string* error_msg); + bool LoadVdex(int vdex_fd, + const std::string& vdex_filename, + bool writable, + bool low_4gb, + std::string* error_msg); + virtual bool Load(const std::string& elf_filename, uint8_t* oat_file_begin, bool writable, @@ -125,6 +147,13 @@ class OatFileBase : public OatFile { bool low_4gb, std::string* error_msg) = 0; + virtual bool Load(int oat_fd, + uint8_t* oat_file_begin, + bool writable, + bool executable, + bool low_4gb, + std::string* error_msg) = 0; + bool ComputeFields(uint8_t* requested_base, const std::string& file_path, std::string* error_msg); @@ -192,6 +221,46 @@ OatFileBase* OatFileBase::OpenOatFile(const std::string& vdex_filename, return ret.release(); } +template <typename kOatFileBaseSubType> +OatFileBase* OatFileBase::OpenOatFile(int vdex_fd, + int oat_fd, + const std::string& vdex_location, + const std::string& oat_location, + uint8_t* requested_base, + uint8_t* oat_file_begin, + bool writable, + bool executable, + bool low_4gb, + const char* abs_dex_location, + std::string* error_msg) { + std::unique_ptr<OatFileBase> ret(new kOatFileBaseSubType(oat_location, executable)); + + if (kIsVdexEnabled && !ret->LoadVdex(vdex_fd, vdex_location, writable, low_4gb, error_msg)) { + return nullptr; + } + + if (!ret->Load(oat_fd, + oat_file_begin, + writable, + executable, + low_4gb, + error_msg)) { + return nullptr; + } + + if (!ret->ComputeFields(requested_base, oat_location, error_msg)) { + return nullptr; + } + + ret->PreSetup(oat_location); + + if (!ret->Setup(abs_dex_location, error_msg)) { + return nullptr; + } + + return ret.release(); +} + bool OatFileBase::LoadVdex(const std::string& vdex_filename, bool writable, bool low_4gb, @@ -206,6 +275,33 @@ bool OatFileBase::LoadVdex(const std::string& vdex_filename, return true; } +bool OatFileBase::LoadVdex(int vdex_fd, + const std::string& vdex_filename, + bool writable, + bool low_4gb, + std::string* error_msg) { + if (vdex_fd != -1) { + struct stat s; + int rc = TEMP_FAILURE_RETRY(fstat(vdex_fd, &s)); + if (rc == -1) { + PLOG(WARNING) << "Failed getting length of vdex file"; + } else { + vdex_ = VdexFile::Open(vdex_fd, + s.st_size, + vdex_filename, + writable, + low_4gb, + false /* unquicken */, + error_msg); + if (vdex_.get() == nullptr) { + *error_msg = "Failed opening vdex file."; + return false; + } + } + } + return true; +} + bool OatFileBase::ComputeFields(uint8_t* requested_base, const std::string& file_path, std::string* error_msg) { @@ -458,7 +554,9 @@ bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { } const uint8_t* dex_file_pointer = DexBegin() + dex_file_offset; - if (UNLIKELY(!DexFile::IsMagicValid(dex_file_pointer))) { + + const bool valid_magic = NativeDexFile::IsMagicValid(dex_file_pointer); + if (UNLIKELY(!valid_magic)) { *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with invalid " "dex file magic '%s'", GetLocation().c_str(), @@ -467,7 +565,7 @@ bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { dex_file_pointer); return false; } - if (UNLIKELY(!DexFile::IsVersionValid(dex_file_pointer))) { + if (UNLIKELY(!NativeDexFile::IsVersionValid(dex_file_pointer))) { *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with invalid " "dex file version '%s'", GetLocation().c_str(), @@ -611,7 +709,8 @@ bool OatFileBase::Setup(const char* abs_dex_location, std::string* error_msg) { reinterpret_cast<const DexFile::Header*>(dex_file_pointer)->method_ids_size_); } - std::string canonical_location = DexFile::GetDexCanonicalLocation(dex_file_location.c_str()); + std::string canonical_location = + DexFileLoader::GetDexCanonicalLocation(dex_file_location.c_str()); // Create the OatDexFile and add it to the owning container. OatDexFile* oat_dex_file = new OatDexFile(this, @@ -712,6 +811,10 @@ class DlOpenOatFile FINAL : public OatFileBase { bool low_4gb, std::string* error_msg) OVERRIDE; + bool Load(int, uint8_t*, bool, bool, bool, std::string*) { + return false; + } + // Ask the linker where it mmaped the file and notify our mmap wrapper of the regions. void PreSetup(const std::string& elf_filename) OVERRIDE; @@ -973,6 +1076,13 @@ class ElfOatFile FINAL : public OatFileBase { bool low_4gb, std::string* error_msg) OVERRIDE; + bool Load(int oat_fd, + uint8_t* oat_file_begin, // Override where the file is loaded to if not null + bool writable, + bool executable, + bool low_4gb, + std::string* error_msg) OVERRIDE; + void PreSetup(const std::string& elf_filename ATTRIBUTE_UNUSED) OVERRIDE { } @@ -1065,6 +1175,31 @@ bool ElfOatFile::Load(const std::string& elf_filename, error_msg); } +bool ElfOatFile::Load(int oat_fd, + uint8_t* oat_file_begin, // Override where the file is loaded to if not null + bool writable, + bool executable, + bool low_4gb, + std::string* error_msg) { + ScopedTrace trace(__PRETTY_FUNCTION__); + if (oat_fd != -1) { + std::unique_ptr<File> file = std::make_unique<File>(oat_fd, false); + file->DisableAutoClose(); + if (file == nullptr) { + *error_msg = StringPrintf("Failed to open oat filename for reading: %s", + strerror(errno)); + return false; + } + return ElfOatFile::ElfFileOpen(file.get(), + oat_file_begin, + writable, + executable, + low_4gb, + error_msg); + } + return false; +} + bool ElfOatFile::ElfFileOpen(File* file, uint8_t* oat_file_begin, bool writable, @@ -1096,8 +1231,8 @@ std::string OatFile::ResolveRelativeEncodedDexLocation( const char* abs_dex_location, const std::string& rel_dex_location) { if (abs_dex_location != nullptr && rel_dex_location[0] != '/') { // Strip :classes<N>.dex used for secondary multidex files. - std::string base = DexFile::GetBaseLocation(rel_dex_location); - std::string multidex_suffix = DexFile::GetMultiDexSuffix(rel_dex_location); + std::string base = DexFileLoader::GetBaseLocation(rel_dex_location); + std::string multidex_suffix = DexFileLoader::GetMultiDexSuffix(rel_dex_location); // Check if the base is a suffix of the provided abs_dex_location. std::string target_suffix = "/" + base; @@ -1194,6 +1329,33 @@ OatFile* OatFile::Open(const std::string& oat_filename, return with_internal; } +OatFile* OatFile::Open(int vdex_fd, + int oat_fd, + const std::string& oat_location, + uint8_t* requested_base, + uint8_t* oat_file_begin, + bool executable, + bool low_4gb, + const char* abs_dex_location, + std::string* error_msg) { + CHECK(!oat_location.empty()) << oat_location; + + std::string vdex_location = GetVdexFilename(oat_location); + + OatFile* with_internal = OatFileBase::OpenOatFile<ElfOatFile>(vdex_fd, + oat_fd, + vdex_location, + oat_location, + requested_base, + oat_file_begin, + false /* writable */, + executable, + low_4gb, + abs_dex_location, + error_msg); + return with_internal; +} + OatFile* OatFile::OpenWritable(File* file, const std::string& location, const char* abs_dex_location, @@ -1324,7 +1486,7 @@ const OatFile::OatDexFile* OatFile::GetOatDexFile(const char* dex_location, oat_dex_file = secondary_lb->second; // May be null. } else { // We haven't seen this dex_location before, we must check the canonical location. - std::string dex_canonical_location = DexFile::GetDexCanonicalLocation(dex_location); + std::string dex_canonical_location = DexFileLoader::GetDexCanonicalLocation(dex_location); if (dex_canonical_location != dex_location) { StringPiece canonical_key(dex_canonical_location); auto canonical_it = oat_dex_files_.find(canonical_key); @@ -1342,7 +1504,7 @@ const OatFile::OatDexFile* OatFile::GetOatDexFile(const char* dex_location, if (oat_dex_file == nullptr) { if (error_msg != nullptr) { - std::string dex_canonical_location = DexFile::GetDexCanonicalLocation(dex_location); + std::string dex_canonical_location = DexFileLoader::GetDexCanonicalLocation(dex_location); *error_msg = "Failed to find OatDexFile for DexFile " + std::string(dex_location) + " (canonical path " + dex_canonical_location + ") in OatFile " + GetLocation(); } @@ -1352,7 +1514,7 @@ const OatFile::OatDexFile* OatFile::GetOatDexFile(const char* dex_location, if (dex_location_checksum != nullptr && oat_dex_file->GetDexFileLocationChecksum() != *dex_location_checksum) { if (error_msg != nullptr) { - std::string dex_canonical_location = DexFile::GetDexCanonicalLocation(dex_location); + std::string dex_canonical_location = DexFileLoader::GetDexCanonicalLocation(dex_location); std::string checksum = StringPrintf("0x%08x", oat_dex_file->GetDexFileLocationChecksum()); std::string required_checksum = StringPrintf("0x%08x", *dex_location_checksum); *error_msg = "OatDexFile for DexFile " + std::string(dex_location) @@ -1408,14 +1570,14 @@ std::unique_ptr<const DexFile> OatFile::OatDexFile::OpenDexFile(std::string* err ScopedTrace trace(__PRETTY_FUNCTION__); static constexpr bool kVerify = false; static constexpr bool kVerifyChecksum = false; - return DexFile::Open(dex_file_pointer_, - FileSize(), - dex_file_location_, - dex_file_location_checksum_, - this, - kVerify, - kVerifyChecksum, - error_msg); + return DexFileLoader::Open(dex_file_pointer_, + FileSize(), + dex_file_location_, + dex_file_location_checksum_, + this, + kVerify, + kVerifyChecksum, + error_msg); } uint32_t OatFile::OatDexFile::GetOatClassOffset(uint16_t class_def_index) const { diff --git a/runtime/oat_file.h b/runtime/oat_file.h index 04cb3a0a6e..7d4e6dfd6b 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -89,6 +89,18 @@ class OatFile { const char* abs_dex_location, std::string* error_msg); + // Similar to OatFile::Open(const std::string...), but accepts input vdex and + // odex files as file descriptors. + static OatFile* Open(int vdex_fd, + int oat_fd, + const std::string& oat_location, + uint8_t* requested_base, + uint8_t* oat_file_begin, + bool executable, + bool low_4gb, + const char* abs_dex_location, + std::string* error_msg); + // Open an oat file from an already opened File. // Does not use dlopen underneath so cannot be used for runtime use // where relocations may be required. Currently used from diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index f3a0725f79..a7fe9b1205 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -27,6 +27,7 @@ #include "base/stl_util.h" #include "class_linker.h" #include "compiler_filter.h" +#include "dex_file_loader.h" #include "exec_utils.h" #include "gc/heap.h" #include "gc/space/image_space.h" @@ -69,7 +70,9 @@ std::ostream& operator << (std::ostream& stream, const OatFileAssistant::OatStat OatFileAssistant::OatFileAssistant(const char* dex_location, const InstructionSet isa, - bool load_executable) + bool load_executable, + int vdex_fd, + int oat_fd) : isa_(isa), load_executable_(load_executable), odex_(this, /*is_oat_location*/ false), @@ -109,7 +112,7 @@ OatFileAssistant::OatFileAssistant(const char* dex_location, std::string error_msg; std::string odex_file_name; if (DexLocationToOdexFilename(dex_location_, isa_, &odex_file_name, &error_msg)) { - odex_.Reset(odex_file_name); + odex_.Reset(odex_file_name, vdex_fd, oat_fd); } else { LOG(WARNING) << "Failed to determine odex file name: " << error_msg; } @@ -132,7 +135,7 @@ OatFileAssistant::OatFileAssistant(const char* dex_location, LOG(WARNING) << "Failed to determine dex file parent directory: " << dex_location_; } else { std::string parent = dex_location_.substr(0, pos); - if (access(parent.c_str(), W_OK) == 0) { + if (access(parent.c_str(), W_OK) == 0 || oat_fd > 0) { dex_parent_writable_ = true; } else { VLOG(oat) << "Dex parent of " << dex_location_ << " is not writable: " << strerror(errno); @@ -349,7 +352,7 @@ bool OatFileAssistant::LoadDexFiles( // Load the rest of the multidex entries for (size_t i = 1;; i++) { - std::string multidex_dex_location = DexFile::GetMultiDexLocation(i, dex_location.c_str()); + std::string multidex_dex_location = DexFileLoader::GetMultiDexLocation(i, dex_location.c_str()); oat_dex_file = oat_file.GetOatDexFile(multidex_dex_location.c_str(), nullptr); if (oat_dex_file == nullptr) { // There are no more multidex entries to load. @@ -401,7 +404,7 @@ bool OatFileAssistant::DexChecksumUpToDate(const VdexFile& file, std::string* er uint32_t expected_checksum = (*required_dex_checksums)[i]; uint32_t actual_checksum = file.GetLocationChecksum(i); if (expected_checksum != actual_checksum) { - std::string dex = DexFile::GetMultiDexLocation(i, dex_location_.c_str()); + std::string dex = DexFileLoader::GetMultiDexLocation(i, dex_location_.c_str()); *error_msg = StringPrintf("Dex checksum does not match for dex: %s." "Expected: %u, actual: %u", dex.c_str(), @@ -430,7 +433,7 @@ bool OatFileAssistant::DexChecksumUpToDate(const OatFile& file, std::string* err } for (uint32_t i = 0; i < number_of_dex_files; i++) { - std::string dex = DexFile::GetMultiDexLocation(i, dex_location_.c_str()); + std::string dex = DexFileLoader::GetMultiDexLocation(i, dex_location_.c_str()); uint32_t expected_checksum = (*required_dex_checksums)[i]; const OatFile::OatDexFile* oat_dex_file = file.GetOatDexFile(dex.c_str(), nullptr); if (oat_dex_file == nullptr) { @@ -863,9 +866,9 @@ const std::vector<uint32_t>* OatFileAssistant::GetRequiredDexChecksums() { required_dex_checksums_found_ = false; cached_required_dex_checksums_.clear(); std::string error_msg; - if (DexFile::GetMultiDexChecksums(dex_location_.c_str(), - &cached_required_dex_checksums_, - &error_msg)) { + if (DexFileLoader::GetMultiDexChecksums(dex_location_.c_str(), + &cached_required_dex_checksums_, + &error_msg)) { required_dex_checksums_found_ = true; has_original_dex_files_ = true; } else { @@ -879,7 +882,7 @@ const std::vector<uint32_t>* OatFileAssistant::GetRequiredDexChecksums() { if (odex_file != nullptr) { required_dex_checksums_found_ = true; for (size_t i = 0; i < odex_file->GetOatHeader().GetDexFileCount(); i++) { - std::string dex = DexFile::GetMultiDexLocation(i, dex_location_.c_str()); + std::string dex = DexFileLoader::GetMultiDexLocation(i, dex_location_.c_str()); const OatFile::OatDexFile* odex_dex_file = odex_file->GetOatDexFile(dex.c_str(), nullptr); if (odex_dex_file == nullptr) { required_dex_checksums_found_ = false; @@ -1016,11 +1019,28 @@ OatFileAssistant::OatStatus OatFileAssistant::OatFileInfo::Status() { // Check to see if there is a vdex file we can make use of. std::string error_msg; std::string vdex_filename = GetVdexFilename(filename_); - std::unique_ptr<VdexFile> vdex = VdexFile::Open(vdex_filename, - /*writeable*/false, - /*low_4gb*/false, - /*unquicken*/false, - &error_msg); + std::unique_ptr<VdexFile> vdex; + if (vdex_fd_ == -1) { + vdex = VdexFile::Open(vdex_filename, + false /*writeable*/, + false /*low_4gb*/, + false /*unquicken*/, + &error_msg); + } else { + struct stat s; + int rc = TEMP_FAILURE_RETRY(fstat(vdex_fd_, &s)); + if (rc == -1) { + PLOG(WARNING) << "Failed getting length of vdex file"; + } else { + vdex = VdexFile::Open(vdex_fd_, + s.st_size, + vdex_filename, + false /*writable*/, + false /*low_4gb*/, + false /* unquicken */, + &error_msg); + } + } if (vdex == nullptr) { status_ = kOatCannotOpen; VLOG(oat) << "unable to open vdex file " << vdex_filename << ": " << error_msg; @@ -1095,14 +1115,26 @@ const OatFile* OatFileAssistant::OatFileInfo::GetFile() { load_attempted_ = true; if (filename_provided_) { std::string error_msg; - file_.reset(OatFile::Open(filename_.c_str(), - filename_.c_str(), - nullptr, - nullptr, - oat_file_assistant_->load_executable_, - /*low_4gb*/false, - oat_file_assistant_->dex_location_.c_str(), - &error_msg)); + if (oat_fd_ != -1 && vdex_fd_ != -1) { + file_.reset(OatFile::Open(vdex_fd_, + oat_fd_, + filename_.c_str(), + nullptr, + nullptr, + oat_file_assistant_->load_executable_, + false /* low_4gb */, + oat_file_assistant_->dex_location_.c_str(), + &error_msg)); + } else { + file_.reset(OatFile::Open(filename_.c_str(), + filename_.c_str(), + nullptr, + nullptr, + oat_file_assistant_->load_executable_, + false /* low_4gb */, + oat_file_assistant_->dex_location_.c_str(), + &error_msg)); + } if (file_.get() == nullptr) { VLOG(oat) << "OatFileAssistant test for existing oat file " << filename_ << ": " << error_msg; @@ -1169,9 +1201,12 @@ void OatFileAssistant::OatFileInfo::Reset() { status_attempted_ = false; } -void OatFileAssistant::OatFileInfo::Reset(const std::string& filename) { +void OatFileAssistant::OatFileInfo::Reset(const std::string& filename, int vdex_fd, + int oat_fd) { filename_provided_ = true; filename_ = filename; + vdex_fd_ = vdex_fd; + oat_fd_ = oat_fd; Reset(); } diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h index 6dc3c197b2..0f74ca4b02 100644 --- a/runtime/oat_file_assistant.h +++ b/runtime/oat_file_assistant.h @@ -121,7 +121,9 @@ class OatFileAssistant { // executable code for this dex location. OatFileAssistant(const char* dex_location, const InstructionSet isa, - bool load_executable); + bool load_executable, + int vdex_fd = -1, + int oat_fd = -1); ~OatFileAssistant(); @@ -349,7 +351,7 @@ class OatFileAssistant { // Clear any cached information and switch to getting info about the oat // file with the given filename. - void Reset(const std::string& filename); + void Reset(const std::string& filename, int vdex_fd = -1, int oat_fd = -1); // Release the loaded oat file for runtime use. // Returns null if the oat file hasn't been loaded or is out of date. @@ -386,6 +388,9 @@ class OatFileAssistant { bool filename_provided_ = false; std::string filename_; + int oat_fd_ = -1; + int vdex_fd_ = -1; + bool load_attempted_ = false; std::unique_ptr<OatFile> file_; diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc index 3ecd1b516d..d99036df7e 100644 --- a/runtime/oat_file_assistant_test.cc +++ b/runtime/oat_file_assistant_test.cc @@ -20,6 +20,7 @@ #include <string> #include <vector> +#include <fcntl.h> #include <gtest/gtest.h> @@ -222,6 +223,125 @@ TEST_F(OatFileAssistantTest, OatUpToDate) { EXPECT_TRUE(oat_file_assistant.HasOriginalDexFiles()); } +// Case: Passing valid file descriptors of updated odex/vdex filesalong with +// the dex file. +// Expect: The status is kNoDexOptNeeded. +TEST_F(OatFileAssistantTest, GetDexOptNeededWithFd) { + std::string dex_location = GetScratchDir() + "/OatUpToDate.jar"; + std::string odex_location = GetScratchDir() + "/OatUpToDate.odex"; + std::string vdex_location = GetScratchDir() + "/OatUpToDate.vdex"; + + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), + odex_location.c_str(), + CompilerFilter::kSpeed, + true, + false, + false); + + android::base::unique_fd odex_fd(open(odex_location.c_str(), O_RDONLY)); + android::base::unique_fd vdex_fd(open(vdex_location.c_str(), O_RDONLY)); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), + kRuntimeISA, + false, + vdex_fd.get(), + odex_fd.get()); + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken)); + EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kExtract)); + EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kEverything)); + + EXPECT_FALSE(oat_file_assistant.IsInBootClassPath()); + EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus()); + EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus()); + EXPECT_TRUE(oat_file_assistant.HasOriginalDexFiles()); +} + +// Case: Passing valid odex fd, however, invalid fd for vdex with +// the dex file. +// Expect: The status is kDex2oatFromScratch. +TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidVdexFd) { + std::string dex_location = GetScratchDir() + "/OatUpToDate.jar"; + std::string odex_location = GetScratchDir() + "/OatUpToDate.odex"; + + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), + odex_location.c_str(), + CompilerFilter::kSpeed, + true, + false, + false); + + android::base::unique_fd odex_fd(open(odex_location.c_str(), O_RDONLY)); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), + kRuntimeISA, + false, + -1, + odex_fd.get()); + EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); + EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus()); + EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus()); +} + +// Case: Passing valid vdex fd, however, invalid fd for odex with +// the dex file. +// Expect: The status is kDex2oatFromScratch. +TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexFd) { + std::string dex_location = GetScratchDir() + "/OatUpToDate.jar"; + std::string odex_location = GetScratchDir() + "/OatUpToDate.odex"; + std::string vdex_location = GetScratchDir() + "/OatUpToDate.vdex"; + + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), + odex_location.c_str(), + CompilerFilter::kSpeed, + true, + false, + false); + + android::base::unique_fd vdex_fd(open(vdex_location.c_str(), O_RDONLY)); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), + kRuntimeISA, + false, + vdex_fd.get(), + -1); + // Even though the vdex file is up to date, because we don't have the oat + // file, we can't know that the vdex depends on the boot image and is up to + // date with respect to the boot image. Instead we must assume the vdex file + // depends on the boot image and is out of date with respect to the boot + // image. + EXPECT_EQ(-OatFileAssistant::kDex2OatForBootImage, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); + EXPECT_EQ(OatFileAssistant::kOatBootImageOutOfDate, oat_file_assistant.OdexFileStatus()); + EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus()); +} + +// Case: Passing invalid vdex and odex fd with the dex file. +// Expect: The status is kDex2oatFromScratch. +TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexVdexFd) { + std::string dex_location = GetScratchDir() + "/OatUpToDate.jar"; + + Copy(GetDexSrc1(), dex_location); + + OatFileAssistant oat_file_assistant(dex_location.c_str(), + kRuntimeISA, + false, + -1, + -1); + EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch, + oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed)); + EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus()); + EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus()); +} + // Case: We have a DEX file and up-to-date OAT file for it. We load the dex file // via a symlink. // Expect: The status is kNoDexOptNeeded. diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc index 7cabae55e4..1e7cf723dc 100644 --- a/runtime/oat_file_manager.cc +++ b/runtime/oat_file_manager.cc @@ -31,6 +31,7 @@ #include "class_linker.h" #include "class_loader_context.h" #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "dex_file_tracking_registrar.h" #include "gc/scoped_gc_critical_section.h" #include "gc/space/image_space.h" @@ -94,7 +95,7 @@ const OatFile* OatFileManager::FindOpenedOatFileFromDexLocation( for (const std::unique_ptr<const OatFile>& oat_file : oat_files_) { const std::vector<const OatDexFile*>& oat_dex_files = oat_file->GetOatDexFiles(); for (const OatDexFile* oat_dex_file : oat_dex_files) { - if (DexFile::GetBaseLocation(oat_dex_file->GetDexFileLocation()) == dex_base_location) { + if (DexFileLoader::GetBaseLocation(oat_dex_file->GetDexFileLocation()) == dex_base_location) { return oat_file.get(); } } @@ -435,8 +436,13 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( // Update the oat file on disk if we can, based on the --compiler-filter // option derived from the current runtime options. // This may fail, but that's okay. Best effort is all that matters here. - switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/false, - context.get(), + // TODO(calin): b/64530081 b/66984396. Pass a null context to verify and compile + // secondary dex files in isolation (and avoid to extract/verify the main apk + // if it's in the class path). Note this trades correctness for performance + // since the resulting slow down is unacceptable in some cases until b/64530081 + // is fixed. + switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/ false, + /*class_loader_context*/ nullptr, /*out*/ &error_msg)) { case OatFileAssistant::kUpdateFailed: LOG(WARNING) << error_msg; @@ -591,7 +597,7 @@ std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( if (oat_file_assistant.HasOriginalDexFiles()) { if (Runtime::Current()->IsDexFileFallbackEnabled()) { static constexpr bool kVerifyChecksum = true; - if (!DexFile::Open( + if (!DexFileLoader::Open( dex_location, dex_location, kVerifyChecksum, /*out*/ &error_msg, &dex_files)) { LOG(WARNING) << error_msg; error_msgs->push_back("Failed to open dex files from " + std::string(dex_location) diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index 1d524fd5e6..9888186ed0 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -310,6 +310,9 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .Define("-XX:ThreadSuspendTimeout=_") // in ms .WithType<MillisecondsToNanoseconds>() // store as ns .IntoKey(M::ThreadSuspendTimeout) + .Define("-XX:GlobalRefAllocStackTraceLimit=_") // Number of free slots to enable tracing. + .WithType<unsigned int>() + .IntoKey(M::GlobalRefAllocStackTraceLimit) .Define("-XX:SlowDebug=_") .WithType<bool>() .WithValueMap({{"false", false}, {"true", true}}) diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 7c05cb6174..a4ed21e450 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -67,6 +67,7 @@ #include "class_linker-inl.h" #include "compiler_callbacks.h" #include "debugger.h" +#include "dex_file_loader.h" #include "elf_file.h" #include "entrypoints/runtime_asm_entrypoints.h" #include "experimental_flags.h" @@ -1020,7 +1021,7 @@ static size_t OpenDexFiles(const std::vector<std::string>& dex_filenames, LOG(WARNING) << "Skipping non-existent dex file '" << dex_filename << "'"; continue; } - if (!DexFile::Open(dex_filename, dex_location, kVerifyChecksum, &error_msg, dex_files)) { + if (!DexFileLoader::Open(dex_filename, dex_location, kVerifyChecksum, &error_msg, dex_files)) { LOG(WARNING) << "Failed to open .dex from file '" << dex_filename << "': " << error_msg; ++failure_count; } diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index 78a60faa3a..cafae22e8c 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -145,4 +145,6 @@ RUNTIME_OPTIONS_KEY (void (*)(), HookAbort, nullpt RUNTIME_OPTIONS_KEY (bool, SlowDebug, false) +RUNTIME_OPTIONS_KEY (unsigned int, GlobalRefAllocStackTraceLimit, 0) // 0 = off + #undef RUNTIME_OPTIONS_KEY diff --git a/runtime/utils.cc b/runtime/utils.cc index b72dec62bd..1f6bd742b6 100644 --- a/runtime/utils.cc +++ b/runtime/utils.cc @@ -48,6 +48,7 @@ #include "base/stl_util.h" #include "base/unix_file/fd_file.h" #include "dex_file-inl.h" +#include "dex_file_loader.h" #include "dex_instruction.h" #include "oat_quick_method_header.h" #include "os.h" @@ -858,7 +859,7 @@ bool GetDalvikCacheFilename(const char* location, const char* cache_location, !android::base::EndsWith(location, ".art") && !android::base::EndsWith(location, ".oat")) { cache_file += "/"; - cache_file += DexFile::kClassesDex; + cache_file += DexFileLoader::kClassesDex; } std::replace(cache_file.begin(), cache_file.end(), '/', '@'); *filename = StringPrintf("%s/%s", cache_location, cache_file.c_str()); diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc index b95522062e..55bc9ecac5 100644 --- a/runtime/vdex_file.cc +++ b/runtime/vdex_file.cc @@ -25,6 +25,7 @@ #include "base/stl_util.h" #include "base/unix_file/fd_file.h" #include "dex_file.h" +#include "dex_file_loader.h" #include "dex_to_dex_decompiler.h" namespace art { @@ -151,15 +152,15 @@ bool VdexFile::OpenAllDexFiles(std::vector<std::unique_ptr<const DexFile>>* dex_ size_t size = reinterpret_cast<const DexFile::Header*>(dex_file_start)->file_size_; // TODO: Supply the location information for a vdex file. static constexpr char kVdexLocation[] = ""; - std::string location = DexFile::GetMultiDexLocation(i, kVdexLocation); - std::unique_ptr<const DexFile> dex(DexFile::Open(dex_file_start, - size, - location, - GetLocationChecksum(i), - nullptr /*oat_dex_file*/, - false /*verify*/, - false /*verify_checksum*/, - error_msg)); + std::string location = DexFileLoader::GetMultiDexLocation(i, kVdexLocation); + std::unique_ptr<const DexFile> dex(DexFileLoader::Open(dex_file_start, + size, + location, + GetLocationChecksum(i), + nullptr /*oat_dex_file*/, + false /*verify*/, + false /*verify_checksum*/, + error_msg)); if (dex == nullptr) { return false; } diff --git a/test/1914-get-local-instance/expected.txt b/test/1914-get-local-instance/expected.txt index 4117942392..09f0df1937 100644 --- a/test/1914-get-local-instance/expected.txt +++ b/test/1914-get-local-instance/expected.txt @@ -10,3 +10,6 @@ Running public void art.Test1914$TargetClass.InstanceMethod(java.lang.Runnable) Running public native void art.Test1914$TargetClass.NativeInstanceMethod(java.lang.Runnable) with "GetThis" on remote thread. "GetThis" on public native void art.Test1914$TargetClass.NativeInstanceMethod(java.lang.Runnable) got value: TargetClass("NativeInstanceMethodObject") Value is 'TargetClass("NativeInstanceMethodObject")' (class: class art.Test1914$TargetClass) +Running public abstract void art.Test1914$Foo.InterfaceProxyMethod(java.lang.Runnable) with "GetThis" on remote thread. +"GetThis" on public abstract void art.Test1914$Foo.InterfaceProxyMethod(java.lang.Runnable) got value: Proxy for [interface art.Test1914$Foo] + Value is 'Proxy for [interface art.Test1914$Foo]' (class: PROXY CLASS) diff --git a/test/1914-get-local-instance/src/art/Test1914.java b/test/1914-get-local-instance/src/art/Test1914.java index c09f519db8..e47f9cbf38 100644 --- a/test/1914-get-local-instance/src/art/Test1914.java +++ b/test/1914-get-local-instance/src/art/Test1914.java @@ -18,7 +18,9 @@ package art; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; +import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.nio.ByteBuffer; import java.util.concurrent.Semaphore; import java.util.Arrays; @@ -35,7 +37,7 @@ public class Test1914 { public static void reportValue(Object val) { System.out.println("\tValue is '" + val + "' (class: " - + (val != null ? val.getClass() : "NULL") + ")"); + + (val != null ? (val instanceof Proxy ? "PROXY CLASS" : val.getClass()) : "NULL") + ")"); } public static void StaticMethod(Runnable safepoint) { @@ -151,7 +153,10 @@ public class Test1914 { private StackTrace.StackFrameData findStackFrame(Thread thr) { for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) { - if (frame.method.equals(target)) { + if (frame.method.equals(target) || + (frame.method.getName().equals(target.getName()) && + Arrays.deepEquals(frame.method.getParameterTypes(), target.getParameterTypes()) && + ((Method)frame.method).getReturnType().equals(target.getReturnType()))) { return frame; } } @@ -163,6 +168,25 @@ public class Test1914 { return klass.getDeclaredMethod(name, Runnable.class); } + public static interface Foo { + public void InterfaceProxyMethod(Runnable r); + } + + public static Object getProxyObject(final Class... k) { + return Proxy.newProxyInstance( + Test1914.class.getClassLoader(), + k, + (p, m, a) -> { + if (m.getName().equals("toString")) { + return "Proxy for " + Arrays.toString(k); + } else { + ((Runnable)a[0]).run(); + reportValue(p); + return null; + } + }); + } + public static void run() throws Exception { Locals.EnableLocalVariableAccess(); final TestCase[] MAIN_TEST_CASES = new TestCase[] { @@ -172,6 +196,8 @@ public class Test1914 { getMethod(TargetClass.class, "InstanceMethod")), new TestCase(new TargetClass("NativeInstanceMethodObject"), getMethod(TargetClass.class, "NativeInstanceMethod")), + new TestCase(getProxyObject(Foo.class), + getMethod(Foo.class, "InterfaceProxyMethod")), }; for (TestCase t: MAIN_TEST_CASES) { diff --git a/test/1929-exception-catch-exception/build b/test/1929-exception-catch-exception/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/1929-exception-catch-exception/build +++ b/test/1929-exception-catch-exception/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/1938-transform-abstract-single-impl/expected.txt b/test/1938-transform-abstract-single-impl/expected.txt new file mode 100644 index 0000000000..6a06f9baab --- /dev/null +++ b/test/1938-transform-abstract-single-impl/expected.txt @@ -0,0 +1,4 @@ +JNI_OnLoad called +Running sayHi() - hello +redefining TransformAbstract +Running sayHi() - Goodbye diff --git a/test/1938-transform-abstract-single-impl/info.txt b/test/1938-transform-abstract-single-impl/info.txt new file mode 100644 index 0000000000..5df8306981 --- /dev/null +++ b/test/1938-transform-abstract-single-impl/info.txt @@ -0,0 +1,2 @@ +Tests that single-implementation abstract methods don't crash the runtime when +their declaring class is redefined. diff --git a/test/1938-transform-abstract-single-impl/run b/test/1938-transform-abstract-single-impl/run new file mode 100755 index 0000000000..adb1a1c507 --- /dev/null +++ b/test/1938-transform-abstract-single-impl/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-run "$@" --jvmti --no-app-image diff --git a/test/1938-transform-abstract-single-impl/src/Main.java b/test/1938-transform-abstract-single-impl/src/Main.java new file mode 100644 index 0000000000..7ac2172eb4 --- /dev/null +++ b/test/1938-transform-abstract-single-impl/src/Main.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import art.Redefinition; +import java.util.Base64; +public class Main { + static abstract class TransformAbstract { + public abstract void doSayHi(); + + public void sayHi() { + System.out.println("hello"); + } + } + + static final class TransformConcrete extends TransformAbstract { + public final void doSayHi() { + System.out.print("Running sayHi() - "); + sayHi(); + } + } + + public static native void ensureJitCompiled(Class k, String m); + + /** + * base64 encoded class/dex file for + * static abstract class TransformAbstract { + * public abstract void doSayHi(); + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAIQoABgAPCQAQABEIABIKABMAFAcAFgcAGQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAdkb1NheUhpAQAFc2F5SGkBAApTb3VyY2VGaWxlAQAJTWFpbi5q" + + "YXZhDAAHAAgHABoMABsAHAEAB0dvb2RieWUHAB0MAB4AHwcAIAEAFk1haW4kVHJhbnNmb3JtQWJz" + + "dHJhY3QBABFUcmFuc2Zvcm1BYnN0cmFjdAEADElubmVyQ2xhc3NlcwEAEGphdmEvbGFuZy9PYmpl" + + "Y3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2" + + "YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAARNYWlu" + + "BCAABQAGAAAAAAADAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAHAQB" + + "AAsACAAAAAEADAAIAAEACQAAACUAAgABAAAACbIAAhIDtgAEsQAAAAEACgAAAAoAAgAAAB8ACAAg" + + "AAIADQAAAAIADgAYAAAACgABAAUAFQAXBAg="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCQkoTiKzIz0l96rtsnUxdY4Kwx+YINWFHEAwAAcAAAAHhWNBIAAAAAAAAAAAADAAAV" + + "AAAAcAAAAAkAAADEAAAAAgAAAOgAAAABAAAAAAEAAAUAAAAIAQAAAQAAADABAAB0AgAAUAEAAKoB" + + "AACyAQAAuwEAANUBAADdAQAAAQIAACECAAA4AgAATAIAAGACAAB0AgAAfwIAAJICAACVAgAAmQIA" + + "AKYCAACvAgAAtQIAALoCAADDAgAAygIAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAA" + + "DAAAAAwAAAAIAAAAAAAAAA0AAAAIAAAApAEAAAcABAARAAAAAAAAAAAAAAAAAAAADwAAAAAAAAAT" + + "AAAABAABABIAAAAFAAAAAAAAAAAAAAAABAAABQAAAAAAAAAKAAAAlAEAAOwCAAAAAAAAAgAAANwC" + + "AADiAgAAAQABAAEAAADRAgAABAAAAHAQBAAAAA4AAwABAAIAAADWAgAACAAAAGIAAAAaAQEAbiAD" + + "ABAADgBQAQAAAAAAAAAAAAAAAAAAAQAAAAYABjxpbml0PgAHR29vZGJ5ZQAYTE1haW4kVHJhbnNm" + + "b3JtQWJzdHJhY3Q7AAZMTWFpbjsAIkxkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsA" + + "HkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJM" + + "amF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07" + + "AAlNYWluLmphdmEAEVRyYW5zZm9ybUFic3RyYWN0AAFWAAJWTAALYWNjZXNzRmxhZ3MAB2RvU2F5" + + "SGkABG5hbWUAA291dAAHcHJpbnRsbgAFc2F5SGkABXZhbHVlABwABw4AHwAHDngAAgIBFBgBAgMC" + + "DiQIBBAXCwAAAQIAgIAE3AIBgQgAAQH0AgAAEAAAAAAAAAABAAAAAAAAAAEAAAAVAAAAcAAAAAIA" + + "AAAJAAAAxAAAAAMAAAACAAAA6AAAAAQAAAABAAAAAAEAAAUAAAAFAAAACAEAAAYAAAABAAAAMAEA" + + "AAMQAAABAAAAUAEAAAEgAAACAAAAXAEAAAYgAAABAAAAlAEAAAEQAAABAAAApAEAAAIgAAAVAAAA" + + "qgEAAAMgAAACAAAA0QIAAAQgAAACAAAA3AIAAAAgAAABAAAA7AIAAAAQAAABAAAAAAMAAA=="); + + public static void main(String[] args) throws Exception { + System.loadLibrary(args[0]); + Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE); + + ensureJitCompiled(TransformAbstract.class, "sayHi"); + ensureJitCompiled(TransformConcrete.class, "doSayHi"); + + TransformAbstract t1 = new TransformConcrete(); + t1.doSayHi(); + + assertSingleImplementation(TransformAbstract.class, "doSayHi", true); + + System.out.println("redefining TransformAbstract"); + Redefinition.doCommonClassRedefinition(TransformAbstract.class, CLASS_BYTES, DEX_BYTES); + + t1.doSayHi(); + } + + private static native boolean hasSingleImplementation(Class<?> clazz, String method_name); + private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) { + if (hasSingleImplementation(clazz, method_name) != b) { + System.out.println(clazz + "." + method_name + + " doesn't have single implementation value of " + b); + } + } +} diff --git a/test/1938-transform-abstract-single-impl/src/art/Redefinition.java b/test/1938-transform-abstract-single-impl/src/art/Redefinition.java new file mode 100644 index 0000000000..56d2938a01 --- /dev/null +++ b/test/1938-transform-abstract-single-impl/src/art/Redefinition.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.util.ArrayList; +// Common Redefinition functions. Placed here for use by CTS +public class Redefinition { + public static final class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } + } + + // A set of possible test configurations. Test should set this if they need to. + // This must be kept in sync with the defines in ti-agent/common_helper.cc + public static enum Config { + COMMON_REDEFINE(0), + COMMON_RETRANSFORM(1), + COMMON_TRANSFORM(2); + + private final int val; + private Config(int val) { + this.val = val; + } + } + + public static void setTestConfiguration(Config type) { + nativeSetTestConfiguration(type.val); + } + + private static native void nativeSetTestConfiguration(int type); + + // Transforms the class + public static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); + + public static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + public static void addMultiTransformationResults(CommonClassDefinition... defs) { + for (CommonClassDefinition d : defs) { + addCommonTransformationResult(d.target.getCanonicalName(), + d.class_file_bytes, + d.dex_file_bytes); + } + } + + public static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); + public static native void doCommonClassRetransformation(Class<?>... target); + public static native void setPopRetransformations(boolean pop); + public static native void popTransformationFor(String name); + public static native void enableCommonRetransformation(boolean enable); + public static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/1939-proxy-frames/expected.txt b/test/1939-proxy-frames/expected.txt new file mode 100644 index 0000000000..a4c97c9bbe --- /dev/null +++ b/test/1939-proxy-frames/expected.txt @@ -0,0 +1,8 @@ +Running public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) with "GetThis" on remote thread. +"GetThis" on public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) got value: Proxy for [interface art.Test1939$Foo] +Running public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) with "GetLocalReference0" on remote thread. +"GetLocalReference0" on public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) failed due to JVMTI_ERROR_OPAQUE_FRAME +Running public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) with "GetProxyFrameLocation" on remote thread. +"GetProxyFrameLocation" on public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) got value: -1 +Running public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) with "GetProxyFrameMethod" on remote thread. +"GetProxyFrameMethod" on public abstract void art.Test1939$Foo.InterfaceProxyMethod(java.lang.Runnable) got value: public final void $Proxy0.InterfaceProxyMethod(java.lang.Runnable) diff --git a/test/1939-proxy-frames/info.txt b/test/1939-proxy-frames/info.txt new file mode 100644 index 0000000000..9fc3d62cd6 --- /dev/null +++ b/test/1939-proxy-frames/info.txt @@ -0,0 +1,2 @@ +Test for jvmti get local instance + diff --git a/test/1939-proxy-frames/local_instance.cc b/test/1939-proxy-frames/local_instance.cc new file mode 100644 index 0000000000..dc833bfd90 --- /dev/null +++ b/test/1939-proxy-frames/local_instance.cc @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <iostream> +#include <pthread.h> +#include <stdio.h> +#include <vector> + +#include "android-base/logging.h" +#include "jni.h" +#include "scoped_local_ref.h" +#include "scoped_primitive_array.h" + +#include "jvmti.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test1939ProxyFrames { + +extern "C" JNIEXPORT jobject Java_art_Test1939_GetFrameMethod(JNIEnv* env, + jclass, + jthread thr, + jint depth) { + jmethodID m = nullptr; + jlong loc = -1; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameLocation(thr, depth, &m, &loc))) { + return nullptr; + } + jclass klass = nullptr; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetMethodDeclaringClass(m, &klass))) { + return nullptr; + } + jobject res = env->ToReflectedMethod(klass, m, false); + env->DeleteLocalRef(klass); + return res; +} + +extern "C" JNIEXPORT jlong Java_art_Test1939_GetFrameLocation(JNIEnv* env, + jclass, + jthread thr, + jint depth) { + jmethodID m = nullptr; + jlong loc = -1; + JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameLocation(thr, depth, &m, &loc)); + return loc; +} + +} // namespace Test1939ProxyFrames +} // namespace art + diff --git a/test/1939-proxy-frames/run b/test/1939-proxy-frames/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/1939-proxy-frames/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/1939-proxy-frames/src/Main.java b/test/1939-proxy-frames/src/Main.java new file mode 100644 index 0000000000..85cab34d53 --- /dev/null +++ b/test/1939-proxy-frames/src/Main.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1939.run(); + } +} diff --git a/test/1939-proxy-frames/src/art/Breakpoint.java b/test/1939-proxy-frames/src/art/Breakpoint.java new file mode 100644 index 0000000000..bbb89f707f --- /dev/null +++ b/test/1939-proxy-frames/src/art/Breakpoint.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.HashSet; +import java.util.Set; +import java.util.Objects; + +public class Breakpoint { + public static class Manager { + public static class BP { + public final Executable method; + public final long location; + + public BP(Executable method) { + this(method, getStartLocation(method)); + } + + public BP(Executable method, long location) { + this.method = method; + this.location = location; + } + + @Override + public boolean equals(Object other) { + return (other instanceof BP) && + method.equals(((BP)other).method) && + location == ((BP)other).location; + } + + @Override + public String toString() { + return method.toString() + " @ " + getLine(); + } + + @Override + public int hashCode() { + return Objects.hash(method, location); + } + + public int getLine() { + try { + LineNumber[] lines = getLineNumberTable(method); + int best = -1; + for (LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + } + + private Set<BP> breaks = new HashSet<>(); + + public void setBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.add(b)) { + Breakpoint.setBreakpoint(b.method, b.location); + } + } + } + public void setBreakpoint(Executable method, long location) { + setBreakpoints(new BP(method, location)); + } + + public void clearBreakpoints(BP... bs) { + for (BP b : bs) { + if (breaks.remove(b)) { + Breakpoint.clearBreakpoint(b.method, b.location); + } + } + } + public void clearBreakpoint(Executable method, long location) { + clearBreakpoints(new BP(method, location)); + } + + public void clearAllBreakpoints() { + clearBreakpoints(breaks.toArray(new BP[0])); + } + } + + public static void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + Thread thr) { + startBreakpointWatch(methodClass, breakpointReached, false, thr); + } + + /** + * Enables the trapping of breakpoint events. + * + * If allowRecursive == true then breakpoints will be sent even if one is currently being handled. + */ + public static native void startBreakpointWatch(Class<?> methodClass, + Executable breakpointReached, + boolean allowRecursive, + Thread thr); + public static native void stopBreakpointWatch(Thread thr); + + public static final class LineNumber implements Comparable<LineNumber> { + public final long location; + public final int line; + + private LineNumber(long loc, int line) { + this.location = loc; + this.line = line; + } + + public boolean equals(Object other) { + return other instanceof LineNumber && ((LineNumber)other).line == line && + ((LineNumber)other).location == location; + } + + public int compareTo(LineNumber other) { + int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line)); + if (v != 0) { + return v; + } else { + return Long.valueOf(location).compareTo(Long.valueOf(other.location)); + } + } + } + + public static native void setBreakpoint(Executable m, long loc); + public static void setBreakpoint(Executable m, LineNumber l) { + setBreakpoint(m, l.location); + } + + public static native void clearBreakpoint(Executable m, long loc); + public static void clearBreakpoint(Executable m, LineNumber l) { + clearBreakpoint(m, l.location); + } + + private static native Object[] getLineNumberTableNative(Executable m); + public static LineNumber[] getLineNumberTable(Executable m) { + Object[] nativeTable = getLineNumberTableNative(m); + long[] location = (long[])(nativeTable[0]); + int[] lines = (int[])(nativeTable[1]); + if (lines.length != location.length) { + throw new Error("Lines and locations have different lengths!"); + } + LineNumber[] out = new LineNumber[lines.length]; + for (int i = 0; i < lines.length; i++) { + out[i] = new LineNumber(location[i], lines[i]); + } + return out; + } + + public static native long getStartLocation(Executable m); + + public static int locationToLine(Executable m, long location) { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + int best = -1; + for (Breakpoint.LineNumber l : lines) { + if (l.location > location) { + break; + } else { + best = l.line; + } + } + return best; + } catch (Exception e) { + return -1; + } + } + + public static long lineToLocation(Executable m, int line) throws Exception { + try { + Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m); + for (Breakpoint.LineNumber l : lines) { + if (l.line == line) { + return l.location; + } + } + throw new Exception("Unable to find line " + line + " in " + m); + } catch (Exception e) { + throw new Exception("Unable to get line number info for " + m, e); + } + } +} + diff --git a/test/1939-proxy-frames/src/art/Locals.java b/test/1939-proxy-frames/src/art/Locals.java new file mode 100644 index 0000000000..22e21be398 --- /dev/null +++ b/test/1939-proxy-frames/src/art/Locals.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Executable; +import java.util.Objects; + +public class Locals { + public static native void EnableLocalVariableAccess(); + + public static class VariableDescription { + public final long start_location; + public final int length; + public final String name; + public final String signature; + public final String generic_signature; + public final int slot; + + public VariableDescription( + long start, int length, String name, String sig, String gen_sig, int slot) { + this.start_location = start; + this.length = length; + this.name = name; + this.signature = sig; + this.generic_signature = gen_sig; + this.slot = slot; + } + + @Override + public String toString() { + return String.format( + "VariableDescription { " + + "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" + + "}", + this.signature, + this.name, + this.generic_signature, + this.slot, + this.start_location, + this.length); + } + public boolean equals(Object other) { + if (!(other instanceof VariableDescription)) { + return false; + } else { + VariableDescription v = (VariableDescription)other; + return Objects.equals(v.signature, signature) && + Objects.equals(v.name, name) && + Objects.equals(v.generic_signature, generic_signature) && + v.slot == slot && + v.start_location == start_location && + v.length == length; + } + } + public int hashCode() { + return Objects.hash(this.signature, this.name, this.generic_signature, this.slot, + this.start_location, this.length); + } + } + + public static native VariableDescription[] GetLocalVariableTable(Executable e); + + public static VariableDescription GetVariableAtLine( + Executable e, String name, String sig, int line) throws Exception { + return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line)); + } + + public static VariableDescription GetVariableAtLocation( + Executable e, String name, String sig, long loc) { + VariableDescription[] vars = GetLocalVariableTable(e); + for (VariableDescription var : vars) { + if (var.start_location <= loc && + var.length + var.start_location > loc && + var.name.equals(name) && + var.signature.equals(sig)) { + return var; + } + } + throw new Error( + "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc); + } + + public static native int GetLocalVariableInt(Thread thr, int depth, int slot); + public static native long GetLocalVariableLong(Thread thr, int depth, int slot); + public static native float GetLocalVariableFloat(Thread thr, int depth, int slot); + public static native double GetLocalVariableDouble(Thread thr, int depth, int slot); + public static native Object GetLocalVariableObject(Thread thr, int depth, int slot); + public static native Object GetLocalInstance(Thread thr, int depth); + + public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) { + SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue()); + } + public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) { + SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue()); + } + public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) { + SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue()); + } + public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) { + SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue()); + } + public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val); + public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val); + public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val); + public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val); + public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val); +} diff --git a/test/1939-proxy-frames/src/art/StackTrace.java b/test/1939-proxy-frames/src/art/StackTrace.java new file mode 100644 index 0000000000..2ea2f201e8 --- /dev/null +++ b/test/1939-proxy-frames/src/art/StackTrace.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Executable; + +public class StackTrace { + public static class StackFrameData { + public final Thread thr; + public final Executable method; + public final long current_location; + public final int depth; + + public StackFrameData(Thread thr, Executable e, long loc, int depth) { + this.thr = thr; + this.method = e; + this.current_location = loc; + this.depth = depth; + } + @Override + public String toString() { + return String.format( + "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }", + this.thr, + this.method, + this.current_location, + this.depth); + } + } + + public static native int GetStackDepth(Thread thr); + + private static native StackFrameData[] nativeGetStackTrace(Thread thr); + + public static StackFrameData[] GetStackTrace(Thread thr) { + // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not + // suspended. The spec says that not being suspended is fine but since we want this to be + // consistent we will suspend for the RI. + boolean suspend_thread = + !System.getProperty("java.vm.name").equals("Dalvik") && + !thr.equals(Thread.currentThread()) && + !Suspension.isSuspended(thr); + if (suspend_thread) { + Suspension.suspend(thr); + } + StackFrameData[] out = nativeGetStackTrace(thr); + if (suspend_thread) { + Suspension.resume(thr); + } + return out; + } +} + diff --git a/test/1939-proxy-frames/src/art/Suspension.java b/test/1939-proxy-frames/src/art/Suspension.java new file mode 100644 index 0000000000..16e62ccac9 --- /dev/null +++ b/test/1939-proxy-frames/src/art/Suspension.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +public class Suspension { + // Suspends a thread using jvmti. + public native static void suspend(Thread thr); + + // Resumes a thread using jvmti. + public native static void resume(Thread thr); + + public native static boolean isSuspended(Thread thr); + + public native static int[] suspendList(Thread... threads); + public native static int[] resumeList(Thread... threads); +} diff --git a/test/1939-proxy-frames/src/art/Test1939.java b/test/1939-proxy-frames/src/art/Test1939.java new file mode 100644 index 0000000000..83d0d2ca4b --- /dev/null +++ b/test/1939-proxy-frames/src/art/Test1939.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package art; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.Consumer; + +public class Test1939 { + public static interface SafepointFunction { + public void invoke( + Thread thread, + Method target, + int depth) throws Exception; + } + + public static interface GetterFunction { + public Object GetVar(Thread t, int depth); + } + + public static SafepointFunction NamedGet(final String type, final GetterFunction get) { + return new SafepointFunction() { + public void invoke(Thread t, Method method, int depth) { + try { + Object res = get.GetVar(t, depth); + System.out.println(this + " on " + method + " got value: " + res); + } catch (Exception e) { + System.out.println(this + " on " + method + " failed due to " + e.getMessage()); + } + } + public String toString() { + return "\"Get" + type + "\""; + } + }; + } + + public static class TestCase { + public final Object thiz; + public final Method target; + + public TestCase(Method target) { + this(null, target); + } + public TestCase(Object thiz, Method target) { + this.thiz = thiz; + this.target = target; + } + + public static class ThreadPauser implements Runnable { + public final Semaphore sem_wakeup_main; + public final Semaphore sem_wait; + + public ThreadPauser() { + sem_wakeup_main = new Semaphore(0); + sem_wait = new Semaphore(0); + } + + public void run() { + try { + sem_wakeup_main.release(); + sem_wait.acquire(); + } catch (Exception e) { + throw new Error("Error with semaphores!", e); + } + } + + public void waitForOtherThreadToPause() throws Exception { + sem_wakeup_main.acquire(); + } + + public void wakeupOtherThread() throws Exception { + sem_wait.release(); + } + } + + public void exec(final SafepointFunction safepoint) throws Exception { + System.out.println("Running " + target + " with " + safepoint + " on remote thread."); + final ThreadPauser pause = new ThreadPauser(); + Thread remote = new Thread( + () -> { + try { + target.invoke(thiz, pause); + } catch (Exception e) { + throw new Error("Error invoking remote thread " + Thread.currentThread(), e); + } + }, + "remote thread for " + target + " with " + safepoint); + remote.start(); + pause.waitForOtherThreadToPause(); + try { + Suspension.suspend(remote); + StackTrace.StackFrameData frame = findStackFrame(remote); + safepoint.invoke(remote, target, frame.depth); + } finally { + Suspension.resume(remote); + pause.wakeupOtherThread(); + remote.join(); + } + } + + private StackTrace.StackFrameData findStackFrame(Thread thr) { + for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) { + if (frame.method.equals(target) || + (frame.method.getName().equals(target.getName()) && + Arrays.deepEquals(frame.method.getParameterTypes(), target.getParameterTypes()) && + ((Method)frame.method).getReturnType().equals(target.getReturnType()))) { + return frame; + } + } + throw new Error("Unable to find stack frame in method " + target + " on thread " + thr); + } + } + + public static Method getMethod(Class<?> klass, String name) throws Exception { + return klass.getDeclaredMethod(name, Runnable.class); + } + + public static interface Foo { + public void InterfaceProxyMethod(Runnable r); + } + + public static Object getProxyObject(final Class... k) { + return Proxy.newProxyInstance( + Test1939.class.getClassLoader(), + k, + (p, m, a) -> { + if (m.getName().equals("toString")) { + return "Proxy for " + Arrays.toString(k); + } else { + ((Runnable)a[0]).run(); + return null; + } + }); + } + + public static void run() throws Exception { + Locals.EnableLocalVariableAccess(); + TestCase test = new TestCase( + getProxyObject(Foo.class), getMethod(Foo.class, "InterfaceProxyMethod")); + test.exec(NamedGet("This", Locals::GetLocalInstance)); + test.exec(NamedGet("LocalReference0", (t, d) -> Locals.GetLocalVariableObject(t, d, 0))); + test.exec(NamedGet("ProxyFrameLocation", (t, d) -> Long.valueOf(GetFrameLocation(t, d)))); + test.exec(NamedGet("ProxyFrameMethod", Test1939::GetFrameMethod)); + } + + public static native long GetFrameLocation(Thread thr, int depth); + public static native Executable GetFrameMethod(Thread thr, int depth); +} + diff --git a/test/450-checker-types/build b/test/450-checker-types/build index 947ec9a560..3721955670 100755 --- a/test/450-checker-types/build +++ b/test/450-checker-types/build @@ -21,6 +21,6 @@ export USE_JACK=false export DESUGAR=false # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/458-checker-instruct-simplification/build b/test/458-checker-instruct-simplification/build index 947ec9a560..3721955670 100755 --- a/test/458-checker-instruct-simplification/build +++ b/test/458-checker-instruct-simplification/build @@ -21,6 +21,6 @@ export USE_JACK=false export DESUGAR=false # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/463-checker-boolean-simplifier/build b/test/463-checker-boolean-simplifier/build index 947ec9a560..3721955670 100755 --- a/test/463-checker-boolean-simplifier/build +++ b/test/463-checker-boolean-simplifier/build @@ -21,6 +21,6 @@ export USE_JACK=false export DESUGAR=false # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/476-checker-ctor-fence-redun-elim/build b/test/476-checker-ctor-fence-redun-elim/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/476-checker-ctor-fence-redun-elim/build +++ b/test/476-checker-ctor-fence-redun-elim/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/482-checker-loop-back-edge-use/build b/test/482-checker-loop-back-edge-use/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/482-checker-loop-back-edge-use/build +++ b/test/482-checker-loop-back-edge-use/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/484-checker-register-hints/build b/test/484-checker-register-hints/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/484-checker-register-hints/build +++ b/test/484-checker-register-hints/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/530-checker-lse/build b/test/530-checker-lse/build index 42b99ad9f8..10ffcc537d 100755 --- a/test/530-checker-lse/build +++ b/test/530-checker-lse/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/549-checker-types-merge/build b/test/549-checker-types-merge/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/549-checker-types-merge/build +++ b/test/549-checker-types-merge/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/565-checker-doublenegbitwise/build b/test/565-checker-doublenegbitwise/build index 947ec9a560..3721955670 100755 --- a/test/565-checker-doublenegbitwise/build +++ b/test/565-checker-doublenegbitwise/build @@ -21,6 +21,6 @@ export USE_JACK=false export DESUGAR=false # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/565-checker-rotate/build b/test/565-checker-rotate/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/565-checker-rotate/build +++ b/test/565-checker-rotate/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/566-checker-signum/build b/test/566-checker-signum/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/566-checker-signum/build +++ b/test/566-checker-signum/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/567-checker-compare/build b/test/567-checker-compare/build index 1d269dcdb2..10ffcc537d 100644 --- a/test/567-checker-compare/build +++ b/test/567-checker-compare/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/570-checker-osr/build b/test/570-checker-osr/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/570-checker-osr/build +++ b/test/570-checker-osr/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/586-checker-null-array-get/build b/test/586-checker-null-array-get/build index 947ec9a560..3721955670 100755 --- a/test/586-checker-null-array-get/build +++ b/test/586-checker-null-array-get/build @@ -21,6 +21,6 @@ export USE_JACK=false export DESUGAR=false # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/593-checker-boolean-2-integral-conv/build b/test/593-checker-boolean-2-integral-conv/build index 947ec9a560..3721955670 100755 --- a/test/593-checker-boolean-2-integral-conv/build +++ b/test/593-checker-boolean-2-integral-conv/build @@ -21,6 +21,6 @@ export USE_JACK=false export DESUGAR=false # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/597-deopt-invoke-stub/run b/test/597-deopt-invoke-stub/run index bc04498bfe..53b7c4cc71 100644 --- a/test/597-deopt-invoke-stub/run +++ b/test/597-deopt-invoke-stub/run @@ -14,5 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# We want to run in debuggable mode and compiled. -exec ${RUN} --jit -Xcompiler-option --debuggable "${@}" +# In order to test deoptimizing at quick-to-interpreter bridge, +# we want to run in debuggable mode with jit compilation. +# We also bump up the jit threshold to 10 to make sure that the method +# that should be interpreted is not compiled. +exec ${RUN} --jit --runtime-option -Xjitthreshold:10000 -Xcompiler-option --debuggable "${@}" diff --git a/test/611-checker-simplify-if/build b/test/611-checker-simplify-if/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/611-checker-simplify-if/build +++ b/test/611-checker-simplify-if/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/618-checker-induction/build b/test/618-checker-induction/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/618-checker-induction/build +++ b/test/618-checker-induction/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/910-methods/build b/test/910-methods/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/910-methods/build +++ b/test/910-methods/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/910-methods/check b/test/910-methods/check index f9552ada26..e6f7d7773f 100644 --- a/test/910-methods/check +++ b/test/910-methods/check @@ -19,7 +19,7 @@ if [[ "$USE_JACK" == true ]]; then patch -p0 expected.txt < expected_jack.diff fi -if [[ "$DX" == 'd8' ]]; then +if [[ "$USE_D8" == true ]]; then patch -p0 expected.txt < expected_d8.diff fi diff --git a/test/911-get-stack-trace/build b/test/911-get-stack-trace/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/911-get-stack-trace/build +++ b/test/911-get-stack-trace/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/913-heaps/build b/test/913-heaps/build index 42b99ad9f8..10ffcc537d 100644 --- a/test/913-heaps/build +++ b/test/913-heaps/build @@ -15,6 +15,6 @@ # limitations under the License. # See b/65168732 -export DX=$ANDROID_HOST_OUT/bin/dx +export USE_D8=false ./default-build "$@" diff --git a/test/983-source-transform-verify/source_transform.cc b/test/983-source-transform-verify/source_transform.cc index 570ade364d..ef67acec98 100644 --- a/test/983-source-transform-verify/source_transform.cc +++ b/test/983-source-transform-verify/source_transform.cc @@ -29,6 +29,7 @@ #include "base/macros.h" #include "bytecode_utils.h" #include "dex_file.h" +#include "dex_file_loader.h" #include "dex_instruction.h" #include "jit/jit.h" #include "native_stack_dump.h" @@ -66,14 +67,14 @@ void JNICALL CheckDexFileHook(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED, return; } std::string error; - std::unique_ptr<const DexFile> dex(DexFile::Open(class_data, - class_data_len, - "fake_location.dex", - /*location_checksum*/ 0, - /*oat_dex_file*/ nullptr, - /*verify*/ true, - /*verify_checksum*/ true, - &error)); + std::unique_ptr<const DexFile> dex(DexFileLoader::Open(class_data, + class_data_len, + "fake_location.dex", + /*location_checksum*/ 0, + /*oat_dex_file*/ nullptr, + /*verify*/ true, + /*verify_checksum*/ true, + &error)); if (dex.get() == nullptr) { std::cout << "Failed to verify dex file for " << name << " because " << error << std::endl; return; diff --git a/test/Android.bp b/test/Android.bp index 31474d5107..b737345729 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -277,6 +277,7 @@ art_cc_defaults { "1930-monitor-info/monitor.cc", "1932-monitor-events-misc/monitor_misc.cc", "1934-jvmti-signal-thread/signal_threads.cc", + "1939-proxy-frames/local_instance.cc", ], shared_libs: [ "libbase", diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index d37e6bc4f3..9e1afc8cb5 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -71,9 +71,9 @@ VDEX_FILTER="" PROFILE="n" RANDOM_PROFILE="n" # The normal dex2oat timeout. -DEX2OAT_TIMEOUT="60" +DEX2OAT_TIMEOUT="300" # 5 mins # The *hard* timeout where we really start trying to kill the dex2oat. -DEX2OAT_RT_TIMEOUT="90" +DEX2OAT_RT_TIMEOUT="360" # 6 mins # if "y", set -Xstacktracedir and inform the test of its location. When # this is set, stack trace dumps (from signal 3) will be written to a file diff --git a/test/knownfailures.json b/test/knownfailures.json index 229d618a2d..47b2f22a3c 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -1,5 +1,12 @@ [ { + "tests": [ "1939-proxy-frames", "1914-get-local-instance" ], + "description": ["Test 1939 & 1914 seems to consistently fail in gcstress on 64 bit with", + "a proxy this object having no associated class!"], + "variant": "gcstress", + "bug": "http://b/67679263" + }, + { "tests": "1934-jvmti-signal-thread", "description": ["Disables 1934-jvmti-signal-thread in tracing configurations"], "variant": "trace | stream", @@ -237,7 +244,7 @@ }, { "tests": "597-deopt-invoke-stub", - "variant": "interp-ac | interpreter | optimizing | trace | stream", + "variant": "speed-profile | interp-ac | interpreter | optimizing | trace | stream", "description": ["This test expects JIT compilation and no AOT for", "testing deoptimizing at quick-to-interpreter bridge."] }, diff --git a/test/testrunner/run_build_test_target.py b/test/testrunner/run_build_test_target.py index 49444d42cb..492b792239 100755 --- a/test/testrunner/run_build_test_target.py +++ b/test/testrunner/run_build_test_target.py @@ -96,7 +96,9 @@ if target.has_key('run-test'): run_test_command = [os.path.join(env.ANDROID_BUILD_TOP, 'art/test/testrunner/testrunner.py')] run_test_command += target.get('run-test', []) - run_test_command += ['-j', str(n_threads)] + # Let testrunner compute concurrency based on #cpus. + # b/65822340 + # run_test_command += ['-j', str(n_threads)] run_test_command += ['-b'] run_test_command += ['--host'] run_test_command += ['--verbose'] diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py index 929fca6b5d..ca29d0a484 100755 --- a/test/testrunner/testrunner.py +++ b/test/testrunner/testrunner.py @@ -237,6 +237,7 @@ def setup_test_env(): n_thread = get_default_threads('target') else: n_thread = get_default_threads('host') + print_text("Concurrency: " + str(n_thread) + "\n") global semaphore semaphore = threading.Semaphore(n_thread) @@ -480,7 +481,7 @@ def run_test(command, test, test_variant, test_name): if test_passed: print_test_info(test_name, 'PASS') else: - failed_tests.append((test_name, script_output)) + failed_tests.append((test_name, str(command) + "\n" + script_output)) if not env.ART_TEST_KEEP_GOING: stop_testrunner = True print_test_info(test_name, 'FAIL', ('%s\n%s') % ( @@ -535,10 +536,17 @@ def print_test_info(test_name, result, failed_test_info=""): total_test_count) if result == 'FAIL' or result == 'TIMEOUT': - info += ('%s %s %s\n') % ( - progress_info, - test_name, - COLOR_ERROR + result + COLOR_NORMAL) + if not verbose: + info += ('%s %s %s\n') % ( + progress_info, + test_name, + COLOR_ERROR + result + COLOR_NORMAL) + else: + info += ('%s %s %s\n%s\n') % ( + progress_info, + test_name, + COLOR_ERROR + result + COLOR_NORMAL, + failed_test_info) else: result_text = '' if result == 'PASS': diff --git a/tools/ahat/Android.mk b/tools/ahat/Android.mk index cf31e2e5bd..5eccba1327 100644 --- a/tools/ahat/Android.mk +++ b/tools/ahat/Android.mk @@ -20,9 +20,9 @@ include art/build/Android.common_path.mk # --- ahat.jar ---------------- include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_JAR_MANIFEST := src/manifest.txt -LOCAL_JAVA_RESOURCE_FILES := $(LOCAL_PATH)/src/style.css +LOCAL_SRC_FILES := $(call all-java-files-under, src/main) +LOCAL_JAR_MANIFEST := etc/ahat.mf +LOCAL_JAVA_RESOURCE_FILES := $(LOCAL_PATH)/etc/style.css LOCAL_IS_HOST_MODULE := true LOCAL_MODULE_TAGS := optional LOCAL_MODULE := ahat @@ -49,9 +49,9 @@ ifneq ($(EMMA_INSTRUMENT),true) include $(CLEAR_VARS) LOCAL_MODULE := ahat-test-dump LOCAL_MODULE_TAGS := tests -LOCAL_SRC_FILES := $(call all-java-files-under, test-dump) +LOCAL_SRC_FILES := $(call all-java-files-under, src/test-dump) LOCAL_PROGUARD_ENABLED := obfuscation -LOCAL_PROGUARD_FLAG_FILES := test-dump/config.pro +LOCAL_PROGUARD_FLAG_FILES := etc/test-dump.pro include $(BUILD_JAVA_LIBRARY) # Determine the location of the test-dump.jar, test-dump.hprof, and proguard @@ -87,15 +87,15 @@ $(AHAT_TEST_DUMP_BASE_HPROF): $(AHAT_TEST_DUMP_JAR) $(AHAT_TEST_DUMP_DEPENDENCIE # --- ahat-tests.jar -------------- include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-java-files-under, test) -LOCAL_JAR_MANIFEST := test/manifest.txt +LOCAL_SRC_FILES := $(call all-java-files-under, src/test) +LOCAL_JAR_MANIFEST := etc/ahat-tests.mf LOCAL_JAVA_RESOURCE_FILES := \ $(AHAT_TEST_DUMP_HPROF) \ $(AHAT_TEST_DUMP_BASE_HPROF) \ $(AHAT_TEST_DUMP_PROGUARD_MAP) \ - $(LOCAL_PATH)/test-dump/L.hprof \ - $(LOCAL_PATH)/test-dump/O.hprof \ - $(LOCAL_PATH)/test-dump/RI.hprof + $(LOCAL_PATH)/etc/L.hprof \ + $(LOCAL_PATH)/etc/O.hprof \ + $(LOCAL_PATH)/etc/RI.hprof LOCAL_STATIC_JAVA_LIBRARIES := ahat junit-host LOCAL_IS_HOST_MODULE := true LOCAL_MODULE_TAGS := tests diff --git a/tools/ahat/test-dump/L.hprof b/tools/ahat/etc/L.hprof Binary files differindex 1acdf7965d..1acdf7965d 100644 --- a/tools/ahat/test-dump/L.hprof +++ b/tools/ahat/etc/L.hprof diff --git a/tools/ahat/test-dump/O.hprof b/tools/ahat/etc/O.hprof Binary files differindex d474c6c6b4..d474c6c6b4 100644 --- a/tools/ahat/test-dump/O.hprof +++ b/tools/ahat/etc/O.hprof diff --git a/tools/ahat/etc/README.txt b/tools/ahat/etc/README.txt new file mode 100644 index 0000000000..e9b5b22dae --- /dev/null +++ b/tools/ahat/etc/README.txt @@ -0,0 +1,9 @@ +L.hprof + A version of the test-dump hprof generated on Android L, with one of the + ROOT_DEBUGGER records manually changed to a ROOT_FINALIZING record. + +O.hprof + A version of the test-dump hprof generated on Android O. + +RI.hprof + A version of the test-dump hprof generated on the reference implementation. diff --git a/tools/ahat/test-dump/RI.hprof b/tools/ahat/etc/RI.hprof Binary files differindex 9482542a7f..9482542a7f 100644 --- a/tools/ahat/test-dump/RI.hprof +++ b/tools/ahat/etc/RI.hprof diff --git a/tools/ahat/test/manifest.txt b/tools/ahat/etc/ahat-tests.mf index af17fadded..af17fadded 100644 --- a/tools/ahat/test/manifest.txt +++ b/tools/ahat/etc/ahat-tests.mf diff --git a/tools/ahat/src/manifest.txt b/tools/ahat/etc/ahat.mf index 1753406e6e..1753406e6e 100644 --- a/tools/ahat/src/manifest.txt +++ b/tools/ahat/etc/ahat.mf diff --git a/tools/ahat/src/style.css b/tools/ahat/etc/style.css index 47fae1d551..47fae1d551 100644 --- a/tools/ahat/src/style.css +++ b/tools/ahat/etc/style.css diff --git a/tools/ahat/test-dump/config.pro b/tools/ahat/etc/test-dump.pro index 284e4b8621..284e4b8621 100644 --- a/tools/ahat/test-dump/config.pro +++ b/tools/ahat/etc/test-dump.pro diff --git a/tools/ahat/src/AhatHandler.java b/tools/ahat/src/main/com/android/ahat/AhatHandler.java index d4b4d1b107..d4b4d1b107 100644 --- a/tools/ahat/src/AhatHandler.java +++ b/tools/ahat/src/main/com/android/ahat/AhatHandler.java diff --git a/tools/ahat/src/AhatHttpHandler.java b/tools/ahat/src/main/com/android/ahat/AhatHttpHandler.java index 1d05a66653..1d05a66653 100644 --- a/tools/ahat/src/AhatHttpHandler.java +++ b/tools/ahat/src/main/com/android/ahat/AhatHttpHandler.java diff --git a/tools/ahat/src/BitmapHandler.java b/tools/ahat/src/main/com/android/ahat/BitmapHandler.java index 836aef67b8..836aef67b8 100644 --- a/tools/ahat/src/BitmapHandler.java +++ b/tools/ahat/src/main/com/android/ahat/BitmapHandler.java diff --git a/tools/ahat/src/Column.java b/tools/ahat/src/main/com/android/ahat/Column.java index 819e586ef9..819e586ef9 100644 --- a/tools/ahat/src/Column.java +++ b/tools/ahat/src/main/com/android/ahat/Column.java diff --git a/tools/ahat/src/Doc.java b/tools/ahat/src/main/com/android/ahat/Doc.java index 5a70c4c74b..5a70c4c74b 100644 --- a/tools/ahat/src/Doc.java +++ b/tools/ahat/src/main/com/android/ahat/Doc.java diff --git a/tools/ahat/src/DocString.java b/tools/ahat/src/main/com/android/ahat/DocString.java index 76e9e80d46..76e9e80d46 100644 --- a/tools/ahat/src/DocString.java +++ b/tools/ahat/src/main/com/android/ahat/DocString.java diff --git a/tools/ahat/src/DominatedList.java b/tools/ahat/src/main/com/android/ahat/DominatedList.java index 75133b2184..75133b2184 100644 --- a/tools/ahat/src/DominatedList.java +++ b/tools/ahat/src/main/com/android/ahat/DominatedList.java diff --git a/tools/ahat/src/HeapTable.java b/tools/ahat/src/main/com/android/ahat/HeapTable.java index b04f2aebf7..b04f2aebf7 100644 --- a/tools/ahat/src/HeapTable.java +++ b/tools/ahat/src/main/com/android/ahat/HeapTable.java diff --git a/tools/ahat/src/HtmlDoc.java b/tools/ahat/src/main/com/android/ahat/HtmlDoc.java index 5a22fc75fe..5a22fc75fe 100644 --- a/tools/ahat/src/HtmlDoc.java +++ b/tools/ahat/src/main/com/android/ahat/HtmlDoc.java diff --git a/tools/ahat/src/HtmlEscaper.java b/tools/ahat/src/main/com/android/ahat/HtmlEscaper.java index 75a68277d3..75a68277d3 100644 --- a/tools/ahat/src/HtmlEscaper.java +++ b/tools/ahat/src/main/com/android/ahat/HtmlEscaper.java diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/main/com/android/ahat/Main.java index 31c485d851..31c485d851 100644 --- a/tools/ahat/src/Main.java +++ b/tools/ahat/src/main/com/android/ahat/Main.java diff --git a/tools/ahat/src/Menu.java b/tools/ahat/src/main/com/android/ahat/Menu.java index 6d38dc5731..6d38dc5731 100644 --- a/tools/ahat/src/Menu.java +++ b/tools/ahat/src/main/com/android/ahat/Menu.java diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/main/com/android/ahat/ObjectHandler.java index 79f8b76c92..bfd5d5cacd 100644 --- a/tools/ahat/src/ObjectHandler.java +++ b/tools/ahat/src/main/com/android/ahat/ObjectHandler.java @@ -19,6 +19,7 @@ package com.android.ahat; import com.android.ahat.heapdump.AhatArrayInstance; import com.android.ahat.heapdump.AhatClassInstance; import com.android.ahat.heapdump.AhatClassObj; +import com.android.ahat.heapdump.AhatHeap; import com.android.ahat.heapdump.AhatInstance; import com.android.ahat.heapdump.AhatSnapshot; import com.android.ahat.heapdump.DiffFields; @@ -66,7 +67,10 @@ class ObjectHandler implements AhatHandler { doc.big(Summarizer.summarize(inst)); printAllocationSite(doc, query, inst); - printGcRootPath(doc, query, inst); + + if (!inst.isUnreachable()) { + printGcRootPath(doc, query, inst); + } doc.section("Object Info"); AhatClassObj cls = inst.getClassObj(); @@ -257,23 +261,54 @@ class ObjectHandler implements AhatHandler { if (bitmap != null) { doc.section("Bitmap Image"); doc.println(DocString.image( - DocString.formattedUri("bitmap?id=%d", bitmap.getId()), "bitmap image")); + DocString.formattedUri("bitmap?id=0x%x", bitmap.getId()), "bitmap image")); } } private void printGcRootPath(Doc doc, Query query, AhatInstance inst) { doc.section("Sample Path from GC Root"); List<PathElement> path = inst.getPathFromGcRoot(); - doc.table(new Column(""), new Column("Path Element")); - doc.row(DocString.text("(rooted)"), - DocString.link(DocString.uri("root"), DocString.text("ROOT"))); - for (PathElement element : path) { - DocString label = DocString.text("→ "); - label.append(Summarizer.summarize(element.instance)); - label.append(element.field); - doc.row(DocString.text(element.isDominator ? "(dominator)" : ""), label); + + // Add a dummy PathElement as a marker for the root. + final PathElement root = new PathElement(null, null); + path.add(0, root); + + HeapTable.TableConfig<PathElement> table = new HeapTable.TableConfig<PathElement>() { + public String getHeapsDescription() { + return "Bytes Retained by Heap (Dominators Only)"; + } + + public long getSize(PathElement element, AhatHeap heap) { + if (element == root) { + return heap.getSize().getSize(); + } + if (element.isDominator) { + return element.instance.getRetainedSize(heap).getSize(); + } + return 0; + } + + public List<HeapTable.ValueConfig<PathElement>> getValueConfigs() { + HeapTable.ValueConfig<PathElement> value = new HeapTable.ValueConfig<PathElement>() { + public String getDescription() { + return "Path Element"; + } + + public DocString render(PathElement element) { + if (element == root) { + return DocString.link(DocString.uri("rooted"), DocString.text("ROOT")); + } else { + DocString label = DocString.text("→ "); + label.append(Summarizer.summarize(element.instance)); + label.append(element.field); + return label; + } + } + }; + return Collections.singletonList(value); + } }; - doc.end(); + HeapTable.render(doc, query, DOMINATOR_PATH_ID, table, mSnapshot, path); } public void printDominatedObjects(Doc doc, Query query, AhatInstance inst) { diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java index 1a8f018bd5..1a8f018bd5 100644 --- a/tools/ahat/src/ObjectsHandler.java +++ b/tools/ahat/src/main/com/android/ahat/ObjectsHandler.java diff --git a/tools/ahat/src/OverviewHandler.java b/tools/ahat/src/main/com/android/ahat/OverviewHandler.java index c9f84259a9..c9f84259a9 100644 --- a/tools/ahat/src/OverviewHandler.java +++ b/tools/ahat/src/main/com/android/ahat/OverviewHandler.java diff --git a/tools/ahat/src/Query.java b/tools/ahat/src/main/com/android/ahat/Query.java index f910608771..9c2783c081 100644 --- a/tools/ahat/src/Query.java +++ b/tools/ahat/src/main/com/android/ahat/Query.java @@ -65,7 +65,7 @@ class Query { */ public long getLong(String name, long defaultValue) { String value = get(name, null); - return value == null ? defaultValue : Long.parseLong(value); + return value == null ? defaultValue : Long.decode(value); } /** @@ -73,7 +73,7 @@ class Query { */ public int getInt(String name, int defaultValue) { String value = get(name, null); - return value == null ? defaultValue : Integer.parseInt(value); + return value == null ? defaultValue : Integer.decode(value); } /** diff --git a/tools/ahat/src/RootedHandler.java b/tools/ahat/src/main/com/android/ahat/RootedHandler.java index 26451a3963..26451a3963 100644 --- a/tools/ahat/src/RootedHandler.java +++ b/tools/ahat/src/main/com/android/ahat/RootedHandler.java diff --git a/tools/ahat/src/SiteHandler.java b/tools/ahat/src/main/com/android/ahat/SiteHandler.java index 543eaa376a..543eaa376a 100644 --- a/tools/ahat/src/SiteHandler.java +++ b/tools/ahat/src/main/com/android/ahat/SiteHandler.java diff --git a/tools/ahat/src/SitePrinter.java b/tools/ahat/src/main/com/android/ahat/SitePrinter.java index 32037f4414..32037f4414 100644 --- a/tools/ahat/src/SitePrinter.java +++ b/tools/ahat/src/main/com/android/ahat/SitePrinter.java diff --git a/tools/ahat/src/SizeTable.java b/tools/ahat/src/main/com/android/ahat/SizeTable.java index 46e395669f..46e395669f 100644 --- a/tools/ahat/src/SizeTable.java +++ b/tools/ahat/src/main/com/android/ahat/SizeTable.java diff --git a/tools/ahat/src/StaticHandler.java b/tools/ahat/src/main/com/android/ahat/StaticHandler.java index 4a68f1c12f..4a68f1c12f 100644 --- a/tools/ahat/src/StaticHandler.java +++ b/tools/ahat/src/main/com/android/ahat/StaticHandler.java diff --git a/tools/ahat/src/SubsetSelector.java b/tools/ahat/src/main/com/android/ahat/SubsetSelector.java index 79399c178b..79399c178b 100644 --- a/tools/ahat/src/SubsetSelector.java +++ b/tools/ahat/src/main/com/android/ahat/SubsetSelector.java diff --git a/tools/ahat/src/Summarizer.java b/tools/ahat/src/main/com/android/ahat/Summarizer.java index 50b2e4b3b3..ae0776ab0b 100644 --- a/tools/ahat/src/Summarizer.java +++ b/tools/ahat/src/main/com/android/ahat/Summarizer.java @@ -51,7 +51,9 @@ class Summarizer { } // Annotate unreachable objects as such. - if (!inst.isReachable()) { + if (inst.isWeaklyReachable()) { + formatted.append("weak "); + } else if (inst.isUnreachable()) { formatted.append("unreachable "); } @@ -65,7 +67,7 @@ class Summarizer { // Don't make links to placeholder objects. formatted.append(linkText); } else { - URI objTarget = DocString.formattedUri("object?id=%d", inst.getId()); + URI objTarget = DocString.formattedUri("object?id=0x%x", inst.getId()); formatted.appendLink(objTarget, linkText); } @@ -100,7 +102,7 @@ class Summarizer { AhatInstance bitmap = inst.getAssociatedBitmapInstance(); String thumbnail = ""; if (bitmap != null) { - URI uri = DocString.formattedUri("bitmap?id=%d", bitmap.getId()); + URI uri = DocString.formattedUri("bitmap?id=0x%x", bitmap.getId()); formatted.appendThumbnail(uri, "bitmap image"); } return formatted; diff --git a/tools/ahat/src/dominators/DominatorsComputation.java b/tools/ahat/src/main/com/android/ahat/dominators/DominatorsComputation.java index 58b7b59f9a..58b7b59f9a 100644 --- a/tools/ahat/src/dominators/DominatorsComputation.java +++ b/tools/ahat/src/main/com/android/ahat/dominators/DominatorsComputation.java diff --git a/tools/ahat/src/heapdump/AhatArrayInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatArrayInstance.java index 50a4805bed..50a4805bed 100644 --- a/tools/ahat/src/heapdump/AhatArrayInstance.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatArrayInstance.java diff --git a/tools/ahat/src/heapdump/AhatClassInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java index 94efa5049f..94efa5049f 100644 --- a/tools/ahat/src/heapdump/AhatClassInstance.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java diff --git a/tools/ahat/src/heapdump/AhatClassObj.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java index be0f71306e..be0f71306e 100644 --- a/tools/ahat/src/heapdump/AhatClassObj.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java diff --git a/tools/ahat/src/heapdump/AhatField.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatField.java index a25ee2869d..a25ee2869d 100644 --- a/tools/ahat/src/heapdump/AhatField.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatField.java diff --git a/tools/ahat/src/heapdump/AhatHeap.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatHeap.java index b8897a182c..b8897a182c 100644 --- a/tools/ahat/src/heapdump/AhatHeap.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatHeap.java diff --git a/tools/ahat/src/heapdump/AhatInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java index c04448728f..cb2d738f23 100644 --- a/tools/ahat/src/heapdump/AhatInstance.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java @@ -136,13 +136,28 @@ public abstract class AhatInstance implements Diffable<AhatInstance>, } /** - * Returns whether this object is strongly-reachable. + * Returns true if this object is strongly-reachable. */ - public boolean isReachable() { + public boolean isStronglyReachable() { return mImmediateDominator != null; } /** + * Returns true if this object is reachable only through a + * soft/weak/phantom/finalizer reference. + */ + public boolean isWeaklyReachable() { + return !isStronglyReachable() && mNextInstanceToGcRoot != null; + } + + /** + * Returns true if this object is completely unreachable. + */ + public boolean isUnreachable() { + return !isStronglyReachable() && !isWeaklyReachable(); + } + + /** * Returns the heap that this instance is allocated on. */ public AhatHeap getHeap() { @@ -499,6 +514,10 @@ public abstract class AhatInstance implements Diffable<AhatInstance>, } else { if (ref.ref.mSoftReverseReferences == null) { ref.ref.mSoftReverseReferences = new ArrayList<AhatInstance>(); + if (ref.ref.mNextInstanceToGcRoot == null) { + ref.ref.mNextInstanceToGcRoot = ref.src; + ref.ref.mNextInstanceToGcRootField = ref.field; + } } ref.ref.mSoftReverseReferences.add(ref.src); } diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatPlaceHolderClassObj.java index 07f5b50012..07f5b50012 100644 --- a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatPlaceHolderClassObj.java diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatPlaceHolderInstance.java index 884940370d..884940370d 100644 --- a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatPlaceHolderInstance.java diff --git a/tools/ahat/src/heapdump/AhatSnapshot.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java index 945966cec7..945966cec7 100644 --- a/tools/ahat/src/heapdump/AhatSnapshot.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java diff --git a/tools/ahat/src/heapdump/Diff.java b/tools/ahat/src/main/com/android/ahat/heapdump/Diff.java index 98c7e58d56..98c7e58d56 100644 --- a/tools/ahat/src/heapdump/Diff.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/Diff.java diff --git a/tools/ahat/src/heapdump/DiffFields.java b/tools/ahat/src/main/com/android/ahat/heapdump/DiffFields.java index e3c671fe21..e3c671fe21 100644 --- a/tools/ahat/src/heapdump/DiffFields.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/DiffFields.java diff --git a/tools/ahat/src/heapdump/Diffable.java b/tools/ahat/src/main/com/android/ahat/heapdump/Diffable.java index 53442c857e..53442c857e 100644 --- a/tools/ahat/src/heapdump/Diffable.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/Diffable.java diff --git a/tools/ahat/src/heapdump/DiffedFieldValue.java b/tools/ahat/src/main/com/android/ahat/heapdump/DiffedFieldValue.java index 3cd273ed98..3cd273ed98 100644 --- a/tools/ahat/src/heapdump/DiffedFieldValue.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/DiffedFieldValue.java diff --git a/tools/ahat/src/heapdump/DominatorReferenceIterator.java b/tools/ahat/src/main/com/android/ahat/heapdump/DominatorReferenceIterator.java index 0b99e496cc..0b99e496cc 100644 --- a/tools/ahat/src/heapdump/DominatorReferenceIterator.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/DominatorReferenceIterator.java diff --git a/tools/ahat/src/heapdump/Field.java b/tools/ahat/src/main/com/android/ahat/heapdump/Field.java index dff401796a..dff401796a 100644 --- a/tools/ahat/src/heapdump/Field.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/Field.java diff --git a/tools/ahat/src/heapdump/FieldValue.java b/tools/ahat/src/main/com/android/ahat/heapdump/FieldValue.java index 20e6da7271..20e6da7271 100644 --- a/tools/ahat/src/heapdump/FieldValue.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/FieldValue.java diff --git a/tools/ahat/src/heapdump/HprofFormatException.java b/tools/ahat/src/main/com/android/ahat/heapdump/HprofFormatException.java index 0e128cd50a..0e128cd50a 100644 --- a/tools/ahat/src/heapdump/HprofFormatException.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/HprofFormatException.java diff --git a/tools/ahat/src/heapdump/Instances.java b/tools/ahat/src/main/com/android/ahat/heapdump/Instances.java index 085144650f..085144650f 100644 --- a/tools/ahat/src/heapdump/Instances.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/Instances.java diff --git a/tools/ahat/src/heapdump/Parser.java b/tools/ahat/src/main/com/android/ahat/heapdump/Parser.java index 756b7d2554..756b7d2554 100644 --- a/tools/ahat/src/heapdump/Parser.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/Parser.java diff --git a/tools/ahat/src/heapdump/PathElement.java b/tools/ahat/src/main/com/android/ahat/heapdump/PathElement.java index 196a24628c..196a24628c 100644 --- a/tools/ahat/src/heapdump/PathElement.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/PathElement.java diff --git a/tools/ahat/src/heapdump/Reference.java b/tools/ahat/src/main/com/android/ahat/heapdump/Reference.java index 980f2780b6..980f2780b6 100644 --- a/tools/ahat/src/heapdump/Reference.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/Reference.java diff --git a/tools/ahat/src/heapdump/RootType.java b/tools/ahat/src/main/com/android/ahat/heapdump/RootType.java index af552ea2c9..af552ea2c9 100644 --- a/tools/ahat/src/heapdump/RootType.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/RootType.java diff --git a/tools/ahat/src/heapdump/Site.java b/tools/ahat/src/main/com/android/ahat/heapdump/Site.java index 821493f1be..523550ad2c 100644 --- a/tools/ahat/src/heapdump/Site.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/Site.java @@ -186,7 +186,7 @@ public class Site implements Diffable<Site> { // Add all reachable objects allocated at this site. for (AhatInstance inst : mObjects) { - if (inst.isReachable()) { + if (inst.isStronglyReachable()) { AhatHeap heap = inst.getHeap(); Size size = inst.getSize(); ObjectsInfo info = getObjectsInfo(heap, inst.getClassObj()); diff --git a/tools/ahat/src/heapdump/Size.java b/tools/ahat/src/main/com/android/ahat/heapdump/Size.java index 7c8db900df..7c8db900df 100644 --- a/tools/ahat/src/heapdump/Size.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/Size.java diff --git a/tools/ahat/src/heapdump/SkipNullsIterator.java b/tools/ahat/src/main/com/android/ahat/heapdump/SkipNullsIterator.java index e99fe5e8ea..e99fe5e8ea 100644 --- a/tools/ahat/src/heapdump/SkipNullsIterator.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/SkipNullsIterator.java diff --git a/tools/ahat/src/heapdump/Sort.java b/tools/ahat/src/main/com/android/ahat/heapdump/Sort.java index efe0d6b59b..efe0d6b59b 100644 --- a/tools/ahat/src/heapdump/Sort.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/Sort.java diff --git a/tools/ahat/src/heapdump/SuperRoot.java b/tools/ahat/src/main/com/android/ahat/heapdump/SuperRoot.java index a2adbd2808..a2adbd2808 100644 --- a/tools/ahat/src/heapdump/SuperRoot.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/SuperRoot.java diff --git a/tools/ahat/src/heapdump/Type.java b/tools/ahat/src/main/com/android/ahat/heapdump/Type.java index 726bc47cf2..726bc47cf2 100644 --- a/tools/ahat/src/heapdump/Type.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/Type.java diff --git a/tools/ahat/src/heapdump/Value.java b/tools/ahat/src/main/com/android/ahat/heapdump/Value.java index 01fd25057d..01fd25057d 100644 --- a/tools/ahat/src/heapdump/Value.java +++ b/tools/ahat/src/main/com/android/ahat/heapdump/Value.java diff --git a/tools/ahat/src/proguard/ProguardMap.java b/tools/ahat/src/main/com/android/ahat/proguard/ProguardMap.java index 50c110aad4..50c110aad4 100644 --- a/tools/ahat/src/proguard/ProguardMap.java +++ b/tools/ahat/src/main/com/android/ahat/proguard/ProguardMap.java diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/src/test-dump/Main.java index 333d28c214..333d28c214 100644 --- a/tools/ahat/test-dump/Main.java +++ b/tools/ahat/src/test-dump/Main.java diff --git a/tools/ahat/test/DiffFieldsTest.java b/tools/ahat/src/test/com/android/ahat/DiffFieldsTest.java index 19399757a6..19399757a6 100644 --- a/tools/ahat/test/DiffFieldsTest.java +++ b/tools/ahat/src/test/com/android/ahat/DiffFieldsTest.java diff --git a/tools/ahat/test/DiffTest.java b/tools/ahat/src/test/com/android/ahat/DiffTest.java index 585f29ae61..585f29ae61 100644 --- a/tools/ahat/test/DiffTest.java +++ b/tools/ahat/src/test/com/android/ahat/DiffTest.java diff --git a/tools/ahat/test/DominatorsTest.java b/tools/ahat/src/test/com/android/ahat/DominatorsTest.java index 0424e10dc8..0424e10dc8 100644 --- a/tools/ahat/test/DominatorsTest.java +++ b/tools/ahat/src/test/com/android/ahat/DominatorsTest.java diff --git a/tools/ahat/test/HtmlEscaperTest.java b/tools/ahat/src/test/com/android/ahat/HtmlEscaperTest.java index a36db356f5..a36db356f5 100644 --- a/tools/ahat/test/HtmlEscaperTest.java +++ b/tools/ahat/src/test/com/android/ahat/HtmlEscaperTest.java diff --git a/tools/ahat/test/InstanceTest.java b/tools/ahat/src/test/com/android/ahat/InstanceTest.java index 49a21e2d70..a4908fd0ab 100644 --- a/tools/ahat/test/InstanceTest.java +++ b/tools/ahat/src/test/com/android/ahat/InstanceTest.java @@ -214,7 +214,9 @@ public class InstanceTest { // reference as having a non-null referent. TestDump dump = TestDump.getTestDump(); AhatInstance ref = dump.getDumpedAhatInstance("aSoftReference"); - assertNotNull(ref.getReferent()); + AhatInstance referent = ref.getReferent(); + assertNotNull(referent); + assertTrue(referent.isWeaklyReachable()); } @Test diff --git a/tools/ahat/test/NativeAllocationTest.java b/tools/ahat/src/test/com/android/ahat/NativeAllocationTest.java index 7436be8311..7436be8311 100644 --- a/tools/ahat/test/NativeAllocationTest.java +++ b/tools/ahat/src/test/com/android/ahat/NativeAllocationTest.java diff --git a/tools/ahat/test/ObjectHandlerTest.java b/tools/ahat/src/test/com/android/ahat/ObjectHandlerTest.java index 1b8a781e0c..1b8a781e0c 100644 --- a/tools/ahat/test/ObjectHandlerTest.java +++ b/tools/ahat/src/test/com/android/ahat/ObjectHandlerTest.java diff --git a/tools/ahat/test/OverviewHandlerTest.java b/tools/ahat/src/test/com/android/ahat/OverviewHandlerTest.java index c2f773b64b..c2f773b64b 100644 --- a/tools/ahat/test/OverviewHandlerTest.java +++ b/tools/ahat/src/test/com/android/ahat/OverviewHandlerTest.java diff --git a/tools/ahat/test/PerformanceTest.java b/tools/ahat/src/test/com/android/ahat/PerformanceTest.java index e13974bb6f..e13974bb6f 100644 --- a/tools/ahat/test/PerformanceTest.java +++ b/tools/ahat/src/test/com/android/ahat/PerformanceTest.java diff --git a/tools/ahat/test/ProguardMapTest.java b/tools/ahat/src/test/com/android/ahat/ProguardMapTest.java index ad40f45665..ad40f45665 100644 --- a/tools/ahat/test/ProguardMapTest.java +++ b/tools/ahat/src/test/com/android/ahat/ProguardMapTest.java diff --git a/tools/ahat/test/QueryTest.java b/tools/ahat/src/test/com/android/ahat/QueryTest.java index 5bcf8eafc3..5bcf8eafc3 100644 --- a/tools/ahat/test/QueryTest.java +++ b/tools/ahat/src/test/com/android/ahat/QueryTest.java diff --git a/tools/ahat/test/RootedHandlerTest.java b/tools/ahat/src/test/com/android/ahat/RootedHandlerTest.java index f325b8e9a7..f325b8e9a7 100644 --- a/tools/ahat/test/RootedHandlerTest.java +++ b/tools/ahat/src/test/com/android/ahat/RootedHandlerTest.java diff --git a/tools/ahat/test/SiteHandlerTest.java b/tools/ahat/src/test/com/android/ahat/SiteHandlerTest.java index 37596be8bb..37596be8bb 100644 --- a/tools/ahat/test/SiteHandlerTest.java +++ b/tools/ahat/src/test/com/android/ahat/SiteHandlerTest.java diff --git a/tools/ahat/test/SiteTest.java b/tools/ahat/src/test/com/android/ahat/SiteTest.java index dc0fe08297..dc0fe08297 100644 --- a/tools/ahat/test/SiteTest.java +++ b/tools/ahat/src/test/com/android/ahat/SiteTest.java diff --git a/tools/ahat/test/TestDump.java b/tools/ahat/src/test/com/android/ahat/TestDump.java index a0d1021ef1..a0d1021ef1 100644 --- a/tools/ahat/test/TestDump.java +++ b/tools/ahat/src/test/com/android/ahat/TestDump.java diff --git a/tools/ahat/test/TestHandler.java b/tools/ahat/src/test/com/android/ahat/TestHandler.java index 859e39a688..859e39a688 100644 --- a/tools/ahat/test/TestHandler.java +++ b/tools/ahat/src/test/com/android/ahat/TestHandler.java diff --git a/tools/ahat/test/Tests.java b/tools/ahat/src/test/com/android/ahat/Tests.java index 0e7043291d..0e7043291d 100644 --- a/tools/ahat/test/Tests.java +++ b/tools/ahat/src/test/com/android/ahat/Tests.java diff --git a/tools/ahat/test-dump/README.txt b/tools/ahat/test-dump/README.txt deleted file mode 100644 index e7ea584b26..0000000000 --- a/tools/ahat/test-dump/README.txt +++ /dev/null @@ -1,7 +0,0 @@ - -Main.java - A program used to generate a heap dump used for tests. -L.hprof - A version of the test dump generated on Android L, - with one of the ROOT_DEBUGGER records manually changed to a - ROOT_FINALIZING record. -O.hprof - A version of the test dump generated on Android O. -RI.hprof - A version of the test dump generated on the reference implementation. @@ -220,6 +220,11 @@ function detect_boot_image_location() { echo "$image_location" } +# If android logging is not explicitly set, only print warnings and errors. +if [ -z "$ANDROID_LOG_TAGS" ]; then + ANDROID_LOG_TAGS='*:w' +fi + # Runs dalvikvm, returns its exit code. # (Oat directories are cleaned up in between runs) function run_art() { @@ -229,15 +234,16 @@ function run_art() { # First cleanup any left-over 'oat' files from the last time dalvikvm was run. cleanup_oat_directory_for_classpath "$@" # Run dalvikvm. - verbose_run ANDROID_DATA=$ANDROID_DATA \ - ANDROID_ROOT=$ANDROID_ROOT \ - LD_LIBRARY_PATH=$LD_LIBRARY_PATH \ - PATH=$ANDROID_ROOT/bin:$PATH \ - LD_USE_LOAD_BIAS=1 \ - $LAUNCH_WRAPPER $ART_BINARY_PATH $lib \ - -XXlib:$LIBART \ - -Xnorelocate \ - -Ximage:"$image_location" \ + verbose_run ANDROID_DATA="$ANDROID_DATA" \ + ANDROID_ROOT="$ANDROID_ROOT" \ + LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \ + PATH="$ANDROID_ROOT/bin:$PATH" \ + LD_USE_LOAD_BIAS=1 \ + ANDROID_LOG_TAGS="$ANDROID_LOG_TAGS" \ + $LAUNCH_WRAPPER $ART_BINARY_PATH $lib \ + -XXlib:"$LIBART" \ + -Xnorelocate \ + -Ximage:"$image_location" \ "$@" ret=$? diff --git a/tools/libjdwp_art_failures.txt b/tools/libjdwp_art_failures.txt index 6b5daec5a7..8d67c45cce 100644 --- a/tools/libjdwp_art_failures.txt +++ b/tools/libjdwp_art_failures.txt @@ -48,7 +48,7 @@ name: "org.apache.harmony.jpda.tests.jdwp.Events.VMDeath002Test#testVMDeathRequest" }, { - description: "Test fails with INTERNAL error due to proxy frame!", + description: "Test fails with OPAQUE_FRAME error due to attempting a GetLocalReference on a proxy frame instead of GetLocalInstance!", result: EXEC_FAILED, bug: 66903662, name: "org.apache.harmony.jpda.tests.jdwp.StackFrame.ProxyThisObjectTest#testThisObject" @@ -71,6 +71,33 @@ "org.apache.harmony.jpda.tests.jdwp.EventModifiers.InstanceOnlyModifierTest#testMethodExit", "org.apache.harmony.jpda.tests.jdwp.EventModifiers.InstanceOnlyModifierTest#testMethodExitWithReturnValue" ] }, +/* TODO Investigate these failures more closely */ +{ + description: "Tests that fail when run on the chromium buildbots against the prebuilt libjdwp.so in certain configurations", + result: EXEC_FAILED, + bug: 67497270, + names: [ + "org.apache.harmony.jpda.tests.jdwp.Events.CombinedEvents003Test#testCombinedEvents003_01", + "org.apache.harmony.jpda.tests.jdwp.Events.CombinedEventsTest#testCombinedEvents_01", + "org.apache.harmony.jpda.tests.jdwp.Events.CombinedEventsTest#testCombinedEvents_02", + "org.apache.harmony.jpda.tests.jdwp.Events.CombinedEventsTest#testCombinedEvents_03", + "org.apache.harmony.jpda.tests.jdwp.Events.CombinedEventsTest#testCombinedEvents_04", + "org.apache.harmony.jpda.tests.jdwp.Events.CombinedEventsTest#testCombinedEvents_06", + "org.apache.harmony.jpda.tests.jdwp.Events.VMDeathTest#testVMDeathEvent", + "org.apache.harmony.jpda.tests.jdwp.MultiSession.ClassPrepareTest#testClassPrepare001", + "org.apache.harmony.jpda.tests.jdwp.MultiSession.ExceptionTest#testException001", + "org.apache.harmony.jpda.tests.jdwp.MultiSession.FieldAccessTest#testFieldAccess001", + "org.apache.harmony.jpda.tests.jdwp.MultiSession.FieldModificationTest#testFieldModification001", + "org.apache.harmony.jpda.tests.jdwp.MultiSession.SingleStepTest#testSingleStep001", + "org.apache.harmony.jpda.tests.jdwp.MultiSession.VMDeathTest#testVMDeathRequest", + "org.apache.harmony.jpda.tests.jdwp.ReferenceType.SignatureWithGenericTest#testSignatureWithGeneric001", + "org.apache.harmony.jpda.tests.jdwp.StackFrame.GetValues002Test#testGetValues005_Int2", + "org.apache.harmony.jpda.tests.jdwp.VirtualMachine.SetDefaultStratumTest#testSetDefaultStratum001", + "org.apache.harmony.jpda.tests.jdwp.ThreadReference.StatusTest#testStatus001", + "org.apache.harmony.jpda.tests.jdwp.VirtualMachine.AllClassesTest#testAllClasses002", + "org.apache.harmony.jpda.tests.jdwp.VirtualMachine.AllClassesWithGenericTest#testAllClassesWithGeneric001" + ] +}, /* TODO Categorize these failures more. */ { description: "Tests that fail on both ART and RI. These tests are likely incorrect", |