builder,loader: fix -ldflags -X not overriding variables with default values

When a string variable already had a source-level default value, the -ldflags
"-X" flag was silently ignored and the variable remained the default value
at runtime.

Fix by stripping the InitOrder entry for each -X variable before
LoadSSA() is called. With no entry in InitOrder, go/ssa emits no init
store, so the global stays zero-valued in the IR. makeGlobalsModule
still injects the actual -X values at final link time.

The -X values remain out of the per-package build cache with only the
variable names appearing in the cache key.

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram
2026-04-24 10:08:45 +02:00
committed by Ron Evans
parent 8793dc37fa
commit 1a1506ef79
6 changed files with 59 additions and 2 deletions
+12
View File
@@ -255,6 +255,18 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
result.PackagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path() result.PackagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
} }
// Strip default initializers for -X globals from the type info before
// building SSA. This prevents go/ssa from emitting init stores for them,
// so that makeGlobalsModule can supply the correct values at final link
// time without any runtime init overwriting them. The -X values themselves
// are kept out of the per-package build cache; only the variable names
// appear in the cache key.
for _, pkg := range lprogram.Sorted() {
for name := range globalValues[pkg.Pkg.Path()] {
pkg.StripVarInitializer(name)
}
}
// Create the *ssa.Program. This does not yet build the entire SSA of the // Create the *ssa.Program. This does not yet build the entire SSA of the
// program so it's pretty fast and doesn't need to be parallelized. // program so it's pretty fast and doesn't need to be parallelized.
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
+23
View File
@@ -304,6 +304,29 @@ func (p *Program) Sorted() []*Package {
return p.sorted return p.sorted
} }
// StripVarInitializer removes the package-level initializer for the named
// variable from the type info. This prevents go/ssa from emitting an init
// store for it, leaving the global zero-initialized in the IR. An external
// value (e.g. from -ldflags -X via makeGlobalsModule) can then be linked in
// at final link time without any runtime init overwriting it.
//
// Must be called after Parse() (typechecking populates InitOrder) and before
// LoadSSA() (which consumes InitOrder to build the package init function).
//
// Only 1:1 var initializers (var x = expr) are matched. Multi-variable
// initializers (var x, y = f()) are left untouched.
func (p *Package) StripVarInitializer(name string) {
n := 0
for _, init := range p.info.InitOrder {
if len(init.Lhs) == 1 && init.Lhs[0].Name() == name {
continue // drop this initializer
}
p.info.InitOrder[n] = init
n++
}
p.info.InitOrder = p.info.InitOrder[:n]
}
// MainPkg returns the last package in the Sorted() slice. This is the main // MainPkg returns the last package in the Sorted() slice. This is the main
// package of the program. // package of the program.
func (p *Program) MainPkg() *Package { func (p *Program) MainPkg() *Package {
+13
View File
@@ -155,6 +155,19 @@ func TestBuild(t *testing.T) {
} }
runTestWithConfig("ldflags.go", t, opts, nil, nil) runTestWithConfig("ldflags.go", t, opts, nil, nil)
}) })
// Same as ldflags, but the global has a default value in source.
// -ldflags -X must override it, matching standard Go behaviour.
t.Run("ldflags-initialized", func(t *testing.T) {
t.Parallel()
opts := optionsFromTarget("", sema)
opts.GlobalValues = map[string]map[string]string{
"main": {
"someGlobal": "foobar",
},
}
runTestWithConfig("ldflags-initialized.go", t, opts, nil, nil)
})
}) })
if testing.Short() { if testing.Short() {
+9
View File
@@ -0,0 +1,9 @@
package main
// This global has a default value. It should be overridable via
// -ldflags="-X main.someGlobal=value" just like an uninitialized global.
var someGlobal = "default"
func main() {
println("someGlobal:", someGlobal)
}
+1
View File
@@ -0,0 +1 @@
someGlobal: foobar
+1 -2
View File
@@ -1,7 +1,6 @@
package main package main
// These globals can be changed using -ldflags="-X main.someGlobal=value". // This global can be changed using -ldflags="-X main.someGlobal=value".
// At the moment, only globals without an initializer can be replaced this way.
var someGlobal string var someGlobal string
func main() { func main() {