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 }