diff --git a/v2/cmd/wails/flags/dev.go b/v2/cmd/wails/flags/dev.go index d31d8bc8774..6e0eea4352a 100644 --- a/v2/cmd/wails/flags/dev.go +++ b/v2/cmd/wails/flags/dev.go @@ -31,6 +31,7 @@ type Dev struct { AppArgs string `flag:"appargs" description:"arguments to pass to the underlying app (quoted and space separated)"` Save bool `flag:"save" description:"Save the given flags as defaults"` FrontendDevServerURL string `flag:"frontenddevserverurl" description:"The url of the external frontend dev server to use"` + DlvFlag string `flag:"dlvflag" description:"Debug flags pass to dlv"` ViteServerTimeout int `flag:"viteservertimeout" description:"The timeout in seconds for Vite server detection (default: 10)"` // Internal state diff --git a/v2/cmd/wails/generate.go b/v2/cmd/wails/generate.go index 15a6b33d868..2625f2a25d4 100644 --- a/v2/cmd/wails/generate.go +++ b/v2/cmd/wails/generate.go @@ -12,6 +12,7 @@ import ( "github.com/wailsapp/wails/v2/cmd/wails/flags" "github.com/wailsapp/wails/v2/cmd/wails/internal/template" "github.com/wailsapp/wails/v2/internal/colour" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/wailsapp/wails/v2/internal/fs" "github.com/wailsapp/wails/v2/internal/project" "github.com/wailsapp/wails/v2/pkg/clilogger" @@ -230,7 +231,7 @@ func processPackageLockJSON(frontendDir string) error { if err != nil { return err } - json := string(data) + json := conv.BytesToString(data) // We will ignore these errors - it's not critical printBulletPoint("Updating package-lock.json data...") diff --git a/v2/cmd/wails/internal/dev/dev.go b/v2/cmd/wails/internal/dev/dev.go index 9495b5bf26f..d6f66a1e56b 100644 --- a/v2/cmd/wails/internal/dev/dev.go +++ b/v2/cmd/wails/internal/dev/dev.go @@ -23,6 +23,7 @@ import ( "github.com/wailsapp/wails/v2/cmd/wails/flags" "github.com/wailsapp/wails/v2/cmd/wails/internal/gomod" "github.com/wailsapp/wails/v2/cmd/wails/internal/logutils" + "github.com/wailsapp/wails/v2/internal/conv" "golang.org/x/mod/semver" "github.com/wailsapp/wails/v2/pkg/commands/buildtags" @@ -314,7 +315,29 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process os.Setenv("frontenddevserverurl", f.FrontendDevServerURL) // Start up new binary with correct args - newProcess := process.NewProcess(appBinary, args...) + + var command string + if len(f.DlvFlag) == 0 { + command = appBinary + } else { + command = "dlv" + + // Use shlex to properly handle quoted arguments + dlvArgs, err := shlex.Split(f.DlvFlag) + if err != nil { + buildOptions.Logger.Fatal("Unable to parse dlvflag: %s", err.Error()) + } + newArgs := append(dlvArgs, "exec", appBinary) + if len(args) > 0 { + newArgs = append(newArgs, "--") + + newArgs = append(newArgs, args...) + } + args = newArgs + } + + logutils.LogGreen("Executing: " + command + " " + strings.Join(args, " ")) + newProcess := process.NewProcess(command, args...) err = newProcess.Start(exitCodeChannel) if err != nil { // Remove binary @@ -485,7 +508,7 @@ func doWatcherLoop(cwd string, reloadDirs string, buildOptions *build.Options, d if err != nil { logutils.LogRed("Error reading assetdir from devserver: %s", err.Error()) } else { - assetDir = string(content) + assetDir = conv.BytesToString(content) } resp.Body.Close() } diff --git a/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go b/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go index b37334ec325..088c7307a99 100644 --- a/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go +++ b/v2/internal/binding/binding_test/binding_conflicting_package_name_test.go @@ -10,6 +10,7 @@ import ( "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/int_package" "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/map_package" "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/uint_package" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/wailsapp/wails/v2/internal/logger" ) @@ -57,7 +58,7 @@ func TestConflictingPackageName(t *testing.T) { } // then - generatedBindings := string(rawGeneratedBindings) + generatedBindings := conv.BytesToString(rawGeneratedBindings) if generatedBindings != expectedBindings { t.Fatalf("the generated bindings does not match the expected ones.\nWanted:\n%s\n\nGot:\n%s", expectedBindings, generatedBindings) } diff --git a/v2/internal/binding/binding_test/binding_returned_promises_test.go b/v2/internal/binding/binding_test/binding_returned_promises_test.go index 94941d0a390..37f9aa9640e 100644 --- a/v2/internal/binding/binding_test/binding_returned_promises_test.go +++ b/v2/internal/binding/binding_test/binding_returned_promises_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/wailsapp/wails/v2/internal/binding" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/wailsapp/wails/v2/internal/logger" ) @@ -74,7 +75,7 @@ func TestPromises(t *testing.T) { } // then - generatedBindings := string(rawGeneratedBindings) + generatedBindings := conv.BytesToString(rawGeneratedBindings) if generatedBindings != expectedPromiseBindings { t.Fatalf("the generated bindings does not match the expected ones.\nWanted:\n%s\n\nGot:\n%s", expectedPromiseBindings, generatedBindings) } diff --git a/v2/internal/binding/binding_test/binding_type_alias_test.go b/v2/internal/binding/binding_test/binding_type_alias_test.go index 90b009c5f6c..43275a56a6e 100644 --- a/v2/internal/binding/binding_test/binding_type_alias_test.go +++ b/v2/internal/binding/binding_test/binding_type_alias_test.go @@ -1,11 +1,13 @@ package binding_test import ( - "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/int_package" "io/fs" "os" "testing" + "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/int_package" + "github.com/wailsapp/wails/v2/internal/conv" + "github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/logger" ) @@ -56,7 +58,7 @@ func TestAliases(t *testing.T) { } // then - generatedBindings := string(rawGeneratedBindings) + generatedBindings := conv.BytesToString(rawGeneratedBindings) if generatedBindings != expectedTypeAliasBindings { t.Fatalf("the generated bindings does not match the expected ones.\nWanted:\n%s\n\nGot:\n%s", expectedTypeAliasBindings, generatedBindings) diff --git a/v2/internal/conv/conv.go b/v2/internal/conv/conv.go new file mode 100644 index 00000000000..5df69e2f851 --- /dev/null +++ b/v2/internal/conv/conv.go @@ -0,0 +1,11 @@ +package conv + +import ( + "unsafe" +) + +// BytesToString converts []byte to string without memory allocation. +// WARNING: The returned string must be used as read-only! +func BytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} diff --git a/v2/internal/frontend/desktop/darwin/clipboard.go b/v2/internal/frontend/desktop/darwin/clipboard.go index c40ba877175..ec82781a26e 100644 --- a/v2/internal/frontend/desktop/darwin/clipboard.go +++ b/v2/internal/frontend/desktop/darwin/clipboard.go @@ -4,6 +4,8 @@ package darwin import ( "os/exec" + + "github.com/wailsapp/wails/v2/internal/conv" ) func (f *Frontend) ClipboardGetText() (string, error) { @@ -12,7 +14,7 @@ func (f *Frontend) ClipboardGetText() (string, error) { if err != nil { return "", err } - return string(out), nil + return conv.BytesToString(out), nil } func (f *Frontend) ClipboardSetText(text string) error { diff --git a/v2/internal/frontend/desktop/darwin/frontend.go b/v2/internal/frontend/desktop/darwin/frontend.go index c9e70d4be2d..79d59c0460e 100644 --- a/v2/internal/frontend/desktop/darwin/frontend.go +++ b/v2/internal/frontend/desktop/darwin/frontend.go @@ -26,6 +26,7 @@ import ( "os" "unsafe" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/wailsapp/wails/v2/pkg/assetserver" "github.com/wailsapp/wails/v2/pkg/assetserver/webview" @@ -462,7 +463,7 @@ func (f *Frontend) Callback(message string) { if err != nil { panic(err) } - f.ExecJS(`window.wails.Callback(` + string(escaped) + `);`) + f.ExecJS(`window.wails.Callback(` + conv.BytesToString(escaped) + `);`) } func (f *Frontend) ExecJS(js string) { diff --git a/v2/internal/frontend/desktop/linux/frontend.go b/v2/internal/frontend/desktop/linux/frontend.go index c009a18cad2..3453c3ee3cb 100644 --- a/v2/internal/frontend/desktop/linux/frontend.go +++ b/v2/internal/frontend/desktop/linux/frontend.go @@ -516,7 +516,7 @@ func (f *Frontend) Callback(message string) { if err != nil { panic(err) } - f.ExecJS(`window.wails.Callback(` + string(escaped) + `);`) + f.ExecJS(`window.wails.Callback(` + conv.BytesToString(escaped) + `);`) } func (f *Frontend) startDrag() { diff --git a/v2/internal/frontend/desktop/linux/single_instance.go b/v2/internal/frontend/desktop/linux/single_instance.go index 0317dee4912..a827061e607 100644 --- a/v2/internal/frontend/desktop/linux/single_instance.go +++ b/v2/internal/frontend/desktop/linux/single_instance.go @@ -5,11 +5,12 @@ package linux import ( "encoding/json" - "github.com/godbus/dbus/v5" - "github.com/wailsapp/wails/v2/pkg/options" "log" "os" "strings" + + "github.com/godbus/dbus/v5" + "github.com/wailsapp/wails/v2/pkg/options" ) type dbusHandler func(string) @@ -68,7 +69,7 @@ func SetupSingleInstance(uniqueID string) { return } - err = conn.Object(dbusName, dbus.ObjectPath(dbusPath)).Call(dbusName+".SendMessage", 0, string(serialized)).Store() + err = conn.Object(dbusName, dbus.ObjectPath(dbusPath)).Call(dbusName+".SendMessage", 0, conv.BytesToString(serialized)).Store() if err != nil { return } diff --git a/v2/internal/frontend/desktop/windows/frontend.go b/v2/internal/frontend/desktop/windows/frontend.go index 5df13ed98a8..59671e6912c 100644 --- a/v2/internal/frontend/desktop/windows/frontend.go +++ b/v2/internal/frontend/desktop/windows/frontend.go @@ -872,7 +872,7 @@ func (f *Frontend) Callback(message string) { panic(err) } f.mainWindow.Invoke(func() { - f.chromium.Eval(`window.wails.Callback(` + string(escaped) + `);`) + f.chromium.Eval(`window.wails.Callback(` + conv.BytesToString(escaped) + `);`) }) } diff --git a/v2/internal/frontend/desktop/windows/single_instance.go b/v2/internal/frontend/desktop/windows/single_instance.go index a02b7edb926..b0855d625f5 100644 --- a/v2/internal/frontend/desktop/windows/single_instance.go +++ b/v2/internal/frontend/desktop/windows/single_instance.go @@ -4,13 +4,14 @@ package windows import ( "encoding/json" - "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" - "github.com/wailsapp/wails/v2/pkg/options" - "golang.org/x/sys/windows" "log" "os" "syscall" "unsafe" + + "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" + "github.com/wailsapp/wails/v2/pkg/options" + "golang.org/x/sys/windows" ) type COPYDATASTRUCT struct { @@ -63,7 +64,7 @@ func SetupSingleInstance(uniqueId string) { return } - SendMessage(hwnd, string(serialized)) + SendMessage(hwnd, conv.BytesToString(serialized)) // exit second instance of app after sending message os.Exit(0) } diff --git a/v2/internal/frontend/dispatcher/calls.go b/v2/internal/frontend/dispatcher/calls.go index ba10629137f..5b8bc7043d3 100644 --- a/v2/internal/frontend/dispatcher/calls.go +++ b/v2/internal/frontend/dispatcher/calls.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/wailsapp/wails/v2/internal/frontend" ) @@ -59,13 +60,13 @@ func (d *Dispatcher) processCallMessage(message string, sender frontend.Frontend callbackMessage.Result = result } messageData, err := json.Marshal(callbackMessage) - d.log.Trace("json call result data: %+v\n", string(messageData)) + d.log.Trace("json call result data: %+v\n", conv.BytesToString(messageData)) if err != nil { // what now? d.log.Fatal(err.Error()) } - return "c" + string(messageData), nil + return "c" + conv.BytesToString(messageData), nil } // CallbackMessage defines a message that contains the result of a call @@ -81,6 +82,6 @@ func (d *Dispatcher) NewErrorCallback(message string, callbackID string) (string Err: message, } messageData, err := json.Marshal(result) - d.log.Trace("json call result data: %+v\n", string(messageData)) - return string(messageData), err + d.log.Trace("json call result data: %+v\n", conv.BytesToString(messageData)) + return conv.BytesToString(messageData), err } diff --git a/v2/internal/frontend/dispatcher/securecalls.go b/v2/internal/frontend/dispatcher/securecalls.go index 8cdcdfb8507..40136883038 100644 --- a/v2/internal/frontend/dispatcher/securecalls.go +++ b/v2/internal/frontend/dispatcher/securecalls.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/wailsapp/wails/v2/internal/frontend" ) @@ -47,11 +48,11 @@ func (d *Dispatcher) processSecureCallMessage(message string, sender frontend.Fr callbackMessage.Result = result } messageData, err := json.Marshal(callbackMessage) - d.log.Trace("json call result data: %+v\n", string(messageData)) + d.log.Trace("json call result data: %+v\n", conv.BytesToString(messageData)) if err != nil { // what now? d.log.Fatal(err.Error()) } - return "c" + string(messageData), nil + return "c" + conv.BytesToString(messageData), nil } diff --git a/v2/internal/menumanager/contextmenu.go b/v2/internal/menumanager/contextmenu.go index f05bcdc49b8..c45157b2049 100644 --- a/v2/internal/menumanager/contextmenu.go +++ b/v2/internal/menumanager/contextmenu.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/wailsapp/wails/v2/pkg/menu" ) @@ -19,7 +20,7 @@ func (t *ContextMenu) AsJSON() (string, error) { if err != nil { return "", err } - return string(data), nil + return conv.BytesToString(data), nil } func NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu { diff --git a/v2/internal/menumanager/processedMenu.go b/v2/internal/menumanager/processedMenu.go index c87646ccba9..d410fdb98c2 100644 --- a/v2/internal/menumanager/processedMenu.go +++ b/v2/internal/menumanager/processedMenu.go @@ -3,6 +3,7 @@ package menumanager import ( "encoding/json" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/menu/keys" ) @@ -134,7 +135,7 @@ func (w *WailsMenu) AsJSON() (string, error) { if err != nil { return "", err } - return string(menuAsJSON), nil + return conv.BytesToString(menuAsJSON), nil } func (w *WailsMenu) processRadioGroups() { diff --git a/v2/internal/menumanager/traymenu.go b/v2/internal/menumanager/traymenu.go index 5efc4a86192..c3733206879 100644 --- a/v2/internal/menumanager/traymenu.go +++ b/v2/internal/menumanager/traymenu.go @@ -8,6 +8,7 @@ import ( "sync" "github.com/leaanthony/go-ansi-parser" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/pkg/errors" "github.com/wailsapp/wails/v2/pkg/menu" @@ -49,7 +50,7 @@ func (t *TrayMenu) AsJSON() (string, error) { if err != nil { return "", err } - return string(data), nil + return conv.BytesToString(data), nil } func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu { @@ -205,7 +206,7 @@ func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) { return "", errors.Wrap(err, "[UpdateTrayMenuLabel] ") } - return string(data), nil + return conv.BytesToString(data), nil } func (m *Manager) GetContextMenus() ([]string, error) { diff --git a/v2/internal/s/s.go b/v2/internal/s/s.go index 5ba0d2eaacf..4aa486e61a2 100644 --- a/v2/internal/s/s.go +++ b/v2/internal/s/s.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/bitfield/script" + "github.com/wailsapp/wails/v2/internal/conv" ) var ( @@ -263,7 +264,7 @@ func LOADSTRING(filename string) string { mute() data := LOADBYTES(filename) unmute() - return string(data) + return conv.BytesToString(data) } // SAVEBYTES will create a file with the given string diff --git a/v2/pkg/commands/bindings/bindings_test.go b/v2/pkg/commands/bindings/bindings_test.go index 53f42f2c78d..2bc4e372583 100644 --- a/v2/pkg/commands/bindings/bindings_test.go +++ b/v2/pkg/commands/bindings/bindings_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/matryer/is" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/wailsapp/wails/v2/pkg/templates" ) @@ -61,7 +62,7 @@ func TestGenerateBindings(t *testing.T) { i.NoErr(err) pathToRepository := filepath.Join(workingDirectory, "..", "..", "..") absPathToRepo, _ := filepath.Abs(pathToRepository) - goModString := string(goMod) + goModString := conv.BytesToString(goMod) goModSplit := strings.Split(goModString, "=>") goModSplit[1] = absPathToRepo goModString = strings.Join(goModSplit, "=> ") diff --git a/v2/pkg/commands/build/base.go b/v2/pkg/commands/build/base.go index 239932ce83d..6f574de604d 100644 --- a/v2/pkg/commands/build/base.go +++ b/v2/pkg/commands/build/base.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/pterm/pterm" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/wailsapp/wails/v2/internal/system" @@ -377,7 +378,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error { if err != nil { if options.Platform == "darwin" { output, _ := cmd.CombinedOutput() - stdErr := string(output) + stdErr := conv.BytesToString(output) if strings.Contains(err.Error(), "ld: framework not found UniformTypeIdentifiers") || strings.Contains(stdErr, "ld: framework not found UniformTypeIdentifiers") { pterm.Warning.Println(` diff --git a/v2/pkg/git/git.go b/v2/pkg/git/git.go index a0ac68ca95e..e476aaedb68 100644 --- a/v2/pkg/git/git.go +++ b/v2/pkg/git/git.go @@ -6,6 +6,7 @@ import ( "runtime" "strings" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/wailsapp/wails/v2/internal/shell" ) @@ -46,7 +47,7 @@ func EscapeName(str string) (string, error) { return "", err } // Remove the surrounding quotes - escaped := string(b[1 : len(b)-1]) + escaped := conv.BytesToString(b[1 : len(b)-1]) // Check if username is JSON compliant var js json.RawMessage diff --git a/v2/tools/release/release.go b/v2/tools/release/release.go index 4178fcc950f..9e579cbb2ca 100644 --- a/v2/tools/release/release.go +++ b/v2/tools/release/release.go @@ -9,6 +9,7 @@ import ( "time" "github.com/samber/lo" + "github.com/wailsapp/wails/v2/internal/conv" "github.com/wailsapp/wails/v2/internal/s" ) @@ -26,7 +27,7 @@ func checkError(err error) { func updateVersion() string { currentVersionData, err := os.ReadFile(versionFile) checkError(err) - currentVersion := string(currentVersionData) + currentVersion := conv.BytesToString(currentVersionData) vsplit := strings.Split(currentVersion, ".") minorVersion, err := strconv.Atoi(vsplit[len(vsplit)-1]) checkError(err) @@ -81,7 +82,7 @@ func main() { // Read in `src/pages/changelog.mdx` changelogData, err := os.ReadFile("src/pages/changelog.mdx") checkError(err) - changelog := string(changelogData) + changelog := conv.BytesToString(changelogData) // Split on the line that has `## [Unreleased]` changelogSplit := strings.Split(changelog, "## [Unreleased]") // Get today's date in YYYY-MM-DD format diff --git a/website/src/pages/changelog.mdx b/website/src/pages/changelog.mdx index 30a50c16ba4..3e700d79b57 100644 --- a/website/src/pages/changelog.mdx +++ b/website/src/pages/changelog.mdx @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `ContentProtection` option to allow hiding the application window from screen sharing software [#4241](https://github.com/wailsapp/wails/pull/4241) by [@Taiterbase](https://github.com/Taiterbase) - Added `build:tags` to project specification for automatically adding compilation tags by @symball in [PR](https://github.com/wailsapp/wails/pull/4439) - Support for binding generics in [PR](https://github.dev/wailsapp/wails/pull/3626) by @ktsivkov +- Add `dlvflag` for golang debug in [PR](https://github.com/wailsapp/wails/pull/4410) by @gongzhxu +- Standardized string conversions to avoid subtle memory/runtime in [PR](https://github.com/wailsapp/wails/pull/4410) by @gongzhxu ### Fixed - Added url validation for BrowserOpenURL by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/4484)