| // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | #include "vm/flags.h" | 
 |  | 
 | #include "platform/assert.h" | 
 | #include "vm/isolate.h" | 
 | #include "vm/json_stream.h" | 
 | #include "vm/os.h" | 
 |  | 
 | namespace dart { | 
 |  | 
 | DEFINE_FLAG(bool, print_flags, false, "Print flags as they are being parsed."); | 
 | DEFINE_FLAG(bool, | 
 |             ignore_unrecognized_flags, | 
 |             false, | 
 |             "Ignore unrecognized flags."); | 
 |  | 
 | #define PRODUCT_FLAG_MACRO(name, type, default_value, comment)                 \ | 
 |   type FLAG_##name =                                                           \ | 
 |       Flags::Register_##type(&FLAG_##name, #name, default_value, comment); | 
 |  | 
 | #if defined(DEBUG) | 
 | #define DEBUG_FLAG_MACRO(name, type, default_value, comment)                   \ | 
 |   type FLAG_##name =                                                           \ | 
 |       Flags::Register_##type(&FLAG_##name, #name, default_value, comment); | 
 | #else  // defined(DEBUG) | 
 | #define DEBUG_FLAG_MACRO(name, type, default_value, comment) | 
 | #endif  // defined(DEBUG) | 
 |  | 
 | #if defined(PRODUCT) && defined(DART_PRECOMPILED_RUNTIME) | 
 | // Nothing to be done for the product flag definitions. | 
 | #define RELEASE_FLAG_MACRO(name, product_value, type, default_value, comment) | 
 | // Nothing to be done for the precompilation flag definitions. | 
 | #define PRECOMPILE_FLAG_MACRO(name, pre_value, product_value, type,            \ | 
 |                               default_value, comment) | 
 |  | 
 | #elif defined(PRODUCT)  // !PRECOMPILED | 
 | // Nothing to be done for the product flag definitions. | 
 | #define RELEASE_FLAG_MACRO(name, product_value, type, default_value, comment) | 
 | // Nothing to be done for the precompilation flag definitions. | 
 | #define PRECOMPILE_FLAG_MACRO(name, pre_value, product_value, type,            \ | 
 |                               default_value, comment) | 
 |  | 
 | #elif defined(DART_PRECOMPILED_RUNTIME)  // !PRODUCT | 
 | #define RELEASE_FLAG_MACRO(name, product_value, type, default_value, comment)  \ | 
 |   type FLAG_##name =                                                           \ | 
 |       Flags::Register_##type(&FLAG_##name, #name, default_value, comment); | 
 | // Nothing to be done for the precompilation flag definitions. | 
 | #define PRECOMPILE_FLAG_MACRO(name, pre_value, product_value, type,            \ | 
 |                               default_value, comment) | 
 |  | 
 | #else  // !PRODUCT && !PRECOMPILED | 
 | #define RELEASE_FLAG_MACRO(name, product_value, type, default_value, comment)  \ | 
 |   type FLAG_##name =                                                           \ | 
 |       Flags::Register_##type(&FLAG_##name, #name, default_value, comment); | 
 | #define PRECOMPILE_FLAG_MACRO(name, pre_value, product_value, type,            \ | 
 |                               default_value, comment)                          \ | 
 |   type FLAG_##name =                                                           \ | 
 |       Flags::Register_##type(&FLAG_##name, #name, default_value, comment); | 
 | #endif | 
 |  | 
 | // Define all of the non-product flags here. | 
 | FLAG_LIST(PRODUCT_FLAG_MACRO, | 
 |           RELEASE_FLAG_MACRO, | 
 |           PRECOMPILE_FLAG_MACRO, | 
 |           DEBUG_FLAG_MACRO) | 
 |  | 
 | #undef PRODUCT_FLAG_MACRO | 
 | #undef RELEASE_FLAG_MACRO | 
 | #undef PRECOMPILE_FLAG_MACRO | 
 | #undef DEBUG_FLAG_MACRO | 
 |  | 
 | #if defined(DART_PRECOMPILER) | 
 | #if defined(TARGET_USES_THREAD_SANITIZER) | 
 | constexpr bool kDefaultTargetThreadSanitizer = true; | 
 | #else | 
 | constexpr bool kDefaultTargetThreadSanitizer = false; | 
 | #endif | 
 | DEFINE_FLAG(bool, | 
 |             target_thread_sanitizer, | 
 |             kDefaultTargetThreadSanitizer, | 
 |             "Generate Dart code compatible with Thread Sanitizer"); | 
 | #if defined(TARGET_USES_MEMORY_SANITIZER) | 
 | constexpr bool kDefaultTargetMemorySanitizer = true; | 
 | #else | 
 | constexpr bool kDefaultTargetMemorySanitizer = false; | 
 | #endif | 
 | DEFINE_FLAG(bool, | 
 |             target_memory_sanitizer, | 
 |             kDefaultTargetMemorySanitizer, | 
 |             "Generate Dart code compatible with Memory Sanitizer"); | 
 | #endif | 
 |  | 
 | bool Flags::initialized_ = false; | 
 |  | 
 | // List of registered flags. | 
 | Flag** Flags::flags_ = nullptr; | 
 | intptr_t Flags::capacity_ = 0; | 
 | intptr_t Flags::num_flags_ = 0; | 
 |  | 
 | class Flag { | 
 |  public: | 
 |   enum FlagType { | 
 |     kBoolean, | 
 |     kInteger, | 
 |     kUint64, | 
 |     kString, | 
 |     kFlagHandler, | 
 |     kOptionHandler, | 
 |     kNumFlagTypes | 
 |   }; | 
 |  | 
 |   Flag(const char* name, const char* comment, void* addr, FlagType type) | 
 |       : name_(name), comment_(comment), addr_(addr), type_(type) {} | 
 |   Flag(const char* name, const char* comment, FlagHandler handler) | 
 |       : name_(name), | 
 |         comment_(comment), | 
 |         flag_handler_(handler), | 
 |         type_(kFlagHandler) {} | 
 |   Flag(const char* name, const char* comment, OptionHandler handler) | 
 |       : name_(name), | 
 |         comment_(comment), | 
 |         option_handler_(handler), | 
 |         type_(kOptionHandler) {} | 
 |  | 
 |   void Print() { | 
 |     if (IsUnrecognized()) { | 
 |       OS::PrintErr("%s: unrecognized\n", name_); | 
 |       return; | 
 |     } | 
 |     switch (type_) { | 
 |       case kBoolean: { | 
 |         OS::Print("%s: %s (%s)\n", name_, *this->bool_ptr_ ? "true" : "false", | 
 |                   comment_); | 
 |         break; | 
 |       } | 
 |       case kInteger: { | 
 |         OS::Print("%s: %d (%s)\n", name_, *this->int_ptr_, comment_); | 
 |         break; | 
 |       } | 
 |       case kUint64: { | 
 |         OS::Print("%s: %" Pu64 " (%s)\n", name_, *this->uint64_ptr_, comment_); | 
 |         break; | 
 |       } | 
 |       case kString: { | 
 |         if (*this->charp_ptr_ != nullptr) { | 
 |           OS::Print("%s: '%s' (%s)\n", name_, *this->charp_ptr_, comment_); | 
 |         } else { | 
 |           OS::Print("%s: (null) (%s)\n", name_, comment_); | 
 |         } | 
 |         break; | 
 |       } | 
 |       case kOptionHandler: | 
 |       case kFlagHandler: { | 
 |         OS::Print("%s: (%s)\n", name_, comment_); | 
 |         break; | 
 |       } | 
 |       default: | 
 |         UNREACHABLE(); | 
 |         break; | 
 |     } | 
 |   } | 
 |  | 
 |   bool IsUnrecognized() const { | 
 |     return (type_ == kBoolean) && (bool_ptr_ == nullptr); | 
 |   } | 
 |  | 
 |   const char* name_; | 
 |   const char* comment_; | 
 |  | 
 |   // For kString, kOptionHandler, kFlagHandler flags this stores the copy | 
 |   // of the original flag value passed to SetFlagFromString | 
 |   CStringUniquePtr string_value_; | 
 |   union { | 
 |     void* addr_; | 
 |     bool* bool_ptr_; | 
 |     int* int_ptr_; | 
 |     uint64_t* uint64_ptr_; | 
 |     charp* charp_ptr_; | 
 |     FlagHandler flag_handler_; | 
 |     OptionHandler option_handler_; | 
 |   }; | 
 |   const FlagType type_; | 
 |   bool changed_ = false; | 
 | }; | 
 |  | 
 | Flag* Flags::Lookup(const char* name) { | 
 |   for (intptr_t i = 0; i < num_flags_; i++) { | 
 |     Flag* flag = flags_[i]; | 
 |     if (strcmp(flag->name_, name) == 0) { | 
 |       return flag; | 
 |     } | 
 |   } | 
 |   return nullptr; | 
 | } | 
 |  | 
 | bool Flags::IsSet(const char* name) { | 
 |   Flag* flag = Lookup(name); | 
 |   return (flag != nullptr) && (flag->type_ == Flag::kBoolean) && | 
 |          (flag->bool_ptr_ != nullptr) && (*flag->bool_ptr_ == true); | 
 | } | 
 |  | 
 | void Flags::Cleanup() { | 
 |   ASSERT(initialized_); | 
 |   initialized_ = false; | 
 | } | 
 |  | 
 | void Flags::AddFlag(Flag* flag) { | 
 |   ASSERT(!initialized_); | 
 |   if (num_flags_ == capacity_) { | 
 |     if (flags_ == nullptr) { | 
 |       capacity_ = 256; | 
 |       flags_ = new Flag*[capacity_]; | 
 |     } else { | 
 |       intptr_t new_capacity = capacity_ * 2; | 
 |       Flag** new_flags = new Flag*[new_capacity]; | 
 |       for (intptr_t i = 0; i < num_flags_; i++) { | 
 |         new_flags[i] = flags_[i]; | 
 |       } | 
 |       delete[] flags_; | 
 |       flags_ = new_flags; | 
 |       capacity_ = new_capacity; | 
 |     } | 
 |   } | 
 |   flags_[num_flags_++] = flag; | 
 | } | 
 |  | 
 | bool Flags::Register_bool(bool* addr, | 
 |                           const char* name, | 
 |                           bool default_value, | 
 |                           const char* comment) { | 
 |   Flag* flag = Lookup(name); | 
 |   if (flag != nullptr) { | 
 |     ASSERT(flag->IsUnrecognized()); | 
 |     return default_value; | 
 |   } | 
 |   flag = new Flag(name, comment, addr, Flag::kBoolean); | 
 |   AddFlag(flag); | 
 |   return default_value; | 
 | } | 
 |  | 
 | int Flags::Register_int(int* addr, | 
 |                         const char* name, | 
 |                         int default_value, | 
 |                         const char* comment) { | 
 |   ASSERT(Lookup(name) == nullptr); | 
 |  | 
 |   Flag* flag = new Flag(name, comment, addr, Flag::kInteger); | 
 |   AddFlag(flag); | 
 |  | 
 |   return default_value; | 
 | } | 
 |  | 
 | uint64_t Flags::Register_uint64_t(uint64_t* addr, | 
 |                                   const char* name, | 
 |                                   uint64_t default_value, | 
 |                                   const char* comment) { | 
 |   ASSERT(Lookup(name) == nullptr); | 
 |  | 
 |   Flag* flag = new Flag(name, comment, addr, Flag::kUint64); | 
 |   AddFlag(flag); | 
 |  | 
 |   return default_value; | 
 | } | 
 |  | 
 | const char* Flags::Register_charp(charp* addr, | 
 |                                   const char* name, | 
 |                                   const char* default_value, | 
 |                                   const char* comment) { | 
 |   ASSERT(Lookup(name) == nullptr); | 
 |   Flag* flag = new Flag(name, comment, addr, Flag::kString); | 
 |   AddFlag(flag); | 
 |   return default_value; | 
 | } | 
 |  | 
 | bool Flags::RegisterFlagHandler(FlagHandler handler, | 
 |                                 const char* name, | 
 |                                 const char* comment) { | 
 |   ASSERT(Lookup(name) == nullptr); | 
 |   Flag* flag = new Flag(name, comment, handler); | 
 |   AddFlag(flag); | 
 |   return false; | 
 | } | 
 |  | 
 | bool Flags::RegisterOptionHandler(OptionHandler handler, | 
 |                                   const char* name, | 
 |                                   const char* comment) { | 
 |   ASSERT(Lookup(name) == nullptr); | 
 |   Flag* flag = new Flag(name, comment, handler); | 
 |   AddFlag(flag); | 
 |   return false; | 
 | } | 
 |  | 
 | static void Normalize(char* s) { | 
 |   intptr_t len = strlen(s); | 
 |   for (intptr_t i = 0; i < len; i++) { | 
 |     if (s[i] == '-') { | 
 |       s[i] = '_'; | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | bool Flags::SetFlagFromString(Flag* flag, const char* argument) { | 
 |   ASSERT(!flag->IsUnrecognized()); | 
 |   switch (flag->type_) { | 
 |     case Flag::kBoolean: { | 
 |       if (strcmp(argument, "true") == 0) { | 
 |         *flag->bool_ptr_ = true; | 
 |       } else if (strcmp(argument, "false") == 0) { | 
 |         *flag->bool_ptr_ = false; | 
 |       } else { | 
 |         return false; | 
 |       } | 
 |       break; | 
 |     } | 
 |     case Flag::kString: { | 
 |       flag->string_value_.reset(argument == nullptr ? nullptr | 
 |                                                     : Utils::StrDup(argument)); | 
 |       *flag->charp_ptr_ = flag->string_value_.get(); | 
 |       break; | 
 |     } | 
 |     case Flag::kInteger: { | 
 |       char* endptr = nullptr; | 
 |       const intptr_t len = strlen(argument); | 
 |       int base = 10; | 
 |       if ((len > 2) && (argument[0] == '0') && (argument[1] == 'x')) { | 
 |         base = 16; | 
 |       } | 
 |       int val = strtol(argument, &endptr, base); | 
 |       if (endptr == argument + len) { | 
 |         *flag->int_ptr_ = val; | 
 |       } else { | 
 |         return false; | 
 |       } | 
 |       break; | 
 |     } | 
 |     case Flag::kUint64: { | 
 |       char* endptr = nullptr; | 
 |       const intptr_t len = strlen(argument); | 
 |       int base = 10; | 
 |       if ((len > 2) && (argument[0] == '0') && (argument[1] == 'x')) { | 
 |         base = 16; | 
 |       } | 
 |       int64_t val = strtoll(argument, &endptr, base); | 
 |       if (endptr == argument + len) { | 
 |         *flag->uint64_ptr_ = static_cast<uint64_t>(val); | 
 |       } else { | 
 |         return false; | 
 |       } | 
 |       break; | 
 |     } | 
 |     case Flag::kFlagHandler: { | 
 |       if (strcmp(argument, "true") == 0) { | 
 |         (flag->flag_handler_)(true); | 
 |       } else if (strcmp(argument, "false") == 0) { | 
 |         (flag->flag_handler_)(false); | 
 |       } else { | 
 |         return false; | 
 |       } | 
 |       flag->string_value_.reset(Utils::StrDup(argument)); | 
 |       break; | 
 |     } | 
 |     case Flag::kOptionHandler: { | 
 |       flag->string_value_.reset(Utils::StrDup(argument)); | 
 |       (flag->option_handler_)(argument); | 
 |       break; | 
 |     } | 
 |     default: { | 
 |       UNREACHABLE(); | 
 |       return false; | 
 |     } | 
 |   } | 
 |   flag->changed_ = true; | 
 |   return true; | 
 | } | 
 |  | 
 | void Flags::Parse(const char* option) { | 
 |   // Find the beginning of the option argument, if it exists. | 
 |   const char* equals = option; | 
 |   while ((*equals != '\0') && (*equals != '=')) { | 
 |     equals++; | 
 |   } | 
 |  | 
 |   const char* argument = nullptr; | 
 |  | 
 |   // Determine if this is an option argument. | 
 |   if (*equals != '=') { | 
 |     // No explicit option argument. Determine if there is a "no_" prefix | 
 |     // preceding the name. | 
 |     const char* const kNo1Prefix = "no_"; | 
 |     const char* const kNo2Prefix = "no-"; | 
 |     const intptr_t kNo1PrefixLen = strlen(kNo1Prefix); | 
 |     const intptr_t kNo2PrefixLen = strlen(kNo2Prefix); | 
 |     if (strncmp(option, kNo1Prefix, kNo1PrefixLen) == 0) { | 
 |       option += kNo1PrefixLen;  // Skip the "no_" when looking up the name. | 
 |       argument = "false"; | 
 |     } else if (strncmp(option, kNo2Prefix, kNo2PrefixLen) == 0) { | 
 |       option += kNo2PrefixLen;  // Skip the "no-" when looking up the name. | 
 |       argument = "false"; | 
 |     } else { | 
 |       argument = "true"; | 
 |     } | 
 |   } else { | 
 |     // The argument for the option starts right after the equals sign. | 
 |     argument = equals + 1; | 
 |   } | 
 |  | 
 |   // Initialize the flag name. | 
 |   intptr_t name_len = equals - option; | 
 |   char* name = new char[name_len + 1]; | 
 |   strncpy(name, option, name_len); | 
 |   name[name_len] = '\0'; | 
 |   Normalize(name); | 
 |  | 
 |   Flag* flag = Flags::Lookup(name); | 
 |   if (flag == nullptr) { | 
 |     // Collect unrecognized flags. | 
 |     char* new_flag = new char[name_len + 1]; | 
 |     strncpy(new_flag, option, name_len); | 
 |     new_flag[name_len] = '\0'; | 
 |     Normalize(new_flag);  // Or a later lookup may fail. | 
 |     Flags::Register_bool(nullptr, new_flag, true, nullptr); | 
 |   } else { | 
 |     // Only set values for recognized flags, skip collected | 
 |     // unrecognized flags. | 
 |     if (!flag->IsUnrecognized()) { | 
 |       if (!SetFlagFromString(flag, argument)) { | 
 |         OS::PrintErr("Ignoring flag: %s is an invalid value for flag %s\n", | 
 |                      argument, name); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   delete[] name; | 
 | } | 
 |  | 
 | static bool IsValidFlag(const char* name, | 
 |                         const char* prefix, | 
 |                         intptr_t prefix_length) { | 
 |   intptr_t name_length = strlen(name); | 
 |   return ((name_length > prefix_length) && | 
 |           (strncmp(name, prefix, prefix_length) == 0)); | 
 | } | 
 |  | 
 | int Flags::CompareFlagNames(const void* left, const void* right) { | 
 |   const Flag* left_flag = *reinterpret_cast<const Flag* const*>(left); | 
 |   const Flag* right_flag = *reinterpret_cast<const Flag* const*>(right); | 
 |   return strcmp(left_flag->name_, right_flag->name_); | 
 | } | 
 |  | 
 | char* Flags::ProcessCommandLineFlags(int number_of_vm_flags, | 
 |                                      const char** vm_flags) { | 
 |   if (initialized_) { | 
 |     return Utils::StrDup("Flags already set"); | 
 |   } | 
 |  | 
 |   qsort(flags_, num_flags_, sizeof flags_[0], CompareFlagNames); | 
 |  | 
 |   const char* const kPrefix = "--"; | 
 |   const intptr_t kPrefixLen = strlen(kPrefix); | 
 |  | 
 |   int i = 0; | 
 |   while ((i < number_of_vm_flags) && | 
 |          IsValidFlag(vm_flags[i], kPrefix, kPrefixLen)) { | 
 |     const char* option = vm_flags[i] + kPrefixLen; | 
 |     Parse(option); | 
 |     i++; | 
 |   } | 
 |  | 
 |   if (!FLAG_ignore_unrecognized_flags) { | 
 |     int unrecognized_count = 0; | 
 |     TextBuffer error(64); | 
 |     for (intptr_t j = 0; j < num_flags_; j++) { | 
 |       Flag* flag = flags_[j]; | 
 |       if (flag->IsUnrecognized()) { | 
 |         if (unrecognized_count == 0) { | 
 |           error.Printf("Unrecognized flags: %s", flag->name_); | 
 |         } else { | 
 |           error.Printf(", %s", flag->name_); | 
 |         } | 
 |         unrecognized_count++; | 
 |       } | 
 |     } | 
 |     if (unrecognized_count > 0) { | 
 |       return error.Steal(); | 
 |     } | 
 |   } | 
 |   if (FLAG_print_flags) { | 
 |     PrintFlags(); | 
 |   } | 
 |  | 
 |   initialized_ = true; | 
 |   return nullptr; | 
 | } | 
 |  | 
 | bool Flags::SetFlag(const char* name, const char* value, const char** error) { | 
 |   Flag* flag = Lookup(name); | 
 |   if (flag == nullptr) { | 
 |     *error = "Cannot set flag: flag not found"; | 
 |     return false; | 
 |   } | 
 |   if (!SetFlagFromString(flag, value)) { | 
 |     *error = "Cannot set flag: invalid value"; | 
 |     return false; | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | void Flags::PrintFlags() { | 
 |   OS::Print("Flag settings:\n"); | 
 |   for (intptr_t i = 0; i < num_flags_; ++i) { | 
 |     flags_[i]->Print(); | 
 |   } | 
 | } | 
 |  | 
 | #ifndef PRODUCT | 
 | void Flags::PrintFlagToJSONArray(JSONArray* jsarr, const Flag* flag) { | 
 |   if (flag->IsUnrecognized()) { | 
 |     return; | 
 |   } | 
 |   JSONObject jsflag(jsarr); | 
 |   jsflag.AddProperty("name", flag->name_); | 
 |   jsflag.AddProperty("comment", flag->comment_); | 
 |   jsflag.AddProperty("modified", flag->changed_); | 
 |   switch (flag->type_) { | 
 |     case Flag::kBoolean: { | 
 |       jsflag.AddProperty("_flagType", "Bool"); | 
 |       jsflag.AddProperty("valueAsString", | 
 |                          (*flag->bool_ptr_ ? "true" : "false")); | 
 |       break; | 
 |     } | 
 |     case Flag::kInteger: { | 
 |       jsflag.AddProperty("_flagType", "Int"); | 
 |       jsflag.AddPropertyF("valueAsString", "%d", *flag->int_ptr_); | 
 |       break; | 
 |     } | 
 |     case Flag::kUint64: { | 
 |       jsflag.AddProperty("_flagType", "UInt64"); | 
 |       jsflag.AddPropertyF("valueAsString", "%" Pu64, *flag->uint64_ptr_); | 
 |       break; | 
 |     } | 
 |     case Flag::kString: { | 
 |       jsflag.AddProperty("_flagType", "String"); | 
 |       if (flag->charp_ptr_ != nullptr) { | 
 |         jsflag.AddPropertyF("valueAsString", "%s", *flag->charp_ptr_); | 
 |       } else { | 
 |         // valueAsString missing means nullptr. | 
 |       } | 
 |       break; | 
 |     } | 
 |     case Flag::kFlagHandler: { | 
 |       jsflag.AddProperty("_flagType", "Bool"); | 
 |       const char* value = flag->string_value_.get(); | 
 |       jsflag.AddProperty("valueAsString", value == nullptr ? "false" : value); | 
 |       break; | 
 |     } | 
 |     case Flag::kOptionHandler: { | 
 |       jsflag.AddProperty("_flagType", "String"); | 
 |       if (flag->string_value_ != nullptr) { | 
 |         jsflag.AddProperty("valueAsString", flag->string_value_.get()); | 
 |       } else { | 
 |         // valueAsString missing means nullptr. | 
 |       } | 
 |       break; | 
 |     } | 
 |     default: | 
 |       UNREACHABLE(); | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | void Flags::PrintJSON(JSONStream* js) { | 
 |   JSONObject jsobj(js); | 
 |   jsobj.AddProperty("type", "FlagList"); | 
 |   JSONArray jsarr(&jsobj, "flags"); | 
 |   for (intptr_t i = 0; i < num_flags_; ++i) { | 
 |     PrintFlagToJSONArray(&jsarr, flags_[i]); | 
 |   } | 
 | } | 
 | #endif  // !PRODUCT | 
 |  | 
 | }  // namespace dart |