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

x/tools/go/ssa: wrong base pointer of loadconstraint when handling *ssa.Next in genInstr()@gen.go #45735

Open
april1989 opened this issue Apr 23, 2021 · 2 comments

Comments

@april1989
Copy link

@april1989 april1989 commented Apr 23, 2021

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

$ go version 1.16.3

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
GOHOSTARCH="amd64"
GOHOSTOS="darwin"

What did you do?

Run go pointer analysis under the directory /grpc-go/examples/features/xds/client (master with commit e38032e927812bb354297adcab933bedeff6c177) to analyze the main.go file. I do not think the commit is a necessary, probably the code from recent master is also able to reproduce the issue. I used the program below to run pointer analysis:

func main() {
	cfg := &packages.Config{
		Mode:  packages.LoadAllSyntax, // the level of information returned for each package
		Dir:   "",                     // directory in which to run the build system's query tool
		Tests: false,                  // setting Tests will include related test packages
	}
	initial, err := packages.Load(cfg)
	if err != nil {
		panic(fmt.Sprintln(err))
	}
	prog, pkgs := ssautil.AllPackages(initial, 0)
	prog.Build()
	mains, err := findMainPackages(pkgs)
	if err != nil {
		return
	}

	logfile, _ := os.Create("/Users/bozhen/Documents/GO2/default-x-tools/_logs/my_log_0") //replace with your log
	config := &pointer.Config{
		Mains:          mains,
		BuildCallGraph: true,
		Reflection:     false,
		Log:            logfile,
	}

	result, err := pointer.Analyze(config)
	if err != nil {
		panic(err) // internal error in pointer analysis
	}
	cg := result.CallGraph
	fmt.Println(cg)
}

func findMainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) {
	var mains []*ssa.Package
	for _, p := range pkgs {
		if p.Pkg.Name() == "main" && p.Func("main") != nil {
			mains = append(mains, p)
		}
	}
	if len(mains) == 0 { 
		return nil, fmt.Errorf("no main packages")
	}
	return mains, nil
}

What did you expect to see?

In the log, the constraints generated for IR statementt40 = next t34 in function (*google.golang.org/grpc/xds/internal/balancer/edsbalancer.balancerGroup).close should be:

; t40 = next t34
	localobj[t33] = n0
	load n319631 <- n319625[4]

where n319631 (the base pointer of the load) is from "Creating nodes for local values":

create n319629 bool for t40#0
create n319630 invalid type for t40#1
create n319631 *google.golang.org/grpc/xds/internal/balancer/edsbalancer.subBalancerWithConfig for t40#2
val[t40] = n319629  (*ssa.Next)

What did you see instead?

Instead, the generated constraints are:

; t40 = next t34
	localobj[t33] = n0
	load n319634 <- n319625[4]

where n319634 is from a irrelevant local variable at the begging of this function:

; *t4 = false:bool
	create n319634 bool for false:bool
	globalobj[false:bool] = n0
	val[false:bool] = n319634  (*ssa.Const)
	store n319598[0] <- n319634

I copied the grpc function here with which source code the above two IR statements mapped to:

func (bg *balancerGroup) close() {
	bg.incomingMu.Lock()
	if bg.incomingStarted { // !! this maps to IR: *t4 = false:bool
		bg.incomingStarted = false

		for _, pState := range bg.idToPickerState {
			// Reset everything to IDLE but keep the entry in map (to keep the
			// weight).
			pState.picker = nil
			pState.state = connectivity.Idle
		}

		// Also remove all SubConns.
		for sc := range bg.scToSubBalancer {
			bg.cc.RemoveSubConn(sc)
			delete(bg.scToSubBalancer, sc)
		}
	}
	bg.incomingMu.Unlock()

	bg.outgoingMu.Lock()
	if bg.outgoingStarted {
		bg.outgoingStarted = false
		for _, config := range bg.idToBalancerConfig { // !! this maps to IR: t40 = next t34
			config.stopBalancer()
		}
	}
	bg.outgoingMu.Unlock()
	// Clear(true) runs clear function to close sub-balancers in cache. It
	// must be called out of outgoing mutex.
	bg.balancerCache.Clear(true)
}

So, why is the node for t40 replaced by the node for t4 in the generated constraints, if my understanding is correct?

I checked the source code a little bit, found that the wrong mapping is because of the computation of odst += ksize when generating constraints for *ssa.Next and the key type is invalid (the code is at go/pointer/gen.go:1078 of the most recent commit when I submit this issue). Should not this be odst += 1? Because the algorithm only creates three pointers/nodes for this *ssa.Next instruction: ok, key and value. There is no flatten copy for key or value pointers. So, if the key type is invalid, shouldn't it skip this key node and use the value node (+= 1) as the load base, since the value will be extract later?

Thanks.

@cherrymui cherrymui changed the title Wrong base pointer of loadconstraint when handling *ssa.Next in genInstr()@gen.go x/tools/go/ssa: wrong base pointer of loadconstraint when handling *ssa.Next in genInstr()@gen.go Apr 26, 2021
@gopherbot gopherbot added this to the Unreleased milestone Apr 26, 2021
@cherrymui
Copy link
Contributor

@cherrymui cherrymui commented Apr 26, 2021

@timothy-king
Copy link
Contributor

@timothy-king timothy-king commented Apr 30, 2021

cc @alandonovan is the original author of the package and probably understands this code the best. Fixing this requires understanding an edge case in the pointer package, in particular the constraint generation for (*ssa.Next) on maps. Below is a summary of what is happening. I am not yet confident I understand what needs to be fixed.

I was able to reproduce the report following https://grpc.io/docs/languages/go/quickstart/.

Here is what is happening in way too much detail.

The instruction is:

        t40 = next t34      (ok bool, k invalid type, v *subBalancerWithConfig)

Note the invalid type k.

The local nodes are indeed as above (the constants differ from the original report):

        create n169136 bool for t40#0
        create n169137 invalid type for t40#1
        create n169138 *google.golang.org/grpc/xds/internal/balancer/edsbalancer.subBalancerWithConfig for t40#2
        val[t40] = n169136  (*ssa.Next)

At the end of the *ssa.Next case within genInstr() (line) the values of the following expressions are

ksize: 4
vsize: 1
osrc: 4
odst: 5
sz: 1
a.valueNode(instr): 169136
(a.valueNode(instr)+odst): 169141

From this we know the code took the else branch of if tTuple.At(1).Type() != tInvalid. This corresponds to k invalid type.

We then enter genLoad() here. The code looks for the objectNode() of the map t33. Nothing matches and we see the local object as n0. This matches this line in the output:

        localobj[t33] = n0

This means we fall into the load() case of genload(). This is called with the arguments:

load(dst: 169141, src: 169132, offset: 4, sizeof: 1)

This then creates the anomalous load constraint in the reported issue:

load n169141 <- n169132[4]

n169141 is just 169141 == a.valueNode(instr) + odst.

The region n169141 is created during the genIntr() while processing *t4 = false:bool as report. AFAICT what has happened is effectively a buffer overflow past the last region created during the "Creating nodes for local values". If so, the fact that they are connected is a coincidence.

All of that put together, it looks like (*ssa.Next) is trying to flatten the memory when the key is an invalid kind, but either:

  • is not allocating the additional memory cells when this is the case,
  • should not be flattening in the k is an invalid kind case,
  • this needs to have some kind of support from objectNode() that should be happening, or
  • the k invalid type needs to be fixed before pointer analysis.

Hopefully @alandonovan can give some direction on which of these (*ssa.Next) should be modeled as.

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
4 participants