{"record":{"id":"a35d50e3da4d42c0","repo":"NationalSecurityAgency/ghidra","slug":"ghidra-python-interpreter-has-already-been-cleaned","errorCode":null,"errorMessage":"Ghidra python interpreter has already been cleaned up.","messagePattern":"Ghidra python interpreter has already been cleaned up\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"Ghidra/Extensions/Jython/src/main/java/ghidra/jython/GhidraJythonInterpreter.java","lineNumber":166,"sourceCode":"\t\t\t\tsystemState.path.append(Py.newStringOrUnicode(pyDevSrcDir.getAbsolutePath()));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Pushes (executes) a line of Python to the interpreter.\n\t *\n\t * @param line the line of Python to push to the interpreter\n\t * @param script a PythonScript from which we load state (or null)\n\t * @return true if more input is needed before execution can occur\n\t * @throws PyException if an unhandled exception occurred while executing the line of python\n\t * @throws IllegalStateException if this interpreter has been cleaned up.\n\t */\n\tpublic synchronized boolean push(String line, JythonScript script)\n\t\t\tthrows PyException, IllegalStateException {\n\n\t\tif (cleanedUp) {\n\t\t\tthrow new IllegalStateException(\n\t\t\t\t\"Ghidra python interpreter has already been cleaned up.\");\n\t\t}\n\n\t\tinitializePythonPath();\n\t\tinjectScriptHierarchy(script);\n\n\t\tif (buffer.length() > 0) {\n\t\t\tbuffer.append(\"\\n\");\n\t\t}\n\t\tbuffer.append(line);\n\t\tPy.getThreadState().tracefunc = interruptTraceFunction;\n\t\tPy.getSystemState().stderr = getSystemState().stderr; // needed to properly display SyntaxError\n\t\tboolean more;\n\t\ttry {\n\t\t\tmore = runsource(buffer.toString(), \"python\");\n\t\t\tgetSystemState().stderr.invoke(\"flush\");\n\t\t\tif (!more) {\n\t\t\t\tresetbuffer();","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/NationalSecurityAgency/ghidra/blob/d5f144c24d6bc53c9cbf4448c6d11143e7696206/Ghidra/Extensions/Jython/src/main/java/ghidra/jython/GhidraJythonInterpreter.java#L148-L184","documentation":"Thrown by GhidraJythonInterpreter.push() (interactive line execution) when the interpreter's cleanedUp flag is true. Once cleanup() runs, the underlying Jython/InteractiveInterpreter state is torn down and the instance is permanently unusable. The class deliberately removes any reset path, so the only valid action is to obtain a fresh interpreter via GhidraJythonInterpreter.get().","triggerScenarios":"Calling interpreter.push(line, script) after interpreter.cleanup() has been invoked on the same instance, e.g. a Jython REPL component that was disposed but a queued or background task still submits a line; or a long-lived JythonScript holding a stale interpreter reference whose owning plugin/tool was closed.","commonSituations":"Closing the Jython interpreter plugin window or the CodeBrowser tool while a script thread is mid-execution; headless runs that reuse an interpreter across script invocations after teardown; UI actions dispatched after the interactive console provider was disposed.","solutions":["Stop using the cleaned-up instance; call GhidraJythonInterpreter.get() to create a new interpreter and route subsequent push() calls to it.","Track the interpreter lifecycle with the owning component (e.g. dispose()/cleanup()) and null out references so stale callers cannot reach a dead instance.","If unsure whether the instance is live, guard with a state check or catch IllegalStateException and reinitialize before retrying once."],"exampleFix":"// before\ninterpreter.cleanup();\n// ... later, in another thread\ninterpreter.push(line, script); // throws IllegalStateException\n\n// after\ninterpreter.cleanup();\ninterpreter = GhidraJythonInterpreter.get();\ninterpreter.push(line, script);","handlingStrategy":"validation","validationCode":"// There is no public isCleanedUp(); track lifecycle yourself, or wrap.\npublic boolean isInterpreterUsable(GhidraJythonInterpreter i) {\n    return i != null; // add your own 'alive' flag tracked alongside cleanup()\n}","typeGuard":"null","tryCatchPattern":"try {\n    interpreter.push(line, script);\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"already been cleaned up\")) {\n        interpreter = GhidraJythonInterpreter.get(); // recreate once\n        interpreter.push(line, script);\n    } else throw e;\n}","preventionTips":["Tie interpreter ownership to a component lifecycle; call cleanup() only in dispose() and null the reference.","Never cache an interpreter across tool open/close cycles.","Route all push() calls through a single accessor that refreshes the instance after cleanup."],"tags":["jython","interpreter-lifecycle","ghidra-script"],"backgroundTag":null,"analyzedSha":"d5f144c24d6bc53c9cbf4448c6d11143e7696206","analyzedAt":"2026-08-14T01:00:57.564Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}