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 }