Skip to content

Commit cb6ac5a

Browse files
pgimalacstapelberg
authored andcommitted
internal/descfmt: fix dce with Go 1.26 by not calling MethodByName for Methods
Update internal/descfmt/stringer.go to avoid calling MethodByName("Methods"). This call makes the linker keep all methods named "Methods", in particular reflect.Value.Methods() (added in Go 1.26) will be kept, but it makes every exported method of every reachable type reachable, significantly increasing binary size. Fixes golang/protobuf#1704. Change-Id: I9b2f38a34ca7b9d1cbc50acab8367c740ec096c0 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/734280 Reviewed-by: Michael Stapelberg <stapelberg@google.com> Reviewed-by: Lasse Folger <lassefolger@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 232b109 commit cb6ac5a

1 file changed

Lines changed: 63 additions & 55 deletions

File tree

internal/descfmt/stringer.go

Lines changed: 63 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,13 @@ func formatDescOpt(t protoreflect.Descriptor, isRoot, allowMulti bool, record fu
261261
}...)
262262

263263
case protoreflect.ServiceDescriptor:
264-
rs.Append(rv, []methodAndName{
265-
{rv.MethodByName("Methods"), "Methods"},
266-
}...)
264+
// MethodByName(<constant-string>) makes the linker keep all methods with
265+
// the given name for all reachable types.
266+
// In particular for the name "Methods" it means the linker will keep
267+
// `reflect.Value.Methods()` and `reflect.Type.Methods()` with Go 1.26+,
268+
// which disable method dead code elimination entirely.
269+
// So we avoid using MethodByName for Methods.
270+
rs.appendCallResult(rv, "Methods", reflect.ValueOf(t.Methods()))
267271

268272
case protoreflect.MethodDescriptor:
269273
rs.Append(rv, []methodAndName{
@@ -299,66 +303,70 @@ func (rs *records) AppendRecs(fieldName string, newRecs [2]string) {
299303

300304
func (rs *records) Append(v reflect.Value, accessors ...methodAndName) {
301305
for _, a := range accessors {
302-
if rs.record != nil {
303-
rs.record(a.name)
304-
}
305306
var rv reflect.Value
306307
if a.method.IsValid() {
307308
rv = a.method.Call(nil)[0]
308309
}
309-
if v.Kind() == reflect.Struct && !rv.IsValid() {
310-
rv = v.FieldByName(a.name)
311-
}
312-
if !rv.IsValid() {
313-
panic(fmt.Sprintf("unknown accessor: %v.%s", v.Type(), a.name))
314-
}
315-
if _, ok := rv.Interface().(protoreflect.Value); ok {
316-
rv = rv.MethodByName("Interface").Call(nil)[0]
317-
if !rv.IsNil() {
318-
rv = rv.Elem()
319-
}
320-
}
310+
rs.appendCallResult(v, a.name, rv)
311+
}
312+
}
321313

322-
// Ignore zero values.
323-
var isZero bool
324-
switch rv.Kind() {
325-
case reflect.Interface, reflect.Slice:
326-
isZero = rv.IsNil()
327-
case reflect.Bool:
328-
isZero = rv.Bool() == false
329-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
330-
isZero = rv.Int() == 0
331-
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
332-
isZero = rv.Uint() == 0
333-
case reflect.String:
334-
isZero = rv.String() == ""
335-
}
336-
if n, ok := rv.Interface().(list); ok {
337-
isZero = n.Len() == 0
338-
}
339-
if isZero {
340-
continue
314+
func (rs *records) appendCallResult(val reflect.Value, name string, rv reflect.Value) {
315+
if rs.record != nil {
316+
rs.record(name)
317+
}
318+
if val.Kind() == reflect.Struct && !rv.IsValid() {
319+
rv = val.FieldByName(name)
320+
}
321+
if !rv.IsValid() {
322+
panic(fmt.Sprintf("unknown accessor: %v.%s", val.Type(), name))
323+
}
324+
if _, ok := rv.Interface().(protoreflect.Value); ok {
325+
rv = rv.MethodByName("Interface").Call(nil)[0]
326+
if !rv.IsNil() {
327+
rv = rv.Elem()
341328
}
329+
}
342330

343-
// Format the value.
344-
var s string
345-
v := rv.Interface()
346-
switch v := v.(type) {
347-
case list:
348-
s = formatListOpt(v, false, rs.allowMulti)
349-
case protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor:
350-
s = string(v.(protoreflect.Descriptor).Name())
351-
case protoreflect.Descriptor:
352-
s = string(v.FullName())
353-
case string:
354-
s = strconv.Quote(v)
355-
case []byte:
356-
s = fmt.Sprintf("%q", v)
357-
default:
358-
s = fmt.Sprint(v)
359-
}
360-
rs.recs = append(rs.recs, [2]string{a.name, s})
331+
// Ignore zero values.
332+
var isZero bool
333+
switch rv.Kind() {
334+
case reflect.Interface, reflect.Slice:
335+
isZero = rv.IsNil()
336+
case reflect.Bool:
337+
isZero = rv.Bool() == false
338+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
339+
isZero = rv.Int() == 0
340+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
341+
isZero = rv.Uint() == 0
342+
case reflect.String:
343+
isZero = rv.String() == ""
344+
}
345+
if n, ok := rv.Interface().(list); ok {
346+
isZero = n.Len() == 0
347+
}
348+
if isZero {
349+
return
350+
}
351+
352+
// Format the value.
353+
var s string
354+
v := rv.Interface()
355+
switch v := v.(type) {
356+
case list:
357+
s = formatListOpt(v, false, rs.allowMulti)
358+
case protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor:
359+
s = string(v.(protoreflect.Descriptor).Name())
360+
case protoreflect.Descriptor:
361+
s = string(v.FullName())
362+
case string:
363+
s = strconv.Quote(v)
364+
case []byte:
365+
s = fmt.Sprintf("%q", v)
366+
default:
367+
s = fmt.Sprint(v)
361368
}
369+
rs.recs = append(rs.recs, [2]string{name, s})
362370
}
363371

364372
func (rs *records) Join() string {

0 commit comments

Comments
 (0)