1 /*
2 Permission is hereby granted, free of charge, to any person obtaining a copy of
3 this software and associated documentation files (the "Software"), to deal in
4 the Software without restriction, including without limitation the rights to
5 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
6 of the Software, and to permit persons to whom the Software is furnished to do
7 so, subject to the following conditions:
8 
9 The above copyright notice and this permission notice shall be included in all
10 copies or substantial portions of the Software.
11 
12 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18 SOFTWARE.
19 */
20 
21 module pyd.references;
22 
23 import std.traits;
24 import std.typetuple;
25 import std.string: format;
26 import std.exception: enforce;
27 
28 import pyd.func_wrap;
29 import deimos.python.Python;
30 import util.multi_index;
31 import util.typeinfo;
32 
33 // s/wrapped_class_object!T/PyObject/
34 
35 /**
36  * A useful check for whether a given class has been wrapped. Mainly used by
37  * the conversion functions (see make_object.d), but possibly useful elsewhere.
38  */
39 template is_wrapped(T) {
40     alias pyd_references!T.Mapping Mapping;
41 
42     static if(!is(Mapping.TypeInfoType!T == T)) {
43         alias is_wrapped!(Mapping.TypeInfoType!T) is_wrapped;
44     }else{
45         bool is_wrapped = false;
46     }
47 }
48 
49 /// _
50 // wrapped_class_type
51 template PydTypeObject(T) {
52     alias pyd_references!T.Mapping Mapping;
53 
54     static if(!is(Mapping.TypeInfoType!T == T)) {
55         alias PydTypeObject!(Mapping.TypeInfoType!T) PydTypeObject;
56     }else{
57         // The type object, an instance of PyType_Type
58         PyTypeObject PydTypeObject;
59     }
60 }
61 
62 template IsRefParam(alias p) {
63     enum IsRefParam = p == ParameterStorageClass.ref_ ||
64         p == ParameterStorageClass.out_;
65 }
66 
67 template FnHasRefParams(Fn) {
68     alias anySatisfy!(IsRefParam, ParameterStorageClassTuple!(Fn))
69         FnHasRefParams;
70 }
71 
72 struct DFn_Py_Mapping {
73     const(void)* d;
74     PyObject* py;
75     TypeInfo d_typeinfo;
76     TypeInfo[] params_info;
77     uint functionAttributes;
78     string linkage;
79     Constness constness;
80 
81     this(Fn)(Fn d, PyObject* py) if(isFunctionPointer!Fn) {
82         static assert(!FnHasRefParams!Fn,
83                 "Pyd cannot handle ref or out parameters at this time");
84         this.d = DKey(d);
85         this.d_typeinfo = typeid(TypeInfoType!Fn);
86         this.params_info = (cast(TypeInfo_Tuple) typeid(ParameterTypeTuple!Fn))
87             .elements;
88         this.py = py;
89         this.functionAttributes = .functionAttributes!Fn;
90         this.linkage = functionLinkage!Fn;
91         this.constness = .constness!Fn;
92     }
93 
94     template TypeInfoType(T) if(isFunctionPointer!T) {
95         alias Unqual!(
96                 SetFunctionAttributes!(T, functionLinkage!T,
97                     FunctionAttribute.none)) TypeInfoType;
98     }
99 
100     public static const(void)* DKey(T)(T t)
101         if(isFunctionPointer!T) {
102             return cast(const(void)*) t;
103         }
104 
105     public T FromKey(T)() {
106         return cast(T) this.d;
107     }
108 }
109 
110 struct DDg_Py_Mapping {
111     const(void)*[2] d;
112     PyObject* py;
113     TypeInfo d_typeinfo;
114     TypeInfo[] params_info;
115     uint functionAttributes;
116     string linkage;
117     Constness constness;
118 
119     this(Dg)(Dg d, PyObject* py) if(isDelegate!Dg) {
120         static assert(!FnHasRefParams!Dg,
121                 "Pyd cannot handle ref or out parameters at this time");
122         this.d = DKey(d);
123         this.d_typeinfo = typeid(TypeInfoType!Dg);
124         this.params_info = (cast(TypeInfo_Tuple) typeid(ParameterTypeTuple!Dg))
125             .elements;
126         this.py = py;
127         this.functionAttributes = .functionAttributes!Dg;
128         this.linkage = functionLinkage!Dg;
129         this.constness = .constness!Dg;
130     }
131 
132     template TypeInfoType(T) {
133         alias Unqual!(
134                 SetFunctionAttributes!(T, functionLinkage!T,
135                     FunctionAttribute.none)) TypeInfoType;
136     }
137 
138     public static const(void)*[2] DKey(T)(T t)
139         if(isDelegate!T) {
140             typeof(return) key;
141             key[0] = cast(const(void)*) t.ptr;
142             key[1] = cast(const(void)*) t.funcptr;
143             return key;
144         }
145 
146     public T FromKey(T)() {
147         T x;
148         x.ptr = cast(void*) d[0];
149         x.funcptr = cast(typeof(x.funcptr)) d[1];
150         return x;
151     }
152 }
153 
154 struct DStruct_Py_Mapping {
155     const(void)* d;
156     PyObject* py;
157     TypeInfo d_typeinfo;
158     Constness constness;
159 
160     this(S)(S d, PyObject* py) if(isPointer!S &&
161             is(PointerTarget!S == struct)) {
162         this.d = DKey(d);
163         this.d_typeinfo = typeid(TypeInfoType!S);
164         this.py = py;
165         this.constness = .constness!S;
166     }
167 
168     template TypeInfoType(T) if(isPointer!T && is(PointerTarget!T == struct)) {
169         alias Unqual!T TypeInfoType;
170     }
171 
172     public static const(void)* DKey(T)(T t)
173         if(isPointer!T && is(PointerTarget!T == struct)) {
174             return cast(const(void)*) t;
175     }
176 
177     public T FromKey(T)() {
178         return cast(T) this.d;
179     }
180 }
181 
182 struct DClass_Py_Mapping {
183     const(void)* d;
184     PyObject* py;
185     TypeInfo d_typeinfo;
186     Constness constness;
187 
188     this(S)(S d, PyObject* py) if(is(S == class)) {
189         this.d = cast(const(void)*) d;
190         this.d_typeinfo = typeid(TypeInfoType!S);
191         this.py = py;
192         this.constness = .constness!S;
193     }
194 
195     template TypeInfoType(T) if(is(T == class)) {
196             alias Unqual!T TypeInfoType;
197     }
198 
199     public static const(void)* DKey(T)(T t)
200         if(is(T == class)) {
201             return cast(const(void)*) t;
202     }
203 
204     public T FromKey(T)() {
205         return cast(T) this.d;
206     }
207 }
208 
209 /// A bidirectional mapping of a pyobject and the d object associated to it.
210 /// On the python side, these are weak references; we don't want to prevent
211 /// python from reclaiming objects it is finished with. As such, on the D side,
212 /// if you take PyObject*s out of here and store them for an extended time
213 /// elsewhere, be sure to increment the reference count.
214 /// On the D side, we have strong references, but that is incidental to the GC.
215 /// If you stick d objects not allocated with the GC, there will probably be
216 /// leaks.
217 /// We use malloc for the container's structure because we can't use the GC
218 /// inside a destructor and we need to use this container there.
219 template reference_container(Mapping) {
220     static if(is(Mapping == DStruct_Py_Mapping)) {
221         // See #104 for the reason for the hash functions below
222         alias MultiIndexContainer!(
223             Mapping,
224             IndexedBy!(
225                 HashedNonUnique!("a.d", "cast(size_t) *cast(const void**) &a"), "d",
226                 HashedUnique!("a.py", "cast(size_t) *cast(const void**) &a"), "python"
227                 ),
228             MallocAllocator, MutableView)
229             Container;
230     }else{
231         alias MultiIndexContainer!(Mapping, IndexedBy!(
232                     HashedUnique!("a.d"), "d",
233                     HashedUnique!("a.py"), "python"),
234                 MallocAllocator, MutableView)
235             Container;
236     }
237     Container _reference_container = null;
238 
239     @property reference_container() {
240         if(!_reference_container) {
241             _reference_container = new Container();
242             Py_AtExit(&clear);
243         }
244         return _reference_container;
245     }
246 
247     extern(C) void clear() {
248         if(_reference_container) {
249             _reference_container.d.clear();
250         }
251     }
252 }
253 
254 // A mapping of all GC references that are being held by Python.
255 template pyd_references(T) {
256     static if(isDelegate!T) {
257         alias DDg_Py_Mapping Mapping;
258     }else static if (isFunctionPointer!T) {
259         alias DFn_Py_Mapping Mapping;
260     }else static if(isPointer!T && is(PointerTarget!T == struct)) {
261         alias DStruct_Py_Mapping Mapping;
262     }else static if (is(T == class)) {
263         alias DClass_Py_Mapping Mapping;
264     }else static assert(0, format("type %s cannot sent to pyd, because ??",
265                 T.stringof));
266     alias reference_container!Mapping container;
267 }
268 
269 void set_pyd_mapping(T) (PyObject* _self, T t) {
270     import std.stdio;
271     alias pyd_references!T.Mapping Mapping;
272     alias pyd_references!T.container container;
273 
274     Mapping mapping = Mapping(t, _self);
275     auto py_index = container.python;
276     auto range = py_index.equalRange(_self);
277     if (range.empty) {
278         auto count = py_index.insert(mapping);
279         enforce(count != 0,
280                 format("could not add py reference %x for T=%s, t=%s",
281                     _self, T.stringof,  Mapping.DKey(t)));
282     }else{
283         auto count = py_index.replace(PSR(range).front, mapping);
284         enforce(count != 0,
285                 format("could not update py reference %x for T=%s, t=%s",
286                     _self, T.stringof,  Mapping.DKey(t)));
287     }
288 }
289 
290 void remove_pyd_mapping(T)(PyObject* self) {
291     import std.range;
292     import std.stdio;
293     alias pyd_references!T.Mapping Mapping;
294     alias pyd_references!T.container container;
295 
296     auto py_index = container.python;
297     auto range = py_index.equalRange(self);
298     if(!range.empty) {
299         py_index.remove(take(PSR(range),1));
300     }
301 }
302 
303 bool isConversionAddingFunctionAttributes(
304         uint fromTypeFunctionAttributes,
305         uint toTypeFunctionAttributes) {
306     return (~(fromTypeFunctionAttributes | StrippedFunctionAttributes) & toTypeFunctionAttributes) != 0;
307 }
308 
309 
310 /**
311  * Returns the object contained in a Python wrapped type.
312  */
313 T get_d_reference(T) (PyObject* _self) {
314     alias pyd_references!T.container container;
315     alias pyd_references!T.Mapping Mapping;
316     import thread = pyd.thread;
317 
318     thread.ensureAttached();
319 
320     enforce(is_wrapped!T,
321             format(
322                 "Error extracting D object: Type %s is not wrapped.",
323                 typeid(T).toString()));
324     enforce(_self !is null,
325             "Error: trying to find D reference for null PyObject*!");
326 
327     auto py_index = container.python;
328     auto range = py_index.equalRange(_self);
329 
330     enforce(!range.empty,
331             "Error extracting D object: reference not found!");
332     // don't cast away type!
333     static if(is(T == class)) {
334         auto tif = cast(TypeInfo_Class) range.front.d_typeinfo;
335         auto t_info = typeid(Mapping.TypeInfoType!T);
336         bool found = false;
337         while(tif) {
338             if(t_info == tif) {
339                 found = true;
340                 break;
341             }
342             tif = tif.base;
343         }
344 
345         enforce(found,
346             format(
347                 "Type mismatch extracting D object: found: %s, required: %s",
348                 range.front.d_typeinfo,
349                 typeid(Mapping.TypeInfoType!T)));
350 
351     }else{
352         enforce(typeid(Mapping.TypeInfoType!T) == range.front.d_typeinfo,
353             format(
354                 "Type mismatch extracting D object: found: %s, required: %s",
355                 range.front.d_typeinfo,
356                 typeid(Mapping.TypeInfoType!T)));
357     }
358     // don't cast away constness!
359     // mutable => const, etc, okay
360     enforce(constCompatible(range.front.constness, constness!T),
361             format(
362                 "constness mismatch required: %s, found: %s",
363                 constness_ToString(constness!T),
364                 constness_ToString(range.front.constness)));
365     static if(isFunctionPointer!T || isDelegate!T) {
366         // don't cast away linkage!
367         enforce(range.front.linkage == functionLinkage!T,
368                 format(
369                     "trying to convert a extern(\"%s\") " ~
370                     "%s to extern(\"%s\")",
371                     range.front.linkage, (isDelegate!T ? "delegate":"function"),
372                     functionLinkage!T));
373         // losing function attributes is ok,
374         // giving a function new ones, not so much
375         enforce(
376                 !isConversionAddingFunctionAttributes(
377                     range.front.functionAttributes, functionAttributes!T),
378                 format(
379                     "trying to convert %s%s to %s",
380                     SetFunctionAttributes!(T,
381                         functionLinkage!T,
382                         FunctionAttribute.none).stringof,
383                     attrs_to_string(range.front.functionAttributes),
384                     T.stringof));
385     }
386 
387     return range.front.FromKey!T();
388 }
389 
390 /// If the passed D reference has an existing Python object, return a borrowed
391 /// reference to it. Otherwise, return null.
392 PyObject_BorrowedRef* get_python_reference(T) (T t) {
393     alias pyd_references!T.container container;
394     alias pyd_references!T.Mapping Mapping;
395 
396     auto d_index = container.d;
397     auto range = d_index.equalRange(Mapping.DKey(t));
398     if(range.empty) return null;
399     return borrowed(range.front.py);
400 }
401 
402 /**
403  * Returns a new Python object of a wrapped type.
404  */
405 // WrapPyObject_FromTypeAndObject
406 // WrapPyObject_FromObject
407 PyObject* wrap_d_object(T)(T t, PyTypeObject* type = null) {
408     if (!type) {
409         type = &PydTypeObject!T;
410     }
411     if (is_wrapped!(T)) {
412         // If this object is already wrapped, get the existing object.
413         PyObject_BorrowedRef* obj_p = get_python_reference(t);
414         if (obj_p) {
415             return Py_INCREF(obj_p);
416         }
417         // Otherwise, allocate a new object
418         PyObject* obj = type.tp_new(type, null, null);
419         // Set the contained instance
420         set_pyd_mapping(obj, t);
421         return obj;
422     } else {
423         PyErr_SetString(PyExc_RuntimeError,
424                 (format("Type %s is not wrapped by Pyd.", typeid(T))).ptr);
425         return null;
426     }
427 }