1 /******************************************************************************
2 
3     Ocean Exceptions
4 
5     Copyright:
6         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
7         All rights reserved.
8 
9     License:
10         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
11         Alternatively, this file may be distributed under the terms of the Tango
12         3-Clause BSD License (see LICENSE_BSD.txt for details).
13 
14 *******************************************************************************/
15 
16 module ocean.core.Exception;
17 
18 static import ocean.core.Enforce;
19 import ocean.meta.types.Qualifiers;
20 
21 version (unittest)
22 {
23     import ocean.core.Test;
24 }
25 
26 
27 /******************************************************************************
28 
29     Creates an iteratable data structure over a chained sequence of
30     exceptions.
31 
32 *******************************************************************************/
33 
34 struct ExceptionChain
35 {
36     /**************************************************************************
37 
38         Exception that forms the root of the exception chain.  This can
39         be passed in like a constructor argument:
40 
41             foreach (e; ExceptionChain(myException))
42             {
43                 ...
44             }
45 
46     ***************************************************************************/
47 
48     private Throwable root;
49 
50 
51     /**************************************************************************
52 
53         Allows the user to iterate over the exception chain
54 
55     ***************************************************************************/
56 
57     public int opApply (scope int delegate (ref Throwable) dg)
58     {
59         int result;
60 
61         for (auto e = root; e !is null; e = e.next)
62         {
63             result = dg(e);
64 
65             if (result)
66             {
67                 break;
68             }
69         }
70 
71         return result;
72     }
73 }
74 
75 unittest
76 {
77     auto e1 = new Exception("1");
78     auto e2 = new Exception("2", __FILE__, __LINE__, e1);
79 
80     size_t counter;
81     foreach (e; ExceptionChain(e2))
82         ++counter;
83     test (counter == 2);
84 }
85 
86 /******************************************************************************
87 
88     Common code to implement reusable exception semantics - one that has
89     mutable message buffer where message data gets stored to
90 
91 ******************************************************************************/
92 
93 public template ReusableExceptionImplementation()
94 {
95     import ocean.meta.types.Qualifiers;
96     import ocean.core.Buffer;
97     static import ocean.core.array.Mutation;
98     static import ocean.text.convert.Formatter;
99     static import ocean.text.convert.Integer_tango;
100 
101     static assert (is(typeof(this) : Exception));
102 
103     /**************************************************************************
104 
105         Fields used instead of `msg` for mutable messages. `Exception.msg`
106         has `istring` type thus can't be overwritten with new data
107 
108     ***************************************************************************/
109 
110     protected Buffer!(char) reused_msg;
111 
112     /***************************************************************************
113 
114         Constructs exception object with mutable buffer pre-allocated to length
115         `size` and other fields kept invalid.
116 
117         Params:
118             size = initial size/length of mutable message buffer
119 
120     ***************************************************************************/
121 
122     this (size_t size = 128)
123     {
124         this.reused_msg.length = size;
125         super(null);
126     }
127 
128     /***************************************************************************
129 
130         Sets exception information for this instance.
131 
132         Params:
133             msg  = exception message
134             file = file where the exception has been thrown
135             line = line where the exception has been thrown
136 
137         Returns:
138             this instance
139 
140     ***************************************************************************/
141 
142     public typeof (this) set ( cstring msg, istring file = __FILE__,
143         long line = __LINE__ )
144     {
145         ocean.core.array.Mutation.copy(this.reused_msg, msg);
146         this.msg  = null;
147         this.file = file;
148         this.line = line;
149         return this;
150     }
151 
152    /***************************************************************************
153 
154         Throws this instance if ok is false, 0 or null.
155 
156         Params:
157             ok   = condition to enforce
158             msg  = exception message
159             file = file where the exception has been thrown
160             line = line where the exception has been thrown
161 
162         Throws:
163             this instance if ok is false, 0 or null.
164 
165     ***************************************************************************/
166 
167     public void enforce ( T ) ( T ok, lazy cstring msg, istring file = __FILE__,
168         long line = __LINE__ )
169     {
170         if (!ok)
171             throw this.set(msg, file, line);
172     }
173 
174     static if (is(typeof(Throwable.message)))
175     {
176         /**************************************************************************
177 
178             Returns:
179                 currently active exception message
180 
181         ***************************************************************************/
182 
183         public override cstring message ( ) const
184         {
185             return this.msg[].ptr is null ? this.reused_msg[] : this.msg;
186         }
187     }
188 
189     /**************************************************************************
190 
191         Appends new substring to mutable exception message
192 
193         Intended to be used in hosts that do dynamic formatting of the
194         error message and want to avoid repeating allocation with help
195         of ReusableException semantics
196 
197         Leaves other fields untouched
198 
199         Params:
200             msg = string to append to the message
201 
202         Returns:
203             this instance
204 
205     ***************************************************************************/
206 
207     public typeof (this) append ( cstring msg )
208     {
209         ocean.core.array.Mutation.append(this.reused_msg, msg);
210         return this;
211     }
212 
213     /**************************************************************************
214 
215         Appends an integer to mutable exception message
216 
217         Does not cause any appenditional allocations
218 
219         Params:
220             num = number to be formatted
221             hex = optional, indicates that value needs to be formatted as hex
222 
223         Returns:
224             this instance
225 
226     **************************************************************************/
227 
228     public typeof (this) append ( long num, bool hex = false )
229     {
230         char[long.max.stringof.length + 1] buff;
231         if (hex)
232         {
233             ocean.core.array.Mutation.append(this.reused_msg, "0x"[]);
234             ocean.core.array.Mutation.append(
235                 this.reused_msg,
236                 ocean.text.convert.Integer_tango.format(buff, num, "X"));
237         }
238         else
239             ocean.core.array.Mutation.append(
240                 this.reused_msg,
241                 ocean.text.convert.Integer_tango.format(buff, num));
242         return this;
243     }
244 
245     /**************************************************************************
246 
247         Appends formatted string
248 
249         Params:
250             fmt = string format to use
251             args = variadic args list to format
252 
253         Returns:
254             this instance
255 
256     **************************************************************************/
257 
258     public typeof (this) fmtAppend ( Args... ) ( cstring fmt, Args args )
259     {
260         ocean.text.convert.Formatter.sformat(this.reused_msg, fmt, args);
261         return this;
262     }
263 }
264 
265 ///
266 unittest
267 {
268     auto e = new SomeReusableException(100);
269 
270     e.set("message");
271     test (e.message() == "message");
272     auto old_ptr = e.reused_msg[].ptr;
273 
274     try
275     {
276         ocean.core.Enforce.enforce(e, false, "immutable");
277         assert (false);
278     }
279     catch (SomeReusableException) { }
280     test (e.message() == "immutable");
281 
282     try
283     {
284         e.enforce(false, "longer message");
285     }
286     catch (SomeReusableException) { }
287     test (e.message() == "longer message");
288     test (old_ptr is e.reused_msg[].ptr);
289 
290     try
291     {
292         e.badName("NAME", 42);
293     }
294     catch (SomeReusableException) { }
295     test (e.message() == "Wrong name (NAME) 0x2A 42");
296     test (old_ptr is e.reused_msg[].ptr);
297 }
298 
299 unittest
300 {
301     auto e = new SomeReusableException;
302     e.set("aaa").fmtAppend("{0}{0}", 42);
303     test!("==")(e.message(), "aaa4242");
304 }
305 
306 version (unittest)
307 {
308     private class SomeReusableException : Exception
309     {
310         void badName(istring name, uint id)
311         {
312             this.set("Wrong name (")
313                 .append(name)
314                 .append(") ")
315                 .append(id, true)
316                 .append(" ")
317                 .append(id);
318             throw this;
319         }
320 
321         mixin ReusableExceptionImplementation!();
322     }
323 }
324 
325 
326 /******************************************************************************
327 
328     Common code to implement exception constructor, which takes a message as
329     a required parameter, and file and line with default value, and forward
330     it to the `super` constructor
331 
332 ******************************************************************************/
333 
334 public template DefaultExceptionCtor()
335 {
336     import ocean.meta.types.Qualifiers;
337 
338     public this (istring msg, istring file = __FILE__,
339                  typeof(__LINE__) line = __LINE__)
340     {
341         super (msg, file, line);
342     }
343 }
344 
345 version (unittest)
346 {
347     public class CardBoardException : Exception
348     {
349         mixin DefaultExceptionCtor;
350     }
351 }
352 
353 ///
354 unittest
355 {
356     auto e = new CardBoardException("Transmogrification failed");
357     try
358     {
359         throw e;
360     }
361     catch (CardBoardException e)
362     {
363         test!("==")(e.message(), "Transmogrification failed"[]);
364         test!("==")(e.file, __FILE__[]);
365         test!("==")(e.line, __LINE__ - 9);
366     }
367 }
368 
369 // Check that the inherited `Exception.toString` is not
370 // shadowed by anything (like nested imports).
371 unittest
372 {
373     auto s = (new SomeReusableException).toString();
374 }