@scriptc/compiler 0.0.32 → 0.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/cc-cache.test.js +308 -1
- package/dist/backend/cc-cache.test.js.map +1 -1
- package/dist/backend/cc.d.ts +6 -0
- package/dist/backend/cc.js +707 -68
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +53 -2
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +12 -5
- package/dist/backend/emission/emitter.js +160 -22
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/emission/may-throw.js +7 -4
- package/dist/backend/emission/may-throw.js.map +1 -1
- package/dist/backend/ffi-callbacks.d.ts +38 -1
- package/dist/backend/ffi-callbacks.js +76 -3
- package/dist/backend/ffi-callbacks.js.map +1 -1
- package/dist/backend/llvm/emitter.js +243 -12
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.js +2 -0
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/ffi/profile.d.ts +17 -5
- package/dist/ffi/profile.js +169 -22
- package/dist/ffi/profile.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +1 -0
- package/dist/frontend/lowering/lower-calls.js +89 -23
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.js +65 -19
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +12 -0
- package/dist/frontend/lowering/lowerer.js +20 -1
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/ts7/checker.d.ts +2 -0
- package/dist/frontend/ts7/checker.js +20 -3
- package/dist/frontend/ts7/checker.js.map +1 -1
- package/dist/frontend/types.d.ts +8 -0
- package/dist/frontend/types.js +33 -5
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +22 -9
- package/dist/ir/nodes.js +7 -1
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/serialize.d.ts +1 -1
- package/dist/ir/serialize.js +1 -1
- package/dist/ir/validate.js +74 -1
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/int-infer.test.js +4 -4
- package/dist/library/profile.d.ts +3 -0
- package/dist/library/profile.js +9 -1
- package/dist/library/profile.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
-
import { chmod, cp, mkdir, mkdtemp, readdir, readFile, rm, stat, utimes, writeFile } from "node:fs/promises";
|
|
4
|
+
import { chmod, cp, mkdir, mkdtemp, readdir, readFile, rm, stat, symlink, utimes, writeFile } from "node:fs/promises";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { delimiter, join } from "node:path";
|
|
7
7
|
import { afterAll, afterEach, expect, test as vitestTest } from "vitest";
|
|
@@ -203,6 +203,101 @@ test("the runtime fingerprint includes the textually included Ryū sources", asy
|
|
|
203
203
|
await utimes(join(ryuDir, "d2s.c"), pinnedTime, pinnedTime);
|
|
204
204
|
expect(await runtimeFingerprint(rtDir)).not.toBe(first);
|
|
205
205
|
});
|
|
206
|
+
test("the runtime fingerprint includes newly added nested headers", async () => {
|
|
207
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-fingerprint-nested-"));
|
|
208
|
+
scratch.push(dir);
|
|
209
|
+
const rtDir = join(dir, "src");
|
|
210
|
+
const ryuDir = join(dir, "vendor", "ryu");
|
|
211
|
+
await Promise.all([
|
|
212
|
+
mkdir(join(rtDir, "sys"), { recursive: true }),
|
|
213
|
+
mkdir(ryuDir, { recursive: true }),
|
|
214
|
+
]);
|
|
215
|
+
await writeFile(join(rtDir, "scr_number.c"), "int scriptc_probe;\n");
|
|
216
|
+
const first = await runtimeFingerprint(rtDir);
|
|
217
|
+
// A new file below an existing include root can win resolution without
|
|
218
|
+
// changing any path selected by the previous dependency scan.
|
|
219
|
+
await writeFile(join(rtDir, "sys", "types.h"), "#define SCRIPTC_SHADOW 1\n");
|
|
220
|
+
expect(await runtimeFingerprint(rtDir)).not.toBe(first);
|
|
221
|
+
});
|
|
222
|
+
test.skipIf(process.platform === "win32")("the runtime fingerprint follows symlinked source files", async () => {
|
|
223
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-fingerprint-symlink-"));
|
|
224
|
+
scratch.push(dir);
|
|
225
|
+
const rtDir = join(dir, "src");
|
|
226
|
+
const ryuDir = join(dir, "vendor", "ryu");
|
|
227
|
+
const target = join(dir, "runtime-target.h");
|
|
228
|
+
await Promise.all([
|
|
229
|
+
mkdir(rtDir, { recursive: true }),
|
|
230
|
+
mkdir(ryuDir, { recursive: true }),
|
|
231
|
+
writeFile(target, "#define SCRIPTC_SYMLINK 1\n"),
|
|
232
|
+
]);
|
|
233
|
+
await symlink(target, join(rtDir, "linked.h"));
|
|
234
|
+
const first = await runtimeFingerprint(rtDir);
|
|
235
|
+
await writeFile(target, "#define SCRIPTC_SYMLINK 2\n");
|
|
236
|
+
expect(await runtimeFingerprint(rtDir)).not.toBe(first);
|
|
237
|
+
});
|
|
238
|
+
test.skipIf(process.platform === "win32")("new nested runtime headers invalidate complete and output-local artifacts", async () => {
|
|
239
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-runtime-shadow-"));
|
|
240
|
+
scratch.push(dir);
|
|
241
|
+
const cacheRoot = join(dir, "cache");
|
|
242
|
+
const fakeRuntime = join(dir, "runtime", "src");
|
|
243
|
+
const projectDir = join(dir, "project");
|
|
244
|
+
const originalRuntime = runtimeSrcDir();
|
|
245
|
+
const cPath = join(projectDir, "program.c");
|
|
246
|
+
const firstOut = join(projectDir, "first");
|
|
247
|
+
const crossOutput = join(projectDir, "cross-output");
|
|
248
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
249
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
250
|
+
const oldRuntimeDir = process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
|
|
251
|
+
try {
|
|
252
|
+
await Promise.all([
|
|
253
|
+
cp(originalRuntime, fakeRuntime, { recursive: true }),
|
|
254
|
+
mkdir(projectDir),
|
|
255
|
+
mkdir(join(dir, "runtime", "vendor"), { recursive: true }).then(() => cp(join(originalRuntime, "..", "vendor", "ryu"), join(dir, "runtime", "vendor", "ryu"), { recursive: true })),
|
|
256
|
+
]);
|
|
257
|
+
await mkdir(join(fakeRuntime, "sys"));
|
|
258
|
+
const numberSource = join(fakeRuntime, "scr_number.c");
|
|
259
|
+
await writeFile(numberSource, `${await readFile(numberSource, "utf8")}\n` +
|
|
260
|
+
"#ifndef SCRIPTC_SHADOW_VALUE\n#define SCRIPTC_SHADOW_VALUE 1\n#endif\n" +
|
|
261
|
+
"int scriptc_shadow_value(void) { return SCRIPTC_SHADOW_VALUE; }\n");
|
|
262
|
+
await writeFile(cPath, "#include <stdio.h>\nint scriptc_shadow_value(void);\n" +
|
|
263
|
+
'int main(void) { printf("%d\\n", scriptc_shadow_value()); return 0; }\n');
|
|
264
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
265
|
+
process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"] = fakeRuntime;
|
|
266
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
267
|
+
await compileC({ cPath, outPath: firstOut, cacheIdentity: "scriptc-generated-v1" });
|
|
268
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("1");
|
|
269
|
+
// scr_runtime.h includes <sys/types.h>. This newly created header wins
|
|
270
|
+
// the existing -I runtime search without changing any dependency path
|
|
271
|
+
// selected during the first build.
|
|
272
|
+
await writeFile(join(fakeRuntime, "sys", "types.h"), "#include_next <sys/types.h>\n#define SCRIPTC_SHADOW_VALUE 2\n");
|
|
273
|
+
// A new output path bypasses the output-local stamp and probes the
|
|
274
|
+
// cross-output complete-artifact cache directly.
|
|
275
|
+
await compileC({
|
|
276
|
+
cPath,
|
|
277
|
+
outPath: crossOutput,
|
|
278
|
+
cacheIdentity: "scriptc-generated-v1",
|
|
279
|
+
});
|
|
280
|
+
expect(execFileSync(crossOutput, { encoding: "utf8" }).trim()).toBe("2");
|
|
281
|
+
// The original output has an output-local stamp from the first build;
|
|
282
|
+
// it must invalidate for the same namespace change.
|
|
283
|
+
await compileC({ cPath, outPath: firstOut, cacheIdentity: "scriptc-generated-v1" });
|
|
284
|
+
expect(execFileSync(firstOut, { encoding: "utf8" }).trim()).toBe("2");
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
if (oldCacheDir === undefined)
|
|
288
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
289
|
+
else
|
|
290
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
291
|
+
if (oldNoCache === undefined)
|
|
292
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
293
|
+
else
|
|
294
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
295
|
+
if (oldRuntimeDir === undefined)
|
|
296
|
+
delete process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"];
|
|
297
|
+
else
|
|
298
|
+
process.env["SCRIPTC_TEST_RUNTIME_SRC_DIR"] = oldRuntimeDir;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
206
301
|
test("implicit dependency seeds include separately compiled vendor system headers", async () => {
|
|
207
302
|
const includes = await implicitDependencyProbeIncludes(runtimeSrcDir());
|
|
208
303
|
// zlib's crc32.c is compiled independently and is not textually reachable
|
|
@@ -2283,6 +2378,218 @@ test("system libraries relink after an in-place rebuild while runtime objects re
|
|
|
2283
2378
|
process.env["LIBRARY_PATH"] = oldLibraryPath;
|
|
2284
2379
|
}
|
|
2285
2380
|
});
|
|
2381
|
+
test("frontend-generated same-output builds no-op only while output and dependency stamp are valid", async () => {
|
|
2382
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-local-artifact-"));
|
|
2383
|
+
scratch.push(dir);
|
|
2384
|
+
const cacheRoot = join(dir, "cache");
|
|
2385
|
+
const cPath = join(dir, "program.c");
|
|
2386
|
+
const outPath = join(dir, "program");
|
|
2387
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2388
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
2389
|
+
const oldUmask = process.umask();
|
|
2390
|
+
try {
|
|
2391
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
2392
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2393
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("valid"); return 0; }\n');
|
|
2394
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2395
|
+
const stampPath = join(cacheRoot, "local", createHash("sha256").update(outPath).digest("hex"));
|
|
2396
|
+
const stamp = JSON.parse(await readFile(stampPath, "utf8"));
|
|
2397
|
+
expect(stamp.dependencies.length).toBeGreaterThan(0);
|
|
2398
|
+
const pinnedTime = new Date("2001-01-01T00:00:00.000Z");
|
|
2399
|
+
await utimes(outPath, pinnedTime, pinnedTime);
|
|
2400
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2401
|
+
expect((await stat(outPath)).mtimeMs).toBe(pinnedTime.getTime());
|
|
2402
|
+
process.umask(0o077);
|
|
2403
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2404
|
+
expect((await stat(outPath)).mode & 0o777).toBe(0o700);
|
|
2405
|
+
process.umask(oldUmask);
|
|
2406
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2407
|
+
expect((await stat(outPath)).mode & 0o777).toBe(0o777 & ~oldUmask);
|
|
2408
|
+
// Generated TU bytes join the key: an actual source edit must replace the
|
|
2409
|
+
// output even though its path, runtime, and toolchain are unchanged.
|
|
2410
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("edited"); return 0; }\n');
|
|
2411
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2412
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("edited");
|
|
2413
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("valid"); return 0; }\n');
|
|
2414
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2415
|
+
// A damaged output cannot no-op even when every build input is unchanged.
|
|
2416
|
+
await writeFile(outPath, "damaged\n");
|
|
2417
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2418
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("valid");
|
|
2419
|
+
// The stamp is disposable cache data. A changed SDK/linker dependency
|
|
2420
|
+
// metadata record must take the strict CAS path, which reinstalls output.
|
|
2421
|
+
const damagedStamp = JSON.parse(await readFile(stampPath, "utf8"));
|
|
2422
|
+
damagedStamp.dependencies[0].size++;
|
|
2423
|
+
await writeFile(stampPath, `${JSON.stringify(damagedStamp)}\n`);
|
|
2424
|
+
await utimes(outPath, pinnedTime, pinnedTime);
|
|
2425
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2426
|
+
expect((await stat(outPath)).mtimeMs).toBeGreaterThan(pinnedTime.getTime());
|
|
2427
|
+
}
|
|
2428
|
+
finally {
|
|
2429
|
+
if (oldCacheDir === undefined)
|
|
2430
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2431
|
+
else
|
|
2432
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2433
|
+
if (oldNoCache === undefined)
|
|
2434
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2435
|
+
else
|
|
2436
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
2437
|
+
process.umask(oldUmask);
|
|
2438
|
+
}
|
|
2439
|
+
});
|
|
2440
|
+
test.skipIf(process.platform === "win32")("output-local hits follow symlinked header targets", async () => {
|
|
2441
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-local-artifact-symlink-"));
|
|
2442
|
+
scratch.push(dir);
|
|
2443
|
+
const cacheRoot = join(dir, "cache");
|
|
2444
|
+
const cPath = join(dir, "program.c");
|
|
2445
|
+
const header = join(dir, "value.h");
|
|
2446
|
+
const target = join(dir, "target.h");
|
|
2447
|
+
const outPath = join(dir, "program");
|
|
2448
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2449
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
2450
|
+
try {
|
|
2451
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
2452
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2453
|
+
await writeFile(target, '#define VALUE "one"\n');
|
|
2454
|
+
await symlink(target, header);
|
|
2455
|
+
await writeFile(cPath, '#include <stdio.h>\n#include "value.h"\nint main(void) { puts(VALUE); return 0; }\n');
|
|
2456
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2457
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("one");
|
|
2458
|
+
// clang's dependency file names the symlink path. The local stamp must
|
|
2459
|
+
// also follow that path and invalidate when only the target changes.
|
|
2460
|
+
await writeFile(target, '#define VALUE "two"\n');
|
|
2461
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2462
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("two");
|
|
2463
|
+
}
|
|
2464
|
+
finally {
|
|
2465
|
+
if (oldCacheDir === undefined)
|
|
2466
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2467
|
+
else
|
|
2468
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2469
|
+
if (oldNoCache === undefined)
|
|
2470
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2471
|
+
else
|
|
2472
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
2473
|
+
}
|
|
2474
|
+
});
|
|
2475
|
+
test.skipIf(process.platform === "win32")("output-local hits detect newly shadowing nested headers", async () => {
|
|
2476
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-local-artifact-shadow-"));
|
|
2477
|
+
scratch.push(dir);
|
|
2478
|
+
const cacheRoot = join(dir, "cache");
|
|
2479
|
+
const cPath = join(dir, "program.c");
|
|
2480
|
+
const outPath = join(dir, "program");
|
|
2481
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2482
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
2483
|
+
try {
|
|
2484
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
2485
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2486
|
+
await mkdir(join(dir, "sys"));
|
|
2487
|
+
await writeFile(cPath, '#include <stdio.h>\n#include "sys/param.h"\nint main(void) { printf("%d\\n", MAXPATHLEN); return 0; }\n');
|
|
2488
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2489
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).not.toBe("7");
|
|
2490
|
+
// The source directory itself does not necessarily change metadata when
|
|
2491
|
+
// a child directory gains a file. Its recursive namespace digest must.
|
|
2492
|
+
await writeFile(join(dir, "sys", "param.h"), "#define MAXPATHLEN 7\n");
|
|
2493
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2494
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("7");
|
|
2495
|
+
}
|
|
2496
|
+
finally {
|
|
2497
|
+
if (oldCacheDir === undefined)
|
|
2498
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2499
|
+
else
|
|
2500
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2501
|
+
if (oldNoCache === undefined)
|
|
2502
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2503
|
+
else
|
|
2504
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
2505
|
+
}
|
|
2506
|
+
});
|
|
2507
|
+
test("fresh processes preserve output-local dependency coverage after source edits", async () => {
|
|
2508
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-local-artifact-process-"));
|
|
2509
|
+
scratch.push(dir);
|
|
2510
|
+
const cacheRoot = join(dir, "cache");
|
|
2511
|
+
const cPath = join(dir, "program.c");
|
|
2512
|
+
const outPath = join(dir, "program");
|
|
2513
|
+
const helperPath = join(dir, "compile.mjs");
|
|
2514
|
+
const childEnv = { ...process.env, SCRIPTC_CACHE_DIR: cacheRoot };
|
|
2515
|
+
delete childEnv["SCRIPTC_NO_CACHE"];
|
|
2516
|
+
delete childEnv["SCRIPTC_TEST_STABLE_TOOLCHAIN"];
|
|
2517
|
+
delete childEnv["SCRIPTC_TEST_TRUST_COMPILER_WRAPPER"];
|
|
2518
|
+
await writeFile(helperPath, `import { compileC } from ${JSON.stringify(new URL("./cc.ts", import.meta.url).href)};\n` +
|
|
2519
|
+
`await compileC({ cPath: process.argv[2], outPath: process.argv[3], cacheIdentity: "scriptc-generated-v1" });\n`);
|
|
2520
|
+
const compileInFreshProcess = () => {
|
|
2521
|
+
execFileSync(process.execPath, ["--import", "tsx", helperPath, cPath, outPath], {
|
|
2522
|
+
env: childEnv,
|
|
2523
|
+
stdio: "pipe",
|
|
2524
|
+
});
|
|
2525
|
+
};
|
|
2526
|
+
const stampPath = join(cacheRoot, "local", createHash("sha256").update(outPath).digest("hex"));
|
|
2527
|
+
const dependencyPaths = async () => {
|
|
2528
|
+
const stamp = JSON.parse(await readFile(stampPath, "utf8"));
|
|
2529
|
+
return stamp.dependencies.map((dependency) => dependency.path);
|
|
2530
|
+
};
|
|
2531
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("one"); return 0; }\n');
|
|
2532
|
+
compileInFreshProcess();
|
|
2533
|
+
const firstPaths = await dependencyPaths();
|
|
2534
|
+
expect(firstPaths.length).toBeGreaterThan(2);
|
|
2535
|
+
// The metadata fingerprints are restored from cache files in this second
|
|
2536
|
+
// Node process; their in-memory fingerprint-to-path map starts empty.
|
|
2537
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("two"); return 0; }\n');
|
|
2538
|
+
compileInFreshProcess();
|
|
2539
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("two");
|
|
2540
|
+
expect(await dependencyPaths()).toEqual(firstPaths);
|
|
2541
|
+
});
|
|
2542
|
+
test("native metadata snapshots survive source edits and repair after tampering", async () => {
|
|
2543
|
+
const dir = await mkdtemp(join(tmpdir(), "scriptc-native-metadata-"));
|
|
2544
|
+
scratch.push(dir);
|
|
2545
|
+
const cacheRoot = join(dir, "cache");
|
|
2546
|
+
const cPath = join(dir, "program.c");
|
|
2547
|
+
const outPath = join(dir, "program");
|
|
2548
|
+
const oldCacheDir = process.env["SCRIPTC_CACHE_DIR"];
|
|
2549
|
+
const oldNoCache = process.env["SCRIPTC_NO_CACHE"];
|
|
2550
|
+
try {
|
|
2551
|
+
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
|
2552
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2553
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("one"); return 0; }\n');
|
|
2554
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2555
|
+
const metadataDir = join(cacheRoot, "meta");
|
|
2556
|
+
const metadataNames = await readdir(metadataDir);
|
|
2557
|
+
expect(metadataNames.length).toBeGreaterThanOrEqual(3);
|
|
2558
|
+
const pinnedTime = new Date("2002-01-01T00:00:00.000Z");
|
|
2559
|
+
await Promise.all(metadataNames.map((name) => utimes(join(metadataDir, name), pinnedTime, pinnedTime)));
|
|
2560
|
+
// Program bytes are not toolchain identity. A source edit should reuse the
|
|
2561
|
+
// validated target/compiler/link snapshots without republishing them.
|
|
2562
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("two"); return 0; }\n');
|
|
2563
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2564
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("two");
|
|
2565
|
+
for (const name of metadataNames) {
|
|
2566
|
+
expect((await stat(join(metadataDir, name))).mtimeMs).toBe(pinnedTime.getTime());
|
|
2567
|
+
}
|
|
2568
|
+
// Snapshot files are disposable cache data. Tampering invalidates one,
|
|
2569
|
+
// strict discovery repairs it, and the requested source edit still lands.
|
|
2570
|
+
const damagedPath = join(metadataDir, metadataNames[0]);
|
|
2571
|
+
const damaged = JSON.parse(await readFile(damagedPath, "utf8"));
|
|
2572
|
+
damaged.integrity = "0".repeat(64);
|
|
2573
|
+
await writeFile(damagedPath, `${JSON.stringify(damaged)}\n`);
|
|
2574
|
+
await utimes(damagedPath, pinnedTime, pinnedTime);
|
|
2575
|
+
await writeFile(cPath, '#include <stdio.h>\nint main(void) { puts("three"); return 0; }\n');
|
|
2576
|
+
await compileC({ cPath, outPath, cacheIdentity: "scriptc-generated-v1" });
|
|
2577
|
+
expect(execFileSync(outPath, { encoding: "utf8" }).trim()).toBe("three");
|
|
2578
|
+
expect((await stat(damagedPath)).mtimeMs).toBeGreaterThan(pinnedTime.getTime());
|
|
2579
|
+
const repaired = JSON.parse(await readFile(damagedPath, "utf8"));
|
|
2580
|
+
expect(repaired.integrity).not.toBe("0".repeat(64));
|
|
2581
|
+
}
|
|
2582
|
+
finally {
|
|
2583
|
+
if (oldCacheDir === undefined)
|
|
2584
|
+
delete process.env["SCRIPTC_CACHE_DIR"];
|
|
2585
|
+
else
|
|
2586
|
+
process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
|
2587
|
+
if (oldNoCache === undefined)
|
|
2588
|
+
delete process.env["SCRIPTC_NO_CACHE"];
|
|
2589
|
+
else
|
|
2590
|
+
process.env["SCRIPTC_NO_CACHE"] = oldNoCache;
|
|
2591
|
+
}
|
|
2592
|
+
});
|
|
2286
2593
|
test("damaged complete artifacts are rejected and rebuilt", async () => {
|
|
2287
2594
|
const dir = await mkdtemp(join(tmpdir(), "scriptc-artifact-integrity-"));
|
|
2288
2595
|
scratch.push(dir);
|