1 /* GDC -- D front-end for GCC
2    Copyright (C) 2004 David Friedman
3    
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8  
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13  
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 */
18 /*
19   This code is based on the libstdc++ exception handling routines.
20 */
21 
22 module rt.compiler.gdc.gcc.deh;
23 
24 private import rt.compiler.gdc.gcc.unwind;
25 private import rt.compiler.gdc.gcc.unwind_pe;
26 private import rt.compiler.gdc.gcc.builtins;
27 
28 private import tango.stdc.stdlib;
29 
30 // "GNUCD__\0"
31 const _Unwind_Exception_Class GDC_Exception_Class = 0x005f5f4443554e47L;
32 
33 struct Phase1Info
34 {
35     int handlerSwitchValue;
36     ubyte *languageSpecificData;
37     _Unwind_Ptr landingPad;
38 }
39 
40 struct OurUnwindException {
41 
42     // Cache parsed handler data from the personality routine Phase 1
43     // for Phase 2.
44     Phase1Info cache;
45 
46     void save(_Unwind_Context* context, ref Phase1Info info)
47     {
48         cache = info;
49     }
50     void restore(ref Phase1Info info)
51     {
52         info = cache;
53     }
54 
55     Object obj;
56     
57     /* The exception object must be directly behind unwindHeader. (See
58        IRState::exceptionObject.) */
59     static assert(unwindHeader.offsetof - obj.offsetof == obj.sizeof);
60 
61     // The generic exception header
62     _Unwind_Exception unwindHeader;
63     
64     static OurUnwindException * fromHeader(_Unwind_Exception * p_ue) {
65         return cast(OurUnwindException *)
66             (cast(void*) p_ue - OurUnwindException.unwindHeader.offsetof);
67     }
68 }
69 
70 // D doesn't define these, so they are private for now.
71 private void dehTerminate()
72 {
73     //  replaces std::terminate and terminating with a specific handler
74     abort();
75 }
76 private void dehUnexpected()
77 {
78 }
79 private void dehBeginCatch(_Unwind_Exception *exc)
80 {
81     // nothing
82 }
83 
84 // This is called by the unwinder.
85 
86 private extern (C) void
87 _gdc_cleanupException(_Unwind_Reason_Code code, _Unwind_Exception *exc)
88 {
89   // If we haven't been caught by a foreign handler, then this is
90   // some sort of unwind error.  In that case just die immediately.
91   // _Unwind_DeleteException in the HP-UX IA64 libunwind library
92   //  returns _URC_NO_REASON and not _URC_FOREIGN_EXCEPTION_CAUGHT
93   // like the GCC _Unwind_DeleteException function does.
94   if (code != _URC_FOREIGN_EXCEPTION_CAUGHT && code != _URC_NO_REASON)
95       dehTerminate(); // __terminate (header->terminateHandler);
96 
97   OurUnwindException * p = OurUnwindException.fromHeader( exc );
98   delete p;
99 }
100 
101 // This is called by compiler-generated code for throw statements.
102 extern (C) public void
103 _d_throw(Object obj)
104 {
105     OurUnwindException * exc = new OurUnwindException;
106     exc.obj = obj;
107     static if ( is(typeof(exc.unwindHeader.exception_class = GDC_Exception_Class)) )
108         exc.unwindHeader.exception_class = GDC_Exception_Class;
109     else
110         exc.unwindHeader.exception_class[] = GDC_Exception_Class[];
111     exc.unwindHeader.exception_cleanup = & _gdc_cleanupException;
112     
113     version (GNU_SjLj_Exceptions) {
114         _Unwind_SjLj_RaiseException (&exc.unwindHeader);
115     } else {
116         _Unwind_RaiseException (&exc.unwindHeader);
117     }
118 
119     // Some sort of unwinding error.  Note that terminate is a handler.
120     dehBeginCatch (&exc.unwindHeader);
121     dehTerminate(); // std::terminate ();
122 }
123 
124 extern (C) int _d_isbaseof(ClassInfo, ClassInfo);
125 
126 // rethrow?
127 
128 // extern(C) alias personalityImpl ...; would be nice
129 version (GNU_SjLj_Exceptions) {
130     extern (C) _Unwind_Reason_Code __gdc_personality_sj0(int iversion,
131         _Unwind_Action actions,
132         _Unwind_Exception_Class exception_class,
133         _Unwind_Exception *ue_header,
134         _Unwind_Context *context) {
135         return personalityImpl(iversion, actions,
136             exception_class != GDC_Exception_Class,
137             ue_header, context);
138     }
139     
140     private int __builtin_eh_return_data_regno(int x) { return x; }
141 } else {
142     extern (C) _Unwind_Reason_Code __gdc_personality_v0(int iversion,
143         _Unwind_Action actions,
144         _Unwind_Exception_Class exception_class,
145         _Unwind_Exception *ue_header,
146         _Unwind_Context *context) {
147         return personalityImpl(iversion, actions,
148             exception_class != GDC_Exception_Class,
149             ue_header, context);
150     }
151 }
152 
153 private _Unwind_Reason_Code personalityImpl(int iversion,
154     _Unwind_Action actions,
155     bool foreign_exception,
156     _Unwind_Exception *ue_header,
157     _Unwind_Context *context)
158 {
159     enum Found
160       {
161         nothing,
162         terminate,
163         cleanup,
164         handler
165       }
166 
167     Found found_type;
168     lsda_header_info info;
169     OurUnwindException * xh = OurUnwindException.fromHeader(ue_header);
170     //ubyte *language_specific_data;
171     ubyte *p;
172     ubyte *action_record;
173     //int handler_switch_value;
174     _Unwind_Ptr /*landing_pad, */ip;
175     Phase1Info phase1;
176 
177     if (iversion != 1)
178         return _URC_FATAL_PHASE1_ERROR;
179 
180     // Shortcut for phase 2 found handler for domestic exception.
181     if (actions == (_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME)
182         && ! foreign_exception) {
183 
184         xh.restore(phase1);
185         found_type = (phase1.landingPad == 0 ? Found.terminate : Found.handler);
186         goto install_context;
187     }
188 
189     phase1.languageSpecificData = cast(ubyte *) _Unwind_GetLanguageSpecificData( context );
190     
191     // If no LSDA, then there are no handlers or cleanups.
192     if ( ! phase1.languageSpecificData )
193     {
194         return _URC_CONTINUE_UNWIND;
195     }
196 
197     // Parse the LSDA header
198     p = parse_lsda_header(context, phase1.languageSpecificData, & info);
199     info.ttype_base = base_of_encoded_value(info.ttype_encoding, context);
200     ip = _Unwind_GetIP(context) - 1;
201     phase1.landingPad = 0;
202     action_record = null;
203     phase1.handlerSwitchValue = 0;
204 
205     version (GNU_SjLj_Exceptions) {
206         // The given "IP" is an index into the call-site table, with two
207         // exceptions -- -1 means no-action, and 0 means terminate.  But
208         // since we're using uleb128 values, we've not got random access
209         // to the array.
210         if (cast(int) ip < 0)
211             return _URC_CONTINUE_UNWIND;
212         else if (ip == 0)
213             {
214                 // Fall through to set found_terminate.
215             }
216         else
217             {
218                 _Unwind_Word cs_lp, cs_action;
219                 do
220                     {
221                         p = read_uleb128 (p, &cs_lp);
222                         p = read_uleb128 (p, &cs_action);
223                     }
224                 while (--ip);
225 
226                 // Can never have null landing pad for sjlj -- that would have
227                 // been indicated by a -1 call site index.
228                 phase1.landingPad = cs_lp + 1;
229                 if (cs_action)
230                     action_record = info.action_table + cs_action - 1;
231                 goto found_something;
232             }
233     } else {
234         while (p < info.action_table) {
235             _Unwind_Ptr cs_start, cs_len, cs_lp;
236             _Unwind_Word cs_action;
237 
238             // Note that all call-site encodings are "absolute" displacements.
239             p = read_encoded_value (null, info.call_site_encoding, p, &cs_start);
240             p = read_encoded_value (null, info.call_site_encoding, p, &cs_len);
241             p = read_encoded_value (null, info.call_site_encoding, p, &cs_lp);
242             p = read_uleb128 (p, &cs_action);
243 
244             // The table is sorted, so if we've passed the ip, stop.
245             if (ip < info.Start + cs_start)
246                 p = info.action_table;
247             else if (ip < info.Start + cs_start + cs_len)
248                 {
249                     if (cs_lp)
250                         phase1.landingPad = info.LPStart + cs_lp;
251                     if (cs_action)
252                         action_record = info.action_table + cs_action - 1;
253                     goto found_something;
254                 }
255         }
256     }
257 
258   // If ip is not present in the table, call terminate.  This is for
259   // a destructor inside a cleanup, or a library routine the compiler
260   // was not expecting to throw.
261   found_type = Found.terminate;
262   goto do_something;
263 
264  found_something:
265   if (phase1.landingPad == 0)
266     {
267       // If ip is present, and has a null landing pad, there are
268       // no cleanups or handlers to be run.
269       found_type = Found.nothing;
270     }
271   else if (action_record == null)
272     {
273       // If ip is present, has a non-null landing pad, and a null
274       // action table offset, then there are only cleanups present.
275       // Cleanups use a zero switch value, as set above.
276       found_type = Found.cleanup;
277     }
278   else
279     {
280       // Otherwise we have a catch handler or exception specification.
281 
282       _Unwind_Sword ar_filter, ar_disp;
283       ClassInfo throw_type, catch_type;
284       bool saw_cleanup = false;
285       bool saw_handler = false;
286 
287       // During forced unwinding, we only run cleanups.  With a foreign
288       // exception class, there's no exception type.
289       // ??? What to do about GNU Java and GNU Ada exceptions.
290 
291       if ((actions & _UA_FORCE_UNWIND)
292           || foreign_exception)
293         throw_type = null;
294       else
295         throw_type = xh.obj.classinfo;
296 
297       while (1)
298         {
299           p = action_record;
300           p = read_sleb128 (p, &ar_filter);
301           read_sleb128 (p, &ar_disp);
302 
303           if (ar_filter == 0)
304             {
305               // Zero filter values are cleanups.
306               saw_cleanup = true;
307             }
308           else if (ar_filter > 0)
309             {
310               // Positive filter values are handlers.
311               catch_type = get_classinfo_entry(& info, ar_filter);
312 
313               // Null catch type is a catch-all handler; we can catch foreign
314               // exceptions with this.  Otherwise we must match types.
315                 // D Note: will be performing dynamic cast twice, potentially
316                 // Once here and once at the landing pad .. unless we cached
317                 // here and had a begin_catch call.
318               if (! catch_type
319                   || (throw_type
320                       && _d_isbaseof( throw_type, catch_type )))
321                 {
322                   saw_handler = true;
323                   break;
324                 }
325             }
326           else
327             {
328                 // D Note: we don't have these...
329                 break;
330                 /*
331               // Negative filter values are exception specifications.
332               // ??? How do foreign exceptions fit in?  As far as I can
333               // see we can't match because there's no __cxa_exception
334               // object to stuff bits in for __cxa_call_unexpected to use.
335               // Allow them iff the exception spec is non-empty.  I.e.
336               // a throw() specification results in __unexpected.
337               if (throw_type
338                   ? ! check_exception_spec (&info, throw_type, thrown_ptr,
339                                             ar_filter)
340                   : empty_exception_spec (&info, ar_filter))
341                 {
342                   saw_handler = true;
343                   break;
344                   }
345                 */
346             }
347 
348           if (ar_disp == 0)
349             break;
350           action_record = p + ar_disp;
351         }
352 
353       if (saw_handler)
354         {
355           phase1.handlerSwitchValue = ar_filter;
356           found_type = Found.handler;
357         }
358       else
359         found_type = (saw_cleanup ? Found.cleanup : Found.nothing);
360     }
361 
362  do_something:
363    if (found_type == Found.nothing)
364    {
365        return _URC_CONTINUE_UNWIND;
366    }
367 
368   if (actions & _UA_SEARCH_PHASE)
369     {
370       if (found_type == Found.cleanup)
371       {
372           return _URC_CONTINUE_UNWIND;
373       }
374 
375       // For domestic exceptions, we cache data from phase 1 for phase 2.
376       if (! foreign_exception)
377           xh.save(context, phase1);
378       return _URC_HANDLER_FOUND;
379     }
380     
381  install_context:
382     if ( (actions & _UA_FORCE_UNWIND)
383         || foreign_exception) {
384 
385         if (found_type == Found.terminate) {
386             dehTerminate();
387         } else if (phase1.handlerSwitchValue < 0) {
388             dehUnexpected();
389         }
390         
391     } else {
392       if (found_type == Found.terminate)
393         {
394           dehBeginCatch (&xh.unwindHeader);
395           dehTerminate(); // __terminate (xh.terminateHandler);
396         }
397     }
398 
399     static if (is(typeof(__builtin_extend_pointer)))
400         /* For targets with pointers smaller than the word size, we must extend the
401            pointer, and this extension is target dependent.  */
402         _Unwind_SetGR (context, __builtin_eh_return_data_regno (0),
403             __builtin_extend_pointer (&xh.unwindHeader));
404     else
405         _Unwind_SetGR (context, __builtin_eh_return_data_regno (0),
406             cast(_Unwind_Ptr) &xh.unwindHeader);
407     _Unwind_SetGR (context, __builtin_eh_return_data_regno (1),
408         phase1.handlerSwitchValue);
409     _Unwind_SetIP (context, phase1.landingPad);
410     
411     return _URC_INSTALL_CONTEXT;
412 }
413 
414 struct lsda_header_info
415 {
416   _Unwind_Ptr Start;
417   _Unwind_Ptr LPStart;
418   _Unwind_Ptr ttype_base;
419   ubyte *TType;
420   ubyte *action_table;
421   ubyte ttype_encoding;
422   ubyte call_site_encoding;
423 }
424 
425 private ubyte *
426 parse_lsda_header (_Unwind_Context *context, ubyte *p,
427                    lsda_header_info *info)
428 {
429   _Unwind_Word tmp;
430   ubyte lpstart_encoding;
431 
432   info.Start = (context ? _Unwind_GetRegionStart (context) : 0);
433 
434   // Find @LPStart, the base to which landing pad offsets are relative.
435   lpstart_encoding = *p++;
436   if (lpstart_encoding != DW_EH_PE_omit)
437     p = read_encoded_value (context, lpstart_encoding, p, &info.LPStart);
438   else
439     info.LPStart = info.Start;
440 
441   // Find @TType, the base of the handler and exception spec type data.
442   info.ttype_encoding = *p++;
443   if (info.ttype_encoding != DW_EH_PE_omit)
444     {
445       p = read_uleb128 (p, &tmp);
446       info.TType = p + tmp;
447     }
448   else
449     info.TType = null;
450 
451   // The encoding and length of the call-site table; the action table
452   // immediately follows.
453   info.call_site_encoding = *p++;
454   p = read_uleb128 (p, &tmp);
455   info.action_table = p + tmp;
456 
457   return p;
458 }
459 
460 private ClassInfo
461 get_classinfo_entry (lsda_header_info *info, _Unwind_Word i)
462 {
463   _Unwind_Ptr ptr;
464 
465   i *= size_of_encoded_value (info.ttype_encoding);
466   read_encoded_value_with_base (info.ttype_encoding, info.ttype_base,
467                                     info.TType - i, &ptr);
468   
469   return cast(ClassInfo)cast(void *)(ptr);
470 }