private void followFlows(Function func, AddressSetView restrictSet, TaskMonitor monitor)
			throws CancelledException {
		if (frameReg == null) {
			return;
		}

		// check if this is thunk function
		if (func.isThunk()) {
			return;
		}

		// if extrapop is has an unknown purge, check for a purge on return instructions
		int purge = (short) program.getCompilerSpec().getDefaultCallingConvention().getExtrapop();
		final boolean possiblePurge = purge == -1 || purge > 3200 || purge < -3200;

		// follow all flows building up context
		// use context to fill out addresses on certain instructions 
		final ContextEvaluator eval = new ContextEvaluatorAdapter() {

			@Override
			public boolean evaluateContextBefore(VarnodeContext context, Instruction instr) {
				Varnode stackRegVarnode = context.getRegisterVarnode(frameReg);
				Varnode stackValue = context.getValue(stackRegVarnode, true, this);

				if (stackValue != null && context.isSymbol(stackValue) &&
					context.isStackSymbolicSpace(stackValue)) {
					long stackPointerDepth = stackValue.getOffset();
					int size = stackValue.getSize();
					stackPointerDepth = (stackPointerDepth << 8 * (8 - size)) >> 8 * (8 - size);
					setDepth(instr, (int) stackPointerDepth);
				}

				return false;
			}

			@Override
			public boolean evaluateContext(VarnodeContext context, Instruction instr) {
				FlowType ftype = instr.getFlowType();
				if (possiblePurge && ftype.isTerminal()) {
					String mnemonicStr = instr.getMnemonicString().toLowerCase();
					if ("ret".equals(mnemonicStr) || "retf".equals(mnemonicStr)) {
						// x86 has a scalar operand to purge value from the stack
						int tempPurge = 0;
						Scalar scalar = instr.getScalar(0);
						if (scalar != null) {
							tempPurge = (int) scalar.getSignedValue();
							stackPurge = tempPurge;
						}
						else {
							stackPurge = 0;
						}
					}
				}

				return false;
			}

			@Override
			public boolean evaluateSymbolicReference(VarnodeContext context, Instruction instr,
					Address address) {
				if (instr.getFlowType().isTerminal()) {
					return false;
				}
				checkForStackOffset(context, instr, address, -1);
				return false;
			}

			private void checkForStackOffset(VarnodeContext context, Instruction instr,
					Address address, int opIndex) {
				String spaceName = address.getAddressSpace().getName();
				if (spaceName.startsWith("track_") || context.isStackSpaceName(spaceName)) {
					// TODO: what to do on a Symbolic reference
				}
			}
		};

		// set the stack pointer to be tracked
		stackReg = program.getLanguage().getDefaultCompilerSpec().getStackPointer();
		if (stackReg == null) {
			return;
		}

		symEval.setRegister(func.getEntryPoint(), stackReg);

		setDepth(func.getEntryPoint(), 0);

		symEval.flowConstants(func.getEntryPoint(), restrictSet, eval, true, monitor);

		return;
	}
Follow the flows of the subroutine, accumulating information about the
stack pointer and any other register the stack pointer is assigned to.

@param func
           function to analyze
@param monitor
           monitor to provide feedback and cancel
@throws CancelledException if monitor canceled
Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CallDepthChangeInfo.java