fix(script): use compile instead analyze for scripts with deps (#1912)
This commit is contained in:
@@ -49,7 +49,7 @@ public class ScriptCompleteProvider extends CompletionProviderBase {
|
||||
}
|
||||
|
||||
private final AbstractCodeArea codeArea;
|
||||
private ScriptServices scriptComplete;
|
||||
private ScriptServices scriptServices;
|
||||
|
||||
public ScriptCompleteProvider(AbstractCodeArea codeArea) {
|
||||
this.codeArea = codeArea;
|
||||
@@ -60,8 +60,9 @@ public class ScriptCompleteProvider extends CompletionProviderBase {
|
||||
String code = codeArea.getText();
|
||||
int caretPos = codeArea.getCaretPosition();
|
||||
// TODO: resolve error after reusing ScriptCompiler
|
||||
scriptComplete = new ScriptServices(codeArea.getNode().getName());
|
||||
ScriptCompletionResult result = scriptComplete.complete(code, caretPos);
|
||||
scriptServices = new ScriptServices();
|
||||
String scriptName = codeArea.getNode().getName();
|
||||
ScriptCompletionResult result = scriptServices.complete(scriptName, code, caretPos);
|
||||
int replacePos = getReplacePos(caretPos, result);
|
||||
if (!result.getReports().isEmpty()) {
|
||||
LOG.debug("Script completion reports: {}", result.getReports());
|
||||
|
||||
@@ -128,6 +128,9 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
|
||||
|
||||
private void runScript() {
|
||||
scriptArea.save();
|
||||
if (!checkScript()) {
|
||||
return;
|
||||
}
|
||||
resetResultLabel();
|
||||
|
||||
TabbedPane tabbedPane = getTabbedPane();
|
||||
@@ -152,17 +155,17 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
|
||||
String code = scriptArea.getText();
|
||||
String fileName = scriptArea.getNode().getName();
|
||||
|
||||
ScriptServices scriptServices = new ScriptServices(fileName);
|
||||
ScriptAnalyzeResult result = scriptServices.analyze(code, scriptArea.getCaretPosition());
|
||||
ScriptServices scriptServices = new ScriptServices();
|
||||
ScriptAnalyzeResult result = scriptServices.analyze(fileName, code);
|
||||
boolean success = result.getSuccess();
|
||||
List<ScriptDiagnostic> issues = result.getIssues();
|
||||
boolean success = true;
|
||||
for (ScriptDiagnostic issue : issues) {
|
||||
Severity severity = issue.getSeverity();
|
||||
if (severity == Severity.ERROR || severity == Severity.FATAL) {
|
||||
scriptLog.error("{}", issue.render(false, true, true, true));
|
||||
success = false;
|
||||
} else {
|
||||
scriptLog.warn("Compiler issue: {}", issue);
|
||||
} else if (severity == Severity.WARNING) {
|
||||
scriptLog.warn("Compile issue: {}", issue);
|
||||
}
|
||||
}
|
||||
List<JadxLintError> lintErrs = Collections.emptyList();
|
||||
@@ -175,7 +178,7 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
|
||||
errorService.addLintErrors(lintErrs);
|
||||
errorService.apply();
|
||||
if (!success) {
|
||||
resultLabel.setText("Compiler issues: " + issues.size());
|
||||
resultLabel.setText("Compile issues: " + issues.size());
|
||||
showScriptLog();
|
||||
} else if (!lintErrs.isEmpty()) {
|
||||
resultLabel.setText("Lint issues: " + lintErrs.size());
|
||||
|
||||
@@ -62,6 +62,9 @@ public class ScriptErrorService extends AbstractParser {
|
||||
|
||||
public void addCompilerIssues(List<ScriptDiagnostic> issues) {
|
||||
for (ScriptDiagnostic issue : issues) {
|
||||
if (issue.getSeverity() == ScriptDiagnostic.Severity.DEBUG) {
|
||||
continue;
|
||||
}
|
||||
DefaultParserNotice notice;
|
||||
SourceCode.Location loc = issue.getLocation();
|
||||
if (loc == null) {
|
||||
|
||||
@@ -4,15 +4,17 @@ import jadx.plugins.script.ScriptEval
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices
|
||||
import kotlin.script.experimental.api.ReplAnalyzerResult
|
||||
import kotlin.script.experimental.api.ReplCompletionResult
|
||||
import kotlin.script.experimental.api.ResultWithDiagnostics
|
||||
import kotlin.script.experimental.api.ScriptCompilationConfiguration
|
||||
import kotlin.script.experimental.api.ScriptDiagnostic
|
||||
import kotlin.script.experimental.api.SourceCode
|
||||
import kotlin.script.experimental.api.SourceCodeCompletionVariant
|
||||
import kotlin.script.experimental.api.analysisDiagnostics
|
||||
import kotlin.script.experimental.api.hostConfiguration
|
||||
import kotlin.script.experimental.api.renderedResultType
|
||||
import kotlin.script.experimental.api.valueOrNull
|
||||
import kotlin.script.experimental.host.toScriptSource
|
||||
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
|
||||
import kotlin.script.experimental.jvm.util.isError
|
||||
import kotlin.script.experimental.jvm.util.toSourceCodePosition
|
||||
|
||||
const val AUTO_COMPLETE_INSERT_STR = "ABCDEF" // defined at KJvmReplCompleter.INSERTED_STRING
|
||||
@@ -23,42 +25,55 @@ data class ScriptCompletionResult(
|
||||
)
|
||||
|
||||
data class ScriptAnalyzeResult(
|
||||
val success: Boolean,
|
||||
val issues: List<ScriptDiagnostic>,
|
||||
val renderType: String?,
|
||||
val reports: List<ScriptDiagnostic>,
|
||||
)
|
||||
|
||||
class ScriptServices(private val scriptName: String) {
|
||||
private val replCompiler = KJvmReplCompilerWithIdeServices()
|
||||
private val compileConf = ScriptEval().buildCompileConf()
|
||||
class ScriptServices {
|
||||
private val compileConf = ScriptEval.compileConf
|
||||
private val replCompiler = KJvmReplCompilerWithIdeServices(
|
||||
compileConf[ScriptCompilationConfiguration.hostConfiguration]
|
||||
?: defaultJvmScriptingHostConfiguration,
|
||||
)
|
||||
|
||||
fun complete(code: String, cursor: Int): ScriptCompletionResult {
|
||||
val result = complete(code.toScriptSource(scriptName), cursor)
|
||||
fun complete(scriptName: String, code: String, cursor: Int): ScriptCompletionResult {
|
||||
val snippet = code.toScriptSource(scriptName)
|
||||
val result = runBlocking {
|
||||
replCompiler.complete(snippet, cursor.toSourceCodePosition(snippet), compileConf)
|
||||
}
|
||||
return ScriptCompletionResult(
|
||||
completions = result.valueOrNull()?.toList() ?: emptyList(),
|
||||
reports = result.reports,
|
||||
)
|
||||
}
|
||||
|
||||
fun analyze(code: String, cursor: Int): ScriptAnalyzeResult {
|
||||
val result = analyze(code.toScriptSource(scriptName), cursor)
|
||||
val analyzerResult = result.valueOrNull()
|
||||
return ScriptAnalyzeResult(
|
||||
issues = analyzerResult?.get(ReplAnalyzerResult.analysisDiagnostics)?.toList() ?: emptyList(),
|
||||
renderType = analyzerResult?.get(ReplAnalyzerResult.renderedResultType),
|
||||
reports = result.reports,
|
||||
)
|
||||
}
|
||||
|
||||
private fun complete(code: SourceCode, cursor: Int): ResultWithDiagnostics<ReplCompletionResult> {
|
||||
return runBlocking {
|
||||
replCompiler.complete(code, cursor.toSourceCodePosition(code), compileConf)
|
||||
}
|
||||
}
|
||||
|
||||
private fun analyze(code: SourceCode, cursor: Int): ResultWithDiagnostics<ReplAnalyzerResult> {
|
||||
return runBlocking {
|
||||
replCompiler.analyze(code, cursor.toSourceCodePosition(code), compileConf)
|
||||
fun analyze(scriptName: String, code: String): ScriptAnalyzeResult {
|
||||
// TODO: temp solution: analyze do not work with dependencies, use compile instead
|
||||
val sourceCode = code.toScriptSource(scriptName)
|
||||
if (code.contains("@file:DependsOn(")) {
|
||||
val result = runBlocking {
|
||||
ScriptEval().compile(sourceCode)
|
||||
}
|
||||
return ScriptAnalyzeResult(
|
||||
success = !result.isError(),
|
||||
issues = result.reports,
|
||||
renderType = null,
|
||||
)
|
||||
} else {
|
||||
val result = runBlocking {
|
||||
val cursor = SourceCode.Position(0, 0) // not used
|
||||
replCompiler.analyze(sourceCode, cursor, compileConf)
|
||||
}
|
||||
val analyzerResult = result.valueOrNull()
|
||||
val issues = mutableListOf<ScriptDiagnostic>()
|
||||
analyzerResult?.get(ReplAnalyzerResult.analysisDiagnostics)?.let(issues::addAll)
|
||||
issues.addAll(result.reports)
|
||||
return ScriptAnalyzeResult(
|
||||
success = !result.isError(),
|
||||
issues = issues,
|
||||
renderType = analyzerResult?.get(ReplAnalyzerResult.renderedResultType),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package jadx.plugins.script.ide
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class ScriptServicesTest {
|
||||
|
||||
@Test
|
||||
fun testAnalyzeSimple() {
|
||||
val name = "simple"
|
||||
val script = getSampleScript(name)
|
||||
val result = ScriptServices().analyze(name, script)
|
||||
println(result)
|
||||
assertThat(result.success).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAnalyzeDeps() {
|
||||
val name = "test-deps"
|
||||
val script = getSampleScript(name)
|
||||
val result = ScriptServices().analyze(name, script)
|
||||
println(result)
|
||||
assertThat(result.success).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testComplete() {
|
||||
val name = "simple"
|
||||
val script = getSampleScript(name)
|
||||
val idx = script.indexOf("jadx.log.info")
|
||||
val completePos = idx + 7 // jadx.lo| <- complete 'log'
|
||||
val curScript = script.removeRange(completePos, script.indexOf("\n", idx))
|
||||
|
||||
val result = ScriptServices().complete(name, curScript, completePos)
|
||||
println(result)
|
||||
assertThat(result.completions)
|
||||
.hasSize(1)
|
||||
.allMatch { c -> c.text == "log" }
|
||||
}
|
||||
|
||||
private fun getSampleScript(scriptName: String): String {
|
||||
val resFile = javaClass.classLoader.getResource("samples/$scriptName.jadx.kts")
|
||||
return resFile!!.readText()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
val jadx = getJadxInstance()
|
||||
|
||||
jadx.afterLoad {
|
||||
jadx.log.info { "Hello" }
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
@file:DependsOn("org.apache.commons:commons-text:1.10.0")
|
||||
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
|
||||
val jadx = getJadxInstance()
|
||||
|
||||
jadx.afterLoad {
|
||||
jadx.classes.forEach {
|
||||
jadx.log.info { "Escaped name: ${StringEscapeUtils.escapeJava(it.fullName)}" }
|
||||
}
|
||||
}
|
||||
+22
-18
@@ -4,13 +4,13 @@ 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.CompiledScript
|
||||
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.SourceCode
|
||||
import kotlin.script.experimental.api.constructorArgs
|
||||
import kotlin.script.experimental.host.toScriptSource
|
||||
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
|
||||
@@ -22,7 +22,19 @@ import kotlin.time.toDuration
|
||||
|
||||
class ScriptEval {
|
||||
|
||||
private val scriptingHost = BasicJvmScriptingHost()
|
||||
companion object {
|
||||
val scriptingHost = BasicJvmScriptingHost()
|
||||
|
||||
val compileConf = createJvmCompilationConfigurationFromTemplate<JadxScriptTemplate>()
|
||||
|
||||
private val baseEvalConf = createJvmEvaluationConfigurationFromTemplate<JadxScriptTemplate>()
|
||||
|
||||
private fun buildEvalConf(scriptData: JadxScriptData): ScriptEvaluationConfiguration {
|
||||
return ScriptEvaluationConfiguration(baseEvalConf) {
|
||||
constructorArgs(scriptData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun process(init: JadxPluginContext, scriptOptions: JadxScriptAllOptions): List<JadxScriptData> {
|
||||
val jadx = init.decompiler
|
||||
@@ -39,26 +51,18 @@ class ScriptEval {
|
||||
return scriptDataList
|
||||
}
|
||||
|
||||
fun buildCompileConf(): ScriptCompilationConfiguration {
|
||||
return createJvmCompilationConfigurationFromTemplate<JadxScriptTemplate> {
|
||||
// forcing compiler to not use modules while building script classpath
|
||||
// because shadow jar remove all modules-info.class (https://github.com/johnrengelman/shadow/issues/710)
|
||||
compilerOptions("-Xjdk-release=1.8")
|
||||
}
|
||||
}
|
||||
|
||||
fun buildEvalConf(scriptData: JadxScriptData): ScriptEvaluationConfiguration {
|
||||
return createJvmEvaluationConfigurationFromTemplate<JadxScriptTemplate> {
|
||||
constructorArgs(scriptData)
|
||||
}
|
||||
suspend fun compile(script: SourceCode): ResultWithDiagnostics<CompiledScript> {
|
||||
return scriptingHost.compiler(script, compileConf)
|
||||
}
|
||||
|
||||
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)
|
||||
val result = scriptingHost.eval(
|
||||
scriptData.scriptFile.toScriptSource(),
|
||||
compileConf,
|
||||
buildEvalConf(scriptData),
|
||||
)
|
||||
processEvalResult(result, scriptData)
|
||||
}
|
||||
scriptData.log.debug { "Script '${scriptData.scriptName}' executed in ${execTime.toDuration(DurationUnit.MILLISECONDS)}" }
|
||||
|
||||
@@ -17,6 +17,7 @@ class JadxScriptPluginTest {
|
||||
args.inputFiles.run {
|
||||
add(getSampleFile("hello.smali"))
|
||||
add(getSampleFile("test.jadx.kts"))
|
||||
add(getSampleFile("test-deps.jadx.kts"))
|
||||
}
|
||||
val elapsed = measureTimeMillis {
|
||||
JadxDecompiler(args).use { jadx ->
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
@file:DependsOn("org.apache.commons:commons-text:1.10.0")
|
||||
|
||||
import org.apache.commons.text.StringEscapeUtils
|
||||
|
||||
val jadx = getJadxInstance()
|
||||
|
||||
jadx.afterLoad {
|
||||
jadx.classes.forEach {
|
||||
jadx.log.info { "Escaped name: ${StringEscapeUtils.escapeJava(it.fullName)}" }
|
||||
}
|
||||
}
|
||||
+14
-7
@@ -10,6 +10,7 @@ import kotlin.script.experimental.api.ScriptConfigurationRefinementContext
|
||||
import kotlin.script.experimental.api.acceptedLocations
|
||||
import kotlin.script.experimental.api.asSuccess
|
||||
import kotlin.script.experimental.api.collectedAnnotations
|
||||
import kotlin.script.experimental.api.compilerOptions
|
||||
import kotlin.script.experimental.api.defaultImports
|
||||
import kotlin.script.experimental.api.dependencies
|
||||
import kotlin.script.experimental.api.ide
|
||||
@@ -28,6 +29,7 @@ import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
|
||||
import kotlin.script.experimental.jvm.jvm
|
||||
|
||||
@KotlinScript(
|
||||
displayName = "Jadx Script",
|
||||
fileExtension = "jadx.kts",
|
||||
compilationConfiguration = JadxScriptConfiguration::class,
|
||||
)
|
||||
@@ -66,7 +68,11 @@ object JadxScriptConfiguration : ScriptCompilationConfiguration({
|
||||
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
|
||||
}
|
||||
|
||||
isStandalone(false)
|
||||
isStandalone(true)
|
||||
|
||||
// forcing compiler to not use modules while building script classpath
|
||||
// because shadow jar remove all modules-info.class (https://github.com/johnrengelman/shadow/issues/710)
|
||||
compilerOptions.append("-Xjdk-release=1.8")
|
||||
})
|
||||
|
||||
private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver())
|
||||
@@ -75,10 +81,11 @@ fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContex
|
||||
val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?: return context.compilationConfiguration.asSuccess()
|
||||
return runBlocking { resolver.resolveFromScriptSourceAnnotations(annotations) }
|
||||
.onSuccess {
|
||||
context.compilationConfiguration.with {
|
||||
dependencies.append(JvmDependency(it))
|
||||
}.asSuccess()
|
||||
}
|
||||
return runBlocking {
|
||||
resolver.resolveFromScriptSourceAnnotations(annotations)
|
||||
}.onSuccess {
|
||||
context.compilationConfiguration.with {
|
||||
dependencies.append(JvmDependency(it))
|
||||
}.asSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user