1 // Copyright David Abrahams 2002.
2 // Distributed under the Boost Software License, Version 1.0. (See
3 // accompanying file LICENSE_1_0.txt or copy at
4 // http://www.boost.org/LICENSE_1_0.txt)
5 #include <boost/python/object/inheritance.hpp>
6 #include <boost/python/type_id.hpp>
7 #include <boost/graph/breadth_first_search.hpp>
8 #if _MSC_FULL_VER >= 13102171 && _MSC_FULL_VER <= 13102179
9 # include <boost/graph/reverse_graph.hpp>
10 #endif
11 #include <boost/graph/adjacency_list.hpp>
12 #include <boost/graph/reverse_graph.hpp>
13 #include <boost/property_map/property_map.hpp>
14 #include <boost/bind.hpp>
15 #include <boost/integer_traits.hpp>
16 #include <boost/tuple/tuple.hpp>
17 #include <boost/tuple/tuple_comparison.hpp>
18 #include <queue>
19 #include <vector>
20 #include <functional>
21 
22 //
23 // Procedure:
24 //
25 //      The search is a BFS over the space of (type,address) pairs
26 //      guided by the edges of the casting graph whose nodes
27 //      correspond to classes, and whose edges are traversed by
28 //      applying associated cast functions to an address. We use
29 //      vertex distance to the goal node in the cast_graph to rate the
30 //      paths. The vertex distance to any goal node is calculated on
31 //      demand and outdated by the addition of edges to the graph.
32 
33 namespace boost {
34 namespace
35 {
36   enum edge_cast_t { edge_cast = 8010 };
unused_variable(const T &)37   template <class T> inline void unused_variable(const T&) { }
38 }
39 
40 // Install properties
41 BOOST_INSTALL_PROPERTY(edge, cast);
42 
43 namespace
44 {
45   typedef void*(*cast_function)(void*);
46 
47   //
48   // Here we put together the low-level data structures of the
49   // casting graph representation.
50   //
51   typedef python::type_info class_id;
52 
53   // represents a graph of available casts
54 
55 #if 0
56   struct cast_graph
57       :
58 #else
59         typedef
60 #endif
61         adjacency_list<vecS,vecS, bidirectionalS, no_property
62 
63       // edge index property allows us to look up edges in the connectivity matrix
64       , property<edge_index_t,std::size_t
65 
66                  // The function which casts a void* from the edge's source type
67                  // to its destination type.
68                  , property<edge_cast_t,cast_function> > >
69 #if 0
70   {};
71 #else
72   cast_graph;
73 #endif
74 
75   typedef cast_graph::vertex_descriptor vertex_t;
76   typedef cast_graph::edge_descriptor edge_t;
77 
78   struct smart_graph
79   {
80       typedef std::vector<std::size_t>::const_iterator node_distance_map;
81 
82       typedef std::pair<cast_graph::out_edge_iterator
83                         , cast_graph::out_edge_iterator> out_edges_t;
84 
85       // Return a map of the distances from any node to the given
86       // target node
distances_toboost::__anon4c3d80290211::smart_graph87       node_distance_map distances_to(vertex_t target) const
88       {
89           std::size_t n = num_vertices(m_topology);
90           if (m_distances.size() != n * n)
91           {
92               m_distances.clear();
93               m_distances.resize(n * n, (std::numeric_limits<std::size_t>::max)());
94               m_known_vertices = n;
95           }
96 
97           std::vector<std::size_t>::iterator to_target = m_distances.begin() + n * target;
98 
99           // this node hasn't been used as a target yet
100           if (to_target[target] != 0)
101           {
102               typedef reverse_graph<cast_graph> reverse_cast_graph;
103               reverse_cast_graph reverse_topology(m_topology);
104 
105               to_target[target] = 0;
106 
107               breadth_first_search(
108                   reverse_topology, target
109                   , visitor(
110                       make_bfs_visitor(
111                           record_distances(
112                               make_iterator_property_map(
113                                   to_target
114                                   , get(vertex_index, reverse_topology)
115 # ifdef BOOST_NO_STD_ITERATOR_TRAITS
116                                   , *to_target
117 # endif
118                                   )
119                               , on_tree_edge()
120                               ))));
121           }
122 
123           return to_target;
124       }
125 
topologyboost::__anon4c3d80290211::smart_graph126       cast_graph& topology() { return m_topology; }
topologyboost::__anon4c3d80290211::smart_graph127       cast_graph const& topology() const { return m_topology; }
128 
smart_graphboost::__anon4c3d80290211::smart_graph129       smart_graph()
130           : m_known_vertices(0)
131       {}
132 
133    private:
134       cast_graph m_topology;
135       mutable std::vector<std::size_t> m_distances;
136       mutable std::size_t m_known_vertices;
137   };
138 
full_graph()139   smart_graph& full_graph()
140   {
141       static smart_graph x;
142       return x;
143   }
144 
up_graph()145   smart_graph& up_graph()
146   {
147       static smart_graph x;
148       return x;
149   }
150 
151   //
152   // Our index of class types
153   //
154   using boost::python::objects::dynamic_id_function;
155   typedef tuples::tuple<
156       class_id               // static type
157       , vertex_t             // corresponding vertex
158       , dynamic_id_function  // dynamic_id if polymorphic, or 0
159       >
160   index_entry_interface;
161   typedef index_entry_interface::inherited index_entry;
162   enum { ksrc_static_t, kvertex, kdynamic_id };
163 
164   typedef std::vector<index_entry> type_index_t;
165 
166 
type_index()167   type_index_t& type_index()
168   {
169       static type_index_t x;
170       return x;
171   }
172 
173   template <class Tuple>
174   struct select1st
175   {
176       typedef typename tuples::element<0, Tuple>::type result_type;
177 
operator ()boost::__anon4c3d80290211::select1st178       result_type const& operator()(Tuple const& x) const
179       {
180           return tuples::get<0>(x);
181       }
182   };
183 
184   // map a type to a position in the index
type_position(class_id type)185   inline type_index_t::iterator type_position(class_id type)
186   {
187       typedef index_entry entry;
188 
189       return std::lower_bound(
190           type_index().begin(), type_index().end()
191           , boost::make_tuple(type, vertex_t(), dynamic_id_function(0))
192           , boost::bind<bool>(std::less<class_id>()
193                , boost::bind<class_id>(select1st<entry>(), _1)
194                , boost::bind<class_id>(select1st<entry>(), _2)));
195   }
196 
seek_type(class_id type)197   inline index_entry* seek_type(class_id type)
198   {
199       type_index_t::iterator p = type_position(type);
200       if (p == type_index().end() || tuples::get<ksrc_static_t>(*p) != type)
201           return 0;
202       else
203           return &*p;
204   }
205 
206   // Get the entry for a type, inserting if necessary
demand_type(class_id type)207   inline type_index_t::iterator demand_type(class_id type)
208   {
209       type_index_t::iterator p = type_position(type);
210 
211       if (p != type_index().end() && tuples::get<ksrc_static_t>(*p) == type)
212           return p;
213 
214       vertex_t v = add_vertex(full_graph().topology());
215       vertex_t v2 = add_vertex(up_graph().topology());
216       unused_variable(v2);
217       assert(v == v2);
218       return type_index().insert(p, boost::make_tuple(type, v, dynamic_id_function(0)));
219   }
220 
221   // Map a two types to a vertex in the graph, inserting if necessary
222   typedef std::pair<type_index_t::iterator, type_index_t::iterator>
223         type_index_iterator_pair;
224 
225   inline type_index_iterator_pair
demand_types(class_id t1,class_id t2)226   demand_types(class_id t1, class_id t2)
227   {
228       // be sure there will be no reallocation
229       type_index().reserve(type_index().size() + 2);
230       type_index_t::iterator first = demand_type(t1);
231       type_index_t::iterator second = demand_type(t2);
232       if (first == second)
233           ++first;
234       return std::make_pair(first, second);
235   }
236 
237   struct q_elt
238   {
q_eltboost::__anon4c3d80290211::q_elt239       q_elt(std::size_t distance
240             , void* src_address
241             , vertex_t target
242             , cast_function cast
243             )
244           : distance(distance)
245           , src_address(src_address)
246           , target(target)
247           , cast(cast)
248       {}
249 
250       std::size_t distance;
251       void* src_address;
252       vertex_t target;
253       cast_function cast;
254 
operator <boost::__anon4c3d80290211::q_elt255       bool operator<(q_elt const& rhs) const
256       {
257           return distance < rhs.distance;
258       }
259   };
260 
261   // Optimization:
262   //
263   // Given p, src_t, dst_t
264   //
265   // Get a pointer pd to the most-derived object
266   //    if it's polymorphic, dynamic_cast to void*
267   //    otherwise pd = p
268   //
269   // Get the most-derived typeid src_td
270   //
271   // ptrdiff_t offset = p - pd
272   //
273   // Now we can keep a cache, for [src_t, offset, src_td, dst_t] of
274   // the cast transformation function to use on p and the next src_t
275   // in the chain.  src_td, dst_t don't change throughout this
276   // process. In order to represent unreachability, when a pair is
277   // found to be unreachable, we stick a 0-returning "dead-cast"
278   // function in the cache.
279 
280   // This is needed in a few places below
identity_cast(void * p)281   inline void* identity_cast(void* p)
282   {
283       return p;
284   }
285 
search(smart_graph const & g,void * p,vertex_t src,vertex_t dst)286   void* search(smart_graph const& g, void* p, vertex_t src, vertex_t dst)
287   {
288       // I think this test was thoroughly bogus -- dwa
289       // If we know there's no path; bail now.
290       // if (src > g.known_vertices() || dst > g.known_vertices())
291       //    return 0;
292 
293       smart_graph::node_distance_map d(g.distances_to(dst));
294 
295       if (d[src] == (std::numeric_limits<std::size_t>::max)())
296           return 0;
297 
298       typedef property_map<cast_graph,edge_cast_t>::const_type cast_map;
299       cast_map casts = get(edge_cast, g.topology());
300 
301       typedef std::pair<vertex_t,void*> search_state;
302       typedef std::vector<search_state> visited_t;
303       visited_t visited;
304       std::priority_queue<q_elt> q;
305 
306       q.push(q_elt(d[src], p, src, identity_cast));
307       while (!q.empty())
308       {
309           q_elt top = q.top();
310           q.pop();
311 
312           // Check to see if we have a real state
313           void* dst_address = top.cast(top.src_address);
314           if (dst_address == 0)
315               continue;
316 
317           if (top.target == dst)
318               return dst_address;
319 
320           search_state s(top.target,dst_address);
321 
322           visited_t::iterator pos = std::lower_bound(
323               visited.begin(), visited.end(), s);
324 
325           // If already visited, continue
326           if (pos != visited.end() && *pos == s)
327               continue;
328 
329           visited.insert(pos, s); // mark it
330 
331           // expand it:
332           smart_graph::out_edges_t edges = out_edges(s.first, g.topology());
333           for (cast_graph::out_edge_iterator p = edges.first
334                    , finish = edges.second
335                    ; p != finish
336                    ; ++p
337               )
338           {
339               edge_t e = *p;
340               q.push(q_elt(
341                          d[target(e, g.topology())]
342                          , dst_address
343                          , target(e, g.topology())
344                          , boost::get(casts, e)));
345           }
346       }
347       return 0;
348   }
349 
350   struct cache_element
351   {
352       typedef tuples::tuple<
353           class_id              // source static type
354           , class_id            // target type
355           , std::ptrdiff_t      // offset within source object
356           , class_id            // source dynamic type
357           >::inherited key_type;
358 
cache_elementboost::__anon4c3d80290211::cache_element359       cache_element(key_type const& k)
360           : key(k)
361           , offset(0)
362       {}
363 
364       key_type key;
365       std::ptrdiff_t offset;
366 
367       BOOST_STATIC_CONSTANT(
368           std::ptrdiff_t, not_found = integer_traits<std::ptrdiff_t>::const_min);
369 
operator <boost::__anon4c3d80290211::cache_element370       bool operator<(cache_element const& rhs) const
371       {
372           return this->key < rhs.key;
373       }
374 
unreachableboost::__anon4c3d80290211::cache_element375       bool unreachable() const
376       {
377           return offset == not_found;
378       }
379   };
380 
381   enum { kdst_t = ksrc_static_t + 1, koffset, ksrc_dynamic_t };
382   typedef std::vector<cache_element> cache_t;
383 
cache()384   cache_t& cache()
385   {
386       static cache_t x;
387       return x;
388   }
389 
convert_type(void * const p,class_id src_t,class_id dst_t,bool polymorphic)390   inline void* convert_type(void* const p, class_id src_t, class_id dst_t, bool polymorphic)
391   {
392       // Quickly rule out unregistered types
393       index_entry* src_p = seek_type(src_t);
394       if (src_p == 0)
395           return 0;
396 
397       index_entry* dst_p = seek_type(dst_t);
398       if (dst_p == 0)
399           return 0;
400 
401       // Look up the dynamic_id function and call it to get the dynamic
402       // info
403       boost::python::objects::dynamic_id_t dynamic_id = polymorphic
404           ? tuples::get<kdynamic_id>(*src_p)(p)
405           : std::make_pair(p, src_t);
406 
407       // Look in the cache first for a quickie address translation
408       std::ptrdiff_t offset = (char*)p - (char*)dynamic_id.first;
409 
410       cache_element seek(boost::make_tuple(src_t, dst_t, offset, dynamic_id.second));
411       cache_t& c = cache();
412       cache_t::iterator const cache_pos
413           = std::lower_bound(c.begin(), c.end(), seek);
414 
415 
416       // if found in the cache, we're done
417       if (cache_pos != c.end() && cache_pos->key == seek.key)
418       {
419           return cache_pos->offset == cache_element::not_found
420               ? 0 : (char*)p + cache_pos->offset;
421       }
422 
423       // If we are starting at the most-derived type, only look in the up graph
424       smart_graph const& g = polymorphic && dynamic_id.second != src_t
425           ? full_graph() : up_graph();
426 
427       void* result = search(
428           g, p, tuples::get<kvertex>(*src_p)
429           , tuples::get<kvertex>(*dst_p));
430 
431       // update the cache
432       c.insert(cache_pos, seek)->offset
433           = (result == 0) ? cache_element::not_found : (char*)result - (char*)p;
434 
435       return result;
436   }
437 }
438 
439 namespace python { namespace objects {
440 
find_dynamic_type(void * p,class_id src_t,class_id dst_t)441 BOOST_PYTHON_DECL void* find_dynamic_type(void* p, class_id src_t, class_id dst_t)
442 {
443     return convert_type(p, src_t, dst_t, true);
444 }
445 
find_static_type(void * p,class_id src_t,class_id dst_t)446 BOOST_PYTHON_DECL void* find_static_type(void* p, class_id src_t, class_id dst_t)
447 {
448     return convert_type(p, src_t, dst_t, false);
449 }
450 
add_cast(class_id src_t,class_id dst_t,cast_function cast,bool is_downcast)451 BOOST_PYTHON_DECL void add_cast(
452     class_id src_t, class_id dst_t, cast_function cast, bool is_downcast)
453 {
454     // adding an edge will invalidate any record of unreachability in
455     // the cache.
456     static std::size_t expected_cache_len = 0;
457     cache_t& c = cache();
458     if (c.size() > expected_cache_len)
459     {
460         c.erase(std::remove_if(
461                     c.begin(), c.end(),
462                     mem_fn(&cache_element::unreachable))
463                 , c.end());
464 
465         // If any new cache entries get added, we'll have to do this
466         // again when the next edge is added
467         expected_cache_len = c.size();
468     }
469 
470     type_index_iterator_pair types = demand_types(src_t, dst_t);
471     vertex_t src = tuples::get<kvertex>(*types.first);
472     vertex_t dst = tuples::get<kvertex>(*types.second);
473 
474     cast_graph* const g[2] = { &up_graph().topology(), &full_graph().topology() };
475 
476     for (cast_graph*const* p = g + (is_downcast ? 1 : 0); p < g + 2; ++p)
477     {
478         edge_t e;
479         bool added;
480 
481         tie(e, added) = add_edge(src, dst, **p);
482         assert(added);
483 
484         put(get(edge_cast, **p), e, cast);
485         put(get(edge_index, **p), e, num_edges(full_graph().topology()) - 1);
486     }
487 }
488 
register_dynamic_id_aux(class_id static_id,dynamic_id_function get_dynamic_id)489 BOOST_PYTHON_DECL void register_dynamic_id_aux(
490     class_id static_id, dynamic_id_function get_dynamic_id)
491 {
492     tuples::get<kdynamic_id>(*demand_type(static_id)) = get_dynamic_id;
493 }
494 
495 }}} // namespace boost::python::objects
496