1// Package process provides dependency-leaf process-environment helpers.
2//
3// This file must not import bootstrap, desktop, or any other internal
4// package: both cmd/dumber and internal/infrastructure/desktop import it, and
5// bootstrap already imports desktop. Keep it to the standard library so the
6// merge rule can be shared without an import cycle.
7package process
8
9import "strings"
10
11// RuntimeSafetySetting is the GODEBUG key that must be set before any
12// goroutine or CEF subprocess is created. Go stack shrinking can invalidate
13// frames retained across foreign CEF/GTK stacks.
14const RuntimeSafetySetting = "gcshrinkstackoff"
15
16// EnvironmentValue returns the value for name in an environ slice.
17func EnvironmentValue(environ []string, name string) string {
18 prefix := name + "="
19 for _, entry := range environ {
20 if strings.HasPrefix(entry, prefix) {
21 return strings.TrimPrefix(entry, prefix)
22 }
23 }
24 return ""
25}
26
27// ReplaceEnvironmentValue returns a copy of environ with name set to value.
28func ReplaceEnvironmentValue(environ []string, name, value string) []string {
29 prefix := name + "="
30 updated := make([]string, 0, len(environ)+1)
31 for _, entry := range environ {
32 if !strings.HasPrefix(entry, prefix) {
33 updated = append(updated, entry)
34 }
35 }
36 return append(updated, prefix+value)
37}
38
39// MergeRuntimeSafety returns a copy of environ with RuntimeSafetySetting=1
40// merged into GODEBUG, preserving all unrelated entries (including other
41// GODEBUG keys) and their relative order. It reports whether a change was
42// applied. An already-effective single setting entry is detected
43// independently of its position, so direct invocation never performs an
44// unnecessary self-exec; duplicate or conflicting entries still collapse to
45// one normalized trailing entry.
46func MergeRuntimeSafety(environ []string) ([]string, bool) {
47 updated := append([]string(nil), environ...)
48 // Collect every GODEBUG entry: duplicates are legal in an environ
49 // slice and the first entry alone never describes the effective
50 // setting, so normalization must merge them all before deciding.
51 godebugValues := make([]string, 0, 1)
52 for _, entry := range updated {
53 if strings.HasPrefix(entry, "GODEBUG=") {
54 godebugValues = append(godebugValues, strings.TrimPrefix(entry, "GODEBUG="))
55 }
56 }
57 current := strings.Join(godebugValues, ",")
58 parts := strings.Split(current, ",")
59 kept := make([]string, 0, len(parts)+1)
60 var settingParts []string
61 for _, part := range parts {
62 part = strings.TrimSpace(part)
63 if part == "" {
64 continue
65 }
66 key, _, _ := strings.Cut(part, "=")
67 if key == RuntimeSafetySetting {
68 settingParts = append(settingParts, part)
69 continue
70 }
71 kept = append(kept, part)
72 }
73 if len(godebugValues) == 1 && len(settingParts) == 1 && settingParts[0] == RuntimeSafetySetting+"=1" {
74 // Exactly one entry with one effective setting: already safe in
75 // place, independent of its position among the other keys. Leave
76 // the order untouched so direct invocation performs no self-exec.
77 return updated, false
78 }
79 // Zero, duplicate, conflicting, or valueless entries collapse to one
80 // normalized trailing entry.
81 merged := strings.Join(append(kept, RuntimeSafetySetting+"=1"), ",")
82 if current == merged {
83 return updated, false
84 }
85 return ReplaceEnvironmentValue(updated, "GODEBUG", merged), true
86}