opentofu/helper/shadow/closer.go
James Bardin 657932261b Make sure shadow.closeWalker doesn't copy Mutexes
The Close methods on shadow.Values require pointer receivers because
they contain a sync.Mutex, but that value was being copied through
Value.Interface by the closeWalker.  Because reflectwalk passes the
struct fields to the StructField method as they are defined in the
struct, and they may have been read as a value, we can't immediately
call Interface() to check the method set without possibly copying the
internal mutex values. Use the Implements method to first check if we
need to call Interface, and if it's not, then we can check if the value
is addressable.

Because of this use of reflection, we can't vet for the copying of these
locks. The minimal amount of code in the Close method left us only with
a race detected within the mutex itself, which leads to a stacktrace
pointing to the runtime rather than our code.
2017-07-07 11:20:54 -04:00

84 lines
1.8 KiB
Go

package shadow
import (
"fmt"
"io"
"reflect"
"github.com/hashicorp/go-multierror"
"github.com/mitchellh/reflectwalk"
)
// Close will close all shadow values within the given structure.
//
// This uses reflection to walk the structure, find all shadow elements,
// and close them. Currently this will only find struct fields that are
// shadow values, and not slice elements, etc.
func Close(v interface{}) error {
// We require a pointer so we can address the internal fields
val := reflect.ValueOf(v)
if val.Kind() != reflect.Ptr {
return fmt.Errorf("value must be a pointer")
}
// Walk and close
var w closeWalker
if err := reflectwalk.Walk(v, &w); err != nil {
return err
}
return w.Err
}
type closeWalker struct {
Err error
}
func (w *closeWalker) Struct(reflect.Value) error {
// Do nothing. We implement this for reflectwalk.StructWalker
return nil
}
var closerType = reflect.TypeOf((*io.Closer)(nil)).Elem()
func (w *closeWalker) StructField(f reflect.StructField, v reflect.Value) error {
// Not sure why this would be but lets avoid some panics
if !v.IsValid() {
return nil
}
// Empty for exported, so don't check unexported fields
if f.PkgPath != "" {
return nil
}
// Verify the io.Closer is in this package
typ := v.Type()
if typ.PkgPath() != "github.com/hashicorp/terraform/helper/shadow" {
return nil
}
var closer io.Closer
if v.Type().Implements(closerType) {
closer = v.Interface().(io.Closer)
} else if v.CanAddr() {
// The Close method may require a pointer receiver, but we only have a value.
v := v.Addr()
if v.Type().Implements(closerType) {
closer = v.Interface().(io.Closer)
}
}
if closer == nil {
return reflectwalk.SkipEntry
}
// Close it
if err := closer.Close(); err != nil {
w.Err = multierror.Append(w.Err, err)
}
// Don't go into the struct field
return reflectwalk.SkipEntry
}