@Override
	public boolean queryIndex(Program program, PluginTool tool, QueryType queryType) {

		if (queryType.equals(QueryType.SRCSINK) && !isValid()) {
			Msg.showWarn(this, tool.getActiveWindow(), getName() + " Query Warning",
				getName() + " query cannot be performed because there are no sources or sinks.");
			return false;
		}

		List<String> paramList = new ArrayList<String>();
		File queryFile = null;
		taintOptions = plugin.getOptions();

		try {

			// Make sure we can access and execute the engine binary.
			Path engine = Path.of(taintOptions.getTaintEnginePath());
			File engineFile = engine.toFile();

			if (!engineFile.exists()) {
				plugin.consoleMessage("The " + getName() + " binary (" +
					engineFile.getCanonicalPath() + ") cannot be found or executed.");
				engineFile = getFilePath(taintOptions.getTaintEnginePath(),
					"Select the " + getName() + " binary");
				if (engineFile == null) {
					plugin.consoleMessage(
						"No " + getName() + " engine has been specified; exiting query function.");
					return false;
				}
			}

			plugin.consoleMessage("Using " + getName() + " binary: " + engineFile.toString());

			Path indexDirectory = Path.of(taintOptions.getTaintOutputDirectory());
			Path indexDBPath = Path.of(taintOptions.getTaintOutputDirectory(),
				taintOptions.getTaintIndexDBName(program.getName()));
			File indexDBFile = indexDBPath.toFile();
			
			if (usesIndex) {
				plugin.consoleMessage("Attempting to use index: " + indexDBFile.toString());
	
				if (!indexDBFile.exists()) {
					plugin.consoleMessage("The index database for the binary named: " +
						program.getName() + " does not exist; create it first.");
					return false;
				}
	
				plugin.consoleMessage("Using index database: " + indexDBFile);
			}

			switch (queryType) {
				case SRCSINK:
					// Generate a datalog query file based on the selected source, sink, etc. data.
					// This file can be overwritten
					Path queryPath = Path.of(taintOptions.getTaintOutputDirectory(),
						taintOptions.getTaintQueryDLName());
					queryFile = queryPath.toFile();
					writeQueryFile(queryFile);
					plugin.consoleMessage("The datalog query file: " + queryFile.toString() +
						" has been written and can be referenced later if needed.");
					break;
				case DEFAULT:
					plugin.consoleMessage("Performing default query.");
					break;
				case CUSTOM:
					plugin.consoleMessage("Performing custom query.");
					break;
				default:
					plugin.consoleMessage("Unknown query type.");
			}

			buildQuery(paramList, engine, indexDBFile, indexDirectory.toString());

			if (queryType.equals(QueryType.SRCSINK) || queryType.equals(QueryType.CUSTOM)) {
				// The datalog that specifies the query.
				if (queryType.equals(QueryType.CUSTOM)) {					
					Path queryPath = Path.of(taintOptions.getTaintOutputDirectory(),
						taintOptions.getTaintQueryDLName());
					GhidraFileChooser chooser = new GhidraFileChooser(plugin.getProvider().getComponent());
					chooser.setCurrentDirectory(queryPath.toFile());
					queryFile = chooser.getSelectedFile();
					if (queryFile == null) {
						return false;
					}
				}
				paramList.add(queryFile.getAbsolutePath());
			}

			Msg.info(this, "Query Param List: " + paramList.toString());
			try {
				ProcessBuilder pb = new ProcessBuilder(paramList);
				pb.directory(new File(taintOptions.getTaintOutputDirectory()));
				pb.redirectError(Redirect.INHERIT);
				Process p = pb.start();

				readQueryResultsIntoDataFrame(program, p.getInputStream());

				// We wait for the process to finish after starting to read the input stream,
				// otherwise waitFor() might wait for a running process trying to write to 
				// a filled output buffer. This causes waitFor() to wait indefinitely.
				p.waitFor();

			}
			catch (InterruptedException e) {
				Msg.error(this, e.getMessage());
				return false;
			}

		}
		catch (Exception e) {
			Msg.error(this, "Problems running query: " + e);
			return false;
		}
		return true;
	}
Build the query string, save it to a file the users selects, and run the
engine using the index and the query that is saved to the file.
Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/decompiler/taint/AbstractTaintState.java