mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
Keep ICU symbols out of non-Intl binaries in the zero-cost gate
ICU builds with symbol renaming on, so its C API symbols carry the _78 version suffix, its data symbol is icudt78_dat, and its C++ symbols are Itanium manglings that name namespace icu_78. icuSymbols() classifies exactly those, and the gate's dynamic-tier check now fails a candidate that links any of them without the pattern being allowed: a program that does not lower to Intl links no ICU object at all. The engine-free Intl functions (scr_intl_num_format_en_us in scr_lib.c, scr_intl_segmenter_new in scr_grapheme.c) share the scr_intl_ prefix and are deliberately not part of the test; any scr_intl.c function that does link brings an ICU symbol with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
995eb54dbf
commit
7cf37d9dc9
@@ -393,6 +393,38 @@ export function dynamicTierSymbols(names) {
|
||||
* program without a call into Node.js may define any of these. */
|
||||
export const NODE_ISLAND_PREFIXES = ["scr_node_island", "scr_loop_set_node_island"];
|
||||
|
||||
/** The namespace ICU's own symbols live in after its version-suffixed
|
||||
* renaming: `icu_78`, which Itanium mangling spells `6icu_78`. */
|
||||
export const ICU_CPP_NAMESPACE = "6icu_78";
|
||||
|
||||
/** The ICU archive: no program that answers its Intl forms without
|
||||
* ICU — that is, every program whose IR has no `icu.*` libCall — may define
|
||||
* a symbol from it. The test is exact because ICU builds with symbol
|
||||
* renaming on: every C API symbol carries the `_78` version suffix
|
||||
* (`ubrk_open_78`), its data symbol is `icudt78_dat`, and every C++ symbol
|
||||
* is an Itanium mangling that names namespace `icu_78`, which the mangling
|
||||
* spells `6icu_78` wherever the name occurs — as the function's own
|
||||
* namespace (`_ZN6icu_78...`), in its parameter types, in vtables, typeinfo
|
||||
* and guard variables, or in a libc++ instantiation over an ICU type. No
|
||||
* symbol outside the archive carries either mark.
|
||||
*
|
||||
* scr_intl.c's own functions are deliberately not tested by name. The
|
||||
* engine-free Intl paths share their prefix (`scr_intl_num_format_en_us` in
|
||||
* scr_lib.c, `scr_intl_segmenter_new` in scr_grapheme.c), and they must stay
|
||||
* linkable without ICU (I4). Any scr_intl.c function that does link brings an
|
||||
* ICU symbol with it, which the test catches. The C++ standard library
|
||||
* merged into the archive cannot leak on its own either: an archive
|
||||
* contributes a member only when something references it, and only ICU's
|
||||
* objects reference libc++. */
|
||||
export function icuSymbols(names) {
|
||||
return [...new Set(names)]
|
||||
.filter(
|
||||
(name) =>
|
||||
name.endsWith("_78") || name.includes(ICU_CPP_NAMESPACE) || name.startsWith("icudt78"),
|
||||
)
|
||||
.sort();
|
||||
}
|
||||
|
||||
/** The Node island symbols among `names`, sorted. */
|
||||
export function nodeIslandSymbols(names) {
|
||||
return [...new Set(names)]
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
linkedRuntimeFiles,
|
||||
llvmFunctionHashes,
|
||||
median,
|
||||
icuSymbols,
|
||||
nodeIslandSymbols,
|
||||
nmOutput,
|
||||
parseNm,
|
||||
@@ -379,6 +380,10 @@ function dynamicTierCheck(program, baseline, candidate, artifacts, allowed) {
|
||||
const nodeIsland = nodeIslandSymbols(definedFunctions(candidate.exe));
|
||||
if (nodeIsland.length > 0 && !allowed)
|
||||
failures.push(`links Node island symbols: ${nodeIsland.slice(0, 5).join(", ")}`);
|
||||
// Intl over ICU is linked only into programs whose IR lowers to it. A
|
||||
// program that legitimately calls it names the pattern in --allow.
|
||||
const icu = icuSymbols(definedFunctions(candidate.exe));
|
||||
if (icu.length > 0 && !allowed) failures.push(`links ICU symbols: ${icu.slice(0, 5).join(", ")}`);
|
||||
if (typed && after.length > 0)
|
||||
failures.push(
|
||||
`fully typed program links dynamic-tier symbols: ${after.slice(0, 5).join(", ")}`,
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// The symbol classifier the zero-cost gate uses to prove that a program which
|
||||
// does not use Intl links no part of the ICU archive. The samples
|
||||
// are the shapes `nm` printed for the archive-linked binaries: ICU's C API
|
||||
// with its `_78` version suffix, its data symbol, and the several Itanium
|
||||
// manglings of names in namespace icu_78 — own namespace, parameters only,
|
||||
// internal linkage, guard variables, vtables and typeinfo.
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { icuSymbols } from "../../scripts/zero-cost-compare.mjs";
|
||||
|
||||
const ICU_SYMBOLS = [
|
||||
"ubrk_open_78",
|
||||
"unum_open_78",
|
||||
"u_strToUTF8_78",
|
||||
"icudt78_dat",
|
||||
"_ZN6icu_7813UnicodeString6appendEPKDsi",
|
||||
"_ZNK6icu_7824DateTimePatternGenerator17getDateTimeFormatE16UDateFormatStyleR10UErrorCode",
|
||||
"_Z24ulocimp_toLanguageTag_78PKcRN6icu_788ByteSinkEbR10UErrorCode",
|
||||
"_ZL10createPathPKciS0_iS0_RN6icu_7810CharStringEP10UErrorCode",
|
||||
"_ZGVZN6icu_7823IslamicUmalquraCalendar19handleComputeFieldsEiR10UErrorCodeE14kUmalquraStart",
|
||||
"_ZTWN6icu_7810CharStringE",
|
||||
"_ZTVN6icu_7813UnicodeStringE",
|
||||
"_ZTIN6icu_7813UnicodeStringE",
|
||||
"_ZTSN6icu_7813UnicodeStringE",
|
||||
// A libc++ instantiation over an ICU type exists only inside the archive.
|
||||
"_ZNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6appendERN6icu_7810CharStringE",
|
||||
];
|
||||
|
||||
// The engine-free Intl paths share the `scr_intl_` prefix and must stay
|
||||
// linkable without ICU (grapheme segmentation, the en-US number fast path),
|
||||
// so the classifier does not key on it.
|
||||
const NON_ICU_SYMBOLS = [
|
||||
"main",
|
||||
"scr_intl_num_format_en_us",
|
||||
"scr_intl_segmenter_new",
|
||||
"scr_intl_segmenter_refusing",
|
||||
"scr_dyn_call",
|
||||
"scr_string_concat",
|
||||
];
|
||||
|
||||
describe("icuSymbols", () => {
|
||||
it("classifies every ICU archive symbol", () => {
|
||||
expect(icuSymbols(ICU_SYMBOLS)).toEqual([...ICU_SYMBOLS].sort());
|
||||
});
|
||||
|
||||
it("leaves the engine-free runtime alone", () => {
|
||||
expect(icuSymbols(NON_ICU_SYMBOLS)).toEqual([]);
|
||||
});
|
||||
|
||||
it("deduplicates and sorts", () => {
|
||||
expect(icuSymbols(["ubrk_close_78", "main", "ubrk_close_78", "icudt78_dat"])).toEqual([
|
||||
"icudt78_dat",
|
||||
"ubrk_close_78",
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user