// 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"
#include "vm/version.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_ADDRESS_SANITIZER)
constexpr bool kDefaultTargetAddressSanitizer = true;
#else
constexpr bool kDefaultTargetAddressSanitizer = false;
#endif
DEFINE_FLAG(bool,
            target_address_sanitizer,
            kDefaultTargetAddressSanitizer,
            "Generate Dart code compatible with Address 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");

#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");
#endif

static bool IsMainOrDevChannel() {
  return strstr("|main|dev|", Version::Channel()) != nullptr;
}

static void EnableExperimentSharedData(bool value) {
  if (value && !IsMainOrDevChannel()) {
    FATAL(
        "Shared memory multithreading in only available for "
        "experimentation in dev or main");
  }
  dart::FLAG_experimental_shared_data = value;
}

bool FLAG_experimental_shared_data = false;
DEFINE_FLAG_HANDLER(EnableExperimentSharedData,
                    experimental_shared_data,
                    "Enable experiment to share data between isolates.");

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
