runtime: make SetFinalizer(x, f) accept any f for which f(x) is valid

Originally the requirement was f(x) where f's argument is
exactly x's type.

CL 11858043 relaxed the requirement in a non-standard
way: f's argument must be exactly x's type or interface{}.

If we're going to relax the requirement, it should be done
in a way consistent with the rest of Go. This CL allows f's
argument to have any type for which x is assignable;
that's the same requirement the compiler would impose
if compiling f(x) directly.

Fixes #5368.

R=dvyukov, bradfitz, pieter
CC=golang-dev
https://golang.org/cl/12895043
diff --git a/src/pkg/runtime/extern.go b/src/pkg/runtime/extern.go
index 92e63b2..527e9cd 100644
--- a/src/pkg/runtime/extern.go
+++ b/src/pkg/runtime/extern.go
@@ -130,7 +130,7 @@
 // The argument x must be a pointer to an object allocated by
 // calling new or by taking the address of a composite literal.
 // The argument f must be a function that takes a single argument
-// of x's type or interface{}, and can have arbitrary ignored return
+// to which x's type can be assigned, and can have arbitrary ignored return
 // values. If either of these is not true, SetFinalizer aborts the
 // program.
 //
diff --git a/src/pkg/runtime/iface.c b/src/pkg/runtime/iface.c
index b86bdd9..06a621a 100644
--- a/src/pkg/runtime/iface.c
+++ b/src/pkg/runtime/iface.c
@@ -482,6 +482,16 @@
 	ret->tab = itab(inter, t, 0);
 }
 
+bool
+runtime·ifaceE2I2(InterfaceType *inter, Eface e, Iface *ret)
+{
+	ret->tab = itab(inter, e.type, 1);
+	if(ret->tab == nil)
+		return false;
+	ret->data = e.data;
+	return true;
+}
+
 // For reflect
 //	func ifaceE2I(t *InterfaceType, e interface{}, dst *Iface)
 void
diff --git a/src/pkg/runtime/malloc.goc b/src/pkg/runtime/malloc.goc
index 15deb85..179a068 100644
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -741,6 +741,7 @@
 	Type *t;
 	Type *fint;
 	PtrType *ot;
+	Iface iface;
 
 	if(obj.type == nil) {
 		runtime·printf("runtime.SetFinalizer: first argument is nil interface\n");
@@ -755,7 +756,8 @@
 		goto throw;
 	}
 	nret = 0;
-	ot = nil;
+	ot = (PtrType*)obj.type;
+	fint = nil;
 	if(finalizer.type != nil) {
 		if(finalizer.type->kind != KindFunc)
 			goto badfunc;
@@ -763,9 +765,16 @@
 		if(ft->dotdotdot || ft->in.len != 1)
 			goto badfunc;
 		fint = *(Type**)ft->in.array;
-		if(fint->kind == KindInterface && ((InterfaceType*)fint)->mhdr.len == 0)
-			ot = (PtrType*)obj.type;
-		else if(fint != obj.type)
+		if(fint == obj.type) {
+			// ok - same type
+		} else if(fint->kind == KindPtr && (fint->x == nil || fint->x->name == nil || obj.type->x == nil || obj.type->x->name == nil) && ((PtrType*)fint)->elem == ((PtrType*)obj.type)->elem) {
+			// ok - not same type, but both pointers,
+			// one or the other is unnamed, and same element type, so assignable.
+		} else if(fint->kind == KindInterface && ((InterfaceType*)fint)->mhdr.len == 0) {
+			// ok - satisfies empty interface
+		} else if(fint->kind == KindInterface && runtime·ifaceE2I2((InterfaceType*)fint, obj, &iface)) {
+			// ok - satisfies non-empty interface
+		} else
 			goto badfunc;
 
 		// compute size needed for return parameters
@@ -776,14 +785,14 @@
 		nret = ROUND(nret, sizeof(void*));
 	}
 	
-	if(!runtime·addfinalizer(obj.data, finalizer.data, nret, ot)) {
+	if(!runtime·addfinalizer(obj.data, finalizer.data, nret, fint, ot)) {
 		runtime·printf("runtime.SetFinalizer: finalizer already set\n");
 		goto throw;
 	}
 	return;
 
 badfunc:
-	runtime·printf("runtime.SetFinalizer: second argument is %S, not func(%S) or func(interface{})\n", *finalizer.type->string, *obj.type->string);
+	runtime·printf("runtime.SetFinalizer: cannot pass %S to finalizer %S\n", *obj.type->string, *finalizer.type->string);
 throw:
 	runtime·throw("runtime.SetFinalizer");
 }
diff --git a/src/pkg/runtime/malloc.h b/src/pkg/runtime/malloc.h
index 3616654..7efe071 100644
--- a/src/pkg/runtime/malloc.h
+++ b/src/pkg/runtime/malloc.h
@@ -481,7 +481,6 @@
 void	runtime·helpgc(int32 nproc);
 void	runtime·gchelper(void);
 
-bool	runtime·getfinalizer(void *p, bool del, FuncVal **fn, uintptr *nret, void **ot);
 void	runtime·walkfintab(void (*fn)(void*));
 
 enum
diff --git a/src/pkg/runtime/mfinal.c b/src/pkg/runtime/mfinal.c
index 0412c8b..bd0b619 100644
--- a/src/pkg/runtime/mfinal.c
+++ b/src/pkg/runtime/mfinal.c
@@ -5,6 +5,7 @@
 #include "runtime.h"
 #include "arch_GOARCH.h"
 #include "malloc.h"
+#include "type.h"
 
 enum { debug = 0 };
 
@@ -13,7 +14,8 @@
 {
 	FuncVal *fn;
 	uintptr nret;
-	void *ot;
+	Type *fint;
+	PtrType *ot;
 };
 
 // Finalizer hash table.  Direct hash, linear scan, at most 3/4 full.
@@ -43,7 +45,7 @@
 } fintab[TABSZ];
 
 static void
-addfintab(Fintab *t, void *k, FuncVal *fn, uintptr nret, void *ot)
+addfintab(Fintab *t, void *k, FuncVal *fn, uintptr nret, Type *fint, PtrType *ot)
 {
 	int32 i, j;
 
@@ -68,6 +70,7 @@
 	t->key[i] = k;
 	t->val[i].fn = fn;
 	t->val[i].nret = nret;
+	t->val[i].fint = fint;
 	t->val[i].ot = ot;
 }
 
@@ -126,7 +129,7 @@
 	for(i=0; i<tab->max; i++) {
 		k = tab->key[i];
 		if(k != nil && k != (void*)-1)
-			addfintab(&newtab, k, tab->val[i].fn, tab->val[i].nret, tab->val[i].ot);
+			addfintab(&newtab, k, tab->val[i].fn, tab->val[i].nret, tab->val[i].fint, tab->val[i].ot);
 	}
 	
 	runtime·free(tab->key);
@@ -140,7 +143,7 @@
 }
 
 bool
-runtime·addfinalizer(void *p, FuncVal *f, uintptr nret, void *ot)
+runtime·addfinalizer(void *p, FuncVal *f, uintptr nret, Type *fint, PtrType *ot)
 {
 	Fintab *tab;
 	byte *base;
@@ -169,7 +172,7 @@
 		resizefintab(tab);
 	}
 
-	addfintab(tab, p, f, nret, ot);
+	addfintab(tab, p, f, nret, fint, ot);
 	runtime·setblockspecial(p, true);
 	runtime·unlock(tab);
 	return true;
@@ -178,7 +181,7 @@
 // get finalizer; if del, delete finalizer.
 // caller is responsible for updating RefHasFinalizer (special) bit.
 bool
-runtime·getfinalizer(void *p, bool del, FuncVal **fn, uintptr *nret, void **ot)
+runtime·getfinalizer(void *p, bool del, FuncVal **fn, uintptr *nret, Type **fint, PtrType **ot)
 {
 	Fintab *tab;
 	bool res;
@@ -192,6 +195,7 @@
 		return false;
 	*fn = f.fn;
 	*nret = f.nret;
+	*fint = f.fint;
 	*ot = f.ot;
 	return true;
 }
diff --git a/src/pkg/runtime/mfinal_test.go b/src/pkg/runtime/mfinal_test.go
index 98874a5..0d9b41b 100644
--- a/src/pkg/runtime/mfinal_test.go
+++ b/src/pkg/runtime/mfinal_test.go
@@ -12,55 +12,52 @@
 	"time"
 )
 
-func TestFinalizerTypeSucceed(t *testing.T) {
-	if runtime.GOARCH != "amd64" {
-		t.Skipf("Skipping on non-amd64 machine")
-	}
-	ch := make(chan bool)
-	func() {
-		v := new(int)
-		*v = 97531
-		runtime.SetFinalizer(v, func(v *int) {
-			if *v != 97531 {
-				t.Errorf("*int in finalizer has the wrong value: %d\n", *v)
-			}
-			close(ch)
-		})
-		v = nil
-	}()
-	runtime.GC()
-	select {
-	case <-ch:
-	case <-time.After(time.Second * 4):
-		t.Errorf("Finalizer set by SetFinalizer(*int, func(*int)) didn't run")
-	}
+type Tintptr *int // assignable to *int
+type Tint int     // *Tint implements Tinter, interface{}
+
+func (t *Tint) m() {}
+
+type Tinter interface {
+	m()
 }
 
-func TestFinalizerInterface(t *testing.T) {
+func TestFinalizerType(t *testing.T) {
 	if runtime.GOARCH != "amd64" {
 		t.Skipf("Skipping on non-amd64 machine")
 	}
-	ch := make(chan bool)
-	func() {
-		v := new(int)
-		*v = 97531
-		runtime.SetFinalizer(v, func(v interface{}) {
-			i, ok := v.(*int)
-			if !ok {
-				t.Errorf("Expected *int from interface{} in finalizer, got %v", *i)
-			}
-			if *i != 97531 {
-				t.Errorf("*int from interface{} has the wrong value: %d\n", *i)
-			}
-			close(ch)
-		})
-		v = nil
-	}()
-	runtime.GC()
-	select {
-	case <-ch:
-	case <-time.After(time.Second * 4):
-		t.Errorf("Finalizer set by SetFinalizer(*int, func(interface{})) didn't run")
+
+	ch := make(chan bool, 10)
+	finalize := func(x *int) {
+		if *x != 97531 {
+			t.Errorf("finalizer %d, want %d", *x, 97531)
+		}
+		ch <- true
+	}
+
+	var finalizerTests = []struct {
+		convert   func(*int) interface{}
+		finalizer interface{}
+	}{
+		{func(x *int) interface{} { return x }, func(v *int) { finalize(v) }},
+		{func(x *int) interface{} { return Tintptr(x) }, func(v Tintptr) { finalize(v) }},
+		{func(x *int) interface{} { return Tintptr(x) }, func(v *int) { finalize(v) }},
+		{func(x *int) interface{} { return (*Tint)(x) }, func(v *Tint) { finalize((*int)(v)) }},
+		{func(x *int) interface{} { return (*Tint)(x) }, func(v Tinter) { finalize((*int)(v.(*Tint))) }},
+	}
+
+	for _, tt := range finalizerTests {
+		func() {
+			v := new(int)
+			*v = 97531
+			runtime.SetFinalizer(tt.convert(v), tt.finalizer)
+			v = nil
+		}()
+		runtime.GC()
+		select {
+		case <-ch:
+		case <-time.After(time.Second * 4):
+			t.Errorf("Finalizer of type %T didn't run", tt.finalizer)
+		}
 	}
 }
 
diff --git a/src/pkg/runtime/mgc0.c b/src/pkg/runtime/mgc0.c
index 5c91388..6af75ae 100644
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -112,6 +112,7 @@
 	FuncVal *fn;
 	void *arg;
 	uintptr nret;
+	Type *fint;
 	PtrType *ot;
 };
 
@@ -1607,10 +1608,11 @@
 	FuncVal *fn;
 	uintptr nret;
 	PtrType *ot;
+	Type *fint;
 	FinBlock *block;
 	Finalizer *f;
 
-	if(!runtime·getfinalizer(p, true, &fn, &nret, &ot)) {
+	if(!runtime·getfinalizer(p, true, &fn, &nret, &fint, &ot)) {
 		runtime·setblockspecial(p, false);
 		runtime·MProf_Free(p, size);
 		return false;
@@ -1633,6 +1635,7 @@
 	finq->cnt++;
 	f->fn = fn;
 	f->nret = nret;
+	f->fint = fint;
 	f->ot = ot;
 	f->arg = p;
 	runtime·unlock(&finlock);
@@ -2297,7 +2300,7 @@
 	FinBlock *fb, *next;
 	byte *frame;
 	uint32 framesz, framecap, i;
-	Eface *ef;
+	Eface *ef, ef1;
 
 	frame = nil;
 	framecap = 0;
@@ -2327,12 +2330,22 @@
 					frame = runtime·mallocgc(framesz, 0, FlagNoPointers|FlagNoInvokeGC);
 					framecap = framesz;
 				}
-				if(f->ot == nil)
+				if(f->fint == nil)
+					runtime·throw("missing type in runfinq");
+				if(f->fint->kind == KindPtr) {
+					// direct use of pointer
 					*(void**)frame = f->arg;
-				else {
+				} else if(((InterfaceType*)f->fint)->mhdr.len == 0) {
+					// convert to empty interface
 					ef = (Eface*)frame;
 					ef->type = f->ot;
 					ef->data = f->arg;
+				} else {
+					// convert to interface with methods, via empty interface.
+					ef1.type = f->ot;
+					ef1.data = f->arg;
+					if(!runtime·ifaceE2I2((InterfaceType*)f->fint, ef1, (Iface*)frame))
+						runtime·throw("invalid type conversion in runfinq");
 				}
 				reflect·call(f->fn, frame, framesz);
 				f->fn = nil;
diff --git a/src/pkg/runtime/runtime.h b/src/pkg/runtime/runtime.h
index c93a139..cc7ccd4 100644
--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -810,7 +810,6 @@
 uintptr	runtime·efacehash(Eface, uintptr);
 void*	runtime·malloc(uintptr size);
 void	runtime·free(void *v);
-bool	runtime·addfinalizer(void*, FuncVal *fn, uintptr, void*);
 void	runtime·runpanic(Panic*);
 uintptr	runtime·getcallersp(void*);
 int32	runtime·mcount(void);
@@ -1046,7 +1045,7 @@
 void	runtime·printcreatedby(G*);
 
 void	runtime·ifaceE2I(InterfaceType*, Eface, Iface*);
-
+bool	runtime·ifaceE2I2(InterfaceType*, Eface, Iface*);
 uintptr	runtime·memlimit(void);
 
 // float.c
diff --git a/src/pkg/runtime/type.h b/src/pkg/runtime/type.h
index 769a807..075fffd 100644
--- a/src/pkg/runtime/type.h
+++ b/src/pkg/runtime/type.h
@@ -98,3 +98,7 @@
 	Type;
 	Type *elem;
 };
+
+// Here instead of in runtime.h because it uses the type names.
+bool	runtime·addfinalizer(void*, FuncVal *fn, uintptr, Type*, PtrType*);
+bool	runtime·getfinalizer(void *p, bool del, FuncVal **fn, uintptr *nret, Type**, PtrType**);