1 /** A safe union, or variant, disallowing reading invalid types
2 
3 The C API implements operand values as regular unions, enabling (mis)interpretation of the stored value in any of the
4 encompassed types. This implementation provides a safer union that throws when interpreting the union's value in a
5 different type than it was stored as.
6 */
7 module capstone.utils.safeunion;
8 
9 import std.traits: FieldTypeTuple, FieldNameTuple;
10 import std.meta;
11 import std.exception: enforce, basicExceptionCtors, assertThrown, assertNotThrown;
12 
13 /** Constructs a `SafeUnion` wrapping the specified union type
14 
15 Enables access of the stored value through the field names of the wrapped union.
16 */
17 template SafeUnion(U) if (is(U == union)) {
18 	template FieldSpec(T, string s) {
19 		alias Type = T;
20 		alias name = s;
21 	}
22 
23 	template parseSpecs(Specs...) {
24 		enum midIdx = Specs.length/2;
25 		static if(Specs.length == 0)
26 			alias parseSpecs = AliasSeq!();
27 		else static if(is(Specs[0]))
28 			alias parseSpecs = AliasSeq!(FieldSpec!(Specs[0], Specs[midIdx]), parseSpecs!(Specs[1..midIdx], Specs[midIdx+1..$]));
29 		else
30 			static assert(0, "Attempted to instantiate MultiValue with an invalid argument: " ~ Specs[0].stringof ~ " " ~ Specs[midIdx].stringof);
31 	}
32 
33 	alias fieldSpecs = parseSpecs!(FieldTypeTuple!U, FieldNameTuple!U);
34 	alias extractType(alias spec) = spec.Type;
35 	alias extractName(alias spec) = spec.name;
36 	alias FieldTypes = staticMap!(extractType, fieldSpecs);
37 	alias fieldNames = staticMap!(extractName, fieldSpecs);
38 
39 	struct SafeUnion{
40 		private U wrapped;
41 		private string curType;
42 
43 		// Generate getters (by identifier)
44 		static foreach(spec; fieldSpecs){
45 			mixin("@property " ~ spec.name ~"() const {
46 				auto expectedType = spec.Type.stringof;
47 				enforce!SafeUnionException(curType == expectedType,
48 				\"Trying to read a '\" ~ spec.Type.stringof ~ \"' but a '\" ~ curType ~ \"' is stored\");
49 				return wrapped." ~ spec.name ~ ";
50 			}");
51 		}
52 
53 		// Generate setters
54 		static foreach(spec; fieldSpecs){
55 			mixin("@property " ~ spec.name ~ "(spec.Type val){wrapped." ~ spec.name ~ " = val; curType = spec.Type.stringof;}");
56 		}
57 	}
58 }
59 ///
60 unittest{
61 	import capstone.api;
62 	import capstone.x86;
63 
64 	auto cs = new CapstoneX86(ModeFlags(Mode.bit32));
65 	auto x86reg = new X86Register(cs, X86RegisterId.eip);
66 
67 	SafeUnion!X86OpValue safeVal;
68 	assertThrown(safeVal.imm);    	   // Not initialised
69 	safeVal.reg = x86reg;
70 	assertThrown(safeVal.imm);    	   // cannot access the `long` since a `X86Register` is currently stored
71 	safeVal.imm = 42;
72 	assertNotThrown(safeVal.imm);      // can access the `long` now
73 
74 	// Corresponding operations on a regular union go unnoticed
75 	X86OpValue unsafeVal;
76 	unsafeVal.reg = x86reg;
77 	assertNotThrown(unsafeVal.imm);
78 }
79 
80 /// Thrown on misuse of `SafeUnion`
81 class SafeUnionException : Exception{
82 	///
83 	mixin basicExceptionCtors;
84 }