mattn / anko

Scriptable interpreter written in golang
http://play-anko.appspot.com/
MIT License
1.45k stars 120 forks source link

when "new" and "make" structure, field will not be zero value. #344

Open For-ACGN opened 3 years ago

For-ACGN commented 3 years ago

test code:

type FooStruct struct {
    function  func(string)
    Pointer   *int
    Slice     []string
    Map       map[string]string
    Channel   chan string
    Function  func(string)
    Interface interface{}
    Transport http.RoundTripper
    Str2      FooStruct2
    Str2p     *FooStruct2
}

type FooStruct2 struct {
    Pointer *int
}

// Println is used to check structure fields are Zero Value.
func (f *FooStruct) Println() {
    fmt.Println("func(unexported):", f.function == nil)
    fmt.Println("pointer:", f.Pointer == nil)
    fmt.Println("slice:", f.Slice == nil)
    fmt.Println("map:", f.Map == nil)
    fmt.Println("chan:", f.Channel == nil)
    fmt.Println("func:", f.Function == nil)
    fmt.Println("interface{}:", f.Interface == nil)
    fmt.Println("interface:", f.Transport == nil)
    fmt.Println("str2:", f.Str2.Pointer == nil)
    fmt.Println("str2p:", f.Str2p == nil)
    fmt.Println()
}

func TestAnkoMakeStruct(t *testing.T) {
    // Zero Value
    fs1 := new(FooStruct)
    fs1.Println()
    fs2 := FooStruct{}
    fs2.Println()

    // some fields not Zero Value
    e := env.NewEnv()
    err := e.DefineType("FooStruct", reflect.TypeOf(fs1).Elem())
    require.NoError(t, err)
    src := `
fs1 = new(FooStruct)
fs1.Println()

fs2 = make(FooStruct)
fs2.Println()
`
    stmt, err := parser.ParseSrc(src)
    require.NoError(t, err)
    _, err = vm.Run(e, nil, stmt)
    require.NoError(t, err)
}

output:

func(unexported): true
pointer: true
slice: true
map: true
chan: true
func: true
interface{}: true
interface: true
str2: true
str2p: true

func(unexported): true
pointer: true
slice: true
map: true
chan: true
func: true
interface{}: true
interface: true
str2: true
str2p: true

func(unexported): true
pointer: true
slice: false
map: false
chan: false
func: false
interface{}: true
interface: true
str2: true
str2p: true

func(unexported): true
pointer: true
slice: false
map: false
chan: false
func: false
interface{}: true
interface: true
str2: true
str2p: true
slice, map, chan and func will not be set zero value, but pointer is zero value.
it will occur some package panic like net/http.Client
// code in src/net/http
func (c *Client) checkRedirect(req *Request, via []*Request) error {
    fn := c.CheckRedirect
    if fn == nil {       // [error] in anko, this if is unexpected.
        fn = defaultCheckRedirect
    }
    return fn(req, via)
}

func TestAnkoMakeHTTPClient(t *testing.T) {
    e := env.NewEnv()

    src := `
http = import("net/http")

// must 302
url = "http://example.com/302"
req, err = http.NewRequest(http.MethodGet, url, nil)
if err != nil {
    return false, err
}
client = new(http.Client)
resp, err = client.Do(req) // will return a nil pointer error
if err != nil {
    return false, err
}

println(client.CheckRedirect == nil) // false
println(client.CheckRedirect) // invalid function

// ok
client.CheckRedirect = nil
resp, err = client.Do(req)
if err != nil {
    return false, err
}
`
    stmt, err := parser.ParseSrc(src)
    require.NoError(t, err)
    _, err = vm.Run(e, nil, stmt)
    require.NoError(t, err)
}

file anko/vm/vm.go:

line 394:

func makeValue(t reflect.Type) (reflect.Value, error) {
    switch t.Kind() {
    case reflect.Chan:
        return reflect.MakeChan(t, 0), nil
    case reflect.Func:
        return reflect.MakeFunc(t, nil), nil
    case reflect.Map:
        // note creating slice as work around to create map
        // just doing MakeMap can give incorrect type for defined types
        value := reflect.MakeSlice(reflect.SliceOf(t), 0, 1)
        value = reflect.Append(value, reflect.MakeMap(reflect.MapOf(t.Key(), t.Elem())))
        return value.Index(0), nil
    case reflect.Ptr:
        ptrV := reflect.New(t.Elem())
        v, err := makeValue(t.Elem())
        if err != nil {
            return nilValue, err
        }

        ptrV.Elem().Set(v)
        return ptrV, nil
    case reflect.Slice:
        return reflect.MakeSlice(t, 0, 0), nil
    case reflect.Struct:
         structV := reflect.New(t).Elem()
         for i := 0; i < structV.NumField(); i++ {
                        // here Pointer will be zero value, but other type is not
            if structV.Field(i).Kind() == reflect.Ptr {
                continue
            }
            v, err := makeValue(structV.Field(i).Type())
            if err != nil {
                return nilValue, err
            }
            if structV.Field(i).CanSet() {
                structV.Field(i).Set(v)
            }
         }
         return structV, nil

            // only this code is ok
        // return reflect.New(t).Elem(), nil
    }
    return reflect.New(t).Elem(), nil
}