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()