"Transaction has not been started" Exception when updating db
r1fl opened this issue · 3 comments
When making modifications to the program db on Ghidra 10.0.4 the following exception occurs
Transaction has not been started
db.NoTransactionException: Transaction has not been started
Sample code:
import ghidra.program.model.listing.CodeUnit as CodeUnit
var listing = currentProgram.getListing()
listing.setComment(toAddr(0x0), CodeUnit.PLATE_COMMENT, "Hello Ghidra")
Full trace:
Transaction has not been started
db.NoTransactionException: Transaction has not been started
org.jetbrains.kotlinx.jupyter.ReplEvalRuntimeException: Transaction has not been started
at org.jetbrains.kotlinx.jupyter.repl.impl.InternalEvaluatorImpl.eval(InternalEvaluatorImpl.kt:91)
at org.jetbrains.kotlinx.jupyter.repl.impl.CellExecutorImpl$execute$$inlined$with$lambda$1.invoke(CellExecutorImpl.kt:59)
at org.jetbrains.kotlinx.jupyter.repl.impl.CellExecutorImpl$execute$$inlined$with$lambda$1.invoke(CellExecutorImpl.kt:28)
at org.jetbrains.kotlinx.jupyter.ReplForJupyterImpl.withHost(repl.kt:535)
at org.jetbrains.kotlinx.jupyter.repl.impl.CellExecutorImpl.execute(CellExecutorImpl.kt:58)
at org.jetbrains.kotlinx.jupyter.repl.CellExecutor$DefaultImpls.execute$default(CellExecutor.kt:20)
at org.jetbrains.kotlinx.jupyter.ReplForJupyterImpl$eval$1.invoke(repl.kt:369)
at org.jetbrains.kotlinx.jupyter.ReplForJupyterImpl$eval$1.invoke(repl.kt:146)
at org.jetbrains.kotlinx.jupyter.ReplForJupyterImpl.withEvalContext(repl.kt:344)
at org.jetbrains.kotlinx.jupyter.ReplForJupyterImpl.eval(repl.kt:359)
at org.jetbrains.kotlinx.jupyter.ProtocolKt$shellMessagesHandler$res$1.invoke(protocol.kt:290)
at org.jetbrains.kotlinx.jupyter.ProtocolKt$shellMessagesHandler$res$1.invoke(protocol.kt)
at org.jetbrains.kotlinx.jupyter.JupyterConnection$runExecution$execThread$1.invoke(connection.kt:162)
at org.jetbrains.kotlinx.jupyter.JupyterConnection$runExecution$execThread$1.invoke(connection.kt:23)
at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)
Caused by: db.NoTransactionException: Transaction has not been started
at db.DBHandle.checkTransaction(DBHandle.java:396)
at db.Table.putRecord(Table.java:938)
at ghidra.program.database.code.CommentHistoryAdapterV0.createRecord(CommentHistoryAdapterV0.java:75)
at ghidra.program.database.code.CodeManager.createCommentHistoryRecord(CodeManager.java:3459)
at ghidra.program.database.code.CodeManager.sendNotification(CodeManager.java:3438)
at ghidra.program.database.code.CodeUnitDB.setComment(CodeUnitDB.java:493)
at ghidra.program.database.code.DataDB.setComment(DataDB.java:556)
at ghidra.program.database.code.CodeManager.setComment(CodeManager.java:3383)
at ghidra.program.database.ListingDB.setComment(ListingDB.java:756)
at Line_562.<init>(Line_562.jupyter-kts:6)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at kotlin.script.experimental.jvm.BasicJvmScriptEvaluator.evalWithConfigAndOtherScriptsResults(BasicJvmScriptEvaluator.kt:96)
at kotlin.script.experimental.jvm.BasicJvmScriptEvaluator.invoke$suspendImpl(BasicJvmScriptEvaluator.kt:41)
at kotlin.script.experimental.jvm.BasicJvmScriptEvaluator.invoke(BasicJvmScriptEvaluator.kt)
at kotlin.script.experimental.jvm.BasicJvmReplEvaluator.eval(BasicJvmReplEvaluator.kt:51)
at org.jetbrains.kotlinx.jupyter.repl.impl.InternalEvaluatorImpl$eval$resultWithDiagnostics$1.invokeSuspend(InternalEvaluatorImpl.kt:84)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:84)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at org.jetbrains.kotlinx.jupyter.repl.impl.InternalEvaluatorImpl.eval(InternalEvaluatorImpl.kt:84)
... 14 more
This is in fact (mostly) intentional. The Jython REPL implicitly wraps every every execution inside a transaction, but the Ghidra Kotlin kernel does not do this, because I needed it so rarely, and I rather make it explicit when a DB modifying action occurs, by manually using startTransaction
and endTransaction
.
With this context: Would you agree with this sentiment? We considered providing a more elegant wrapper for this, like a line magic to mark a cell as an DB transaction and implicitly wrap it, but that might be a fairly large amount of effort to implement for what I personally consider a negligible benefit.
edit: fixed links to documentation
So of the top of my head this would be:
import ghidra.program.model.listing.CodeUnit as CodeUnit
var id = currentProgram.startTransaction("Set line comment from Kernel")
var listing = currentProgram.getListing()
listing.setComment(toAddr(0x0), CodeUnit.PLATE_COMMENT, "Hello Ghidra")
currentProgram.endTransaction(id)
or even more specific:
import ghidra.program.model.listing.CodeUnit as CodeUnit
var listing = currentProgram.getListing()
var id = currentProgram.startTransaction("Set line comment from Kernel")
listing.setComment(toAddr(0x0), CodeUnit.PLATE_COMMENT, "Hello Ghidra")
currentProgram.endTransaction(id)
I'm closing this for now, under the assumption that you just needed the information about the transaction API. If you run into situations where this API becomes too cumbersome to use in some scenario feel free to reopen this issue. I'm happy to address a concrete case, one simple idea could be an extension function to allow something like this:
import ghidra.program.model.listing.CodeUnit as CodeUnit
var listing = currentProgram.getListing()
currentProgram.runTransaction { listing.setComment(toAddr(0x0), CodeUnit.PLATE_COMMENT, "Hello Ghidra") }