Files
sdk/runtime/vm/flags.cc
T
Tess Strickland 9b06e26620 Reland "[vm] Enforce that entry points must be annotated by default."
This is a reland of commit cb9ecbc363

This reland only turns on the entry point verification flag by
default in AOT mode. After Flutter tests that use native access
in JIT mode have been appropriately updated, a followup CL will
turn this flag on by default in JIT mode as well.

Original change's description:
> [vm] Enforce that entry points must be annotated by default.
>
> Changes the default value of the --verify-entry-points flag
> to true.
>
> Changes the default value for the check_is_entrypoint argument to
> to the Invoke/InvokeGetter/InvokeSetter flags to true. The mirrors
> library implementation and calls via vm-service explicitly pass
> false for this argument now.
>
> Add annotations as needed, such as annotating classes with
> annotated generative constructors. In some cases, the annotations
> were more general than needed (e.g., annotating with a no-argument
> entry point annotation when only the setter is needed), so make
> those annotations more specific.
>
> As this pattern is already common in downstream code, allow
> Dart_Invoke on fields as long as the field is annotated for getter
> access. (That is, calling Dart_Invoke for a field is equivalent to
> retrieving the closure value via Dart_GetField and then calling
> Dart_InvokeClosure.)
>
> TEST=vm/cc/DartAPI_MissingEntryPoints
>      vm/dart/entrypoints_verification_test
>
> Issue: https://github.com/dart-lang/sdk/issues/50649
> Issue: https://github.com/flutter/flutter/issues/118608
>
> Change-Id: Ibb3bf15632ab2958d8791b449af8651d47f871a5
> Cq-Include-Trybots: luci.dart.try:vm-aot-linux-product-x64-try,vm-aot-linux-debug-x64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-product-arm64-try,vm-aot-dwarf-linux-product-x64-try
> CoreLibraryReviewExempt: adding/editing vm-only pragma annotations
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/363566
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Tess Strickland <sstrickl@google.com>

TEST=vm/cc/DartAPI_MissingEntryPoints
     vm/dart/entrypoints_verification_test

Change-Id: I24919c32ab4760c7c5435c378879791086256f02
Cq-Include-Trybots: luci.dart.try:vm-aot-linux-product-x64-try,vm-aot-linux-debug-x64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-product-arm64-try,vm-aot-dwarf-linux-product-x64-try,flutter-linux-try,vm-linux-debug-x64-try,vm-linux-release-x64-try,vm-appjit-linux-product-x64-try
CoreLibraryReviewExempt: adding/editing vm-only pragma annotations
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/391620
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
2024-12-04 14:36:46 +00:00

597 lines
18 KiB
C++

// 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)
// Nothing to be done for the AOT flag definitions.
#define AOT_FLAG_MACRO(name, pre_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)
#define AOT_FLAG_MACRO(name, pre_value, type, default_value, comment) \
type FLAG_##name = \
Flags::Register_##type(&FLAG_##name, #name, 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)
// Nothing to be done for the AOT flag definitions.
#define AOT_FLAG_MACRO(name, pre_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);
#define AOT_FLAG_MACRO(name, pre_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,
AOT_FLAG_MACRO,
DEBUG_FLAG_MACRO)
#undef PRODUCT_FLAG_MACRO
#undef RELEASE_FLAG_MACRO
#undef PRECOMPILE_FLAG_MACRO
#undef AOT_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