NationalSecurityAgency/ghidra · warning · InvalidInputException

Didn't remove external library

Error message

Didn't remove external library {libName} since it isn't empty.

What it means

Thrown by ExternalProgramMerger.removeExternalLibrary when an external library still has external locations referencing it. The merger's policy is to only remove empty libraries; iter.hasNext() returning true means at least one ExternalLocation remains, so removal is aborted and InvalidInputException is raised. This protects against orphaning external location references during a merge.

Solutions

  1. Remove or relocate the external locations in the library before attempting to remove the library.
  2. Keep the library instead of removing it; the exception is informational - catch it and append the message to infoBuf (as the merger's callers already do).
  3. Resolve the merge conflict in the GUI so the library/locations are consistent before check-in.
  4. In scripting, call extMgr.getExternalLocations(libName) first and only remove when iter is empty.

Example fix

// before
removeExternalLibrary(program, libName);
// library has locations -> InvalidInputException

// after
ExternalManager extMgr = program.getExternalManager();
if (!extMgr.getExternalLocations(libName).hasNext()) {
    extMgr.removeExternalLibrary(libName);
} else {
    infoBuf.append("Library " + libName + " not removed: still has external locations\n");
}
Defensive patterns

Strategy: validation

Validate before calling

ExternalManager extMgr = program.getExternalManager();
boolean empty = !extMgr.getExternalLocations(libName).hasNext();
if (!empty) {
    // do not call removeExternalLibrary; relocate or keep the library
}

Type guard

boolean isExternalLibraryEmpty(Program p, String libName) {
    return !p.getExternalManager().getExternalLocations(libName).hasNext();
}

Try / catch

try {
    removeExternalLibrary(program, libName);
} catch (InvalidInputException e) {
    if (e.getMessage().contains("isn't empty")) {
        // keep the library; log and continue
        infoBuf.append(e.getMessage() + "\n");
    } else throw e;
}

Prevention

When it happens

Trigger: During a multi-user program merge (version-control check-in), the merger attempts to remove an external library (because latest or 'my' version dropped it) but the result program still has external locations pointing into that library. Triggered by autoMergeWhenOnlyLatestChanged / autoMergeWhenOnlyMyChanged when latestName or myName is null.

Common situations: One branch deleted a library while another branch (or the result) still references its locations. Merge of programs where external locations were added but the library was simultaneously renamed/removed. Re-entrant merge state.

Related errors


AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14). Data as JSON: /api/errors/036572e9bd256990. Report an issue: GitHub.

Appendix: source

Thrown at Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ExternalProgramMerger.java:581

		if (s != null) {
			return (s.getSource() == SourceType.USER_DEFINED);
		}
		return false;
	}

	/**
	 * Removes the indicated external library from the indicated program version
	 * if it is empty.
	 * @param program the program
	 * @param libName the external library name
	 * @throws InvalidInputException if there is no such enterrnal library in the program
	 */
	private void removeExternalLibrary(Program program, String libName)
			throws InvalidInputException {
		ExternalManager extMgr = program.getExternalManager();
		ExternalLocationIterator iter = extMgr.getExternalLocations(libName);
		if (iter.hasNext()) {
			throw new InvalidInputException(
				"Didn't remove external library " + libName + " since it isn't empty.");
		}
		if (!extMgr.removeExternalLibrary(libName)) {
			throw new InvalidInputException("Didn't remove external library " + libName);
		}
	}

	/**
	 * Determines whether the latest external program name and my external program name are equals.
	 * @param latestName the latest external program name or null.
	 * @param myName my external program name or null.
	 * @return true if the names are equal.
	 */
	private boolean same(String latestName, String myName) {
		return SystemUtilities.isEqual(latestName, myName);
	}

	/**

View on GitHub (pinned to d5f144c24d)