public AddressSet flowConstants(Address fromAddr, Address startAddr, AddressSetView restrictSet,
			ContextEvaluator eval, VarnodeContext vContext, TaskMonitor monitor)
			throws CancelledException {
		visitedBody = new AddressSet();
		AddressSet conflicts = new AddressSet();

		// prime the context stack with the entry point address
		Stack<SavedFlowState> contextStack = new Stack<>();
		contextStack.push(new SavedFlowState(vContext, null, fromAddr, startAddr, NOT_CONTINUING_CURRRENTLY));
		canceled = false;

		// only stop flowing on unknown bad calls when the stack depth could be unknown
		boolean callCouldCauseBadStackDepth = program.getCompilerSpec()
				.getDefaultCallingConvention()
				.getExtrapop() == PrototypeModel.UNKNOWN_EXTRAPOP;

		HashMap<Address,HashSet<Address>> visitedMap = new HashMap<>();
		while (!contextStack.isEmpty()) {
			monitor.checkCancelled();
			if (canceled) {
				visitedBody.add(conflicts); // put the conflict/redone addresses back in
				return visitedBody;
			}

			SavedFlowState nextFlow = contextStack.pop();
			boolean justPopped = true;
			Address nextAddr = nextFlow.destination;
			Address flowFromAddr = nextFlow.source;
			FlowType flowType = nextFlow.flowType;
			int pcodeStartIndex = nextFlow.pcodeIndex;
			int continueAfterHittingFlow = nextFlow.continueAfterHittingFlow;
			nextFlow.restoreState();
			
			if (flowType != null) {
				// if call flow,  is inlined call, only inlined flows are pushed onto the flow stack
				//
				if (flowType.isCall()) {
					AddressSet savedBody = visitedBody;
					Function func = getFunctionAt(nextAddr);
					flowConstants(nextFlow.source, nextAddr, func.getBody(), eval, vContext, monitor);
					visitedBody = savedBody;
					continue;
				}
			
				// if jump flow, make sure it isn't jumping to another function
				//
				if (flowType.isJump() && !flowType.isConditional()) {
					// only jump to a computed location if there is no function there
					Function func = getFunctionAt(nextAddr);
					if (func != null && !func.getBody().contains(startAddr)) {
						// handle jump as if it were a call
						vContext.flowStart(nextAddr);
						handleFunctionSideEffects(getInstructionAt(flowFromAddr), nextAddr, monitor);
						continue;
					}
				}
			}

			HashSet<Address> visitSet = visitedMap.get(nextAddr);
			if (visitSet != null) {
				// already flowed to nextAddr from flowFromAddr
				if (visitSet.contains(flowFromAddr)) {
					continue;
				}
				// already hit nextAddr once.
				// continue for some number of instructions
				if (continueAfterHittingFlow == NOT_CONTINUING_CURRRENTLY) {
					continueAfterHittingFlow = 0;
				}
			}
			else {
				visitSet = new HashSet<>();
				visitedMap.put(nextAddr, visitSet);
				// never flowed to here, but have visited before
				if (continueAfterHittingFlow == NOT_CONTINUING_CURRRENTLY && visitedBody.contains(nextAddr)) {
					continueAfterHittingFlow = 0;
				}
			}

			visitSet.add(flowFromAddr);
			
			// record new flow from one basic block to another
			vContext.flowToAddress(fromAddr, nextAddr);

			lastFullHashCode = 0;
			lastInstrCode = -1;
			sameInstrCount = 0;
			Address maxAddr = null;
			while (nextAddr != null) {
				monitor.checkCancelled();

				// special flow start, retrieves the flow from/to saved state if there is one, and applies it
				//    As if a mergeFuture flow had been done.
				vContext.flowStart(nextAddr);
				
				if (!visitedBody.contains(nextAddr)) {
					// got to a flow never been to before, turn off any continue flow behavior
					continueAfterHittingFlow = NOT_CONTINUING_CURRRENTLY;
				}

				if (restrictSet != null && !restrictSet.contains(nextAddr)) {
					break;
				}

				Instruction instr = getInstructionAt(nextAddr);
				if (instr == null) {
					break;
				}
				FlowType originalFlowType = instr.getFlowType();

				// check that we aren't in a string of the same instruction
				if (checkSameInstructionRun(instr)) {
					break;
				}

				Address minInstrAddress = instr.getMinAddress();
				maxAddr = instr.getMaxAddress();

				// if this instruction has a delay slot, adjust maxAddr accordingly
				//
				if (instr.getPrototype().hasDelaySlots()) {
					maxAddr = minInstrAddress.add(instr.getDefaultFallThroughOffset() - 1);
				}

				vContext.setCurrentInstruction(instr);

				if (evaluator != null) {
					if (evaluator.evaluateContextBefore(vContext, instr)) {
						visitedBody.add(conflicts); // put the conflict/redone addresses back in
						return visitedBody;
					}
				}
				
				//
				// apply the pcode effects
				//
				boolean continueCurrentTrace = applyPcode(contextStack, vContext, instr, pcodeStartIndex, continueAfterHittingFlow, monitor);
				pcodeStartIndex = 0;

				/* Allow evaluateContext routine to change override the flowtype of an instruction.
				 * Jumps Changed to calls will now continue processing.
				 * There is a danger with this, since the calling convention might not get applied correctly.
				 * TODO: The side-effects are fixed if this occurs, except for things like callfixup.
				 */
				if (evaluator != null) {
					if (evaluator.evaluateContext(vContext, instr)) {
						visitedBody.add(conflicts); // put the conflict/redone addresses back in
						return visitedBody;
					}
				}
				
				// if the instruction changed it's type to a call, need to handle the call side effects
				FlowType instrFlow = instr.getFlowType();
				if (!originalFlowType.equals(instrFlow) && instrFlow.isCall()) {
					Address targets[] = getInstructionFlows(instr);
					for (Address target : targets) {
						handleFunctionSideEffects(instr, target, monitor);
					}
				}
				
				// if already hit a flow, only continue through code until MAX_EXTRA instructions or hit a call
				if (visitedBody.contains(minInstrAddress) && !justPopped) {
					// even if second time through, run a few more instructions to see if get to a call
					if (continueAfterHittingFlow > NOT_CONTINUING_CURRRENTLY) {
						continueAfterHittingFlow++;
					} else {
						continueAfterHittingFlow=0; // start counting, hit body
					}
					if (continueAfterHittingFlow >= MAX_EXTRA_INSTRUCTION_FLOW || instrFlow.isCall()) {
						break;
					}
				}
				// add this instruction to processed body set
				visitedBody.addRange(minInstrAddress, maxAddr);
				
				justPopped = false;
				
				vContext.flowEnd(minInstrAddress);
				
				// if already hit a flow, only continue until a call is hit,
				// TODO: this could be changed to some number of instructions
				// if (continueAfterHittingFlow > 0 && (instrFlow.isCall())) {
				// 	break;
				// }

				boolean simpleFlow = isSimpleFallThrough(instrFlow);
				// once we encounter any flow, must set the hitCodeFlow flag
				//   This should be set after the current instruction has been processed.
				hitCodeFlow |= !simpleFlow;

				// go to the fall thru address, unless this instruction had flow
				// then add it's flow to the end of the list and process other flows
				Address fallThru = instr.getFallThrough();
				nextAddr = null;
				if (continueCurrentTrace) {
					nextAddr = fallThru;
				}
			}
			
		}

		//System.out.println(startAddr + " = " + instructionCount + ", " + continueCount);
		visitedBody.add(conflicts); // put the conflict/redone addresses back in
		return visitedBody;
	}
Do not depend on this method!  For display debugging purposes only.
This will change.

@param addr
@param reg
@return
Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java