Skip to content

DeepCopy of time.Time generate invalid code #91

@Radek-Pysny

Description

@Radek-Pysny

Good day!

TL;DR: When I have custom struct with at least one field of type time.Time and I use goderive to generate DeepCopy implementation of that struct, the generated code tries to access private types of time package. This leads into code that does not compile!

If I made anything wrong, please ping me. Perhaps, I missed something important in documetation, or it is misuse... Thanks in advance

How to get to this state

Start Go module from scratch and add goderive as tool for Go module

go get -tool github.com/awalterschulze/goderive

Now we have in go.mod file:

module 15-goderive-issue

go 1.24

tool github.com/awalterschulze/goderive

require (
	github.com/awalterschulze/goderive v0.5.1 // indirect
	github.com/kisielk/gotool v1.0.0 // indirect
	golang.org/x/sys v0.5.0 // indirect
	golang.org/x/tools v0.6.0 // indirect
)

And let us introduce a small example with Xyz structure having field of type time.Time, so deep copy can be generated:

// main.go

package main

import (
	"fmt"
	"time"
)

type Xyz struct {
	timestamp time.Time
}

func (xyz *Xyz) Clone() *Xyz {
	if xyz == nil {
		return nil
	}

	var clone Xyz
	deriveDeepCopyXyz(&clone, xyz)

	return &clone
}

func main() {
	xyz := &Xyz{}
	fmt.Println(xyz)
}

Generate the "deep copy" using:

go tool goderive ./...

Generated code

Whole generated file is:

// Code generated by goderive DO NOT EDIT.

package main

import (
	"reflect"
	"time"
	"unsafe"
)

// deriveDeepCopyXyz recursively copies the contents of src into dst.
func deriveDeepCopyXyz(dst, src *Xyz) {
	func() {
		field := new(time.Time)
		deriveDeepCopy(field, &src.timestamp)
		dst.timestamp = *field
	}()
}

// deriveDeepCopy recursively copies the contents of src into dst.
func deriveDeepCopy(dst, src *time.Time) {
	src_v := reflect.Indirect(reflect.ValueOf(src))
	dst_v := reflect.Indirect(reflect.ValueOf(dst))
	*(*uint64)(unsafe.Pointer(dst_v.FieldByName("wall").UnsafeAddr())) = *(*uint64)(unsafe.Pointer(src_v.FieldByName("wall").UnsafeAddr()))
	*(*int64)(unsafe.Pointer(dst_v.FieldByName("ext").UnsafeAddr())) = *(*int64)(unsafe.Pointer(src_v.FieldByName("ext").UnsafeAddr()))
	if *(**time.Location)(unsafe.Pointer(src_v.FieldByName("loc").UnsafeAddr())) == nil {
		*(**time.Location)(unsafe.Pointer(dst_v.FieldByName("loc").UnsafeAddr())) = nil
	} else {
		*(**time.Location)(unsafe.Pointer(dst_v.FieldByName("loc").UnsafeAddr())) = new(time.Location)
		deriveDeepCopy_(*(**time.Location)(unsafe.Pointer(dst_v.FieldByName("loc").UnsafeAddr())), *(**time.Location)(unsafe.Pointer(src_v.FieldByName("loc").UnsafeAddr())))
	}
}

// deriveDeepCopy_ recursively copies the contents of src into dst.
func deriveDeepCopy_(dst, src *time.Location) {
	src_v := reflect.Indirect(reflect.ValueOf(src))
	dst_v := reflect.Indirect(reflect.ValueOf(dst))
	*(*string)(unsafe.Pointer(dst_v.FieldByName("name").UnsafeAddr())) = *(*string)(unsafe.Pointer(src_v.FieldByName("name").UnsafeAddr()))
	if *(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr())) == nil {
		*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())) = nil
	} else {
		if *(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())) != nil {
			if len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr()))) > len(*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr()))) {
				if cap(*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr()))) >= len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr()))) {
					*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())) = (*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())))[:len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr())))]
				} else {
					*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())) = make([]time.zone, len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr()))))
				}
			} else if len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr()))) < len(*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr()))) {
				*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())) = (*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())))[:len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr())))]
			}
		} else {
			*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())) = make([]time.zone, len(*(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr()))))
		}
		copy(*(*[]time.zone)(unsafe.Pointer(dst_v.FieldByName("zone").UnsafeAddr())), *(*[]time.zone)(unsafe.Pointer(src_v.FieldByName("zone").UnsafeAddr())))
	}
	if *(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr())) == nil {
		*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())) = nil
	} else {
		if *(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())) != nil {
			if len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr()))) > len(*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr()))) {
				if cap(*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr()))) >= len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr()))) {
					*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())) = (*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())))[:len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr())))]
				} else {
					*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())) = make([]time.zoneTrans, len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr()))))
				}
			} else if len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr()))) < len(*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr()))) {
				*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())) = (*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())))[:len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr())))]
			}
		} else {
			*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())) = make([]time.zoneTrans, len(*(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr()))))
		}
		copy(*(*[]time.zoneTrans)(unsafe.Pointer(dst_v.FieldByName("tx").UnsafeAddr())), *(*[]time.zoneTrans)(unsafe.Pointer(src_v.FieldByName("tx").UnsafeAddr())))
	}
	*(*string)(unsafe.Pointer(dst_v.FieldByName("extend").UnsafeAddr())) = *(*string)(unsafe.Pointer(src_v.FieldByName("extend").UnsafeAddr()))
	*(*int64)(unsafe.Pointer(dst_v.FieldByName("cacheStart").UnsafeAddr())) = *(*int64)(unsafe.Pointer(src_v.FieldByName("cacheStart").UnsafeAddr()))
	*(*int64)(unsafe.Pointer(dst_v.FieldByName("cacheEnd").UnsafeAddr())) = *(*int64)(unsafe.Pointer(src_v.FieldByName("cacheEnd").UnsafeAddr()))
	if *(**time.zone)(unsafe.Pointer(src_v.FieldByName("cacheZone").UnsafeAddr())) == nil {
		*(**time.zone)(unsafe.Pointer(dst_v.FieldByName("cacheZone").UnsafeAddr())) = nil
	} else {
		*(**time.zone)(unsafe.Pointer(dst_v.FieldByName("cacheZone").UnsafeAddr())) = new(time.zone)
		**(**time.zone)(unsafe.Pointer(dst_v.FieldByName("cacheZone").UnsafeAddr())) = **(**time.zone)(unsafe.Pointer(src_v.FieldByName("cacheZone").UnsafeAddr()))
	}
}

And the problematic part is in deriveDeepCopy_, where are directly accessed private structs like time.zone and time.zoneTrans. This

Image

Build process failing

Output from build:

# 15-goderive-issue
./derived.gen.go:39:15: name zone not exported by package time
./derived.gen.go:40:13: name zone not exported by package time
./derived.gen.go:42:16: name zone not exported by package time
./derived.gen.go:43:21: name zone not exported by package time
./derived.gen.go:44:22: name zone not exported by package time
./derived.gen.go:45:16: name zone not exported by package time
./derived.gen.go:47:92: name zone not exported by package time
./derived.gen.go:49:28: name zone not exported by package time
./derived.gen.go:50:15: name zone not exported by package time
./derived.gen.go:53:90: name zone not exported by package time
./derived.gen.go:53:90: too many errors

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions