Repository navigation
Caliper v1.5.0
Analysis-engine release: a load mode that takes dependency types from export data, three correctness fixes in how packages and findings are classified, and a refusal to run under a toolchain newer than the binary.
Fixed
-
A load refuses a newer toolchain instead of answering wrongly (#35). go/packages asks for complete types for
every direct import of a package it type-checks from source, and fills
them from export data; when that read fails it callslog.Fatalffrom
inside its importer, which no caller can recover — an MCP server dies
with it. A go command from a newer release than the Go this program was
built with is exactly such a failure. The load now stops before
starting, with an error naming both versions and what to do about it.
Measured on steward with a caliper built by go1.26.7 against go1.27.1's
go list: before, the process exited 1 withinternal error: package "math/rand/v2" without types was imported from "google.golang.org/grpc/internal/transport"; now it returns the
refusal. The default load is refused too, for a different reason: it
completes under skew, but the older type checker cannot process the
newer toolchain's sources, so the same run left 15 of steward's 117
packages in the SSA program and reported no error — a call graph
missing 87% of the module. Unskewed behaviour is unchanged, 117 of 117
packages before and after, in both modes. Export data unreadable for
another reason, a truncated build cache say, can still reach the same
fatal; only the predictable cause is guarded. -
context_misuseno longer flags a detached context in a deferred
cleanup (#10). A deferredRollback,CloseorUnlockruns when
the function returns, including becausectxwas cancelled or timed
out, so passingctxthere skips the cleanup exactly when it is needed
most — pgx'sRollbackreturns at once on a done context and leaves
the connection in a transaction.context.Background()inside a
defer, whether deferred directly, inside a deferred closure, or as
the parent of aWithTimeoutthere, is the recommended idiom and is
not reported.context.TODO()in adeferstill is. Measured on the
repositories the issue was filed from: gameflow goes from 19 findings
to 1, and all 9 removed sites are deferred cleanups (four
tx.Rollbackin the trader store, a migrationRollback, a
conn.Close, an advisory-unlockExec, two deferred metric
decrements); govm's 5 and voya's 0 are unchanged. -
A deferred call is reported once, not twice. The CFG lists a defer
statement in the block that registers it and again in aBlockDefer
block where it runs, which is how a path query sees a deferred
UnlockorCloseon every exit; rules that scan nodes to report on
them saw both copies. On govm,unchecked_errorwithinclude_defers
gave 162 findings of which 8 were exact duplicates; on gameflow,
context_misusegave 19 of which 9 were. Both are now 0, with the
same unique findings (154 and 1 respectively). Affects
context_misuse,unchecked_error,goroutine_leakand
sql_injection;lock_violationis unchanged, since its loop feeds
path queries that need both copies. -
A dependency with a type error inside a function body no longer
drops its importers from the SSA program (#30). The program was
built withssautil.AllPackages, which skips every package go/packages
marksIllTyped— andIllTypedpropagates, so one dependency with
any error removed every loaded package importing it, transitively,
from the call graph, silently: the loaded package's own type-check was
clean and it stayed inEngine.Packages(). On steward, grpc's
internal/transportfails on go-plus 1.27.1 withundefined: http2.TrailerPrefixinside a function body, and 36 of 117 packages
had no SSA package and no call-graph nodes. Now a package is skipped
only for an error outside a function body — a parse error, a type
error in a declaration, export data that could not be read — or for
importing such a package; one whose errors are all inside function
bodies is created from its types alone, so its declarations are there
for its importers and its unfinished bodies never reach the SSA
builder. steward: 117 of 117 packages in the SSA program under the
default load, with the same 13,119 within-module edges as under
DepsFromExportData, edge for edge. Pinned on the fixture by a
dependency with a body-only error (importer analysed, calls its clean
function; the dependency's functions have no bodies) and one with a
declaration error (importer dropped, as before), under both loads.
An error is placed by its source position, so//linedirectives in
generated code cannot move a declaration error into a body; and the
notice go/packages adds when caliper was built with an older Go than
thegocommand it runs does not count as an error.
Default-load timing on steward is unchanged (load 1.6–1.7 s, call
graph 3.8–4.0 s over the now-complete 117 packages; one run of three
hit 11.4 s on a busy machine).
Added
-
EngineOptions.DepsFromExportDatatype-checks the loaded packages
from source and takes every dependency's types from the compiler's
export data (go list -export) instead of parsing and type-checking
its source. It dropspackages.NeedDepsfrom the load; under it the
SSA program is built with the loaded packages from syntax and every
dependency from types alone (#28, for trendvidia/alidade). Default
false, everything unchanged.Measured on steward (117 packages, 1,293 reached with dependencies,
warm build cache, three interleaved runs each, median and range):Load SSA + CHA call graph default 1.93 s (1.4–2.5) 3.46 s (2.5–5.1) DepsFromExportData0.99 s (0.98–1.3) 0.36 s (0.29–0.50) The first split load on a tree whose dependencies are not yet in the
build cache pays their compilation once (3.4 s on steward, where they
were).Limitation: no interprocedural analysis through dependency code. A
dependency's SSA functions have no bodies, so no call-graph edge leaves
one (steward: 8,150,734 such edges by default, 0 under the option) and
no taint flows through one. Calls from a loaded package into a
dependency are still edges to that function; dynamic calls resolve
among fewer candidates, since a package reached only through a
dependency carries just the declarations its importers reference
(steward: 176,011 edges into dependencies by default, 22,519 under the
option). A dependency that does not compile on the host's toolchain has
no export data and is type-checked from source with its bodies skipped;
it is still bodiless in the SSA program, and the loaded packages that
import it are analysed. Pinned byTestDepsFromExportData_*on a
fixture with a third-party dependency (github.com/google/uuid) and a
local one that type-checks but does not compile: the call-graph edges
among the loaded packages are identical under both modes, edge for
edge.On steward the within-module graph is not identical but a strict
superset: 13,119 edges over all 117 packages against 6,366 over 81,
because grpc'sinternal/transporthas a type error inside a function
body on this toolchain (undefined: http2.TrailerPrefix), the default
load sees it and drops the 36 packages importing it from the SSA
program, and the split load never type-checks that body. The 6,366 are
all present under the option and every extra edge touches one of the
36. The default load's drop is #30.