detached 3751461

objc: support string arguments to Send/SendSuper (#18)

commit
375146121d78e0bcfd0a08556735914a895553de
author
TotallyGamerJet · · 2022-08-13 16:56 UTC
committer
GitHub · · 2022-08-13 16:56 UTC
parent
05ec841
signature
GPG Unverified4AEE18F83AFDEB23 The key is unknown
2 files · +61 -25
M objc/objc_runtime_darwin.go +26 -10
@@ -47,7 +47,8 @@ func (id ID) Class() Class {
4747
4848 // Send is a convenience method for sending messages to objects.
4949 func (id ID) Send(sel SEL, args ...interface{}) ID {
50- tmp := createArgs(id, sel, args...)
50+ tmp, keepAlive := createArgs(id, sel, args...)
51+ defer keepAlive()
5152 ret, _, _ := purego.SyscallN(objc_msgSend, tmp...)
5253 return ID(ret)
5354 }
@@ -66,16 +67,18 @@ func (id ID) SendSuper(sel SEL, args ...interface{}) ID {
6667 receiver: id,
6768 superClass: id.Class(),
6869 }
69- tmp := createArgs(0, sel, args...)
70+ tmp, keepAlive := createArgs(0, sel, args...)
71+ defer keepAlive()
7072 tmp[0] = uintptr(unsafe.Pointer(super)) // if createArgs splits the stack the pointer would be wrong
7173 ret, _, _ := purego.SyscallN(objc_msgSendSuper2, tmp...)
7274 return ID(ret)
7375 }
7476
75-func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr) {
77+func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr, keepAlive func()) {
7678 out = make([]uintptr, 2, len(args)+2)
7779 out[0] = uintptr(cls)
7880 out[1] = uintptr(sel)
81+ var alive []interface{}
7982 for _, a := range args {
8083 switch v := a.(type) {
8184 case ID:
@@ -92,7 +95,18 @@ func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr) {
9295 } else {
9396 out = append(out, uintptr(0))
9497 }
98+ case string:
99+ c := strings.CString(v)
100+ alive = append(alive, c)
101+ // NOTE: that although the pointer will be kept alive
102+ // if there are any stack splits before this uintptr
103+ // is no longer needed the pointer will point to random memory.
104+ out = append(out, uintptr(unsafe.Pointer(c)))
95105 case unsafe.Pointer:
106+ alive = append(alive, v)
107+ // NOTE: that although the pointer will be kept alive
108+ // if there are any stack splits before this uintptr
109+ // is no longer needed the pointer will point to random memory.
96110 out = append(out, uintptr(v))
97111 case uintptr:
98112 out = append(out, v)
@@ -104,7 +118,9 @@ func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr) {
104118 panic(fmt.Sprintf("objc: unknown type %T", v))
105119 }
106120 }
107- return out
121+ return out, func() {
122+ runtime.KeepAlive(alive)
123+ }
108124 }
109125
110126 // SEL is an opaque type that represents a method selector
@@ -192,7 +208,7 @@ type Ivar uintptr
192208
193209 // Offset returns the offset of an instance variable that can be used to assign and read the Ivar's value.
194210 //
195-// For instance variables of type id or other object types, call Ivar and SetIvar instead
211+// For instance variables of type ID or other object types, call Ivar and SetIvar instead
196212 // of using this offset to access the instance variable data directly.
197213 func (i Ivar) Offset() uintptr {
198214 ret, _, _ := purego.SyscallN(ivar_getOffset, uintptr(i))
@@ -213,18 +229,18 @@ func IMP(fn interface{}) _IMP {
213229 if x, ok := fn.(uintptr); ok {
214230 return _IMP(x)
215231 }
216- val := reflect.ValueOf(fn)
217- if val.Kind() != reflect.Func {
232+ ty := reflect.TypeOf(fn)
233+ if ty.Kind() != reflect.Func {
218234 panic("objc: not a function")
219235 }
220236 // IMP is stricter than a normal callback
221237 // id (*IMP)(id, SEL, ...)
222238 switch {
223- case val.Type().NumIn() < 2:
239+ case ty.NumIn() < 2:
224240 fallthrough
225- case val.Type().In(0).Kind() != reflect.Uintptr:
241+ case ty.In(0).Kind() != reflect.Uintptr:
226242 fallthrough
227- case val.Type().In(1).Kind() != reflect.Uintptr:
243+ case ty.In(1).Kind() != reflect.Uintptr:
228244 panic("objc: IMP must take a (id, SEL) as its first two arguments")
229245 }
230246 return _IMP(purego.NewCallback(fn))
@@ -47,7 +47,8 @@ func (id ID) Class() Class {
47 47
48 // Send is a convenience method for sending messages to objects.48 // Send is a convenience method for sending messages to objects.
49 func (id ID) Send(sel SEL, args ...interface{}) ID {49 func (id ID) Send(sel SEL, args ...interface{}) ID {
50- tmp := createArgs(id, sel, args...)50+ tmp, keepAlive := createArgs(id, sel, args...)
51+ defer keepAlive()
51 ret, _, _ := purego.SyscallN(objc_msgSend, tmp...)52 ret, _, _ := purego.SyscallN(objc_msgSend, tmp...)
52 return ID(ret)53 return ID(ret)
53 }54 }
@@ -66,16 +67,18 @@ func (id ID) SendSuper(sel SEL, args ...interface{}) ID {
66 receiver: id,67 receiver: id,
67 superClass: id.Class(),68 superClass: id.Class(),
68 }69 }
69- tmp := createArgs(0, sel, args...)70+ tmp, keepAlive := createArgs(0, sel, args...)
71+ defer keepAlive()
70 tmp[0] = uintptr(unsafe.Pointer(super)) // if createArgs splits the stack the pointer would be wrong72 tmp[0] = uintptr(unsafe.Pointer(super)) // if createArgs splits the stack the pointer would be wrong
71 ret, _, _ := purego.SyscallN(objc_msgSendSuper2, tmp...)73 ret, _, _ := purego.SyscallN(objc_msgSendSuper2, tmp...)
72 return ID(ret)74 return ID(ret)
73 }75 }
74 76
75-func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr) {77+func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr, keepAlive func()) {
76 out = make([]uintptr, 2, len(args)+2)78 out = make([]uintptr, 2, len(args)+2)
77 out[0] = uintptr(cls)79 out[0] = uintptr(cls)
78 out[1] = uintptr(sel)80 out[1] = uintptr(sel)
81+ var alive []interface{}
79 for _, a := range args {82 for _, a := range args {
80 switch v := a.(type) {83 switch v := a.(type) {
81 case ID:84 case ID:
@@ -92,7 +95,18 @@ func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr) {
92 } else {95 } else {
93 out = append(out, uintptr(0))96 out = append(out, uintptr(0))
94 }97 }
98+ case string:
99+ c := strings.CString(v)
100+ alive = append(alive, c)
101+ // NOTE: that although the pointer will be kept alive
102+ // if there are any stack splits before this uintptr
103+ // is no longer needed the pointer will point to random memory.
104+ out = append(out, uintptr(unsafe.Pointer(c)))
95 case unsafe.Pointer:105 case unsafe.Pointer:
106+ alive = append(alive, v)
107+ // NOTE: that although the pointer will be kept alive
108+ // if there are any stack splits before this uintptr
109+ // is no longer needed the pointer will point to random memory.
96 out = append(out, uintptr(v))110 out = append(out, uintptr(v))
97 case uintptr:111 case uintptr:
98 out = append(out, v)112 out = append(out, v)
@@ -104,7 +118,9 @@ func createArgs(cls ID, sel SEL, args ...interface{}) (out []uintptr) {
104 panic(fmt.Sprintf("objc: unknown type %T", v))118 panic(fmt.Sprintf("objc: unknown type %T", v))
105 }119 }
106 }120 }
107- return out121+ return out, func() {
122+ runtime.KeepAlive(alive)
123+ }
108 }124 }
109 125
110 // SEL is an opaque type that represents a method selector126 // SEL is an opaque type that represents a method selector
@@ -192,7 +208,7 @@ type Ivar uintptr
192 208
193 // Offset returns the offset of an instance variable that can be used to assign and read the Ivar's value.209 // Offset returns the offset of an instance variable that can be used to assign and read the Ivar's value.
194 //210 //
195-// For instance variables of type id or other object types, call Ivar and SetIvar instead211+// For instance variables of type ID or other object types, call Ivar and SetIvar instead
196 // of using this offset to access the instance variable data directly.212 // of using this offset to access the instance variable data directly.
197 func (i Ivar) Offset() uintptr {213 func (i Ivar) Offset() uintptr {
198 ret, _, _ := purego.SyscallN(ivar_getOffset, uintptr(i))214 ret, _, _ := purego.SyscallN(ivar_getOffset, uintptr(i))
@@ -213,18 +229,18 @@ func IMP(fn interface{}) _IMP {
213 if x, ok := fn.(uintptr); ok {229 if x, ok := fn.(uintptr); ok {
214 return _IMP(x)230 return _IMP(x)
215 }231 }
216- val := reflect.ValueOf(fn)232+ ty := reflect.TypeOf(fn)
217- if val.Kind() != reflect.Func {233+ if ty.Kind() != reflect.Func {
218 panic("objc: not a function")234 panic("objc: not a function")
219 }235 }
220 // IMP is stricter than a normal callback236 // IMP is stricter than a normal callback
221 // id (*IMP)(id, SEL, ...)237 // id (*IMP)(id, SEL, ...)
222 switch {238 switch {
223- case val.Type().NumIn() < 2:239+ case ty.NumIn() < 2:
224 fallthrough240 fallthrough
225- case val.Type().In(0).Kind() != reflect.Uintptr:241+ case ty.In(0).Kind() != reflect.Uintptr:
226 fallthrough242 fallthrough
227- case val.Type().In(1).Kind() != reflect.Uintptr:243+ case ty.In(1).Kind() != reflect.Uintptr:
228 panic("objc: IMP must take a (id, SEL) as its first two arguments")244 panic("objc: IMP must take a (id, SEL) as its first two arguments")
229 }245 }
230 return _IMP(purego.NewCallback(fn))246 return _IMP(purego.NewCallback(fn))
M objc/objc_runtime_darwin_test.go +35 -15
@@ -21,32 +21,32 @@ func init() {
2121 }
2222
2323 func ExampleAllocateClassPair() {
24- var class = objc.AllocateClassPair(objc.GetClass("NSObject\x00"), "FooObject\x00", 0)
25- class.AddMethod(objc.RegisterName("run\x00"), objc.IMP(func(self objc.ID, _cmd objc.SEL) {
24+ var class = objc.AllocateClassPair(objc.GetClass("NSObject"), "FooObject", 0)
25+ class.AddMethod(objc.RegisterName("run"), objc.IMP(func(self objc.ID, _cmd objc.SEL) {
2626 fmt.Println("Hello World!")
27- }), "v@:\x00")
27+ }), "v@:")
2828 class.Register()
2929
30- var fooObject = objc.ID(class).Send(objc.RegisterName("new\x00"))
31- fooObject.Send(objc.RegisterName("run\x00"))
30+ var fooObject = objc.ID(class).Send(objc.RegisterName("new"))
31+ fooObject.Send(objc.RegisterName("run"))
3232 // Output: Hello World!
3333 }
3434
3535 func ExampleClass_AddIvar() {
36- var class = objc.AllocateClassPair(objc.GetClass("NSObject\x00"), "BarObject\x00", 0)
37- class.AddIvar("bar\x00", int(0), "q\x00")
38- var barOffset = class.InstanceVariable("bar\x00").Offset()
39- class.AddMethod(objc.RegisterName("bar\x00"), objc.IMP(func(self objc.ID, _cmd objc.SEL) int {
36+ var class = objc.AllocateClassPair(objc.GetClass("NSObject"), "BarObject", 0)
37+ class.AddIvar("bar", int(0), "q")
38+ var barOffset = class.InstanceVariable("bar").Offset()
39+ class.AddMethod(objc.RegisterName("bar"), objc.IMP(func(self objc.ID, _cmd objc.SEL) int {
4040 return *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(self)) + barOffset))
41- }), "q@:\x00")
42- class.AddMethod(objc.RegisterName("setBar:\x00"), objc.IMP(func(self objc.ID, _cmd objc.SEL, bar int) {
41+ }), "q@:")
42+ class.AddMethod(objc.RegisterName("setBar:"), objc.IMP(func(self objc.ID, _cmd objc.SEL, bar int) {
4343 *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(self)) + barOffset)) = bar
44- }), "v@:q\x00")
44+ }), "v@:q")
4545 class.Register()
4646
47- var barObject = objc.ID(class).Send(objc.RegisterName("new\x00"))
48- barObject.Send(objc.RegisterName("setBar:\x00"), 123)
49- var bar = int(barObject.Send(objc.RegisterName("bar\x00")))
47+ var barObject = objc.ID(class).Send(objc.RegisterName("new"))
48+ barObject.Send(objc.RegisterName("setBar:"), 123)
49+ var bar = int(barObject.Send(objc.RegisterName("bar")))
5050 fmt.Println(bar)
5151 // Output: 123
5252 }
@@ -60,3 +60,23 @@ func ExampleIMP() {
6060
6161 // Output: IMP: 105 567
6262 }
63+
64+func ExampleID_SendSuper() {
65+ super := objc.AllocateClassPair(objc.GetClass("NSObject"), "SuperObject", 0)
66+ super.AddMethod(objc.RegisterName("doSomething"), objc.IMP(func(self objc.ID, _cmd objc.SEL) {
67+ fmt.Println("In Super!")
68+ }), "v@:")
69+ super.Register()
70+
71+ child := objc.AllocateClassPair(super, "ChildObject", 0)
72+ child.AddMethod(objc.RegisterName("doSomething"), objc.IMP(func(self objc.ID, _cmd objc.SEL) {
73+ fmt.Println("In Child")
74+ self.SendSuper(_cmd)
75+ }), "v@:")
76+ child.Register()
77+
78+ objc.ID(child).Send(objc.RegisterName("new")).Send(objc.RegisterName("doSomething"))
79+
80+ // Output: In Child
81+ // In Super!
82+}
@@ -21,32 +21,32 @@ func init() {
21 }21 }
22 22
23 func ExampleAllocateClassPair() {23 func ExampleAllocateClassPair() {
24- var class = objc.AllocateClassPair(objc.GetClass("NSObject\x00"), "FooObject\x00", 0)24+ var class = objc.AllocateClassPair(objc.GetClass("NSObject"), "FooObject", 0)
25- class.AddMethod(objc.RegisterName("run\x00"), objc.IMP(func(self objc.ID, _cmd objc.SEL) {25+ class.AddMethod(objc.RegisterName("run"), objc.IMP(func(self objc.ID, _cmd objc.SEL) {
26 fmt.Println("Hello World!")26 fmt.Println("Hello World!")
27- }), "v@:\x00")27+ }), "v@:")
28 class.Register()28 class.Register()
29 29
30- var fooObject = objc.ID(class).Send(objc.RegisterName("new\x00"))30+ var fooObject = objc.ID(class).Send(objc.RegisterName("new"))
31- fooObject.Send(objc.RegisterName("run\x00"))31+ fooObject.Send(objc.RegisterName("run"))
32 // Output: Hello World!32 // Output: Hello World!
33 }33 }
34 34
35 func ExampleClass_AddIvar() {35 func ExampleClass_AddIvar() {
36- var class = objc.AllocateClassPair(objc.GetClass("NSObject\x00"), "BarObject\x00", 0)36+ var class = objc.AllocateClassPair(objc.GetClass("NSObject"), "BarObject", 0)
37- class.AddIvar("bar\x00", int(0), "q\x00")37+ class.AddIvar("bar", int(0), "q")
38- var barOffset = class.InstanceVariable("bar\x00").Offset()38+ var barOffset = class.InstanceVariable("bar").Offset()
39- class.AddMethod(objc.RegisterName("bar\x00"), objc.IMP(func(self objc.ID, _cmd objc.SEL) int {39+ class.AddMethod(objc.RegisterName("bar"), objc.IMP(func(self objc.ID, _cmd objc.SEL) int {
40 return *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(self)) + barOffset))40 return *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(self)) + barOffset))
41- }), "q@:\x00")41+ }), "q@:")
42- class.AddMethod(objc.RegisterName("setBar:\x00"), objc.IMP(func(self objc.ID, _cmd objc.SEL, bar int) {42+ class.AddMethod(objc.RegisterName("setBar:"), objc.IMP(func(self objc.ID, _cmd objc.SEL, bar int) {
43 *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(self)) + barOffset)) = bar43 *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(self)) + barOffset)) = bar
44- }), "v@:q\x00")44+ }), "v@:q")
45 class.Register()45 class.Register()
46 46
47- var barObject = objc.ID(class).Send(objc.RegisterName("new\x00"))47+ var barObject = objc.ID(class).Send(objc.RegisterName("new"))
48- barObject.Send(objc.RegisterName("setBar:\x00"), 123)48+ barObject.Send(objc.RegisterName("setBar:"), 123)
49- var bar = int(barObject.Send(objc.RegisterName("bar\x00")))49+ var bar = int(barObject.Send(objc.RegisterName("bar")))
50 fmt.Println(bar)50 fmt.Println(bar)
51 // Output: 12351 // Output: 123
52 }52 }
@@ -60,3 +60,23 @@ func ExampleIMP() {
60 60
61 // Output: IMP: 105 56761 // Output: IMP: 105 567
62 }62 }
63+
64+func ExampleID_SendSuper() {
65+ super := objc.AllocateClassPair(objc.GetClass("NSObject"), "SuperObject", 0)
66+ super.AddMethod(objc.RegisterName("doSomething"), objc.IMP(func(self objc.ID, _cmd objc.SEL) {
67+ fmt.Println("In Super!")
68+ }), "v@:")
69+ super.Register()
70+
71+ child := objc.AllocateClassPair(super, "ChildObject", 0)
72+ child.AddMethod(objc.RegisterName("doSomething"), objc.IMP(func(self objc.ID, _cmd objc.SEL) {
73+ fmt.Println("In Child")
74+ self.SendSuper(_cmd)
75+ }), "v@:")
76+ child.Register()
77+
78+ objc.ID(child).Send(objc.RegisterName("new")).Send(objc.RegisterName("doSomething"))
79+
80+ // Output: In Child
81+ // In Super!
82+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close