Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/link: -X on a constant, type, or func doesn't report an error #47072

Open
colin-sitehost opened this issue Jul 6, 2021 · 12 comments
Open

cmd/link: -X on a constant, type, or func doesn't report an error #47072

colin-sitehost opened this issue Jul 6, 2021 · 12 comments

Comments

@colin-sitehost
Copy link

@colin-sitehost colin-sitehost commented Jul 6, 2021

What version of Go are you using (go version)?

$ go version
go version go1.16.5 linux/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/user/.cache/go-build"
GOENV="/home/user/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/user/go/pkg/mod"
GOOS="linux"
GOPATH="/home/user/go"
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.16.5"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/path/to/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build1151947343=/tmp/go-build -gno-record-gcc-switches"

What did you do?

package main

import "fmt"

const Const = "one"

func Func() string { return "two" }

type Type struct{}

func main() {
	fmt.Println(Const, Func(), Type{})
}
go run -ldflags "-X=main.Const=three -X=main.Func=four -X=main.Type=five" .

What did you expect to see?

main.Const: cannot set with -X: const not supported
main.Func: cannot set with -X: func not supported
main.Type: cannot set with -X: type not supported

What did you see instead?

one two {}
@smasher164 smasher164 changed the title ldflags X compiles as nop for non var types cmd/compile: ldflags X compiles as nop for non-var types Jul 7, 2021
@mknyszek mknyszek added this to the Backlog milestone Jul 7, 2021
@mknyszek
Copy link
Contributor

@mknyszek mknyszek commented Jul 7, 2021

I don't think it's possible to override a constant at link time (ldflags is passing additional flags to the linker), because by that point the compiler has already propagated the constant. It doesn't exist anymore at that point. This is not only a technical limitation, but a language limitation, too. Note that constants are only compile-time, not link-time.

Variables on the other hand, do have a specific location that the linker can populate, hence how -X works.

@mknyszek mknyszek closed this Jul 7, 2021
@mknyszek mknyszek changed the title cmd/compile: ldflags X compiles as nop for non-var types cmd/link: -X does not work on constants Jul 7, 2021
@mknyszek
Copy link
Contributor

@mknyszek mknyszek commented Jul 7, 2021

Oh, and I should also mention that types are also only compile-time, hence that restriction. And finally, top-level functions are immutable.

@zikaeroh
Copy link
Contributor

@zikaeroh zikaeroh commented Jul 7, 2021

Unless I'm mistaken, the original issue was saying that these cases silently failed, not that they should work, hence the expected behavior being error messages, but the actual behavior being a runnable binary.

@zikaeroh
Copy link
Contributor

@zikaeroh zikaeroh commented Jul 7, 2021

And indeed:

$ cat main.go
package main

import "fmt"

const Const = "one"

func Func() string { return "two" }

type Type struct{}

func main() {
	fmt.Println(Const, Func(), Type{})
}

$ go run -ldflags "-X=main.Const=three -X=main.Func=four -X=main.Type=five" .
one two {}

To show that the -X flag names are valid, a regular var works:

$ cat main.go
package main

import "fmt"

var Var string

const Const = "one"

func Func() string { return "two" }

type Type struct{}

func main() {
	fmt.Println(Var, Const, Func(), Type{})
}

$ go run -ldflags "-X=main.Var=var -X=main.Const=three -X=main.Func=four -X=main.Type=five" .
var one two {}

@mknyszek
Copy link
Contributor

@mknyszek mknyszek commented Jul 7, 2021

@zikaeroh Got it. Thanks.

I'm not sure it's actually possible for the linker to know what Const, Func, or Type are, but it could at least say something like that relocation doesn't exist.

CC @cherrymui @thanm

@mknyszek mknyszek reopened this Jul 7, 2021
@mknyszek mknyszek changed the title cmd/link: -X does not work on constants cmd/link: -X on a constant, type, or func doesn't report an error Jul 7, 2021
@thanm
Copy link
Contributor

@thanm thanm commented Jul 7, 2021

This would be a relatively straightforward change to make, but I would be a little concerned that there is a lot of code that already relies on the current behavior (which is to say, if the "-X" symbol is not present or not a variable, the option is silently ignored).

In the current implementation, -X is silently ignored if:

  • the named symbol is not found
  • the symbol is not a variable
  • the symbol is dead

It would seem weird to add a new error for "symbol not a variable" and not have errors for the other cases.

@colin-sitehost
Copy link
Author

@colin-sitehost colin-sitehost commented Jul 7, 2021

if we are concerned with compatibility, just pix the fix to a version in go.mod; but it reads as undefined behaviour today, so I think we could convince ourselves that this is fine? clearly the current behaviour is different, but "try your best to set pkg.sym" seems like a bad interface, esp since some things can fail. (e.g. var is not string/[]byte)

I support returning errors for "not found" and "not var", not sure what "dead" means in this context?

@thanm
Copy link
Contributor

@thanm thanm commented Jul 8, 2021

I support returning errors for "not found" and "not var", not sure what "dead" means in this context?

By "dead" I mean unreachable/unreferenced (transitively) from main.main. The linker's "dead code" pass will remove such variables.

@colin-sitehost
Copy link
Author

@colin-sitehost colin-sitehost commented Jul 8, 2021

ok, so "dead" and "not found" are indistinguishable to this component of the linker?

if so, I am comfortable erroring on both, since saying I want to set the value of an unused thing feels orthogonal to the compiler erroring on s := "" and then never using s. (this may be doubly important to pin to a given go version.)

if not, I think there is still an argument to error on "dead" symbols, but if we can type check and it is a var _ string, I could be convinced ignoring that case is ok. (dead funcs, non string vars, etc. should all error.)

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jul 9, 2021

We have explicitly decided to not give an error when using -X with a variable that does not exist, because that breaks people who use -X in scripts (or go env -w) to build many programs, some having the variable and some not. See also #20649 and #30782.

@colin-sitehost
Copy link
Author

@colin-sitehost colin-sitehost commented Jul 9, 2021

I can respect that decision[0], but there is a little uncertainty I want to clear up; you mention the following across the two issues:

The -X option intentionally sets the variable if present and does nothing if absent.

We decided a long time ago that the linker should accept a -X option for an unknown symbol[.]

and based on @rsc's comment, it seems like you would be happy to error on type conflicts, but it was impossible/infeasible?

it seems like the issue was closed because there was no migration path. however, now that a path exists with the go version directive in modules, could we reopen #20649 to track "the symbol is not a variable" case?

that being said, I may be reading too much into #20649, since your comments seem to suggest that -X is merely best effort. would it would be more consistent to silently fail always, including for wrong var type:

main.foo: cannot set with -X: not a var of type string (type.[]uint8)
main.bar: cannot set with -X: not a var of type string (type.struct {})

0] I would also be remiss if I did not call out that while some people may find the feature helpful, this is a footgun. it does not appear to be well documented, and I had to go out of your way to notice that const foo = "changeme" is not being set correctly.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jul 10, 2021

I think it would be good if -X would fail if it is used to change a constant, type, or function. However, I don't see a way to implement that. I think we could make that change today if we could figure out how to do it.

I do not think that -X should give an error if it is used to change a variable that is not defined in a program. This doesn't have anything to do with a lack of a migration path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants