From ff66f95a8a868eabccf7498f01fd08f6c3ea63c6 Mon Sep 17 00:00:00 2001 From: Skylot <118523+skylot@users.noreply.github.com> Date: Sat, 15 Feb 2025 21:35:34 +0000 Subject: [PATCH] fix(smali-input): improve error report for smali assemble (#2411) --- .../plugins/input/smali/SmaliConvert.java | 23 ++--- .../jadx/plugins/input/smali/SmaliUtils.java | 93 +++++++++++++------ 2 files changed, 74 insertions(+), 42 deletions(-) diff --git a/jadx-plugins/jadx-smali-input/src/main/java/jadx/plugins/input/smali/SmaliConvert.java b/jadx-plugins/jadx-smali-input/src/main/java/jadx/plugins/input/smali/SmaliConvert.java index 97ba1aad9..e3dc3c1d2 100644 --- a/jadx-plugins/jadx-smali-input/src/main/java/jadx/plugins/input/smali/SmaliConvert.java +++ b/jadx-plugins/jadx-smali-input/src/main/java/jadx/plugins/input/smali/SmaliConvert.java @@ -1,11 +1,8 @@ package jadx.plugins.input.smali; -import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.PrintStream; -import java.io.Reader; import java.nio.file.FileSystems; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.util.ArrayList; @@ -33,12 +30,8 @@ public class SmaliConvert { if (smaliFiles.isEmpty()) { return false; } - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - collectSystemErrors(out, () -> compile(smaliFiles, options)); - boolean success = out.size() == 0; - if (!success) { - LOG.error("Smali error:\n{}", out); - } + try { + compile(smaliFiles, options); } catch (Exception e) { LOG.error("Smali process error", e); } @@ -56,7 +49,7 @@ public class SmaliConvert { int threads = options.getThreads(); LOG.debug("Compiling smali files: {}, threads: {}", inputFiles.size(), threads); long start = System.currentTimeMillis(); - if (threads == 1) { + if (threads == 1 || inputFiles.size() == 1) { for (Path inputFile : inputFiles) { assemble(inputFile, smaliOptions); } @@ -78,12 +71,12 @@ public class SmaliConvert { } private void assemble(Path inputFile, SmaliOptions smaliOptions) { - String fileName = inputFile.toAbsolutePath().toString(); - try (Reader reader = Files.newBufferedReader(inputFile)) { - byte[] assemble = SmaliUtils.assemble(reader, smaliOptions); - dexData.add(new SimpleDexData(fileName, assemble)); + Path path = inputFile.toAbsolutePath(); + try { + byte[] dexContent = SmaliUtils.assemble(path.toFile(), smaliOptions); + dexData.add(new SimpleDexData(path.toString(), dexContent)); } catch (Exception e) { - throw new RuntimeException("Fail to compile: " + fileName, e); + LOG.error("Failed to assemble smali file: {}", path, e); } } diff --git a/jadx-plugins/jadx-smali-input/src/main/java/jadx/plugins/input/smali/SmaliUtils.java b/jadx-plugins/jadx-smali-input/src/main/java/jadx/plugins/input/smali/SmaliUtils.java index 554c5712f..22470bd94 100644 --- a/jadx-plugins/jadx-smali-input/src/main/java/jadx/plugins/input/smali/SmaliUtils.java +++ b/jadx-plugins/jadx-smali-input/src/main/java/jadx/plugins/input/smali/SmaliUtils.java @@ -1,17 +1,20 @@ package jadx.plugins.input.smali; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; -import java.io.Reader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.RecognitionException; -import org.antlr.runtime.TokenSource; +import org.antlr.runtime.TokenStream; import org.antlr.runtime.tree.CommonTreeNodeStream; +import org.antlr.runtime.tree.TreeNodeStream; import com.android.tools.smali.dexlib2.Opcodes; import com.android.tools.smali.dexlib2.writer.builder.DexBuilder; import com.android.tools.smali.dexlib2.writer.io.MemoryDataStore; -import com.android.tools.smali.smali.LexerErrorInterface; import com.android.tools.smali.smali.SmaliOptions; import com.android.tools.smali.smali.smaliFlexLexer; import com.android.tools.smali.smali.smaliParser; @@ -24,31 +27,67 @@ import com.android.tools.smali.smali.smaliTreeWalker; public class SmaliUtils { @SuppressWarnings("ExtractMethodRecommender") - public static byte[] assemble(Reader reader, SmaliOptions options) throws IOException, RecognitionException { - LexerErrorInterface lexer = new smaliFlexLexer(reader, options.apiLevel); - CommonTokenStream tokens = new CommonTokenStream((TokenSource) lexer); - smaliParser parser = new smaliParser(tokens); - parser.setVerboseErrors(options.verboseErrors); - parser.setAllowOdex(options.allowOdexOpcodes); - parser.setApiLevel(options.apiLevel); - smaliParser.smali_file_return parseResult = parser.smali_file(); - if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) { - throw new RuntimeException("Parse error"); - } - CommonTreeNodeStream treeStream = new CommonTreeNodeStream(parseResult.getTree()); - treeStream.setTokenStream(tokens); + public static byte[] assemble(File smaliFile, SmaliOptions options) throws IOException { + StringBuilder errors = new StringBuilder(); + try (FileInputStream fis = new FileInputStream(smaliFile); + InputStreamReader reader = new InputStreamReader(fis, StandardCharsets.UTF_8)) { - DexBuilder dexBuilder = new DexBuilder(Opcodes.forApi(options.apiLevel)); - smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); - dexGen.setApiLevel(options.apiLevel); - dexGen.setVerboseErrors(options.verboseErrors); - dexGen.setDexBuilder(dexBuilder); - dexGen.smali_file(); - if (dexGen.getNumberOfSyntaxErrors() > 0) { - throw new RuntimeException("Compile error"); + smaliFlexLexer lexer = new smaliFlexLexer(reader, options.apiLevel); + lexer.setSourceFile(smaliFile); + CommonTokenStream tokens = new CommonTokenStream(lexer); + ParserWrapper parser = new ParserWrapper(tokens, errors); + parser.setVerboseErrors(options.verboseErrors); + parser.setAllowOdex(options.allowOdexOpcodes); + parser.setApiLevel(options.apiLevel); + ParserWrapper.smali_file_return parseResult = parser.smali_file(); + if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) { + throw new RuntimeException("Smali parse error: " + errors); + } + CommonTreeNodeStream treeStream = new CommonTreeNodeStream(parseResult.getTree()); + treeStream.setTokenStream(tokens); + + DexBuilder dexBuilder = new DexBuilder(Opcodes.forApi(options.apiLevel)); + TreeWalkerWrapper dexGen = new TreeWalkerWrapper(treeStream, errors); + dexGen.setApiLevel(options.apiLevel); + dexGen.setVerboseErrors(options.verboseErrors); + dexGen.setDexBuilder(dexBuilder); + dexGen.smali_file(); + if (dexGen.getNumberOfSyntaxErrors() > 0) { + throw new RuntimeException("Smali compile error: " + errors); + } + MemoryDataStore dataStore = new MemoryDataStore(); + dexBuilder.writeTo(dataStore); + return dataStore.getData(); + } catch (RecognitionException e) { + throw new RuntimeException("Smali process error: " + errors, e); + } + } + + private static final class ParserWrapper extends smaliParser { + private final StringBuilder errors; + + public ParserWrapper(TokenStream input, StringBuilder errors) { + super(input); + this.errors = errors; + } + + @Override + public void emitErrorMessage(String msg) { + errors.append('\n').append(msg); + } + } + + private static final class TreeWalkerWrapper extends smaliTreeWalker { + private final StringBuilder errors; + + public TreeWalkerWrapper(TreeNodeStream input, StringBuilder errors) { + super(input); + this.errors = errors; + } + + @Override + public void emitErrorMessage(String msg) { + errors.append('\n').append(msg); } - MemoryDataStore dataStore = new MemoryDataStore(); - dexBuilder.writeTo(dataStore); - return dataStore.getData(); } }