Each side of the language barrier maintains a map of reference numbers to objects. Each entry has a reference count that exactly matches the number of active proxy objects on the other side. When a reference crosses the barrier, the count is incremented and when a proxy finalizer is run, the count is decremented. If the count reaches 0, the reference number and its object are removed from the map. There is a possibility that a reference number is passed to the other side, and the last proxy is then immediately garbage collected and finalized. The reference counter then reaches 0 before the other side has converted the reference number to its object, crashing the program. This is possible in both Go/Java/ObjC but is most likely to happen in ObjC because its own automatic reference count runtime frees objects as soon as they are statically never referenced again. Fix the race by always incrementing the reference count before sending a reference across the barrier. When converting the reference back into an object on the other side, decrement the counter again. Only the new ObjC test fails without this fix, but I left the Java counterpart in for good measure. Change-Id: I92743aabec275b4a5b82b952052e7e284872ce02 Reviewed-on: https://go-review.googlesource.com/21311 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
151 lines
3.4 KiB
Go
151 lines
3.4 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package seq
|
|
|
|
//#cgo LDFLAGS: -llog
|
|
//#include <android/log.h>
|
|
//#include <string.h>
|
|
//import "C"
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"sync"
|
|
)
|
|
|
|
type countedObj struct {
|
|
obj interface{}
|
|
cnt int32
|
|
}
|
|
|
|
// also known to bind/java/Seq.java and bind/objc/seq_darwin.m
|
|
const NullRefNum = 41
|
|
|
|
// refs stores Go objects that have been passed to another language.
|
|
var refs struct {
|
|
sync.Mutex
|
|
next int32 // next reference number to use for Go object, always negative
|
|
refs map[interface{}]int32
|
|
objs map[int32]countedObj
|
|
}
|
|
|
|
func init() {
|
|
refs.Lock()
|
|
refs.next = -24 // Go objects get negative reference numbers. Arbitrary starting point.
|
|
refs.refs = make(map[interface{}]int32)
|
|
refs.objs = make(map[int32]countedObj)
|
|
refs.Unlock()
|
|
}
|
|
|
|
// A Ref represents a Java or Go object passed across the language
|
|
// boundary.
|
|
type Ref struct {
|
|
Bind_Num int32
|
|
}
|
|
|
|
type proxy interface {
|
|
// Use a strange name and hope that user code does not implement it
|
|
Bind_proxy_refnum__() int32
|
|
}
|
|
|
|
// ToRefNum increments the reference count for an object and
|
|
// returns its refnum.
|
|
func ToRefNum(obj interface{}) int32 {
|
|
// We don't track foreign objects, so if obj is a proxy
|
|
// return its refnum.
|
|
if r, ok := obj.(proxy); ok {
|
|
refnum := r.Bind_proxy_refnum__()
|
|
if refnum <= 0 {
|
|
panic(fmt.Errorf("seq: proxy contained invalid Go refnum: %d", refnum))
|
|
}
|
|
return refnum
|
|
}
|
|
refs.Lock()
|
|
num := refs.refs[obj]
|
|
if num != 0 {
|
|
s := refs.objs[num]
|
|
refs.objs[num] = countedObj{s.obj, s.cnt + 1}
|
|
} else {
|
|
num = refs.next
|
|
refs.next--
|
|
if refs.next > 0 {
|
|
panic("refs.next underflow")
|
|
}
|
|
refs.refs[obj] = num
|
|
refs.objs[num] = countedObj{obj, 1}
|
|
}
|
|
refs.Unlock()
|
|
|
|
return int32(num)
|
|
}
|
|
|
|
// FromRefNum returns the Ref for a refnum. If the refnum specifies a
|
|
// foreign object, a finalizer is set to track its lifetime.
|
|
func FromRefNum(num int32) *Ref {
|
|
if num == NullRefNum {
|
|
return nil
|
|
}
|
|
ref := &Ref{num}
|
|
if num > 0 {
|
|
// This is a foreign object reference.
|
|
// Track its lifetime with a finalizer.
|
|
runtime.SetFinalizer(ref, FinalizeRef)
|
|
}
|
|
|
|
return ref
|
|
}
|
|
|
|
// Bind_IncNum increments the foreign reference count and
|
|
// return the refnum.
|
|
func (r *Ref) Bind_IncNum() int32 {
|
|
refnum := r.Bind_Num
|
|
IncForeignRef(refnum)
|
|
return refnum
|
|
}
|
|
|
|
// Get returns the underlying object.
|
|
func (r *Ref) Get() interface{} {
|
|
refnum := r.Bind_Num
|
|
refs.Lock()
|
|
o, ok := refs.objs[refnum]
|
|
refs.Unlock()
|
|
if !ok {
|
|
panic(fmt.Sprintf("unknown ref %d", refnum))
|
|
}
|
|
// This is a Go reference and its refnum was incremented
|
|
// before crossing the language barrier.
|
|
Delete(refnum)
|
|
return o.obj
|
|
}
|
|
|
|
// Inc increments the reference count for a refnum. Called from Bind_proxy_refnum
|
|
// functions.
|
|
func Inc(num int32) {
|
|
refs.Lock()
|
|
o, ok := refs.objs[num]
|
|
if !ok {
|
|
panic(fmt.Sprintf("seq.Inc: unknown refnum: %d", num))
|
|
}
|
|
refs.objs[num] = countedObj{o.obj, o.cnt + 1}
|
|
refs.Unlock()
|
|
}
|
|
|
|
// Delete decrements the reference count and removes the pinned object
|
|
// from the object map when the reference count becomes zero.
|
|
func Delete(num int32) {
|
|
refs.Lock()
|
|
defer refs.Unlock()
|
|
o, ok := refs.objs[num]
|
|
if !ok {
|
|
panic(fmt.Sprintf("seq.Delete unknown refnum: %d", num))
|
|
}
|
|
if o.cnt <= 1 {
|
|
delete(refs.objs, num)
|
|
delete(refs.refs, o.obj)
|
|
} else {
|
|
refs.objs[num] = countedObj{o.obj, o.cnt - 1}
|
|
}
|
|
}
|