public bool IsNoreturnFunction(AnalyzedSymbol function, IStackAnalyzerLogger optionalLogger)
 {
     return(false);
 }
        public FunctionStackUsage AnalyzeFunctionStackUsage(AnalyzedSymbol function, IStackAnalyzerLogger optionalLogger)
        {
            FunctionStackUsage result = new FunctionStackUsage {
                CalledFunctions = new List <CalledFunctionStackUsage>()
            };

            var codePaths = new Queue <PendingCodePath>();

            codePaths.Enqueue(new PendingCodePath(function.Address, new StackAnalyzerContext()));

            HashSet <ulong> coveredAddresses = new HashSet <ulong>();

#if DEBUG
            int codePathNumber = 0;
#endif

            while (codePaths.Count > 0)
            {
#if DEBUG
                codePathNumber++;
#endif
                var path = codePaths.Dequeue();
                var ctx  = path.Context;

                optionalLogger?.LogLine($"***Starting code path at 0x{path.Address:x8} with stack depth = {path.Context.Depth}" + (path.Context.EntryStackDepthForLocalCalls != 0 ? $" (local subroutine with a stack offset of {path.Context.EntryStackDepthForLocalCalls})" : ""));

                foreach (var insn in _Host.TryReadInstructions(path.Address))
                {
                    if (insn.Opcode == null)
                    {
                        result.AppendFlag(FunctionStackUsageFlags.HasJumpsToUnreadableAddresses, insn, optionalLogger);
                        break;
                    }

                    if (coveredAddresses.Contains(insn.Address))
                    {
                        optionalLogger?.LogLine($"*** 0x{path.Address:x8} already analyzed");
                        break;  //Already checked this instruction from another path. We may want to double-check that the stack depth is the same as last time though.
                    }

                    coveredAddresses.Add(insn.Address);

                    var effects = ClassifyInstruction(insn);

                    if (effects.HasAnyEffect(StackRelatedInstructionEffect.ChangesStackPointerUnpredictably))
                    {
                        result.AppendFlag(FunctionStackUsageFlags.HasDynamicStack, insn, optionalLogger);
                    }
                    else if (effects.HasAnyEffect(StackRelatedInstructionEffect.RegisterJump))
                    {
                        result.AppendFlag(FunctionStackUsageFlags.HasDynamicCalls, insn, optionalLogger);
                    }
                    else if (effects.HasAnyEffect(StackRelatedInstructionEffect.WarningMask))
                    {
                        result.AppendFlag(FunctionStackUsageFlags.HasOtherWarning, insn, optionalLogger);
                    }

                    if (effects.HasAnyEffect(StackRelatedInstructionEffect.SavesStackPointerWithDelta))
                    {
                        ctx.SavedStackDepth = ctx.Depth + effects.StackDelta;
                    }
                    if (effects.HasAnyEffect(StackRelatedInstructionEffect.RestoresStackPointer))
                    {
                        ctx.Depth = ctx.SavedStackDepth;
                        if (ctx.FramePointedChangedUnpredictably)
                        {
                            result.AppendFlag(FunctionStackUsageFlags.HasDynamicStack, insn, optionalLogger);
                        }
                    }
                    if (effects.HasAnyEffect(StackRelatedInstructionEffect.ChangesFramePointerUnpredictably))
                    {
                        ctx.FramePointedChangedUnpredictably = true;
                    }

                    if (effects.HasAnyEffect(StackRelatedInstructionEffect.MovesSavedStackPointer))
                    {
                        ctx.SavedStackDepth += effects.StackDelta;
                    }

                    if (effects.HasAnyEffect(StackRelatedInstructionEffect.MovesStackPointer))
                    {
                        result.MaximumOwnDepthIncludingPushedArguments = Math.Max(result.MaximumOwnDepthIncludingPushedArguments, ctx.Depth);
                        ctx.Depth += effects.StackDelta;
                        result.MaximumOwnDepthIncludingPushedArguments = Math.Max(result.MaximumOwnDepthIncludingPushedArguments, ctx.Depth);
                        if (ctx.Depth < 0)
                        {
                            optionalLogger?.ReportWarning(insn, FunctionStackUsageFlags.HasStackImbalance);
                            result.AppendFlag(FunctionStackUsageFlags.HasStackUnderrun, insn, optionalLogger);
                        }
                    }

                    if (effects.HasAnyEffect(StackRelatedInstructionEffect.FunctionCall))
                    {
                        bool isLocalCall = false;
                        if (effects.HasAnyEffect(StackRelatedInstructionEffect.JumpTargetKnown))
                        {
                            if (function.ContainsAddress(effects.JumpTarget))
                            {
                                //This is a local subroutine (e.g. used by __aeabi_dmul). Once it reaches the 'bx lr' instruction, it will get back to the next instruction after the current one.
                                isLocalCall = true;
                                var ctx2 = ctx.Clone();
                                ctx2.EntryStackDepthForLocalCalls = ctx2.Depth;
                                codePaths.Enqueue(new PendingCodePath(effects.JumpTarget, ctx2));
                            }
                            else
                            {
                                result.CalledFunctions.Add(new CalledFunctionStackUsage(insn.Address, effects.JumpTarget, ctx.Depth, false));
                            }
                        }

                        if (!isLocalCall && _Host.IsNoreturnFunction(effects.JumpTarget))
                        {
                            break;  //This call will never return
                        }
                    }

                    optionalLogger?.ReportInstructionStatus(ctx.Depth, insn, effects.ToString());

                    if (effects.HasAnyEffect(StackRelatedInstructionEffect.ReturnFromCall))
                    {
                        //End of path. This is either a return from the function, or a return from a local subroutine.
                        if (ctx.EntryStackDepthForLocalCalls != 0 && effects.HasAnyEffect(StackRelatedInstructionEffect.JumpsViaLinkRegister))
                        {
                            if (ctx.Depth != ctx.EntryStackDepthForLocalCalls)
                            {
                                result.AppendFlag(FunctionStackUsageFlags.HasStackImbalance, insn, optionalLogger);
                            }
                        }
                        else
                        {
                            if (ctx.Depth != 0)
                            {
                                result.AppendFlag(FunctionStackUsageFlags.HasStackImbalance, insn, optionalLogger);
                            }
                        }

                        break;
                    }

                    if (effects.HasAnyEffect(StackRelatedInstructionEffect.UnconditionalJump) && ctx.Depth == 0 && !function.ContainsAddress(effects.JumpTarget))
                    {
                        //This is a tail call at the end of the function
                        if (effects.HasAnyEffect(StackRelatedInstructionEffect.JumpTargetKnown))
                        {
                            result.CalledFunctions.Add(new CalledFunctionStackUsage(insn.Address, effects.JumpTarget, ctx.Depth, true));
                        }
                        break;
                    }

                    if (effects.HasAnyEffect(StackRelatedInstructionEffect.ConditionalJump | StackRelatedInstructionEffect.UnconditionalJump))
                    {
                        if (effects.HasAnyEffect(StackRelatedInstructionEffect.JumpTargetKnown))
                        {
                            codePaths.Enqueue(new PendingCodePath(effects.JumpTarget, ctx.Clone()));
                        }

                        if (effects.HasAnyEffect(StackRelatedInstructionEffect.UnconditionalJump))
                        {
                            break;  //We will continue this when we start analyzing the queued path.
                        }
                    }
                }
            }

            return(result);
        }