summaryrefslogtreecommitdiffstats
path: root/cmdline
diff options
context:
space:
mode:
authorIgor Murashkin <iam@google.com>2015-01-26 10:55:53 -0800
committerIgor Murashkin <iam@google.com>2015-02-04 13:29:19 -0800
commitaaebaa0121be3b9d9f13630585304482cbcaeb4b (patch)
tree0f47257e497fdf920c8d703d2d00adab53934a76 /cmdline
parentbabecc483276b46d84cb83d4f01e577228827305 (diff)
downloadart-aaebaa0121be3b9d9f13630585304482cbcaeb4b.zip
art-aaebaa0121be3b9d9f13630585304482cbcaeb4b.tar.gz
art-aaebaa0121be3b9d9f13630585304482cbcaeb4b.tar.bz2
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
Diffstat (limited to 'cmdline')
-rw-r--r--cmdline/README.md245
-rw-r--r--cmdline/cmdline_parse_result.h138
-rw-r--r--cmdline/cmdline_parser.h635
-rw-r--r--cmdline/cmdline_parser_test.cc534
-rw-r--r--cmdline/cmdline_result.h103
-rw-r--r--cmdline/cmdline_type_parser.h76
-rw-r--r--cmdline/cmdline_types.h820
-rw-r--r--cmdline/detail/cmdline_debug_detail.h40
-rw-r--r--cmdline/detail/cmdline_parse_argument_detail.h503
-rw-r--r--cmdline/detail/cmdline_parser_detail.h128
-rw-r--r--cmdline/memory_representation.h71
-rw-r--r--cmdline/token_range.h425
-rw-r--r--cmdline/unit.h35
13 files changed, 3753 insertions, 0 deletions
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<int> Apple;
+ static const Key<double> Orange;
+ static const Key<bool> 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<FruitVariantMap, FruitVariantMap::Key>;
+
+FruitParser MakeParser() {
+ auto&& builder = FruitParser::Builder();
+ builder.
+ .Define("--help")
+ .IntoKey(FruitVariantMap::Help)
+ Define("--apple:_")
+ .WithType<int>()
+ .IntoKey(FruitVariantMap::Apple)
+ .Define("--orange:_")
+ .WithType<double>()
+ .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<T>()` 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<const char* /*arg name*/, T /*value*/>`.
+
+#### 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<std::vector<std::string>)()
+ .AppendValues()
+```
+Will parse something like `-Dhello -Dworld -Dbar -Dbaz` into `std::vector<std::string>{"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<std::string>` and are meant to be used with `AppendValues` in which case the
+`ParseAndAppend` function should be implemented.
+
+For example:
+```
+template <>
+struct CmdlineType<double> : CmdlineTypeParser<double> {
+ 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<std::vector<std::string>> : CmdlineTypeParser<std::vector<std::string>> {
+ Result ParseAndAppend(const std::string& args,
+ std::vector<std::string>& existing_value) {
+ existing_value.push_back(args);
+ return Result::SuccessNoValue();
+ }
+ static const char* Name() { return "std::vector<std::string>"; }
+};
+```
+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<T>` and it acts similar to a poor version of
+`Either<Left, Right>` in Haskell. In particular, it would be similar to `Either< int ErrorCode,
+Maybe<T> >`.
+
+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<int ErrorCode, Unit>` 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 <typename T>
+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<T> Failure() {
+ return CmdlineParseResult(kFailure);
+ }
+
+ // Create an error result with the failure error code and no message.
+ static CmdlineParseResult<T> Failure(const std::string& message) {
+ return CmdlineParseResult(kFailure, message);
+ }
+
+ // Create a successful result which holds the specified value.
+ static CmdlineParseResult<T> Success(const T& value) {
+ return CmdlineParseResult(value);
+ }
+
+ // Create a successful result, taking over the value.
+ static CmdlineParseResult<T> Success(T&& value) {
+ return CmdlineParseResult(std::forward<T>(value));
+ }
+
+ // Create succesful result, without any values. Used when a value was successfully appended
+ // into an existing object.
+ static CmdlineParseResult<T> SuccessNoValue() {
+ return CmdlineParseResult(T {});
+ }
+
+ // Create an error result with the OutOfRange error and the specified message.
+ static CmdlineParseResult<T> 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<T> 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 <typename T2>
+ static CmdlineParseResult<T> CastError(const CmdlineParseResult<T2>& other) {
+ assert(other.IsError());
+ return CmdlineParseResult<T>(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<T>(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 <vector>
+#include <memory>
+
+namespace art {
+// Build a parser for command line arguments with a small domain specific language.
+// Each parsed type must have a specialized CmdlineType<T> in order to do the string->T parsing.
+// Each argument must also have a VariantMap::Key<T> in order to do the T storage.
+template <typename TVariantMap,
+ template <typename TKeyValue> class TVariantMapKey>
+struct CmdlineParser {
+ template <typename TArg>
+ 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 <typename TArg>
+ static ArgumentBuilder<TArg> 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 <typename TArg>
+ void SaveToMap(const TVariantMapKey<TArg>& 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 <typename TArg>
+ TArg& GetOrCreateFromMap(const TVariantMapKey<TArg>& 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<TVariantMap> 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 <typename TArg>
+ struct ArgumentBuilder {
+ // Add a range check to this argument.
+ ArgumentBuilder<TArg>& 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<TArg>& WithValues(std::initializer_list<TArg> 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<TArg> 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<TArg>& WithValueMap(
+ std::initializer_list<std::pair<const char*, TArg>> 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<TArg>& AppendValues() {
+ argument_info_.appending_values_ = true;
+
+ return *this;
+ }
+
+ // Convenience type alias for the variant map key type definition.
+ using MapKey = TVariantMapKey<TArg>;
+
+ // 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<TArg*>(0); // Blow up.
+ };
+
+ save_value_specified_ = true;
+ load_value_specified_ = true;
+
+ CompleteArgument();
+ return parent_;
+ }
+
+ void SetValuesInternal(const std::vector<TArg>&& value_list) {
+ assert(!argument_info_.has_value_map_);
+
+ argument_info_.has_value_list_ = true;
+ argument_info_.value_list_ = value_list;
+ }
+
+ void SetNames(std::vector<const char*>&& names) {
+ argument_info_.names_ = names;
+ }
+
+ void SetNames(std::initializer_list<const char*> 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<TArg>(
+ 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<SaveDestination> 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<TArg*>(0); // Blow up.
+ };
+ }
+
+ CmdlineParser::Builder& parent_;
+ std::function<void(TArg&)> save_value_;
+ std::function<TArg&(void)> load_value_;
+ bool save_value_specified_;
+ bool load_value_specified_;
+ detail::CmdlineParserArgumentInfo<TArg> argument_info_;
+
+ std::shared_ptr<SaveDestination> save_destination_;
+ };
+
+ struct UntypedArgumentBuilder {
+ // Set a type for this argument. The specific subcommand parser is looked up by the type.
+ template <typename TArg>
+ ArgumentBuilder<TArg> WithType() {
+ return CreateTypedBuilder<TArg>();
+ }
+
+ // When used with multiple aliases, map the position of the alias to the value position.
+ template <typename TArg>
+ ArgumentBuilder<TArg> WithValues(std::initializer_list<TArg> values) {
+ auto&& a = CreateTypedBuilder<TArg>();
+ 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 <typename TArg>
+ ArgumentBuilder<TArg> 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<Unit>& key) {
+ return CreateTypedBuilder<Unit>().IntoKey(key);
+ }
+
+ // Ensure we always move this when returning a new builder.
+ UntypedArgumentBuilder(UntypedArgumentBuilder&&) = default;
+
+ protected:
+ void SetNames(std::vector<const char*>&& names) {
+ names_ = std::move(names);
+ }
+
+ void SetNames(std::initializer_list<const char*> names) {
+ names_ = names;
+ }
+
+ private:
+ // No copying. Move instead.
+ UntypedArgumentBuilder(const UntypedArgumentBuilder&) = delete;
+
+ template <typename TArg>
+ ArgumentBuilder<TArg> CreateTypedBuilder() {
+ auto&& b = CreateArgumentBuilder<TArg>(parent_);
+ InitializeTypedBuilder(&b); // Type-specific initialization
+ b.SetNames(std::move(names_));
+ return std::move(b);
+ }
+
+ template <typename TArg = Unit>
+ typename std::enable_if<std::is_same<TArg, Unit>::value>::type
+ InitializeTypedBuilder(ArgumentBuilder<TArg>* arg_builder) {
+ // Every Unit argument implicitly maps to a runtime value of Unit{}
+ std::vector<Unit> 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 <typename TArg>
+ friend struct ArgumentBuilder;
+ friend struct Builder;
+
+ explicit UntypedArgumentBuilder(CmdlineParser::Builder& parent) : parent_(parent) {}
+ // UntypedArgumentBuilder(UntypedArgumentBuilder&& other) = default;
+
+ CmdlineParser::Builder& parent_;
+ std::vector<const char*> 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<const char*> 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<const char*> 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:<anythinggoeshere>
+ auto&& builder = Define(ignore_name).template WithType<std::string>().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<Unit>().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<detail::CmdlineParseArgumentAny>(arg);
+ completed_arguments_.push_back(std::move(smart_ptr));
+ }
+
+ private:
+ // No copying now!
+ Builder(const Builder& other) = delete;
+
+ template <typename TArg>
+ friend struct ArgumentBuilder;
+ friend struct UntypedArgumentBuilder;
+ friend struct CmdlineParser;
+
+ bool built_ = false;
+ bool ignore_unrecognized_ = false;
+ std::vector<const char*> ignore_list_;
+ std::shared_ptr<SaveDestination> save_destination_;
+
+ std::vector<std::unique_ptr<detail::CmdlineParseArgumentAny>> completed_arguments_;
+ };
+
+ CmdlineResult Parse(const std::string& argv) {
+ std::vector<std::string> 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<const char*>& 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<std::string>& 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<const char*>&& ignore_list,
+ std::shared_ptr<SaveDestination> save_destination,
+ std::vector<std::unique_ptr<detail::CmdlineParseArgumentAny>>&&
+ 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<size_t, size_t> 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<const char*> ignore_list_;
+ std::shared_ptr<SaveDestination> save_destination_;
+ std::vector<std::unique_ptr<detail::CmdlineParseArgumentAny>> completed_arguments_;
+};
+
+// This has to be defined after everything else, since we want the builders to call this.
+template <typename TVariantMap,
+ template <typename TKeyValue> class TVariantMapKey>
+template <typename TArg>
+CmdlineParser<TVariantMap, TVariantMapKey>::ArgumentBuilder<TArg>
+CmdlineParser<TVariantMap, TVariantMapKey>::CreateArgumentBuilder(
+ CmdlineParser<TVariantMap, TVariantMapKey>::Builder& parent) {
+ return CmdlineParser<TVariantMap, TVariantMapKey>::ArgumentBuilder<TArg>(
+ parent, parent.save_destination_);
+}
+
+// This has to be defined after everything else, since we want the builders to call this.
+template <typename TVariantMap,
+ template <typename TKeyValue> class TVariantMapKey>
+void CmdlineParser<TVariantMap, TVariantMapKey>::AppendCompletedArgument(
+ CmdlineParser<TVariantMap, TVariantMapKey>::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 <numeric>
+#include "gtest/gtest.h"
+
+#define EXPECT_NULL(expected) EXPECT_EQ(reinterpret_cast<const void*>(expected), \
+ reinterpret_cast<void*>(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<double>;
+
+ FloatingPoint exp(expected);
+ FloatingPoint act(actual);
+
+ // Compare with ULPs instead of comparing with ==
+ return exp.AlmostEquals(act);
+ }
+
+ template <typename T>
+ bool UsuallyEquals(const T& expected, const T& actual,
+ typename std::enable_if<
+ detail::SupportsEqualityOperator<T>::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 <typename T, typename ... Ignore>
+ bool UsuallyEquals(const T& expected, const T& actual,
+ const Ignore& ... more ATTRIBUTE_UNUSED,
+ typename std::enable_if<std::is_pod<T>::value>::type* = 0,
+ typename std::enable_if<!detail::SupportsEqualityOperator<T>::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 <typename TMap, typename TKey, typename T>
+ ::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<RuntimeParser> 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<std::string> opt = {"hello"};
+
+ EXPECT_SINGLE_PARSE_VALUE(opt, "-Dhello", M::PropertiesList);
+ }
+
+ {
+ std::vector<std::string> opt = {"hello", "world"};
+
+ EXPECT_SINGLE_PARSE_VALUE(opt, "-Dhello -Dworld", M::PropertiesList);
+ }
+
+ {
+ std::vector<std::string> 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<std::string> opt = {"hello"};
+ EXPECT_SINGLE_PARSE_VALUE(opt, "-Xcompiler-option hello", M::CompilerOptions);
+ }
+
+ {
+ std::vector<std::string> opt = {"hello", "world"};
+ EXPECT_SINGLE_PARSE_VALUE(opt,
+ "-Xcompiler-option hello -Xcompiler-option world",
+ M::CompilerOptions);
+ }
+
+ {
+ std::vector<std::string> 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<const char*> 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<const char*> 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 <assert.h>
+#include <utils.h>
+
+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<T> 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<T> specializations.
+//
+// Not strictly necessary, but if the specializations fail to Define all of these functions
+// the compilation will fail.
+template <typename T>
+struct CmdlineTypeParser {
+ // Return value of parsing attempts. Represents a Success(T value) or an Error(int code)
+ using Result = CmdlineParseResult<T>;
+
+ // 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 <string>
+#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<T>
+// and implements either Parse or ParseAndAppend
+// (only if the argument was defined with ::AppendValues()) but not both.
+template <typename T>
+struct CmdlineType : CmdlineTypeParser<T> {
+};
+
+// Specializations for CmdlineType<T> follow:
+
+// Parse argument definitions for Unit-typed arguments.
+template <>
+struct CmdlineType<Unit> : CmdlineTypeParser<Unit> {
+ 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<JDWP::JdwpOptions> : CmdlineTypeParser<JDWP::JdwpOptions> {
+ 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<std::string> 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 <port> or <host>:<port> */
+ 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 <size_t Divisor>
+struct CmdlineType<Memory<Divisor>> : CmdlineTypeParser<Memory<Divisor>> {
+ using typename CmdlineTypeParser<Memory<Divisor>>::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<Divisor>(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<size_t>::max() / mul) {
+ val *= mul;
+ } else {
+ // Clamp to a multiple of 1024.
+ val = std::numeric_limits<size_t>::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<Divisor>::Name(); }
+};
+
+template <>
+struct CmdlineType<double> : CmdlineTypeParser<double> {
+ 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<unsigned int> : CmdlineTypeParser<unsigned int> {
+ 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<int>::min()
+ || result > std::numeric_limits<unsigned int>::max() || result < 0) {
+ return Result::OutOfRange(
+ "Failed to parse integer from " + str + "; out of unsigned int range");
+ }
+
+ return Result::Success(static_cast<unsigned int>(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<MillisecondsToNanoseconds> : CmdlineTypeParser<MillisecondsToNanoseconds> {
+ Result Parse(const std::string& str) {
+ CmdlineType<unsigned int> uint_parser;
+ CmdlineParseResult<unsigned int> 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<std::string> : CmdlineTypeParser<std::string> {
+ 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<std::vector<std::string>> : CmdlineTypeParser<std::vector<std::string>> {
+ 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<std::string>& existing_value) {
+ existing_value.push_back(args);
+ return Result::SuccessNoValue();
+ }
+
+ static const char* Name() { return "std::vector<std::string>"; }
+};
+
+template <char Separator>
+struct ParseStringList {
+ explicit ParseStringList(std::vector<std::string>&& list) : list_(list) {}
+
+ operator std::vector<std::string>() const {
+ return list_;
+ }
+
+ operator std::vector<std::string>&&() && {
+ return std::move(list_);
+ }
+
+ size_t Size() const {
+ return list_.size();
+ }
+
+ std::string Join() const {
+ return art::Join(list_, Separator);
+ }
+
+ static ParseStringList<Separator> Split(const std::string& str) {
+ std::vector<std::string> list;
+ art::Split(str, Separator, &list);
+ return ParseStringList<Separator>(std::move(list));
+ }
+
+ ParseStringList() = default;
+ ParseStringList(const ParseStringList& rhs) = default;
+ ParseStringList(ParseStringList&& rhs) = default;
+
+ private:
+ std::vector<std::string> list_;
+};
+
+template <char Separator>
+struct CmdlineType<ParseStringList<Separator>> : CmdlineTypeParser<ParseStringList<Separator>> {
+ using Result = CmdlineParseResult<ParseStringList<Separator>>;
+
+ Result Parse(const std::string& args) {
+ return Result::Success(ParseStringList<Separator>::Split(args));
+ }
+
+ static const char* Name() { return "ParseStringList<Separator>"; }
+};
+
+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<XGcOption> : CmdlineTypeParser<XGcOption> {
+ Result Parse(const std::string& option) { // -Xgc: already stripped
+ XGcOption xgc{}; // NOLINT [readability/braces] [4]
+
+ std::vector<std::string> 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<BackgroundGcOption>
+ : CmdlineTypeParser<BackgroundGcOption>, 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<LogVerbosity> : CmdlineTypeParser<LogVerbosity> {
+ Result Parse(const std::string& options) {
+ LogVerbosity log_verbosity = LogVerbosity();
+
+ std::vector<std::string> 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<TestProfilerOptions> : CmdlineTypeParser<TestProfilerOptions> {
+ using Result = CmdlineParseResult<TestProfilerOptions>;
+
+ private:
+ using StringResult = CmdlineParseResult<std::string>;
+ using DoubleResult = CmdlineParseResult<double>;
+
+ template <typename T>
+ static Result ParseInto(TestProfilerOptions& options,
+ T TestProfilerOptions::*pField,
+ CmdlineParseResult<T>&& result) {
+ assert(pField != nullptr);
+
+ if (result.IsSuccess()) {
+ options.*pField = result.ReleaseValue();
+ return Result::SuccessNoValue();
+ }
+
+ return Result::CastError(result);
+ }
+
+ template <typename T>
+ static Result ParseIntoRangeCheck(TestProfilerOptions& options,
+ T TestProfilerOptions::*pField,
+ CmdlineParseResult<T>&& result,
+ T min,
+ T max) {
+ if (result.IsSuccess()) {
+ const T& value = result.GetValue();
+
+ if (value < min || value > max) {
+ CmdlineParseResult<T> out_of_range = CmdlineParseResult<T>::OutOfRange(value, min, max);
+ return Result::CastError(out_of_range);
+ }
+ }
+
+ return ParseInto(options, pField, std::forward<CmdlineParseResult<T>>(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<std::string> type_parser;
+
+ return ParseInto(existing,
+ &TestProfilerOptions::output_file_name_,
+ type_parser.Parse(suffix));
+ } else if (StartsWith(option, "period:")) {
+ CmdlineType<unsigned int> type_parser;
+
+ return ParseInto(existing,
+ &TestProfilerOptions::period_s_,
+ type_parser.Parse(suffix));
+ } else if (StartsWith(option, "duration:")) {
+ CmdlineType<unsigned int> type_parser;
+
+ return ParseInto(existing,
+ &TestProfilerOptions::duration_s_,
+ type_parser.Parse(suffix));
+ } else if (StartsWith(option, "interval:")) {
+ CmdlineType<unsigned int> type_parser;
+
+ return ParseInto(existing,
+ &TestProfilerOptions::interval_us_,
+ type_parser.Parse(suffix));
+ } else if (StartsWith(option, "backoff:")) {
+ CmdlineType<double> 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<double> 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<double> 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<unsigned int> 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 <iostream>
+#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 <typename T>
+ 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 <type_traits>
+#include <assert.h>
+#include <functional>
+#include <vector>
+#include <algorithm>
+#include <numeric>
+#include <memory>
+
+#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<size_t, size_t> 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 <typename T>
+ using EnableIfNumeric = std::enable_if<std::is_arithmetic<T>::value>;
+
+ template <typename T>
+ using DisableIfNumeric = std::enable_if<!std::is_arithmetic<T>::value>;
+
+ // Argument definition information, created by an ArgumentBuilder and an UntypedArgumentBuilder.
+ template <typename TArg>
+ struct CmdlineParserArgumentInfo {
+ // This version will only be used if TArg is arithmetic and thus has the <= operators.
+ template <typename T = TArg> // Necessary to get SFINAE to kick in.
+ bool CheckRange(const TArg& value, typename EnableIfNumeric<T>::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 <typename T = TArg>
+ bool CheckRange(const TArg&, typename DisableIfNumeric<T>::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<const TokenRange*, size_t> 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<TArg>::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<const char*> 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<size_t, size_t> token_range_size_;
+
+ // contains all the names in a tokenized form, i.e. as a space-delimited list
+ std::vector<TokenRange> tokenized_names_;
+
+ // contains the tokenized names, but with the _ character stripped
+ std::vector<TokenRange> 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<std::pair<const char*, TArg>> value_map_;
+
+ // For argument definitions created with '.WithValues'
+ bool has_value_list_ = false;
+ std::vector<TArg> 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 <typename T = TArg>
+ void TypedCheck(typename std::enable_if<std::is_same<Unit, T>::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 <typename TArg>
+ struct CmdlineParseArgument : CmdlineParseArgumentAny {
+ explicit CmdlineParseArgument(CmdlineParserArgumentInfo<TArg>&& argument_info,
+ std::function<void(TArg&)>&& save_argument,
+ std::function<TArg&(void)>&& load_argument)
+ : argument_info_(std::forward<decltype(argument_info)>(argument_info)),
+ save_argument_(std::forward<decltype(save_argument)>(save_argument)),
+ load_argument_(std::forward<decltype(load_argument)>(load_argument)) {
+ }
+
+ using UserTypeInfo = CmdlineType<TArg>;
+
+ 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<TokenRange> 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<std::string> 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<std::string> 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<TArg> result = type_parser.ParseAndAppend(argument, existing);
+
+ assert(!argument_info_.has_range_);
+
+ return result;
+ }
+
+ CmdlineParseResult<TArg> 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<TArg>::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<size_t, size_t> 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<TArg> argument_info_;
+ std::function<void(TArg&)> save_argument_;
+ std::function<TArg&(void)> 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 <string>
+#include <sstream>
+#include <vector>
+
+namespace art {
+ // Implementation details for some template querying. Don't look inside if you hate templates.
+ namespace detail {
+ template <typename T>
+ typename std::remove_reference<T>::type& FakeReference();
+
+ // SupportsInsertionOperator<T, TStream>::value will evaluate to a boolean,
+ // whose value is true if the TStream class supports the << operator against T,
+ // and false otherwise.
+ template <typename T2, typename TStream2 = std::ostream>
+ struct SupportsInsertionOperator {
+ private:
+ template <typename TStream, typename T>
+ static std::true_type InsertionOperatorTest(TStream& os, const T& value,
+ std::remove_reference<decltype(os << value)>* = 0); // NOLINT [whitespace/operators] [3]
+
+ template <typename TStream, typename ... T>
+ static std::false_type InsertionOperatorTest(TStream& os, const T& ... args);
+
+ public:
+ static constexpr bool value =
+ decltype(InsertionOperatorTest(FakeReference<TStream2>(), std::declval<T2>()))::value;
+ };
+
+ template <typename TLeft, typename TRight = TLeft, bool IsFloatingPoint = false>
+ struct SupportsEqualityOperatorImpl;
+
+ template <typename TLeft, typename TRight>
+ struct SupportsEqualityOperatorImpl<TLeft, TRight, false> {
+ private:
+ template <typename TL, typename TR>
+ static std::true_type EqualityOperatorTest(const TL& left, const TR& right,
+ std::remove_reference<decltype(left == right)>* = 0); // NOLINT [whitespace/operators] [3]
+
+ template <typename TL, typename ... T>
+ static std::false_type EqualityOperatorTest(const TL& left, const T& ... args);
+
+ public:
+ static constexpr bool value =
+ decltype(EqualityOperatorTest(std::declval<TLeft>(), std::declval<TRight>()))::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 <typename TLeft, typename TRight>
+ struct SupportsEqualityOperatorImpl<TLeft, TRight, true> {
+ static constexpr bool value = true;
+ };
+
+ // SupportsEqualityOperatorImpl<T1, T2>::value will evaluate to a boolean,
+ // whose value is true if T1 can be compared against T2 with ==,
+ // and false otherwise.
+ template <typename TLeft, typename TRight = TLeft>
+ struct SupportsEqualityOperator :
+ SupportsEqualityOperatorImpl<TLeft, TRight,
+ std::is_floating_point<TLeft>::value
+ && std::is_floating_point<TRight>::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 <typename T>
+ std::string ToStringAny(const T& value,
+ typename std::enable_if<
+ SupportsInsertionOperator<T>::value>::type* = 0) {
+ std::stringstream stream;
+ stream << value;
+ return stream.str();
+ }
+
+ template <typename T>
+ std::string ToStringAny(const std::vector<T> value,
+ typename std::enable_if<
+ SupportsInsertionOperator<T>::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 <typename T>
+ std::string ToStringAny(const T&,
+ typename std::enable_if<
+ !SupportsInsertionOperator<T>::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 <string>
+#include <assert.h>
+#include <ostream>
+#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 <size_t Divisor = 1024>
+struct Memory {
+ static_assert(IsPowerOfTwo(Divisor), "Divisor must be a power of 2");
+
+ static Memory<Divisor> FromBytes(size_t bytes) {
+ assert(bytes % Divisor == 0);
+ return Memory<Divisor>(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 <size_t Divisor>
+std::ostream& operator<<(std::ostream& stream, Memory<Divisor> 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 <assert.h>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <memory>
+
+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<std::string>;
+
+ // 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 <typename ForwardIterator>
+ 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<TokenList>(token_list))),
+ begin_(token_list_->begin()),
+ end_(token_list_->end())
+ {}
+
+ // Non-copying constructor. Retain reference to existing list of tokens.
+ TokenRange(std::shared_ptr<TokenList> 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<TokenList> 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<char> 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<size_t>(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<TokenRange> 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<TokenRange>(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 <typename TPredicate>
+ 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<TokenList>(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<std::vector<std::string>> 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_