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