Как мне получить содержимое частного рефлекса. Значение в go? - PullRequest
0 голосов
/ 18 февраля 2020

Я пытаюсь сделать принтер отладки общего назначения для сложных типов данных, потому что %v имеет тенденцию просто печатать значения указателя, а не то, на что они указывают. У меня все работает до тех пор, пока мне не придется иметь дело со структурами, содержащими поля reflect.Value.

Следующий демонстрационный код работает без ошибок: (https://play.golang.org/p/qvdRKc40R8k)

package main

import (
    "fmt"
    "reflect"
)

type MyStruct struct {
    i int
    R reflect.Value
}

func printContents(value interface{}) {
    // Omitted: check if value is actually a struct
    rv := reflect.ValueOf(value)
    for i := 0; i < rv.NumField(); i++ {
        fmt.Printf("%v: ", rv.Type().Field(i).Name)
        field := rv.Field(i)
        switch field.Kind() {
        case reflect.Int:
            fmt.Printf("%v", field.Int())
        case reflect.Struct:
            // Omitted: check if field is actually a reflect.Value to an int
            fmt.Printf("reflect.Value(%v)", field.Interface().(reflect.Value).Int())
        }
        fmt.Printf("\n")
    }
}

func main() {
    printContents(MyStruct{123, reflect.ValueOf(456)})
}

Это печатает:

i: 123
R: reflect.Value(456)

Однако, если я изменю имя поля R MyStruct на r, произойдет сбой:

panic: reflect.Value.Interface: cannot return value obtained from unexported field or method

Конечно, это ошибочно, потому что в противном случае это был бы способ перевести неэкспортированное поле в правильную Голанду, а это нет-нет.

Но это оставляет меня в затруднительном положении dry: Как я могу получить доступ ко всем неэкспортированный reflect.Value относится без использования Interface(), чтобы я мог просмотреть его содержимое и распечатать? Я просмотрел документацию по отражению и не нашел ничего полезного ...

1 Ответ

1 голос
/ 19 февраля 2020

После некоторого копания я нашел решение:

Единственный способ добраться до внутреннего reflect.Value - это вызвать Interface() и напечатать assert, но это вызовет pani c, если вызвал на неисследованное поле. Единственный способ обойти это - использовать пакет unsafe, чтобы очистить флаг только для чтения, чтобы метод Interface() думал, что он экспортируется, когда его нет (в основном мы подрываем систему типов):

type flag uintptr // reflect/value.go:flag

type flagROTester struct {
    A   int
    a   int // reflect/value.go:flagStickyRO
    int     // reflect/value.go:flagEmbedRO
    // Note: flagRO = flagStickyRO | flagEmbedRO
}

var flagOffset uintptr
var maskFlagRO flag
var hasExpectedReflectStruct bool

func initUnsafe() {
    if field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag"); ok {
        flagOffset = field.Offset
    } else {
        log.Println("go-describe: exposeInterface() is disabled because the " +
            "reflect.Value struct no longer has a flag field. Please open an " +
            "issue at https://github.com/kstenerud/go-describe/issues")
        hasExpectedReflectStruct = false
        return
    }

    rv := reflect.ValueOf(flagROTester{})
    getFlag := func(v reflect.Value, name string) flag {
        return flag(reflect.ValueOf(v.FieldByName(name)).FieldByName("flag").Uint())
    }
    flagRO := (getFlag(rv, "a") | getFlag(rv, "int")) ^ getFlag(rv, "A")
    maskFlagRO = ^flagRO

    if flagRO == 0 {
        log.Println("go-describe: exposeInterface() is disabled because the " +
            "reflect flag type no longer has a flagEmbedRO or flagStickyRO bit. " +
            "Please open an issue at https://github.com/kstenerud/go-describe/issues")
        hasExpectedReflectStruct = false
        return
    }

    hasExpectedReflectStruct = true
}

func canExposeInterface() bool {
    return hasExpectedReflectStruct
}

func exposeInterface(v reflect.Value) interface{} {
    pFlag := (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + flagOffset))
    *pFlag &= maskFlagRO
    return v.Interface()
}

Существуют предостережения, в которых unsafe недопустимо или нежелательно во всех средах, не говоря уже о том, что подрыв системы типов редко является правильным решением. Рекомендуется сделать такой код условным для тегов сборки и включить безопасную альтернативу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...