public Address makeReference(VarnodeContext vContext, Instruction instruction, int opIndex,
			long knownSpaceID, long wordOffset, int size, DataType dataType, RefType refType, int pcodeop,
			boolean knownReference, boolean preExisting, TaskMonitor monitor) {

		long spaceID = knownSpaceID;
		if (spaceID == -1) { // speculative reference - only offset is known
			spaceID = getReferenceSpaceID(instruction, wordOffset);
			if (spaceID == -1) {
				return null; // don't make speculative reference
			}
		}

		Address instructionAddress = instruction.getMinAddress();

		Address target;
		try {
			AddressSpace space = program.getAddressFactory().getAddressSpace((int) spaceID);

			if (space.isExternalSpace()) {
				target = space.getAddress(wordOffset, true);
			}
			else {
				// do checks that are actual memory, and not fabricated externals
				if (!space.isLoadedMemorySpace()) {
					return null;
				}
				// for now, don't mark up this area of memory.
				//   Memory at too low an offset could be from a bad calculation (use of zero or other small number)
				if (wordOffset == 0) {
					return null;
				}

				// wrap offset within address space
				if (wordOffset < 0) {
					// offset was sign extended, chop off sign extension
					// Note: this will only work for 64-bit signed extensions like Mips 64/32 hybrid
					target = space.getTruncatedAddress(wordOffset, true);
				}
				else {
					// take offset as is, value might not be a valid address
					target = space.getAddress(wordOffset, true);
				}

				wordOffset = target.getAddressableWordOffset();

				// don't make references to registers
				if (space.hasMappedRegisters() && program.getRegister(target) != null) {
					return null;
				}

				// normalize the address into this overlay space.
				target = instructionAddress.getAddressSpace().getOverlayAddress(target);

				// if this isn't known to be a good address, check that memory contains it
				if (!knownReference && !program.getMemory().contains(target)) {
					// it could be in a non-allocated memory space
					// TODO: Really at this point it should be a constant, and put on a list
					//       to be considered later as a pointer.
					// allow flow references to memory not in program
					//   program could be located in the wrong place, or other flow issues
					if (!refType.isFlow() && !program.getReferenceManager().hasReferencesTo(target)) {
						return null;
					}
				}
			}

			// if the refType is a call, and it isn't computed, we shouldn't be here
			if (refType.isCall() && !refType.isComputed()) {
				return null;
			}

			// give evaluator a chance to stop or change the reference
			target = evaluateReference(vContext, instruction, knownSpaceID, wordOffset, size,
				dataType, refType, pcodeop, knownReference, target);
			if (target == null || preExisting) {
				return null;
			}

			// Pure data references need to be scrutinized
			// TODO: This is a speculative type of reference, and should probably be done elsewhere.
			//
			if (refType.isData() &&
				!evaluatePureDataRef(instruction, wordOffset, refType, target)) {
				return null;
			}

			if (refType.isJump() && refType.isComputed()) {
				// if there are already some reference, don't do the jump here
				// if there are more than one reference, don't do the jump here
				Address[] flows = getInstructionFlows(instruction);
				if (flows.length > 1) {
					return target;
				}
				for (Address address : flows) {
					if (address.equals(target)) {
						return target;
					}
				}
			}
		}
		catch (AddressOutOfBoundsException e) {
			return null;
		}

		opIndex = findOpIndexForRef(vContext, instruction, opIndex, wordOffset, refType);

		// if didn't get the right opIndex, and has a delayslot, and this instruction is not the right flow for the reference
		if (opIndex == -1 && instruction.getPrototype().hasDelaySlots()) {
			if (!instruction.getFlowType().equals(refType)) {
				instruction = instruction.getNext();
				if (instruction == null) {
					return target;
				}
				opIndex = findOpIndexForRef(vContext, instruction, opIndex, wordOffset, refType);
			}
		}

		if (opIndex == -1) {
			if (!refType.isFlow() || target.isExternalAddress()) {
				opIndex = instruction.getNumOperands() - 1;
				// if it is invisible, don't put anything here.  Just put it on the mnemonic
				List<Object> list = instruction.getDefaultOperandRepresentationList(opIndex);
				if (list == null || list.size() == 0) {
					opIndex = -1;
				}
				// if is external, and any refs, just throw the ref on the mnemonic
				if (target.isExternalAddress() && instruction.getReferencesFrom().length != 0) {
					opIndex = -1;
				}
			}
		}

		if (opIndex == Reference.MNEMONIC) {
			instruction.addMnemonicReference(target, refType, SourceType.ANALYSIS);
		}
		else {
			instruction.addOperandReference(opIndex, target, refType, SourceType.ANALYSIS);
		}
		
		return target;
	}
Make a reference from the instruction to the address based on the spaceID,offset passed in.
  This could make a reference into an overlay (overriding the spaceID), or into memory, if
  spaceID is a constant space.
 The target could be an external Address carried along and then finally used.
 External addresses are OK as long as nothing is done to the offset.
 
@param vContext - context to use for any other information needed
@param instruction - instruction to place the reference on.
@param opIndex - operand it should be placed on, or -1 if unknown
@param knownSpaceID target space ID or -1 if only offset is known
@param wordOffset - target offset that is word addressing based
@param size - size of the access to the location
@param refType - type of reference
@param pcodeop - op that caused the reference
@param knownReference - true if reference is known to be a real reference, not speculative
@param preExisting preExisting reference
@param monitor - the task monitor
@return address that was marked up, null otherwise
Ghidra/Features/Base/src/main/java/ghidra/program/util/SymbolicPropogator.java