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/compile: inconsistent behaviour in multiple assignment #23017

Open
go101 opened this Issue Dec 6, 2017 · 10 comments

Comments

Projects
None yet
5 participants
@go101

go101 commented Dec 6, 2017

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

go version go1.9.2 linux/amd64

Does this issue reproduce with the latest release?

yes

What did you do?

Two programs:

program 1:

package main

import "fmt"

func main() {
  var m = map[int]int{}
  var p *int
  
  defer func() {
    fmt.Println(recover())
    fmt.Println(len(m)) // 0
  }()
  m[2], *p = 1, 2
}

program 2:

package main

import "fmt"

func main() {
  var m map[int]int
  var a int
  var p = &a
  
  defer func() {
    fmt.Println(recover())
    fmt.Println(*p) // 5
  }()
  *p, m[2] = 5, 2
}

What did you expect to see?

The behaviors of the two programs should be consistent.

What did you see instead?

The behaviors of the two programs are not consistent.

program 1 finishes zero assignments.
but program 2 finishes one assignment.

@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented Dec 6, 2017

The order of evaluation rules for the language do not specify precisely when a pointer indirection occurs. I think that either behavior is permitted by the language.

CC @griesemer

@ianlancetaylor ianlancetaylor added this to the Unplanned milestone Dec 6, 2017

@randall77

This comment has been minimized.

Contributor

randall77 commented Dec 6, 2017

From the spec:

x := []int{1, 2, 3}
type Point struct { x, y int }
var p *Point
x[2], p.x = 6, 7  // set x[2] = 6, then panic setting p.x = 7

That sounds like program 1, we should set m[2]=1 before panicking.

@griesemer

This comment has been minimized.

Contributor

griesemer commented Dec 6, 2017

[edited: The conclusion below is wrong, see next comment]

@randall77 I think this example in the spec is wrong. The prose before says:

The assignment proceeds in two phases. First, the operands of index expressions and pointer indirections (including implicit pointer indirections in selectors) on the left and the expressions on the right are all evaluated in the usual order. Second, the assignments are carried out in left-to-right order.

That is, we shouldn't get to the assignment if there's a nil-pointer exception. I'd conclude the behavior for program 1 is correct.

Secondly, I don't see anything in the spec stating when panics due to assignments to nil maps should happen in multiple assignments; I'd conclude that the behavior for program 2 is at least not incorrect.

We should a) fix the spec example, and b) decide if we want to say anything more about map assignments.

@griesemer

This comment has been minimized.

Contributor

griesemer commented Dec 6, 2017

Never mind; I misread. The operands are evaluated, but not the entire expression. So the spec example is correct, and program 1 should assign to m.

@mdempsky

This comment has been minimized.

Member

mdempsky commented Dec 6, 2017

My understanding of the spec is that m[2], *p = 1, 2 should be evaluated as:

// First, the operands of index expressions and pointer indirections
// (including implicit pointer indirections in selectors) on the left
// and the expressions on the right are all evaluated in the usual order.
t0, t1, t2, t3, t4 := m, 2, p, 1, 2

// Second, the assignments are carried out in left-to-right order.
t0[t1] = t3
*t2 = t4

That is, program 1 should print 1.

@griesemer griesemer added NeedsFix and removed Documentation labels Dec 6, 2017

@griesemer griesemer modified the milestones: Unplanned, Go1.11 Dec 6, 2017

@mdempsky

This comment has been minimized.

Member

mdempsky commented Dec 6, 2017

Related discussion: #15620.

@mdempsky

This comment has been minimized.

Member

mdempsky commented Dec 6, 2017

Program 1 is miscompiled by cmd/compile because order.go rewrites

m[2], *p = 1, 2

into

t0, t1 := m, 2
t2, *p = 1, 2
t0[t1] = t2
@mdempsky

This comment has been minimized.

Member

mdempsky commented Dec 6, 2017

gccgo prints 1 and 5 for programs 1 and 2, respectively.

@randall77

This comment has been minimized.

Contributor

randall77 commented Dec 11, 2017

Right, the spec says:

The assignment proceeds in two phases. First, the operands of index expressions and pointer indirections (including implicit pointer indirections in selectors) on the left and the expressions on the right are all evaluated in the usual order. Second, the assignments are carried out in left-to-right order.

So I take that to mean that in the first phase, we evaluate the right hand side, plus any arguments of * or [] (or . that really mean C's ->) on the left-hand side, but not the * or [] themselves. Then we do the assignments left-to-right. Both of OP's programs panic in phase 2, as the dereference on the left-hand side doesn't happen in phase 1.

See #22881 also.

@randall77

This comment has been minimized.

Contributor

randall77 commented Dec 12, 2017

OP's program 1 fails for all of go1.3-tip. Go 1.2 gets it right!
1.11 sounds correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment