cb9ecbc363
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>
265 lines
14 KiB
C++
265 lines
14 KiB
C++
// Copyright (c) 2019, 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 <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
// TODO(dartbug.com/40579): This requires static linking to either link
|
|
// dart.exe or dart_precompiled_runtime.exe on Windows.
|
|
// The sample currently fails on Windows in AOT mode.
|
|
#include "include/dart_api.h"
|
|
#include "include/dart_native_api.h"
|
|
|
|
static bool is_dart_precompiled_runtime = true;
|
|
|
|
bool IsTreeShaken(const char* name, Dart_Handle handle, const char* error) {
|
|
// No tree shaking in the JIT runtime.
|
|
if (!is_dart_precompiled_runtime) return false;
|
|
if (Dart_IsApiError(handle)) {
|
|
// All tree-shaking related API errors should include the expected name.
|
|
if (strstr(error, name) == nullptr) return false;
|
|
// Node was tree shaken (e.g., 'Class C not found in library...').
|
|
if (strstr(error, " not found in ") != nullptr) return true;
|
|
// Constructor was tree shaken.
|
|
if (strstr(error, "Dart_New: could not find ") != nullptr) return true;
|
|
} else if (Dart_IsUnhandledExceptionError(handle)) {
|
|
// TFA replaces operations with a throw in some cases. If obfuscation
|
|
// is turned on and the member has no entry point annotation, then its
|
|
// name may be obfuscated in the result and so cannot be depended on.
|
|
if (strstr(error, "Attempt to execute code removed by ") != nullptr) {
|
|
return true;
|
|
}
|
|
// All other tree-shaking related unhandled exceptions are NSM errors
|
|
// that should include the expected name.
|
|
if (strstr(error, name) == nullptr) return false;
|
|
if (strstr(error, "NoSuchMethodError: ") == nullptr) {
|
|
return false;
|
|
}
|
|
if (strstr(error, "No top-level method '") != nullptr) return true;
|
|
if (strstr(error, "No top-level getter '") != nullptr) return true;
|
|
if (strstr(error, "No top-level setter '") != nullptr) return true;
|
|
if (strstr(error, "No static method '") != nullptr) return true;
|
|
if (strstr(error, "No static getter '") != nullptr) return true;
|
|
if (strstr(error, "No static setter '") != nullptr) return true;
|
|
if (strstr(error, "' has no instance method '") != nullptr) return true;
|
|
if (strstr(error, "' has no instance getter '") != nullptr) return true;
|
|
if (strstr(error, "' has no instance setter '") != nullptr) return true;
|
|
}
|
|
// Not an tree shaking-related error.
|
|
return false;
|
|
}
|
|
|
|
#if defined(_MSC_VER)
|
|
#define FATAL(fmt, ...) \
|
|
do { \
|
|
fprintf(stderr, "Failed at %s:%d: " fmt "!\n", __FILE__, __LINE__, \
|
|
__VA_ARGS__); \
|
|
abort(); \
|
|
} while (false)
|
|
#else
|
|
#define FATAL(fmt, ...) \
|
|
do { \
|
|
fprintf(stderr, "Failed at %s:%d: " fmt "!\n", __FILE__, __LINE__, \
|
|
##__VA_ARGS__); \
|
|
abort(); \
|
|
} while (false)
|
|
#endif
|
|
|
|
#define CHECK(H) \
|
|
do { \
|
|
fprintf(stderr, "Checking %s...\n", #H); \
|
|
Dart_Handle __handle__ = H; \
|
|
if (Dart_IsError(__handle__)) { \
|
|
const char* message = Dart_GetError(__handle__); \
|
|
FATAL("\n%s", message); \
|
|
} else { \
|
|
fprintf(stderr, " Check passed.\n\n"); \
|
|
} \
|
|
} while (false)
|
|
|
|
#define ASSERT_SUBSTRING(needle, haystack) \
|
|
do { \
|
|
if (strstr(haystack, needle) == nullptr) { \
|
|
FATAL("expected '%s' within:\n%s\n", needle, haystack); \
|
|
} \
|
|
} while (false)
|
|
|
|
#define FAIL(name, result) \
|
|
do { \
|
|
fprintf(stderr, "Expect failure to access '%s'\n", name); \
|
|
if (!Dart_IsError(result)) { \
|
|
FATAL("No error for accessing %s", name); \
|
|
} \
|
|
const char* error = Dart_GetError(result); \
|
|
if (IsTreeShaken(name, result, error)) { \
|
|
fprintf(stderr, " Received error due to tree shaking: %s\n\n", error); \
|
|
} else if (!Dart_IsApiError(result)) { \
|
|
FATAL("Not an API error for accessing %s: %s", name, error); \
|
|
} else { \
|
|
ASSERT_SUBSTRING(name, error); \
|
|
ASSERT_SUBSTRING("ERROR: ", error); \
|
|
ASSERT_SUBSTRING("' from native code, it must be annotated.", error); \
|
|
fprintf(stderr, " Received expected error: %s\n\n", error); \
|
|
} \
|
|
} while (false)
|
|
|
|
// Some invalid accesses are allowed in AOT since we don't retain @pragma
|
|
// annotations. Only use this if there's no other way to detect missing
|
|
// annotations, e.g., a function with no code signals that the precompiler
|
|
// did not preserve the code object because the function is not annotated
|
|
// as a "call" entry point.
|
|
#define FAIL_UNLESS_PRECOMPILED(name, result) \
|
|
do { \
|
|
if (!is_dart_precompiled_runtime) { \
|
|
FAIL(name, result); \
|
|
} else { \
|
|
CHECK(result); \
|
|
} \
|
|
} while (false);
|
|
|
|
#define FAIL_CLOSURIZE_CONSTRUCTOR(name, result) \
|
|
do { \
|
|
fprintf(stderr, "Expect failure to closurize constructor '%s'\n", name); \
|
|
if (!Dart_IsError(result)) { \
|
|
FATAL("No error for closurizing %s", name); \
|
|
} \
|
|
const char* error = Dart_GetError(result); \
|
|
if (!Dart_IsUnhandledExceptionError(result)) { \
|
|
FATAL("Not an unhandled exception error for closurizing %s: %s", name, \
|
|
error); \
|
|
} else { \
|
|
ASSERT_SUBSTRING(name, error); \
|
|
ASSERT_SUBSTRING("No static getter", error); \
|
|
fprintf(stderr, " Received expected error: %s\n\n", error); \
|
|
} \
|
|
} while (false)
|
|
|
|
#define TEST_FIELDS(target) \
|
|
do { \
|
|
/* Since the fields start off initialized to a non-null value and then */ \
|
|
/* are updated with a null value, check invocation prior to set. */ \
|
|
FAIL("fld0", Dart_GetField(target, Dart_NewStringFromCString("fld0"))); \
|
|
FAIL("fld0", \
|
|
Dart_Invoke(target, Dart_NewStringFromCString("fld0"), 0, nullptr)); \
|
|
FAIL("fld0", Dart_SetField(target, Dart_NewStringFromCString("fld0"), \
|
|
Dart_Null())); \
|
|
CHECK(Dart_GetField(target, Dart_NewStringFromCString("fld1"))); \
|
|
CHECK(Dart_Invoke(target, Dart_NewStringFromCString("fld1"), 0, nullptr)); \
|
|
CHECK(Dart_SetField(target, Dart_NewStringFromCString("fld1"), \
|
|
Dart_Null())); \
|
|
CHECK(Dart_GetField(target, Dart_NewStringFromCString("fld2"))); \
|
|
CHECK(Dart_Invoke(target, Dart_NewStringFromCString("fld2"), 0, nullptr)); \
|
|
if (Dart_IsLibrary(target) || Dart_IsType(target)) { \
|
|
/* There are no implicit setters for static fields, so the pragma */ \
|
|
/* must be checked; in precompiled mode, that means a false positive. */ \
|
|
FAIL_UNLESS_PRECOMPILED( \
|
|
"fld2", Dart_SetField(target, Dart_NewStringFromCString("fld2"), \
|
|
Dart_Null())); \
|
|
} else { \
|
|
FAIL("fld2", Dart_SetField(target, Dart_NewStringFromCString("fld2"), \
|
|
Dart_Null())); \
|
|
} \
|
|
FAIL("fld3", Dart_GetField(target, Dart_NewStringFromCString("fld3"))); \
|
|
FAIL("fld3", \
|
|
Dart_Invoke(target, Dart_NewStringFromCString("fld3"), 0, nullptr)); \
|
|
CHECK(Dart_SetField(target, Dart_NewStringFromCString("fld3"), \
|
|
Dart_Null())); \
|
|
} while (false)
|
|
|
|
DART_EXPORT void RunTests() {
|
|
is_dart_precompiled_runtime = Dart_IsPrecompiledRuntime();
|
|
|
|
Dart_Handle lib = Dart_RootLibrary();
|
|
|
|
//////// Test class access.
|
|
|
|
FAIL("C", Dart_GetClass(lib, Dart_NewStringFromCString("C")));
|
|
|
|
Dart_Handle D_class = Dart_GetClass(lib, Dart_NewStringFromCString("D"));
|
|
CHECK(D_class);
|
|
|
|
Dart_Handle F_class = Dart_GetClass(lib, Dart_NewStringFromCString("F"));
|
|
CHECK(F_class);
|
|
|
|
//////// Test allocation and constructor invocation.
|
|
|
|
CHECK(Dart_Allocate(D_class));
|
|
|
|
FAIL("D.", Dart_New(D_class, Dart_Null(), 0, nullptr));
|
|
|
|
CHECK(Dart_New(D_class, Dart_NewStringFromCString("defined"), 0, nullptr));
|
|
Dart_Handle D =
|
|
Dart_New(D_class, Dart_NewStringFromCString("fact"), 0, nullptr);
|
|
CHECK(D);
|
|
|
|
//////// Test actions against methods
|
|
|
|
fprintf(stderr, "\n\nTesting methods with library target\n\n\n");
|
|
|
|
FAIL("noop", Dart_Invoke(lib, Dart_NewStringFromCString("noop"), 0, nullptr));
|
|
|
|
FAIL("fn0", Dart_Invoke(lib, Dart_NewStringFromCString("fn0"), 0, nullptr));
|
|
|
|
CHECK(Dart_Invoke(lib, Dart_NewStringFromCString("fn1"), 0, nullptr));
|
|
FAIL("fn1_get",
|
|
Dart_Invoke(lib, Dart_NewStringFromCString("fn1_get"), 0, nullptr));
|
|
CHECK(Dart_Invoke(lib, Dart_NewStringFromCString("fn1_call"), 0, nullptr));
|
|
|
|
FAIL("fn0", Dart_GetField(lib, Dart_NewStringFromCString("fn0")));
|
|
|
|
CHECK(Dart_GetField(lib, Dart_NewStringFromCString("fn1")));
|
|
CHECK(Dart_GetField(lib, Dart_NewStringFromCString("fn1_get")));
|
|
FAIL("fn1_call", Dart_GetField(lib, Dart_NewStringFromCString("fn1_call")));
|
|
|
|
fprintf(stderr, "\n\nTesting methods with class target\n\n\n");
|
|
|
|
FAIL_CLOSURIZE_CONSTRUCTOR(
|
|
"defined", Dart_GetField(D_class, Dart_NewStringFromCString("defined")));
|
|
FAIL_CLOSURIZE_CONSTRUCTOR(
|
|
"fact", Dart_GetField(D_class, Dart_NewStringFromCString("fact")));
|
|
|
|
FAIL("fn0", Dart_Invoke(D, Dart_NewStringFromCString("fn0"), 0, nullptr));
|
|
|
|
FAIL("fn2",
|
|
Dart_Invoke(D_class, Dart_NewStringFromCString("fn2"), 0, nullptr));
|
|
|
|
CHECK(Dart_Invoke(D_class, Dart_NewStringFromCString("fn3"), 0, nullptr));
|
|
CHECK(
|
|
Dart_Invoke(D_class, Dart_NewStringFromCString("fn3_call"), 0, nullptr));
|
|
FAIL("fn3_get",
|
|
Dart_Invoke(D_class, Dart_NewStringFromCString("fn3_get"), 0, nullptr));
|
|
|
|
FAIL("fn2", Dart_GetField(D_class, Dart_NewStringFromCString("fn2")));
|
|
|
|
CHECK(Dart_GetField(D_class, Dart_NewStringFromCString("fn3")));
|
|
FAIL("fn3_call",
|
|
Dart_GetField(D_class, Dart_NewStringFromCString("fn3_call")));
|
|
CHECK(Dart_GetField(D_class, Dart_NewStringFromCString("fn3_get")));
|
|
|
|
fprintf(stderr, "\n\nTesting methods with instance target\n\n\n");
|
|
|
|
CHECK(Dart_Invoke(D, Dart_NewStringFromCString("fn1"), 0, nullptr));
|
|
FAIL("fn1_get",
|
|
Dart_Invoke(D, Dart_NewStringFromCString("fn1_get"), 0, nullptr));
|
|
CHECK(Dart_Invoke(D, Dart_NewStringFromCString("fn1_call"), 0, nullptr));
|
|
|
|
FAIL("fn0", Dart_GetField(D, Dart_NewStringFromCString("fn0")));
|
|
|
|
CHECK(Dart_GetField(D, Dart_NewStringFromCString("fn1")));
|
|
CHECK(Dart_GetField(D, Dart_NewStringFromCString("fn1_get")));
|
|
FAIL("fn1_call", Dart_GetField(D, Dart_NewStringFromCString("fn1_call")));
|
|
|
|
//////// Test actions against fields
|
|
|
|
fprintf(stderr, "\n\nTesting fields with library target\n\n\n");
|
|
TEST_FIELDS(lib);
|
|
|
|
fprintf(stderr, "\n\nTesting fields with class target\n\n\n");
|
|
TEST_FIELDS(F_class);
|
|
|
|
fprintf(stderr, "\n\nTesting fields with instance target\n\n\n");
|
|
TEST_FIELDS(D);
|
|
}
|