// this function generates code for an opaque predicate that evaluates to "true"
        // => the "taken" branch of the conditional branch have to be taken for the correct code path
        private void createCodeTruePredicate(ref BasicBlock outStartBasicBlock, ref ConditionalBranchTarget outConditionalBranch,
            BasicBlockGraphNodeLink link, LocalDefinition currentNodeLocal) {

            // create a basic block that checks the opaque predicate (this check evaluates to "true")
            BasicBlock startBasicBlock = new BasicBlock();
            startBasicBlock.startIdx = 0;
            startBasicBlock.endIdx = 0;

            // add graph transformer metadata to the basic block
            GraphTransformerPredicateBasicBlock metadata = new GraphTransformerPredicateBasicBlock();
            metadata.predicateType = GraphOpaquePredicate.True;
            metadata.correspondingGraphNodes.Add(link);
            startBasicBlock.transformationMetadata.Add(metadata);

            // search for the valid and invalid interfaces list of the current valid path id
            List<ITypeReference> validInterfaces = null;
            List<ITypeReference> invalidInterfaces = null;
            foreach (PathElement pathElement in link.graphNode.pathElements) {
                if (pathElement.validPathId == link.validPathId) {
                    validInterfaces = pathElement.validInterfaces;
                    invalidInterfaces = pathElement.invalidInterfaces;
                }
            }
            if (validInterfaces == null
                || invalidInterfaces == null) {
                throw new ArgumentException("No valid/invalid interface list was found.");
            }

            emitPredicateCode(startBasicBlock, currentNodeLocal, validInterfaces, invalidInterfaces);

            // create the conditional branch for the checking basic block (taken => correct code; not taken => dead code)
            ConditionalBranchTarget startBasicBlockExitBranch = new ConditionalBranchTarget();
            startBasicBlockExitBranch.sourceBasicBlock = startBasicBlock;
            startBasicBlock.exitBranch = startBasicBlockExitBranch;

            // create the dead code for this opaque predicate
            BasicBlock deadCodeBasicBlock = new BasicBlock();
            deadCodeBasicBlock.startIdx = 0;
            deadCodeBasicBlock.endIdx = 0;

            // set the dead code basic block as "not taken" target for the conditional branch of the checking basic block
            deadCodeBasicBlock.entryBranches.Add(startBasicBlockExitBranch);
            startBasicBlockExitBranch.notTakenTarget = deadCodeBasicBlock;

            // generate dead code for the dead code basic block
            GraphTransformerDeadCodeBasicBlock deadCodeMetadata = new GraphTransformerDeadCodeBasicBlock();
            deadCodeBasicBlock.transformationMetadata.Add(deadCodeMetadata);

            // set the created basic block and conditional jumps as output
            outStartBasicBlock = startBasicBlock;
            outConditionalBranch = startBasicBlockExitBranch;

        }
        // this function generates code for an opaque predicate that evaluates to "false"
        // => the "not taken" branch of the conditional branch have to be taken for the correct code path
        private void createCodeFalsePredicate(ref BasicBlock outStartBasicBlock, ref ConditionalBranchTarget outConditionalBranch, BasicBlockGraphNodeLink link, LocalDefinition currentNodeLocal) {

            // create a basic block that checks the opaque predicate (this check evaluates to "false")
            BasicBlock startBasicBlock = new BasicBlock();
            startBasicBlock.startIdx = 0;
            startBasicBlock.endIdx = 0;

            // add graph transformer metadata to the basic block
            GraphTransformerPredicateBasicBlock metadata = new GraphTransformerPredicateBasicBlock();
            metadata.predicateType = GraphOpaquePredicate.False;
            metadata.correspondingGraphNodes.Add(link);
            startBasicBlock.transformationMetadata.Add(metadata);

            // search for the valid and invalid interfaces list of the current valid path id
            List<ITypeReference> validInterfaces = null;
            List<ITypeReference> invalidInterfaces = null;
            foreach (PathElement pathElement in link.graphNode.pathElements) {
                if (pathElement.validPathId == link.validPathId) {
                    validInterfaces = pathElement.validInterfaces;
                    invalidInterfaces = pathElement.invalidInterfaces;
                }
            }
            if(validInterfaces == null
                || invalidInterfaces == null) {
                    throw new ArgumentException("No valid/invalid interface list was found.");
            }

            // add a random check to the code
            // important: for this predicate the dead code is reached by the "taken" branch and the correct code is reached by the "not taken" branch
            int randPosition;
            ITypeReference interfaceToCheck = null;
            switch (this.prng.Next(8)) {
                case 0:
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldloc, currentNodeLocal));

                    randPosition = this.prng.Next(validInterfaces.Count());
                    interfaceToCheck = validInterfaces.ElementAt(randPosition);
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Isinst, interfaceToCheck));

                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldnull));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Cgt_Un));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Brfalse, 0));
                    break;

                case 1:
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldloc, currentNodeLocal));

                    randPosition = this.prng.Next(validInterfaces.Count());
                    interfaceToCheck = validInterfaces.ElementAt(randPosition);
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Isinst, interfaceToCheck));

                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldnull));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Cgt_Un));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldc_I4_0));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ceq));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Brtrue, 0));
                    break;

                case 2:
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldloc, currentNodeLocal));

                    randPosition = this.prng.Next(validInterfaces.Count());
                    interfaceToCheck = validInterfaces.ElementAt(randPosition);
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Isinst, interfaceToCheck));

                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldnull));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Cgt_Un));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldc_I4_1));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ceq));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Brfalse, 0));
                    break;

                case 3:
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldloc, currentNodeLocal));

                    randPosition = this.prng.Next(validInterfaces.Count());
                    interfaceToCheck = validInterfaces.ElementAt(randPosition);
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Isinst, interfaceToCheck));

                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldnull));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ceq));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Brtrue, 0));
                    break;

                case 4:
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldloc, currentNodeLocal));

                    randPosition = this.prng.Next(invalidInterfaces.Count());
                    interfaceToCheck = invalidInterfaces.ElementAt(randPosition);
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Isinst, interfaceToCheck));

                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldnull));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Cgt_Un));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Brtrue, 0));
                    break;

                case 5:
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldloc, currentNodeLocal));

                    randPosition = this.prng.Next(invalidInterfaces.Count());
                    interfaceToCheck = invalidInterfaces.ElementAt(randPosition);
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Isinst, interfaceToCheck));

                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldnull));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Cgt_Un));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldc_I4_0));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ceq));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Brfalse, 0));
                    break;

                case 6:
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldloc, currentNodeLocal));

                    randPosition = this.prng.Next(invalidInterfaces.Count());
                    interfaceToCheck = invalidInterfaces.ElementAt(randPosition);
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Isinst, interfaceToCheck));

                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldnull));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Cgt_Un));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldc_I4_1));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ceq));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Brtrue, 0));
                    break;

                case 7:
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldloc, currentNodeLocal));

                    randPosition = this.prng.Next(invalidInterfaces.Count());
                    interfaceToCheck = invalidInterfaces.ElementAt(randPosition);
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Isinst, interfaceToCheck));

                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ldnull));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Ceq));
                    startBasicBlock.operations.Add(this.helperClass.createNewOperation(OperationCode.Brfalse, 0));
                    break;

                default:
                    throw new ArgumentException("This case should never be reached.");
            }

            // create the conditional branch for the checking basic block (not taken => correct code; taken => dead code)
            ConditionalBranchTarget startBasicBlockExitBranch = new ConditionalBranchTarget();
            startBasicBlockExitBranch.sourceBasicBlock = startBasicBlock;
            startBasicBlock.exitBranch = startBasicBlockExitBranch;


            // create the dead code for this opaque predicate
            BasicBlock deadCodeBasicBlock = new BasicBlock();
            deadCodeBasicBlock.startIdx = 0;
            deadCodeBasicBlock.endIdx = 0;

            // set the dead code basic block as "taken" target for the conditional branch of the checking basic block
            deadCodeBasicBlock.entryBranches.Add(startBasicBlockExitBranch);
            startBasicBlockExitBranch.takenTarget = deadCodeBasicBlock;

            // generate dead code for the dead code basic block
            GraphTransformerDeadCodeBasicBlock deadCodeMetadata = new GraphTransformerDeadCodeBasicBlock();
            deadCodeBasicBlock.transformationMetadata.Add(deadCodeMetadata);

            // set the created basic block and conditional jumps as output
            outStartBasicBlock = startBasicBlock;
            outConditionalBranch = startBasicBlockExitBranch;

        }