fix(go): distinguish dependency coverage from parse completeness
Explain unknown dependency coverage when embedded metadata has no main-module record. Preserve decoded dependencies and distinguish GOPATH-style omissions from observed module-mode empty lists. Refs #4191, #4267 Co-authored-by: Y0UW1SH <29498139+Y0UW1SH@users.noreply.github.com>
This commit is contained in:
1 parent
79e83cbd67
commit
a05092726f
4 files changed
+115
-23
No files matched your search
+9
-2
@@ -39,8 +39,8 @@ container format, architecture, word size and byte order. A recognized
|
||||
- `module.dependencies`, with the same fields and their original order;
|
||||
- `module.settings`, retaining ordered key/value entries and duplicate keys;
|
||||
- `module.unparsed_lines`, `module.unparsed_line_bytes_base64` and
|
||||
`module.complete`, so unfamiliar, malformed, conflicting or undecodable records
|
||||
do not disappear behind a complete result.
|
||||
`module.complete`, which says whether every present module-text line was
|
||||
parsed. It does not establish that the dependency inventory is complete.
|
||||
|
||||
Missing checksums are `null`; empty fields and development/local replacement
|
||||
versions remain unchanged. Build settings such as `GOOS`, `GOARCH` or `vcs.revision`
|
||||
@@ -50,6 +50,13 @@ base64 while other decodable module records are still reported; the reader does
|
||||
not insert replacement characters or reject the entire artifact for those
|
||||
strings.
|
||||
|
||||
When no main-module (`mod`) record was decoded, `module.main` is `null` and
|
||||
dependency coverage is unknown. Successfully decoded dependency records remain
|
||||
visible. GOPATH-mode builds may contain only a package `path` and build settings;
|
||||
their empty dependency array does not establish the absence of dependencies.
|
||||
The result's limitations state this explicitly. A module-mode build with a main
|
||||
module record and no dependency records has an observed empty dependency list.
|
||||
|
||||
## Supported boundary
|
||||
|
||||
The built-in TypeScript reader supports ELF, PE and thin Mach-O images with Go's
|
||||
|
||||
+32
-21
@@ -17,6 +17,7 @@ import {
|
||||
import {
|
||||
goBinarySchema,
|
||||
type GoBinary,
|
||||
type GoModuleMetadata,
|
||||
type InspectGoBinaryInput,
|
||||
} from "../domain/go/goBinary.js";
|
||||
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
||||
@@ -138,6 +139,36 @@ const outputLimit = (path: string) =>
|
||||
{ maximum_output_bytes: OUTPUT_BYTES },
|
||||
);
|
||||
|
||||
/** Fixed reader boundaries plus unknowns left by the observed metadata. */
|
||||
const limitationsFor = (
|
||||
info: { go_version: string | null; module_text: string | null } | null,
|
||||
module: GoModuleMetadata | null,
|
||||
): string[] => [
|
||||
"Embedded build metadata is observed file content, not proof of compiler identity, dependency authenticity or runtime behavior.",
|
||||
"Only Go build information is inspected; function names, source mappings, type metadata and decompiled code are not recovered.",
|
||||
"Resource guards bound stable artifact reads and cumulative header searches to 256 MiB each, aggregate embedded strings to 1 MiB, structural table decoding to 16 MiB and complete metadata payloads to 8 MiB; these are not operating-system memory or CPU limits.",
|
||||
...(info !== null && (info.go_version === null || info.module_text === null)
|
||||
? [
|
||||
"Some embedded Go strings contain non-UTF8 bytes; undecodable text is null and exact source bytes are preserved as base64.",
|
||||
]
|
||||
: []),
|
||||
...(info === null
|
||||
? [
|
||||
"No recognized Go build-info record was found in the inspected image; absent metadata does not establish that the binary is not Go.",
|
||||
]
|
||||
: []),
|
||||
...(module !== null && module.main === null
|
||||
? [
|
||||
"Dependency coverage is unknown because no main-module record was decoded from the embedded module information, as in GOPATH-mode builds. Decoded dependency records remain visible; an empty list does not establish the absence of dependencies.",
|
||||
]
|
||||
: []),
|
||||
...(module?.complete === false
|
||||
? [
|
||||
"Some embedded module-text records could not be parsed completely; their original lines are preserved and additional metadata remains unknown.",
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
/** Stable in-process metadata inspection without subprocesses, temporary files or Go installation. */
|
||||
export class GoBinaryProvider implements GoBinaryPort {
|
||||
readonly identity = GO_BINARY_PROVIDER_IDENTITY;
|
||||
@@ -191,27 +222,7 @@ export class GoBinaryProvider implements GoBinaryPort {
|
||||
bytes: snapshot.bytes.length,
|
||||
},
|
||||
build_info: info === null ? null : { ...info, module },
|
||||
limitations: [
|
||||
"Embedded build metadata is observed file content, not proof of compiler identity, dependency authenticity or runtime behavior.",
|
||||
"Only Go build information is inspected; function names, source mappings, type metadata and decompiled code are not recovered.",
|
||||
"Resource guards bound stable artifact reads and cumulative header searches to 256 MiB each, aggregate embedded strings to 1 MiB, structural table decoding to 16 MiB and complete metadata payloads to 8 MiB; these are not operating-system memory or CPU limits.",
|
||||
...(info !== null &&
|
||||
(info.go_version === null || info.module_text === null)
|
||||
? [
|
||||
"Some embedded Go strings contain non-UTF8 bytes; undecodable text is null and exact source bytes are preserved as base64.",
|
||||
]
|
||||
: []),
|
||||
...(info === null
|
||||
? [
|
||||
"No recognized Go build-info record was found in the inspected image; absent metadata does not establish that the binary is not Go.",
|
||||
]
|
||||
: []),
|
||||
...(module?.complete === false
|
||||
? [
|
||||
"Some embedded module-text records could not be parsed completely; their original lines are preserved and additional metadata remains unknown.",
|
||||
]
|
||||
: []),
|
||||
],
|
||||
limitations: limitationsFor(info, module),
|
||||
};
|
||||
if (Buffer.byteLength(JSON.stringify(report), "utf8") > OUTPUT_BYTES)
|
||||
throw outputLimit(input.path);
|
||||
|
||||
@@ -284,3 +284,33 @@ cliTest(
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"returns unknown dependency coverage for parse-complete GOPATH metadata in CLI Evidence",
|
||||
async ({ cli }) => {
|
||||
const moduleText =
|
||||
"path\texample.com/tool/cmd/tool\n" +
|
||||
"build\t-compiler=gc\n" +
|
||||
"build\tGOOS=linux\n";
|
||||
const root = await createTestTempDirectory("rea-go-gopath-cli-");
|
||||
const path = join(root, "application.elf");
|
||||
await writeFile(path, createGoBinaryFixture({ moduleText }).bytes);
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-go-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const evidence = toolContract("inspect_go_binary").outputSchema.parse(
|
||||
result.json,
|
||||
);
|
||||
expect(evidence.normalized_result.build_info?.module).toMatchObject({
|
||||
path: "example.com/tool/cmd/tool",
|
||||
main: null,
|
||||
dependencies: [],
|
||||
complete: true,
|
||||
unparsed_lines: [],
|
||||
});
|
||||
expect(evidence.limitations.join(" ")).toContain(
|
||||
"Dependency coverage is unknown",
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -82,3 +82,47 @@ it.skipIf(process.platform === "win32")(
|
||||
if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisInputError);
|
||||
},
|
||||
);
|
||||
|
||||
it("reports a parse-complete GOPATH module text as unknown dependency coverage", async () => {
|
||||
const moduleText =
|
||||
"path\texample.com/tool/cmd/tool\n" +
|
||||
"build\t-compiler=gc\n" +
|
||||
"build\tGOOS=linux\n";
|
||||
const root = await createTestTempDirectory("rea-go-gopath-metadata-");
|
||||
const path = join(root, "application.elf");
|
||||
await writeFile(path, createGoBinaryFixture({ moduleText }).bytes);
|
||||
const result = await new GoBinaryProvider().inspect({ path });
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.build_info?.module_text).toBe(moduleText);
|
||||
expect(result.value.build_info?.module).toMatchObject({
|
||||
path: "example.com/tool/cmd/tool",
|
||||
main: null,
|
||||
dependencies: [],
|
||||
complete: true,
|
||||
unparsed_lines: [],
|
||||
});
|
||||
expect(result.value.limitations.join(" ")).toContain(
|
||||
"Dependency coverage is unknown",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps a module-mode build with no dependency records as an observed empty list", async () => {
|
||||
const moduleText =
|
||||
"path\texample.com/tool\n" +
|
||||
"mod\texample.com/tool\t(devel)\t\n" +
|
||||
"build\tGOOS=linux\n";
|
||||
const root = await createTestTempDirectory("rea-go-empty-module-deps-");
|
||||
const path = join(root, "application.elf");
|
||||
await writeFile(path, createGoBinaryFixture({ moduleText }).bytes);
|
||||
const result = await new GoBinaryProvider().inspect({ path });
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.build_info?.module_text).toBe(moduleText);
|
||||
expect(result.value.build_info?.module).toMatchObject({
|
||||
main: { path: "example.com/tool", version: "(devel)" },
|
||||
dependencies: [],
|
||||
complete: true,
|
||||
});
|
||||
expect(result.value.limitations.join(" ")).not.toContain(
|
||||
"Dependency coverage is unknown",
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user