Ejemplo n.º 1
0
        public void LastBlockOnFlatScopeWithSingleItemShouldReturnFirstBasicBlockInList()
        {
            var scope = new ScopeBlock <int>();

            scope.Blocks.Add(new BasicBlock <int>());
            Assert.Same(scope.Blocks[0], scope.GetLastBlock());
        }
Ejemplo n.º 2
0
        public void AddHelpers()
        {
            var scope = new ScopeBlock();

            var initBlock = new BasicBlock <IRInstrList>(1, new IRInstrList {
                new IRInstruction(IROpCode.RET)
            });

            scope.Content.Add(initBlock);

            var retnBlock = new BasicBlock <IRInstrList>(0, new IRInstrList {
                new IRInstruction(IROpCode.VCALL, IRConstant.FromI4(rt.Descriptor.Runtime.VMCall[VMCalls.EXIT]))
            });

            scope.Content.Add(initBlock);

            CompileHelpers(methodINIT, scope);

            var info = rt.Descriptor.Data.LookupInfo(methodINIT);

            scope.ProcessBasicBlocks <ILInstrList>(block => {
                if (block.Id == 1)
                {
                    AddHelper(null, methodINIT, (ILBlock)block);
                    var blockKey  = info.BlockKeys[block];
                    info.EntryKey = blockKey.EntryKey;
                    info.ExitKey  = blockKey.ExitKey;
                }
                rt.AddBlock(methodINIT, (ILBlock)block);
            });
        }
Ejemplo n.º 3
0
        private static ScopeBlock <TInstruction> BuildBlocksFromSortedNodes <TInstruction>(
            ControlFlowGraph <TInstruction> cfg,
            IEnumerable <ControlFlowNode <TInstruction> > sorting)
        {
            // We maintain a stack of scope information. Every time we enter a new region, we enter a new scope,
            // and similarly, we leave a scope when we leave a region.

            var rootScope  = new ScopeBlock <TInstruction>();
            var scopeStack = new IndexableStack <ScopeInfo <TInstruction> >();

            scopeStack.Push(new ScopeInfo <TInstruction>(cfg, rootScope));

            // Add the nodes in the order of the sorting.
            foreach (var node in sorting)
            {
                var currentScope = scopeStack.Peek();
                if (node.ParentRegion != currentScope.Region)
                {
                    UpdateScopeStack(scopeStack, node);
                    currentScope = scopeStack.Peek();
                }

                currentScope.AddBlock(node.Contents);
            }

            return(rootScope);
        }
Ejemplo n.º 4
0
        public void FirstBlockOnNonEmptyFlatScopeShouldReturnFirstBasicBlockInList()
        {
            var scope = new ScopeBlock <int>();

            scope.Blocks.Add(new BasicBlock <int>());
            Assert.Same(scope.Blocks[0], scope.GetFirstBlock());
        }
Ejemplo n.º 5
0
        // 컴파일
        public string GetSource()
        {
            try
            {
                GEntry       entry = new GEntry();
                List <GBase> list  = new List <GBase>();

                foreach (VariableBlock variableBlock in GetGlobalVariableBlockList())
                {
                    entry.Append(new GDefine(variableBlock.GVariable));
                }

                foreach (var block in Master.Children)
                {
                    if (block is ScopeBlock)
                    {
                        ScopeBlock scopeBlock = block as ScopeBlock;
                        entry.Append(scopeBlock.GScope);
                    }
                }

                return(entry.ToSource().TrimStart());
            }
            catch (ToObjectException e)
            {
                return(e.Message);
            }
        }
Ejemplo n.º 6
0
 internal AnnotationOutsideType(ScopeBlock parentBlock, string annotation)
 {
     ParentBlock = parentBlock;
     Annotations = new List <string> {
         annotation
     };
 }
Ejemplo n.º 7
0
        public void Translate(ScopeBlock rootScope)
        {
            var blockMap = rootScope.UpdateBasicBlocks <IRInstrList, ILInstrList>(
                block => { return(Translate(block.Content)); },
                (id, content) => new ILBlock(id, content));

            rootScope.ProcessBasicBlocks <ILInstrList>(block => {
                foreach (var instr in block.Content)
                {
                    if (instr.Operand is ILBlockTarget)
                    {
                        var op    = (ILBlockTarget)instr.Operand;
                        op.Target = blockMap[(BasicBlock <IRInstrList>)op.Target];
                    }
                    else if (instr.Operand is ILJumpTable)
                    {
                        var op = (ILJumpTable)instr.Operand;
                        for (int i = 0; i < op.Targets.Length; i++)
                        {
                            op.Targets[i] = blockMap[(BasicBlock <IRInstrList>)op.Targets[i]];
                        }
                    }
                }
            });
        }
Ejemplo n.º 8
0
        private EHMap MapEHs(ScopeBlock rootScope)
        {
            var map = new EHMap();

            this.MapEHsInternal(rootScope, map);
            return(map);
        }
Ejemplo n.º 9
0
        private static void EnterGenericRegion <TInstruction>(IndexableStack <ScopeInfo <TInstruction> > scopeStack,
                                                              IControlFlowRegion <TInstruction> enteredRegion)
        {
            var scopeBlock = new ScopeBlock <TInstruction>();

            scopeStack.Peek().AddBlock(scopeBlock);
            scopeStack.Push(new ScopeInfo <TInstruction>(enteredRegion, scopeBlock));
        }
Ejemplo n.º 10
0
 List <BaseBlock> UpdateParent(List <BaseBlock> lb, ScopeBlock parent)
 {
     foreach (var bb in lb)
     {
         bb.Parent = parent;
     }
     return(lb);
 }
Ejemplo n.º 11
0
        IEnumerable <ScopeBlock> getAllScopeBlocks(ScopeBlock scopeBlock)
        {
            var list = new List <ScopeBlock>();

            list.Add(scopeBlock);
            list.AddRange(scopeBlock.getAllScopeBlocks());
            return(list);
        }
Ejemplo n.º 12
0
        int ToCalculable(int i)
        {
            ScopeBlock expectedScope = new ScopeBlock();
            expectedScope.start.line = ToCalculable(31);
            expectedScope.start.column = ToCalculable(1);

            expectedScope.end.line = ToCalculable(32);
            expectedScope.end.column = ToCalculable(1);
Ejemplo n.º 13
0
        public IRTransformer(ScopeBlock rootScope, IRContext ctx, DarksVMRuntime runtime)
        {
            RootScope = rootScope;
            Context   = ctx;
            Runtime   = runtime;

            Annotations = new Dictionary <object, object>();
            InitPipeline();
        }
Ejemplo n.º 14
0
        private void AddTryStart(IRTransformer tr)
        {
            var tryStartInstrs = new List <IRInstruction>();

            for (int i = 0; i < this.thisScopes.Length; i++)
            {
                ScopeBlock scope = this.thisScopes[i];
                if (scope.Type != ScopeType.Try)
                {
                    continue;
                }
                if (scope.GetBasicBlocks().First() != tr.Block)
                {
                    continue;
                }

                // Search for handler/filter
                IBasicBlock handler = null, filter = null;
                this.SearchForHandlers(tr.RootScope, scope.ExceptionHandler, ref handler, ref filter);
                Debug.Assert(handler != null &&
                             (scope.ExceptionHandler.HandlerType != ExceptionHandlerType.Filter || filter != null));

                // Add instructions
                tryStartInstrs.Add(new IRInstruction(IROpCode.PUSH, new IRBlockTarget(handler)));

                IIROperand tryOperand = null;
                int        ehType;
                if (scope.ExceptionHandler.HandlerType == ExceptionHandlerType.Catch)
                {
                    tryOperand = IRConstant.FromI4((int)tr.VM.Data.GetId(scope.ExceptionHandler.CatchType));
                    ehType     = tr.VM.Runtime.RTFlags.EH_CATCH;
                }
                else if (scope.ExceptionHandler.HandlerType == ExceptionHandlerType.Filter)
                {
                    tryOperand = new IRBlockTarget(filter);
                    ehType     = tr.VM.Runtime.RTFlags.EH_FILTER;
                }
                else if (scope.ExceptionHandler.HandlerType == ExceptionHandlerType.Fault)
                {
                    ehType = tr.VM.Runtime.RTFlags.EH_FAULT;
                }
                else if (scope.ExceptionHandler.HandlerType == ExceptionHandlerType.Finally)
                {
                    ehType = tr.VM.Runtime.RTFlags.EH_FINALLY;
                }
                else
                {
                    throw new InvalidProgramException();
                }

                tryStartInstrs.Add(new IRInstruction(IROpCode.TRY, IRConstant.FromI4(ehType), tryOperand)
                {
                    Annotation = new EHInfo(scope.ExceptionHandler)
                });
            }
            tr.Instructions.InsertRange(0, tryStartInstrs);
        }
Ejemplo n.º 15
0
        public ILPostTransformer(MethodDef method, ScopeBlock rootScope, DarksVMRuntime runtime)
        {
            this.RootScope = rootScope;
            this.Method    = method;
            this.Runtime   = runtime;

            this.Annotations = new Dictionary <object, object>();
            this.pipeline    = this.InitPipeline();
        }
Ejemplo n.º 16
0
        public ILPostTransformer(MethodDef method, ScopeBlock rootScope, VMRuntime runtime)
        {
            RootScope = rootScope;
            Method    = method;
            Runtime   = runtime;

            Annotations = new Dictionary <object, object>();
            pipeline    = InitPipeline();
        }
Ejemplo n.º 17
0
            public List <BaseBlock> GetBlocks(ScopeBlock parent)
            {
                if (blocksLeft.Count == 0)
                {
                    return(new List <BaseBlock>());
                }
                var lb = GetBlocks(0, blocksLeft[blocksLeft.Count - 1].endInstr, out int startIndex, out int endIndex);

                return(UpdateParent(lb, parent));
            }
Ejemplo n.º 18
0
        /// <summary>
        /// Converts a method block into instructions
        /// </summary>
        /// <param name="methodBlock"></param>
        /// <param name="instructions"></param>
        /// <param name="exceptionHandlers"></param>
        /// <param name="locals"></param>
        public static void Generate(ScopeBlock methodBlock, out IList <Instruction> instructions, out IList <ExceptionHandler> exceptionHandlers, out IList <Local> locals)
        {
            var generator = new CodeGenerator();

            generator.Layout(methodBlock);
            instructions      = generator.GenerateInstructions();
            exceptionHandlers = generator.GenerateExceptionHandlers();
            locals            = GenerateLocals((List <Instruction>)instructions);
            generator.Cleanup();
        }
Ejemplo n.º 19
0
 public void deobfuscate(ScopeBlock scopeBlock)
 {
     while (true) {
         var switchObfuscationInfo = new SwitchObfuscationInfo((instr) => getLocalVar(instr));
         if (!findSwitchObfuscation(scopeBlock, switchObfuscationInfo))
             break;
         switchObfuscationInfo.fixSwitchBranches(scopeBlock);
         scopeBlock.removeDeadBlocks(new List<Block>(switchObfuscationInfo.SwitchTargetBlocks));
         scopeBlock.mergeBlocks();
     }
 }
Ejemplo n.º 20
0
        private ILASTBuilder(MethodDef method, CilBody body, ScopeBlock scope)
        {
            this.method = method;
            this.body   = body;
            this.scope  = scope;

            this.basicBlocks     = scope.GetBasicBlocks().Cast <CILBlock>().ToList();
            this.blockHeaders    = this.basicBlocks.ToDictionary(block => block.Content[0], block => block);
            this.blockStates     = new Dictionary <CILBlock, BlockState>();
            this.instrReferences = new List <ILASTExpression>();
            Debug.Assert(this.basicBlocks.Count > 0);
        }
Ejemplo n.º 21
0
        /// <summary>
        /// Restores <see cref="MethodDef"/> from method block
        /// </summary>
        /// <param name="methodDef"></param>
        /// <param name="methodBlock"></param>
        public static void FromMethodBlock(this MethodDef methodDef, ScopeBlock methodBlock)
        {
            var body = methodDef.Body;

            CodeGenerator.Generate(methodBlock, out var instructions, out var exceptionHandlers, out var locals);
            body.Instructions.Clear();
            body.Instructions.AddRange(instructions);
            body.ExceptionHandlers.Clear();
            body.ExceptionHandlers.AddRange(exceptionHandlers);
            body.Variables.Clear();
            body.Variables.AddRange(locals);
        }
Ejemplo n.º 22
0
 public void Transform(IRTransformer tr)
 {
     this.thisScopes = tr.RootScope.SearchBlock(tr.Block);
     this.AddTryStart(tr);
     if (this.thisScopes[this.thisScopes.Length - 1].Type == ScopeType.Handler)
     {
         ScopeBlock   tryScope = this.SearchForTry(tr.RootScope, this.thisScopes[this.thisScopes.Length - 1].ExceptionHandler);
         ScopeBlock[] scopes   = tr.RootScope.SearchBlock(tryScope.GetBasicBlocks().First());
         this.thisScopes = scopes.TakeWhile(s => s != tryScope).ToArray();
     }
     tr.Instructions.VisitInstrs(this.VisitInstr, tr);
 }
Ejemplo n.º 23
0
        /// <summary>
        /// Inlines all basic blocks as much as possible.
        /// </summary>
        /// <param name="methodBlock"></param>
        /// <returns></returns>
        public static int Inline(ScopeBlock methodBlock)
        {
            if (methodBlock is null)
            {
                throw new ArgumentNullException(nameof(methodBlock));
            }
            if (methodBlock.Type != BlockType.Method)
            {
                throw new ArgumentException($"{nameof(methodBlock)} is not a method block");
            }

            return(Shared.BlockInliner.Inline(methodBlock, (x, y) => ((BasicBlock)x).Redirect((BasicBlock)y), (x, y) => ((BasicBlock)x).Concat((BasicBlock)y), t => ((BasicBlock)t).Erase()));
        }
Ejemplo n.º 24
0
        public void TestFindsSingleLineScope()
        {
            ScopeBlock expectedScope = new ScopeBlock();
            expectedScope.end.Line      = ToCalculable(3);
            expectedScope.start.Line    = ToCalculable(3);
            expectedScope.start.Column  = ToCalculable(1);
            expectedScope.end.Column    = ToCalculable(3);

            expectedScope.scope = ScopeBlock.Scope.Other;

            ScopeBlock scope = scopeFinder.FindScope(finder.Find("B"));
            Assert.AreEqual(expectedScope, scope);
        }
Ejemplo n.º 25
0
        /// <summary>
        /// Removes all unused blocks
        /// </summary>
        /// <param name="methodBlock"></param>
        /// <returns></returns>
        public static int RemoveUnusedBlocks(ScopeBlock methodBlock)
        {
            if (methodBlock is null)
            {
                throw new ArgumentNullException(nameof(methodBlock));
            }
            if (methodBlock.Type != BlockType.Method)
            {
                throw new ArgumentException($"{nameof(methodBlock)} is not a method block");
            }

            return(Shared.BlockCleaner.RemoveUnusedBlocks(methodBlock, t => ((BasicBlock)t).Erase()));
        }
Ejemplo n.º 26
0
        private static void EnterNextRegion <TInstruction>(
            IndexableStack <ScopeInfo <TInstruction> > scopeStack,
            IControlFlowRegion <TInstruction>[] activeRegions)
        {
            var enteredRegion = activeRegions[scopeStack.Count];

            // Add new scope block to the current scope.
            var currentScope = scopeStack.Peek();

            if (enteredRegion is ExceptionHandlerRegion <TInstruction> ehRegion)
            {
                // We entered an exception handler region.
                var ehBlock = new ExceptionHandlerBlock <TInstruction>();
                currentScope.AddBlock(ehBlock);
                scopeStack.Push(new ScopeInfo <TInstruction>(ehRegion, ehBlock));
            }
            else if (enteredRegion.ParentRegion is ExceptionHandlerRegion <TInstruction> parentEhRegion)
            {
                // We entered one of the exception handler sub regions. Figure out which one it is.
                ScopeBlock <TInstruction> enteredBlock;

                if (!(currentScope.Block is ExceptionHandlerBlock <TInstruction> ehBlock))
                {
                    throw new InvalidOperationException("The parent scope is not an exception handler scope.");
                }

                if (parentEhRegion.ProtectedRegion == enteredRegion)
                {
                    // We entered the protected region.
                    enteredBlock = ehBlock.ProtectedBlock;
                }
                else
                {
                    // We entered a handler region.
                    enteredBlock = new ScopeBlock <TInstruction>();
                    ehBlock.HandlerBlocks.Add(enteredBlock);
                }

                // Push the entered scope.
                scopeStack.Push(new ScopeInfo <TInstruction>(parentEhRegion.ProtectedRegion, enteredBlock));
            }
            else
            {
                // Fall back method: just enter a new scope block.
                var scopeBlock = new ScopeBlock <TInstruction>();
                currentScope.AddBlock(scopeBlock);
                scopeStack.Push(new ScopeInfo <TInstruction>(enteredRegion, scopeBlock));
            }
        }
Ejemplo n.º 27
0
        public void TestIgnoresMultipleTrailingLowerScope()
        {
            ScopeBlock expectedScope = new ScopeBlock();
            expectedScope.start.Line = ToCalculable(13);
            expectedScope.start.Column = ToCalculable(1);

            expectedScope.end.Line = ToCalculable(13);
            expectedScope.end.Column = ToCalculable(9);

            expectedScope.scope = ScopeBlock.Scope.Other;

            ScopeBlock scope = scopeFinder.FindScope(finder.Find("F"));
            Console.WriteLine(scope);
            Assert.AreEqual(expectedScope, scope);
        }
Ejemplo n.º 28
0
        public void TestFindsMultiLineScope()
        {
            ScopeBlock expectedScope = new ScopeBlock();
            expectedScope.start.Line = ToCalculable(5);
            expectedScope.start.Column = ToCalculable(1);

            expectedScope.end.Line = ToCalculable(7);
            expectedScope.end.Column = ToCalculable(1);

            expectedScope.scope = ScopeBlock.Scope.Other;

            ScopeBlock scope = scopeFinder.FindScope(finder.Find("C"));
            Console.WriteLine(scope);
            Assert.AreEqual(expectedScope, scope);
        }
Ejemplo n.º 29
0
        public void AddMethod(MethodDef method, ScopeBlock rootScope)
        {
            ILBlock entry = null;

            foreach (ILBlock block in rootScope.GetBasicBlocks())
            {
                if (block.Id == 0)
                {
                    entry = block;
                }
                basicBlocks.Add(Tuple.Create(method, block));
            }
            Debug.Assert(entry != null);
            methodMap[method] = Tuple.Create(rootScope, entry);
        }
Ejemplo n.º 30
0
        public void TestIgnoresLineComments()
        {
            ScopeBlock expectedScope = new ScopeBlock();
            expectedScope.start.line = ToCalculable(31);
            expectedScope.start.column = ToCalculable(1);

            expectedScope.end.line = ToCalculable(32);
            expectedScope.end.column = ToCalculable(1);

            expectedScope.scope = ScopeBlock.Scope.Other;


            ScopeBlock scope = scopeFinder.FindScope(finder.Find("H"));
            Console.WriteLine(scope);
            Assert.AreEqual(expectedScope, scope);
        }
Ejemplo n.º 31
0
 private ScopeBlock SearchForTry(ScopeBlock scope, ExceptionHandler eh)
 {
     if (scope.ExceptionHandler == eh && scope.Type == ScopeType.Try)
     {
         return(scope);
     }
     foreach (var child in scope.Children)
     {
         var s = SearchForTry(child, eh);
         if (s != null)
         {
             return(s);
         }
     }
     return(null);
 }
Ejemplo n.º 32
0
        bool findSwitchObfuscation(ScopeBlock scopeBlock, SwitchObfuscationInfo switchObfuscationInfo)
        {
            foreach (var bb in scopeBlock.getBaseBlocks()) {
                var block = bb as Block;
                if (block == null || foundBlocks.ContainsKey(block))
                    continue;

                if (block.Instructions.Count != 2 || !block.Instructions[0].isLdloc() || block.Instructions[1].OpCode != OpCodes.Switch)
                    continue;
                switchObfuscationInfo.switchBlock = block;
                switchObfuscationInfo.stateVar = getLocalVar(block.Instructions[0]);
                var typeName = switchObfuscationInfo.stateVar.VariableType.FullName;
                if (typeName != "System.Int32" && typeName != "System.UInt32")
                    continue;

                foundBlocks[block] = true;
                return true;
            }
            return false;
        }
			List<BaseBlock> UpdateParent(List<BaseBlock> lb, ScopeBlock parent) {
				foreach (var bb in lb)
					bb.Parent = parent;
				return lb;
			}
			public List<BaseBlock> GetBlocks(ScopeBlock parent) {
				if (blocksLeft.Count == 0)
					return new List<BaseBlock>();
				int startIndex, endIndex;
				var lb = GetBlocks(0, blocksLeft[blocksLeft.Count - 1].endInstr, out startIndex, out endIndex);
				return UpdateParent(lb, parent);
			}
			// Replace the BaseBlocks with a new BaseBlock, returning the old ones.
			public List<BaseBlock> Replace(int startInstr, int endInstr, ScopeBlock bb) {
				if (endInstr < startInstr)
					return new List<BaseBlock>();

				int startIndex, endIndex;
				var rv = GetBlocks(startInstr, endInstr, out startIndex, out endIndex);
				UpdateParent(rv, bb);

				var bbi = new BaseBlockInfo(blocksLeft[startIndex].startInstr, blocksLeft[endIndex].endInstr, bb);
				blocksLeft.RemoveRange(startIndex, endIndex - startIndex + 1);
				blocksLeft.Insert(startIndex, bbi);

				return rv;
			}
Ejemplo n.º 36
0
 public BrTrueDeobfuscator(ScopeBlock scopeBlock, IEnumerable<Block> blocks)
     : base(scopeBlock, blocks)
 {
 }
Ejemplo n.º 37
0
 public CondBranchDeobfuscator(ScopeBlock scopeBlock, IEnumerable<Block> blocks)
 {
     this.scopeBlock = scopeBlock;
     this.blocks = blocks;
 }
Ejemplo n.º 38
0
			void AddTargets(List<BaseBlock> dest, ScopeBlock scopeBlock) {
				foreach (var block in scopeBlock.GetAllBlocks())
					AddTargets(dest, block.GetTargets());
			}
Ejemplo n.º 39
0
			public Sorter(ScopeBlock scopeBlock, IList<BaseBlock> validBlocks, bool skipFirstBlock) {
				this.scopeBlock = scopeBlock;
				this.validBlocks = validBlocks;
				this.skipFirstBlock = skipFirstBlock;
			}
Ejemplo n.º 40
0
 public BlockState(ScopeBlock scopeBlock)
 {
     this.scopeBlock = scopeBlock;
 }
Ejemplo n.º 41
0
 public BlocksSorter(ScopeBlock scopeBlock)
 {
     this.scopeBlock = scopeBlock;
 }
 public ForwardScanOrder(ScopeBlock scopeBlock, IList<BaseBlock> sorted)
 {
     this.scopeBlock = scopeBlock;
     this.sorted = sorted;
 }
 void addScopeBlock(ScopeBlock scopeBlock)
 {
     scopeBlocksToCheck.Push(scopeBlock);
 }
 public ScopeBlockInfo(ScopeBlock scopeBlock)
 {
     this.scopeBlock = scopeBlock;
 }
        void processScopeBlock(ScopeBlock scopeBlock)
        {
            if (scopeBlock == null || checkedScopeBlocks.ContainsKey(scopeBlock))
                return;
            checkedScopeBlocks[scopeBlock] = true;
            addBaseBlock(scopeBlock);

            if (scopeBlock is TryBlock) {
                var tryBlock = (TryBlock)scopeBlock;
                foreach (var handler in tryBlock.TryHandlerBlocks)
                    addScopeBlock(handler);
            }
            else if (scopeBlock is TryHandlerBlock) {
                var tryHandlerBlock = (TryHandlerBlock)scopeBlock;
                addScopeBlock(tryHandlerBlock.FilterHandlerBlock);
                addScopeBlock(tryHandlerBlock.HandlerBlock);
            }
        }
Ejemplo n.º 46
0
		IEnumerable<ScopeBlock> GetAllScopeBlocks(ScopeBlock scopeBlock) {
			var list = new List<ScopeBlock>();
			list.Add(scopeBlock);
			list.AddRange(scopeBlock.GetAllScopeBlocks());
			return list;
		}
Ejemplo n.º 47
0
 public void fixSwitchBranches(ScopeBlock scopeBlock)
 {
     findAllSwitchTargetBlocks();
     foreach (var switchTargetBlock in new List<Block>(switchTargetBlocks.Keys)) {
         foreach (var block in new List<Block>(switchTargetBlock.Sources)) {
             int numInstrs;
             Block switchTarget;
             if (getSwitchIndex(block, out numInstrs, out switchTarget))
                 block.replaceLastInstrsWithBranch(numInstrs, switchTarget);
         }
     }
 }