fix: scripts runtime code and docs clean up

This commit is contained in:
Skylot
2022-11-14 12:09:07 +00:00
parent 17574ee554
commit 9a6dec0dbd
17 changed files with 146 additions and 155 deletions
+3 -3
View File
@@ -7,14 +7,14 @@ plugins {
dependencies {
implementation(project(':jadx-core'))
implementation(project(":jadx-cli"))
implementation(project(':jadx-cli'))
// import mappings
implementation project(':jadx-plugins:jadx-rename-mappings')
// jadx-script autocomplete support
implementation(project(":jadx-plugins::jadx-script:jadx-script-ide"))
implementation(project(":jadx-plugins::jadx-script:jadx-script-runtime"))
implementation(project(':jadx-plugins:jadx-script:jadx-script-ide'))
implementation(project(':jadx-plugins:jadx-script:jadx-script-runtime'))
implementation 'org.jetbrains.kotlin:kotlin-scripting-common:1.8.10'
implementation 'com.fifesoft:autocomplete:3.3.1'
@@ -6,7 +6,7 @@ import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.utils.UiUtils;
import static jadx.plugins.script.runtime.JadxScriptTemplateKt.JADX_SCRIPT_LOG_PREFIX;
import static jadx.plugins.script.runtime.ScriptRuntime.JADX_SCRIPT_LOG_PREFIX;
class LogAppender implements ILogListener {
private final LogOptions options;
@@ -6,7 +6,7 @@ import ch.qos.logback.classic.Level;
import jadx.core.utils.Utils;
import static jadx.plugins.script.runtime.JadxScriptTemplateKt.JADX_SCRIPT_LOG_PREFIX;
import static jadx.plugins.script.runtime.ScriptRuntime.JADX_SCRIPT_LOG_PREFIX;
public class LogOptions {
@@ -27,10 +27,10 @@ import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.utils.Icons;
import jadx.plugins.script.ide.ScriptCompiler;
import jadx.plugins.script.ide.ScriptCompletionResult;
import jadx.plugins.script.ide.ScriptServices;
import static jadx.plugins.script.ide.ScriptCompilerKt.AUTO_COMPLETE_INSERT_STR;
import static jadx.plugins.script.ide.ScriptServicesKt.AUTO_COMPLETE_INSERT_STR;
public class ScriptCompleteProvider extends CompletionProviderBase {
private static final Logger LOG = LoggerFactory.getLogger(ScriptCompleteProvider.class);
@@ -49,11 +49,10 @@ public class ScriptCompleteProvider extends CompletionProviderBase {
}
private final AbstractCodeArea codeArea;
private ScriptCompiler scriptComplete;
private ScriptServices scriptComplete;
public ScriptCompleteProvider(AbstractCodeArea codeArea) {
this.codeArea = codeArea;
// this.scriptComplete = new ScriptCompiler(codeArea.getNode().getName());
}
private List<Completion> getCompletions() {
@@ -61,7 +60,7 @@ public class ScriptCompleteProvider extends CompletionProviderBase {
String code = codeArea.getText();
int caretPos = codeArea.getCaretPosition();
// TODO: resolve error after reusing ScriptCompiler
scriptComplete = new ScriptCompiler(codeArea.getNode().getName());
scriptComplete = new ScriptServices(codeArea.getNode().getName());
ScriptCompletionResult result = scriptComplete.complete(code, caretPos);
int replacePos = getReplacePos(caretPos, result);
if (!result.getReports().isEmpty()) {
@@ -40,9 +40,9 @@ import jadx.gui.utils.UiUtils;
import jadx.gui.utils.ui.ActionHandler;
import jadx.gui.utils.ui.NodeLabel;
import jadx.plugins.script.ide.ScriptAnalyzeResult;
import jadx.plugins.script.ide.ScriptCompiler;
import jadx.plugins.script.ide.ScriptServices;
import static jadx.plugins.script.runtime.JadxScriptTemplateKt.JADX_SCRIPT_LOG_PREFIX;
import static jadx.plugins.script.runtime.ScriptRuntime.JADX_SCRIPT_LOG_PREFIX;
public class ScriptContentPanel extends AbstractCodeContentPanel {
private static final long serialVersionUID = 6575696321112417513L;
@@ -154,8 +154,8 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
String code = scriptArea.getText();
String fileName = scriptArea.getNode().getName();
ScriptCompiler scriptCompiler = new ScriptCompiler(fileName);
ScriptAnalyzeResult result = scriptCompiler.analyze(code, scriptArea.getCaretPosition());
ScriptServices scriptServices = new ScriptServices(fileName);
ScriptAnalyzeResult result = scriptServices.analyze(code, scriptArea.getCaretPosition());
List<ScriptDiagnostic> issues = result.getIssues();
boolean success = true;
for (ScriptDiagnostic issue : issues) {
+9 -10
View File
@@ -1,11 +1,10 @@
## JADX scripting support
NOTE: work still in progress!
:exclamation: Work still in progress! Script API is not stable!
### Examples
Check script examples in [`examples/scripts/`](https://github.com/skylot/jadx/tree/master/jadx-plugins/jadx-script/examples/scripts)
(start with [`hello`](https://github.com/skylot/jadx/blob/master/jadx-plugins/jadx-script/examples/scripts/hello.jadx.kts))
Check script examples in [`examples/scripts/`](https://github.com/skylot/jadx/tree/master/jadx-plugins/jadx-script/examples/scripts)(start with [`hello`](https://github.com/skylot/jadx/blob/master/jadx-plugins/jadx-script/examples/scripts/hello.jadx.kts))
### Script usage
@@ -15,16 +14,16 @@ Just add script file as input
#### In jadx-gui
1. Add script file to the project
1. Add script file to the project (using `Add files` or `New script` by right-click menu on `Inputs/Scripts`)
2. Script will appear in `Inputs/Scripts` section
3. After script change you need to reload project (`Reload` button in toolbar or `F5`)
4. You can enable `Live reload` option in `File` menu to reload project automatically on scripts change
3. After script change, you can run it using `Run` button in script editor toolbar or reload whole project (`Reload` button in toolbar or `F5`).
Also, you can enable `Live reload` option in `File` menu to reload project automatically on scripts change
### Script development
Jadx-gui for now don't support ~~autocompletion,~~ errors highlighting, docs and code navigation,
so best approach for script editing is to open jadx project in IntelliJ Idea and write your script in `examples/scripts/` folder.
Also, this will allow to debug your scripts: for that you need to create run configuration for jadx-cli or jadx-gui
Jadx-gui for now don't support ~~autocompletion~~, ~~errors highlighting~~, code navigation and docs,
so the best approach for script editing is to open jadx project in IntelliJ IDEA and write your script in `examples/scripts/` folder.
Also, this allows to debug your scripts: for that you need to create run configuration for jadx-cli or jadx-gui
add breakpoints and next run it in debug mode (jadx-gui is preferred because of faster script reload).
Script logs and compilation errors will appear in `Log viewer` (separate script log will be added later)
Script logs and compilation errors will appear in `Log viewer` (try filter for show only script related logs)
@@ -1,8 +1,8 @@
dependencies {
implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
implementation("org.jetbrains.kotlin:kotlin-script-runtime")
implementation(kotlin("stdlib-common"))
implementation(kotlin("script-runtime"))
implementation("io.github.microutils:kotlin-logging-jvm:3.0.5")
@@ -1,12 +1,12 @@
dependencies {
implementation("org.jetbrains.kotlin:kotlin-scripting-common")
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
implementation("org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable")
implementation("org.jetbrains.kotlin:kotlin-scripting-ide-services")
implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime"))
implementation(project(":jadx-plugins:jadx-script:jadx-script-plugin"))
implementation(kotlin("scripting-common"))
implementation(kotlin("scripting-jvm"))
implementation(kotlin("scripting-compiler-embeddable"))
implementation(kotlin("scripting-ide-services"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("io.github.microutils:kotlin-logging-jvm:3.0.5")
}
@@ -1,6 +1,6 @@
package jadx.plugins.script.ide
import jadx.plugins.script.runner.ScriptEval
import jadx.plugins.script.ScriptEval
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices
import kotlin.script.experimental.api.ReplAnalyzerResult
@@ -28,7 +28,7 @@ data class ScriptAnalyzeResult(
val reports: List<ScriptDiagnostic>,
)
class ScriptCompiler(private val scriptName: String) {
class ScriptServices(private val scriptName: String) {
private val replCompiler = KJvmReplCompilerWithIdeServices()
private val compileConf = ScriptEval().buildCompileConf()
@@ -5,11 +5,9 @@ plugins {
dependencies {
implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime"))
implementation("org.jetbrains.kotlin:kotlin-scripting-common")
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm-host")
implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime"))
implementation(kotlin("scripting-common"))
implementation(kotlin("scripting-jvm"))
implementation(kotlin("scripting-jvm-host"))
implementation("io.github.microutils:kotlin-logging-jvm:3.0.5")
}
@@ -4,7 +4,6 @@ import jadx.api.plugins.JadxPlugin
import jadx.api.plugins.JadxPluginContext
import jadx.api.plugins.JadxPluginInfo
import jadx.plugins.script.passes.JadxScriptAfterLoadPass
import jadx.plugins.script.runner.ScriptEval
import jadx.plugins.script.runtime.data.JadxScriptAllOptions
class JadxScriptPlugin : JadxPlugin {
@@ -14,7 +13,9 @@ class JadxScriptPlugin : JadxPlugin {
override fun init(init: JadxPluginContext) {
init.registerOptions(scriptOptions)
val scriptStates = ScriptEval().process(init, scriptOptions) ?: return
init.addPass(JadxScriptAfterLoadPass(scriptStates))
val scripts = ScriptEval().process(init, scriptOptions)
if (scripts.isNotEmpty()) {
init.addPass(JadxScriptAfterLoadPass(scripts))
}
}
}
@@ -0,0 +1,91 @@
package jadx.plugins.script
import jadx.api.JadxDecompiler
import jadx.api.plugins.JadxPluginContext
import jadx.plugins.script.runtime.JadxScriptData
import jadx.plugins.script.runtime.JadxScriptTemplate
import jadx.plugins.script.runtime.data.JadxScriptAllOptions
import kotlin.script.experimental.api.EvaluationResult
import kotlin.script.experimental.api.ResultValue
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.ScriptDiagnostic.Severity
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
import kotlin.script.experimental.api.compilerOptions
import kotlin.script.experimental.api.constructorArgs
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate
import kotlin.system.measureTimeMillis
class ScriptEval {
private val scriptingHost = BasicJvmScriptingHost()
fun process(init: JadxPluginContext, scriptOptions: JadxScriptAllOptions): List<JadxScriptData> {
val jadx = init.decompiler as JadxDecompiler
val scripts = jadx.args.inputFiles.filter { f -> f.name.endsWith(".jadx.kts") }
if (scripts.isEmpty()) {
return emptyList()
}
val scriptDataList = mutableListOf<JadxScriptData>()
for (scriptFile in scripts) {
val scriptData = JadxScriptData(jadx, init, scriptOptions, scriptFile)
scriptDataList.add(scriptData)
eval(scriptData)
}
return scriptDataList
}
fun buildCompileConf(): ScriptCompilationConfiguration {
return createJvmCompilationConfigurationFromTemplate<JadxScriptTemplate>() {
compilerOptions(listOf("-Xuse-k2"))
}
}
fun buildEvalConf(scriptData: JadxScriptData): ScriptEvaluationConfiguration {
return createJvmEvaluationConfigurationFromTemplate<JadxScriptTemplate> {
constructorArgs(scriptData)
}
}
private fun eval(scriptData: JadxScriptData) {
scriptData.log.debug { "Loading script: ${scriptData.scriptFile.absolutePath}" }
val execTime = measureTimeMillis {
val compilationConf = buildCompileConf()
val evalConf = buildEvalConf(scriptData)
val result = scriptingHost.eval(scriptData.scriptFile.toScriptSource(), compilationConf, evalConf)
processEvalResult(result, scriptData)
}
scriptData.log.debug { "Script '${scriptData.scriptName}' executed in $execTime ms" }
}
private fun processEvalResult(res: ResultWithDiagnostics<EvaluationResult>, scriptData: JadxScriptData) {
val log = scriptData.log
for (r in res.reports) {
val msg = r.render(withSeverity = false)
when (r.severity) {
Severity.FATAL, Severity.ERROR -> log.error(r.exception) { "Script execution error: $msg" }
Severity.WARNING -> log.warn { "Script execution issue: $msg" }
Severity.INFO -> log.info { "Script report: $msg" }
Severity.DEBUG -> log.debug { "Script report: $msg" }
}
}
when (res) {
is ResultWithDiagnostics.Success -> {
when (val retVal = res.value.returnValue) {
is ResultValue.Error -> log.error(retVal.error) { "Script execution error:" }
is ResultValue.Value -> log.info { "Script execution result: $retVal" }
is ResultValue.Unit -> {}
ResultValue.NotEvaluated -> {}
}
}
is ResultWithDiagnostics.Failure -> {
scriptData.error = true
log.error { "Script execution failed: ${scriptData.scriptName}" }
}
}
}
}
@@ -3,27 +3,24 @@ package jadx.plugins.script.passes
import jadx.api.JadxDecompiler
import jadx.api.plugins.pass.impl.SimpleJadxPassInfo
import jadx.api.plugins.pass.types.JadxAfterLoadPass
import jadx.plugins.script.runner.ScriptStates
import mu.KotlinLogging
import jadx.plugins.script.runtime.JadxScriptData
private val LOG = KotlinLogging.logger {}
class JadxScriptAfterLoadPass(private val scriptStates: ScriptStates) : JadxAfterLoadPass {
class JadxScriptAfterLoadPass(private val scripts: List<JadxScriptData>) : JadxAfterLoadPass {
override fun getInfo() = SimpleJadxPassInfo("JadxScriptAfterLoad", "Execute scripts 'afterLoad' block")
override fun init(decompiler: JadxDecompiler) {
for (script in scriptStates.getScripts()) {
for (script in scripts) {
if (script.error) {
continue
}
try {
for (b in script.scriptData.afterLoad) {
for (b in script.afterLoad) {
b.invoke()
}
} catch (e: Throwable) {
script.error = true
LOG.error(e) { "Error executing 'afterLoad' block in script: ${script.scriptFile.name}" }
script.log.error(e) { "Error executing 'afterLoad' block in script: ${script.scriptFile.name}" }
}
}
}
@@ -1,78 +0,0 @@
package jadx.plugins.script.runner
import jadx.api.JadxDecompiler
import jadx.api.plugins.JadxPluginContext
import jadx.plugins.script.runtime.JadxScriptData
import jadx.plugins.script.runtime.JadxScriptTemplate
import jadx.plugins.script.runtime.data.JadxScriptAllOptions
import mu.KotlinLogging
import java.io.File
import kotlin.script.experimental.api.EvaluationResult
import kotlin.script.experimental.api.ResultValue
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
import kotlin.script.experimental.api.constructorArgs
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate
import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate
private val LOG = KotlinLogging.logger {}
class ScriptEval {
fun process(init: JadxPluginContext, scriptOptions: JadxScriptAllOptions): ScriptStates? {
val jadx = init.decompiler as JadxDecompiler
val scripts = jadx.args.inputFiles.filter { f -> f.name.endsWith(".jadx.kts") }
if (scripts.isEmpty()) {
return null
}
val scriptStates = ScriptStates()
for (scriptFile in scripts) {
val scriptData = JadxScriptData(jadx, init, scriptOptions, scriptFile)
load(scriptFile, scriptData)
scriptStates.add(scriptFile, scriptData)
}
return scriptStates
}
private fun load(scriptFile: File, scriptData: JadxScriptData) {
LOG.debug { "Loading script: ${scriptFile.absolutePath}" }
val result = eval(scriptFile, scriptData)
processEvalResult(result, scriptFile)
}
fun buildCompileConf() = createJvmCompilationConfigurationFromTemplate<JadxScriptTemplate>()
fun buildEvalConf(scriptData: JadxScriptData): ScriptEvaluationConfiguration {
return createJvmEvaluationConfigurationFromTemplate<JadxScriptTemplate> {
constructorArgs(scriptData)
}
}
private fun eval(scriptFile: File, scriptData: JadxScriptData): ResultWithDiagnostics<EvaluationResult> {
val compilationConf = buildCompileConf()
val evalConf = buildEvalConf(scriptData)
return BasicJvmScriptingHost().eval(scriptFile.toScriptSource(), compilationConf, evalConf)
}
private fun processEvalResult(res: ResultWithDiagnostics<EvaluationResult>, scriptFile: File) {
when (res) {
is ResultWithDiagnostics.Success -> {
val result = res.value.returnValue
if (result is ResultValue.Error) {
result.error.printStackTrace()
}
}
is ResultWithDiagnostics.Failure -> {
LOG.error { "Script execution failed: ${scriptFile.name}" }
res.reports
.filter { it.severity >= ScriptDiagnostic.Severity.ERROR }
.forEach { r ->
LOG.error(r.exception) { r.render(withSeverity = false) }
}
}
}
}
}
@@ -1,21 +1 @@
package jadx.plugins.script.runner
import jadx.plugins.script.runtime.JadxScriptData
import java.io.File
data class ScriptStateData(
val scriptFile: File,
val scriptData: JadxScriptData,
var error: Boolean = false,
)
class ScriptStates {
private val data: MutableList<ScriptStateData> = ArrayList()
fun add(scriptFile: File, scriptData: JadxScriptData) {
data.add(ScriptStateData(scriptFile, scriptData))
}
fun getScripts() = data
}
@@ -1,7 +1,6 @@
package jadx.plugins.script.runtime
import kotlinx.coroutines.runBlocking
import mu.KotlinLogging
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.ScriptAcceptedLocation
@@ -28,19 +27,19 @@ import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
import kotlin.script.experimental.jvm.jvm
const val JADX_SCRIPT_LOG_PREFIX = "JadxScript:"
@KotlinScript(
fileExtension = "jadx.kts",
compilationConfiguration = JadxScriptConfiguration::class,
)
abstract class JadxScriptTemplate(
private val scriptData: JadxScriptData,
scriptData: JadxScriptData,
) {
val scriptName = scriptData.scriptName
val log = KotlinLogging.logger("$JADX_SCRIPT_LOG_PREFIX$scriptName")
val log = scriptData.log
fun getJadxInstance() = JadxScriptInstance(scriptData, log)
private val scriptInstance = JadxScriptInstance(scriptData, log)
fun getJadxInstance() = scriptInstance
fun println(message: Any?) {
log.info(message?.toString())
@@ -1,3 +1,4 @@
@file:JvmName("ScriptRuntime")
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package jadx.plugins.script.runtime
@@ -17,17 +18,21 @@ import jadx.plugins.script.runtime.data.Replace
import jadx.plugins.script.runtime.data.Search
import jadx.plugins.script.runtime.data.Stages
import mu.KLogger
import mu.KotlinLogging
import java.io.File
const val JADX_SCRIPT_LOG_PREFIX = "JadxScript:"
class JadxScriptData(
val jadxInstance: JadxDecompiler,
val pluginContext: JadxPluginContext,
val options: JadxScriptAllOptions,
val scriptFile: File,
) {
val afterLoad: MutableList<() -> Unit> = ArrayList()
val scriptName = scriptFile.name.removeSuffix(".jadx.kts")
val log = KotlinLogging.logger("$JADX_SCRIPT_LOG_PREFIX$scriptName")
val afterLoad: MutableList<() -> Unit> = ArrayList()
var error: Boolean = false
}
class JadxScriptInstance(