Files
sdk/runtime/bin/entrypoints_verification_test.cc
T
Tess Strickland 1ac77f57dd [vm] Align entry point verification and the precompiler.
For entry-point pragma annotations, most of the time they are
used with either no argument or with an argument that evaluates
to either

* false to denote the annotation should not take effect, or
* null or true to denote the annotation should take effect.

However, the user can also specify that only part of the operations
on a member should be accessed from native code by using a string
argument that is either 'call', 'set', or 'get'.

The entry point verification in Invoke/InvokeGetter/InvokeSetter
assumes that for getters and setters, the only valid string argument
is 'get' or 'set', respectively. This is because those methods are
called via `Dart_GetField`[0] and `Dart_SetField`, respectively, as if
they were the getter or setter of a defined field.

However, the precompiler previously assumed that the string
argument 'call' was the only string argument that meant the link
to a function's code object should be saved. Similarly, it assumed the
string argument 'get' for functions meant that their implicit closure
function should be saved, which ends up including getters. Furthermore,
it did not do anything with setters annotated with the string argument
'set'. This means that the code link would not be saved for getters or
setters that were annotated with the string argument expected by the
entry point verifier.

This CL aligns the precompiler to match the expectations of other
parts of the codebase. It also changes TFA to report an error
if a getter or setter is marked with the string argument 'call'.

[0] `Dart_Invoke` can be called with the name of a getter that
returns a closure, but doing so is semantically equivalent to
calling `Dart_GetField` followed by `Dart_InvokeClosure`.

TEST=vm/dart/entrypoint_verification_test

Fixes: https://github.com/dart-lang/sdk/issues/59920
Change-Id: Ia2768bbaf9058bb14a1cdfb331eb85fa082a0e90
Cq-Include-Trybots: luci.dart.try:vm-aot-dwarf-linux-product-x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-product-x64-try,vm-aot-mac-product-arm64-try,vm-aot-obfuscate-linux-release-x64-try,vm-linux-debug-x64-try,vm-linux-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404823
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2025-01-24 03:21:22 -08:00

304 lines
15 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 dartaotruntime.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_dartaotruntime = true;
bool IsTreeShaken(const char* name, Dart_Handle handle, const char* error) {
// No tree shaking in the JIT runtime.
if (!is_dartaotruntime) 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_dartaotruntime) { \
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)
#define TEST_GETTERS(target) \
do { \
FAIL("get1", Dart_GetField(target, Dart_NewStringFromCString("get1"))); \
CHECK(Dart_GetField(target, Dart_NewStringFromCString("get2"))); \
CHECK(Dart_GetField(target, Dart_NewStringFromCString("get3"))); \
} while (false)
#define TEST_SETTERS(target, value) \
do { \
FAIL("set1", \
Dart_SetField(target, Dart_NewStringFromCString("set1"), value)); \
CHECK(Dart_SetField(target, Dart_NewStringFromCString("set2"), value)); \
CHECK(Dart_SetField(target, Dart_NewStringFromCString("set3"), value)); \
} while (false)
DART_EXPORT void RunTests() {
is_dartaotruntime = 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);
//////// Test actions against getter and setter functions.
fprintf(stderr, "\n\nTesting getters with library target\n\n\n");
TEST_GETTERS(lib);
fprintf(stderr, "\n\nTesting getters with class target\n\n\n");
TEST_GETTERS(F_class);
fprintf(stderr, "\n\nTesting getters with instance target\n\n\n");
TEST_GETTERS(D);
Dart_Handle test_value =
Dart_GetField(lib, Dart_NewStringFromCString("testValue"));
CHECK(test_value);
fprintf(stderr, "\n\nTesting setters with library target\n\n\n");
TEST_SETTERS(lib, test_value);
fprintf(stderr, "\n\nTesting setters with class target\n\n\n");
TEST_SETTERS(F_class, test_value);
fprintf(stderr, "\n\nTesting setters with instance target\n\n\n");
TEST_SETTERS(D, test_value);
}