From aaebaa0121be3b9d9f13630585304482cbcaeb4b Mon Sep 17 00:00:00 2001 From: Igor Murashkin Date: Mon, 26 Jan 2015 10:55:53 -0800 Subject: art: Refactor RuntimeOptions/ParsedOptions Refactor the RuntimeOptions to be a type-safe map (VariantMap, see runtime_options.h) and the ParsedOptions to delegate the parsing to CmdlineParser (see cmdline/cmdline_parser.h). This is the start of a command line parsing refactor, and may include more in the future (dex2oat, patchoat, etc). For more details of the command line parsing generator usage see cmdline/README.md Change-Id: Ic67c6bca5e1f33bf2ec60e2e3ff8c366bab91563 --- cmdline/README.md | 245 ++++++++ cmdline/cmdline_parse_result.h | 138 +++++ cmdline/cmdline_parser.h | 635 +++++++++++++++++++ cmdline/cmdline_parser_test.cc | 534 ++++++++++++++++ cmdline/cmdline_result.h | 103 ++++ cmdline/cmdline_type_parser.h | 76 +++ cmdline/cmdline_types.h | 820 +++++++++++++++++++++++++ cmdline/detail/cmdline_debug_detail.h | 40 ++ cmdline/detail/cmdline_parse_argument_detail.h | 503 +++++++++++++++ cmdline/detail/cmdline_parser_detail.h | 128 ++++ cmdline/memory_representation.h | 71 +++ cmdline/token_range.h | 425 +++++++++++++ cmdline/unit.h | 35 ++ 13 files changed, 3753 insertions(+) create mode 100644 cmdline/README.md create mode 100644 cmdline/cmdline_parse_result.h create mode 100644 cmdline/cmdline_parser.h create mode 100644 cmdline/cmdline_parser_test.cc create mode 100644 cmdline/cmdline_result.h create mode 100644 cmdline/cmdline_type_parser.h create mode 100644 cmdline/cmdline_types.h create mode 100644 cmdline/detail/cmdline_debug_detail.h create mode 100644 cmdline/detail/cmdline_parse_argument_detail.h create mode 100644 cmdline/detail/cmdline_parser_detail.h create mode 100644 cmdline/memory_representation.h create mode 100644 cmdline/token_range.h create mode 100644 cmdline/unit.h (limited to 'cmdline') diff --git a/cmdline/README.md b/cmdline/README.md new file mode 100644 index 0000000..8cac77f --- /dev/null +++ b/cmdline/README.md @@ -0,0 +1,245 @@ +Cmdline +=================== + +Introduction +------------- +This directory contains the classes that do common command line tool initialization and parsing. The +long term goal is eventually for all `art` command-line tools to be using these helpers. + +---------- + + +## Cmdline Parser +------------- + +The `CmdlineParser` class provides a fluent interface using a domain-specific language to quickly +generate a type-safe value parser that process a user-provided list of strings (`argv`). Currently, +it can parse a string into a `VariantMap`, although in the future it might be desirable to parse +into any struct of any field. + +To use, create a `CmdlineParser::Builder` and then chain the `Define` methods together with +`WithType` and `IntoXX` methods. + +### Quick Start +For example, to save the values into a user-defined variant map: + +``` +struct FruitVariantMap : VariantMap { + static const Key Apple; + static const Key Orange; + static const Key Help; +}; +// Note that some template boilerplate has been avoided for clarity. +// See variant_map_test.cc for how to completely define a custom map. + +using FruitParser = CmdlineParser; + +FruitParser MakeParser() { + auto&& builder = FruitParser::Builder(); + builder. + .Define("--help") + .IntoKey(FruitVariantMap::Help) + Define("--apple:_") + .WithType() + .IntoKey(FruitVariantMap::Apple) + .Define("--orange:_") + .WithType() + .WithRange(0.0, 1.0) + .IntoKey(FruitVariantMap::Orange); + + return builder.Build(); +} + +int main(char** argv, int argc) { + auto parser = MakeParser(); + auto result = parser.parse(argv, argc)); + if (result.isError()) { + std::cerr << result.getMessage() << std::endl; + return EXIT_FAILURE; + } + auto map = parser.GetArgumentsMap(); + std::cout << "Help? " << map.GetOrDefault(FruitVariantMap::Help) << std::endl; + std::cout << "Apple? " << map.GetOrDefault(FruitVariantMap::Apple) << std::endl; + std::cout << "Orange? " << map.GetOrDefault(FruitVariantMap::Orange) << std::endl; + + return EXIT_SUCCESS; +} +``` + +In the above code sample, we define a parser which is capable of parsing something like `--help +--apple:123 --orange:0.456` . It will error out automatically if invalid flags are given, or if the +appropriate flags are given but of the the wrong type/range. So for example, `--foo` will not parse +(invalid argument), neither will `--apple:fruit` (fruit is not an int) nor `--orange:1234` (1234 is +out of range of [0.0, 1.0]) + +### Argument Definitions in Detail +#### Define method +The 'Define' method takes one or more aliases for the argument. Common examples might be `{"-h", +"--help"}` where both `--help` and `-h` are aliases for the same argument. + +The simplest kind of argument just tests for presence, but we often want to parse out a particular +type of value (such as an int or double as in the above `FruitVariantMap` example). To do that, a +_wildcard_ must be used to denote the location within the token that the type will be parsed out of. + +For example with `-orange:_` the parse would know to check all tokens in an `argv` list for the +`-orange:` prefix and then strip it, leaving only the remains to be parsed. + +#### WithType method (optional) +After an argument definition is provided, the parser builder needs to know what type the argument +will be in order to provide the type safety and make sure the rest of the argument definition is +correct as early as possible (in essence, everything but the parsing of the argument name is done at +compile time). + +Everything that follows a `WithType()` call is thus type checked to only take `T` values. + +If this call is omitted, the parser generator assumes you are building a `Unit` type (i.e. an +argument that only cares about presence). + +#### WithRange method (optional) +Some values will not make sense outside of a `[min, max]` range, so this is an option to quickly add +a range check without writing custom code. The range check is performed after the main parsing +happens and happens for any type implementing the `<=` operators. + +#### WithValueMap (optional) +When parsing an enumeration, it might be very convenient to map a list of possible argument string +values into its runtime value. + +With something like +``` + .Define("-hello:_") + .WithValueMap({"world", kWorld}, + {"galaxy", kGalaxy}) +``` +It will parse either `-hello:world` or `-hello:galaxy` only (and error out on other variations of +`-hello:whatever`), converting it to the type-safe value of `kWorld` or `kGalaxy` respectively. + +This is meant to be another shorthand (like `WithRange`) to avoid writing a custom type parser. In +general it takes a variadic number of `pair`. + +#### WithValues (optional) +When an argument definition has multiple aliases with no wildcards, it might be convenient to +quickly map them into discrete values. + +For example: +``` + .Define({"-xinterpret", "-xnointerpret"}) + .WithValues({true, false} +``` +It will parse `-xinterpret` as `true` and `-xnointerpret` as `false`. + +In general, it uses the position of the argument alias to map into the WithValues position value. + +(Note that this method will not work when the argument definitions have a wildcard because there is +no way to position-ally match that). + +#### AppendValues (optional) +By default, the argument is assumed to appear exactly once, and if the user specifies it more than +once, only the latest value is taken into account (and all previous occurrences of the argument are +ignored). + +In some situations, we may want to accumulate the argument values instead of discarding the previous +ones. + +For example +``` + .Define("-D") + .WithType)() + .AppendValues() +``` +Will parse something like `-Dhello -Dworld -Dbar -Dbaz` into `std::vector{"hello", +"world", "bar", "baz"}`. + +### Setting an argument parse target (required) +To complete an argument definition, the parser generator also needs to know where to save values. +Currently, only `IntoKey` is supported, but that may change in the future. + +#### IntoKey (required) +This specifies that when a value is parsed, it will get saved into a variant map using the specific +key. + +For example, +``` + .Define("-help") + .IntoKey(Map::Help) +``` +will save occurrences of the `-help` argument by doing a `Map.Set(Map::Help, ParsedValue("-help"))` +where `ParsedValue` is an imaginary function that parses the `-help` argment into a specific type +set by `WithType`. + +### Ignoring unknown arguments +This is highly discouraged, but for compatibility with `JNI` which allows argument ignores, there is +an option to ignore any argument tokens that are not known to the parser. This is done with the +`Ignore` function which takes a list of argument definition names. + +It's semantically equivalent to making a series of argument definitions that map to `Unit` but don't +get saved anywhere. Values will still get parsed as normal, so it will *not* ignore known arguments +with invalid values, only user-arguments for which it could not find a matching argument definition. + +### Parsing custom types +Any type can be parsed from a string by specializing the `CmdlineType` class and implementing the +static interface provided by `CmdlineTypeParser`. It is recommended to inherit from +`CmdlineTypeParser` since it already provides default implementations for every method. + +The `Parse` method should be implemented for most types. Some types will allow appending (such as an +`std::vector` and are meant to be used with `AppendValues` in which case the +`ParseAndAppend` function should be implemented. + +For example: +``` +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& str) { + char* end = nullptr; + errno = 0; + double value = strtod(str.c_str(), &end); + + if (*end != '\0') { + return Result::Failure("Failed to parse double from " + str); + } + if (errno == ERANGE) { + return Result::OutOfRange( + "Failed to parse double from " + str + "; overflow/underflow occurred"); + } + + return Result::Success(value); + } + + static const char* Name() { return "double"; } + // note: Name() is just here for more user-friendly errors, + // but in the future we will use non-standard ways of getting the type name + // at compile-time and this will no longer be required +}; +``` +Will parse any non-append argument definitions with a type of `double`. + +For an appending example: +``` +template <> +struct CmdlineType> : CmdlineTypeParser> { + Result ParseAndAppend(const std::string& args, + std::vector& existing_value) { + existing_value.push_back(args); + return Result::SuccessNoValue(); + } + static const char* Name() { return "std::vector"; } +}; +``` +Will parse multiple instances of the same argument repeatedly into the `existing_value` (which will +be default-constructed to `T{}` for the first occurrence of the argument). + +#### What is a `Result`? +`Result` is a typedef for `CmdlineParseResult` and it acts similar to a poor version of +`Either` in Haskell. In particular, it would be similar to `Either< int ErrorCode, +Maybe >`. + +There are helpers like `Result::Success(value)`, `Result::Failure(string message)` and so on to +quickly construct these without caring about the type. + +When successfully parsing a single value, `Result::Success(value)` should be used, and when +successfully parsing an appended value, use `Result::SuccessNoValue()` and write back the new value +into `existing_value` as an out-parameter. + +When many arguments are parsed, the result is collapsed down to a `CmdlineResult` which acts as a +`Either` where the right side simply indicates success. When values are +successfully stored, the parser will automatically save it into the target destination as a side +effect. diff --git a/cmdline/cmdline_parse_result.h b/cmdline/cmdline_parse_result.h new file mode 100644 index 0000000..d6ac341 --- /dev/null +++ b/cmdline/cmdline_parse_result.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_CMDLINE_PARSE_RESULT_H_ +#define ART_CMDLINE_CMDLINE_PARSE_RESULT_H_ + +#include "cmdline_result.h" +#include "detail/cmdline_parser_detail.h" + +namespace art { +// Result of a type-parsing attempt. If successful holds the strongly-typed value, +// otherwise it holds either a usage or a failure string message that should be displayed back +// to the user. +// +// CmdlineType::Parse/CmdlineType::ParseAndAppend must return this type. +template +struct CmdlineParseResult : CmdlineResult { + using CmdlineResult::CmdlineResult; + + // Create an error result with the usage error code and the specified message. + static CmdlineParseResult Usage(const std::string& message) { + return CmdlineParseResult(kUsage, message); + } + + // Create an error result with the failure error code and no message. + static CmdlineParseResult Failure() { + return CmdlineParseResult(kFailure); + } + + // Create an error result with the failure error code and no message. + static CmdlineParseResult Failure(const std::string& message) { + return CmdlineParseResult(kFailure, message); + } + + // Create a successful result which holds the specified value. + static CmdlineParseResult Success(const T& value) { + return CmdlineParseResult(value); + } + + // Create a successful result, taking over the value. + static CmdlineParseResult Success(T&& value) { + return CmdlineParseResult(std::forward(value)); + } + + // Create succesful result, without any values. Used when a value was successfully appended + // into an existing object. + static CmdlineParseResult SuccessNoValue() { + return CmdlineParseResult(T {}); + } + + // Create an error result with the OutOfRange error and the specified message. + static CmdlineParseResult OutOfRange(const std::string& message) { + return CmdlineParseResult(kOutOfRange, message); + } + + // Create an error result with the OutOfRange code and a custom message + // which is printed from the actual/min/max values. + // Values are converted to string using the ostream<< operator. + static CmdlineParseResult OutOfRange(const T& value, + const T& min, + const T& max) { + return CmdlineParseResult(kOutOfRange, + "actual: " + art::detail::ToStringAny(value) + + ", min: " + art::detail::ToStringAny(min) + + ", max: " + art::detail::ToStringAny(max)); + } + + // Get a read-only reference to the underlying value. + // The result must have been successful and must have a value. + const T& GetValue() const { + assert(IsSuccess()); + assert(has_value_); + return value_; + } + + // Get a mutable reference to the underlying value. + // The result must have been successful and must have a value. + T& GetValue() { + assert(IsSuccess()); + assert(has_value_); + return value_; + } + + // Take over the value. + // The result must have been successful and must have a value. + T&& ReleaseValue() { + assert(IsSuccess()); + assert(has_value_); + return std::move(value_); + } + + // Whether or not the result has a value (e.g. created with Result::Success). + // Error results never have values, success results commonly, but not always, have values. + bool HasValue() const { + return has_value_; + } + + // Cast an error-result from type T2 to T1. + // Safe since error-results don't store a typed value. + template + static CmdlineParseResult CastError(const CmdlineParseResult& other) { + assert(other.IsError()); + return CmdlineParseResult(other.GetStatus()); + } + + // Make sure copying is allowed + CmdlineParseResult(const CmdlineParseResult& other) = default; + // Make sure moving is cheap + CmdlineParseResult(CmdlineParseResult&& other) = default; + + private: + explicit CmdlineParseResult(const T& value) + : CmdlineResult(kSuccess), value_(value), has_value_(true) {} + explicit CmdlineParseResult(T&& value) + : CmdlineResult(kSuccess), value_(std::forward(value)), has_value_(true) {} + explicit CmdlineParseResult() + : CmdlineResult(kSuccess), value_(), has_value_(false) {} + + T value_; + bool has_value_ = false; +}; + +} // namespace art + +#endif // ART_CMDLINE_CMDLINE_PARSE_RESULT_H_ diff --git a/cmdline/cmdline_parser.h b/cmdline/cmdline_parser.h new file mode 100644 index 0000000..a555356 --- /dev/null +++ b/cmdline/cmdline_parser.h @@ -0,0 +1,635 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_CMDLINE_PARSER_H_ +#define ART_CMDLINE_CMDLINE_PARSER_H_ + +#define CMDLINE_NDEBUG 1 // Do not output any debugging information for parsing. + +#include "cmdline/detail/cmdline_parser_detail.h" +#include "cmdline/detail/cmdline_parse_argument_detail.h" +#include "cmdline/detail/cmdline_debug_detail.h" + +#include "cmdline_type_parser.h" +#include "token_range.h" +#include "cmdline_types.h" +#include "cmdline_result.h" +#include "cmdline_parse_result.h" + +#include "runtime/base/variant_map.h" +#include "utils.h" + +#include +#include + +namespace art { +// Build a parser for command line arguments with a small domain specific language. +// Each parsed type must have a specialized CmdlineType in order to do the string->T parsing. +// Each argument must also have a VariantMap::Key in order to do the T storage. +template class TVariantMapKey> +struct CmdlineParser { + template + struct ArgumentBuilder; + + struct Builder; // Build the parser. + struct UntypedArgumentBuilder; // Build arguments which weren't yet given a type. + + private: + // Forward declare some functions that we need to use before fully-defining structs. + template + static ArgumentBuilder CreateArgumentBuilder(Builder& parent); + static void AppendCompletedArgument(Builder& builder, detail::CmdlineParseArgumentAny* arg); + + // Allow argument definitions to save their values when they are parsed, + // without having a dependency on CmdlineParser or any of the builders. + // + // A shared pointer to the save destination is saved into the load/save argument callbacks. + // + // This also allows the underlying storage (i.e. a variant map) to be released + // to the user, without having to recreate all of the callbacks. + struct SaveDestination { + SaveDestination() : variant_map_(new TVariantMap()) {} + + // Save value to the variant map. + template + void SaveToMap(const TVariantMapKey& key, TArg& value) { + variant_map_->Set(key, value); + } + + // Get the existing value from a map, creating the value if it did not already exist. + template + TArg& GetOrCreateFromMap(const TVariantMapKey& key) { + auto* ptr = variant_map_->Get(key); + if (ptr == nullptr) { + variant_map_->Set(key, TArg()); + ptr = variant_map_->Get(key); + assert(ptr != nullptr); + } + + return *ptr; + } + + protected: + // Release the map, clearing it as a side-effect. + // Future saves will be distinct from previous saves. + TVariantMap&& ReleaseMap() { + return std::move(*variant_map_); + } + + // Get a read-only reference to the variant map. + const TVariantMap& GetMap() { + return *variant_map_; + } + + // Clear all potential save targets. + void Clear() { + variant_map_->Clear(); + } + + private: + // Don't try to copy or move this. Just don't. + SaveDestination(const SaveDestination&) = delete; + SaveDestination(SaveDestination&&) = delete; + SaveDestination& operator=(const SaveDestination&) = delete; + SaveDestination& operator=(SaveDestination&&) = delete; + + std::shared_ptr variant_map_; + + // Allow the parser to change the underlying pointers when we release the underlying storage. + friend struct CmdlineParser; + }; + + public: + // Builder for the argument definition of type TArg. Do not use this type directly, + // it is only a separate type to provide compile-time enforcement against doing + // illegal builds. + template + struct ArgumentBuilder { + // Add a range check to this argument. + ArgumentBuilder& WithRange(const TArg& min, const TArg& max) { + argument_info_.has_range_ = true; + argument_info_.min_ = min; + argument_info_.max_ = max; + + return *this; + } + + // Map the list of names into the list of values. List of names must not have + // any wildcards '_' in it. + // + // Do not use if a value map has already been set. + ArgumentBuilder& WithValues(std::initializer_list value_list) { + SetValuesInternal(value_list); + return *this; + } + + // When used with a single alias, map the alias into this value. + // Same as 'WithValues({value})' , but allows the omission of the curly braces {}. + ArgumentBuilder WithValue(const TArg& value) { + return WithValues({ value }); + } + + // Map the parsed string values (from _) onto a concrete value. If no wildcard + // has been specified, then map the value directly from the arg name (i.e. + // if there are multiple aliases, then use the alias to do the mapping). + // + // Do not use if a values list has already been set. + ArgumentBuilder& WithValueMap( + std::initializer_list> key_value_list) { + assert(!argument_info_.has_value_list_); + + argument_info_.has_value_map_ = true; + argument_info_.value_map_ = key_value_list; + + return *this; + } + + // If this argument is seen multiple times, successive arguments mutate the same value + // instead of replacing it with a new value. + ArgumentBuilder& AppendValues() { + argument_info_.appending_values_ = true; + + return *this; + } + + // Convenience type alias for the variant map key type definition. + using MapKey = TVariantMapKey; + + // Write the results of this argument into the key. + // To look up the parsed arguments, get the map and then use this key with VariantMap::Get + CmdlineParser::Builder& IntoKey(const MapKey& key) { + // Only capture save destination as a pointer. + // This allows the parser to later on change the specific save targets. + auto save_destination = save_destination_; + save_value_ = [save_destination, &key](TArg& value) { + save_destination->SaveToMap(key, value); + CMDLINE_DEBUG_LOG << "Saved value into map '" + << detail::ToStringAny(value) << "'" << std::endl; + }; + + load_value_ = [save_destination, &key]() -> TArg& { + TArg& value = save_destination->GetOrCreateFromMap(key); + CMDLINE_DEBUG_LOG << "Loaded value from map '" << detail::ToStringAny(value) << "'" + << std::endl; + + return value; + }; + + save_value_specified_ = true; + load_value_specified_ = true; + + CompleteArgument(); + return parent_; + } + + // Ensure we always move this when returning a new builder. + ArgumentBuilder(ArgumentBuilder&&) = default; + + protected: + // Used by builder to internally ignore arguments by dropping them on the floor after parsing. + CmdlineParser::Builder& IntoIgnore() { + save_value_ = [](TArg& value) { + CMDLINE_DEBUG_LOG << "Ignored value '" << detail::ToStringAny(value) << "'" << std::endl; + }; + load_value_ = []() -> TArg& { + assert(false && "Should not be appending values to ignored arguments"); + return *reinterpret_cast(0); // Blow up. + }; + + save_value_specified_ = true; + load_value_specified_ = true; + + CompleteArgument(); + return parent_; + } + + void SetValuesInternal(const std::vector&& value_list) { + assert(!argument_info_.has_value_map_); + + argument_info_.has_value_list_ = true; + argument_info_.value_list_ = value_list; + } + + void SetNames(std::vector&& names) { + argument_info_.names_ = names; + } + + void SetNames(std::initializer_list names) { + argument_info_.names_ = names; + } + + private: + // Copying is bad. Move only. + ArgumentBuilder(const ArgumentBuilder&) = delete; + + // Called by any function that doesn't chain back into this builder. + // Completes the argument builder and save the information into the main builder. + void CompleteArgument() { + assert(save_value_specified_ && + "No Into... function called, nowhere to save parsed values to"); + assert(load_value_specified_ && + "No Into... function called, nowhere to load parsed values from"); + + argument_info_.CompleteArgument(); + + // Appending the completed argument is destructive. The object is no longer + // usable since all the useful information got moved out of it. + AppendCompletedArgument(parent_, + new detail::CmdlineParseArgument( + std::move(argument_info_), + std::move(save_value_), + std::move(load_value_))); + } + + friend struct CmdlineParser; + friend struct CmdlineParser::Builder; + friend struct CmdlineParser::UntypedArgumentBuilder; + + ArgumentBuilder(CmdlineParser::Builder& parser, + std::shared_ptr save_destination) + : parent_(parser), + save_value_specified_(false), + load_value_specified_(false), + save_destination_(save_destination) { + save_value_ = [](TArg&) { + assert(false && "No save value function defined"); + }; + + load_value_ = []() -> TArg& { + assert(false && "No load value function defined"); + return *reinterpret_cast(0); // Blow up. + }; + } + + CmdlineParser::Builder& parent_; + std::function save_value_; + std::function load_value_; + bool save_value_specified_; + bool load_value_specified_; + detail::CmdlineParserArgumentInfo argument_info_; + + std::shared_ptr save_destination_; + }; + + struct UntypedArgumentBuilder { + // Set a type for this argument. The specific subcommand parser is looked up by the type. + template + ArgumentBuilder WithType() { + return CreateTypedBuilder(); + } + + // When used with multiple aliases, map the position of the alias to the value position. + template + ArgumentBuilder WithValues(std::initializer_list values) { + auto&& a = CreateTypedBuilder(); + a.WithValues(values); + return std::move(a); + } + + // When used with a single alias, map the alias into this value. + // Same as 'WithValues({value})' , but allows the omission of the curly braces {}. + template + ArgumentBuilder WithValue(const TArg& value) { + return WithValues({ value }); + } + + // Set the current building argument to target this key. + // When this command line argument is parsed, it can be fetched with this key. + Builder& IntoKey(const TVariantMapKey& key) { + return CreateTypedBuilder().IntoKey(key); + } + + // Ensure we always move this when returning a new builder. + UntypedArgumentBuilder(UntypedArgumentBuilder&&) = default; + + protected: + void SetNames(std::vector&& names) { + names_ = std::move(names); + } + + void SetNames(std::initializer_list names) { + names_ = names; + } + + private: + // No copying. Move instead. + UntypedArgumentBuilder(const UntypedArgumentBuilder&) = delete; + + template + ArgumentBuilder CreateTypedBuilder() { + auto&& b = CreateArgumentBuilder(parent_); + InitializeTypedBuilder(&b); // Type-specific initialization + b.SetNames(std::move(names_)); + return std::move(b); + } + + template + typename std::enable_if::value>::type + InitializeTypedBuilder(ArgumentBuilder* arg_builder) { + // Every Unit argument implicitly maps to a runtime value of Unit{} + std::vector values(names_.size(), Unit{}); // NOLINT [whitespace/braces] [5] + arg_builder->SetValuesInternal(std::move(values)); + } + + // No extra work for all other types + void InitializeTypedBuilder(void*) {} + + template + friend struct ArgumentBuilder; + friend struct Builder; + + explicit UntypedArgumentBuilder(CmdlineParser::Builder& parent) : parent_(parent) {} + // UntypedArgumentBuilder(UntypedArgumentBuilder&& other) = default; + + CmdlineParser::Builder& parent_; + std::vector names_; + }; + + // Build a new parser given a chain of calls to define arguments. + struct Builder { + Builder() : save_destination_(new SaveDestination()) {} + + // Define a single argument. The default type is Unit. + UntypedArgumentBuilder Define(const char* name) { + return Define({name}); + } + + // Define a single argument with multiple aliases. + UntypedArgumentBuilder Define(std::initializer_list names) { + auto&& b = UntypedArgumentBuilder(*this); + b.SetNames(names); + return std::move(b); + } + + // Whether the parser should give up on unrecognized arguments. Not recommended. + Builder& IgnoreUnrecognized(bool ignore_unrecognized) { + ignore_unrecognized_ = ignore_unrecognized; + return *this; + } + + // Provide a list of arguments to ignore for backwards compatibility. + Builder& Ignore(std::initializer_list ignore_list) { + for (auto&& ignore_name : ignore_list) { + std::string ign = ignore_name; + + // Ignored arguments are just like a regular definition which have very + // liberal parsing requirements (no range checks, no value checks). + // Unlike regular argument definitions, when a value gets parsed into its + // stronger type, we just throw it away. + + if (ign.find("_") != std::string::npos) { // Does the arg-def have a wildcard? + // pretend this is a string, e.g. -Xjitconfig: + auto&& builder = Define(ignore_name).template WithType().IntoIgnore(); + assert(&builder == this); + (void)builder; // Ignore pointless unused warning, it's used in the assert. + } else { + // pretend this is a unit, e.g. -Xjitblocking + auto&& builder = Define(ignore_name).template WithType().IntoIgnore(); + assert(&builder == this); + (void)builder; // Ignore pointless unused warning, it's used in the assert. + } + } + ignore_list_ = ignore_list; + return *this; + } + + // Finish building the parser; performs sanity checks. Return value is moved, not copied. + // Do not call this more than once. + CmdlineParser Build() { + assert(!built_); + built_ = true; + + auto&& p = CmdlineParser(ignore_unrecognized_, + std::move(ignore_list_), + save_destination_, + std::move(completed_arguments_)); + + return std::move(p); + } + + protected: + void AppendCompletedArgument(detail::CmdlineParseArgumentAny* arg) { + auto smart_ptr = std::unique_ptr(arg); + completed_arguments_.push_back(std::move(smart_ptr)); + } + + private: + // No copying now! + Builder(const Builder& other) = delete; + + template + friend struct ArgumentBuilder; + friend struct UntypedArgumentBuilder; + friend struct CmdlineParser; + + bool built_ = false; + bool ignore_unrecognized_ = false; + std::vector ignore_list_; + std::shared_ptr save_destination_; + + std::vector> completed_arguments_; + }; + + CmdlineResult Parse(const std::string& argv) { + std::vector tokenized; + Split(argv, ' ', &tokenized); + + return Parse(TokenRange(std::move(tokenized))); + } + + // Parse the arguments; storing results into the arguments map. Returns success value. + CmdlineResult Parse(const char* argv) { + return Parse(std::string(argv)); + } + + // Parse the arguments; storing the results into the arguments map. Returns success value. + // Assumes that argv[0] is a valid argument (i.e. not the program name). + CmdlineResult Parse(const std::vector& argv) { + return Parse(TokenRange(argv.begin(), argv.end())); + } + + // Parse the arguments; storing the results into the arguments map. Returns success value. + // Assumes that argv[0] is a valid argument (i.e. not the program name). + CmdlineResult Parse(const std::vector& argv) { + return Parse(TokenRange(argv.begin(), argv.end())); + } + + // Parse the arguments (directly from an int main(argv,argc)). Returns success value. + // Assumes that argv[0] is the program name, and ignores it. + CmdlineResult Parse(const char* argv[], int argc) { + return Parse(TokenRange(&argv[1], argc - 1)); // ignore argv[0] because it's the program name + } + + // Look up the arguments that have been parsed; use the target keys to lookup individual args. + const TVariantMap& GetArgumentsMap() const { + return save_destination_->GetMap(); + } + + // Release the arguments map that has been parsed; useful for move semantics. + TVariantMap&& ReleaseArgumentsMap() { + return save_destination_->ReleaseMap(); + } + + // How many arguments were defined? + size_t CountDefinedArguments() const { + return completed_arguments_.size(); + } + + // Ensure we have a default move constructor. + CmdlineParser(CmdlineParser&& other) = default; + // Ensure we have a default move assignment operator. + CmdlineParser& operator=(CmdlineParser&& other) = default; + + private: + friend struct Builder; + + // Construct a new parser from the builder. Move all the arguments. + explicit CmdlineParser(bool ignore_unrecognized, + std::vector&& ignore_list, + std::shared_ptr save_destination, + std::vector>&& + completed_arguments) + : ignore_unrecognized_(ignore_unrecognized), + ignore_list_(std::move(ignore_list)), + save_destination_(save_destination), + completed_arguments_(std::move(completed_arguments)) { + assert(save_destination != nullptr); + } + + // Parse the arguments; storing results into the arguments map. Returns success value. + // The parsing will fail on the first non-success parse result and return that error. + // + // All previously-parsed arguments are cleared out. + // Otherwise, all parsed arguments will be stored into SaveDestination as a side-effect. + // A partial parse will result only in a partial save of the arguments. + CmdlineResult Parse(TokenRange&& arguments_list) { + save_destination_->Clear(); + + for (size_t i = 0; i < arguments_list.Size(); ) { + TokenRange possible_name = arguments_list.Slice(i); + + size_t best_match_size = 0; // How many tokens were matched in the best case. + size_t best_match_arg_idx = 0; + bool matched = false; // At least one argument definition has been matched? + + // Find the closest argument definition for the remaining token range. + size_t arg_idx = 0; + for (auto&& arg : completed_arguments_) { + size_t local_match = arg->MaybeMatches(possible_name); + + if (local_match > best_match_size) { + best_match_size = local_match; + best_match_arg_idx = arg_idx; + matched = true; + } + arg_idx++; + } + + // Saw some kind of unknown argument + if (matched == false) { + if (UNLIKELY(ignore_unrecognized_)) { // This is usually off, we only need it for JNI. + // Consume 1 token and keep going, hopefully the next token is a good one. + ++i; + continue; + } + // Common case: + // Bail out on the first unknown argument with an error. + return CmdlineResult(CmdlineResult::kUnknown, + std::string("Unknown argument: ") + possible_name[0]); + } + + // Look at the best-matched argument definition and try to parse against that. + auto&& arg = completed_arguments_[best_match_arg_idx]; + + assert(arg->MaybeMatches(possible_name) == best_match_size); + + // Try to parse the argument now, if we have enough tokens. + std::pair num_tokens = arg->GetNumTokens(); + size_t min_tokens; + size_t max_tokens; + + std::tie(min_tokens, max_tokens) = num_tokens; + + if ((i + min_tokens) > arguments_list.Size()) { + // expected longer command line but it was too short + // e.g. if the argv was only "-Xms" without specifying a memory option + CMDLINE_DEBUG_LOG << "Parse failure, i = " << i << ", arg list " << arguments_list.Size() << + " num tokens in arg_def: " << min_tokens << "," << max_tokens << std::endl; + return CmdlineResult(CmdlineResult::kFailure, + std::string("Argument ") + + possible_name[0] + ": incomplete command line arguments, expected " + + std::to_string(size_t(i + min_tokens) - arguments_list.Size()) + + " more tokens"); + } + + if (best_match_size > max_tokens || best_match_size < min_tokens) { + // Even our best match was out of range, so parsing would fail instantly. + return CmdlineResult(CmdlineResult::kFailure, + std::string("Argument ") + possible_name[0] + ": too few tokens " + "matched " + std::to_string(best_match_size) + + " but wanted " + std::to_string(num_tokens.first)); + } + + // We have enough tokens to begin exact parsing. + TokenRange exact_range = possible_name.Slice(0, max_tokens); + + size_t consumed_tokens = 1; // At least 1 if we ever want to try to resume parsing on error + CmdlineResult parse_attempt = arg->ParseArgument(exact_range, &consumed_tokens); + + if (parse_attempt.IsError()) { + // We may also want to continue parsing the other tokens to gather more errors. + return parse_attempt; + } // else the value has been successfully stored into the map + + assert(consumed_tokens > 0); // Don't hang in an infinite loop trying to parse + i += consumed_tokens; + + // TODO: also handle ignoring arguments for backwards compatibility + } // for + + return CmdlineResult(CmdlineResult::kSuccess); + } + + bool ignore_unrecognized_ = false; + std::vector ignore_list_; + std::shared_ptr save_destination_; + std::vector> completed_arguments_; +}; + +// This has to be defined after everything else, since we want the builders to call this. +template class TVariantMapKey> +template +CmdlineParser::ArgumentBuilder +CmdlineParser::CreateArgumentBuilder( + CmdlineParser::Builder& parent) { + return CmdlineParser::ArgumentBuilder( + parent, parent.save_destination_); +} + +// This has to be defined after everything else, since we want the builders to call this. +template class TVariantMapKey> +void CmdlineParser::AppendCompletedArgument( + CmdlineParser::Builder& builder, + detail::CmdlineParseArgumentAny* arg) { + builder.AppendCompletedArgument(arg); +} + +} // namespace art + +#endif // ART_CMDLINE_CMDLINE_PARSER_H_ diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc new file mode 100644 index 0000000..a875641 --- /dev/null +++ b/cmdline/cmdline_parser_test.cc @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cmdline_parser.h" +#include "runtime/runtime_options.h" +#include "runtime/parsed_options.h" + +#include "utils.h" +#include +#include "gtest/gtest.h" + +#define EXPECT_NULL(expected) EXPECT_EQ(reinterpret_cast(expected), \ + reinterpret_cast(NULL)); + +namespace art { + bool UsuallyEquals(double expected, double actual); + + // This has a gtest dependency, which is why it's in the gtest only. + bool operator==(const TestProfilerOptions& lhs, const TestProfilerOptions& rhs) { + return lhs.enabled_ == rhs.enabled_ && + lhs.output_file_name_ == rhs.output_file_name_ && + lhs.period_s_ == rhs.period_s_ && + lhs.duration_s_ == rhs.duration_s_ && + lhs.interval_us_ == rhs.interval_us_ && + UsuallyEquals(lhs.backoff_coefficient_, rhs.backoff_coefficient_) && + UsuallyEquals(lhs.start_immediately_, rhs.start_immediately_) && + UsuallyEquals(lhs.top_k_threshold_, rhs.top_k_threshold_) && + UsuallyEquals(lhs.top_k_change_threshold_, rhs.top_k_change_threshold_) && + lhs.profile_type_ == rhs.profile_type_ && + lhs.max_stack_depth_ == rhs.max_stack_depth_; + } + + bool UsuallyEquals(double expected, double actual) { + using FloatingPoint = ::testing::internal::FloatingPoint; + + FloatingPoint exp(expected); + FloatingPoint act(actual); + + // Compare with ULPs instead of comparing with == + return exp.AlmostEquals(act); + } + + template + bool UsuallyEquals(const T& expected, const T& actual, + typename std::enable_if< + detail::SupportsEqualityOperator::value>::type* = 0) { + return expected == actual; + } + + // Try to use memcmp to compare simple plain-old-data structs. + // + // This should *not* generate false positives, but it can generate false negatives. + // This will mostly work except for fields like float which can have different bit patterns + // that are nevertheless equal. + // If a test is failing because the structs aren't "equal" when they really are + // then it's recommended to implement operator== for it instead. + template + bool UsuallyEquals(const T& expected, const T& actual, + const Ignore& ... more ATTRIBUTE_UNUSED, + typename std::enable_if::value>::type* = 0, + typename std::enable_if::value>::type* = 0 + ) { + return memcmp(std::addressof(expected), std::addressof(actual), sizeof(T)) == 0; + } + + bool UsuallyEquals(const XGcOption& expected, const XGcOption& actual) { + return memcmp(std::addressof(expected), std::addressof(actual), sizeof(expected)) == 0; + } + + bool UsuallyEquals(const char* expected, std::string actual) { + return std::string(expected) == actual; + } + + template + ::testing::AssertionResult IsExpectedKeyValue(const T& expected, + const TMap& map, + const TKey& key) { + auto* actual = map.Get(key); + if (actual != nullptr) { + if (!UsuallyEquals(expected, *actual)) { + return ::testing::AssertionFailure() + << "expected " << detail::ToStringAny(expected) << " but got " + << detail::ToStringAny(*actual); + } + return ::testing::AssertionSuccess(); + } + + return ::testing::AssertionFailure() << "key was not in the map"; + } + +class CmdlineParserTest : public ::testing::Test { + public: + CmdlineParserTest() = default; + ~CmdlineParserTest() = default; + + protected: + using M = RuntimeArgumentMap; + using RuntimeParser = ParsedOptions::RuntimeParser; + + static void SetUpTestCase() { + art::InitLogging(nullptr); // argv = null + } + + virtual void SetUp() { + parser_ = ParsedOptions::MakeParser(false); // do not ignore unrecognized options + } + + static ::testing::AssertionResult IsResultSuccessful(CmdlineResult result) { + if (result.IsSuccess()) { + return ::testing::AssertionSuccess(); + } else { + return ::testing::AssertionFailure() + << result.GetStatus() << " with: " << result.GetMessage(); + } + } + + static ::testing::AssertionResult IsResultFailure(CmdlineResult result, + CmdlineResult::Status failure_status) { + if (result.IsSuccess()) { + return ::testing::AssertionFailure() << " got success but expected failure: " + << failure_status; + } else if (result.GetStatus() == failure_status) { + return ::testing::AssertionSuccess(); + } + + return ::testing::AssertionFailure() << " expected failure " << failure_status + << " but got " << result.GetStatus(); + } + + std::unique_ptr parser_; +}; + +#define EXPECT_KEY_EXISTS(map, key) EXPECT_TRUE((map).Exists(key)) +#define EXPECT_KEY_VALUE(map, key, expected) EXPECT_TRUE(IsExpectedKeyValue(expected, map, key)) + +#define EXPECT_SINGLE_PARSE_EMPTY_SUCCESS(argv) \ + do { \ + EXPECT_TRUE(IsResultSuccessful(parser_->Parse(argv))); \ + EXPECT_EQ(0u, parser_->GetArgumentsMap().Size()); \ + } while (false) + +#define _EXPECT_SINGLE_PARSE_EXISTS(argv, key) \ + do { \ + EXPECT_TRUE(IsResultSuccessful(parser_->Parse(argv))); \ + RuntimeArgumentMap args = parser_->ReleaseArgumentsMap(); \ + EXPECT_EQ(1u, args.Size()); \ + EXPECT_KEY_EXISTS(args, key); \ + +#define EXPECT_SINGLE_PARSE_EXISTS(argv, key) \ + _EXPECT_SINGLE_PARSE_EXISTS(argv, key); \ + } while (false) + +#define EXPECT_SINGLE_PARSE_VALUE(expected, argv, key) \ + _EXPECT_SINGLE_PARSE_EXISTS(argv, key); \ + EXPECT_KEY_VALUE(args, key, expected); \ + } while (false) // NOLINT [readability/namespace] [5] + +#define EXPECT_SINGLE_PARSE_VALUE_STR(expected, argv, key) \ + EXPECT_SINGLE_PARSE_VALUE(std::string(expected), argv, key) + +#define EXPECT_SINGLE_PARSE_FAIL(argv, failure_status) \ + do { \ + EXPECT_TRUE(IsResultFailure(parser_->Parse(argv), failure_status));\ + RuntimeArgumentMap args = parser_->ReleaseArgumentsMap();\ + EXPECT_EQ(0u, args.Size()); \ + } while (false) + +TEST_F(CmdlineParserTest, TestSimpleSuccesses) { + auto& parser = *parser_; + + EXPECT_LT(0u, parser.CountDefinedArguments()); + + { + // Test case 1: No command line arguments + EXPECT_TRUE(IsResultSuccessful(parser.Parse(""))); + RuntimeArgumentMap args = parser.ReleaseArgumentsMap(); + EXPECT_EQ(0u, args.Size()); + } + + EXPECT_SINGLE_PARSE_EXISTS("-Xzygote", M::Zygote); + EXPECT_SINGLE_PARSE_VALUE_STR("/hello/world", "-Xbootclasspath:/hello/world", M::BootClassPath); + EXPECT_SINGLE_PARSE_VALUE("/hello/world", "-Xbootclasspath:/hello/world", M::BootClassPath); + EXPECT_SINGLE_PARSE_VALUE(false, "-Xverify:none", M::Verify); + EXPECT_SINGLE_PARSE_VALUE(true, "-Xverify:remote", M::Verify); + EXPECT_SINGLE_PARSE_VALUE(true, "-Xverify:all", M::Verify); + EXPECT_SINGLE_PARSE_VALUE(Memory<1>(234), "-Xss234", M::StackSize); + EXPECT_SINGLE_PARSE_VALUE(MemoryKiB(1234*MB), "-Xms1234m", M::MemoryInitialSize); + EXPECT_SINGLE_PARSE_VALUE(true, "-XX:EnableHSpaceCompactForOOM", M::EnableHSpaceCompactForOOM); + EXPECT_SINGLE_PARSE_VALUE(false, "-XX:DisableHSpaceCompactForOOM", M::EnableHSpaceCompactForOOM); + EXPECT_SINGLE_PARSE_VALUE(0.5, "-XX:HeapTargetUtilization=0.5", M::HeapTargetUtilization); + EXPECT_SINGLE_PARSE_VALUE(5u, "-XX:ParallelGCThreads=5", M::ParallelGCThreads); +} // TEST_F + +TEST_F(CmdlineParserTest, TestSimpleFailures) { + // Test argument is unknown to the parser + EXPECT_SINGLE_PARSE_FAIL("abcdefg^%@#*(@#", CmdlineResult::kUnknown); + // Test value map substitution fails + EXPECT_SINGLE_PARSE_FAIL("-Xverify:whatever", CmdlineResult::kFailure); + // Test value type parsing failures + EXPECT_SINGLE_PARSE_FAIL("-Xsswhatever", CmdlineResult::kFailure); // invalid memory value + EXPECT_SINGLE_PARSE_FAIL("-Xms123", CmdlineResult::kFailure); // memory value too small + EXPECT_SINGLE_PARSE_FAIL("-XX:HeapTargetUtilization=0.0", CmdlineResult::kOutOfRange); // toosmal + EXPECT_SINGLE_PARSE_FAIL("-XX:HeapTargetUtilization=2.0", CmdlineResult::kOutOfRange); // toolarg + EXPECT_SINGLE_PARSE_FAIL("-XX:ParallelGCThreads=-5", CmdlineResult::kOutOfRange); // too small + EXPECT_SINGLE_PARSE_FAIL("-Xgc:blablabla", CmdlineResult::kUsage); // not a valid suboption +} // TEST_F + +TEST_F(CmdlineParserTest, TestLogVerbosity) { + { + const char* log_args = "-verbose:" + "class,compiler,gc,heap,jdwp,jni,monitor,profiler,signals,startup,third-party-jni," + "threads,verifier"; + + LogVerbosity log_verbosity = LogVerbosity(); + log_verbosity.class_linker = true; + log_verbosity.compiler = true; + log_verbosity.gc = true; + log_verbosity.heap = true; + log_verbosity.jdwp = true; + log_verbosity.jni = true; + log_verbosity.monitor = true; + log_verbosity.profiler = true; + log_verbosity.signals = true; + log_verbosity.startup = true; + log_verbosity.third_party_jni = true; + log_verbosity.threads = true; + log_verbosity.verifier = true; + + EXPECT_SINGLE_PARSE_VALUE(log_verbosity, log_args, M::Verbose); + } + + { + const char* log_args = "-verbose:" + "class,compiler,gc,heap,jdwp,jni,monitor"; + + LogVerbosity log_verbosity = LogVerbosity(); + log_verbosity.class_linker = true; + log_verbosity.compiler = true; + log_verbosity.gc = true; + log_verbosity.heap = true; + log_verbosity.jdwp = true; + log_verbosity.jni = true; + log_verbosity.monitor = true; + + EXPECT_SINGLE_PARSE_VALUE(log_verbosity, log_args, M::Verbose); + } + + EXPECT_SINGLE_PARSE_FAIL("-verbose:blablabla", CmdlineResult::kUsage); // invalid verbose opt +} // TEST_F + +TEST_F(CmdlineParserTest, TestXGcOption) { + /* + * Test success + */ + { + XGcOption option_all_true{}; // NOLINT [readability/braces] [4] + option_all_true.collector_type_ = gc::CollectorType::kCollectorTypeCMS; + option_all_true.verify_pre_gc_heap_ = true; + option_all_true.verify_pre_sweeping_heap_ = true; + option_all_true.verify_post_gc_heap_ = true; + option_all_true.verify_pre_gc_rosalloc_ = true; + option_all_true.verify_pre_sweeping_rosalloc_ = true; + option_all_true.verify_post_gc_rosalloc_ = true; + + const char * xgc_args_all_true = "-Xgc:concurrent," + "preverify,presweepingverify,postverify," + "preverify_rosalloc,presweepingverify_rosalloc," + "postverify_rosalloc,precise," + "verifycardtable"; + + EXPECT_SINGLE_PARSE_VALUE(option_all_true, xgc_args_all_true, M::GcOption); + + XGcOption option_all_false{}; // NOLINT [readability/braces] [4] + option_all_false.collector_type_ = gc::CollectorType::kCollectorTypeMS; + option_all_false.verify_pre_gc_heap_ = false; + option_all_false.verify_pre_sweeping_heap_ = false; + option_all_false.verify_post_gc_heap_ = false; + option_all_false.verify_pre_gc_rosalloc_ = false; + option_all_false.verify_pre_sweeping_rosalloc_ = false; + option_all_false.verify_post_gc_rosalloc_ = false; + + const char* xgc_args_all_false = "-Xgc:nonconcurrent," + "nopreverify,nopresweepingverify,nopostverify,nopreverify_rosalloc," + "nopresweepingverify_rosalloc,nopostverify_rosalloc,noprecise,noverifycardtable"; + + EXPECT_SINGLE_PARSE_VALUE(option_all_false, xgc_args_all_false, M::GcOption); + + XGcOption option_all_default{}; // NOLINT [readability/braces] [4] + + option_all_default.collector_type_ = gc::kCollectorTypeDefault; + option_all_default.verify_pre_gc_heap_ = false; + option_all_default.verify_pre_sweeping_heap_ = kIsDebugBuild; + option_all_default.verify_post_gc_heap_ = false; + option_all_default.verify_pre_gc_rosalloc_ = kIsDebugBuild; + option_all_default.verify_pre_sweeping_rosalloc_ = false; + option_all_default.verify_post_gc_rosalloc_ = false; + + const char* xgc_args_blank = "-Xgc:"; + EXPECT_SINGLE_PARSE_VALUE(option_all_default, xgc_args_blank, M::GcOption); + } + + /* + * Test failures + */ + EXPECT_SINGLE_PARSE_FAIL("-Xgc:blablabla", CmdlineResult::kUsage); // invalid Xgc opt +} // TEST_F + +/* + * {"-Xrunjdwp:_", "-agentlib:jdwp=_"} + */ +TEST_F(CmdlineParserTest, TestJdwpOptions) { + /* + * Test success + */ + { + /* + * "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n" + */ + JDWP::JdwpOptions opt = JDWP::JdwpOptions(); + opt.transport = JDWP::JdwpTransportType::kJdwpTransportSocket; + opt.port = 8000; + opt.server = true; + + const char *opt_args = "-Xrunjdwp:transport=dt_socket,address=8000,server=y"; + + EXPECT_SINGLE_PARSE_VALUE(opt, opt_args, M::JdwpOptions); + } + + { + /* + * "Example: -agentlib:jdwp=transport=dt_socket,address=localhost:6500,server=n\n"); + */ + JDWP::JdwpOptions opt = JDWP::JdwpOptions(); + opt.transport = JDWP::JdwpTransportType::kJdwpTransportSocket; + opt.host = "localhost"; + opt.port = 6500; + opt.server = false; + + const char *opt_args = "-agentlib:jdwp=transport=dt_socket,address=localhost:6500,server=n"; + + EXPECT_SINGLE_PARSE_VALUE(opt, opt_args, M::JdwpOptions); + } + + /* + * Test failures + */ + EXPECT_SINGLE_PARSE_FAIL("-Xrunjdwp:help", CmdlineResult::kUsage); // usage for help only + EXPECT_SINGLE_PARSE_FAIL("-Xrunjdwp:blabla", CmdlineResult::kFailure); // invalid subarg + EXPECT_SINGLE_PARSE_FAIL("-agentlib:jdwp=help", CmdlineResult::kUsage); // usage for help only + EXPECT_SINGLE_PARSE_FAIL("-agentlib:jdwp=blabla", CmdlineResult::kFailure); // invalid subarg +} // TEST_F + +/* + * -D_ -D_ -D_ ... + */ +TEST_F(CmdlineParserTest, TestPropertiesList) { + /* + * Test successes + */ + { + std::vector opt = {"hello"}; + + EXPECT_SINGLE_PARSE_VALUE(opt, "-Dhello", M::PropertiesList); + } + + { + std::vector opt = {"hello", "world"}; + + EXPECT_SINGLE_PARSE_VALUE(opt, "-Dhello -Dworld", M::PropertiesList); + } + + { + std::vector opt = {"one", "two", "three"}; + + EXPECT_SINGLE_PARSE_VALUE(opt, "-Done -Dtwo -Dthree", M::PropertiesList); + } +} // TEST_F + +/* +* -Xcompiler-option foo -Xcompiler-option bar ... +*/ +TEST_F(CmdlineParserTest, TestCompilerOption) { + /* + * Test successes + */ + { + std::vector opt = {"hello"}; + EXPECT_SINGLE_PARSE_VALUE(opt, "-Xcompiler-option hello", M::CompilerOptions); + } + + { + std::vector opt = {"hello", "world"}; + EXPECT_SINGLE_PARSE_VALUE(opt, + "-Xcompiler-option hello -Xcompiler-option world", + M::CompilerOptions); + } + + { + std::vector opt = {"one", "two", "three"}; + EXPECT_SINGLE_PARSE_VALUE(opt, + "-Xcompiler-option one -Xcompiler-option two -Xcompiler-option three", + M::CompilerOptions); + } +} // TEST_F + +/* +* -X-profile-* +*/ +TEST_F(CmdlineParserTest, TestProfilerOptions) { + /* + * Test successes + */ + + { + TestProfilerOptions opt; + opt.enabled_ = true; + + EXPECT_SINGLE_PARSE_VALUE(opt, + "-Xenable-profiler", + M::ProfilerOpts); + } + + { + TestProfilerOptions opt; + // also need to test 'enabled' + opt.output_file_name_ = "hello_world.txt"; + + EXPECT_SINGLE_PARSE_VALUE(opt, + "-Xprofile-filename:hello_world.txt ", + M::ProfilerOpts); + } + + { + TestProfilerOptions opt = TestProfilerOptions(); + // also need to test 'enabled' + opt.output_file_name_ = "output.txt"; + opt.period_s_ = 123u; + opt.duration_s_ = 456u; + opt.interval_us_ = 789u; + opt.backoff_coefficient_ = 2.0; + opt.start_immediately_ = true; + opt.top_k_threshold_ = 50.0; + opt.top_k_change_threshold_ = 60.0; + opt.profile_type_ = kProfilerMethod; + opt.max_stack_depth_ = 1337u; + + EXPECT_SINGLE_PARSE_VALUE(opt, + "-Xprofile-filename:output.txt " + "-Xprofile-period:123 " + "-Xprofile-duration:456 " + "-Xprofile-interval:789 " + "-Xprofile-backoff:2.0 " + "-Xprofile-start-immediately " + "-Xprofile-top-k-threshold:50.0 " + "-Xprofile-top-k-change-threshold:60.0 " + "-Xprofile-type:method " + "-Xprofile-max-stack-depth:1337", + M::ProfilerOpts); + } + + { + TestProfilerOptions opt = TestProfilerOptions(); + opt.profile_type_ = kProfilerBoundedStack; + + EXPECT_SINGLE_PARSE_VALUE(opt, + "-Xprofile-type:stack", + M::ProfilerOpts); + } +} // TEST_F + +TEST_F(CmdlineParserTest, TestIgnoreUnrecognized) { + RuntimeParser::Builder parserBuilder; + + parserBuilder + .Define("-help") + .IntoKey(M::Help) + .IgnoreUnrecognized(true); + + parser_.reset(new RuntimeParser(parserBuilder.Build())); + + EXPECT_SINGLE_PARSE_EMPTY_SUCCESS("-non-existent-option"); + EXPECT_SINGLE_PARSE_EMPTY_SUCCESS("-non-existent-option1 --non-existent-option-2"); +} // TEST_F + +TEST_F(CmdlineParserTest, TestIgnoredArguments) { + std::initializer_list ignored_args = { + "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", + "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:abdef", + "-Xdexopt:foobar", "-Xnoquithandler", "-Xjnigreflimit:ixnay", "-Xgenregmap", "-Xnogenregmap", + "-Xverifyopt:never", "-Xcheckdexsum", "-Xincludeselectedop", "-Xjitop:noop", + "-Xincludeselectedmethod", "-Xjitthreshold:123", "-Xjitcodecachesize:12345", + "-Xjitblocking", "-Xjitmethod:_", "-Xjitclass:nosuchluck", "-Xjitoffset:none", + "-Xjitconfig:yes", "-Xjitcheckcg", "-Xjitverbose", "-Xjitprofile", + "-Xjitdisableopt", "-Xjitsuspendpoll", "-XX:mainThreadStackSize=1337" + }; + + // Check they are ignored when parsed one at a time + for (auto&& arg : ignored_args) { + SCOPED_TRACE(arg); + EXPECT_SINGLE_PARSE_EMPTY_SUCCESS(arg); + } + + // Check they are ignored when we pass it all together at once + std::vector argv = ignored_args; + EXPECT_SINGLE_PARSE_EMPTY_SUCCESS(argv); +} // TEST_F + +TEST_F(CmdlineParserTest, MultipleArguments) { + EXPECT_TRUE(IsResultSuccessful(parser_->Parse( + "-help -XX:ForegroundHeapGrowthMultiplier=0.5 " + "-Xnodex2oat -Xmethod-trace -XX:LargeObjectSpace=map"))); + + auto&& map = parser_->ReleaseArgumentsMap(); + EXPECT_EQ(5u, map.Size()); + EXPECT_KEY_VALUE(map, M::Help, Unit{}); // NOLINT [whitespace/braces] [5] + EXPECT_KEY_VALUE(map, M::ForegroundHeapGrowthMultiplier, 0.5); + EXPECT_KEY_VALUE(map, M::Dex2Oat, false); + EXPECT_KEY_VALUE(map, M::MethodTrace, Unit{}); // NOLINT [whitespace/braces] [5] + EXPECT_KEY_VALUE(map, M::LargeObjectSpace, gc::space::LargeObjectSpaceType::kMap); +} // TEST_F +} // namespace art diff --git a/cmdline/cmdline_result.h b/cmdline/cmdline_result.h new file mode 100644 index 0000000..bf3a85d --- /dev/null +++ b/cmdline/cmdline_result.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_CMDLINE_RESULT_H_ +#define ART_CMDLINE_CMDLINE_RESULT_H_ + +#include +#include + +namespace art { + // Result of an attempt to process the command line arguments. If fails, specifies + // the specific error code and an error message. + // Use the value-carrying CmdlineParseResult to get an additional value out in a success case. + struct CmdlineResult { + enum Status { + kSuccess, + // Error codes: + kUsage, + kFailure, + kOutOfRange, + kUnknown, + }; + + // Short-hand for checking if the result was successful. + operator bool() const { + return IsSuccess(); + } + + // Check if the operation has succeeded. + bool IsSuccess() const { return status_ == kSuccess; } + // Check if the operation was not a success. + bool IsError() const { return status_ != kSuccess; } + // Get the specific status, regardless of whether it's failure or success. + Status GetStatus() const { return status_; } + + // Get the error message, *must* only be called for error status results. + const std::string& GetMessage() const { assert(IsError()); return message_; } + + // Constructor any status. No message. + explicit CmdlineResult(Status status) : status_(status) {} + + // Constructor with an error status, copying the message. + CmdlineResult(Status status, const std::string& message) + : status_(status), message_(message) { + assert(status != kSuccess); + } + + // Constructor with an error status, taking over the message. + CmdlineResult(Status status, std::string&& message) + : status_(status), message_(message) { + assert(status != kSuccess); + } + + // Make sure copying exists + CmdlineResult(const CmdlineResult& other) = default; + // Make sure moving is cheap + CmdlineResult(CmdlineResult&& other) = default; + + private: + const Status status_; + const std::string message_; + }; + + // TODO: code-generate this + static inline std::ostream& operator<<(std::ostream& stream, CmdlineResult::Status status) { + switch (status) { + case CmdlineResult::kSuccess: + stream << "kSuccess"; + break; + case CmdlineResult::kUsage: + stream << "kUsage"; + break; + case CmdlineResult::kFailure: + stream << "kFailure"; + break; + case CmdlineResult::kOutOfRange: + stream << "kOutOfRange"; + break; + case CmdlineResult::kUnknown: + stream << "kUnknown"; + break; + default: + UNREACHABLE(); + } + return stream; + } + +} // namespace art + +#endif // ART_CMDLINE_CMDLINE_RESULT_H_ diff --git a/cmdline/cmdline_type_parser.h b/cmdline/cmdline_type_parser.h new file mode 100644 index 0000000..fa5cdaf --- /dev/null +++ b/cmdline/cmdline_type_parser.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_CMDLINE_TYPE_PARSER_H_ +#define ART_CMDLINE_CMDLINE_TYPE_PARSER_H_ + +#include "cmdline_parse_result.h" + +namespace art { + +// Base class for user-defined CmdlineType specializations. +// +// Not strictly necessary, but if the specializations fail to Define all of these functions +// the compilation will fail. +template +struct CmdlineTypeParser { + // Return value of parsing attempts. Represents a Success(T value) or an Error(int code) + using Result = CmdlineParseResult; + + // Parse a single value for an argument definition out of the wildcard component. + // + // e.g. if the argument definition was "foo:_", and the user-provided input was "foo:bar", + // then args is "bar". + Result Parse(const std::string& args ATTRIBUTE_UNUSED) { + assert(false); + return Result::Failure("Missing type specialization and/or value map"); + } + + // Parse a value and append it into the existing value so far, for argument + // definitions which are marked with AppendValues(). + // + // The value is parsed out of the wildcard component as in Parse. + // + // If the initial value does not exist yet, a default value is created by + // value-initializing with 'T()'. + Result ParseAndAppend(const std::string& args ATTRIBUTE_UNUSED, + T& existing_value ATTRIBUTE_UNUSED) { + assert(false); + return Result::Failure("Missing type specialization and/or value map"); + } + + // Runtime type name of T, so that we can print more useful error messages. + static const char* Name() { assert(false); return "UnspecializedType"; } + + // Whether or not your type can parse argument definitions defined without a "_" + // e.g. -Xenable-profiler just mutates the existing profiler struct in-place + // so it doesn't need to do any parsing other than token recognition. + // + // If this is false, then either the argument definition has a _, from which the parsing + // happens, or the tokens get mapped to a value list/map from which a 1:1 matching occurs. + // + // This should almost *always* be false! + static constexpr bool kCanParseBlankless = false; + + protected: + // Don't accidentally initialize instances of this directly; they will assert at runtime. + CmdlineTypeParser() = default; +}; + + +} // namespace art + +#endif // ART_CMDLINE_CMDLINE_TYPE_PARSER_H_ diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h new file mode 100644 index 0000000..9221023 --- /dev/null +++ b/cmdline/cmdline_types.h @@ -0,0 +1,820 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ART_CMDLINE_CMDLINE_TYPES_H_ +#define ART_CMDLINE_CMDLINE_TYPES_H_ + +#define CMDLINE_NDEBUG 1 // Do not output any debugging information for parsing. + +#include "cmdline/memory_representation.h" +#include "cmdline/detail/cmdline_debug_detail.h" +#include "cmdline_type_parser.h" + +// Includes for the types that are being specialized +#include +#include "unit.h" +#include "jdwp/jdwp.h" +#include "runtime/base/logging.h" +#include "gc/collector_type.h" +#include "gc/space/large_object_space.h" +#include "profiler_options.h" + +namespace art { + +// The default specialization will always fail parsing the type from a string. +// Provide your own specialization that inherits from CmdlineTypeParser +// and implements either Parse or ParseAndAppend +// (only if the argument was defined with ::AppendValues()) but not both. +template +struct CmdlineType : CmdlineTypeParser { +}; + +// Specializations for CmdlineType follow: + +// Parse argument definitions for Unit-typed arguments. +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& args) { + if (args == "") { + return Result::Success(Unit{}); // NOLINT [whitespace/braces] [5] + } + return Result::Failure("Unexpected extra characters " + args); + } +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& options) { + VLOG(jdwp) << "ParseJdwpOptions: " << options; + + if (options == "help") { + return Result::Usage( + "Example: -Xrunjdwp:transport=dt_socket,address=8000,server=y\n" + "Example: -Xrunjdwp:transport=dt_socket,address=localhost:6500,server=n\n"); + } + + const std::string s; + + std::vector pairs; + Split(options, ',', &pairs); + + JDWP::JdwpOptions jdwp_options = JDWP::JdwpOptions(); + std::stringstream error_stream; + + for (size_t i = 0; i < pairs.size(); ++i) { + std::string::size_type equals = pairs[i].find('='); + if (equals == std::string::npos) { + return Result::Failure(s + + "Can't parse JDWP option '" + pairs[i] + "' in '" + options + "'"); + } + + if (!ParseJdwpOption(pairs[i].substr(0, equals), + pairs[i].substr(equals + 1), + error_stream, + jdwp_options)) { + return Result::Failure(error_stream.str()); + } + } + + if (jdwp_options.transport == JDWP::kJdwpTransportUnknown) { + return Result::Failure(s + "Must specify JDWP transport: " + options); + } + if (!jdwp_options.server && (jdwp_options.host.empty() || jdwp_options.port == 0)) { + return Result::Failure(s + "Must specify JDWP host and port when server=n: " + options); + } + + return Result::Success(std::move(jdwp_options)); + } + + bool ParseJdwpOption(const std::string& name, const std::string& value, + std::ostream& error_stream, + JDWP::JdwpOptions& jdwp_options) { + if (name == "transport") { + if (value == "dt_socket") { + jdwp_options.transport = JDWP::kJdwpTransportSocket; + } else if (value == "dt_android_adb") { + jdwp_options.transport = JDWP::kJdwpTransportAndroidAdb; + } else { + error_stream << "JDWP transport not supported: " << value; + return false; + } + } else if (name == "server") { + if (value == "n") { + jdwp_options.server = false; + } else if (value == "y") { + jdwp_options.server = true; + } else { + error_stream << "JDWP option 'server' must be 'y' or 'n'"; + return false; + } + } else if (name == "suspend") { + if (value == "n") { + jdwp_options.suspend = false; + } else if (value == "y") { + jdwp_options.suspend = true; + } else { + error_stream << "JDWP option 'suspend' must be 'y' or 'n'"; + return false; + } + } else if (name == "address") { + /* this is either or : */ + std::string port_string; + jdwp_options.host.clear(); + std::string::size_type colon = value.find(':'); + if (colon != std::string::npos) { + jdwp_options.host = value.substr(0, colon); + port_string = value.substr(colon + 1); + } else { + port_string = value; + } + if (port_string.empty()) { + error_stream << "JDWP address missing port: " << value; + return false; + } + char* end; + uint64_t port = strtoul(port_string.c_str(), &end, 10); + if (*end != '\0' || port > 0xffff) { + error_stream << "JDWP address has junk in port field: " << value; + return false; + } + jdwp_options.port = port; + } else if (name == "launch" || name == "onthrow" || name == "oncaught" || name == "timeout") { + /* valid but unsupported */ + LOG(INFO) << "Ignoring JDWP option '" << name << "'='" << value << "'"; + } else { + LOG(INFO) << "Ignoring unrecognized JDWP option '" << name << "'='" << value << "'"; + } + + return true; + } + + static const char* Name() { return "JdwpOptions"; } +}; + +template +struct CmdlineType> : CmdlineTypeParser> { + using typename CmdlineTypeParser>::Result; + + Result Parse(const std::string arg) { + CMDLINE_DEBUG_LOG << "Parsing memory: " << arg << std::endl; + size_t val = ParseMemoryOption(arg.c_str(), Divisor); + CMDLINE_DEBUG_LOG << "Memory parsed to size_t value: " << val << std::endl; + + if (val == 0) { + return Result::Failure(std::string("not a valid memory value, or not divisible by ") + + std::to_string(Divisor)); + } + + return Result::Success(Memory(val)); + } + + // Parse a string of the form /[0-9]+[kKmMgG]?/, which is used to specify + // memory sizes. [kK] indicates kilobytes, [mM] megabytes, and + // [gG] gigabytes. + // + // "s" should point just past the "-Xm?" part of the string. + // "div" specifies a divisor, e.g. 1024 if the value must be a multiple + // of 1024. + // + // The spec says the -Xmx and -Xms options must be multiples of 1024. It + // doesn't say anything about -Xss. + // + // Returns 0 (a useless size) if "s" is malformed or specifies a low or + // non-evenly-divisible value. + // + static size_t ParseMemoryOption(const char* s, size_t div) { + // strtoul accepts a leading [+-], which we don't want, + // so make sure our string starts with a decimal digit. + if (isdigit(*s)) { + char* s2; + size_t val = strtoul(s, &s2, 10); + if (s2 != s) { + // s2 should be pointing just after the number. + // If this is the end of the string, the user + // has specified a number of bytes. Otherwise, + // there should be exactly one more character + // that specifies a multiplier. + if (*s2 != '\0') { + // The remainder of the string is either a single multiplier + // character, or nothing to indicate that the value is in + // bytes. + char c = *s2++; + if (*s2 == '\0') { + size_t mul; + if (c == '\0') { + mul = 1; + } else if (c == 'k' || c == 'K') { + mul = KB; + } else if (c == 'm' || c == 'M') { + mul = MB; + } else if (c == 'g' || c == 'G') { + mul = GB; + } else { + // Unknown multiplier character. + return 0; + } + + if (val <= std::numeric_limits::max() / mul) { + val *= mul; + } else { + // Clamp to a multiple of 1024. + val = std::numeric_limits::max() & ~(1024-1); + } + } else { + // There's more than one character after the numeric part. + return 0; + } + } + // The man page says that a -Xm value must be a multiple of 1024. + if (val % div == 0) { + return val; + } + } + } + return 0; + } + + static const char* Name() { return Memory::Name(); } +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& str) { + char* end = nullptr; + errno = 0; + double value = strtod(str.c_str(), &end); + + if (*end != '\0') { + return Result::Failure("Failed to parse double from " + str); + } + if (errno == ERANGE) { + return Result::OutOfRange( + "Failed to parse double from " + str + "; overflow/underflow occurred"); + } + + return Result::Success(value); + } + + static const char* Name() { return "double"; } +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& str) { + 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 Result::Failure("Failed to parse integer from " + str); + } else if ((errno == ERANGE) || // NOLINT [runtime/int] [4] + result < std::numeric_limits::min() + || result > std::numeric_limits::max() || result < 0) { + return Result::OutOfRange( + "Failed to parse integer from " + str + "; out of unsigned int range"); + } + + return Result::Success(static_cast(result)); + } + + static const char* Name() { return "unsigned integer"; } +}; + +// Lightweight nanosecond value type. Allows parser to convert user-input from milliseconds +// to nanoseconds automatically after parsing. +// +// All implicit conversion from uint64_t uses nanoseconds. +struct MillisecondsToNanoseconds { + // Create from nanoseconds. + MillisecondsToNanoseconds(uint64_t nanoseconds) : nanoseconds_(nanoseconds) { // NOLINT [runtime/explicit] [5] + } + + // Create from milliseconds. + static MillisecondsToNanoseconds FromMilliseconds(unsigned int milliseconds) { + return MillisecondsToNanoseconds(MsToNs(milliseconds)); + } + + // Get the underlying nanoseconds value. + uint64_t GetNanoseconds() const { + return nanoseconds_; + } + + // Get the milliseconds value [via a conversion]. Loss of precision will occur. + uint64_t GetMilliseconds() const { + return NsToMs(nanoseconds_); + } + + // Get the underlying nanoseconds value. + operator uint64_t() const { + return GetNanoseconds(); + } + + // Default constructors/copy-constructors. + MillisecondsToNanoseconds() : nanoseconds_(0ul) {} + MillisecondsToNanoseconds(const MillisecondsToNanoseconds& rhs) = default; + MillisecondsToNanoseconds(MillisecondsToNanoseconds&& rhs) = default; + + private: + uint64_t nanoseconds_; +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& str) { + CmdlineType uint_parser; + CmdlineParseResult res = uint_parser.Parse(str); + + if (res.IsSuccess()) { + return Result::Success(MillisecondsToNanoseconds::FromMilliseconds(res.GetValue())); + } else { + return Result::CastError(res); + } + } + + static const char* Name() { return "MillisecondsToNanoseconds"; } +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& args) { + return Result::Success(args); + } + + Result ParseAndAppend(const std::string& args, + std::string& existing_value) { + if (existing_value.empty()) { + existing_value = args; + } else { + existing_value += ' '; + existing_value += args; + } + return Result::SuccessNoValue(); + } +}; + +template <> +struct CmdlineType> : CmdlineTypeParser> { + Result Parse(const std::string& args) { + assert(false && "Use AppendValues() for a string vector type"); + return Result::Failure("Unconditional failure: string vector must be appended: " + args); + } + + Result ParseAndAppend(const std::string& args, + std::vector& existing_value) { + existing_value.push_back(args); + return Result::SuccessNoValue(); + } + + static const char* Name() { return "std::vector"; } +}; + +template +struct ParseStringList { + explicit ParseStringList(std::vector&& list) : list_(list) {} + + operator std::vector() const { + return list_; + } + + operator std::vector&&() && { + return std::move(list_); + } + + size_t Size() const { + return list_.size(); + } + + std::string Join() const { + return art::Join(list_, Separator); + } + + static ParseStringList Split(const std::string& str) { + std::vector list; + art::Split(str, Separator, &list); + return ParseStringList(std::move(list)); + } + + ParseStringList() = default; + ParseStringList(const ParseStringList& rhs) = default; + ParseStringList(ParseStringList&& rhs) = default; + + private: + std::vector list_; +}; + +template +struct CmdlineType> : CmdlineTypeParser> { + using Result = CmdlineParseResult>; + + Result Parse(const std::string& args) { + return Result::Success(ParseStringList::Split(args)); + } + + static const char* Name() { return "ParseStringList"; } +}; + +static gc::CollectorType ParseCollectorType(const std::string& option) { + if (option == "MS" || option == "nonconcurrent") { + return gc::kCollectorTypeMS; + } else if (option == "CMS" || option == "concurrent") { + return gc::kCollectorTypeCMS; + } else if (option == "SS") { + return gc::kCollectorTypeSS; + } else if (option == "GSS") { + return gc::kCollectorTypeGSS; + } else if (option == "CC") { + return gc::kCollectorTypeCC; + } else if (option == "MC") { + return gc::kCollectorTypeMC; + } else { + return gc::kCollectorTypeNone; + } +} + +struct XGcOption { + // These defaults are used when the command line arguments for -Xgc: + // are either omitted completely or partially. + gc::CollectorType collector_type_ = kUseReadBarrier ? + // If RB is enabled (currently a build-time decision), + // use CC as the default GC. + gc::kCollectorTypeCC : + gc::kCollectorTypeDefault; + bool verify_pre_gc_heap_ = false; + bool verify_pre_sweeping_heap_ = kIsDebugBuild; + bool verify_post_gc_heap_ = false; + bool verify_pre_gc_rosalloc_ = kIsDebugBuild; + bool verify_pre_sweeping_rosalloc_ = false; + bool verify_post_gc_rosalloc_ = false; +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& option) { // -Xgc: already stripped + XGcOption xgc{}; // NOLINT [readability/braces] [4] + + std::vector gc_options; + Split(option, ',', &gc_options); + for (const std::string& gc_option : gc_options) { + gc::CollectorType collector_type = ParseCollectorType(gc_option); + if (collector_type != gc::kCollectorTypeNone) { + xgc.collector_type_ = collector_type; + } else if (gc_option == "preverify") { + xgc.verify_pre_gc_heap_ = true; + } else if (gc_option == "nopreverify") { + xgc.verify_pre_gc_heap_ = false; + } else if (gc_option == "presweepingverify") { + xgc.verify_pre_sweeping_heap_ = true; + } else if (gc_option == "nopresweepingverify") { + xgc.verify_pre_sweeping_heap_ = false; + } else if (gc_option == "postverify") { + xgc.verify_post_gc_heap_ = true; + } else if (gc_option == "nopostverify") { + xgc.verify_post_gc_heap_ = false; + } else if (gc_option == "preverify_rosalloc") { + xgc.verify_pre_gc_rosalloc_ = true; + } else if (gc_option == "nopreverify_rosalloc") { + xgc.verify_pre_gc_rosalloc_ = false; + } else if (gc_option == "presweepingverify_rosalloc") { + xgc.verify_pre_sweeping_rosalloc_ = true; + } else if (gc_option == "nopresweepingverify_rosalloc") { + xgc.verify_pre_sweeping_rosalloc_ = false; + } else if (gc_option == "postverify_rosalloc") { + xgc.verify_post_gc_rosalloc_ = true; + } else if (gc_option == "nopostverify_rosalloc") { + xgc.verify_post_gc_rosalloc_ = false; + } else if ((gc_option == "precise") || + (gc_option == "noprecise") || + (gc_option == "verifycardtable") || + (gc_option == "noverifycardtable")) { + // Ignored for backwards compatibility. + } else { + return Result::Usage(std::string("Unknown -Xgc option ") + gc_option); + } + } + + return Result::Success(std::move(xgc)); + } + + static const char* Name() { return "XgcOption"; } +}; + +struct BackgroundGcOption { + // If background_collector_type_ is kCollectorTypeNone, it defaults to the + // XGcOption::collector_type_ after parsing options. If you set this to + // kCollectorTypeHSpaceCompact then we will do an hspace compaction when + // we transition to background instead of a normal collector transition. + gc::CollectorType background_collector_type_; + + BackgroundGcOption(gc::CollectorType background_collector_type) // NOLINT [runtime/explicit] [5] + : background_collector_type_(background_collector_type) {} + BackgroundGcOption() + : background_collector_type_(gc::kCollectorTypeNone) { + + if (kUseReadBarrier) { + background_collector_type_ = gc::kCollectorTypeCC; // Disable background compaction for CC. + } + } + + operator gc::CollectorType() const { return background_collector_type_; } +}; + +template<> +struct CmdlineType + : CmdlineTypeParser, private BackgroundGcOption { + Result Parse(const std::string& substring) { + // Special handling for HSpaceCompact since this is only valid as a background GC type. + if (substring == "HSpaceCompact") { + background_collector_type_ = gc::kCollectorTypeHomogeneousSpaceCompact; + } else { + gc::CollectorType collector_type = ParseCollectorType(substring); + if (collector_type != gc::kCollectorTypeNone) { + background_collector_type_ = collector_type; + } else { + return Result::Failure(); + } + } + + BackgroundGcOption res = *this; + return Result::Success(res); + } + + static const char* Name() { return "BackgroundGcOption"; } +}; + +template <> +struct CmdlineType : CmdlineTypeParser { + Result Parse(const std::string& options) { + LogVerbosity log_verbosity = LogVerbosity(); + + std::vector verbose_options; + Split(options, ',', &verbose_options); + for (size_t j = 0; j < verbose_options.size(); ++j) { + if (verbose_options[j] == "class") { + log_verbosity.class_linker = true; + } else if (verbose_options[j] == "compiler") { + log_verbosity.compiler = true; + } else if (verbose_options[j] == "gc") { + log_verbosity.gc = true; + } else if (verbose_options[j] == "heap") { + log_verbosity.heap = true; + } else if (verbose_options[j] == "jdwp") { + log_verbosity.jdwp = true; + } else if (verbose_options[j] == "jni") { + log_verbosity.jni = true; + } else if (verbose_options[j] == "monitor") { + log_verbosity.monitor = true; + } else if (verbose_options[j] == "profiler") { + log_verbosity.profiler = true; + } else if (verbose_options[j] == "signals") { + log_verbosity.signals = true; + } else if (verbose_options[j] == "startup") { + log_verbosity.startup = true; + } else if (verbose_options[j] == "third-party-jni") { + log_verbosity.third_party_jni = true; + } else if (verbose_options[j] == "threads") { + log_verbosity.threads = true; + } else if (verbose_options[j] == "verifier") { + log_verbosity.verifier = true; + } else { + return Result::Usage(std::string("Unknown -verbose option ") + verbose_options[j]); + } + } + + return Result::Success(log_verbosity); + } + + static const char* Name() { return "LogVerbosity"; } +}; + +// TODO: Replace with art::ProfilerOptions for the real thing. +struct TestProfilerOptions { + // Whether or not the applications should be profiled. + bool enabled_; + // Destination file name where the profiling data will be saved into. + std::string output_file_name_; + // Generate profile every n seconds. + uint32_t period_s_; + // Run profile for n seconds. + uint32_t duration_s_; + // Microseconds between samples. + uint32_t interval_us_; + // Coefficient to exponential backoff. + double backoff_coefficient_; + // Whether the profile should start upon app startup or be delayed by some random offset. + bool start_immediately_; + // Top K% of samples that are considered relevant when deciding if the app should be recompiled. + double top_k_threshold_; + // How much the top K% samples needs to change in order for the app to be recompiled. + double top_k_change_threshold_; + // The type of profile data dumped to the disk. + ProfileDataType profile_type_; + // The max depth of the stack collected by the profiler + uint32_t max_stack_depth_; + + TestProfilerOptions() : + enabled_(false), + output_file_name_(), + period_s_(0), + duration_s_(0), + interval_us_(0), + backoff_coefficient_(0), + start_immediately_(0), + top_k_threshold_(0), + top_k_change_threshold_(0), + profile_type_(ProfileDataType::kProfilerMethod), + max_stack_depth_(0) { + } + + TestProfilerOptions(const TestProfilerOptions& other) = default; + TestProfilerOptions(TestProfilerOptions&& other) = default; +}; + +static inline std::ostream& operator<<(std::ostream& stream, const TestProfilerOptions& options) { + stream << "TestProfilerOptions {" << std::endl; + +#define PRINT_TO_STREAM(field) \ + stream << #field << ": '" << options.field << "'" << std::endl; + + PRINT_TO_STREAM(enabled_); + PRINT_TO_STREAM(output_file_name_); + PRINT_TO_STREAM(period_s_); + PRINT_TO_STREAM(duration_s_); + PRINT_TO_STREAM(interval_us_); + PRINT_TO_STREAM(backoff_coefficient_); + PRINT_TO_STREAM(start_immediately_); + PRINT_TO_STREAM(top_k_threshold_); + PRINT_TO_STREAM(top_k_change_threshold_); + PRINT_TO_STREAM(profile_type_); + PRINT_TO_STREAM(max_stack_depth_); + + stream << "}"; + + return stream; +#undef PRINT_TO_STREAM +} + +template <> +struct CmdlineType : CmdlineTypeParser { + using Result = CmdlineParseResult; + + private: + using StringResult = CmdlineParseResult; + using DoubleResult = CmdlineParseResult; + + template + static Result ParseInto(TestProfilerOptions& options, + T TestProfilerOptions::*pField, + CmdlineParseResult&& result) { + assert(pField != nullptr); + + if (result.IsSuccess()) { + options.*pField = result.ReleaseValue(); + return Result::SuccessNoValue(); + } + + return Result::CastError(result); + } + + template + static Result ParseIntoRangeCheck(TestProfilerOptions& options, + T TestProfilerOptions::*pField, + CmdlineParseResult&& result, + T min, + T max) { + if (result.IsSuccess()) { + const T& value = result.GetValue(); + + if (value < min || value > max) { + CmdlineParseResult out_of_range = CmdlineParseResult::OutOfRange(value, min, max); + return Result::CastError(out_of_range); + } + } + + return ParseInto(options, pField, std::forward>(result)); + } + + static StringResult ParseStringAfterChar(const std::string& s, char c) { + std::string parsed_value; + + std::string::size_type colon = s.find(c); + if (colon == std::string::npos) { + return StringResult::Usage(std::string() + "Missing char " + c + " in option " + s); + } + // Add one to remove the char we were trimming until. + parsed_value = s.substr(colon + 1); + return StringResult::Success(parsed_value); + } + + static std::string RemovePrefix(const std::string& source) { + size_t prefix_idx = source.find(":"); + + if (prefix_idx == std::string::npos) { + return ""; + } + + return source.substr(prefix_idx + 1); + } + + public: + Result ParseAndAppend(const std::string& option, TestProfilerOptions& existing) { + // Special case which doesn't include a wildcard argument definition. + // We pass-it through as-is. + if (option == "-Xenable-profiler") { + existing.enabled_ = true; + return Result::SuccessNoValue(); + } + + // The rest of these options are always the wildcard from '-Xprofile-*' + std::string suffix = RemovePrefix(option); + + if (StartsWith(option, "filename:")) { + CmdlineType type_parser; + + return ParseInto(existing, + &TestProfilerOptions::output_file_name_, + type_parser.Parse(suffix)); + } else if (StartsWith(option, "period:")) { + CmdlineType type_parser; + + return ParseInto(existing, + &TestProfilerOptions::period_s_, + type_parser.Parse(suffix)); + } else if (StartsWith(option, "duration:")) { + CmdlineType type_parser; + + return ParseInto(existing, + &TestProfilerOptions::duration_s_, + type_parser.Parse(suffix)); + } else if (StartsWith(option, "interval:")) { + CmdlineType type_parser; + + return ParseInto(existing, + &TestProfilerOptions::interval_us_, + type_parser.Parse(suffix)); + } else if (StartsWith(option, "backoff:")) { + CmdlineType type_parser; + + return ParseIntoRangeCheck(existing, + &TestProfilerOptions::backoff_coefficient_, + type_parser.Parse(suffix), + 1.0, + 10.0); + + } else if (option == "start-immediately") { + existing.start_immediately_ = true; + return Result::SuccessNoValue(); + } else if (StartsWith(option, "top-k-threshold:")) { + CmdlineType type_parser; + + return ParseIntoRangeCheck(existing, + &TestProfilerOptions::top_k_threshold_, + type_parser.Parse(suffix), + 0.0, + 100.0); + } else if (StartsWith(option, "top-k-change-threshold:")) { + CmdlineType type_parser; + + return ParseIntoRangeCheck(existing, + &TestProfilerOptions::top_k_change_threshold_, + type_parser.Parse(suffix), + 0.0, + 100.0); + } else if (option == "type:method") { + existing.profile_type_ = kProfilerMethod; + return Result::SuccessNoValue(); + } else if (option == "type:stack") { + existing.profile_type_ = kProfilerBoundedStack; + return Result::SuccessNoValue(); + } else if (StartsWith(option, "max-stack-depth:")) { + CmdlineType type_parser; + + return ParseInto(existing, + &TestProfilerOptions::max_stack_depth_, + type_parser.Parse(suffix)); + } else { + return Result::Failure(std::string("Invalid suboption '") + option + "'"); + } + } + + static const char* Name() { return "TestProfilerOptions"; } + static constexpr bool kCanParseBlankless = true; +}; + + +} // namespace art +#endif // ART_CMDLINE_CMDLINE_TYPES_H_ diff --git a/cmdline/detail/cmdline_debug_detail.h b/cmdline/detail/cmdline_debug_detail.h new file mode 100644 index 0000000..79028f4 --- /dev/null +++ b/cmdline/detail/cmdline_debug_detail.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_DETAIL_CMDLINE_DEBUG_DETAIL_H_ +#define ART_CMDLINE_DETAIL_CMDLINE_DEBUG_DETAIL_H_ + +#include +#ifndef CMDLINE_NDEBUG +#define CMDLINE_DEBUG_LOG std::cerr +#else +#define CMDLINE_DEBUG_LOG ::art::detail::debug_log_ignore() +#endif + +namespace art { + // Implementation details for some template querying. Don't look inside if you hate templates. + namespace detail { + struct debug_log_ignore { + // Ignore most of the normal operator<< usage. + template + debug_log_ignore& operator<<(const T&) { return *this; } + // Ignore std::endl and the like. + debug_log_ignore& operator<<(std::ostream& (*)(std::ostream&) ) { return *this; } + }; + } // namespace detail // NOLINT [readability/namespace] [5] +} // namespace art + +#endif // ART_CMDLINE_DETAIL_CMDLINE_DEBUG_DETAIL_H_ diff --git a/cmdline/detail/cmdline_parse_argument_detail.h b/cmdline/detail/cmdline_parse_argument_detail.h new file mode 100644 index 0000000..81ef36b --- /dev/null +++ b/cmdline/detail/cmdline_parse_argument_detail.h @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_DETAIL_CMDLINE_PARSE_ARGUMENT_DETAIL_H_ +#define ART_CMDLINE_DETAIL_CMDLINE_PARSE_ARGUMENT_DETAIL_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "cmdline/cmdline_parse_result.h" +#include "cmdline/token_range.h" +#include "cmdline/unit.h" +#include "cmdline/cmdline_types.h" + +namespace art { + // Implementation details for the parser. Do not look inside if you hate templates. + namespace detail { + // A non-templated base class for argument parsers. Used by the general parser + // to parse arguments, without needing to know the argument type at compile time. + // + // This is an application of the type erasure idiom. + struct CmdlineParseArgumentAny { + virtual ~CmdlineParseArgumentAny() {} + + // Attempt to parse this argument starting at arguments[position]. + // If the parsing succeeds, the parsed value will be saved as a side-effect. + // + // In most situations, the parsing will not match by returning kUnknown. In this case, + // no tokens were consumed and the position variable will not be updated. + // + // At other times, parsing may fail due to validation but the initial token was still matched + // (for example an out of range value, or passing in a string where an int was expected). + // In this case the tokens are still consumed, and the position variable will get incremented + // by all the consumed tokens. + // + // The # of tokens consumed by the parse attempt will be set as an out-parameter into + // consumed_tokens. The parser should skip this many tokens before parsing the next + // argument. + virtual CmdlineResult ParseArgument(const TokenRange& arguments, size_t* consumed_tokens) = 0; + // How many tokens should be taken off argv for parsing this argument. + // For example "--help" is just 1, "-compiler-option _" would be 2 (since there's a space). + // + // A [min,max] range is returned to represent argument definitions with multiple + // value tokens. (e.g. {"-h", "-h " } would return [1,2]). + virtual std::pair GetNumTokens() const = 0; + // Get the run-time typename of the argument type. + virtual const char* GetTypeName() const = 0; + // Try to do a close match, returning how many tokens were matched against this argument + // definition. More tokens is better. + // + // Do a quick match token-by-token, and see if they match. + // Any tokens with a wildcard in them are only matched up until the wildcard. + // If this is true, then the wildcard matching later on can still fail, so this is not + // a guarantee that the argument is correct, it's more of a strong hint that the + // user-provided input *probably* was trying to match this argument. + // + // Returns how many tokens were either matched (or ignored because there was a + // wildcard present). 0 means no match. If the Size() tokens are returned. + virtual size_t MaybeMatches(const TokenRange& tokens) = 0; + }; + + template + using EnableIfNumeric = std::enable_if::value>; + + template + using DisableIfNumeric = std::enable_if::value>; + + // Argument definition information, created by an ArgumentBuilder and an UntypedArgumentBuilder. + template + struct CmdlineParserArgumentInfo { + // This version will only be used if TArg is arithmetic and thus has the <= operators. + template // Necessary to get SFINAE to kick in. + bool CheckRange(const TArg& value, typename EnableIfNumeric::type* = 0) { + if (has_range_) { + return min_ <= value && value <= max_; + } + return true; + } + + // This version will be used at other times when TArg is not arithmetic. + template + bool CheckRange(const TArg&, typename DisableIfNumeric::type* = 0) { + assert(!has_range_); + return true; + } + + // Do a quick match token-by-token, and see if they match. + // Any tokens with a wildcard in them only match the prefix up until the wildcard. + // + // If this is true, then the wildcard matching later on can still fail, so this is not + // a guarantee that the argument is correct, it's more of a strong hint that the + // user-provided input *probably* was trying to match this argument. + size_t MaybeMatches(TokenRange token_list) const { + auto best_match = FindClosestMatch(token_list); + + return best_match.second; + } + + // Attempt to find the closest match (see MaybeMatches). + // + // Returns the token range that was the closest match and the # of tokens that + // this range was matched up until. + std::pair FindClosestMatch(TokenRange token_list) const { + const TokenRange* best_match_ptr = nullptr; + + size_t best_match = 0; + for (auto&& token_range : tokenized_names_) { + size_t this_match = token_range.MaybeMatches(token_list, std::string("_")); + + if (this_match > best_match) { + best_match_ptr = &token_range; + best_match = this_match; + } + } + + return std::make_pair(best_match_ptr, best_match); + } + + // Mark the argument definition as completed, do not mutate the object anymore after this + // call is done. + // + // Performs several sanity checks and token calculations. + void CompleteArgument() { + assert(names_.size() >= 1); + assert(!is_completed_); + + is_completed_ = true; + + size_t blank_count = 0; + size_t token_count = 0; + + size_t global_blank_count = 0; + size_t global_token_count = 0; + for (auto&& name : names_) { + std::string s(name); + + size_t local_blank_count = std::count(s.begin(), s.end(), '_'); + size_t local_token_count = std::count(s.begin(), s.end(), ' '); + + if (global_blank_count != 0) { + assert(local_blank_count == global_blank_count + && "Every argument descriptor string must have same amount of blanks (_)"); + } + + if (local_blank_count != 0) { + global_blank_count = local_blank_count; + blank_count++; + + assert(local_blank_count == 1 && "More than one blank is not supported"); + assert(s.back() == '_' && "The blank character must only be at the end of the string"); + } + + if (global_token_count != 0) { + assert(local_token_count == global_token_count + && "Every argument descriptor string must have same amount of tokens (spaces)"); + } + + if (local_token_count != 0) { + global_token_count = local_token_count; + token_count++; + } + + // Tokenize every name, turning it from a string to a token list. + tokenized_names_.clear(); + for (auto&& name1 : names_) { + // Split along ' ' only, removing any duplicated spaces. + tokenized_names_.push_back( + TokenRange::Split(name1, {' '}).RemoveToken(" ")); + } + + // remove the _ character from each of the token ranges + // we will often end up with an empty token (i.e. ["-XX", "_"] -> ["-XX", ""] + // and this is OK because we still need an empty token to simplify + // range comparisons + simple_names_.clear(); + + for (auto&& tokenized_name : tokenized_names_) { + simple_names_.push_back(tokenized_name.RemoveCharacter('_')); + } + } + + if (token_count != 0) { + assert(("Every argument descriptor string must have equal amount of tokens (spaces)" && + token_count == names_.size())); + } + + if (blank_count != 0) { + assert(("Every argument descriptor string must have an equal amount of blanks (_)" && + blank_count == names_.size())); + } + + using_blanks_ = blank_count > 0; + { + size_t smallest_name_token_range_size = + std::accumulate(tokenized_names_.begin(), tokenized_names_.end(), ~(0u), + [](size_t min, const TokenRange& cur) { + return std::min(min, cur.Size()); + }); + size_t largest_name_token_range_size = + std::accumulate(tokenized_names_.begin(), tokenized_names_.end(), 0u, + [](size_t max, const TokenRange& cur) { + return std::max(max, cur.Size()); + }); + + token_range_size_ = std::make_pair(smallest_name_token_range_size, + largest_name_token_range_size); + } + + if (has_value_list_) { + assert(names_.size() == value_list_.size() + && "Number of arg descriptors must match number of values"); + assert(!has_value_map_); + } + if (has_value_map_) { + if (!using_blanks_) { + assert(names_.size() == value_map_.size() && + "Since no blanks were specified, each arg is mapped directly into a mapped " + "value without parsing; sizes must match"); + } + + assert(!has_value_list_); + } + + if (!using_blanks_ && !CmdlineType::kCanParseBlankless) { + assert((has_value_map_ || has_value_list_) && + "Arguments without a blank (_) must provide either a value map or a value list"); + } + + TypedCheck(); + } + + // List of aliases for a single argument definition, e.g. {"-Xdex2oat", "-Xnodex2oat"}. + std::vector names_; + // Is there at least 1 wildcard '_' in the argument definition? + bool using_blanks_ = false; + // [min, max] token counts in each arg def + std::pair token_range_size_; + + // contains all the names in a tokenized form, i.e. as a space-delimited list + std::vector tokenized_names_; + + // contains the tokenized names, but with the _ character stripped + std::vector simple_names_; + + // For argument definitions created with '.AppendValues()' + // Meaning that parsing should mutate the existing value in-place if possible. + bool appending_values_ = false; + + // For argument definitions created with '.WithRange(min, max)' + bool has_range_ = false; + TArg min_; + TArg max_; + + // For argument definitions created with '.WithValueMap' + bool has_value_map_ = false; + std::vector> value_map_; + + // For argument definitions created with '.WithValues' + bool has_value_list_ = false; + std::vector value_list_; + + // Make sure there's a default constructor. + CmdlineParserArgumentInfo() = default; + + // Ensure there's a default move constructor. + CmdlineParserArgumentInfo(CmdlineParserArgumentInfo&&) = default; + + private: + // Perform type-specific checks at runtime. + template + void TypedCheck(typename std::enable_if::value>::type* = 0) { + assert(!using_blanks_ && + "Blanks are not supported in Unit arguments; since a Unit has no parse-able value"); + } + + void TypedCheck() {} + + bool is_completed_ = false; + }; + + // A virtual-implementation of the necessary argument information in order to + // be able to parse arguments. + template + struct CmdlineParseArgument : CmdlineParseArgumentAny { + explicit CmdlineParseArgument(CmdlineParserArgumentInfo&& argument_info, + std::function&& save_argument, + std::function&& load_argument) + : argument_info_(std::forward(argument_info)), + save_argument_(std::forward(save_argument)), + load_argument_(std::forward(load_argument)) { + } + + using UserTypeInfo = CmdlineType; + + virtual CmdlineResult ParseArgument(const TokenRange& arguments, size_t* consumed_tokens) { + assert(arguments.Size() > 0); + assert(consumed_tokens != nullptr); + + auto closest_match_res = argument_info_.FindClosestMatch(arguments); + size_t best_match_size = closest_match_res.second; + const TokenRange* best_match_arg_def = closest_match_res.first; + + if (best_match_size > arguments.Size()) { + // The best match has more tokens than were provided. + // Shouldn't happen in practice since the outer parser does this check. + return CmdlineResult(CmdlineResult::kUnknown, "Size mismatch"); + } + + assert(best_match_arg_def != nullptr); + *consumed_tokens = best_match_arg_def->Size(); + + if (!argument_info_.using_blanks_) { + return ParseArgumentSingle(arguments.Join(' ')); + } + + // Extract out the blank value from arguments + // e.g. for a def of "foo:_" and input "foo:bar", blank_value == "bar" + std::string blank_value = ""; + size_t idx = 0; + for (auto&& def_token : *best_match_arg_def) { + auto&& arg_token = arguments[idx]; + + // Does this definition-token have a wildcard in it? + if (def_token.find('_') == std::string::npos) { + // No, regular token. Match 1:1 against the argument token. + bool token_match = def_token == arg_token; + + if (!token_match) { + return CmdlineResult(CmdlineResult::kFailure, + std::string("Failed to parse ") + best_match_arg_def->GetToken(0) + + " at token " + std::to_string(idx)); + } + } else { + // This is a wild-carded token. + TokenRange def_split_wildcards = TokenRange::Split(def_token, {'_'}); + + // Extract the wildcard contents out of the user-provided arg_token. + std::unique_ptr arg_matches = + def_split_wildcards.MatchSubstrings(arg_token, "_"); + if (arg_matches == nullptr) { + return CmdlineResult(CmdlineResult::kFailure, + std::string("Failed to parse ") + best_match_arg_def->GetToken(0) + + ", with a wildcard pattern " + def_token + + " at token " + std::to_string(idx)); + } + + // Get the corresponding wildcard tokens from arg_matches, + // and concatenate it to blank_value. + for (size_t sub_idx = 0; + sub_idx < def_split_wildcards.Size() && sub_idx < arg_matches->Size(); ++sub_idx) { + if (def_split_wildcards[sub_idx] == "_") { + blank_value += arg_matches->GetToken(sub_idx); + } + } + } + + ++idx; + } + + return ParseArgumentSingle(blank_value); + } + + private: + virtual CmdlineResult ParseArgumentSingle(const std::string& argument) { + // TODO: refactor to use LookupValue for the value lists/maps + + // Handle the 'WithValueMap(...)' argument definition + if (argument_info_.has_value_map_) { + for (auto&& value_pair : argument_info_.value_map_) { + const char* name = value_pair.first; + + if (argument == name) { + return SaveArgument(value_pair.second); + } + } + + // Error case: Fail, telling the user what the allowed values were. + std::vector allowed_values; + for (auto&& value_pair : argument_info_.value_map_) { + const char* name = value_pair.first; + allowed_values.push_back(name); + } + + std::string allowed_values_flat = Join(allowed_values, ','); + return CmdlineResult(CmdlineResult::kFailure, + "Argument value '" + argument + "' does not match any of known valid" + "values: {" + allowed_values_flat + "}"); + } + + // Handle the 'WithValues(...)' argument definition + if (argument_info_.has_value_list_) { + size_t arg_def_idx = 0; + for (auto&& value : argument_info_.value_list_) { + auto&& arg_def_token = argument_info_.names_[arg_def_idx]; + + if (arg_def_token == argument) { + return SaveArgument(value); + } + ++arg_def_idx; + } + + assert(arg_def_idx + 1 == argument_info_.value_list_.size() && + "Number of named argument definitions must match number of values defined"); + + // Error case: Fail, telling the user what the allowed values were. + std::vector allowed_values; + for (auto&& arg_name : argument_info_.names_) { + allowed_values.push_back(arg_name); + } + + std::string allowed_values_flat = Join(allowed_values, ','); + return CmdlineResult(CmdlineResult::kFailure, + "Argument value '" + argument + "' does not match any of known valid" + "values: {" + allowed_values_flat + "}"); + } + + // Handle the regular case where we parsed an unknown value from a blank. + UserTypeInfo type_parser; + + if (argument_info_.appending_values_) { + TArg& existing = load_argument_(); + CmdlineParseResult result = type_parser.ParseAndAppend(argument, existing); + + assert(!argument_info_.has_range_); + + return result; + } + + CmdlineParseResult result = type_parser.Parse(argument); + + if (result.IsSuccess()) { + TArg& value = result.GetValue(); + + // Do a range check for 'WithRange(min,max)' argument definition. + if (!argument_info_.CheckRange(value)) { + return CmdlineParseResult::OutOfRange( + value, argument_info_.min_, argument_info_.max_); + } + + return SaveArgument(value); + } + + // Some kind of type-specific parse error. Pass the result as-is. + CmdlineResult raw_result = std::move(result); + return raw_result; + } + + public: + virtual const char* GetTypeName() const { + // TODO: Obviate the need for each type specialization to hardcode the type name + return UserTypeInfo::Name(); + } + + // How many tokens should be taken off argv for parsing this argument. + // For example "--help" is just 1, "-compiler-option _" would be 2 (since there's a space). + // + // A [min,max] range is returned to represent argument definitions with multiple + // value tokens. (e.g. {"-h", "-h " } would return [1,2]). + virtual std::pair GetNumTokens() const { + return argument_info_.token_range_size_; + } + + // See if this token range might begin the same as the argument definition. + virtual size_t MaybeMatches(const TokenRange& tokens) { + return argument_info_.MaybeMatches(tokens); + } + + private: + CmdlineResult SaveArgument(const TArg& value) { + assert(!argument_info_.appending_values_ + && "If the values are being appended, then the updated parse value is " + "updated by-ref as a side effect and shouldn't be stored directly"); + TArg val = value; + save_argument_(val); + return CmdlineResult(CmdlineResult::kSuccess); + } + + CmdlineParserArgumentInfo argument_info_; + std::function save_argument_; + std::function load_argument_; + }; + } // namespace detail // NOLINT [readability/namespace] [5] [whitespace/comments] [2] +} // namespace art + +#endif // ART_CMDLINE_DETAIL_CMDLINE_PARSE_ARGUMENT_DETAIL_H_ diff --git a/cmdline/detail/cmdline_parser_detail.h b/cmdline/detail/cmdline_parser_detail.h new file mode 100644 index 0000000..9b43bb0 --- /dev/null +++ b/cmdline/detail/cmdline_parser_detail.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_DETAIL_CMDLINE_PARSER_DETAIL_H_ +#define ART_CMDLINE_DETAIL_CMDLINE_PARSER_DETAIL_H_ + +#include +#include +#include + +namespace art { + // Implementation details for some template querying. Don't look inside if you hate templates. + namespace detail { + template + typename std::remove_reference::type& FakeReference(); + + // SupportsInsertionOperator::value will evaluate to a boolean, + // whose value is true if the TStream class supports the << operator against T, + // and false otherwise. + template + struct SupportsInsertionOperator { + private: + template + static std::true_type InsertionOperatorTest(TStream& os, const T& value, + std::remove_reference* = 0); // NOLINT [whitespace/operators] [3] + + template + static std::false_type InsertionOperatorTest(TStream& os, const T& ... args); + + public: + static constexpr bool value = + decltype(InsertionOperatorTest(FakeReference(), std::declval()))::value; + }; + + template + struct SupportsEqualityOperatorImpl; + + template + struct SupportsEqualityOperatorImpl { + private: + template + static std::true_type EqualityOperatorTest(const TL& left, const TR& right, + std::remove_reference* = 0); // NOLINT [whitespace/operators] [3] + + template + static std::false_type EqualityOperatorTest(const TL& left, const T& ... args); + + public: + static constexpr bool value = + decltype(EqualityOperatorTest(std::declval(), std::declval()))::value; + }; + + // Partial specialization when TLeft/TRight are both floating points. + // This is a work-around because decltype(floatvar1 == floatvar2) + // will not compile with clang: + // error: comparing floating point with == or != is unsafe [-Werror,-Wfloat-equal] + template + struct SupportsEqualityOperatorImpl { + static constexpr bool value = true; + }; + + // SupportsEqualityOperatorImpl::value will evaluate to a boolean, + // whose value is true if T1 can be compared against T2 with ==, + // and false otherwise. + template + struct SupportsEqualityOperator : + SupportsEqualityOperatorImpl::value + && std::is_floating_point::value> { + }; + + // Convert any kind of type to an std::string, even if there's no + // serialization support for it. Unknown types get converted to an + // an arbitrary value. + // + // Meant for printing user-visible errors or unit test failures only. + template + std::string ToStringAny(const T& value, + typename std::enable_if< + SupportsInsertionOperator::value>::type* = 0) { + std::stringstream stream; + stream << value; + return stream.str(); + } + + template + std::string ToStringAny(const std::vector value, + typename std::enable_if< + SupportsInsertionOperator::value>::type* = 0) { + std::stringstream stream; + stream << "vector{"; + + for (size_t i = 0; i < value.size(); ++i) { + stream << ToStringAny(value[i]); + + if (i != value.size() - 1) { + stream << ','; + } + } + + stream << "}"; + return stream.str(); + } + + template + std::string ToStringAny(const T&, + typename std::enable_if< + !SupportsInsertionOperator::value>::type* = 0 + ) { + return std::string("(unknown type [no operator<< implemented] for )"); + } + } // namespace detail // NOLINT [readability/namespace] [5] +} // namespace art + +#endif // ART_CMDLINE_DETAIL_CMDLINE_PARSER_DETAIL_H_ diff --git a/cmdline/memory_representation.h b/cmdline/memory_representation.h new file mode 100644 index 0000000..93387de --- /dev/null +++ b/cmdline/memory_representation.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_MEMORY_REPRESENTATION_H_ +#define ART_CMDLINE_MEMORY_REPRESENTATION_H_ + +#include +#include +#include +#include "utils.h" + +namespace art { + +// An integral representation of bytes of memory. +// The underlying runtime size_t value is guaranteed to be a multiple of Divisor. +template +struct Memory { + static_assert(IsPowerOfTwo(Divisor), "Divisor must be a power of 2"); + + static Memory FromBytes(size_t bytes) { + assert(bytes % Divisor == 0); + return Memory(bytes); + } + + Memory() : Value(0u) {} + Memory(size_t value) : Value(value) { // NOLINT [runtime/explicit] [5] + assert(value % Divisor == 0); + } + operator size_t() const { return Value; } + + size_t ToBytes() const { + return Value; + } + + static constexpr size_t kDivisor = Divisor; + + static const char* Name() { + static std::string str; + if (str.empty()) { + str = "Memory<" + std::to_string(Divisor) + '>'; + } + + return str.c_str(); + } + + size_t Value; +}; + +template +std::ostream& operator<<(std::ostream& stream, Memory memory) { + return stream << memory.Value << '*' << Divisor; +} + +using MemoryKiB = Memory<1024>; + +} // namespace art + +#endif // ART_CMDLINE_MEMORY_REPRESENTATION_H_ diff --git a/cmdline/token_range.h b/cmdline/token_range.h new file mode 100644 index 0000000..50c54fe --- /dev/null +++ b/cmdline/token_range.h @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_TOKEN_RANGE_H_ +#define ART_CMDLINE_TOKEN_RANGE_H_ + +#include +#include +#include +#include +#include + +namespace art { +// A range of tokens to make token matching algorithms easier. +// +// We try really hard to avoid copying and store only a pointer and iterators to the +// interiors of the vector, so a typical copy constructor never ends up doing a deep copy. +// It is up to the user to play nice and not to mutate the strings in-place. +// +// Tokens are only copied if a mutating operation is performed (and even then only +// if it *actually* mutates the token). +struct TokenRange { + // Short-hand for a vector of strings. A single string and a token is synonymous. + using TokenList = std::vector; + + // Copying-from-vector constructor. + explicit TokenRange(const TokenList& token_list) + : token_list_(new TokenList(token_list)), + begin_(token_list_->begin()), + end_(token_list_->end()) + {} + + // Copying-from-iterator constructor + template + explicit TokenRange(ForwardIterator it_begin, ForwardIterator it_end) + : token_list_(new TokenList(it_begin, it_end)), + begin_(token_list_->begin()), + end_(token_list_->end()) + {} + +#if 0 + // Copying-from-vector constructor. + TokenRange(const TokenList& token_list ATTRIBUTE_UNUSED, + TokenList::const_iterator it_begin, + TokenList::const_iterator it_end) + : token_list_(new TokenList(it_begin, it_end)), + begin_(token_list_->begin()), + end_(token_list_->end()) { + assert(it_begin >= token_list.begin()); + assert(it_end <= token_list.end()); + } +#endif + + // Copying from char array constructor, convertings into tokens (strings) along the way. + TokenRange(const char* token_list[], size_t length) + : token_list_(new TokenList(&token_list[0], &token_list[length])), + begin_(token_list_->begin()), + end_(token_list_->end()) + {} + + // Non-copying move-from-vector constructor. Takes over the token vector. + explicit TokenRange(TokenList&& token_list) + : token_list_(new TokenList(std::forward(token_list))), + begin_(token_list_->begin()), + end_(token_list_->end()) + {} + + // Non-copying constructor. Retain reference to existing list of tokens. + TokenRange(std::shared_ptr token_list, + TokenList::const_iterator it_begin, + TokenList::const_iterator it_end) + : token_list_(token_list), + begin_(it_begin), + end_(it_end) { + assert(it_begin >= token_list->begin()); + assert(it_end <= token_list->end()); + } + + // Non-copying copy constructor. + TokenRange(const TokenRange& other) = default; + + // Non-copying move constructor. + TokenRange(TokenRange&& other) = default; + + // Non-copying constructor. Retains reference to an existing list of tokens, with offset. + explicit TokenRange(std::shared_ptr token_list) + : token_list_(token_list), + begin_(token_list_->begin()), + end_(token_list_->end()) + {} + + // Iterator type for begin() and end(). Guaranteed to be a RandomAccessIterator. + using iterator = TokenList::const_iterator; + + // Iterator type for const begin() and const end(). Guaranteed to be a RandomAccessIterator. + using const_iterator = iterator; + + // Create a token range by splitting a string. Each separator gets their own token. + // Since the separator are retained as tokens, it might be useful to call + // RemoveToken afterwards. + static TokenRange Split(const std::string& string, std::initializer_list separators) { + TokenList new_token_list; + + std::string tok; + for (auto&& c : string) { + for (char sep : separators) { + if (c == sep) { + // We spotted a separator character. + // Push back everything before the last separator as a new token. + // Push back the separator as a token. + if (!tok.empty()) { + new_token_list.push_back(tok); + tok = ""; + } + new_token_list.push_back(std::string() + sep); + } else { + // Build up the token with another character. + tok += c; + } + } + } + + if (!tok.empty()) { + new_token_list.push_back(tok); + } + + return TokenRange(std::move(new_token_list)); + } + + // A RandomAccessIterator to the first element in this range. + iterator begin() const { + return begin_; + } + + // A RandomAccessIterator to one past the last element in this range. + iterator end() const { + return end_; + } + + // The size of the range, i.e. how many tokens are in it. + size_t Size() const { + return std::distance(begin_, end_); + } + + // Are there 0 tokens in this range? + bool IsEmpty() const { + return Size() > 0; + } + + // Look up a token by it's offset. + const std::string& GetToken(size_t offset) const { + assert(offset < Size()); + return *(begin_ + offset); + } + + // Does this token range equal the other range? + // Equality is defined as having both the same size, and + // each corresponding token being equal. + bool operator==(const TokenRange& other) const { + if (this == &other) { + return true; + } + + if (Size() != other.Size()) { + return false; + } + + return std::equal(begin(), end(), other.begin()); + } + + // Look up the token at the requested index. + const std::string& operator[](int index) const { + assert(index >= 0 && static_cast(index) < Size()); + return *(begin() + index); + } + + // Does this current range start with the other range? + bool StartsWith(const TokenRange& other) const { + if (this == &other) { + return true; + } + + if (Size() < other.Size()) { + return false; + } + + auto& smaller = Size() < other.Size() ? *this : other; + auto& greater = Size() < other.Size() ? other : *this; + + return std::equal(smaller.begin(), smaller.end(), greater.begin()); + } + + // Remove all characters 'c' from each token, potentially copying the underlying tokens. + TokenRange RemoveCharacter(char c) const { + TokenList new_token_list(begin(), end()); + + bool changed = false; + for (auto&& token : new_token_list) { + auto it = std::remove_if(token.begin(), token.end(), [&](char ch) { + if (ch == c) { + changed = true; + return true; + } + return false; + }); + token.erase(it, token.end()); + } + + if (!changed) { + return *this; + } + + return TokenRange(std::move(new_token_list)); + } + + // Remove all tokens matching this one, potentially copying the underlying tokens. + TokenRange RemoveToken(const std::string& token) { + return RemoveIf([&](const std::string& tok) { return tok == token; }); + } + + // Discard all empty tokens, potentially copying the underlying tokens. + TokenRange DiscardEmpty() const { + return RemoveIf([](const std::string& token) { return token.empty(); }); + } + + // Create a non-copying subset of this range. + // Length is trimmed so that the Slice does not go out of range. + TokenRange Slice(size_t offset, size_t length = std::string::npos) const { + assert(offset < Size()); + + if (length != std::string::npos && offset + length > Size()) { + length = Size() - offset; + } + + iterator it_end; + if (length == std::string::npos) { + it_end = end(); + } else { + it_end = begin() + offset + length; + } + + return TokenRange(token_list_, begin() + offset, it_end); + } + + // Try to match the string with tokens from this range. + // Each token is used to match exactly once (after which the next token is used, and so on). + // The matching happens from left-to-right in a non-greedy fashion. + // If the currently-matched token is the wildcard, then the new outputted token will + // contain as much as possible until the next token is matched. + // + // For example, if this == ["a:", "_", "b:] and "_" is the match string, then + // MatchSubstrings on "a:foob:" will yield: ["a:", "foo", "b:"] + // + // Since the string matching can fail (e.g. ["foo"] against "bar"), then this + // function can fail, in which cause it will return null. + std::unique_ptr MatchSubstrings(const std::string& string, + const std::string& wildcard) const { + TokenList new_token_list; + + size_t wildcard_idx = std::string::npos; + size_t string_idx = 0; + + // Function to push all the characters matched as a wildcard so far + // as a brand new token. It resets the wildcard matching. + // Empty wildcards are possible and ok, but only if wildcard matching was on. + auto maybe_push_wildcard_token = [&]() { + if (wildcard_idx != std::string::npos) { + size_t wildcard_length = string_idx - wildcard_idx; + std::string wildcard_substr = string.substr(wildcard_idx, wildcard_length); + new_token_list.push_back(std::move(wildcard_substr)); + + wildcard_idx = std::string::npos; + } + }; + + for (iterator it = begin(); it != end(); ++it) { + const std::string& tok = *it; + + if (tok == wildcard) { + maybe_push_wildcard_token(); + wildcard_idx = string_idx; + continue; + } + + size_t next_token_idx = string.find(tok); + if (next_token_idx == std::string::npos) { + // Could not find token at all + return nullptr; + } else if (next_token_idx != string_idx && wildcard_idx == std::string::npos) { + // Found the token at a non-starting location, and we weren't + // trying to parse the wildcard. + return nullptr; + } + + new_token_list.push_back(string.substr(next_token_idx, tok.size())); + maybe_push_wildcard_token(); + string_idx += tok.size(); + } + + size_t remaining = string.size() - string_idx; + if (remaining > 0) { + if (wildcard_idx == std::string::npos) { + // Some characters were still remaining in the string, + // but it wasn't trying to match a wildcard. + return nullptr; + } + } + + // If some characters are remaining, the rest must be a wildcard. + string_idx += remaining; + maybe_push_wildcard_token(); + + return std::unique_ptr(new TokenRange(std::move(new_token_list))); + } + + // Do a quick match token-by-token, and see if they match. + // Any tokens with a wildcard in them are only matched up until the wildcard. + // If this is true, then the wildcard matching later on can still fail, so this is not + // a guarantee that the argument is correct, it's more of a strong hint that the + // user-provided input *probably* was trying to match this argument. + // + // Returns how many tokens were either matched (or ignored because there was a + // wildcard present). 0 means no match. If the size() tokens are returned. + size_t MaybeMatches(const TokenRange& token_list, const std::string& wildcard) const { + auto token_it = token_list.begin(); + auto token_end = token_list.end(); + auto name_it = begin(); + auto name_end = end(); + + size_t matched_tokens = 0; + + while (token_it != token_end && name_it != name_end) { + // Skip token matching when the corresponding name has a wildcard in it. + const std::string& name = *name_it; + + size_t wildcard_idx = name.find(wildcard); + if (wildcard_idx == std::string::npos) { // No wildcard present + // Did the definition token match the user token? + if (name != *token_it) { + return matched_tokens; + } + } else { + std::string name_prefix = name.substr(0, wildcard_idx); + + // Did the user token start with the up-to-the-wildcard prefix? + if (!StartsWith(*token_it, name_prefix)) { + return matched_tokens; + } + } + + ++token_it; + ++name_it; + ++matched_tokens; + } + + // If we got this far, it's either a full match or the token list was too short. + return matched_tokens; + } + + // Flatten the token range by joining every adjacent token with the separator character. + // e.g. ["hello", "world"].join('$') == "hello$world" + std::string Join(char separator) const { + TokenList tmp(begin(), end()); + return art::Join(tmp, separator); + // TODO: Join should probably take an offset or iterators + } + + private: + static bool StartsWith(const std::string& larger, const std::string& smaller) { + if (larger.size() >= smaller.size()) { + return std::equal(smaller.begin(), smaller.end(), larger.begin()); + } + + return false; + } + + template + TokenRange RemoveIf(const TPredicate& predicate) const { + // If any of the tokens in the token lists are empty, then + // we need to remove them and compress the token list into a smaller one. + bool remove = false; + for (auto it = begin_; it != end_; ++it) { + auto&& token = *it; + + if (predicate(token)) { + remove = true; + break; + } + } + + // Actually copy the token list and remove the tokens that don't match our predicate. + if (remove) { + auto token_list = std::make_shared(begin(), end()); + TokenList::iterator new_end = + std::remove_if(token_list->begin(), token_list->end(), predicate); + token_list->erase(new_end, token_list->end()); + + assert(token_list_->size() > token_list->size() && "Nothing was actually removed!"); + + return TokenRange(token_list); + } + + return *this; + } + + const std::shared_ptr> token_list_; + const iterator begin_; + const iterator end_; +}; +} // namespace art + +#endif // ART_CMDLINE_TOKEN_RANGE_H_ diff --git a/cmdline/unit.h b/cmdline/unit.h new file mode 100644 index 0000000..6b53b18 --- /dev/null +++ b/cmdline/unit.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_CMDLINE_UNIT_H_ +#define ART_CMDLINE_UNIT_H_ + +namespace art { + +// Used for arguments that simply indicate presence (e.g. "-help") without any values. +struct Unit { + // Avoid 'Conditional jump or move depends on uninitialised value(s)' errors + // when running valgrind by specifying a user-defined constructor. + Unit() {} + ~Unit() {} + bool operator==(Unit) const { + return true; + } +}; + +} // namespace art + +#endif // ART_CMDLINE_UNIT_H_ -- cgit v1.1