From 9b76be9e9e45354ca45969e8b80a388772d718df Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 14 Nov 2025 11:56:34 +0800 Subject: [PATCH 01/12] support ldflags rewrites for initialized globals --- cl/_testgo/rewrite/dep/dep.go | 11 +++ cl/_testgo/rewrite/main.go | 23 +++++ cl/_testgo/rewrite/out.ll | 1 + cl/compile.go | 58 ++++++++++++- internal/build/build.go | 154 ++++++++++++++++------------------ internal/build/build_test.go | 66 ++++++++++++++- ssa/globals.go | 10 +++ 7 files changed, 239 insertions(+), 84 deletions(-) create mode 100644 cl/_testgo/rewrite/dep/dep.go create mode 100644 cl/_testgo/rewrite/main.go create mode 100644 cl/_testgo/rewrite/out.ll diff --git a/cl/_testgo/rewrite/dep/dep.go b/cl/_testgo/rewrite/dep/dep.go new file mode 100644 index 00000000..6dc2e879 --- /dev/null +++ b/cl/_testgo/rewrite/dep/dep.go @@ -0,0 +1,11 @@ +package dep + +import "fmt" + +var VarName = "dep-default" +var VarPlain string + +func PrintVar() { + fmt.Printf("dep.VarName: %s\n", VarName) + fmt.Printf("dep.VarPlain: %s\n", VarPlain) +} diff --git a/cl/_testgo/rewrite/main.go b/cl/_testgo/rewrite/main.go new file mode 100644 index 00000000..4bd616e4 --- /dev/null +++ b/cl/_testgo/rewrite/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "runtime" + + dep "github.com/goplus/llgo/cl/_testgo/rewrite/dep" +) + +var VarName = "main-default" +var VarPlain string + +func printLine(label, value string) { + fmt.Printf("%s: %s\n", label, value) +} + +func main() { + printLine("main.VarName", VarName) + printLine("main.VarPlain", VarPlain) + dep.PrintVar() + printLine("runtime.GOROOT()", runtime.GOROOT()) + printLine("runtime.Version()", runtime.Version()) +} diff --git a/cl/_testgo/rewrite/out.ll b/cl/_testgo/rewrite/out.ll new file mode 100644 index 00000000..1c8a0e79 --- /dev/null +++ b/cl/_testgo/rewrite/out.ll @@ -0,0 +1 @@ +; \ No newline at end of file diff --git a/cl/compile.go b/cl/compile.go index 082b34b5..d239bb07 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -127,6 +127,50 @@ type context struct { cgoArgs []llssa.Expr cgoRet llssa.Expr cgoSymbols []string + rewrites map[string]string +} + +func (p *context) rewriteValue(name string) (string, bool) { + if p.rewrites == nil { + return "", false + } + val, ok := p.rewrites[name] + return val, ok +} + +func (p *context) isStringType(typ types.Type) bool { + for typ != nil { + switch t := typ.Underlying().(type) { + case *types.Basic: + return t.Kind() == types.String + case *types.Pointer: + typ = t.Elem() + continue + default: + return false + } + } + return false +} + +func (p *context) globalFullName(g *ssa.Global) string { + name, _, _ := p.varName(g.Pkg.Pkg, g) + return name +} + +func (p *context) rewriteInitStore(store *ssa.Store, g *ssa.Global) (string, bool) { + fn := store.Block().Parent() + if fn == nil || fn.Synthetic != "package initializer" { + return "", false + } + value, ok := p.rewriteValue(p.globalFullName(g)) + if !ok || !p.isStringType(g.Type()) { + return "", false + } + if _, ok := store.Val.(*ssa.Const); !ok { + return "", false + } + return value, true } type pkgState byte @@ -176,7 +220,9 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) { log.Println("==> NewVar", name, typ) } g := pkg.NewVar(name, typ, llssa.Background(vtype)) - if define { + if value, ok := p.rewriteValue(name); ok && p.isStringType(typ) { + g.Init(pkg.ConstString(value)) + } else if define { g.InitNil() } } @@ -816,6 +862,11 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { return } } + if g, ok := va.(*ssa.Global); ok { + if _, ok := p.rewriteInitStore(v, g); ok { + return + } + } ptr := p.compileValue(b, va) val := p.compileValue(b, v.Val) b.Store(ptr, val) @@ -980,12 +1031,12 @@ type Patches = map[string]Patch // NewPackage compiles a Go package to LLVM IR package. func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) { - ret, _, err = NewPackageEx(prog, nil, pkg, files) + ret, _, err = NewPackageEx(prog, nil, nil, pkg, files) return } // NewPackageEx compiles a Go package to LLVM IR package. -func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs []string, err error) { +func NewPackageEx(prog llssa.Program, patches Patches, rewrites map[string]string, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs []string, err error) { pkgProg := pkg.Prog pkgTypes := pkg.Pkg oldTypes := pkgTypes @@ -1018,6 +1069,7 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [ types.Unsafe: {kind: PkgDeclOnly}, // TODO(xsw): PkgNoInit or PkgDeclOnly? }, cgoSymbols: make([]string, 0, 128), + rewrites: rewrites, } ctx.initPyModule() ctx.initFiles(pkgPath, files, pkgName == "C") diff --git a/internal/build/build.go b/internal/build/build.go index 1147d495..0de41c43 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -111,29 +111,28 @@ type OutFmtDetails struct { } type Config struct { - Goos string - Goarch string - Target string // target name (e.g., "rp2040", "wasi") - takes precedence over Goos/Goarch - BinPath string - AppExt string // ".exe" on Windows, empty on Unix - OutFile string // only valid for ModeBuild when len(pkgs) == 1 - OutFmts OutFmts // Output format specifications (only for Target != "") - Emulator bool // run in emulator mode - Port string // target port for flashing - BaudRate int // baudrate for serial communication - RunArgs []string - Mode Mode - BuildMode BuildMode // Build mode: exe, c-archive, c-shared - AbiMode AbiMode - GenExpect bool // only valid for ModeCmpTest - Verbose bool - GenLL bool // generate pkg .ll files - CheckLLFiles bool // check .ll files valid - CheckLinkArgs bool // check linkargs valid - ForceEspClang bool // force to use esp-clang - Tags string - GlobalNames map[string][]string // pkg => names - GlobalDatas map[string]string // pkg.name => data + Goos string + Goarch string + Target string // target name (e.g., "rp2040", "wasi") - takes precedence over Goos/Goarch + BinPath string + AppExt string // ".exe" on Windows, empty on Unix + OutFile string // only valid for ModeBuild when len(pkgs) == 1 + OutFmts OutFmts // Output format specifications (only for Target != "") + Emulator bool // run in emulator mode + Port string // target port for flashing + BaudRate int // baudrate for serial communication + RunArgs []string + Mode Mode + BuildMode BuildMode // Build mode: exe, c-archive, c-shared + AbiMode AbiMode + GenExpect bool // only valid for ModeCmpTest + Verbose bool + GenLL bool // generate pkg .ll files + CheckLLFiles bool // check .ll files valid + CheckLinkArgs bool // check linkargs valid + ForceEspClang bool // force to use esp-clang + Tags string + GlobalRewrites map[string]string // pkg.name => data } func NewDefaultConf(mode Mode) *Config { @@ -335,6 +334,10 @@ func Do(args []string, conf *Config) ([]Package, error) { crossCompile: export, cTransformer: cabi.NewTransformer(prog, export.LLVMTarget, export.TargetABI, conf.AbiMode, cabiOptimize), } + + // default runtime globals must be registered before packages are built + addDefaultGlobalString(conf, "runtime.defaultGOROOT="+runtime.GOROOT(), nil) + addDefaultGlobalString(conf, "runtime.buildVersion="+runtime.Version(), nil) pkgs, err := buildAllPkgs(ctx, initial, verbose) check(err) if mode == ModeGen { @@ -351,13 +354,6 @@ func Do(args []string, conf *Config) ([]Package, error) { allPkgs := append([]*aPackage{}, pkgs...) allPkgs = append(allPkgs, dpkg...) - // update globals importpath.name=value - addGlobalString(conf, "runtime.defaultGOROOT="+runtime.GOROOT(), nil) - addGlobalString(conf, "runtime.buildVersion="+runtime.Version(), nil) - - global, err := createGlobals(ctx, ctx.prog, pkgs) - check(err) - for _, pkg := range initial { if needLink(pkg, mode) { name := path.Base(pkg.PkgPath) @@ -369,7 +365,7 @@ func Do(args []string, conf *Config) ([]Package, error) { } // Link main package using the output path from buildOutFmts - err = linkMainPkg(ctx, pkg, allPkgs, global, outFmts.Out, verbose) + err = linkMainPkg(ctx, pkg, allPkgs, outFmts.Out, verbose) if err != nil { return nil, err } @@ -630,6 +626,14 @@ var ( ) func addGlobalString(conf *Config, arg string, mainPkgs []string) { + addGlobalStringWith(conf, arg, mainPkgs, false) +} + +func addDefaultGlobalString(conf *Config, arg string, mainPkgs []string) { + addGlobalStringWith(conf, arg, mainPkgs, true) +} + +func addGlobalStringWith(conf *Config, arg string, mainPkgs []string, skipIfExists bool) { eq := strings.Index(arg, "=") dot := strings.LastIndex(arg[:eq+1], ".") if eq < 0 || dot < 0 { @@ -640,47 +644,23 @@ func addGlobalString(conf *Config, arg string, mainPkgs []string) { if pkg == "main" { pkgs = mainPkgs } - if conf.GlobalNames == nil { - conf.GlobalNames = make(map[string][]string) + if len(pkgs) == 0 { + return } - if conf.GlobalDatas == nil { - conf.GlobalDatas = make(map[string]string) + if conf.GlobalRewrites == nil { + conf.GlobalRewrites = make(map[string]string) } - for _, pkg := range pkgs { - name := pkg + arg[dot:eq] - value := arg[eq+1:] - if _, ok := conf.GlobalDatas[name]; !ok { - conf.GlobalNames[pkg] = append(conf.GlobalNames[pkg], name) - } - conf.GlobalDatas[name] = value - } -} - -func createGlobals(ctx *context, prog llssa.Program, pkgs []*aPackage) (llssa.Package, error) { - if len(ctx.buildConf.GlobalDatas) == 0 { - return nil, nil - } - for _, pkg := range pkgs { - if pkg.ExportFile == "" { - continue - } - if names, ok := ctx.buildConf.GlobalNames[pkg.PkgPath]; ok { - err := pkg.LPkg.Undefined(names...) - if err != nil { - return nil, err - } - pkg.ExportFile += "-global" - pkg.ExportFile, err = exportObject(ctx, pkg.PkgPath+".global", pkg.ExportFile, []byte(pkg.LPkg.String())) - if err != nil { - return nil, err + suffix := arg[dot:eq] + value := arg[eq+1:] + for _, realPkg := range pkgs { + name := realPkg + suffix + if skipIfExists { + if _, exists := conf.GlobalRewrites[name]; exists { + continue } } + conf.GlobalRewrites[name] = value } - global := prog.NewPackage("", "global") - for name, value := range ctx.buildConf.GlobalDatas { - global.AddGlobalString(name, value) - } - return global, nil } // compileExtraFiles compiles extra files (.s/.c) from target configuration and returns object files @@ -752,7 +732,7 @@ func compileExtraFiles(ctx *context, verbose bool) ([]string, error) { return objFiles, nil } -func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global llssa.Package, outputPath string, verbose bool) error { +func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, outputPath string, verbose bool) error { needRuntime := false needPyInit := false @@ -794,14 +774,6 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l } objFiles = append(objFiles, extraObjFiles...) - if global != nil { - export, err := exportObject(ctx, pkg.PkgPath+".global", pkg.ExportFile+"-global", []byte(global.String())) - if err != nil { - return err - } - objFiles = append(objFiles, export) - } - if IsFullRpathEnabled() { // Treat every link-time library search path, specified by the -L parameter, as a runtime search path as well. // This is to ensure the final executable can locate libraries with a relocatable install_name @@ -1050,7 +1022,7 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error { cl.SetDebug(cl.DbgFlagAll) } - ret, externs, err := cl.NewPackageEx(ctx.prog, ctx.patches, aPkg.SSA, syntax) + ret, externs, err := cl.NewPackageEx(ctx.prog, ctx.patches, aPkg.rewriteVars, aPkg.SSA, syntax) if showDetail { llssa.SetDebug(0) cl.SetDebug(0) @@ -1171,8 +1143,9 @@ type aPackage struct { AltPkg *packages.Cached LPkg llssa.Package - LinkArgs []string - LLFiles []string + LinkArgs []string + LLFiles []string + rewriteVars map[string]string } type Package = *aPackage @@ -1193,7 +1166,8 @@ func allPkgs(ctx *context, initial []*packages.Package, verbose bool) (all []*aP return } } - all = append(all, &aPackage{p, ssaPkg, altPkg, nil, nil, nil}) + rewrites := collectRewriteVars(ctx, pkgPath) + all = append(all, &aPackage{p, ssaPkg, altPkg, nil, nil, nil, rewrites}) } else { errs = append(errs, p) } @@ -1201,6 +1175,26 @@ func allPkgs(ctx *context, initial []*packages.Package, verbose bool) (all []*aP return } +func collectRewriteVars(ctx *context, pkgPath string) map[string]string { + var rewrites map[string]string + add := func(name, value string) { + if rewrites == nil { + rewrites = make(map[string]string) + } + rewrites[name] = value + } + if data := ctx.buildConf.GlobalRewrites; len(data) != 0 { + prefix := pkgPath + "." + for name, value := range data { + if strings.HasPrefix(name, prefix) { + add(name, value) + delete(data, name) + } + } + } + return rewrites +} + func createSSAPkg(prog *ssa.Program, p *packages.Package, verbose bool) *ssa.Package { pkgSSA := prog.ImportedPackage(p.ID) if pkgSSA == nil { diff --git a/internal/build/build_test.go b/internal/build/build_test.go index ead07db0..c71f0d74 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -8,6 +8,9 @@ import ( "fmt" "io" "os" + "os/exec" + "path/filepath" + "runtime" "testing" "github.com/goplus/llgo/internal/mockable" @@ -94,4 +97,65 @@ func TestCmpTest(t *testing.T) { mockRun([]string{"../../cl/_testgo/runtest"}, &Config{Mode: ModeCmpTest}) } -// TestGenerateOutputFilenames removed - functionality moved to filename_test.go +const ( + rewriteMainPkg = "github.com/goplus/llgo/cl/_testgo/rewrite" + rewriteDepPkg = rewriteMainPkg + "/dep" + rewriteDirPath = "../../cl/_testgo/rewrite" +) + +func TestLdFlagsRewriteVars(t *testing.T) { + buildRewriteBinary(t, false, "build-main", "build-pkg") + buildRewriteBinary(t, false, "rerun-main", "rerun-pkg") +} + +func TestLdFlagsRewriteVarsMainAlias(t *testing.T) { + buildRewriteBinary(t, true, "alias-main", "alias-pkg") +} + +func buildRewriteBinary(t *testing.T, useMainAlias bool, mainVal, depVal string) { + t.Helper() + binPath := filepath.Join(t.TempDir(), "rewrite") + if runtime.GOOS == "windows" { + binPath += ".exe" + } + + cfg := &Config{Mode: ModeBuild, OutFile: binPath} + mainKey := rewriteMainPkg + var mainPkgs []string + if useMainAlias { + mainKey = "main" + mainPkgs = []string{rewriteMainPkg} + } + mainPlain := mainVal + "-plain" + depPlain := depVal + "-plain" + gorootVal := "goroot-" + mainVal + versionVal := "version-" + mainVal + addGlobalString(cfg, mainKey+".VarName="+mainVal, mainPkgs) + addGlobalString(cfg, mainKey+".VarPlain="+mainPlain, mainPkgs) + addGlobalString(cfg, rewriteDepPkg+".VarName="+depVal, nil) + addGlobalString(cfg, rewriteDepPkg+".VarPlain="+depPlain, nil) + addGlobalString(cfg, "runtime.defaultGOROOT="+gorootVal, nil) + addGlobalString(cfg, "runtime.buildVersion="+versionVal, nil) + + if _, err := Do([]string{rewriteDirPath}, cfg); err != nil { + t.Fatalf("ModeBuild failed: %v", err) + } + got := runBinary(t, binPath) + want := fmt.Sprintf( + "main.VarName: %s\nmain.VarPlain: %s\ndep.VarName: %s\ndep.VarPlain: %s\nruntime.GOROOT(): %s\nruntime.Version(): %s\n", + mainVal, mainPlain, depVal, depPlain, gorootVal, versionVal, + ) + if got != want { + t.Fatalf("unexpected binary output:\nwant %q\ngot %q", want, got) + } +} + +func runBinary(t *testing.T, path string, args ...string) string { + t.Helper() + cmd := exec.Command(path, args...) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("failed to run %s: %v\n%s", path, err, output) + } + return string(output) +} diff --git a/ssa/globals.go b/ssa/globals.go index fc15fb59..27413096 100644 --- a/ssa/globals.go +++ b/ssa/globals.go @@ -31,6 +31,16 @@ func (pkg Package) AddGlobalString(name string, value string) { pkg.NewVarEx(name, prog.Pointer(styp)).Init(Expr{cv, styp}) } +// ConstString returns an SSA string constant expression within this package. +func (pkg Package) ConstString(value string) Expr { + prog := pkg.Prog + styp := prog.String() + data := pkg.createGlobalStr(value) + length := prog.IntVal(uint64(len(value)), prog.Uintptr()) + cv := llvm.ConstNamedStruct(styp.ll, []llvm.Value{data, length.impl}) + return Expr{cv, styp} +} + // Undefined global string var by names func (pkg Package) Undefined(names ...string) error { prog := pkg.Prog From e2bb68489dadf8b708387c062c8dcb676645bd90 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 14 Nov 2025 12:38:54 +0800 Subject: [PATCH 02/12] build: override vars in alt pkg --- internal/build/build.go | 48 +++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/internal/build/build.go b/internal/build/build.go index 0de41c43..5faca519 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -132,9 +132,11 @@ type Config struct { CheckLinkArgs bool // check linkargs valid ForceEspClang bool // force to use esp-clang Tags string - GlobalRewrites map[string]string // pkg.name => data + GlobalRewrites map[string]Rewrites // pkg => var => value } +type Rewrites map[string]string + func NewDefaultConf(mode Mode) *Config { bin := os.Getenv("GOBIN") if bin == "" { @@ -336,8 +338,8 @@ func Do(args []string, conf *Config) ([]Package, error) { } // default runtime globals must be registered before packages are built - addDefaultGlobalString(conf, "runtime.defaultGOROOT="+runtime.GOROOT(), nil) - addDefaultGlobalString(conf, "runtime.buildVersion="+runtime.Version(), nil) + addGlobalString(conf, "runtime.defaultGOROOT="+runtime.GOROOT(), nil) + addGlobalString(conf, "runtime.buildVersion="+runtime.Version(), nil) pkgs, err := buildAllPkgs(ctx, initial, verbose) check(err) if mode == ModeGen { @@ -626,10 +628,6 @@ var ( ) func addGlobalString(conf *Config, arg string, mainPkgs []string) { - addGlobalStringWith(conf, arg, mainPkgs, false) -} - -func addDefaultGlobalString(conf *Config, arg string, mainPkgs []string) { addGlobalStringWith(conf, arg, mainPkgs, true) } @@ -648,18 +646,22 @@ func addGlobalStringWith(conf *Config, arg string, mainPkgs []string, skipIfExis return } if conf.GlobalRewrites == nil { - conf.GlobalRewrites = make(map[string]string) + conf.GlobalRewrites = make(map[string]Rewrites) } - suffix := arg[dot:eq] + varName := arg[dot+1 : eq] value := arg[eq+1:] for _, realPkg := range pkgs { - name := realPkg + suffix + vars := conf.GlobalRewrites[realPkg] + if vars == nil { + vars = make(Rewrites) + conf.GlobalRewrites[realPkg] = vars + } if skipIfExists { - if _, exists := conf.GlobalRewrites[name]; exists { + if _, exists := vars[varName]; exists { continue } } - conf.GlobalRewrites[name] = value + vars[varName] = value } } @@ -1177,18 +1179,18 @@ func allPkgs(ctx *context, initial []*packages.Package, verbose bool) (all []*aP func collectRewriteVars(ctx *context, pkgPath string) map[string]string { var rewrites map[string]string - add := func(name, value string) { - if rewrites == nil { - rewrites = make(map[string]string) - } - rewrites[name] = value - } + basePath := strings.TrimPrefix(pkgPath, altPkgPathPrefix) if data := ctx.buildConf.GlobalRewrites; len(data) != 0 { - prefix := pkgPath + "." - for name, value := range data { - if strings.HasPrefix(name, prefix) { - add(name, value) - delete(data, name) + for pkg, vars := range data { + trimmed := strings.TrimPrefix(pkg, altPkgPathPrefix) + if trimmed != basePath { + continue + } + for name, value := range vars { + if rewrites == nil { + rewrites = make(map[string]string) + } + rewrites[pkgPath+"."+name] = value } } } From 22a43622a02b8b024243a63e95ef33277e844ab6 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 14 Nov 2025 15:20:24 +0800 Subject: [PATCH 03/12] cl: fix global var rewrite in alt pkg --- cl/compile.go | 7 ++++++- internal/build/build.go | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cl/compile.go b/cl/compile.go index d239bb07..2b3366bb 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -134,7 +134,12 @@ func (p *context) rewriteValue(name string) (string, bool) { if p.rewrites == nil { return "", false } - val, ok := p.rewrites[name] + dot := strings.LastIndex(name, ".") + if dot < 0 { + return "", false + } + varName := name[dot+1:] + val, ok := p.rewrites[varName] return val, ok } diff --git a/internal/build/build.go b/internal/build/build.go index 5faca519..c71b8645 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -350,7 +350,6 @@ func Do(args []string, conf *Config) ([]Package, error) { } return nil, fmt.Errorf("initial package not found") } - dpkg, err := buildAllPkgs(ctx, altPkgs[noRt:], verbose) check(err) allPkgs := append([]*aPackage{}, pkgs...) @@ -1190,7 +1189,7 @@ func collectRewriteVars(ctx *context, pkgPath string) map[string]string { if rewrites == nil { rewrites = make(map[string]string) } - rewrites[pkgPath+"."+name] = value + rewrites[name] = value } } } From 1e4616a7588497781a6e4e17045e7aa4eebc61c2 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 14 Nov 2025 16:55:55 +0800 Subject: [PATCH 04/12] build: don't replace ExportFile --- internal/build/build.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/build/build.go b/internal/build/build.go index c71b8645..e893eeb4 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -750,7 +750,6 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, outputPa if p.ExportFile != "" && aPkg != nil { // skip packages that only contain declarations linkArgs = append(linkArgs, aPkg.LinkArgs...) objFiles = append(objFiles, aPkg.LLFiles...) - objFiles = append(objFiles, aPkg.ExportFile) need1, need2 := isNeedRuntimeOrPyInit(ctx, p) if !needRuntime { needRuntime = need1 @@ -1050,10 +1049,11 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error { aPkg.LinkArgs = append(aPkg.LinkArgs, altLdflags...) } if pkg.ExportFile != "" { - pkg.ExportFile, err = exportObject(ctx, pkg.PkgPath, pkg.ExportFile, []byte(ret.String())) + exportFile, err := exportObject(ctx, pkg.PkgPath, pkg.ExportFile, []byte(ret.String())) if err != nil { return fmt.Errorf("export object of %v failed: %v", pkgPath, err) } + aPkg.LLFiles = append(aPkg.LLFiles, exportFile) if debugBuild || verbose { fmt.Fprintf(os.Stderr, "==> Export %s: %s\n", aPkg.PkgPath, pkg.ExportFile) } From 8ba8ec71b559d03f1e49143c504457c208e99682 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 14 Nov 2025 16:56:59 +0800 Subject: [PATCH 05/12] build: write exports into temp files --- internal/build/build.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/internal/build/build.go b/internal/build/build.go index e893eeb4..0db88eb5 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -1062,7 +1062,8 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error { } func exportObject(ctx *context, pkgPath string, exportFile string, data []byte) (string, error) { - f, err := os.CreateTemp("", "llgo-*.ll") + base := filepath.Base(exportFile) + f, err := os.CreateTemp("", base+"-*.ll") if err != nil { return "", err } @@ -1084,13 +1085,17 @@ func exportObject(ctx *context, pkgPath string, exportFile string, data []byte) } return exportFile, os.Rename(f.Name(), exportFile) } - exportFile += ".o" - args := []string{"-o", exportFile, "-c", f.Name(), "-Wno-override-module"} + objFile, err := os.CreateTemp("", base+"-*.o") + if err != nil { + return "", err + } + objFile.Close() + args := []string{"-o", objFile.Name(), "-c", f.Name(), "-Wno-override-module"} if ctx.buildConf.Verbose { fmt.Fprintln(os.Stderr, "clang", args) } cmd := ctx.compiler() - return exportFile, cmd.Compile(args...) + return objFile.Name(), cmd.Compile(args...) } func llcCheck(env *llvm.Env, exportFile string) (msg string, err error) { From b0f5d34b39cc8a3daabff70acb4bdd4972941312 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 14 Nov 2025 17:32:52 +0800 Subject: [PATCH 06/12] cl: add rewrite coverage test --- cl/rewrite_internal_test.go | 67 +++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 cl/rewrite_internal_test.go diff --git a/cl/rewrite_internal_test.go b/cl/rewrite_internal_test.go new file mode 100644 index 00000000..6c542bc3 --- /dev/null +++ b/cl/rewrite_internal_test.go @@ -0,0 +1,67 @@ +//go:build !llgo +// +build !llgo + +package cl + +import ( + "go/ast" + "go/parser" + "go/token" + "go/types" + "runtime" + "strings" + "testing" + + gpackages "github.com/goplus/gogen/packages" + llssa "github.com/goplus/llgo/ssa" + "github.com/goplus/llgo/ssa/ssatest" + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" +) + +func init() { + llssa.Initialize(llssa.InitAll | llssa.InitNative) +} + +func compileWithRewrites(t *testing.T, src string, rewrites map[string]string) string { + t.Helper() + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "rewrite.go", src, 0) + if err != nil { + t.Fatalf("parse failed: %v", err) + } + importer := gpackages.NewImporter(fset) + mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics + pkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer}, fset, + types.NewPackage(file.Name.Name, file.Name.Name), []*ast.File{file}, mode) + if err != nil { + t.Fatalf("build package failed: %v", err) + } + prog := ssatest.NewProgramEx(t, nil, importer) + prog.TypeSizes(types.SizesFor("gc", runtime.GOARCH)) + ret, _, err := NewPackageEx(prog, nil, rewrites, pkg, []*ast.File{file}) + if err != nil { + t.Fatalf("NewPackageEx failed: %v", err) + } + return ret.String() +} + +func TestRewriteGlobalStrings(t *testing.T) { + const src = `package rewritepkg +var VarInit = "original_value" +var VarPlain string +func Use() string { return VarInit + VarPlain } +` + ir := compileWithRewrites(t, src, map[string]string{ + "VarInit": "rewrite_init", + "VarPlain": "rewrite_plain", + }) + if strings.Contains(ir, "original_value") { + t.Fatalf("original initializer still present:\n%s", ir) + } + for _, want := range []string{`c"rewrite_init"`, `c"rewrite_plain"`} { + if !strings.Contains(ir, want) { + t.Fatalf("missing %s in IR:\n%s", want, ir) + } + } +} From 2a52d422c570a5e548182d8b22b0544ae3efbaa5 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 14 Nov 2025 17:48:16 +0800 Subject: [PATCH 07/12] cl: broaden rewrite coverage --- cl/rewrite_internal_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/cl/rewrite_internal_test.go b/cl/rewrite_internal_test.go index 6c542bc3..18d3ecb3 100644 --- a/cl/rewrite_internal_test.go +++ b/cl/rewrite_internal_test.go @@ -65,3 +65,36 @@ func Use() string { return VarInit + VarPlain } } } } + +func TestRewriteSkipsNonConstStores(t *testing.T) { + const src = `package rewritepkg +import "strings" +var VarInit = strings.ToUpper("original_value") +var VarPlain string +func Use() string { return VarInit + VarPlain } +` + ir := compileWithRewrites(t, src, map[string]string{ + "VarInit": "rewrite_init", + "VarPlain": "rewrite_plain", + }) + if !strings.Contains(ir, `c"rewrite_init"`) { + t.Fatalf("expected rewrite_init constant to remain:\n%s", ir) + } + if !strings.Contains(ir, "strings.ToUpper") { + t.Fatalf("expected call to strings.ToUpper in IR:\n%s", ir) + } +} + +func TestRewriteValueNoDot(t *testing.T) { + ctx := &context{rewrites: map[string]string{"VarInit": "rewrite_init"}} + if _, ok := ctx.rewriteValue("VarInit"); ok { + t.Fatalf("rewriteValue should skip names without package prefix") + } +} + +func TestIsStringTypeDefault(t *testing.T) { + ctx := &context{} + if ctx.isStringType(types.NewPointer(types.Typ[types.Int])) { + t.Fatalf("expected non-string pointer to return false") + } +} From 3a1d8693e968f87368aaa58a03eaf9a4ae783e4a Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 14 Nov 2025 17:57:05 +0800 Subject: [PATCH 08/12] rewrite: address review feedback --- cl/compile.go | 38 ++++++++++--- cl/rewrite_internal_test.go | 3 + internal/build/build.go | 106 ++++++++++++++++++++++-------------- ssa/globals.go | 5 +- 4 files changed, 103 insertions(+), 49 deletions(-) diff --git a/cl/compile.go b/cl/compile.go index 2b3366bb..807052c8 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -130,12 +130,14 @@ type context struct { rewrites map[string]string } +const maxStringTypeDepth = 64 + func (p *context) rewriteValue(name string) (string, bool) { if p.rewrites == nil { return "", false } dot := strings.LastIndex(name, ".") - if dot < 0 { + if dot < 0 || dot == len(name)-1 { return "", false } varName := name[dot+1:] @@ -144,7 +146,9 @@ func (p *context) rewriteValue(name string) (string, bool) { } func (p *context) isStringType(typ types.Type) bool { - for typ != nil { + depth := 0 + for typ != nil && depth < maxStringTypeDepth { + depth++ switch t := typ.Underlying().(type) { case *types.Basic: return t.Kind() == types.String @@ -164,15 +168,21 @@ func (p *context) globalFullName(g *ssa.Global) string { } func (p *context) rewriteInitStore(store *ssa.Store, g *ssa.Global) (string, bool) { + if p.rewrites == nil { + return "", false + } fn := store.Block().Parent() if fn == nil || fn.Synthetic != "package initializer" { return "", false } - value, ok := p.rewriteValue(p.globalFullName(g)) - if !ok || !p.isStringType(g.Type()) { + if _, ok := store.Val.(*ssa.Const); !ok { return "", false } - if _, ok := store.Val.(*ssa.Const); !ok { + if !p.isStringType(g.Type()) { + return "", false + } + value, ok := p.rewriteValue(p.globalFullName(g)) + if !ok { return "", false } return value, true @@ -867,9 +877,11 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { return } } - if g, ok := va.(*ssa.Global); ok { - if _, ok := p.rewriteInitStore(v, g); ok { - return + if p.rewrites != nil { + if g, ok := va.(*ssa.Global); ok { + if _, ok := p.rewriteInitStore(v, g); ok { + return + } } } ptr := p.compileValue(b, va) @@ -1041,6 +1053,16 @@ func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret ll } // NewPackageEx compiles a Go package to LLVM IR package. +// +// Parameters: +// - prog: target LLVM SSA program context +// - patches: optional package patches applied during compilation +// - rewrites: per-package string initializers rewritten at compile time +// - pkg: SSA package to compile +// - files: parsed AST files that belong to the package +// +// The rewrites map uses short variable names (without package qualifier) and +// only affects string-typed globals defined in the current package. func NewPackageEx(prog llssa.Program, patches Patches, rewrites map[string]string, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs []string, err error) { pkgProg := pkg.Prog pkgTypes := pkg.Pkg diff --git a/cl/rewrite_internal_test.go b/cl/rewrite_internal_test.go index 18d3ecb3..aa77f7cd 100644 --- a/cl/rewrite_internal_test.go +++ b/cl/rewrite_internal_test.go @@ -90,6 +90,9 @@ func TestRewriteValueNoDot(t *testing.T) { if _, ok := ctx.rewriteValue("VarInit"); ok { t.Fatalf("rewriteValue should skip names without package prefix") } + if _, ok := ctx.rewriteValue("pkg."); ok { + t.Fatalf("rewriteValue should skip trailing dot names") + } } func TestIsStringTypeDefault(t *testing.T) { diff --git a/internal/build/build.go b/internal/build/build.go index 0db88eb5..99595667 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -111,28 +111,33 @@ type OutFmtDetails struct { } type Config struct { - Goos string - Goarch string - Target string // target name (e.g., "rp2040", "wasi") - takes precedence over Goos/Goarch - BinPath string - AppExt string // ".exe" on Windows, empty on Unix - OutFile string // only valid for ModeBuild when len(pkgs) == 1 - OutFmts OutFmts // Output format specifications (only for Target != "") - Emulator bool // run in emulator mode - Port string // target port for flashing - BaudRate int // baudrate for serial communication - RunArgs []string - Mode Mode - BuildMode BuildMode // Build mode: exe, c-archive, c-shared - AbiMode AbiMode - GenExpect bool // only valid for ModeCmpTest - Verbose bool - GenLL bool // generate pkg .ll files - CheckLLFiles bool // check .ll files valid - CheckLinkArgs bool // check linkargs valid - ForceEspClang bool // force to use esp-clang - Tags string - GlobalRewrites map[string]Rewrites // pkg => var => value + Goos string + Goarch string + Target string // target name (e.g., "rp2040", "wasi") - takes precedence over Goos/Goarch + BinPath string + AppExt string // ".exe" on Windows, empty on Unix + OutFile string // only valid for ModeBuild when len(pkgs) == 1 + OutFmts OutFmts // Output format specifications (only for Target != "") + Emulator bool // run in emulator mode + Port string // target port for flashing + BaudRate int // baudrate for serial communication + RunArgs []string + Mode Mode + BuildMode BuildMode // Build mode: exe, c-archive, c-shared + AbiMode AbiMode + GenExpect bool // only valid for ModeCmpTest + Verbose bool + GenLL bool // generate pkg .ll files + CheckLLFiles bool // check .ll files valid + CheckLinkArgs bool // check linkargs valid + ForceEspClang bool // force to use esp-clang + Tags string + // GlobalRewrites specifies compile-time overrides for global string variables. + // Keys are fully qualified package paths (e.g. "main" or "github.com/user/pkg"). + // Each Rewrites entry maps variable names to replacement string values. Only + // string-typed globals are supported and "main" applies to all root main + // packages in the current build. + GlobalRewrites map[string]Rewrites } type Rewrites map[string]string @@ -626,6 +631,8 @@ var ( errXflags = errors.New("-X flag requires argument of the form importpath.name=value") ) +const maxRewriteValueLength = 1 << 20 // 1 MiB cap per rewrite value + func addGlobalString(conf *Config, arg string, mainPkgs []string) { addGlobalStringWith(conf, arg, mainPkgs, true) } @@ -637,6 +644,9 @@ func addGlobalStringWith(conf *Config, arg string, mainPkgs []string, skipIfExis panic(errXflags) } pkg := arg[:dot] + varName := arg[dot+1 : eq] + value := arg[eq+1:] + validateRewriteInput(pkg, varName, value) pkgs := []string{pkg} if pkg == "main" { pkgs = mainPkgs @@ -647,8 +657,6 @@ func addGlobalStringWith(conf *Config, arg string, mainPkgs []string, skipIfExis if conf.GlobalRewrites == nil { conf.GlobalRewrites = make(map[string]Rewrites) } - varName := arg[dot+1 : eq] - value := arg[eq+1:] for _, realPkg := range pkgs { vars := conf.GlobalRewrites[realPkg] if vars == nil { @@ -664,6 +672,18 @@ func addGlobalStringWith(conf *Config, arg string, mainPkgs []string, skipIfExis } } +func validateRewriteInput(pkg, varName, value string) { + if pkg == "" || strings.ContainsAny(pkg, " \t\r\n") { + panic(fmt.Errorf("invalid package path for rewrite: %q", pkg)) + } + if !token.IsIdentifier(varName) { + panic(fmt.Errorf("invalid variable name for rewrite: %q", varName)) + } + if len(value) > maxRewriteValueLength { + panic(fmt.Errorf("rewrite value too large: %d bytes", len(value))) + } +} + // compileExtraFiles compiles extra files (.s/.c) from target configuration and returns object files func compileExtraFiles(ctx *context, verbose bool) ([]string, error) { if len(ctx.crossCompile.ExtraFiles) == 0 { @@ -1182,23 +1202,29 @@ func allPkgs(ctx *context, initial []*packages.Package, verbose bool) (all []*aP } func collectRewriteVars(ctx *context, pkgPath string) map[string]string { - var rewrites map[string]string - basePath := strings.TrimPrefix(pkgPath, altPkgPathPrefix) - if data := ctx.buildConf.GlobalRewrites; len(data) != 0 { - for pkg, vars := range data { - trimmed := strings.TrimPrefix(pkg, altPkgPathPrefix) - if trimmed != basePath { - continue - } - for name, value := range vars { - if rewrites == nil { - rewrites = make(map[string]string) - } - rewrites[name] = value - } - } + data := ctx.buildConf.GlobalRewrites + if len(data) == 0 { + return nil } - return rewrites + basePath := strings.TrimPrefix(pkgPath, altPkgPathPrefix) + if vars := data[basePath]; vars != nil { + return cloneRewrites(vars) + } + if vars := data[pkgPath]; vars != nil { + return cloneRewrites(vars) + } + return nil +} + +func cloneRewrites(src Rewrites) map[string]string { + if len(src) == 0 { + return nil + } + dup := make(map[string]string, len(src)) + for k, v := range src { + dup[k] = v + } + return dup } func createSSAPkg(prog *ssa.Program, p *packages.Package, verbose bool) *ssa.Package { diff --git a/ssa/globals.go b/ssa/globals.go index 27413096..e3867003 100644 --- a/ssa/globals.go +++ b/ssa/globals.go @@ -31,7 +31,10 @@ func (pkg Package) AddGlobalString(name string, value string) { pkg.NewVarEx(name, prog.Pointer(styp)).Init(Expr{cv, styp}) } -// ConstString returns an SSA string constant expression within this package. +// ConstString creates an SSA expression representing a Go string literal. The +// returned value is backed by an anonymous global constant and can be used to +// initialize package-level variables or other constant contexts that expect a +// Go string value. func (pkg Package) ConstString(value string) Expr { prog := pkg.Prog styp := prog.String() From 4b26cccc90631eb66b852b98f633d423bae1ab3b Mon Sep 17 00:00:00 2001 From: Li Jie Date: Fri, 14 Nov 2025 18:29:00 +0800 Subject: [PATCH 09/12] cl: cover rewrite guards --- cl/rewrite_internal_test.go | 49 +++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/cl/rewrite_internal_test.go b/cl/rewrite_internal_test.go index aa77f7cd..856cedcc 100644 --- a/cl/rewrite_internal_test.go +++ b/cl/rewrite_internal_test.go @@ -101,3 +101,52 @@ func TestIsStringTypeDefault(t *testing.T) { t.Fatalf("expected non-string pointer to return false") } } + +func TestIsStringTypeBranches(t *testing.T) { + ctx := &context{} + if ctx.isStringType(types.NewSlice(types.Typ[types.String])) { + t.Fatalf("slice should trigger default branch and return false") + } + if ctx.isStringType(nil) { + t.Fatalf("nil type should return false") + } +} + +func TestRewriteIgnoredInNonInitStore(t *testing.T) { + const src = `package rewritepkg +var VarInit = "original_value" +func Override() { VarInit = "override_value" } +` + ir := compileWithRewrites(t, src, map[string]string{"VarInit": "rewrite_init"}) + if !strings.Contains(ir, `c"override_value"`) { + t.Fatalf("override store should retain original literal:\n%s", ir) + } + if !strings.Contains(ir, `c"rewrite_init"`) { + t.Fatalf("global initializer should still be rewritten:\n%s", ir) + } +} + +func TestRewriteMissingEntry(t *testing.T) { + const src = `package rewritepkg +var VarInit = "original_value" +var VarOther = "other_value" +` + ir := compileWithRewrites(t, src, map[string]string{"VarInit": "rewrite_init"}) + if !strings.Contains(ir, `c"other_value"`) { + t.Fatalf("VarOther should keep original initializer:\n%s", ir) + } + if !strings.Contains(ir, `c"rewrite_init"`) { + t.Fatalf("VarInit should still be rewritten:\n%s", ir) + } +} + +func TestRewriteIgnoresNonStringVar(t *testing.T) { + const src = `package rewritepkg +type wrapper struct{ v int } +var VarStruct = wrapper{v: 1} +` + ir := compileWithRewrites(t, src, map[string]string{"VarStruct": "rewrite_struct"}) + if strings.Contains(ir, `c"rewrite_struct"`) { + t.Fatalf("non-string variables must not be rewritten:\n%s", ir) + } +} From d17ff2592abf80e71c208c786c2309f3a894620a Mon Sep 17 00:00:00 2001 From: xgopilot Date: Fri, 14 Nov 2025 11:37:09 +0000 Subject: [PATCH 10/12] build: improve error handling and code quality - Fix missing error handling in exportObject function - Add explicit warning for non-string variable rewrites - Improve documentation for maxRewriteValueLength constant Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com> --- cl/compile.go | 11 +++++++++-- internal/build/build.go | 7 ++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/cl/compile.go b/cl/compile.go index 807052c8..2abb36e4 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -235,8 +235,15 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) { log.Println("==> NewVar", name, typ) } g := pkg.NewVar(name, typ, llssa.Background(vtype)) - if value, ok := p.rewriteValue(name); ok && p.isStringType(typ) { - g.Init(pkg.ConstString(value)) + if value, ok := p.rewriteValue(name); ok { + if p.isStringType(typ) { + g.Init(pkg.ConstString(value)) + } else { + log.Printf("warning: ignoring rewrite for non-string variable %s (type: %v)", name, typ) + if define { + g.InitNil() + } + } } else if define { g.InitNil() } diff --git a/internal/build/build.go b/internal/build/build.go index 99595667..2c7de4e4 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -631,6 +631,8 @@ var ( errXflags = errors.New("-X flag requires argument of the form importpath.name=value") ) +// maxRewriteValueLength limits the size of rewrite values to prevent +// excessive memory usage and potential resource exhaustion during compilation. const maxRewriteValueLength = 1 << 20 // 1 MiB cap per rewrite value func addGlobalString(conf *Config, arg string, mainPkgs []string) { @@ -1087,7 +1089,10 @@ func exportObject(ctx *context, pkgPath string, exportFile string, data []byte) if err != nil { return "", err } - f.Write(data) + if _, err := f.Write(data); err != nil { + f.Close() + return "", err + } err = f.Close() if err != nil { return exportFile, err From 1ba7d1e561b5a7135dc84f0e1975c49f5f706247 Mon Sep 17 00:00:00 2001 From: xgopilot Date: Fri, 14 Nov 2025 14:38:46 +0000 Subject: [PATCH 11/12] fix: change to isStringPtrType for global string var - Replace isStringType with isStringPtrType to properly validate that only *string type variables can be rewritten with -ldflags -X - Remove maxStringTypeDepth constant as it's no longer needed - Update tests to reflect the new function name and add test case for valid *string type - Fix compileGlobal to use gbl.Type() for accurate type checking This addresses the review feedback that Go only allows -X rewrites for the basic string type, not derived types like "type T string". Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com> --- cl/compile.go | 31 ++++++++++++------------------- cl/rewrite_internal_test.go | 13 ++++++++----- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/cl/compile.go b/cl/compile.go index 2abb36e4..2f770667 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -130,8 +130,6 @@ type context struct { rewrites map[string]string } -const maxStringTypeDepth = 64 - func (p *context) rewriteValue(name string) (string, bool) { if p.rewrites == nil { return "", false @@ -145,21 +143,16 @@ func (p *context) rewriteValue(name string) (string, bool) { return val, ok } -func (p *context) isStringType(typ types.Type) bool { - depth := 0 - for typ != nil && depth < maxStringTypeDepth { - depth++ - switch t := typ.Underlying().(type) { - case *types.Basic: - return t.Kind() == types.String - case *types.Pointer: - typ = t.Elem() - continue - default: - return false - } +// isStringPtrType checks if typ is a pointer to the basic string type (*string). +// This is used to validate that -ldflags -X can only rewrite variables of type *string, +// not derived string types like "type T string". +func (p *context) isStringPtrType(typ types.Type) bool { + ptr, ok := typ.(*types.Pointer) + if !ok { + return false } - return false + basic, ok := ptr.Elem().Underlying().(*types.Basic) + return ok && basic.Kind() == types.String } func (p *context) globalFullName(g *ssa.Global) string { @@ -178,7 +171,7 @@ func (p *context) rewriteInitStore(store *ssa.Store, g *ssa.Global) (string, boo if _, ok := store.Val.(*ssa.Const); !ok { return "", false } - if !p.isStringType(g.Type()) { + if !p.isStringPtrType(g.Type()) { return "", false } value, ok := p.rewriteValue(p.globalFullName(g)) @@ -236,10 +229,10 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) { } g := pkg.NewVar(name, typ, llssa.Background(vtype)) if value, ok := p.rewriteValue(name); ok { - if p.isStringType(typ) { + if p.isStringPtrType(gbl.Type()) { g.Init(pkg.ConstString(value)) } else { - log.Printf("warning: ignoring rewrite for non-string variable %s (type: %v)", name, typ) + log.Printf("warning: ignoring rewrite for non-string variable %s (type: %v)", name, gbl.Type()) if define { g.InitNil() } diff --git a/cl/rewrite_internal_test.go b/cl/rewrite_internal_test.go index 856cedcc..041c6058 100644 --- a/cl/rewrite_internal_test.go +++ b/cl/rewrite_internal_test.go @@ -95,21 +95,24 @@ func TestRewriteValueNoDot(t *testing.T) { } } -func TestIsStringTypeDefault(t *testing.T) { +func TestIsStringPtrTypeDefault(t *testing.T) { ctx := &context{} - if ctx.isStringType(types.NewPointer(types.Typ[types.Int])) { + if ctx.isStringPtrType(types.NewPointer(types.Typ[types.Int])) { t.Fatalf("expected non-string pointer to return false") } } -func TestIsStringTypeBranches(t *testing.T) { +func TestIsStringPtrTypeBranches(t *testing.T) { ctx := &context{} - if ctx.isStringType(types.NewSlice(types.Typ[types.String])) { + if ctx.isStringPtrType(types.NewSlice(types.Typ[types.String])) { t.Fatalf("slice should trigger default branch and return false") } - if ctx.isStringType(nil) { + if ctx.isStringPtrType(nil) { t.Fatalf("nil type should return false") } + if !ctx.isStringPtrType(types.NewPointer(types.Typ[types.String])) { + t.Fatalf("*string should return true") + } } func TestRewriteIgnoredInNonInitStore(t *testing.T) { From 034b05c53c43bd52129f706e7c3e5f120208ca3f Mon Sep 17 00:00:00 2001 From: xgopilot Date: Fri, 14 Nov 2025 14:49:30 +0000 Subject: [PATCH 12/12] cl: remove Underlying() call to reject string type aliases in rewrites Type aliases like `type T string` are no longer supported for -ldflags -X rewrites. Only direct *string types are now allowed. - Removed Underlying() call from isStringPtrType - Added TestRewriteIgnoresStringAlias test case Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com> --- cl/compile.go | 2 +- cl/rewrite_internal_test.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cl/compile.go b/cl/compile.go index 2f770667..d9381137 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -151,7 +151,7 @@ func (p *context) isStringPtrType(typ types.Type) bool { if !ok { return false } - basic, ok := ptr.Elem().Underlying().(*types.Basic) + basic, ok := ptr.Elem().(*types.Basic) return ok && basic.Kind() == types.String } diff --git a/cl/rewrite_internal_test.go b/cl/rewrite_internal_test.go index 041c6058..8fbf89a3 100644 --- a/cl/rewrite_internal_test.go +++ b/cl/rewrite_internal_test.go @@ -153,3 +153,17 @@ var VarStruct = wrapper{v: 1} t.Fatalf("non-string variables must not be rewritten:\n%s", ir) } } + +func TestRewriteIgnoresStringAlias(t *testing.T) { + const src = `package rewritepkg +type T string +var VarAlias T = "original_value" +` + ir := compileWithRewrites(t, src, map[string]string{"VarAlias": "rewrite_alias"}) + if strings.Contains(ir, `c"rewrite_alias"`) { + t.Fatalf("string alias types must not be rewritten:\n%s", ir) + } + if !strings.Contains(ir, `c"original_value"`) { + t.Fatalf("original value should remain for alias type:\n%s", ir) + } +}