1/*
2 * Implementation of support type for formal protocols.
3 *
4 * See the module DOCSTR for more information.
5 *
6 * XXX:
7 * - deal with optional methods (new in ObjC 2.0)
8 * - creating new protocols isn't actually supported in ObjC 2.0
9 */
10#include "pyobjc.h"
11
12/*
13 * FIXME: Looking in the Protocol structure is a rather crude hack, especially with the Objective-C 2.0
14 * runtime API. Too bad there is no real API for doing what we want...
15 */
16struct Protocol_struct {
17#ifndef __OBJC2__
18	@defs(Protocol);
19#else
20	char *protocol_name;
21	struct objc_protocol_list *protocol_list;
22	struct objc_method_description_list *instance_methods, *class_methods;
23#endif
24};
25
26PyDoc_STRVAR(proto_cls_doc,
27"objc.formal_protocol(name, supers, selector_list)\n"
28"\n"
29"This class is used to proxy Objective-C formal protocols, and can also be \n"
30"used to define new formal protocols.\n"
31"");
32
33typedef struct {
34	PyObject_HEAD
35
36	Protocol* objc;
37} PyObjCFormalProtocol;
38
39
40static void
41proto_dealloc(PyObject* object)
42{
43	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;
44	PyObjC_UnregisterPythonProxy(self->objc, object);
45	object->ob_type->tp_free(object);
46}
47
48
49static PyObject*
50proto_repr(PyObject* object)
51{
52	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;
53	const char* name;
54
55	name = protocol_getName(self->objc);
56	if (name == NULL) {
57		name = "<nil>";
58	}
59
60	return PyString_FromFormat("<%s %s at %p>", self->ob_type->tp_name, name, (void*)self);
61}
62
63static PyObject*
64proto_get__class__(PyObject* object __attribute__((__unused__)), void* closure __attribute__((__unused__)))
65{
66	return PyObjCClass_New([Protocol class]);
67}
68
69static PyObject*
70proto_get__name__(PyObject* object, void* closure __attribute__((__unused__)))
71{
72	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;
73	const char* name = protocol_getName(self->objc);
74
75	if (name == NULL) {
76		Py_INCREF(Py_None);
77		return Py_None;
78	}
79
80	return PyString_FromString(name);
81}
82
83
84static PyObject*
85proto_new(PyTypeObject* type __attribute__((__unused__)),
86	PyObject* args, PyObject* kwds)
87{
88static	char*	keywords[] = { "name", "supers", "selectors", NULL };
89	char* name;
90	PyObject* supers;
91	PyObject* selectors;
92	Py_ssize_t i, len;
93
94#ifndef __LP64__
95	PyObjCFormalProtocol* result = NULL;
96	Py_ssize_t numInstance = 0;
97	Py_ssize_t numClass = 0;
98	struct Protocol_struct* theProtocol = NULL;
99	struct objc_method_description* c;
100#endif
101
102	if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOO:formal_protocol",
103			keywords, &name, &supers, &selectors)) {
104		return NULL;
105	}
106
107	if (supers != Py_None) {
108		supers = PySequence_Fast(supers, "supers need to be a sequence of objc.formal_protocols");
109		if (supers == NULL) return NULL;
110		len = PySequence_Fast_GET_SIZE(supers);
111		for (i = 0; i < len; i++) {
112			PyObject* v = PySequence_Fast_GET_ITEM(supers, i);
113			if (!PyObjCFormalProtocol_Check(v)) {
114				PyErr_SetString(PyExc_TypeError, "supers need to be a sequence of objc.formal_protocols");
115				Py_DECREF(supers);
116				return NULL;
117			}
118		}
119
120	} else {
121		Py_INCREF(supers);
122	}
123
124	selectors = PySequence_Fast(selectors, "selectors need to be a sequence of selectors");
125	if (selectors == NULL) {
126		Py_DECREF(supers);
127		return NULL;
128	}
129
130#ifdef __LP64__
131	/* Radar #5272299: the objective-C 2.0 runtime API doesn't have an interface for constructing new
132	 * formal protocols. We can kind-of fake this for now in 32-bit mode, but that won't work in 64-bit
133	 * mode due to additional flexibility in that mode (which in itself is a good thing).
134	 */
135	Py_DECREF(selectors);
136	PyErr_SetString(PyObjCExc_Error, "The Objective-C 2.0 runtime API doesn't allow creation of formal protocols");
137	return NULL;
138
139#else
140	/*
141	 * The code below isn't actually supported on the ObjC 2.0 runtime, but happens to work
142	 * in 32-bit mode. Radar #5272299 asks for a formal API to do this.
143	 */
144
145
146	len = PySequence_Fast_GET_SIZE(selectors);
147	for (i = 0; i < len; i++) {
148		PyObject* sel = PySequence_Fast_GET_ITEM(selectors, i);
149		if (sel == NULL || !PyObjCSelector_Check(sel)) {
150			PyErr_SetString(PyExc_TypeError,
151				"selectors need to be a sequence of selectors");
152			Py_DECREF(supers);
153			Py_DECREF(selectors);
154			return NULL;
155		}
156		if (PyObjCSelector_GetFlags(sel)&PyObjCSelector_kCLASS_METHOD) {
157			numClass++;
158		} else {
159			numInstance++;
160		}
161	}
162
163	theProtocol = (struct Protocol_struct*)NSAllocateObject([Protocol class], 0, NULL);
164	if (theProtocol == NULL) {
165		PyErr_NoMemory();
166		goto error;
167	}
168
169
170	theProtocol->protocol_name = strdup(name);
171	if (theProtocol->protocol_name == NULL) {
172		PyErr_NoMemory();
173		goto error;
174	}
175
176	if (supers == Py_None) {
177		theProtocol->protocol_list = NULL;
178	} else {
179		len = PySequence_Fast_GET_SIZE(supers);
180		theProtocol->protocol_list = malloc(
181			sizeof(struct objc_protocol_list) +
182			(1+len) * sizeof(Protocol*));
183		theProtocol->protocol_list->next = NULL;
184		theProtocol->protocol_list->count = len;
185		for (i = 0; i < len; i++) {
186			PyObject* v = PySequence_Fast_GET_ITEM(supers, i);
187			theProtocol->protocol_list->list[i] =
188				PyObjCFormalProtocol_GetProtocol(v);
189			if (theProtocol->protocol_list->list[i] == NULL) {
190				goto error;
191			}
192		}
193		theProtocol->protocol_list->list[i] = NULL;
194	}
195
196	if (numInstance != 0) {
197		theProtocol->instance_methods = malloc(
198			sizeof(struct objc_method_description_list) +
199			(1+numInstance)  * sizeof(struct objc_method_description));
200		if (theProtocol->instance_methods == NULL) {
201			PyErr_NoMemory();
202			goto error;
203		}
204		theProtocol->instance_methods->count = 0;
205	}
206	if (numClass != 0) {
207		theProtocol->class_methods = malloc(
208			sizeof(struct objc_method_description_list) +
209			(1+numClass)  * sizeof(struct objc_method_description));
210		if (theProtocol->class_methods == NULL) {
211			PyErr_NoMemory();
212			goto error;
213		}
214		theProtocol->class_methods->count = 0;
215	}
216
217	len = PySequence_Fast_GET_SIZE(selectors);
218	for (i = 0; i < len; i++) {
219		PyObject* sel = PySequence_Fast_GET_ITEM(selectors, i);
220		SEL theSel = PyObjCSelector_GetSelector(sel);
221		char* theSignature = PyObjCSelector_Signature(sel);
222
223		if (PyObjCSelector_GetFlags(sel)&PyObjCSelector_kCLASS_METHOD) {
224			c = &(theProtocol->class_methods->list[
225				theProtocol->class_methods->count++]);
226			c->name = theSel;
227			c->types = strdup(theSignature);
228			if (c->types == NULL) goto error;
229		} else {
230			c = &(theProtocol->instance_methods->list[
231				theProtocol->instance_methods->count++]);
232			c->name = theSel;
233			c->types = strdup(theSignature);
234			if (c->types == NULL) goto error;
235		}
236	}
237
238	if (theProtocol->instance_methods) {
239		c = &(theProtocol->instance_methods->list[
240			theProtocol->instance_methods->count]);
241		c->name = NULL;
242		c->types = NULL;
243	}
244
245	if (theProtocol->class_methods) {
246		c = &(theProtocol->class_methods->list[
247			theProtocol->class_methods->count]);
248		c->name = NULL;
249		c->types = NULL;
250	}
251
252
253	result = (PyObjCFormalProtocol*)PyObject_New(
254			PyObjCFormalProtocol, &PyObjCFormalProtocol_Type);
255	if (result == NULL) goto error;
256
257	Py_DECREF(selectors);
258	Py_DECREF(supers);
259
260	result->objc = (Protocol*)theProtocol;
261	PyObjC_RegisterPythonProxy(result->objc, (PyObject*)result);
262	return (PyObject*)result;
263
264error:
265	Py_DECREF(selectors);
266	Py_DECREF(supers);
267
268	if (theProtocol == NULL) return NULL;
269
270	if (theProtocol->protocol_name != NULL) {
271		free(theProtocol->protocol_name);
272	}
273
274	if (theProtocol->protocol_list != NULL) {
275		free(theProtocol->protocol_list);
276	}
277
278	if (theProtocol->instance_methods != NULL) {
279		for (i = 0; i < theProtocol->instance_methods->count; i++) {
280			c = theProtocol->instance_methods->list + i;
281			if (c->name) {
282				free(c->name);
283			}
284		}
285		free(theProtocol->instance_methods);
286	}
287
288	if (theProtocol->class_methods != NULL) {
289		for (i = 0; i < theProtocol->class_methods->count; i++) {
290			c = theProtocol->class_methods->list + i;
291			if (c->name) {
292				free(c->name);
293			}
294		}
295		free(theProtocol->class_methods);
296	}
297	return NULL;
298
299#endif /* !__LP64__ */
300}
301
302static PyObject*
303proto_name(PyObject* object)
304{
305	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;
306	const char* name = protocol_getName(self->objc);
307
308	if (name == NULL) {
309		Py_INCREF(Py_None);
310		return Py_None;
311	}
312
313	return PyString_FromString(name);
314}
315
316static PyObject*
317proto_conformsTo_(PyObject* object, PyObject* args)
318{
319	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;
320	PyObject* protocol;
321	Protocol* objc_protocol;
322
323	if (!PyArg_ParseTuple(args, "O", &protocol)) {
324		return NULL;
325	}
326
327	if (!PyObjCFormalProtocol_Check(protocol)) {
328		PyErr_SetString(PyExc_TypeError, "Expecting a formal protocol");
329		return NULL;
330	}
331	objc_protocol = PyObjCFormalProtocol_GetProtocol(protocol);
332
333	if (protocol_conformsToProtocol(self->objc, objc_protocol)) {
334		return PyBool_FromLong(1);
335	} else {
336		return PyBool_FromLong(0);
337	}
338}
339
340static PyObject*
341descriptionForInstanceMethod_(PyObject* object, PyObject* sel)
342{
343	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;
344	SEL aSelector = NULL;
345	struct objc_method_description descr;
346
347	if (PyObjCSelector_Check(sel)) {
348		aSelector = PyObjCSelector_GetSelector(sel);
349	} else if (PyString_Check(sel)) {
350		char* s = PyString_AsString(sel);
351		if (*s == '\0') {
352			PyErr_SetString(PyExc_ValueError,
353					"empty selector name");
354			return NULL;
355		}
356
357		aSelector = sel_getUid(s);
358	} else {
359		PyErr_Format(PyExc_TypeError, "expecting a SEL, got instance of %s",
360				sel->ob_type->tp_name);
361		return NULL;
362	}
363
364	descr = protocol_getMethodDescription(self->objc, aSelector, YES, YES);
365	if (descr.name == NULL) {
366		descr = protocol_getMethodDescription(self->objc, aSelector, NO, YES);
367	}
368
369	if (descr.name == NULL) {
370		Py_INCREF(Py_None);
371		return Py_None;
372
373	} else {
374		return Py_BuildValue("(ss)",
375				sel_getName(descr.name),
376				descr.types);
377	}
378}
379
380static PyObject*
381descriptionForClassMethod_(PyObject* object, PyObject* sel)
382{
383	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;
384	SEL aSelector = NULL;
385	struct objc_method_description descr;
386
387	if (PyObjCSelector_Check(sel)) {
388		aSelector = PyObjCSelector_GetSelector(sel);
389	} else if (PyString_Check(sel)) {
390		char* s = PyString_AsString(sel);
391		if (*s == '\0') {
392			PyErr_SetString(PyExc_ValueError,
393					"empty selector name");
394			return NULL;
395		}
396
397		aSelector = sel_getUid(s);
398	}
399
400	descr = protocol_getMethodDescription(self->objc, aSelector, YES, NO);
401	if (descr.name == NULL) {
402		descr = protocol_getMethodDescription(self->objc, aSelector, NO, NO);
403	}
404	if (descr.name == NULL) {
405		Py_INCREF(Py_None);
406		return Py_None;
407	} else {
408		return Py_BuildValue("(ss)",
409			sel_getName(descr.name),
410			descr.types);
411	}
412}
413
414static PyMethodDef proto_methods[] = {
415	{
416		"name",
417		(PyCFunction)proto_name,
418		METH_NOARGS,
419		"Return the  protocol name",
420	},
421	{
422		"conformsTo_",
423		(PyCFunction)proto_conformsTo_,
424		METH_VARARGS,
425		"Does this protocol conform to another protocol"
426	},
427	{
428		"descriptionForInstanceMethod_",
429		(PyCFunction)descriptionForInstanceMethod_,
430		METH_O,
431		"Description for an instance method in the protocol"
432	},
433	{
434		"descriptionForClassMethod_",
435		(PyCFunction)descriptionForClassMethod_,
436		METH_O,
437		"Description for a class method in the protocol"
438	},
439	{ 0, 0, 0, 0 }
440};
441
442static PyGetSetDef proto_getset[] = {
443	{
444		"__class__",
445		(getter)proto_get__class__,
446		NULL,
447		NULL,
448		0
449	},
450	{
451		"__name__",
452		(getter)proto_get__name__,
453		NULL,
454		NULL,
455		0
456	},
457	{ NULL, NULL, NULL, NULL, 0 }
458};
459
460PyTypeObject PyObjCFormalProtocol_Type = {
461	PyObject_HEAD_INIT(&PyType_Type)
462	0,					/* ob_size */
463	"objc.formal_protocol",			/* tp_name */
464	sizeof(PyObjCFormalProtocol),		/* tp_basicsize */
465	0,					/* tp_itemsize */
466	/* methods */
467	proto_dealloc,	 			/* tp_dealloc */
468	0,					/* tp_print */
469	0,					/* tp_getattr */
470	0,					/* tp_setattr */
471	0,					/* tp_compare */
472	proto_repr,				/* tp_repr */
473	0,					/* tp_as_number */
474	0,					/* tp_as_sequence */
475	0,		       			/* tp_as_mapping */
476	0,					/* tp_hash */
477	0,					/* tp_call */
478	0,					/* tp_str */
479	PyObject_GenericGetAttr,		/* tp_getattro */
480	0,					/* tp_setattro */
481	0,					/* tp_as_buffer */
482	Py_TPFLAGS_DEFAULT,			/* tp_flags */
483 	proto_cls_doc,				/* tp_doc */
484 	0,					/* tp_traverse */
485 	0,					/* tp_clear */
486	0,					/* tp_richcompare */
487	0,					/* tp_weaklistoffset */
488	0,					/* tp_iter */
489	0,					/* tp_iternext */
490	proto_methods,				/* tp_methods */
491	0,					/* tp_members */
492	proto_getset,				/* tp_getset */
493	0,					/* tp_base */
494	0,					/* tp_dict */
495	0,					/* tp_descr_get */
496	0,					/* tp_descr_set */
497	0,					/* tp_dictoffset */
498	0,					/* tp_init */
499	0,					/* tp_alloc */
500	proto_new,				/* tp_new */
501	0,		        		/* tp_free */
502	0,					/* tp_is_gc */
503        0,                                      /* tp_bases */
504        0,                                      /* tp_mro */
505        0,                                      /* tp_cache */
506        0,                                      /* tp_subclasses */
507        0,                                      /* tp_weaklist */
508        0                                       /* tp_del */
509#if PY_VERSION_HEX >= 0x02060000
510	, 0                                     /* tp_version_tag */
511#endif
512
513};
514
515
516/*
517 * Find information about a selector in the protocol.
518 *
519 * Return NULL if no information can be found, but does not set an
520 * exception.
521 */
522const char*
523PyObjCFormalProtocol_FindSelectorSignature(PyObject* object, SEL selector, int isClassMethod)
524{
525	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;
526	struct objc_method_description descr;
527
528	descr = protocol_getMethodDescription(self->objc, selector, YES, !isClassMethod);
529	if (descr.name != NULL) {
530		return descr.types;
531	}
532	descr = protocol_getMethodDescription(self->objc, selector, NO, !isClassMethod);
533	if (descr.name != NULL) {
534		return descr.types;
535	}
536	return NULL;
537}
538
539static int
540do_verify(
541	const char* protocol_name,
542	struct objc_method_description* descr,
543	BOOL is_class,
544	BOOL is_required,
545	char* name,
546	PyObject* super_class,
547	PyObject* clsdict, PyObject* metadict)
548{
549	PyObject* meth;
550
551	if (is_class) {
552		meth = findSelInDict(metadict, descr->name);
553	} else {
554		meth = findSelInDict(clsdict, descr->name);
555	}
556	if (meth == NULL || !PyObjCSelector_Check(meth)) {
557
558		meth = PyObjCClass_FindSelector(super_class, descr->name, is_class);
559		if (meth == NULL || !PyObjCSelector_Check(meth)) {
560			if (is_required) {
561				PyErr_Format(PyExc_TypeError,
562					"class %s does not full implement protocol "
563					"%s: no implementation for %s",
564					name,
565					protocol_name,
566					sel_getName(descr->name));
567				return 0;
568			} else {
569				/* Method is not required, ignore */
570				return 1;
571			}
572		}
573	}
574
575	if (is_class) {
576		if (!PyObjCSelector_IsClassMethod(meth)) {
577			PyErr_Format(PyExc_TypeError,
578				"class %s does not correctly implement "
579				"protocol %s: method %s is not a "
580				"class method",
581				name,
582				protocol_name,
583				sel_getName(descr->name)
584			);
585			return 0;
586		}
587	} else {
588		if (PyObjCSelector_IsClassMethod(meth)) {
589			PyErr_Format(PyExc_TypeError,
590				"class %s does not correctly implement "
591				"protocol %s: method %s is not an "
592				"instance method",
593				name,
594				protocol_name,
595				sel_getName(descr->name)
596			);
597			return 0;
598		}
599	}
600
601	if (signaturesEqual(descr->types,
602				PyObjCSelector_Signature(meth))) {
603		return 1;
604	}
605
606	PyErr_Format(PyExc_TypeError,
607		"class %s does not correctly implement "
608		"protocol %s: the signature for method %s "
609		"is %s instead of %s",
610		name,
611		protocol_name,
612		sel_getName(descr->name),
613		PyObjCSelector_Signature(meth),
614		descr->types);
615	return 0;
616}
617
618static int
619do_check(
620    const char* protocol_name,
621    Protocol* protocol,
622    char* name,
623    PyObject* super_class,
624    PyObject* clsdict,
625    PyObject* metadict)
626{
627	int r;
628	unsigned idx;
629
630	unsigned parentCount;
631	Protocol** parents = protocol_copyProtocolList(protocol, &parentCount);
632	if (parents) {
633		for (idx = 0; idx < parentCount; idx++) {
634			r = do_check(protocol_name, parents[idx], name, super_class, clsdict, metadict);
635			if (r == 0) {
636				free(parents);
637				return r;
638			}
639		}
640		free(parents);
641	}
642
643	unsigned int methCount;
644	struct objc_method_description* methinfo;
645
646	methCount = 0;
647	methinfo = protocol_copyMethodDescriptionList(protocol, YES, YES, &methCount);
648	if (methinfo) {
649		for (idx = 0; idx < methCount; idx++) {
650			if (!do_verify(protocol_name, methinfo + idx, NO, YES, name, super_class, clsdict, metadict)) {
651				free(methinfo);
652				return 0;
653			}
654		}
655		free(methinfo);
656	}
657
658	methinfo = protocol_copyMethodDescriptionList(protocol, NO, YES, &methCount);
659	if (methinfo) {
660		for (idx = 0; idx < methCount; idx++) {
661			if (!do_verify(protocol_name, methinfo + idx, NO, NO, name, super_class, clsdict, metadict)) {
662				free(methinfo);
663				return 0;
664			}
665		}
666		free(methinfo);
667	}
668
669	methinfo = protocol_copyMethodDescriptionList(protocol, YES, NO, &methCount);
670	if (methinfo) {
671		for (idx = 0; idx < methCount; idx++) {
672			if (!do_verify(protocol_name, methinfo + idx, YES, YES, name, super_class, clsdict, metadict)) {
673				free(methinfo);
674				return 0;
675			}
676		}
677		free(methinfo);
678	}
679
680	methinfo = protocol_copyMethodDescriptionList(protocol, NO, NO, &methCount);
681	if (methinfo) {
682		for (idx = 0; idx < methCount; idx++) {
683			if (!do_verify(protocol_name, methinfo + idx, YES, NO, name, super_class, clsdict, metadict)) {
684				free(methinfo);
685				return 0;
686			}
687		}
688		free(methinfo);
689	}
690
691	return 1;
692}
693
694int
695PyObjCFormalProtocol_CheckClass(
696	PyObject* obj, char* name, PyObject* super_class, PyObject* clsdict, PyObject* metadict)
697{
698	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)obj;
699
700	if (!PyObjCFormalProtocol_Check(obj)) {
701		PyErr_Format(PyExc_TypeError,
702			"First argument is not an 'objc.formal_protocol' but "
703			"'%s'", obj->ob_type->tp_name);
704		return 0;
705	}
706	if (!PyObjCClass_Check(super_class)) {
707		PyErr_Format(PyExc_TypeError,
708			"Third argument is not an 'objc.objc_class' but "
709			"'%s'", super_class->ob_type->tp_name);
710		return 0;
711	}
712	if (!PyDict_Check(clsdict)) {
713		PyErr_Format(PyExc_TypeError,
714			"Fourth argument is not a 'dict' but '%s'",
715			clsdict->ob_type->tp_name);
716		return 0;
717	}
718
719	return do_check(protocol_getName(self->objc), self->objc, name, super_class, clsdict, metadict);
720}
721
722PyObject* PyObjCFormalProtocol_ForProtocol(Protocol* protocol)
723{
724	PyObjCFormalProtocol* result;
725
726	result = (PyObjCFormalProtocol*)PyObject_New(
727			PyObjCFormalProtocol, &PyObjCFormalProtocol_Type);
728	if (result == NULL) {
729		return NULL;
730	}
731
732	result->objc = protocol;
733	PyObjC_RegisterPythonProxy(result->objc, (PyObject*)result);
734	return (PyObject*)result;
735}
736
737Protocol* PyObjCFormalProtocol_GetProtocol(PyObject* object)
738{
739	PyObjCFormalProtocol* self = (PyObjCFormalProtocol*)object;
740
741	if (!PyObjCFormalProtocol_Check(self)) {
742		PyErr_Format(PyExc_TypeError,
743			"Expecting objc.formal_protocol, got %s",
744			self->ob_type->tp_name);
745		return NULL;
746	}
747
748	return self->objc;
749}
750