1 /**
2  * Support for exception handling for EH_DM and EH_WIN32.
3  * Generate exception handling tables.
4  *
5  * Copyright:   Copyright (C) 1994-1998 by Symantec
6  *              Copyright (C) 2000-2023 by The D Language Foundation, All Rights Reserved
7  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
8  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/backend/eh.d, _eh.d)
10  * Documentation:  https://dlang.org/phobos/dmd_eh.html
11  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/backend/eh.d
12  */
13 
14 module dmd.backend.eh;
15 
16 import core.stdc.stdio;
17 import core.stdc.stdlib;
18 import core.stdc.string;
19 
20 import dmd.backend.barray;
21 import dmd.backend.cc;
22 import dmd.backend.cdef;
23 import dmd.backend.code;
24 import dmd.backend.code_x86;
25 import dmd.backend.dt;
26 import dmd.backend.el;
27 import dmd.backend.global;
28 import dmd.backend.obj;
29 import dmd.backend.ty;
30 import dmd.backend.type;
31 
32 nothrow:
33 @safe:
34 
35 package(dmd) @property @nogc nothrow auto @trusted NPTRSIZE() { return _tysize[TYnptr]; }
36 
37 /****************************
38  * Generate and output scope table.
39  */
40 
41 @trusted
42 Symbol *except_gentables()
43 {
44     //printf("except_gentables()\n");
45     if (config.ehmethod == EHmethod.EH_DM && !(funcsym_p.Sfunc.Fflags3 & Feh_none))
46     {
47         // BUG: alloca() changes the stack size, which is not reflected
48         // in the fixed eh tables.
49         if (Alloca.size)
50             error(null, 0, 0, "cannot mix `core.std.stdlib.alloca()` and exception handling in `%s()`", &funcsym_p.Sident[0]);
51 
52         char[13+5+1] name = void;
53         __gshared int tmpnum;
54         const len = snprintf(name.ptr, name.length, "_HandlerTable%d", tmpnum++);
55 
56         Symbol *s = symbol_name(name[0 .. len],SC.static_,tstypes[TYint]);
57         symbol_keep(s);
58         //symbol_debug(s);
59 
60         except_fillInEHTable(s);
61 
62         outdata(s);                 // output the scope table
63 
64         objmod.ehtables(funcsym_p,cast(uint)funcsym_p.Ssize,s);
65     }
66     return null;
67 }
68 
69 /**********************************************
70  * Initializes the Symbol s with the contents of the exception handler table.
71  */
72 
73 /* This is what the type should be on the target machine, not the host compiler
74  *
75  * struct Guard
76  * {
77  *    if (EHmethod.EH_DM)
78  *    {
79  *        uint offset;            // offset of start of guarded section (Linux)
80  *        uint endoffset;         // ending offset of guarded section (Linux)
81  *    }
82  *    int last_index;             // previous index (enclosing guarded section)
83  *    uint catchoffset;           // offset to catch block from Symbol
84  *    void *finally;              // finally code to execute
85  * }
86  */
87 @trusted
88 void except_fillInEHTable(Symbol *s)
89 {
90     uint fsize = NPTRSIZE;             // target size of function pointer
91     auto dtb = DtBuilder(0);
92 
93     /*
94         void*           pointer to start of function (Windows)
95         uint            offset of ESP from EBP
96         uint            offset from start of function to return code
97         uint nguards;           // dimension of guard[] (Linux)
98         Guard guard[];          // sorted such that the enclosing guarded sections come first
99       catchoffset:
100         uint ncatches;          // number of catch blocks
101         {   void *type;         // Symbol representing type
102             uint bpoffset;      // EBP offset of catch variable
103             void *handler;      // catch handler code
104         } catch[];
105      */
106 
107 /* Be careful of this, as we need the sizeof Guard on the target, not
108  * in the compiler.
109  */
110     uint GUARD_SIZE;
111     if (config.ehmethod == EHmethod.EH_DM)
112         GUARD_SIZE = (I64() ? 3*8 : 5*4);
113     else if (config.ehmethod == EHmethod.EH_WIN32)
114         GUARD_SIZE = 3*4;
115     else
116         assert(0);
117 
118     int sz = 0;
119 
120     // Address of start of function
121     if (config.ehmethod == EHmethod.EH_WIN32)
122     {
123         //symbol_debug(funcsym_p);
124         dtb.xoff(funcsym_p,0,TYnptr);
125         sz += fsize;
126     }
127 
128     //printf("ehtables: func = %s, offset = x%x, startblock.Boffset = x%x\n", funcsym_p.Sident, funcsym_p.Soffset, startblock.Boffset);
129 
130     // Get offset of ESP from EBP
131     long spoff = cod3_spoff();
132     dtb.dword(cast(int)spoff);
133     sz += 4;
134 
135     // Offset from start of function to return code
136     dtb.dword(cast(int)retoffset);
137     sz += 4;
138 
139     // First, calculate starting catch offset
140     int guarddim = 0;                               // max dimension of guard[]
141     int ndctors = 0;                                // number of ESCdctor's
142     foreach (b; BlockRange(startblock))
143     {
144         if (b.BC == BC_try && b.Bscope_index >= guarddim)
145             guarddim = b.Bscope_index + 1;
146 //      printf("b.BC = %2d, Bscope_index = %2d, last_index = %2d, offset = x%x\n",
147 //              b.BC, b.Bscope_index, b.Blast_index, b.Boffset);
148         if (usednteh & EHcleanup)
149             for (code *c = b.Bcode; c; c = code_next(c))
150             {
151                 if (c.Iop == (ESCAPE | ESCddtor))
152                     ndctors++;
153             }
154     }
155     //printf("guarddim = %d, ndctors = %d\n", guarddim, ndctors);
156 
157     if (config.ehmethod == EHmethod.EH_DM)
158     {
159         dtb.size(guarddim + ndctors);
160         sz += NPTRSIZE;
161     }
162 
163     uint catchoffset = sz + (guarddim + ndctors) * GUARD_SIZE;
164 
165     // Generate guard[]
166     int i = 0;
167     foreach (b; BlockRange(startblock))
168     {
169         //printf("b = %p, b.Btry = %p, b.offset = %x\n", b, b.Btry, b.Boffset);
170         if (b.BC == BC_try)
171         {
172             assert(b.Bscope_index >= i);
173             if (i < b.Bscope_index)
174             {   int fillsize = (b.Bscope_index - i) * GUARD_SIZE;
175                 dtb.nzeros( fillsize);
176                 sz += fillsize;
177             }
178             i = b.Bscope_index + 1;
179 
180             int nsucc = b.numSucc();
181 
182             if (config.ehmethod == EHmethod.EH_DM)
183             {
184             //printf("DHandlerInfo: offset = %x", cast(int)(b.Boffset - startblock.Boffset));
185             dtb.dword(cast(int)(b.Boffset - startblock.Boffset));    // offset to start of block
186 
187             // Compute ending offset
188             uint endoffset;
189             for (block *bn = b.Bnext; 1; bn = bn.Bnext)
190             {
191                 //printf("\tbn = %p, bn.Btry = %p, bn.offset = %x\n", bn, bn.Btry, bn.Boffset);
192                 assert(bn);
193                 if (bn.Btry == b.Btry)
194                 {    endoffset = cast(uint)(bn.Boffset - startblock.Boffset);
195                      break;
196                 }
197             }
198             //printf(" endoffset = %x, prev_index = %d\n", endoffset, b.Blast_index);
199             dtb.dword(endoffset);               // offset past end of guarded block
200             }
201 
202             dtb.dword(b.Blast_index);          // parent index
203 
204             if (b.jcatchvar)                           // if try-catch
205             {
206                 assert(catchoffset);
207                 dtb.dword(catchoffset);
208                 dtb.size(0);                  // no finally handler
209 
210                 catchoffset += NPTRSIZE + (nsucc - 1) * (3 * NPTRSIZE);
211             }
212             else                                        // else try-finally
213             {
214                 assert(nsucc == 2);
215                 dtb.dword(0);           // no catch offset
216                 block *bhandler = b.nthSucc(1);
217                 assert(bhandler.BC == BC_finally);
218                 // To successor of BC_finally block
219                 bhandler = bhandler.nthSucc(0);
220                 // finally handler address
221                 if (config.ehmethod == EHmethod.EH_DM)
222                 {
223                     assert(bhandler.Boffset > startblock.Boffset);
224                     dtb.size(bhandler.Boffset - startblock.Boffset);    // finally handler offset
225                 }
226                 else
227                     dtb.coff(cast(uint)bhandler.Boffset);
228             }
229             sz += GUARD_SIZE;
230         }
231     }
232 
233     /* Append to guard[] the guard blocks for temporaries that are created and destroyed
234      * within a single expression. These are marked by the special instruction pairs
235      * (ESCAPE | ESCdctor) and (ESCAPE | ESCddtor).
236      */
237     if (usednteh & EHcleanup)
238     {
239         Barray!int stack;
240 
241     int scopeindex = guarddim;
242     foreach (b; BlockRange(startblock))
243     {
244         /* Set up stack of scope indices
245          */
246         stack.push(b.Btry ? b.Btry.Bscope_index : -1);
247 
248         uint boffset = cast(uint)b.Boffset;
249         for (code *c = b.Bcode; c; c = code_next(c))
250         {
251             if (c.Iop == (ESCAPE | ESCdctor))
252             {
253                 code *c2 = code_next(c);
254                 if (config.ehmethod == EHmethod.EH_WIN32)
255                     nteh_patchindex(c2, scopeindex);
256                 if (config.ehmethod == EHmethod.EH_DM)
257                     dtb.dword(cast(int)(boffset - startblock.Boffset)); // guard offset
258                 // Find corresponding ddtor instruction
259                 int n = 0;
260                 uint eoffset = boffset;
261                 uint foffset;
262                 for (; 1; c2 = code_next(c2))
263                 {
264                     // https://issues.dlang.org/show_bug.cgi?id=13720
265                     // optimizer might elide the corresponding ddtor
266                     if (!c2)
267                         goto Lnodtor;
268 
269                     if (c2.Iop == (ESCAPE | ESCddtor))
270                     {
271                         if (n)
272                             n--;
273                         else
274                         {
275                             foffset = eoffset;
276                             code *cf = code_next(c2);
277                             if (config.ehmethod == EHmethod.EH_WIN32)
278                             {
279                                 nteh_patchindex(cf, stack[stack.length - 1]);
280                                 foffset += calccodsize(cf);
281                                 cf = code_next(cf);
282                             }
283                             foffset += calccodsize(cf);
284                             while (!cf.isJumpOP())
285                             {
286                                 cf = code_next(cf);
287                                 foffset += calccodsize(cf);
288                             }
289                             // https://issues.dlang.org/show_bug.cgi?id=9438
290                             //cf = code_next(cf);
291                             //foffset += calccodsize(cf);
292                             if (config.ehmethod == EHmethod.EH_DM)
293                                 dtb.dword(cast(int)(eoffset - startblock.Boffset)); // guard offset
294                             break;
295                         }
296                     }
297                     else if (c2.Iop == (ESCAPE | ESCdctor))
298                     {
299                         n++;
300                     }
301                     else
302                         eoffset += calccodsize(c2);
303                 }
304                 //printf("boffset = %x, eoffset = %x, foffset = %x\n", boffset, eoffset, foffset);
305                 dtb.dword(stack[stack.length - 1]);   // parent index
306                 dtb.dword(0);           // no catch offset
307                 if (config.ehmethod == EHmethod.EH_DM)
308                 {
309                     assert(foffset > startblock.Boffset);
310                     dtb.size(foffset - startblock.Boffset);    // finally handler offset
311                 }
312                 else
313                     dtb.coff(foffset);  // finally handler address
314                 stack.push(scopeindex);
315                 ++scopeindex;
316                 sz += GUARD_SIZE;
317             }
318             else if (c.Iop == (ESCAPE | ESCddtor))
319             {
320                 stack.setLength(stack.length - 1);
321                 assert(stack.length != 0);
322             }
323         Lnodtor:
324             boffset += calccodsize(c);
325         }
326     }
327         stack.dtor();
328     }
329 
330     // Generate catch[]
331     foreach (b; BlockRange(startblock))
332     {
333         if (b.BC == BC_try && b.jcatchvar)         // if try-catch
334         {
335             int nsucc = b.numSucc();
336             dtb.size(nsucc - 1);           // # of catch blocks
337             sz += NPTRSIZE;
338 
339             for (int j = 1; j < nsucc; ++j)
340             {
341                 block *bcatch = b.nthSucc(j);
342 
343                 dtb.xoff(bcatch.Bcatchtype,0,TYnptr);
344 
345                 dtb.size(cod3_bpoffset(b.jcatchvar));     // EBP offset
346 
347                 // catch handler address
348                 if (config.ehmethod == EHmethod.EH_DM)
349                 {
350                     assert(bcatch.Boffset > startblock.Boffset);
351                     dtb.size(bcatch.Boffset - startblock.Boffset);  // catch handler offset
352                 }
353                 else
354                     dtb.coff(cast(uint)bcatch.Boffset);
355 
356                 sz += 3 * NPTRSIZE;
357             }
358         }
359     }
360     assert(sz != 0);
361     s.Sdt = dtb.finish();
362 }