fix(globals): make package-level lookup state immutable - #27
Merged
Merged
Conversation
hammadmajid
force-pushed
the
fix/18-immutable-globals
branch
from
September 5, 2026 14:14
385d449 to
9327d6e
Compare
Three hardening items on mutable package-level state. None was a live bug; each was one edit away from becoming one. filter.BuiltinTypes was an exported mutable map. Nothing writes it today and both readers run single-threaded before the pipeline starts, but an exported map is a writable global and a FileTypeDef's Extensions/Filenames slices were writable through a read. A single `filter.BuiltinTypes[x] = ...` added later, plausibly for a --type-add flag, becomes a concurrent map write the moment type matching moves off the main goroutine -- a hard runtime crash the race detector does not warn about first. Unexported it to builtinTypes and added LookupType(name) (FileTypeDef, bool), which returns cloned slices so callers cannot reach the table. NewTypeMatcher and ListTypes now read through the accessors, making LookupType the sole read path. packMagic and idxV2Magic were mutable []byte globals used as constants. Any function in the package could overwrite their elements, and one stray copy(idxV2Magic, ...) would silently corrupt validation for every goroutine. They are now const strings. pack.go's comparison drops its redundant string(packMagic) conversion, and pack_index.go trades bytes.Equal for an allocation-free string comparison. The `unused` exclusion for "field zlibPool is unused" in .golangci.yml was stale: pooling landed and pack.go:328 and :347 both use p.zlibPool. It only suppressed hypothetical future findings on that path, and nolintlint does not police issues.exclusions, so it would never self-clean. Deleted. Cross-file note: internal/gitengine/pack_test.go:106 needed one line changed from Write to WriteString, since bytes.Buffer.Write cannot take the now-const idxV2Magic. That file otherwise belongs to another issue in this stack; Main assigned me the single line because the fix does not compile on the other branch, where idxV2Magic is still []byte. Verified with `go build ./...`, `go vet`, and `go test -race -count=1` on ./internal/filter/... and ./internal/gitengine/.... The new TestLookupTypeReturnsCopy was mutation-checked: reverting LookupType to return the table entry directly makes it fail. Closes #18
hammadmajid
force-pushed
the
fix/18-immutable-globals
branch
from
September 5, 2026 14:16
9327d6e to
c1836e6
Compare
hammadmajid
marked this pull request as ready for review
September 5, 2026 14:20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Three small hardening items on mutable package-level state. None is a live bug; each is one edit away from becoming one.
Closes #18
Type of Change
Key Changes
filter.BuiltinTypesunexported. An exported map is a writable global, and aFileTypeDef'sExtensions/Filenamesslices were writable through a read. Nothing writes it today and both readers run single-threaded before the pipeline starts — but onefilter.BuiltinTypes[x] = …added later, plausibly for a--type-addflag, becomes a concurrent map write the moment type matching moves off the main goroutine, which is a hard runtime crash rather than something-racewarns about first.Now
builtinTypes, withLookupType(name string) (FileTypeDef, bool)returningslices.Cloned slices.NewTypeMatcherroutes throughLookupTyperather than reading the table directly: a couple of tiny clones once per process (type names come from CLI args) buys a single read path that cannot rot into a second, slice-leaking convention.packMagic/idxV2Magicare nowconststrings. They were mutable[]byteglobals used as constants; any function in the package could overwrite the elements.pack.goalready compared as a string;pack_index.go'sbytes.Equalbecomes an allocation-freestring(data[:4]) != idxV2Magic.Deleted the stale
unusedexclusion in.golangci.ymlclaimingfield zlibPool is unused. Pooling landed —p.zlibPoolis used twice inpack.go.nolintlintdoes not policeissues.exclusions, so this would never have self-cleaned.Verification & Testing
go test -v -count=1 ./...go test -race -shuffle=on -count=1 ./...go vet ./...grep -rn BuiltinTypesover the whole tree returns nothing.TestLookupTypeReturnsCopymutates the returnedExtensions[0]/Filenames[0], re-looks-up to assert the builtin table is uncorrupted, asserts a matcher built afterwards still matchesDockerfileandmy.dockerfile, and asserts an unknown type reportsok=false. Mutation: revertingLookupTypetoreturn def, truewithout the clone fails all four assertions.One line of
pack_test.go(idxBuf.Write→WriteString) is included becausebytes.Buffer.Writecannot take a const string; it could not be pre-applied on the layer below, whereidxV2Magicis still[]byte.Note
Stacked on #26. Top of the stack at time of writing.
Checklist
gofmtclean