private int createStackPointerVariables(final Function func, TaskMonitor monitor)
			throws CancelledException {

		final Program program = func.getProgram();
		final Listing listing = program.getListing();
		final AddressFactory addrFactory = program.getAddressFactory();
		final ReferenceManager refMgr = program.getReferenceManager();

		// set the stack pointer to be tracked
		Register stackReg = program.getCompilerSpec().getStackPointer();
		if (stackReg == null) {
			return Function.UNKNOWN_STACK_DEPTH_CHANGE;
		}

		ResultsState results = new ResultsState(func.getEntryPoint(), new FunctionAnalyzer() {

			@Override
			public void dataReference(PcodeOp op, int instrOpIndex, Varnode storageVarnode,
					RefType refType, TaskMonitor monitor1) throws CancelledException {
				// TODO Auto-generated method stub
			}

			@Override
			public void indirectDataReference(PcodeOp op, int instrOpIndex, Varnode offsetVarnode,
					int size, int storageSpaceID, RefType refType, TaskMonitor monitor1)
					throws CancelledException {
				// TODO Auto-generated method stub

			}

			@Override
			public boolean resolvedFlow(PcodeOp op, int instrOpIndex, Address destAddr,
					ContextState currentState, ResultsState results1, TaskMonitor monitor1)
					throws CancelledException {
				// TODO Auto-generated method stub
				return false;
			}

			@Override
			public void stackReference(PcodeOp op, int instrOpIndex, int stackOffset, int size,
					int storageSpaceID, RefType refType, TaskMonitor monitor1)
					throws CancelledException {

				if (instrOpIndex < 0) {
					return;
				}
				Address fromAddr = op.getSeqnum().getTarget();
				Instruction instr = listing.getInstructionAt(fromAddr);
				if (instr == null) {
					return;
				}
				Address stackAddr = addrFactory.getStackSpace().getAddress(stackOffset);
				RefType rt = refType;
				Reference ref = refMgr.getReference(fromAddr, stackAddr, instrOpIndex);
				if (ref != null) {
					RefType existingRefType = ref.getReferenceType();
					if (existingRefType == rt) {
						return;
					}
					if (existingRefType == RefType.READ || existingRefType == RefType.WRITE) {
						rt = RefType.READ_WRITE;
					}
				}
				else if (refMgr.getReferencesFrom(fromAddr, instrOpIndex).length != 0) {
					// don't overwrite existing reference
					return;
				}
				if (!dontCreateNewVariables) {
					StackFrame stackFrame = func.getStackFrame();
					Variable existingVar = stackFrame.getVariableContaining(stackOffset);
					DataType type =
						size > 0 ? Undefined.getUndefinedDataType(size) : DataType.DEFAULT;
					if (existingVar != null && existingVar.getDataType() == DataType.DEFAULT) {
						func.removeVariable(existingVar);
						existingVar = null;
					}
					if (existingVar == null) {
						try {
							stackFrame.createVariable(null, stackOffset, type, SourceType.ANALYSIS);
// TODO: How can I tell when these stack variables are used as a pointer ?
						}
						catch (DuplicateNameException e) {
							throw new AssertException(); // unexpected
						}
						catch (InvalidInputException e) {
							Msg.error(this,
								"Failed to create stack variable at " + func.getEntryPoint() +
									", ref-from=" + fromAddr + ", stack-offset=" + stackOffset +
									", size=" + size + ": " + e.getMessage());
						}
					}
				}
				refMgr.addStackReference(fromAddr, instrOpIndex, stackOffset, rt,
					SourceType.ANALYSIS);
			}

			@Override
			public void stackReference(PcodeOp op, int instrOpIndex,
					VarnodeOperation computedStackOffset, int size, int storageSpaceID,
					RefType refType, TaskMonitor monitor1) throws CancelledException {
				//
			}

			@Override
			public List<Address> unresolvedIndirectFlow(PcodeOp op, int instrOpIndex,
					Varnode destination, ContextState currentState, ResultsState results1,
					TaskMonitor monitor1) throws CancelledException {
				return null;
			}

		}, program, true, monitor);

		if (results.getPreservedRegisters().contains(stackReg)) {
			return 0;
		}

		Set<Varnode> spReturnValues = results.getReturnValues(results.getStackPointerVarnode());
		if (!spReturnValues.isEmpty()) {
			for (SequenceNumber seq : results.getReturnAddresses()) {
				ContextState returnState = results.getContextStates(seq).next();
				Varnode varnode =
					returnState.get(results.getStackPointerVarnode(), TaskMonitor.DUMMY);
				Varnode zero = new Varnode(addrFactory.getConstantSpace().getAddress(0),
					stackReg.getMinimumByteSize());
				varnode = replaceInputVarnodes(varnode, results.getStackPointerVarnode(), zero, 4,
					monitor);
				if (varnode == null) {
					continue;
				}
				varnode = simplifyVarnode(varnode, addrFactory);
				if (varnode.isConstant()) {
					long offset = ResultsState.getSignedOffset(varnode);
					return (int) offset;
				}
			}
		}

		return Function.UNKNOWN_STACK_DEPTH_CHANGE;
	}
Create locals and parameters based on references involving purely the stack pointer.
Pushes, Pops, and arithmetic manipulation of the stack pointer must be tracked.

@param func - function to analyze stack pointer references
Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/FunctionResultStateStackAnalysisCmd.java