Commit cc158403 authored by David Crawshaw's avatar David Crawshaw

cmd/compile: track reflect.Type.Method in deadcode

In addition to reflect.Value.Call, exported methods can be invoked
by the Func value in the reflect.Method struct. This CL has the
compiler track what functions get access to a legitimate reflect.Method
struct by looking for interface calls to either of:

	Method(int) reflect.Method
	MethodByName(string) (reflect.Method, bool)

This is a little overly conservative. If a user implements a type
with one of these methods without using the underlying calls on
reflect.Type, the linker will assume the worst and include all
exported methods. But it's cheap.

No change to any of the binary sizes reported in cl/20483.

For #14740

Change-Id: Ie17786395d0453ce0384d8b240ecb043b7726137
Reviewed-on: https://go-review.googlesource.com/20489Reviewed-by: default avatarMatthew Dempsky <mdempsky@google.com>
parent 4112f0f7
...@@ -438,6 +438,9 @@ func compile(fn *Node) { ...@@ -438,6 +438,9 @@ func compile(fn *Node) {
if fn.Func.Pragma&Nosplit != 0 { if fn.Func.Pragma&Nosplit != 0 {
ptxt.From3.Offset |= obj.NOSPLIT ptxt.From3.Offset |= obj.NOSPLIT
} }
if fn.Func.ReflectMethod {
ptxt.From3.Offset |= obj.REFLECTMETHOD
}
if fn.Func.Pragma&Systemstack != 0 { if fn.Func.Pragma&Systemstack != 0 {
ptxt.From.Sym.Cfunc = 1 ptxt.From.Sym.Cfunc = 1
} }
......
...@@ -171,10 +171,11 @@ type Func struct { ...@@ -171,10 +171,11 @@ type Func struct {
Endlineno int32 Endlineno int32
WBLineno int32 // line number of first write barrier WBLineno int32 // line number of first write barrier
Pragma Pragma // go:xxx function annotations Pragma Pragma // go:xxx function annotations
Dupok bool // duplicate definitions ok Dupok bool // duplicate definitions ok
Wrapper bool // is method wrapper Wrapper bool // is method wrapper
Needctxt bool // function uses context register (has closure variables) Needctxt bool // function uses context register (has closure variables)
ReflectMethod bool // function calls reflect.Type.Method or MethodByName
} }
type Op uint8 type Op uint8
......
...@@ -632,6 +632,7 @@ opswitch: ...@@ -632,6 +632,7 @@ opswitch:
} }
case OCALLINTER: case OCALLINTER:
usemethod(n)
t := n.Left.Type t := n.Left.Type
if n.List.Len() != 0 && n.List.First().Op == OAS { if n.List.Len() != 0 && n.List.First().Op == OAS {
break break
...@@ -3765,6 +3766,48 @@ func bounded(n *Node, max int64) bool { ...@@ -3765,6 +3766,48 @@ func bounded(n *Node, max int64) bool {
return false return false
} }
// usemethod check interface method calls for uses of reflect.Type.Method.
func usemethod(n *Node) {
t := n.Left.Type
// Looking for either of:
// Method(int) reflect.Method
// MethodByName(string) (reflect.Method, bool)
//
// TODO(crawshaw): improve precision of match by working out
// how to check the method name.
if n := countfield(t.Params()); n != 1 {
return
}
if n := countfield(t.Results()); n != 1 && n != 2 {
return
}
p0 := t.Params().Field(0)
res0 := t.Results().Field(0)
var res1 *Type
if countfield(t.Results()) == 2 {
res1 = t.Results().Field(1)
}
if res1 == nil {
if p0.Type.Etype != TINT {
return
}
} else {
if p0.Type.Etype != TSTRING {
return
}
if res1.Type.Etype != TBOOL {
return
}
}
if Tconv(res0, 0) != "reflect.Method" {
return
}
Curfn.Func.ReflectMethod = true
}
func usefield(n *Node) { func usefield(n *Node) {
if obj.Fieldtrack_enabled == 0 { if obj.Fieldtrack_enabled == 0 {
return return
......
...@@ -315,6 +315,15 @@ type LSym struct { ...@@ -315,6 +315,15 @@ type LSym struct {
Leaf uint8 Leaf uint8
Seenglobl uint8 Seenglobl uint8
Onlist uint8 Onlist uint8
// ReflectMethod means the function may call reflect.Type.Method or
// reflect.Type.MethodByName. Matching is imprecise (as reflect.Type
// can be used through a custom interface), so ReflectMethod may be
// set in some cases when the reflect package is not called.
//
// Used by the linker to determine what methods can be pruned.
ReflectMethod bool
// Local means make the symbol local even when compiling Go code to reference Go // Local means make the symbol local even when compiling Go code to reference Go
// symbols in other shared libraries, as in this mode symbols are global by // symbols in other shared libraries, as in this mode symbols are global by
// default. "local" here means in the sense of the dynamic linker, i.e. not // default. "local" here means in the sense of the dynamic linker, i.e. not
......
...@@ -242,6 +242,9 @@ func flushplist(ctxt *Link, freeProgs bool) { ...@@ -242,6 +242,9 @@ func flushplist(ctxt *Link, freeProgs bool) {
if flag&NOSPLIT != 0 { if flag&NOSPLIT != 0 {
s.Nosplit = 1 s.Nosplit = 1
} }
if flag&REFLECTMETHOD != 0 {
s.ReflectMethod = true
}
s.Next = nil s.Next = nil
s.Type = STEXT s.Type = STEXT
s.Text = p s.Text = p
...@@ -460,7 +463,11 @@ func writesym(ctxt *Link, b *Biobuf, s *LSym) { ...@@ -460,7 +463,11 @@ func writesym(ctxt *Link, b *Biobuf, s *LSym) {
wrint(b, int64(s.Args)) wrint(b, int64(s.Args))
wrint(b, int64(s.Locals)) wrint(b, int64(s.Locals))
wrint(b, int64(s.Nosplit)) wrint(b, int64(s.Nosplit))
wrint(b, int64(s.Leaf)|int64(s.Cfunc)<<1) flags := int64(s.Leaf) | int64(s.Cfunc)<<1
if s.ReflectMethod {
flags |= 1 << 2
}
wrint(b, flags)
n := 0 n := 0
for a := s.Autom; a != nil; a = a.Link { for a := s.Autom; a != nil; a = a.Link {
n++ n++
......
...@@ -44,4 +44,7 @@ const ( ...@@ -44,4 +44,7 @@ const (
// Only valid on functions that declare a frame size of 0. // Only valid on functions that declare a frame size of 0.
// TODO(mwhudson): only implemented for ppc64x at present. // TODO(mwhudson): only implemented for ppc64x at present.
NOFRAME = 512 NOFRAME = 512
// Function can call reflect.Type.Method or reflect.Type.MethodByName.
REFLECTMETHOD = 1024
) )
...@@ -25,7 +25,7 @@ import ( ...@@ -25,7 +25,7 @@ import (
// //
// 1. direct call // 1. direct call
// 2. through a reachable interface type // 2. through a reachable interface type
// 3. reflect.Value.Call // 3. reflect.Value.Call / reflect.Method.Func
// //
// The first case is handled by the flood fill, a directly called method // The first case is handled by the flood fill, a directly called method
// is marked as reachable. // is marked as reachable.
...@@ -36,8 +36,10 @@ import ( ...@@ -36,8 +36,10 @@ import (
// as reachable. This is extremely conservative, but easy and correct. // as reachable. This is extremely conservative, but easy and correct.
// //
// The third case is handled by looking to see if reflect.Value.Call is // The third case is handled by looking to see if reflect.Value.Call is
// ever marked reachable. If it is, all bets are off and all exported // ever marked reachable, or if a reflect.Method struct is ever
// methods of reachable types are marked reachable. // constructed by a call to reflect.Type.Method or MethodByName. If it
// is, all bets are off and all exported methods of reachable types are
// marked reachable.
// //
// Any unreached text symbols are removed from ctxt.Textp. // Any unreached text symbols are removed from ctxt.Textp.
func deadcode(ctxt *Link) { func deadcode(ctxt *Link) {
...@@ -59,7 +61,7 @@ func deadcode(ctxt *Link) { ...@@ -59,7 +61,7 @@ func deadcode(ctxt *Link) {
callSymSeen := false callSymSeen := false
for { for {
if callSym != nil && callSym.Attr.Reachable() { if callSym != nil && (callSym.Attr.Reachable() || d.reflectMethod) {
// Methods are called via reflection. Give up on // Methods are called via reflection. Give up on
// static analysis, mark all exported methods of // static analysis, mark all exported methods of
// all reachable types as reachable. // all reachable types as reachable.
...@@ -169,6 +171,7 @@ type deadcodepass struct { ...@@ -169,6 +171,7 @@ type deadcodepass struct {
markQueue []*LSym // symbols to flood fill in next pass markQueue []*LSym // symbols to flood fill in next pass
ifaceMethod map[methodsig]bool // methods declared in reached interfaces ifaceMethod map[methodsig]bool // methods declared in reached interfaces
markableMethods []methodref // methods of reached types markableMethods []methodref // methods of reached types
reflectMethod bool
} }
func (d *deadcodepass) cleanupReloc(r *Reloc) { func (d *deadcodepass) cleanupReloc(r *Reloc) {
...@@ -188,6 +191,9 @@ func (d *deadcodepass) mark(s, parent *LSym) { ...@@ -188,6 +191,9 @@ func (d *deadcodepass) mark(s, parent *LSym) {
if s == nil || s.Attr.Reachable() { if s == nil || s.Attr.Reachable() {
return return
} }
if s.Attr.ReflectMethod() {
d.reflectMethod = true
}
s.Attr |= AttrReachable s.Attr |= AttrReachable
s.Reachparent = parent s.Reachparent = parent
d.markQueue = append(d.markQueue, s) d.markQueue = append(d.markQueue, s)
......
...@@ -105,6 +105,7 @@ const ( ...@@ -105,6 +105,7 @@ const (
AttrHidden AttrHidden
AttrOnList AttrOnList
AttrLocal AttrLocal
AttrReflectMethod
) )
func (a Attribute) DuplicateOK() bool { return a&AttrDuplicateOK != 0 } func (a Attribute) DuplicateOK() bool { return a&AttrDuplicateOK != 0 }
...@@ -118,6 +119,7 @@ func (a Attribute) StackCheck() bool { return a&AttrStackCheck != 0 } ...@@ -118,6 +119,7 @@ func (a Attribute) StackCheck() bool { return a&AttrStackCheck != 0 }
func (a Attribute) Hidden() bool { return a&AttrHidden != 0 } func (a Attribute) Hidden() bool { return a&AttrHidden != 0 }
func (a Attribute) OnList() bool { return a&AttrOnList != 0 } func (a Attribute) OnList() bool { return a&AttrOnList != 0 }
func (a Attribute) Local() bool { return a&AttrLocal != 0 } func (a Attribute) Local() bool { return a&AttrLocal != 0 }
func (a Attribute) ReflectMethod() bool { return a&AttrReflectMethod != 0 }
func (a Attribute) CgoExport() bool { func (a Attribute) CgoExport() bool {
return a.CgoExportDynamic() || a.CgoExportStatic() return a.CgoExportDynamic() || a.CgoExportStatic()
......
...@@ -54,8 +54,9 @@ package ld ...@@ -54,8 +54,9 @@ package ld
// - locals [int] // - locals [int]
// - nosplit [int] // - nosplit [int]
// - flags [int] // - flags [int]
// 1 leaf // 1<<0 leaf
// 2 C function // 1<<1 C function
// 1<<2 function may call reflect.Type.Method
// - nlocal [int] // - nlocal [int]
// - local [nlocal automatics] // - local [nlocal automatics]
// - pcln [pcln table] // - pcln [pcln table]
...@@ -264,7 +265,10 @@ overwrite: ...@@ -264,7 +265,10 @@ overwrite:
if rduint8(f) != 0 { if rduint8(f) != 0 {
s.Attr |= AttrNoSplit s.Attr |= AttrNoSplit
} }
rdint(f) // v&1 is Leaf, currently unused flags := rdint(f)
if flags&(1<<2) != 0 {
s.Attr |= AttrReflectMethod
}
n := rdint(f) n := rdint(f)
s.Autom = make([]Auto, n) s.Autom = make([]Auto, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
// This file defines flags attached to various functions // This file defines flags attached to various functions
// and data objects. The compilers, assemblers, and linker must // and data objects. The compilers, assemblers, and linker must
// all agree on these values. // all agree on these values.
//
// Keep in sync with src/cmd/internal/obj/textflag.go.
// Don't profile the marked routine. This flag is deprecated. // Don't profile the marked routine. This flag is deprecated.
#define NOPROF 1 #define NOPROF 1
...@@ -28,3 +30,5 @@ ...@@ -28,3 +30,5 @@
// Only valid on functions that declare a frame size of 0. // Only valid on functions that declare a frame size of 0.
// TODO(mwhudson): only implemented for ppc64x at present. // TODO(mwhudson): only implemented for ppc64x at present.
#define NOFRAME 512 #define NOFRAME 512
// Function can call reflect.Type.Method or reflect.Type.MethodByName.
#define REFLECTMETHOD = 1024
// run
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The linker can prune methods that are not directly called or
// assigned to interfaces, but only if reflect.Type.Method is
// never used. Test it here.
package main
import "reflect"
var called = false
type M int
func (m M) UniqueMethodName() {
called = true
}
var v M
func main() {
reflect.TypeOf(v).Method(0).Func.Interface().(func(M))(v)
if !called {
panic("UniqueMethodName not called")
}
}
// run
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The linker can prune methods that are not directly called or
// assigned to interfaces, but only if reflect.Type.MethodByName is
// never used. Test it here.
package main
import reflect1 "reflect"
var called = false
type M int
func (m M) UniqueMethodName() {
called = true
}
var v M
type MyType interface {
MethodByName(string) (reflect1.Method, bool)
}
func main() {
var t MyType = reflect1.TypeOf(v)
m, _ := t.MethodByName("UniqueMethodName")
m.Func.Interface().(func(M))(v)
if !called {
panic("UniqueMethodName not called")
}
}
// run
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The linker can prune methods that are not directly called or
// assigned to interfaces, but only if reflect.Type.Method is
// never used. Test it here.
package main
import "reflect"
var called = false
type M int
func (m M) UniqueMethodName() {
called = true
}
var v M
type MyType interface {
Method(int) reflect.Method
}
func main() {
var t MyType = reflect.TypeOf(v)
t.Method(0).Func.Interface().(func(M))(v)
if !called {
panic("UniqueMethodName not called")
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment