fix(smali-input): improve error report for smali assemble (#2411)

This commit is contained in:
Skylot
2025-02-15 21:35:34 +00:00
parent 4ef1f3b12b
commit ff66f95a8a
2 changed files with 74 additions and 42 deletions
@@ -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);
}
}
@@ -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();
}
}