Compare commits

..

59 Commits

Author SHA1 Message Date
Skylot d22db30166 fix: use secure xml parser for process manifest 2022-01-20 11:17:12 +00:00
Skylot 6db61e7a59 chore: update dependencies 2022-01-20 10:23:49 +00:00
Skylot 86582de521 feat: use kotlin intrinsic methods for variables rename (#1207) 2022-01-19 17:30:04 +00:00
Skylot a7c63c2eb3 fix: handle method override with several bases (#1234) 2022-01-18 18:27:09 +00:00
Skylot 081a0e21ee fix: precalculate class deps for inline methods (#1339) 2022-01-17 14:38:38 +00:00
Skylot 9ac9c05265 fix: simplify cascading casts (#1336) 2022-01-15 16:31:18 +00:00
Skylot b7daf79b26 fix: add explicit type for non-int constants (#1336) 2022-01-15 14:11:44 +00:00
Skylot b67a3561a4 build: add CodeQL analysis 2022-01-13 22:37:36 +03:00
Skylot 52ac6dbbaf docs: add security.md 2022-01-13 16:45:32 +00:00
Skylot 72381ad8f3 fix: correct literal negate for double and float (#1334) 2022-01-13 14:00:53 +00:00
Skylot 6a065c46f4 chore: update dependencies 2022-01-13 12:12:15 +00:00
Skylot 092d0d7e67 fix(gui): reduce tree focus switching 2022-01-12 19:57:38 +03:00
Skylot 5ca7285558 fix(gui): correct handling for tree row click (#1324) 2022-01-12 16:57:25 +00:00
Skylot 7576f9cd5e fix: wrap negative literals before cast (#1327) 2022-01-12 17:31:40 +03:00
Skylot 46b5725d98 refactor(test): replace inputs with test profiles 2022-01-12 17:31:37 +03:00
Jan S 72542fa6f9 fix(gui): processing threads spinner initialization (#1331)(PR #1332)
* fix: processing threads spinner initialization (#1331)
* fix: processing threads spinner initialization (#1331)
2022-01-12 14:23:07 +00:00
demonlol a250d0461b fix(dbg): support multiple main <action> and <activity-alias> tags (#1322)(PR #1323)
* fix(dgb): support multiple main <action> and <activity-alias> tags in manifest
* Update jadx-gui/src/main/java/jadx/gui/device/debugger/DbgUtils.java
2022-01-02 20:09:24 +03:00
Skylot c7795bfc48 fix: improve anonymous class inline (#523) 2021-12-26 13:06:49 +00:00
Skylot 5de46b7e40 chore: update gradle and dependencies 2021-12-24 12:53:30 +00:00
Skylot 99c70872c1 fix: use debug line numbers only at fixed offsets (#1315) 2021-12-22 22:55:14 +03:00
Skylot 3566669303 chore: update lgtm config 2021-12-22 12:24:01 +00:00
Skylot 4557d05256 fix: use correct type for anonymous class instance (#597) 2021-12-21 17:47:52 +00:00
Skylot fa421d165e build: disable missing warnings from javadoc 2021-12-21 12:52:52 +00:00
Skylot ecf20020d7 chore: cache current working dir in static field, other minor changes 2021-12-20 19:25:07 +00:00
Skylot ae85af61c7 fix: skip input file name checks by zip name validator (#1310) 2021-12-20 18:55:28 +00:00
Skylot 659bbbf4fb fix: correct usage of Path.getParent() 2021-12-20 16:48:50 +00:00
Jan S 427e2dddc4 fix: use relative file paths in .jadx project file (#1312) (PR #1313)
* chore: use relative file paths in .jadx project file (#1312)
* code beautified
* requested changes
2021-12-20 13:52:51 +00:00
skylot d47483f957 docs: use jadx as a library 2021-12-19 20:36:58 +00:00
Skylot 4bd8e26ae7 build: add maven publish 2021-12-19 16:24:09 +00:00
Skylot 01f47282ed fix: forbid 'printStackTrace()' usage 2021-12-18 19:24:36 +00:00
Skylot afdd37cd97 fix: add comments with option references to improve usability 2021-12-15 12:24:37 +00:00
Skylot addaffcd1d chore: update dependencies 2021-12-15 11:56:01 +00:00
Skylot 63f7ce20a4 fix: add merged condition blocks for loop region (#1307) 2021-12-14 14:25:59 +00:00
Skylot f37c23db7a fix: use correct top block for try blocks with same start (#1304) 2021-12-13 18:14:27 +00:00
Skylot d2bde0be21 fix: invoke in nested anonymous classes (#1305) 2021-12-13 00:12:30 +03:00
SiderealArt 9c446ebbd6 feat(gui): add Traditional Chinese translation (PR #1306) 2021-12-12 16:05:10 +00:00
Skylot 0f00fb9a27 fix: handle move-result after invoke-custom with string concat 2021-12-11 16:22:27 +00:00
Skylot 2d6f819c86 chore: update gradle and dependencies 2021-12-11 16:22:27 +00:00
Skylot 56683ac409 fix: improve try/catch bounds detection (#1303) 2021-12-09 17:34:53 +00:00
skylot a72523c7df docs: add link to decompilation troubleshooting 2021-12-08 13:11:31 +00:00
Surendrajat 46eeb0bc22 fix(gui): forward navigation shortcut on macOS (#1297)(PR #1301)
* fix: forward navigation shortcut on macOS
* apply suggestion
2021-12-06 16:45:29 +03:00
Skylot 6e8baef9b2 feat(gui): allow to minimize/maximize search windows (#1298) 2021-12-04 11:04:17 +00:00
Skylot 947b621733 feat: add option to use dx/d8 for convert java bytecode (#1299) 2021-12-03 15:05:28 +00:00
Skylot 4cc00bdaf2 fix: handle super case for invokespecial opcode (#1300) 2021-12-02 18:13:19 +00:00
Moredistant 59ef569a63 fix(gui): update chinese translation (PR #1296) 2021-11-30 11:57:16 +03:00
Choiman1559 abae225915 Update Korean translation (#1294)
* Update Messages_ko_KR.properties

* Update Messages_ko_KR.properties

Add missing translations
2021-11-29 19:56:01 +03:00
Jan S 05bdf9daae perf(res): XML decoding speed enhancement (PR #1293)
* chore: XML decoding speed improved for large APKs (finding class references)
* skip attach class node to xml for SimpleCodeWriter (used in jadx-cli)

Co-authored-by: Skylot <skylot@gmail.com>
2021-11-29 15:08:54 +03:00
Haeter 0a8192168a fix(gui): update Quark report parsing (#1289) (PR #1291) 2021-11-28 19:31:28 +03:00
Hen Ry 88fd5a517e fix(gui): update German translation (PR #1290)
* Updated German translation
2021-11-28 19:15:07 +03:00
zhongqingsong 74c5b616a4 fix(gui): update Chinese translation (PR #1287)
1. According to the English version of the document, complete the left texts.
2. Fix some inaccurate word, such as field, old CN is variable(变量),  inadequacy. signer, old CN is somebody(人), now it's something(者)。
3. Fix improper use of symbols, Lack of symbols in some place, some EN symbol translate to CN symbol.
4. Other change
2021-11-26 18:40:01 +03:00
Skylot 22a61d715b build: sometimes build failing without running gradle daemon 2021-11-25 14:47:17 +03:00
Skylot a90ec7c64a fix: include inlined classes in usage search (#1285) 2021-11-25 14:47:13 +03:00
Jan Peter Stotz b22812b43a fix: APK signature description for unprotected entries only applies to v1 signatures 2021-11-24 16:46:38 +03:00
Jan Peter Stotz 4c0da8c3d5 fix: binary xml hexadecimal int value decoding 2021-11-24 16:46:38 +03:00
Moredistant 9aa30f77b7 fix(gui): update chinese translation (PR #1284) 2021-11-23 15:54:53 +03:00
Martin Kay 2dbef83fa6 feat(gui): smali code highlighting (PR #1283)
* smali code highlighting is basically perfect
* Optimize smali highlight color matching, and provide original jflex generation

* reformat code
* disable checkstyle
* update shell to be more environment independent

Co-authored-by: Skylot <skylot@gmail.com>
2021-11-23 15:53:37 +03:00
Skylot 6ec7f789ef fix: restore usage data after class reload (#1281) 2021-11-22 13:56:15 +00:00
Skylot 31c0afe29e fix: don't unload field init values (#1277) 2021-11-21 18:54:32 +00:00
Skylot 46b07863c1 build: fix bundle build 2021-11-20 20:49:57 +00:00
182 changed files with 6431 additions and 1055 deletions
+41
View File
@@ -0,0 +1,41 @@
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 9 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ['java']
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
queries: +security-extended
languages: ${{ matrix.language }}
# Don't build tests in jadx-core also skip tests execution and checkstyle tasks
- run: |
./gradlew clean build -x checkstyleTest -x checkstyleMain -x test -x ':jadx-core:testClasses'
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+2
View File
@@ -34,3 +34,5 @@ jadx-output/
*.cfg *.cfg
*.orig *.orig
quark.json quark.json
cliff.toml
+8 -1
View File
@@ -11,6 +11,8 @@
Command line and GUI tools for producing Java source code from Android Dex and Apk files Command line and GUI tools for producing Java source code from Android Dex and Apk files
:exclamation: :exclamation: :exclamation: Please note that in most cases Jadx can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
**Main features:** **Main features:**
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files - decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
- decode `AndroidManifest.xml` and other resources from `resources.arsc` - decode `AndroidManifest.xml` and other resources from `resources.arsc`
@@ -49,6 +51,9 @@ For windows you can download it from [oracle.com](https://www.oracle.com/java/te
brew install jadx brew install jadx
``` ```
### Use jadx as a library
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
### Build from source ### Build from source
JDK 8 or higher must be installed: JDK 8 or higher must be installed:
``` ```
@@ -91,6 +96,7 @@ options:
--deobf-rewrite-cfg - force to ignore and overwrite deobfuscation map file --deobf-rewrite-cfg - force to ignore and overwrite deobfuscation map file
--deobf-use-sourcename - use source file name as class name alias --deobf-use-sourcename - use source file name as class name alias
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names --deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
--use-kotlin-methods-for-var-names - use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide, default: apply
--rename-flags - fix options (comma-separated list of): --rename-flags - fix options (comma-separated list of):
'case' - fix case sensitivity issues (according to --fs-case-sensitive option), 'case' - fix case sensitivity issues (according to --fs-case-sensitive option),
'valid' - rename java identifiers to make them valid, 'valid' - rename java identifiers to make them valid,
@@ -101,7 +107,8 @@ options:
--cfg - save methods control flow graph to dot file --cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions) --raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc) -f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
--comments-level - set code comments level, values: none, user_only, error, warn, info, debug, default: info --use-dx - use dx/d8 to convert java bytecode
--comments-level - set code comments level, values: error, warn, info, debug, user-only, none, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress --log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG) -v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET) -q, --quiet - turn off output (set --log-level to QUIET)
+7
View File
@@ -0,0 +1,7 @@
# Security Policy
## Reporting a Vulnerability
To report a security issue, please email `skylot@gmail.com` with a description of the issue, the steps you took to create the issue, affected versions, and, if known, mitigations for the issue.
We will check and respond within 3 working days. If the issue is confirmed as a vulnerability, we will apply required mitigations at the next release.
This project follows a 90 day disclosure timeline.
+26 -30
View File
@@ -1,6 +1,6 @@
plugins { plugins {
id 'com.github.ben-manes.versions' version '0.39.0' id 'com.github.ben-manes.versions' version '0.41.0'
id 'com.diffplug.spotless' version '6.0.0' id 'com.diffplug.spotless' version '6.2.0'
} }
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev" ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
@@ -10,7 +10,6 @@ println("jadx version: ${jadxVersion}")
allprojects { allprojects {
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'checkstyle' apply plugin: 'checkstyle'
apply plugin: 'maven-publish'
version = jadxVersion version = jadxVersion
@@ -27,25 +26,17 @@ allprojects {
} }
} }
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
}
dependencies { dependencies {
implementation 'org.slf4j:slf4j-api:1.7.32' implementation 'org.slf4j:slf4j-api:1.7.33'
compileOnly 'org.jetbrains:annotations:23.0.0' compileOnly 'org.jetbrains:annotations:23.0.0'
testImplementation 'ch.qos.logback:logback-classic:1.2.7' testImplementation 'ch.qos.logback:logback-classic:1.2.10'
testImplementation 'org.hamcrest:hamcrest-library:2.2' testImplementation 'org.hamcrest:hamcrest-library:2.2'
testImplementation 'org.mockito:mockito-core:4.1.0' testImplementation 'org.mockito:mockito-core:4.2.0'
testImplementation 'org.assertj:assertj-core:3.21.0' testImplementation 'org.assertj:assertj-core:3.22.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1' testImplementation 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
testCompileOnly 'org.jetbrains:annotations:23.0.0' testCompileOnly 'org.jetbrains:annotations:23.0.0'
@@ -135,19 +126,7 @@ task copyExe(type: Copy) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy = DuplicatesStrategy.EXCLUDE
} }
task dist { task distWinBundle(type: Copy, dependsOn: 'jadx-gui:distWinWithJre') {
group 'jadx'
description = 'Build jadx distribution zip'
dependsOn(pack)
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
if (os.isWindows()) {
dependsOn('copyExe')
}
}
task distWin(type: Copy, dependsOn: 'jadx-gui:distWinWithJre') {
group 'jadx' group 'jadx'
description = 'Copy bundle to build dir' description = 'Copy bundle to build dir'
destinationDir buildDir destinationDir buildDir
@@ -157,6 +136,23 @@ task distWin(type: Copy, dependsOn: 'jadx-gui:distWinWithJre') {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy = DuplicatesStrategy.EXCLUDE
} }
task dist {
group 'jadx'
description = 'Build jadx distribution zip'
dependsOn(pack)
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
if (os.isWindows()) {
if (project.hasProperty("bundleJRE")) {
println("Build win bundle with JRE")
dependsOn('distWinBundle')
} else {
dependsOn('copyExe')
}
}
}
task cleanBuildDir(type: Delete) { task cleanBuildDir(type: Delete) {
group 'jadx' group 'jadx'
delete buildDir delete buildDir
+3
View File
@@ -0,0 +1,3 @@
plugins {
id 'groovy-gradle-plugin'
}
@@ -0,0 +1,78 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'signing'
}
group = 'io.github.skylot'
version = jadxVersion
java {
withJavadocJar()
withSourcesJar()
}
publishing {
publications {
mavenJava(MavenPublication) {
artifactId = project.name
from components.java
versionMapping {
usage('java-api') {
fromResolutionOf('runtimeClasspath')
}
usage('java-runtime') {
fromResolutionResult()
}
}
pom {
name = project.name
description = 'Dex to Java decompiler'
url = 'https://github.com/skylot/jadx'
licenses {
license {
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id = 'skylot'
name = 'Skylot'
email = 'skylot@gmail.com'
url = 'https://github.com/skylot'
}
}
scm {
connection = 'scm:git:git://github.com/skylot/jadx.git'
developerConnection = 'scm:git:ssh://github.com:skylot/jadx.git'
url = 'https://github.com/skylot/jadx'
}
}
}
}
repositories {
maven {
def releasesRepoUrl = uri('https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/')
def snapshotsRepoUrl = uri('https://s01.oss.sonatype.org/content/repositories/snapshots/')
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
username = project.properties['ossrhUser'].toString()
password = project.properties['ossrhPassword'].toString()
}
}
}
}
signing {
sign publishing.publications.mavenJava
}
javadoc {
if (JavaVersion.current().isJava9Compatible()) {
options.addBooleanOption('html5', true)
}
// disable 'missing' warnings
options.addStringOption('Xdoclint:all,-missing', '-quiet')
}
+6
View File
@@ -124,6 +124,12 @@
<module name="IllegalImport"> <module name="IllegalImport">
<property name="illegalClasses" value="jadx.core.utils.DebugUtils"/> <property name="illegalClasses" value="jadx.core.utils.DebugUtils"/>
</module> </module>
<module name="RegexpSinglelineJava">
<property name="id" value="printstacktrace"/>
<property name="format" value="\.printStackTrace\(\)"/>
<property name="ignoreComments" value="true"/>
<property name="message" value="Using Throwable.printStackTrace() is forbidden. Use logger to print exception"/>
</module>
</module> </module>
<module name="NewlineAtEndOfFile"/> <module name="NewlineAtEndOfFile"/>
+1
View File
@@ -0,0 +1 @@
SmaliTokenMaker.java
+5
View File
@@ -0,0 +1,5 @@
Refer to the following instructions to modify and use to generate SmaliTokenMarker
```shell
jflex SmaliTokenMaker.flex --skel skeleton.default
```
+681
View File
@@ -0,0 +1,681 @@
/*
* Generated on 11/22/21, 8:58 PM
*/
package jadx.gui.ui.codeearea;
import java.io.*;
import javax.swing.text.Segment;
import org.fife.ui.rsyntaxtextarea.*;
/**
* 用于Smali代码高亮
* MartinKay@qq.com
*/
%%
%public
%class SmaliTokenMaker
%extends AbstractJFlexCTokenMaker
%unicode
/* Case sensitive */
%type org.fife.ui.rsyntaxtextarea.Token
%{
/**
* Constructor. This must be here because JFlex does not generate a
* no-parameter constructor.
*/
public SmaliTokenMaker() {
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
* @see #addToken(int, int, int)
*/
private void addHyperlinkToken(int start, int end, int tokenType) {
int so = start + offsetShift;
addToken(zzBuffer, start,end, tokenType, so, true);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
*/
private void addToken(int tokenType) {
addToken(zzStartRead, zzMarkedPos-1, tokenType);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param tokenType The token's type.
* @see #addHyperlinkToken(int, int, int)
*/
private void addToken(int start, int end, int tokenType) {
int so = start + offsetShift;
addToken(zzBuffer, start,end, tokenType, so, false);
}
/**
* Adds the token specified to the current linked list of tokens.
*
* @param array The character array.
* @param start The starting offset in the array.
* @param end The ending offset in the array.
* @param tokenType The token's type.
* @param startOffset The offset in the document at which this token
* occurs.
* @param hyperlink Whether this token is a hyperlink.
*/
public void addToken(char[] array, int start, int end, int tokenType,
int startOffset, boolean hyperlink) {
super.addToken(array, start,end, tokenType, startOffset, hyperlink);
zzStartRead = zzMarkedPos;
}
/**
* {@inheritDoc}
*/
public String[] getLineCommentStartAndEnd(int languageIndex) {
return new String[] { "#", null };
}
/**
* Returns the first token in the linked list of tokens generated
* from <code>text</code>. This method must be implemented by
* subclasses so they can correctly implement syntax highlighting.
*
* @param text The text from which to get tokens.
* @param initialTokenType The token type we should start with.
* @param startOffset The offset into the document at which
* <code>text</code> starts.
* @return The first <code>Token</code> in a linked list representing
* the syntax highlighted text.
*/
public Token getTokenList(Segment text, int initialTokenType, int startOffset) {
resetTokenList();
this.offsetShift = -text.offset + startOffset;
// Start off in the proper state.
int state = Token.NULL;
switch (initialTokenType) {
/* No multi-line comments */
/* No documentation comments */
default:
state = Token.NULL;
}
s = text;
try {
yyreset(zzReader);
yybegin(state);
return yylex();
} catch (IOException ioe) {
ioe.printStackTrace();
return new TokenImpl();
}
}
/**
* Refills the input buffer.
*
* @return <code>true</code> if EOF was reached, otherwise
* <code>false</code>.
*/
private boolean zzRefill() {
return zzCurrentPos>=s.offset+s.count;
}
/**
* Resets the scanner to read from a new input stream.
* Does not close the old reader.
*
* All internal variables are reset, the old input stream
* <b>cannot</b> be reused (internal buffer is discarded and lost).
* Lexical state is set to <tt>YY_INITIAL</tt>.
*
* @param reader the new input stream
*/
public final void yyreset(Reader reader) {
// 's' has been updated.
zzBuffer = s.array;
/*
* We replaced the line below with the two below it because zzRefill
* no longer "refills" the buffer (since the way we do it, it's always
* "full" the first time through, since it points to the segment's
* array). So, we assign zzEndRead here.
*/
//zzStartRead = zzEndRead = s.offset;
zzStartRead = s.offset;
zzEndRead = zzStartRead + s.count - 1;
zzCurrentPos = zzMarkedPos = zzPushbackPos = s.offset;
zzLexicalState = YYINITIAL;
zzReader = reader;
zzAtBOL = true;
zzAtEOF = false;
}
%}
Letter = [A-Za-z]
LetterOrUnderscore = ({Letter}|"_")
NonzeroDigit = [1-9]
Digit = ("0"|{NonzeroDigit})
HexDigit = ({Digit}|[A-Fa-f])
OctalDigit = ([0-7])
AnyCharacterButApostropheOrBackSlash = ([^\\'])
AnyCharacterButDoubleQuoteOrBackSlash = ([^\\\"\n])
EscapedSourceCharacter = ("u"{HexDigit}{HexDigit}{HexDigit}{HexDigit})
Escape = ("\\"(([btnfr\"'\\])|([0123]{OctalDigit}?{OctalDigit}?)|({OctalDigit}{OctalDigit}?)|{EscapedSourceCharacter}))
NonSeparator = ([^\t\f\r\n\ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\']|"#"|"\\")
IdentifierStart = ({LetterOrUnderscore}|"$")
IdentifierPart = ({IdentifierStart}|{Digit}|("\\"{EscapedSourceCharacter}))
LineTerminator = (\n)
WhiteSpace = ([ \t\f]+)
CharLiteral = ([\']({AnyCharacterButApostropheOrBackSlash}|{Escape})[\'])
UnclosedCharLiteral = ([\'][^\'\n]*)
ErrorCharLiteral = ({UnclosedCharLiteral}[\'])
StringLiteral = ([\"]({AnyCharacterButDoubleQuoteOrBackSlash}|{Escape})*[\"])
UnclosedStringLiteral = ([\"]([\\].|[^\\\"])*[^\"]?)
ErrorStringLiteral = ({UnclosedStringLiteral}[\"])
/* No multi-line comments */
/* No documentation comments */
LineCommentBegin = "#"
IntegerLiteral = ({Digit}+)
HexLiteral = (0x{HexDigit}+)
FloatLiteral = (({Digit}+)("."{Digit}+)?(e[+-]?{Digit}+)? | ({Digit}+)?("."{Digit}+)(e[+-]?{Digit}+)?)
ErrorNumberFormat = (({IntegerLiteral}|{HexLiteral}|{FloatLiteral}){NonSeparator}+)
BooleanLiteral = ("true"|"false")
Separator = ([\(\)\{\}\[\]])
Separator2 = ([\;,.])
Identifier = ({IdentifierStart}{IdentifierPart}*)
URLGenDelim = ([:\/\?#\[\]@])
URLSubDelim = ([\!\$&'\(\)\*\+,;=])
URLUnreserved = ({LetterOrUnderscore}|{Digit}|[\-\.\~])
URLCharacter = ({URLGenDelim}|{URLSubDelim}|{URLUnreserved}|[%])
URLCharacters = ({URLCharacter}*)
URLEndCharacter = ([\/\$]|{Letter}|{Digit})
URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
/* Custom Regex */
/* fully-qualified name Rules */
SimpleName = ([a-zA-Z0-9_$]*)
QUALIFIED_TYPE_NAME = ("L"({SimpleName}{SLASH})*{SimpleName}*";")
/* Types */
VOID_TYPE = ("V")
BOOLEAN_TYPE = ("Z")
BYTE_TYPE = ("B")
SHORT_TYPE = ("S")
CHAR_TYPE = ("C")
INT_TYPE = ("I")
LONG_TYPE = ("J")
FLOAT_TYPE = ("F")
DOUBLE_TYPE = ("D")
/* Multi Args Types Highlight */
MULTI_ARGS_TYPES = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+);
/* Types fully-qualified name */
COMPOUND_METHOD_ARG_LITERAL = (({BOOLEAN_TYPE}|{BYTE_TYPE}|{SHORT_TYPE}|{CHAR_TYPE}|{INT_TYPE}|{LONG_TYPE}|{FLOAT_TYPE}|{DOUBLE_TYPE})+{QUALIFIED_TYPE_NAME})
LBRACK = ("[")
RBRACK = ("]")
LPAREN = ("(")
RPAREN = (")")
LBRACE = ("{")
RBRACE = ("}")
COLON = (":")
ASSIGN = ("=")
DOT = (".")
SUB = ("-")
COMMA = (",")
SLASH = ("/")
LT = ("<")
GT = (">")
ARROW = ("->")
SEMI = (";")
ARROW_FUNCTION = ({ARROW}[a-zA-Z_$<>]*)
CustomSeparator = ({Separator}|{ARROW_FUNCTION})
/* Register */
VREGISTER = ("v"("0"|[1-9])*)
PREGISTER = ("p"("0"|[1-9])*)
/* Flags */
FLAG_PSWITCH = (":pswitch_"{SimpleName})
FLAG_PSWITCH_DATA = (":pswitch_data_"{SimpleName})
FLAG_GOTO = (":goto_"{SimpleName})
FLAG_COND = (":cond_"{SimpleName})
FLAG_TRY_START = (":try_start_"{SimpleName})
FLAG_TRY_END = (":try_end_"{SimpleName})
FLAG_CATCH = (":catch_"{SimpleName})
FLAG_CATCHALL = (":catchall_"{SimpleName})
FLAG_ARRAY = (":array_"{SimpleName})
/* No string state */
/* No char state */
/* No MLC state */
/* No documentation comment state */
%state EOL_COMMENT
%%
<YYINITIAL> {
/* Keywords Instructions Highlight */
"nop" |
"move" |
"move/from16" |
"move/16" |
"move-wide" |
"move-wide/from16" |
"move-wide/16" |
"move-object" |
"move-object/from16" |
"move-object/16" |
"move-result" |
"move-result-wide" |
"move-result-object" |
"move-exception" |
"return-void" |
"return" |
"return-wide" |
"return-object" |
"const/4" |
"const/16" |
"const" |
"const/high16" |
"const-wide/16" |
"const-wide/32" |
"const-wide" |
"const-wide/high16" |
"const-string" |
"const-string/jumbo" |
"const-class" |
"monitor-enter" |
"monitor-exit" |
"check-cast" |
"instance-of" |
"array-length" |
"new-instance" |
"new-array" |
"filled-new-array" |
"filled-new-array/range" |
"fill-array-data" |
"throw" |
"goto" |
"goto/16" |
"goto/32" |
"cmpl-float" |
"cmpg-float" |
"cmpl-double" |
"cmpg-double" |
"cmp-long" |
"if-eq" |
"if-ne" |
"if-lt" |
"if-ge" |
"if-gt" |
"if-le" |
"if-eqz" |
"if-nez" |
"if-ltz" |
"if-gez" |
"if-gtz" |
"if-lez" |
"aget" |
"aget-wide" |
"aget-object" |
"aget-boolean" |
"aget-byte" |
"aget-char" |
"aget-short" |
"aput" |
"aput-wide" |
"aput-object" |
"aput-boolean" |
"aput-byte" |
"aput-char" |
"aput-short" |
"iget" |
"iget-wide" |
"iget-object" |
"iget-boolean" |
"iget-byte" |
"iget-char" |
"iget-short" |
"iput" |
"iput-wide" |
"iput-object" |
"iput-boolean" |
"iput-byte" |
"iput-char" |
"iput-short" |
"sget" |
"sget-wide" |
"sget-object" |
"sget-boolean" |
"sget-byte" |
"sget-char" |
"sget-short" |
"sput" |
"sput-wide" |
"sput-object" |
"sput-boolean" |
"sput-byte" |
"sput-char" |
"sput-short" |
"invoke-virtual" |
"invoke-super" |
"invoke-direct" |
"invoke-static" |
"invoke-interface" |
"invoke-virtual/range" |
"invoke-super/range" |
"invoke-direct/range" |
"invoke-static/range" |
"invoke-interface/range" |
"neg-int" |
"not-int" |
"neg-long" |
"not-long" |
"neg-float" |
"neg-double" |
"int-to-long" |
"int-to-float" |
"int-to-double" |
"long-to-int" |
"long-to-float" |
"long-to-double" |
"float-to-int" |
"float-to-long" |
"float-to-double" |
"double-to-int" |
"double-to-long" |
"double-to-float" |
"int-to-byte" |
"int-to-char" |
"int-to-short" |
"add-int" |
"sub-int" |
"mul-int" |
"div-int" |
"rem-int" |
"and-int" |
"or-int" |
"xor-int" |
"shl-int" |
"shr-int" |
"ushr-int" |
"add-long" |
"sub-long" |
"mul-long" |
"div-long" |
"rem-long" |
"and-long" |
"or-long" |
"xor-long" |
"shl-long" |
"shr-long" |
"ushr-long" |
"add-float" |
"sub-float" |
"mul-float" |
"div-float" |
"rem-float" |
"add-double" |
"sub-double" |
"mul-double" |
"div-double" |
"rem-double" |
"add-int/2addr" |
"sub-int/2addr" |
"mul-int/2addr" |
"div-int/2addr" |
"rem-int/2addr" |
"and-int/2addr" |
"or-int/2addr" |
"xor-int/2addr" |
"shl-int/2addr" |
"shr-int/2addr" |
"ushr-int/2addr" |
"add-long/2addr" |
"sub-long/2addr" |
"mul-long/2addr" |
"div-long/2addr" |
"rem-long/2addr" |
"and-long/2addr" |
"or-long/2addr" |
"xor-long/2addr" |
"shl-long/2addr" |
"shr-long/2addr" |
"ushr-long/2addr" |
"add-float/2addr" |
"sub-float/2addr" |
"mul-float/2addr" |
"div-float/2addr" |
"rem-float/2addr" |
"add-double/2addr" |
"sub-double/2addr" |
"mul-double/2addr" |
"div-double/2addr" |
"rem-double/2addr" |
"add-int/lit16" |
"rsub-int" |
"mul-int/lit16" |
"div-int/lit16" |
"rem-int/lit16" |
"and-int/lit16" |
"or-int/lit16" |
"xor-int/lit16" |
"add-int/lit8" |
"rsub-int/lit8" |
"mul-int/lit8" |
"div-int/lit8" |
"rem-int/lit8" |
"and-int/lit8" |
"or-int/lit8" |
"xor-int/lit8" |
"shl-int/lit8" |
"shr-int/lit8" |
"ushr-int/lit8" |
"invoke-polymorphic" |
"invoke-polymorphic/range" |
"invoke-custom" |
"invoke-custom/range" |
"const-method-handle" |
"const-method-type" |
"packed-switch" |
"sparse-switch" { addToken(Token.FUNCTION); }
/* Keywords Modifiers(IDENTIFIER标识符、修饰符) Highlight */
"public" |
"private" |
"protected" |
"final" |
"annotation" |
"static" |
"synthetic" |
"constructor" |
"abstract" |
"enum" |
"interface" |
"transient" |
"bridge" |
"declared-synchronized" |
"volatile" |
"strictfp" |
"varargs" |
"native" { addToken(Token.RESERVED_WORD); }
/* Keywords Directives Highlight */
".method" |
".end method" |
".implements" |
".class" |
".prologue" |
".source" |
".super" |
".field" |
".end field" |
".registers" |
".locals" |
".param" |
".line" |
".catch" |
".catchall" |
".annotation" |
".end annotation" |
".local" |
".end local" |
".restart local" |
".packed-switch" |
".end packed-switch" |
".array-data" |
".end array-data" |
".sparse-switch" |
".end sparse-switch" |
".end param" { addToken(Token.RESERVED_WORD_2); }
/* VARIABLE Register Highlight */
{VREGISTER} |
{PREGISTER} { addToken(Token.VARIABLE); }
/* Data types Highlight */
{QUALIFIED_TYPE_NAME} |
{COMPOUND_METHOD_ARG_LITERAL} |
{MULTI_ARGS_TYPES} |
{QUALIFIED_TYPE_NAME} |
{VOID_TYPE} |
{BOOLEAN_TYPE} |
{BYTE_TYPE} |
{SHORT_TYPE} |
{CHAR_TYPE} |
{INT_TYPE} |
{LONG_TYPE} |
{FLOAT_TYPE} |
{DOUBLE_TYPE} { addToken(Token.DATA_TYPE); }
/* FLAGS */
{FLAG_PSWITCH} |
{FLAG_PSWITCH_DATA} |
{FLAG_GOTO} |
{FLAG_COND} |
{FLAG_TRY_START} |
{FLAG_TRY_END} |
{FLAG_CATCHALL} |
{FLAG_ARRAY} |
{FLAG_CATCH} { addToken(Token.MARKUP_TAG_NAME); }
/* Functions */
/* No functions */
{BooleanLiteral} { addToken(Token.LITERAL_BOOLEAN); }
{LineTerminator} { addNullToken(); return firstToken; }
{Identifier} { addToken(Token.IDENTIFIER); }
{WhiteSpace} { addToken(Token.WHITESPACE); }
/* String/Character literals. */
{CharLiteral} { addToken(Token.LITERAL_CHAR); }
{UnclosedCharLiteral} { addToken(Token.ERROR_CHAR); addNullToken(); return firstToken; }
{ErrorCharLiteral} { addToken(Token.ERROR_CHAR); }
{StringLiteral} { addToken(Token.LITERAL_STRING_DOUBLE_QUOTE); }
{UnclosedStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); addNullToken(); return firstToken; }
{ErrorStringLiteral} { addToken(Token.ERROR_STRING_DOUBLE); }
/* Comment literals. */
/* No multi-line comments */
/* No documentation comments */
{LineCommentBegin} { start = zzMarkedPos-1; yybegin(EOL_COMMENT); }
/* Separators. */
{CustomSeparator} { addToken(Token.SEPARATOR); }
{Separator2} { addToken(Token.IDENTIFIER); }
/* Operators. */
"!" |
";" |
"." |
"=" |
"/" |
"'" |
"(" |
")" |
"," |
"->" |
";->" |
"<" |
">" |
"@" |
"[" |
"]" |
"{" |
"}" { addToken(Token.OPERATOR); }
/* Numbers */
{IntegerLiteral} { addToken(Token.LITERAL_NUMBER_DECIMAL_INT); }
{HexLiteral} { addToken(Token.LITERAL_NUMBER_HEXADECIMAL); }
{FloatLiteral} { addToken(Token.LITERAL_NUMBER_FLOAT); }
{ErrorNumberFormat} { addToken(Token.ERROR_NUMBER_FORMAT); }
/* Ended with a line not in a string or comment. */
<<EOF>> { addNullToken(); return firstToken; }
/* Catch any other (unhandled) characters. */
. { addToken(Token.IDENTIFIER); }
}
/* No char state */
/* No string state */
/* No multi-line comment state */
/* No documentation comment state */
<EOL_COMMENT> {
[^hwf\n]+ {}
{URL} { int temp=zzStartRead; addToken(start,zzStartRead-1, Token.COMMENT_EOL); addHyperlinkToken(temp,zzMarkedPos-1, Token.COMMENT_EOL); start = zzMarkedPos; }
[hwf] {}
\n { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
<<EOF>> { addToken(start,zzStartRead-1, Token.COMMENT_EOL); addNullToken(); return firstToken; }
}
+243
View File
@@ -0,0 +1,243 @@
/** This character denotes the end of file */
public static final int YYEOF = -1;
/** initial size of the lookahead buffer */
--- private static final int ZZ_BUFFERSIZE = ...;
/** lexical states */
--- lexical states, charmap
/* error codes */
private static final int ZZ_UNKNOWN_ERROR = 0;
private static final int ZZ_NO_MATCH = 1;
private static final int ZZ_PUSHBACK_2BIG = 2;
/* error messages for the codes above */
private static final String ZZ_ERROR_MSG[] = {
"Unkown internal scanner error",
"Error: could not match input",
"Error: pushback value was too large"
};
--- isFinal list
/** the input device */
private java.io.Reader zzReader;
/** the current state of the DFA */
private int zzState;
/** the current lexical state */
private int zzLexicalState = YYINITIAL;
/** this buffer contains the current text to be matched and is
the source of the yytext() string */
private char zzBuffer[];
/** the textposition at the last accepting state */
private int zzMarkedPos;
/** the textposition at the last state to be included in yytext */
private int zzPushbackPos;
/** the current text position in the buffer */
private int zzCurrentPos;
/** startRead marks the beginning of the yytext() string in the buffer */
private int zzStartRead;
/** endRead marks the last character in the buffer, that has been read
from input */
private int zzEndRead;
/** number of newlines encountered up to the start of the matched text */
private int yyline;
/** the number of characters up to the start of the matched text */
private int yychar;
/**
* the number of characters from the last newline up to the start of the
* matched text
*/
private int yycolumn;
/**
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
*/
private boolean zzAtBOL = true;
/** zzAtEOF == true <=> the scanner is at the EOF */
private boolean zzAtEOF;
--- user class code
/**
* Creates a new scanner
* There is also a java.io.InputStream version of this constructor.
*
* @param in the java.io.Reader to read input from.
*/
--- constructor declaration
/**
* Closes the input stream.
*/
public final void yyclose() throws java.io.IOException {
zzAtEOF = true; /* indicate end of file */
zzEndRead = zzStartRead; /* invalidate buffer */
if (zzReader != null)
zzReader.close();
}
/**
* Enters a new lexical state
*
* @param newState the new lexical state
*/
public final void yybegin(int newState) {
zzLexicalState = newState;
}
/**
* Returns the text matched by the current regular expression.
*/
public final String yytext() {
return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
}
/**
* Returns the character at position <tt>pos</tt> from the
* matched text.
*
* It is equivalent to yytext().charAt(pos), but faster
*
* @param pos the position of the character to fetch.
* A value from 0 to yylength()-1.
*
* @return the character at position pos
*/
public final char yycharat(int pos) {
return zzBuffer[zzStartRead+pos];
}
/**
* Returns the length of the matched text region.
*/
public final int yylength() {
return zzMarkedPos-zzStartRead;
}
/**
* Reports an error that occured while scanning.
*
* In a wellformed scanner (no or only correct usage of
* yypushback(int) and a match-all fallback rule) this method
* will only be called with things that "Can't Possibly Happen".
* If this method is called, something is seriously wrong
* (e.g. a JFlex bug producing a faulty scanner etc.).
*
* Usual syntax/scanner level error handling should be done
* in error fallback rules.
*
* @param errorCode the code of the errormessage to display
*/
--- zzScanError declaration
String message;
try {
message = ZZ_ERROR_MSG[errorCode];
}
catch (ArrayIndexOutOfBoundsException e) {
message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
}
--- throws clause
}
/**
* Pushes the specified amount of characters back into the input stream.
*
* They will be read again by then next call of the scanning method
*
* @param number the number of characters to be read again.
* This number must not be greater than yylength()!
*/
--- yypushback decl (contains zzScanError exception)
if ( number > yylength() )
zzScanError(ZZ_PUSHBACK_2BIG);
zzMarkedPos -= number;
}
--- zzDoEOF
/**
* Resumes scanning until the next regular expression is matched,
* the end of input is encountered or an I/O-Error occurs.
*
* @return the next token
* @exception java.io.IOException if any I/O-Error occurs
*/
--- yylex declaration
int zzInput;
int zzAction;
// cached fields:
int zzCurrentPosL;
int zzMarkedPosL;
int zzEndReadL = zzEndRead;
char [] zzBufferL = zzBuffer;
char [] zzCMapL = ZZ_CMAP;
--- local declarations
while (true) {
zzMarkedPosL = zzMarkedPos;
--- start admin (line, char, col count)
zzAction = -1;
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
--- start admin (lexstate etc)
zzForAction: {
while (true) {
--- next input, line, col, char count, next transition, isFinal action
zzAction = zzState;
zzMarkedPosL = zzCurrentPosL;
--- line count update
}
}
}
// store back cached position
zzMarkedPos = zzMarkedPosL;
--- char count update
--- actions
default:
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
zzAtEOF = true;
--- eofvalue
}
else {
--- no match
}
}
}
}
--- main
}
-1
View File
@@ -1,2 +1 @@
org.gradle.daemon=false
org.gradle.warning.mode=all org.gradle.warning.mode=all
+2 -2
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=de8f52ad49bdc759164f72439a3bf56ddb1589c4cde802d3cec7d6ad0e0ee410 distributionSha256Sum=b586e04868a22fd817c8971330fec37e298f3242eb85c374181b12d637f80302
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
+3 -2
View File
@@ -7,10 +7,11 @@ dependencies {
runtimeOnly(project(':jadx-plugins:jadx-dex-input')) runtimeOnly(project(':jadx-plugins:jadx-dex-input'))
runtimeOnly(project(':jadx-plugins:jadx-java-input')) runtimeOnly(project(':jadx-plugins:jadx-java-input'))
runtimeOnly(project(':jadx-plugins:jadx-java-convert'))
runtimeOnly(project(':jadx-plugins:jadx-smali-input')) runtimeOnly(project(':jadx-plugins:jadx-smali-input'))
implementation 'com.beust:jcommander:1.81' implementation 'com.beust:jcommander:1.82'
implementation 'ch.qos.logback:logback-classic:1.2.7' implementation 'ch.qos.logback:logback-classic:1.2.10'
} }
application { application {
@@ -14,6 +14,7 @@ import com.beust.jcommander.Parameter;
import jadx.api.CommentsLevel; import jadx.api.CommentsLevel;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum; import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
@@ -101,6 +102,13 @@ public class JadxCLIArgs {
@Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names") @Parameter(names = { "--deobf-parse-kotlin-metadata" }, description = "parse kotlin metadata to class and package names")
protected boolean deobfuscationParseKotlinMetadata = false; protected boolean deobfuscationParseKotlinMetadata = false;
@Parameter(
names = { "--use-kotlin-methods-for-var-names" },
description = "use kotlin intrinsic methods to rename variables, values: disable, apply, apply-and-hide",
converter = UseKotlinMethodsForVarNamesConverter.class
)
protected UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
@Parameter( @Parameter(
names = { "--rename-flags" }, names = { "--rename-flags" },
description = "fix options (comma-separated list of):" description = "fix options (comma-separated list of):"
@@ -125,9 +133,12 @@ public class JadxCLIArgs {
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)") @Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false; protected boolean fallbackMode = false;
@Parameter(names = { "--use-dx" }, description = "use dx/d8 to convert java bytecode")
protected boolean useDx = false;
@Parameter( @Parameter(
names = { "--comments-level" }, names = { "--comments-level" },
description = "set code comments level, values: error, warn, info, debug, user_only, none", description = "set code comments level, values: error, warn, info, debug, user-only, none",
converter = CommentsLevelConverter.class converter = CommentsLevelConverter.class
) )
protected CommentsLevel commentsLevel = CommentsLevel.INFO; protected CommentsLevel commentsLevel = CommentsLevel.INFO;
@@ -220,6 +231,7 @@ public class JadxCLIArgs {
args.setDeobfuscationMaxLength(deobfuscationMaxLength); args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias); args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata); args.setParseKotlinMetadata(deobfuscationParseKotlinMetadata);
args.setUseKotlinMethodsForVarNames(useKotlinMethodsForVarNames);
args.setEscapeUnicode(escapeUnicode); args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers); args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject); args.setExportAsGradleProject(exportAsGradleProject);
@@ -231,6 +243,7 @@ public class JadxCLIArgs {
args.setRenameFlags(renameFlags); args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive); args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel); args.setCommentsLevel(commentsLevel);
args.setUseDxInput(useDx);
return args; return args;
} }
@@ -266,6 +279,10 @@ public class JadxCLIArgs {
return fallbackMode; return fallbackMode;
} }
public boolean isUseDx() {
return useDx;
}
public boolean isShowInconsistentCode() { public boolean isShowInconsistentCode() {
return showInconsistentCode; return showInconsistentCode;
} }
@@ -318,6 +335,10 @@ public class JadxCLIArgs {
return deobfuscationParseKotlinMetadata; return deobfuscationParseKotlinMetadata;
} }
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public boolean isEscapeUnicode() { public boolean isEscapeUnicode() {
return escapeUnicode; return escapeUnicode;
} }
@@ -404,9 +425,22 @@ public class JadxCLIArgs {
} }
} }
public static class UseKotlinMethodsForVarNamesConverter implements IStringConverter<UseKotlinMethodsForVarNames> {
@Override
public UseKotlinMethodsForVarNames convert(String value) {
try {
return UseKotlinMethodsForVarNames.valueOf(value.replace('-', '_').toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: "
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
}
}
}
public static String enumValuesString(Enum<?>[] values) { public static String enumValuesString(Enum<?>[] values) {
return Stream.of(values) return Stream.of(values)
.map(v -> v.name().toLowerCase(Locale.ROOT)) .map(v -> v.name().replace('_', '-').toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", ")); .collect(Collectors.joining(", "));
} }
} }
@@ -39,6 +39,7 @@ public class ConvertToClsSet {
Path output = inputPaths.remove(0); Path output = inputPaths.remove(0);
JadxPluginManager pluginManager = new JadxPluginManager(); JadxPluginManager pluginManager = new JadxPluginManager();
pluginManager.load();
List<ILoadResult> loadedInputs = new ArrayList<>(); List<ILoadResult> loadedInputs = new ArrayList<>();
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) { for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
loadedInputs.add(inputPlugin.loadFiles(inputPaths)); loadedInputs.add(inputPlugin.loadFiles(inputPaths));
+1 -1
View File
@@ -1,5 +1,5 @@
plugins { plugins {
id 'java-library' id 'jadx-library'
} }
dependencies { dependencies {
+27 -1
View File
@@ -85,6 +85,14 @@ public class JadxArgs {
private CommentsLevel commentsLevel = CommentsLevel.INFO; private CommentsLevel commentsLevel = CommentsLevel.INFO;
private boolean useDxInput = false;
public enum UseKotlinMethodsForVarNames {
DISABLE, APPLY, APPLY_AND_HIDE
}
private UseKotlinMethodsForVarNames useKotlinMethodsForVarNames = UseKotlinMethodsForVarNames.APPLY;
public JadxArgs() { public JadxArgs() {
// use default options // use default options
} }
@@ -136,7 +144,7 @@ public class JadxArgs {
} }
public void setThreadsCount(int threadsCount) { public void setThreadsCount(int threadsCount) {
this.threadsCount = threadsCount; this.threadsCount = Math.max(1, threadsCount); // make sure threadsCount >= 1
} }
public boolean isCfgOutput() { public boolean isCfgOutput() {
@@ -423,6 +431,22 @@ public class JadxArgs {
this.commentsLevel = commentsLevel; this.commentsLevel = commentsLevel;
} }
public boolean isUseDxInput() {
return useDxInput;
}
public void setUseDxInput(boolean useDxInput) {
this.useDxInput = useDxInput;
}
public UseKotlinMethodsForVarNames getUseKotlinMethodsForVarNames() {
return useKotlinMethodsForVarNames;
}
public void setUseKotlinMethodsForVarNames(UseKotlinMethodsForVarNames useKotlinMethodsForVarNames) {
this.useKotlinMethodsForVarNames = useKotlinMethodsForVarNames;
}
@Override @Override
public String toString() { public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -442,6 +466,7 @@ public class JadxArgs {
+ ", deobfuscationForceSave=" + deobfuscationForceSave + ", deobfuscationForceSave=" + deobfuscationForceSave
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias + ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
+ ", parseKotlinMetadata=" + parseKotlinMetadata + ", parseKotlinMetadata=" + parseKotlinMetadata
+ ", useKotlinMethodsForVarNames=" + useKotlinMethodsForVarNames
+ ", deobfuscationMinLength=" + deobfuscationMinLength + ", deobfuscationMinLength=" + deobfuscationMinLength
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength + ", deobfuscationMaxLength=" + deobfuscationMaxLength
+ ", escapeUnicode=" + escapeUnicode + ", escapeUnicode=" + escapeUnicode
@@ -454,6 +479,7 @@ public class JadxArgs {
+ ", commentsLevel=" + commentsLevel + ", commentsLevel=" + commentsLevel
+ ", codeCache=" + codeCache + ", codeCache=" + codeCache
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName() + ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
+ ", useDxInput=" + useDxInput
+ '}'; + '}';
} }
} }
@@ -107,6 +107,7 @@ public final class JadxDecompiler implements Closeable {
reset(); reset();
JadxArgsValidator.validate(args); JadxArgsValidator.validate(args);
LOG.info("loading ..."); LOG.info("loading ...");
loadPlugins(args);
loadInputFiles(); loadInputFiles();
root = new RootNode(args); root = new RootNode(args);
@@ -159,6 +160,15 @@ public final class JadxDecompiler implements Closeable {
reset(); reset();
} }
private void loadPlugins(JadxArgs args) {
pluginManager.providesSuggestion("java-input", args.isUseDxInput() ? "java-convert" : "java-input");
pluginManager.load();
if (LOG.isDebugEnabled()) {
LOG.debug("Resolved plugins: {}", Utils.collectionMap(pluginManager.getResolvedPlugins(),
p -> p.getPluginInfo().getPluginId()));
}
}
public void registerPlugin(JadxPlugin plugin) { public void registerPlugin(JadxPlugin plugin) {
pluginManager.register(plugin); pluginManager.register(plugin);
} }
@@ -413,6 +423,10 @@ public final class JadxDecompiler implements Closeable {
classesMap.put(innerCls.getClassNode(), innerCls); classesMap.put(innerCls.getClassNode(), innerCls);
loadJavaClass(innerCls); loadJavaClass(innerCls);
} }
for (JavaClass inlinedCls : javaClass.getInlinedClasses()) {
classesMap.put(inlinedCls.getClassNode(), inlinedCls);
loadJavaClass(inlinedCls);
}
} }
/** /**
@@ -23,6 +23,7 @@ public final class JavaClass implements JavaNode {
private final JavaClass parent; private final JavaClass parent;
private List<JavaClass> innerClasses = Collections.emptyList(); private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaClass> inlinedClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList(); private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList(); private List<JavaMethod> methods = Collections.emptyList();
private boolean listsLoaded; private boolean listsLoaded;
@@ -100,13 +101,23 @@ public final class JavaClass implements JavaNode {
} }
this.innerClasses = Collections.unmodifiableList(list); this.innerClasses = Collections.unmodifiableList(list);
} }
int inlinedClsCount = cls.getInlinedClasses().size();
if (inlinedClsCount != 0) {
List<JavaClass> list = new ArrayList<>(inlinedClsCount);
for (ClassNode inner : cls.getInlinedClasses()) {
JavaClass javaClass = rootDecompiler.convertClassNode(inner);
javaClass.loadLists();
list.add(javaClass);
}
this.inlinedClasses = Collections.unmodifiableList(list);
}
int fieldsCount = cls.getFields().size(); int fieldsCount = cls.getFields().size();
if (fieldsCount != 0) { if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<>(fieldsCount); List<JavaField> flds = new ArrayList<>(fieldsCount);
for (FieldNode f : cls.getFields()) { for (FieldNode f : cls.getFields()) {
if (!f.contains(AFlag.DONT_GENERATE)) { if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(f, this); JavaField javaField = new JavaField(this, f);
flds.add(javaField); flds.add(javaField);
} }
} }
@@ -254,6 +265,11 @@ public final class JavaClass implements JavaNode {
return innerClasses; return innerClasses;
} }
public List<JavaClass> getInlinedClasses() {
loadLists();
return inlinedClasses;
}
public List<JavaField> getFields() { public List<JavaField> getFields() {
loadLists(); loadLists();
return fields; return fields;
@@ -13,7 +13,7 @@ public final class JavaField implements JavaNode {
private final FieldNode field; private final FieldNode field;
private final JavaClass parent; private final JavaClass parent;
JavaField(FieldNode f, JavaClass cls) { JavaField(JavaClass cls, FieldNode f) {
this.field = f; this.field = f;
this.parent = cls; this.parent = cls;
} }
@@ -37,6 +37,10 @@ public class ResourceFile {
private ZipRef zipRef; private ZipRef zipRef;
private String deobfName; private String deobfName;
public static ResourceFile createResourceFile(JadxDecompiler decompiler, File file, ResourceType type) {
return new ResourceFile(decompiler, file.getAbsolutePath(), type);
}
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) { public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) { if (!ZipSecurity.isValidZipEntryName(name)) {
return null; return null;
@@ -41,7 +41,7 @@ public enum ResourceType {
} }
public static ResourceType getFileType(String fileName) { public static ResourceType getFileType(String fileName) {
if (fileName.matches("[^/]+/resources.pb")) { if (fileName.endsWith("/resources.pb")) {
return ARSC; return ARSC;
} }
int dot = fileName.lastIndexOf('.'); int dot = fileName.lastIndexOf('.');
@@ -145,16 +145,8 @@ public final class ResourcesLoader {
return null; return null;
}); });
} else { } else {
addResourceFile(list, file); ResourceType type = ResourceType.getFileType(file.getAbsolutePath());
} list.add(ResourceFile.createResourceFile(jadxRef, file, type));
}
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
list.add(rf);
} }
} }
@@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel; import jadx.api.CommentsLevel;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.core.dex.visitors.AnonymousClassVisitor;
import jadx.core.dex.visitors.AttachCommentsVisitor; import jadx.core.dex.visitors.AttachCommentsVisitor;
import jadx.core.dex.visitors.AttachMethodDetails; import jadx.core.dex.visitors.AttachMethodDetails;
import jadx.core.dex.visitors.AttachTryCatchVisitor; import jadx.core.dex.visitors.AttachTryCatchVisitor;
@@ -37,6 +38,7 @@ import jadx.core.dex.visitors.OverrideMethodVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen; import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ProcessAnonymous; import jadx.core.dex.visitors.ProcessAnonymous;
import jadx.core.dex.visitors.ProcessInstructionsVisitor; import jadx.core.dex.visitors.ProcessInstructionsVisitor;
import jadx.core.dex.visitors.ProcessMethodsForInline;
import jadx.core.dex.visitors.ReSugarCode; import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.ShadowFieldVisitor; import jadx.core.dex.visitors.ShadowFieldVisitor;
import jadx.core.dex.visitors.SignatureProcessor; import jadx.core.dex.visitors.SignatureProcessor;
@@ -46,6 +48,7 @@ import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor; import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor; import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor; import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
import jadx.core.dex.visitors.regions.CheckRegions; import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions; import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor; import jadx.core.dex.visitors.regions.IfRegionVisitor;
@@ -88,6 +91,7 @@ public class Jadx {
passes.add(new RenameVisitor()); passes.add(new RenameVisitor());
passes.add(new UsageInfoVisitor()); passes.add(new UsageInfoVisitor());
passes.add(new ProcessAnonymous()); passes.add(new ProcessAnonymous());
passes.add(new ProcessMethodsForInline());
return passes; return passes;
} }
@@ -128,6 +132,9 @@ public class Jadx {
if (args.isDebugInfo()) { if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor()); passes.add(new DebugInfoApplyVisitor());
} }
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
passes.add(new ProcessKotlinInternals());
}
passes.add(new CodeRenameVisitor()); passes.add(new CodeRenameVisitor());
if (args.isInlineMethods()) { if (args.isInlineMethods()) {
passes.add(new InlineMethods()); passes.add(new InlineMethods());
@@ -135,6 +142,7 @@ public class Jadx {
passes.add(new GenericTypesVisitor()); passes.add(new GenericTypesVisitor());
passes.add(new ShadowFieldVisitor()); passes.add(new ShadowFieldVisitor());
passes.add(new DeboxingVisitor()); passes.add(new DeboxingVisitor());
passes.add(new AnonymousClassVisitor());
passes.add(new ModVisitor()); passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor()); passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode()); passes.add(new ReSugarCode());
@@ -1,10 +1,5 @@
package jadx.core; package jadx.core;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -94,21 +89,13 @@ public final class ProcessClass {
return generateCode(topParentClass); return generateCode(topParentClass);
} }
try { try {
Set<ClassNode> useIn = new HashSet<>(cls.getUseIn());
List<ClassNode> usedInDeps = new ArrayList<>();
for (ClassNode depCls : cls.getDependencies()) { for (ClassNode depCls : cls.getDependencies()) {
if (useIn.contains(depCls)) { process(depCls, false);
// postpone to resolve cross dependencies
usedInDeps.add(depCls);
} else {
process(depCls, false);
}
} }
if (!usedInDeps.isEmpty()) { if (!cls.getCodegenDeps().isEmpty()) {
// process current class before its usage
process(cls, false); process(cls, false);
for (ClassNode depCls : usedInDeps) { for (ClassNode codegenDep : cls.getCodegenDeps()) {
process(depCls, false); process(codegenDep, false);
} }
} }
ICodeInfo code = process(cls, true); ICodeInfo code = process(cls, true);
@@ -42,7 +42,7 @@ public class ConditionGen extends InsnGen {
super(insnGen.mgen, insnGen.fallback); super(insnGen.mgen, insnGen.fallback);
} }
void add(ICodeWriter code, IfCondition condition) throws CodegenException { public void add(ICodeWriter code, IfCondition condition) throws CodegenException {
add(code, new CondStack(), condition); add(code, new CondStack(), condition);
} }
@@ -15,7 +15,6 @@ import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.data.annotations.VarDeclareRef; import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.data.annotations.VarRef; import jadx.api.data.annotations.VarRef;
import jadx.api.plugins.input.data.MethodHandleType; import jadx.api.plugins.input.data.MethodHandleType;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
@@ -112,7 +111,7 @@ public class InsnGen {
} }
code.add(mgen.getNameGen().useArg(reg)); code.add(mgen.getNameGen().useArg(reg));
} else if (arg.isLiteral()) { } else if (arg.isLiteral()) {
code.add(lit((LiteralArg) arg)); addLiteralArg(code, (LiteralArg) arg, flags);
} else if (arg.isInsnWrap()) { } else if (arg.isInsnWrap()) {
addWrappedArg(code, (InsnWrapArg) arg, flags); addWrappedArg(code, (InsnWrapArg) arg, flags);
} else if (arg.isNamed()) { } else if (arg.isNamed()) {
@@ -122,6 +121,15 @@ public class InsnGen {
} }
} }
private void addLiteralArg(ICodeWriter code, LiteralArg litArg, Set<Flags> flags) {
String literalStr = lit(litArg);
if (!flags.contains(Flags.BODY_ONLY_NOWRAP) && literalStr.startsWith("-")) {
code.add('(').add(literalStr).add(')');
} else {
code.add(literalStr);
}
}
private void addWrappedArg(ICodeWriter code, InsnWrapArg arg, Set<Flags> flags) throws CodegenException { private void addWrappedArg(ICodeWriter code, InsnWrapArg arg, Set<Flags> flags) throws CodegenException {
InsnNode wrapInsn = arg.getWrapInsn(); InsnNode wrapInsn = arg.getWrapInsn();
if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) { if (wrapInsn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
@@ -717,14 +725,7 @@ public class InsnGen {
throw new CodegenException("Anonymous inner class unlimited recursion detected." throw new CodegenException("Anonymous inner class unlimited recursion detected."
+ " Convert class to inner: " + cls.getClassInfo().getFullName()); + " Convert class to inner: " + cls.getClassInfo().getFullName());
} }
ArgType parent = cls.get(AType.ANONYMOUS_CLASS_BASE).getBaseType();
cls.add(AFlag.DONT_GENERATE);
ArgType parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
parent = cls.getSuperClass();
}
// hide empty anonymous constructors // hide empty anonymous constructors
for (MethodNode ctor : cls.getMethods()) { for (MethodNode ctor : cls.getMethods()) {
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR) if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
@@ -732,14 +733,22 @@ public class InsnGen {
ctor.add(AFlag.DONT_GENERATE); ctor.add(AFlag.DONT_GENERATE);
} }
} }
code.add("new "); code.add("new ");
if (parent == null) { useClass(code, parent);
code.add("Object");
} else {
useClass(code, parent);
}
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth()); MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
if (callMth != null) {
// copy var names
List<RegisterArg> mthArgs = callMth.getArgRegs();
int argsCount = Math.min(insn.getArgsCount(), mthArgs.size());
for (int i = 0; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
if (arg.isRegister()) {
RegisterArg mthArg = mthArgs.get(i);
RegisterArg insnArg = (RegisterArg) arg;
mthArg.getSVar().setCodeVar(insnArg.getSVar().getCodeVar());
}
}
}
generateMethodArguments(code, insn, 0, callMth); generateMethodArguments(code, insn, 0, callMth);
code.add(' '); code.add(' ');
@@ -763,8 +772,7 @@ public class InsnGen {
case VIRTUAL: case VIRTUAL:
case INTERFACE: case INTERFACE:
InsnArg arg = insn.getArg(0); InsnArg arg = insn.getArg(0);
// FIXME: add 'this' for equals methods in scope if (needInvokeArg(arg)) {
if (!arg.isThis()) {
addArgDot(code, arg); addArgDot(code, arg);
} }
k++; k++;
@@ -799,6 +807,20 @@ public class InsnGen {
generateMethodArguments(code, insn, k, callMthNode); generateMethodArguments(code, insn, k, callMthNode);
} }
// FIXME: add 'this' for equals methods in scope
private boolean needInvokeArg(InsnArg arg) {
if (arg.isAnyThis()) {
if (arg.isThis()) {
return false;
}
ClassNode clsNode = mth.root().resolveClass(arg.getType());
if (clsNode != null && clsNode.contains(AFlag.DONT_GENERATE)) {
return false;
}
}
return true;
}
private void makeInvokeLambda(ICodeWriter code, InvokeCustomNode customNode) throws CodegenException { private void makeInvokeLambda(ICodeWriter code, InvokeCustomNode customNode) throws CodegenException {
if (customNode.isUseRef()) { if (customNode.isUseRef()) {
makeRefLambda(code, customNode); makeRefLambda(code, customNode);
@@ -1016,7 +1038,6 @@ public class InsnGen {
} else { } else {
condGen.wrap(code, insn.getCondition()); condGen.wrap(code, insn.getCondition());
code.add(" ? "); code.add(" ? ");
addCastIfNeeded(code, first, second);
addArg(code, first, false); addArg(code, first, false);
code.add(" : "); code.add(" : ");
addArg(code, second, false); addArg(code, second, false);
@@ -1026,33 +1047,6 @@ public class InsnGen {
} }
} }
private void addCastIfNeeded(ICodeWriter code, InsnArg first, InsnArg second) {
if (first.isLiteral() && second.isLiteral()) {
if (first.getType() == ArgType.BYTE) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (lit1 != Byte.MAX_VALUE && lit1 != Byte.MIN_VALUE
&& lit2 != Byte.MAX_VALUE && lit2 != Byte.MIN_VALUE) {
code.add("(byte) ");
}
} else if (first.getType() == ArgType.SHORT) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (lit1 != Short.MAX_VALUE && lit1 != Short.MIN_VALUE
&& lit2 != Short.MAX_VALUE && lit2 != Short.MIN_VALUE) {
code.add("(short) ");
}
} else if (first.getType() == ArgType.CHAR) {
long lit1 = ((LiteralArg) first).getLiteral();
long lit2 = ((LiteralArg) second).getLiteral();
if (!NameMapper.isPrintableChar((char) (lit1))
&& !NameMapper.isPrintableChar((char) (lit2))) {
code.add("(char) ");
}
}
}
}
private void makeArith(ArithNode insn, ICodeWriter code, Set<Flags> state) throws CodegenException { private void makeArith(ArithNode insn, ICodeWriter code, Set<Flags> state) throws CodegenException {
if (insn.contains(AFlag.ARITH_ONEARG)) { if (insn.contains(AFlag.ARITH_ONEARG)) {
makeArithOneArg(insn, code); makeArithOneArg(insn, code);
@@ -111,7 +111,18 @@ public class MethodGen {
CodeGenUtils.addRenamedComment(code, mth, mth.getName()); CodeGenUtils.addRenamedComment(code, mth, mth.getName());
} }
if (mth.contains(AFlag.INCONSISTENT_CODE) && mth.checkCommentsLevel(CommentsLevel.ERROR)) { if (mth.contains(AFlag.INCONSISTENT_CODE) && mth.checkCommentsLevel(CommentsLevel.ERROR)) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump */"); code.startLine("/*");
code.incIndent();
code.startLine("Code decompiled incorrectly, please refer to instructions dump.");
if (!mth.root().getArgs().isShowInconsistentCode()) {
if (code.isMetadataSupported()) {
code.startLine("To view partially-correct code enable 'Show inconsistent code' option in preferences");
} else {
code.startLine("To view partially-correct add '--show-bad-code' argument");
}
}
code.decIndent();
code.startLine("*/");
} }
code.startLineWithNum(mth.getSourceLine()); code.startLineWithNum(mth.getSourceLine());
@@ -169,7 +180,7 @@ public class MethodGen {
if (overrideAttr == null) { if (overrideAttr == null) {
return; return;
} }
if (!overrideAttr.isAtBaseMth()) { if (!overrideAttr.getBaseMethods().contains(mth)) {
code.startLine("@Override"); code.startLine("@Override");
if (mth.checkCommentsLevel(CommentsLevel.INFO)) { if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
code.add(" // "); code.add(" // ");
@@ -314,7 +325,14 @@ public class MethodGen {
.filter(insn -> insn.getType() != InsnType.NOP) .filter(insn -> insn.getType() != InsnType.NOP)
.count(); .count();
if (insnCountEstimate > 100) { if (insnCountEstimate > 100) {
code.startLine("// Method dump skipped, instructions count: " + insnArr.length); code.incIndent();
code.startLine("Method dump skipped, instructions count: " + insnArr.length);
if (code.isMetadataSupported()) {
code.startLine("To view this dump change 'Code comments level' option to 'DEBUG'");
} else {
code.startLine("To view this dump add '--comments-level debug' option");
}
code.decIndent();
return; return;
} }
} }
@@ -162,8 +162,7 @@ public class NameGen {
InsnNode assignInsn = assignArg.getParentInsn(); InsnNode assignInsn = assignArg.getParentInsn();
if (assignInsn != null) { if (assignInsn != null) {
String name = makeNameFromInsn(assignInsn); String name = makeNameFromInsn(assignInsn);
if (name != null && !NameMapper.isReserved(name)) { if (name != null && NameMapper.isValidAndPrintable(name)) {
assignArg.setName(name);
return name; return name;
} }
} }
@@ -202,7 +201,11 @@ public class NameGen {
return vName; return vName;
} }
if (shortName != null) { if (shortName != null) {
return StringUtils.escape(shortName.toLowerCase()); String lower = StringUtils.escape(shortName.toLowerCase());
if (shortName.equals(lower)) {
return lower + "Var";
}
return lower;
} }
} }
return StringUtils.escape(type.toString()); return StringUtils.escape(type.toString());
@@ -161,7 +161,7 @@ public class RegionGen extends InsnGen {
} }
public void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException { public void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException {
code.startLineWithNum(region.getConditionSourceLine()); code.startLineWithNum(region.getSourceLine());
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL); LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
if (labelAttr != null) { if (labelAttr != null) {
code.add(mgen.getNameGen().getLoopLabel(labelAttr)).add(": "); code.add(mgen.getNameGen().getLoopLabel(labelAttr)).add(": ");
@@ -213,7 +213,7 @@ public class RegionGen extends InsnGen {
code.add("do {"); code.add("do {");
CodeGenUtils.addCodeComments(code, mth, condInsn); CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody()); makeRegionIndent(code, region.getBody());
code.startLineWithNum(region.getConditionSourceLine()); code.startLineWithNum(region.getSourceLine());
code.add("} while ("); code.add("} while (");
conditionGen.add(code, condition); conditionGen.add(code, condition);
code.add(");"); code.add(");");
@@ -58,7 +58,7 @@ public class DeobfPresets {
if (inputFiles.isEmpty()) { if (inputFiles.isEmpty()) {
return null; return null;
} }
Path inputFilePath = inputFiles.get(0).getAbsoluteFile().toPath(); Path inputFilePath = inputFiles.get(0).toPath().toAbsolutePath();
String baseName = FileUtils.getPathBaseName(inputFilePath); String baseName = FileUtils.getPathBaseName(inputFilePath);
return inputFilePath.getParent().resolve(baseName + ".jobf"); return inputFilePath.getParent().resolve(baseName + ".jobf");
} }
@@ -143,19 +143,15 @@ public class NameMapper {
/** /**
* Return modified string with removed: * Return modified string with removed:
* <p>
* <ul> * <ul>
* <li>not printable chars (including unicode) * <li>not printable chars (including unicode)
* <li>chars not valid for java identifier part * <li>chars not valid for java identifier part
* </ul> * </ul>
* <p>
* Note: this 'middle' method must be used with prefixed string: * Note: this 'middle' method must be used with prefixed string:
* <p>
* <ul> * <ul>
* <li>can leave invalid chars for java identifier start (i.e numbers) * <li>can leave invalid chars for java identifier start (i.e numbers)
* <li>result not checked for reserved words * <li>result not checked for reserved words
* </ul> * </ul>
* <p>
*/ */
public static String removeInvalidCharsMiddle(String name) { public static String removeInvalidCharsMiddle(String name) {
if (isValidIdentifier(name) && isAllCharsPrintable(name)) { if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
@@ -33,6 +33,7 @@ public enum AFlag {
SKIP_FIRST_ARG, SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call SKIP_ARG, // skip argument in invoke call
NO_SKIP_ARGS,
ANONYMOUS_CONSTRUCTOR, ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS, ANONYMOUS_CLASS,
@@ -78,6 +79,8 @@ public enum AFlag {
REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again
RERUN_SSA_TRANSFORM, RERUN_SSA_TRANSFORM,
METHOD_CANDIDATE_FOR_INLINE,
// Class processing flags // Class processing flags
RESTART_CODEGEN, // codegen must be executed again RESTART_CODEGEN, // codegen must be executed again
RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage
@@ -2,6 +2,7 @@ package jadx.core.dex.attributes;
import jadx.api.plugins.input.data.attributes.IJadxAttrType; import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute; import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr; import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EdgeInsnAttr; import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
@@ -16,6 +17,7 @@ import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr; import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr; import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr; import jadx.core.dex.attributes.nodes.MethodTypeVarsAttr;
@@ -51,6 +53,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>(); public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>(); public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>(); public static final AType<ClassTypeVarsAttr> CLASS_TYPE_VARS = new AType<>();
public static final AType<AnonymousClassBaseAttr> ANONYMOUS_CLASS_BASE = new AType<>();
// field // field
public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>(); public static final AType<FieldInitInsnAttr> FIELD_INIT_INSN = new AType<>();
@@ -63,6 +66,7 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>(); public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>(); public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>(); public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>();
public static final AType<MethodBridgeAttr> BRIDGED_BY = new AType<>();
// region // region
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>(); public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
@@ -0,0 +1,28 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.args.ArgType;
public class AnonymousClassBaseAttr extends PinnedAttribute {
private final ArgType baseType;
public AnonymousClassBaseAttr(ArgType baseType) {
this.baseType = baseType;
}
public ArgType getBaseType() {
return baseType;
}
@Override
public AType<AnonymousClassBaseAttr> getAttrType() {
return AType.ANONYMOUS_CLASS_BASE;
}
@Override
public String toString() {
return "AnonymousClassBaseAttr{" + baseType + '}';
}
}
@@ -11,6 +11,7 @@ import jadx.api.CommentsLevel;
import jadx.api.plugins.input.data.attributes.IJadxAttrType; import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute; import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.utils.Utils;
public class JadxCommentsAttr implements IJadxAttribute { public class JadxCommentsAttr implements IJadxAttribute {
@@ -44,4 +45,12 @@ public class JadxCommentsAttr implements IJadxAttribute {
public IJadxAttrType<JadxCommentsAttr> getAttrType() { public IJadxAttrType<JadxCommentsAttr> getAttrType() {
return AType.JADX_COMMENTS; return AType.JADX_COMMENTS;
} }
@Override
public String toString() {
return "JadxCommentsAttr{\n "
+ Utils.listToString(comments.entrySet(), "\n ",
e -> e.getKey() + ": \n -> " + Utils.listToString(e.getValue(), "\n -> "))
+ '}';
}
} }
@@ -0,0 +1,28 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.MethodNode;
public class MethodBridgeAttr extends PinnedAttribute {
private final MethodNode bridgeMth;
public MethodBridgeAttr(MethodNode bridgeMth) {
this.bridgeMth = bridgeMth;
}
public MethodNode getBridgeMth() {
return bridgeMth;
}
@Override
public AType<MethodBridgeAttr> getAttrType() {
return AType.BRIDGED_BY;
}
@Override
public String toString() {
return "BRIDGED_BY: " + bridgeMth;
}
}
@@ -1,6 +1,7 @@
package jadx.core.dex.attributes.nodes; package jadx.core.dex.attributes.nodes;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import jadx.api.plugins.input.data.attributes.PinnedAttribute; import jadx.api.plugins.input.data.attributes.PinnedAttribute;
@@ -20,27 +21,26 @@ public class MethodOverrideAttr extends PinnedAttribute {
*/ */
private SortedSet<MethodNode> relatedMthNodes; private SortedSet<MethodNode> relatedMthNodes;
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes) { private Set<IMethodDetails> baseMethods;
public MethodOverrideAttr(List<IMethodDetails> overrideList, SortedSet<MethodNode> relatedMthNodes, Set<IMethodDetails> baseMethods) {
this.overrideList = overrideList; this.overrideList = overrideList;
this.relatedMthNodes = relatedMthNodes; this.relatedMthNodes = relatedMthNodes;
} this.baseMethods = baseMethods;
public boolean isAtBaseMth() {
return overrideList.isEmpty();
} }
public List<IMethodDetails> getOverrideList() { public List<IMethodDetails> getOverrideList() {
return overrideList; return overrideList;
} }
public void setOverrideList(List<IMethodDetails> overrideList) {
this.overrideList = overrideList;
}
public SortedSet<MethodNode> getRelatedMthNodes() { public SortedSet<MethodNode> getRelatedMthNodes() {
return relatedMthNodes; return relatedMthNodes;
} }
public Set<IMethodDetails> getBaseMethods() {
return baseMethods;
}
public void setRelatedMthNodes(SortedSet<MethodNode> relatedMthNodes) { public void setRelatedMthNodes(SortedSet<MethodNode> relatedMthNodes) {
this.relatedMthNodes = relatedMthNodes; this.relatedMthNodes = relatedMthNodes;
} }
@@ -52,6 +52,6 @@ public class MethodOverrideAttr extends PinnedAttribute {
@Override @Override
public String toString() { public String toString() {
return "METHOD_OVERRIDE: " + overrideList; return "METHOD_OVERRIDE: " + getBaseMethods();
} }
} }
@@ -437,7 +437,9 @@ public class InsnDecoder {
case INVOKE_VIRTUAL: case INVOKE_VIRTUAL:
return invoke(insn, InvokeType.VIRTUAL, false); return invoke(insn, InvokeType.VIRTUAL, false);
case INVOKE_CUSTOM: case INVOKE_CUSTOM:
return invoke(insn, InvokeType.CUSTOM, false); return invokeCustom(insn, false);
case INVOKE_SPECIAL:
return invokeSpecial(insn);
case INVOKE_DIRECT_RANGE: case INVOKE_DIRECT_RANGE:
return invoke(insn, InvokeType.DIRECT, true); return invoke(insn, InvokeType.DIRECT, true);
@@ -448,7 +450,7 @@ public class InsnDecoder {
case INVOKE_VIRTUAL_RANGE: case INVOKE_VIRTUAL_RANGE:
return invoke(insn, InvokeType.VIRTUAL, true); return invoke(insn, InvokeType.VIRTUAL, true);
case INVOKE_CUSTOM_RANGE: case INVOKE_CUSTOM_RANGE:
return invoke(insn, InvokeType.CUSTOM, true); return invokeCustom(insn, true);
case NEW_INSTANCE: case NEW_INSTANCE:
ArgType clsType = ArgType.parse(insn.getIndexAsType()); ArgType clsType = ArgType.parse(insn.getIndexAsType());
@@ -506,7 +508,17 @@ public class InsnDecoder {
private InsnNode makeNewArray(InsnData insn) { private InsnNode makeNewArray(InsnData insn) {
ArgType indexType = ArgType.parse(insn.getIndexAsType()); ArgType indexType = ArgType.parse(insn.getIndexAsType());
int dim = (int) insn.getLiteral(); int dim = (int) insn.getLiteral();
ArgType arrType = dim == 0 ? indexType : ArgType.array(indexType, dim); ArgType arrType;
if (dim == 0) {
arrType = indexType;
} else {
if (indexType.isArray()) {
// java bytecode can pass array as a base type
arrType = indexType;
} else {
arrType = ArgType.array(indexType, dim);
}
}
int regsCount = insn.getRegsCount(); int regsCount = insn.getRegsCount();
NewArrayNode newArr = new NewArrayNode(arrType, regsCount - 1); NewArrayNode newArr = new NewArrayNode(arrType, regsCount - 1);
newArr.setResult(InsnArg.reg(insn, 0, arrType)); newArr.setResult(InsnArg.reg(insn, 0, arrType));
@@ -565,10 +577,27 @@ public class InsnDecoder {
return inode; return inode;
} }
private InsnNode invoke(InsnData insn, InvokeType type, boolean isRange) { private InsnNode invokeCustom(InsnData insn, boolean isRange) {
if (type == InvokeType.CUSTOM) { return InvokeCustomBuilder.build(method, insn, isRange);
return InvokeCustomBuilder.build(method, insn, isRange); }
private InsnNode invokeSpecial(InsnData insn) {
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
if (mthRef == null) {
throw new JadxRuntimeException("Failed to load method reference for insn: " + insn);
} }
MethodInfo mthInfo = MethodInfo.fromRef(root, mthRef);
// convert 'special' to 'direct/super' same as dx
InvokeType type;
if (mthInfo.isConstructor() || Objects.equals(mthInfo.getDeclClass(), method.getParentClass().getClassInfo())) {
type = InvokeType.DIRECT;
} else {
type = InvokeType.SUPER;
}
return new InvokeNode(mthInfo, insn, type, false);
}
private InsnNode invoke(InsnData insn, InvokeType type, boolean isRange) {
IMethodRef mthRef = InsnDataUtils.getMethodRef(insn); IMethodRef mthRef = InsnDataUtils.getMethodRef(insn);
if (mthRef == null) { if (mthRef == null) {
throw new JadxRuntimeException("Failed to load method reference for insn: " + insn); throw new JadxRuntimeException("Failed to load method reference for insn: " + insn);
@@ -655,7 +655,7 @@ public abstract class ArgType {
if (from.equals(to)) { if (from.equals(to)) {
return false; return false;
} }
TypeCompareEnum result = root.getTypeUpdate().getTypeCompare().compareTypes(from, to); TypeCompareEnum result = root.getTypeCompare().compareTypes(from, to);
return !result.isNarrow(); return !result.isNarrow();
} }
@@ -232,6 +232,27 @@ public abstract class InsnArg extends Typed {
return contains(AFlag.THIS); return contains(AFlag.THIS);
} }
/**
* Return true for 'this' from other classes (often occur in anonymous classes)
*/
public boolean isAnyThis() {
if (contains(AFlag.THIS)) {
return true;
}
InsnNode wrappedInsn = unwrap();
if (wrappedInsn != null && wrappedInsn.getType() == InsnType.IGET) {
return wrappedInsn.getArg(0).isAnyThis();
}
return false;
}
public InsnNode unwrap() {
if (isInsnWrap()) {
return ((InsnWrapArg) this).getWrapInsn();
}
return null;
}
public boolean isConst() { public boolean isConst() {
return isLiteral() || (isInsnWrap() && ((InsnWrapArg) this).getWrapInsn().isConstInsn()); return isLiteral() || (isInsnWrap() && ((InsnWrapArg) this).getWrapInsn().isConstInsn());
} }
@@ -1,5 +1,7 @@
package jadx.core.dex.instructions.args; package jadx.core.dex.instructions.args;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.TypeGen; import jadx.core.codegen.TypeGen;
import jadx.core.utils.StringUtils; import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -57,12 +59,48 @@ public final class LiteralArg extends InsnArg {
} }
public boolean isInteger() { public boolean isInteger() {
PrimitiveType type = this.type.getPrimitiveType(); switch (type.getPrimitiveType()) {
return type == PrimitiveType.INT case INT:
|| type == PrimitiveType.BYTE case BYTE:
|| type == PrimitiveType.CHAR case CHAR:
|| type == PrimitiveType.SHORT case SHORT:
|| type == PrimitiveType.LONG; case LONG:
return true;
default:
return false;
}
}
public boolean isNegative() {
if (isInteger()) {
return literal < 0;
}
if (type == ArgType.FLOAT) {
float val = Float.intBitsToFloat(((int) literal));
return val < 0 && Float.isFinite(val);
}
if (type == ArgType.DOUBLE) {
double val = Double.longBitsToDouble(literal);
return val < 0 && Double.isFinite(val);
}
return false;
}
@Nullable
public LiteralArg negate() {
long neg;
if (isInteger()) {
neg = -literal;
} else if (type == ArgType.FLOAT) {
float val = Float.intBitsToFloat(((int) literal));
neg = Float.floatToIntBits(-val);
} else if (type == ArgType.DOUBLE) {
double val = Double.longBitsToDouble(literal);
neg = Double.doubleToLongBits(-val);
} else {
return null;
}
return new LiteralArg(neg, type);
} }
@Override @Override
@@ -59,6 +59,11 @@ public class SSAVar {
return assign; return assign;
} }
@Nullable
public InsnNode getAssignInsn() {
return assign.getParentInsn();
}
public void setAssign(@NotNull RegisterArg assign) { public void setAssign(@NotNull RegisterArg assign) {
this.assign = assign; this.assign = assign;
} }
@@ -47,7 +47,6 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.LOADED; import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> { public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
@@ -79,6 +78,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
* Top level classes used in this class (only for top level classes, empty for inners) * Top level classes used in this class (only for top level classes, empty for inners)
*/ */
private List<ClassNode> dependencies = Collections.emptyList(); private List<ClassNode> dependencies = Collections.emptyList();
/**
* Top level classes needed for code generation stage
*/
private List<ClassNode> codegenDeps = Collections.emptyList();
/** /**
* Classes which uses this class * Classes which uses this class
*/ */
@@ -108,6 +111,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
ListConsumer<IFieldData, FieldNode> fieldsConsumer = new ListConsumer<>(fld -> FieldNode.build(this, fld)); ListConsumer<IFieldData, FieldNode> fieldsConsumer = new ListConsumer<>(fld -> FieldNode.build(this, fld));
ListConsumer<IMethodData, MethodNode> methodsConsumer = new ListConsumer<>(mth -> MethodNode.build(this, mth)); ListConsumer<IMethodData, MethodNode> methodsConsumer = new ListConsumer<>(mth -> MethodNode.build(this, mth));
cls.visitFieldsAndMethods(fieldsConsumer, methodsConsumer); cls.visitFieldsAndMethods(fieldsConsumer, methodsConsumer);
if (this.fields != null && this.methods != null) {
// TODO: temporary solution for restore usage info in reloaded methods and fields
restoreUsageData(this.fields, this.methods, fieldsConsumer.getResult(), methodsConsumer.getResult());
}
this.fields = fieldsConsumer.getResult(); this.fields = fieldsConsumer.getResult();
this.methods = methodsConsumer.getResult(); this.methods = methodsConsumer.getResult();
@@ -124,6 +131,24 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
} }
} }
private void restoreUsageData(List<FieldNode> oldFields, List<MethodNode> oldMethods,
List<FieldNode> newFields, List<MethodNode> newMethods) {
Map<FieldInfo, FieldNode> oldFieldMap = Utils.groupBy(oldFields, FieldNode::getFieldInfo);
for (FieldNode newField : newFields) {
FieldNode oldField = oldFieldMap.get(newField.getFieldInfo());
if (oldField != null) {
newField.setUseIn(oldField.getUseIn());
}
}
Map<MethodInfo, MethodNode> oldMethodsMap = Utils.groupBy(oldMethods, MethodNode::getMethodInfo);
for (MethodNode newMethod : newMethods) {
MethodNode oldMethod = oldMethodsMap.get(newMethod.getMethodInfo());
if (oldMethod != null) {
newMethod.setUseIn(oldMethod.getUseIn());
}
}
}
private ArgType checkSuperType(IClassData cls) { private ArgType checkSuperType(IClassData cls) {
String superType = cls.getSuperType(); String superType = cls.getSuperType();
if (superType == null) { if (superType == null) {
@@ -261,12 +286,15 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return true; return true;
} }
public boolean checkProcessed() {
return getTopParentClass().getState().isProcessComplete();
}
public void ensureProcessed() { public void ensureProcessed() {
ClassNode topClass = getTopParentClass(); if (!checkProcessed()) {
ProcessState state = topClass.getState(); ClassNode topParentClass = getTopParentClass();
if (state != PROCESS_COMPLETE) {
throw new JadxRuntimeException("Expected class to be processed at this point," throw new JadxRuntimeException("Expected class to be processed at this point,"
+ " class: " + topClass + ", state: " + state); + " class: " + topParentClass + ", state: " + topParentClass.getState());
} }
} }
@@ -535,6 +563,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return innerClasses; return innerClasses;
} }
public List<ClassNode> getInlinedClasses() {
return inlinedClasses;
}
/** /**
* Get all inner and inlined classes recursively * Get all inner and inlined classes recursively
* *
@@ -707,6 +739,14 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
this.dependencies = dependencies; this.dependencies = dependencies;
} }
public List<ClassNode> getCodegenDeps() {
return codegenDeps;
}
public void setCodegenDeps(List<ClassNode> codegenDeps) {
this.codegenDeps = codegenDeps;
}
public List<ClassNode> getUseIn() { public List<ClassNode> getUseIn() {
return useIn; return useIn;
} }
@@ -0,0 +1,26 @@
package jadx.core.dex.nodes;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.regions.conditions.IfCondition;
public interface IConditionRegion extends IRegion {
@Nullable
IfCondition getCondition();
/**
* Blocks merged into condition
* Needed for backtracking
* TODO: merge into condition object ???
*/
List<BlockNode> getConditionBlocks();
void invertCondition();
boolean simplifyCondition();
int getConditionSourceLine();
}
@@ -120,12 +120,7 @@ public class InsnNode extends LineAttrNode {
if (getArgsCount() == 0) { if (getArgsCount() == 0) {
return false; return false;
} }
for (InsnArg insnArg : arguments) { return InsnUtils.containsVar(arguments, arg);
if (insnArg == arg || arg.sameRegAndSVar(insnArg)) {
return true;
}
}
return false;
} }
/** /**
@@ -305,6 +300,20 @@ public class InsnNode extends LineAttrNode {
} }
} }
/**
* Visit all args recursively (including inner instructions),
* but excluding wrapped args
*/
public void visitArgs(Consumer<InsnArg> visitor) {
for (InsnArg arg : getArguments()) {
if (arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().visitArgs(visitor);
} else {
visitor.accept(arg);
}
}
}
/** /**
* Visit this instruction and all inner (wrapped) instructions * Visit this instruction and all inner (wrapped) instructions
* To terminate visiting return non-null value * To terminate visiting return non-null value
@@ -390,17 +399,16 @@ public class InsnNode extends LineAttrNode {
/** /**
* Make copy of InsnNode object. * Make copy of InsnNode object.
* <p> * <br>
* NOTE: can't copy instruction with result argument * NOTE: can't copy instruction with result argument
* (SSA variable can't be used in two different assigns). * (SSA variable can't be used in two different assigns).
* <p> * <br>
* Prefer use next methods: * Prefer use next methods:
* <ul> * <ul>
* <li>{@link #copyWithoutResult()} to explicitly state that result not needed * <li>{@link #copyWithoutResult()} to explicitly state that result not needed
* <li>{@link #copy(RegisterArg)} to provide new result arg * <li>{@link #copy(RegisterArg)} to provide new result arg
* <li>{@link #copyWithNewSsaVar(MethodNode)} to make new SSA variable for result arg * <li>{@link #copyWithNewSsaVar(MethodNode)} to make new SSA variable for result arg
* </ul> * </ul>
* <p>
*/ */
public InsnNode copy() { public InsnNode copy() {
if (this.getClass() != InsnNode.class) { if (this.getClass() != InsnNode.class) {
@@ -334,6 +334,12 @@ public class RootNode {
return resolveClass(clsInfo); return resolveClass(clsInfo);
} }
/**
* Searches for ClassNode by its full name (original or alias name)
* <br>
* Warning: This method has a runtime of O(n) (n = number of classes).
* If you need to call it more than once consider {@link #buildFullAliasClassCache()} instead
*/
@Nullable @Nullable
public ClassNode searchClassByFullAlias(String fullName) { public ClassNode searchClassByFullAlias(String fullName) {
for (ClassNode cls : classes) { for (ClassNode cls : classes) {
@@ -346,6 +352,20 @@ public class RootNode {
return null; return null;
} }
public Map<String, ClassNode> buildFullAliasClassCache() {
Map<String, ClassNode> classNameCache = new HashMap<>(classes.size());
for (ClassNode cls : classes) {
ClassInfo classInfo = cls.getClassInfo();
String fullName = classInfo.getFullName();
String alias = classInfo.getAliasFullName();
classNameCache.put(fullName, cls);
if (alias != null && !fullName.equals(alias)) {
classNameCache.put(alias, cls);
}
}
return classNameCache;
}
public List<ClassNode> searchClassByShortName(String shortName) { public List<ClassNode> searchClassByShortName(String shortName) {
List<ClassNode> list = new ArrayList<>(); List<ClassNode> list = new ArrayList<>();
for (ClassNode cls : classes) { for (ClassNode cls : classes) {
@@ -251,8 +251,8 @@ public class SignatureParser {
/** /**
* Map of generic types names to extends classes. * Map of generic types names to extends classes.
* <p/> * <p>
* Example: "<T:Ljava/lang/Exception;:Ljava/lang/Object;>" * Example: "&lt;T:Ljava/lang/Exception;:Ljava/lang/Object;&gt;"
*/ */
@SuppressWarnings("ConditionalBreakInInfiniteLoop") @SuppressWarnings("ConditionalBreakInInfiniteLoop")
public List<ArgType> consumeGenericTypeParameters() { public List<ArgType> consumeGenericTypeParameters() {
@@ -8,6 +8,9 @@ import org.jetbrains.annotations.Nullable;
import jadx.core.clsp.ClspClass; import jadx.core.clsp.ClspClass;
import jadx.core.clsp.ClspMethod; import jadx.core.clsp.ClspMethod;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.BaseInvokeNode; import jadx.core.dex.instructions.BaseInvokeNode;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
@@ -15,6 +18,7 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
public class MethodUtils { public class MethodUtils {
private final RootNode root; private final RootNode root;
@@ -67,7 +71,7 @@ public class MethodUtils {
return null; return null;
} }
public boolean processMethodArgsOverloaded(ArgType startCls, MethodInfo mthInfo, @Nullable List<IMethodDetails> collectedMths) { private boolean processMethodArgsOverloaded(ArgType startCls, MethodInfo mthInfo, @Nullable List<IMethodDetails> collectedMths) {
if (startCls == null || !startCls.isObject()) { if (startCls == null || !startCls.isObject()) {
return false; return false;
} }
@@ -122,4 +126,25 @@ public class MethodUtils {
} }
return false; return false;
} }
@Nullable
public IMethodDetails getOverrideBaseMth(MethodNode mth) {
MethodOverrideAttr overrideAttr = mth.get(AType.METHOD_OVERRIDE);
if (overrideAttr == null) {
return null;
}
return Utils.getOne(overrideAttr.getBaseMethods());
}
public ClassInfo getMethodOriginDeclClass(MethodNode mth) {
IMethodDetails baseMth = getOverrideBaseMth(mth);
if (baseMth != null) {
return baseMth.getMethodInfo().getDeclClass();
}
MethodBridgeAttr bridgeAttr = mth.get(AType.BRIDGED_BY);
if (bridgeAttr != null) {
return getMethodOriginDeclClass(bridgeAttr.getBridgeMth());
}
return mth.getMethodInfo().getDeclClass();
}
} }
@@ -0,0 +1,87 @@
package jadx.core.dex.regions.conditions;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IConditionRegion;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.AbstractRegion;
import jadx.core.utils.BlockUtils;
public abstract class ConditionRegion extends AbstractRegion implements IConditionRegion {
@Nullable
private IfCondition condition;
private List<BlockNode> conditionBlocks = Collections.emptyList();
public ConditionRegion(IRegion parent) {
super(parent);
}
@Override
@Nullable
public IfCondition getCondition() {
return condition;
}
@Override
public List<BlockNode> getConditionBlocks() {
return conditionBlocks;
}
@Override
public void invertCondition() {
if (condition != null) {
condition = IfCondition.invert(condition);
}
}
@Override
public boolean simplifyCondition() {
if (condition == null) {
return false;
}
IfCondition updated = IfCondition.simplify(condition);
if (updated != condition) {
condition = updated;
return true;
}
return false;
}
@Override
public int getConditionSourceLine() {
for (BlockNode block : conditionBlocks) {
InsnNode lastInsn = BlockUtils.getLastInsn(block);
if (lastInsn != null) {
int sourceLine = lastInsn.getSourceLine();
if (sourceLine != 0) {
return sourceLine;
}
}
}
return 0;
}
/**
* Prefer way for update condition info
*/
public void updateCondition(IfInfo info) {
this.condition = info.getCondition();
this.conditionBlocks = info.getMergedBlocks();
}
public void updateCondition(IfCondition condition, List<BlockNode> conditionBlocks) {
this.condition = condition;
this.conditionBlocks = conditionBlocks;
}
public void updateCondition(BlockNode block) {
this.condition = IfCondition.fromIfBlock(block);
this.conditionBlocks = Collections.singletonList(block);
}
}
@@ -272,6 +272,12 @@ public final class IfCondition extends AttrNode {
} }
} }
public List<InsnNode> collectInsns() {
List<InsnNode> list = new ArrayList<>();
visitInsns(list::add);
return list;
}
@Nullable @Nullable
public InsnNode getFirstInsn() { public InsnNode getFirstInsn() {
if (mode == Mode.COMPARE) { if (mode == Mode.COMPARE) {
@@ -3,7 +3,6 @@ package jadx.core.dex.regions.conditions;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import jadx.api.ICodeWriter; import jadx.api.ICodeWriter;
import jadx.core.codegen.RegionGen; import jadx.core.codegen.RegionGen;
@@ -11,16 +10,9 @@ import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBranchRegion; import jadx.core.dex.nodes.IBranchRegion;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.AbstractRegion;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
public final class IfRegion extends AbstractRegion implements IBranchRegion { public final class IfRegion extends ConditionRegion implements IBranchRegion {
private List<BlockNode> conditionBlocks;
private IfCondition condition;
private IContainer thenRegion; private IContainer thenRegion;
private IContainer elseRegion; private IContainer elseRegion;
@@ -28,14 +20,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
super(parent); super(parent);
} }
public IfCondition getCondition() {
return condition;
}
public void setCondition(IfCondition condition) {
this.condition = condition;
}
public IContainer getThenRegion() { public IContainer getThenRegion() {
return thenRegion; return thenRegion;
} }
@@ -52,31 +36,8 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
this.elseRegion = elseRegion; this.elseRegion = elseRegion;
} }
public List<BlockNode> getConditionBlocks() {
return conditionBlocks;
}
public void setConditionBlocks(List<BlockNode> conditionBlocks) {
this.conditionBlocks = conditionBlocks;
}
public void setConditionBlocks(Set<BlockNode> conditionBlocks) {
List<BlockNode> list = new ArrayList<>(conditionBlocks);
Collections.sort(list);
this.conditionBlocks = list;
}
public boolean simplifyCondition() {
IfCondition cond = IfCondition.simplify(condition);
if (cond != condition) {
condition = cond;
return true;
}
return false;
}
public void invert() { public void invert() {
condition = IfCondition.invert(condition); invertCondition();
// swap regions // swap regions
IContainer tmp = thenRegion; IContainer tmp = thenRegion;
thenRegion = elseRegion; thenRegion = elseRegion;
@@ -84,20 +45,12 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
} }
public int getSourceLine() { public int getSourceLine() {
for (BlockNode block : conditionBlocks) { return getConditionSourceLine();
InsnNode lastInsn = BlockUtils.getLastInsn(block);
if (lastInsn != null) {
int sourceLine = lastInsn.getSourceLine();
if (sourceLine != 0) {
return sourceLine;
}
}
}
return 0;
} }
@Override @Override
public List<IContainer> getSubBlocks() { public List<IContainer> getSubBlocks() {
List<BlockNode> conditionBlocks = getConditionBlocks();
List<IContainer> all = new ArrayList<>(conditionBlocks.size() + 2); List<IContainer> all = new ArrayList<>(conditionBlocks.size() + 2);
all.addAll(conditionBlocks); all.addAll(conditionBlocks);
if (thenRegion != null) { if (thenRegion != null) {
@@ -151,6 +104,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
@Override @Override
public String toString() { public String toString() {
return "IF " + conditionBlocks + " THEN: " + thenRegion + " ELSE: " + elseRegion; return "IF " + getConditionBlocks() + " THEN: " + thenRegion + " ELSE: " + elseRegion;
} }
} }
@@ -1,7 +1,6 @@
package jadx.core.dex.regions.loops; package jadx.core.dex.regions.loops;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -9,55 +8,45 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter; import jadx.api.ICodeWriter;
import jadx.core.codegen.RegionGen; import jadx.core.codegen.RegionGen;
import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.AbstractRegion; import jadx.core.dex.regions.conditions.ConditionRegion;
import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
public final class LoopRegion extends AbstractRegion { public final class LoopRegion extends ConditionRegion {
private final LoopInfo info; private final LoopInfo info;
/**
* loop header contains one 'if' insn, equals null for infinite loop
*/
@Nullable
private IfCondition condition;
private final BlockNode conditionBlock;
// instruction which must be executed before condition in every loop
private BlockNode preCondition;
private IRegion body;
private final boolean conditionAtEnd; private final boolean conditionAtEnd;
private final @Nullable BlockNode header;
// instruction which must be executed before condition in every loop
private @Nullable BlockNode preCondition;
private IRegion body;
private LoopType type; private LoopType type;
public LoopRegion(IRegion parent, LoopInfo info, @Nullable BlockNode header, boolean reversed) { public LoopRegion(IRegion parent, LoopInfo info, @Nullable BlockNode header, boolean reversed) {
super(parent); super(parent);
this.info = info; this.info = info;
this.conditionBlock = header; this.header = header;
this.condition = IfCondition.fromIfBlock(header);
this.conditionAtEnd = reversed; this.conditionAtEnd = reversed;
if (header != null) {
updateCondition(header);
}
} }
public LoopInfo getInfo() { public LoopInfo getInfo() {
return info; return info;
} }
public IfCondition getCondition() { @Nullable
return condition;
}
public void setCondition(IfCondition condition) {
this.condition = condition;
}
public BlockNode getHeader() { public BlockNode getHeader() {
return conditionBlock; return header;
} }
public IRegion getBody() { public IRegion getBody() {
@@ -79,10 +68,6 @@ public final class LoopRegion extends AbstractRegion {
this.preCondition = preCondition; this.preCondition = preCondition;
} }
private IfNode getIfInsn() {
return (IfNode) BlockUtils.getLastInsn(conditionBlock);
}
/** /**
* Check if pre-conditions can be inlined into loop condition * Check if pre-conditions can be inlined into loop condition
*/ */
@@ -91,7 +76,14 @@ public final class LoopRegion extends AbstractRegion {
if (insns.isEmpty()) { if (insns.isEmpty()) {
return true; return true;
} }
IfNode ifInsn = getIfInsn(); IfCondition condition = getCondition();
if (condition == null) {
return false;
}
List<RegisterArg> conditionArgs = condition.getRegisterArgs();
if (conditionArgs.isEmpty()) {
return false;
}
int size = insns.size(); int size = insns.size();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
InsnNode insn = insns.get(i); InsnNode insn = insns.get(i);
@@ -110,7 +102,7 @@ public final class LoopRegion extends AbstractRegion {
} }
} }
// or in if insn // or in if insn
if (!found && ifInsn.containsVar(res)) { if (!found && InsnUtils.containsVar(conditionArgs, res)) {
found = true; found = true;
} }
if (!found) { if (!found) {
@@ -124,8 +116,8 @@ public final class LoopRegion extends AbstractRegion {
* Move all preCondition block instructions before conditionBlock instructions * Move all preCondition block instructions before conditionBlock instructions
*/ */
public void mergePreCondition() { public void mergePreCondition() {
if (preCondition != null && conditionBlock != null) { if (preCondition != null && header != null) {
List<InsnNode> condInsns = conditionBlock.getInstructions(); List<InsnNode> condInsns = header.getInstructions();
List<InsnNode> preCondInsns = preCondition.getInstructions(); List<InsnNode> preCondInsns = preCondition.getInstructions();
preCondInsns.addAll(condInsns); preCondInsns.addAll(condInsns);
condInsns.clear(); condInsns.clear();
@@ -135,9 +127,13 @@ public final class LoopRegion extends AbstractRegion {
} }
} }
public int getConditionSourceLine() { public int getSourceLine() {
InsnNode lastInsn = BlockUtils.getLastInsn(conditionBlock); InsnNode lastInsn = BlockUtils.getLastInsn(header);
return lastInsn == null ? 0 : lastInsn.getSourceLine(); int headerLine = lastInsn == null ? 0 : lastInsn.getSourceLine();
if (headerLine != 0) {
return headerLine;
}
return getConditionSourceLine();
} }
public LoopType getType() { public LoopType getType() {
@@ -150,17 +146,15 @@ public final class LoopRegion extends AbstractRegion {
@Override @Override
public List<IContainer> getSubBlocks() { public List<IContainer> getSubBlocks() {
List<IContainer> all = new ArrayList<>(3); List<IContainer> all = new ArrayList<>(2 + getConditionBlocks().size());
if (preCondition != null) { if (preCondition != null) {
all.add(preCondition); all.add(preCondition);
} }
if (conditionBlock != null) { all.addAll(getConditionBlocks());
all.add(conditionBlock);
}
if (body != null) { if (body != null) {
all.add(body); all.add(body);
} }
return Collections.unmodifiableList(all); return all;
} }
@Override @Override
@@ -0,0 +1,132 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
name = "AnonymousClassVisitor",
desc = "Prepare anonymous class for inline",
runBefore = {
ModVisitor.class,
CodeShrinkVisitor.class
}
)
public class AnonymousClassVisitor extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws JadxException {
if (cls.contains(AFlag.ANONYMOUS_CLASS)) {
for (MethodNode mth : cls.getMethods()) {
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
processAnonymousConstructor(mth);
break;
}
}
}
return true;
}
private static void processAnonymousConstructor(MethodNode mth) {
List<InsnNode> usedInsns = new ArrayList<>();
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(mth, usedInsns);
if (argsMap.isEmpty()) {
mth.add(AFlag.NO_SKIP_ARGS);
} else {
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
FieldNode field = entry.getValue();
if (field == null) {
continue;
}
InsnArg arg = entry.getKey();
field.addAttr(new FieldReplaceAttr(arg));
field.add(AFlag.DONT_GENERATE);
if (arg.isRegister()) {
arg.add(AFlag.SKIP_ARG);
SkipMethodArgsAttr.skipArg(mth, ((RegisterArg) arg));
}
}
}
for (InsnNode usedInsn : usedInsns) {
usedInsn.add(AFlag.DONT_GENERATE);
}
}
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode mth, List<InsnNode> usedInsns) {
MethodInfo callMth = mth.getMethodInfo();
ClassNode cls = mth.getParentClass();
List<RegisterArg> argList = mth.getArgRegs();
ClassNode outerCls = mth.getUseIn().get(0).getParentClass();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(outerCls.getClassInfo().getType())) {
startArg = 1;
}
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
int argsCount = argList.size();
for (int i = startArg; i < argsCount; i++) {
RegisterArg arg = argList.get(i);
InsnNode useInsn = getParentInsnSkipMove(arg);
if (useInsn == null) {
return Collections.emptyMap();
}
switch (useInsn.getType()) {
case IPUT:
FieldNode fieldNode = cls.searchField((FieldInfo) ((IndexInsnNode) useInsn).getIndex());
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
return Collections.emptyMap();
}
map.put(arg, fieldNode);
usedInsns.add(useInsn);
break;
case CONSTRUCTOR:
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
if (!superConstr.isSuper()) {
return Collections.emptyMap();
}
usedInsns.add(useInsn);
break;
default:
return Collections.emptyMap();
}
}
return map;
}
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar.getUseCount() != 1) {
return null;
}
RegisterArg useArg = sVar.getUseList().get(0);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
return null;
}
if (parentInsn.getType() == InsnType.MOVE) {
return getParentInsnSkipMove(parentInsn.getResult());
}
return parentInsn;
}
}
@@ -1,7 +1,10 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import jadx.api.plugins.input.data.AccessFlags; import jadx.api.plugins.input.data.AccessFlags;
@@ -55,7 +58,7 @@ public class ClassModifier extends AbstractVisitor {
removeSyntheticFields(cls); removeSyntheticFields(cls);
cls.getMethods().forEach(ClassModifier::removeSyntheticMethods); cls.getMethods().forEach(ClassModifier::removeSyntheticMethods);
cls.getMethods().forEach(ClassModifier::removeEmptyMethods); cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
cls.getMethods().forEach(ClassModifier::cleanInsnsInAnonymousConstructor); cls.getMethods().forEach(ClassModifier::processAnonymousConstructor);
return false; return false;
} }
@@ -326,27 +329,86 @@ public class ClassModifier extends AbstractVisitor {
/** /**
* Remove super call and put into removed fields from anonymous constructor * Remove super call and put into removed fields from anonymous constructor
*/ */
private static void cleanInsnsInAnonymousConstructor(MethodNode mth) { private static void processAnonymousConstructor(MethodNode mth) {
if (!mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) { if (!mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
return; return;
} }
for (BlockNode block : mth.getBasicBlocks()) { List<InsnNode> usedInsns = new ArrayList<>();
for (InsnNode insn : block.getInstructions()) { Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(mth, usedInsns);
InsnType type = insn.getType(); for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
if (type == InsnType.CONSTRUCTOR) { FieldNode field = entry.getValue();
ConstructorInsn ctorInsn = (ConstructorInsn) insn; if (field == null) {
if (ctorInsn.isSuper()) { continue;
ctorInsn.add(AFlag.DONT_GENERATE); }
} InsnArg arg = entry.getKey();
} else if (type == InsnType.IPUT) { field.addAttr(new FieldReplaceAttr(arg));
FieldInfo fldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex(); field.add(AFlag.DONT_GENERATE);
FieldNode fieldNode = mth.root().resolveField(fldInfo); if (arg.isRegister()) {
if (fieldNode != null && fieldNode.contains(AFlag.DONT_GENERATE)) { arg.add(AFlag.SKIP_ARG);
insn.add(AFlag.DONT_GENERATE); SkipMethodArgsAttr.skipArg(mth, ((RegisterArg) arg));
}
}
} }
} }
for (InsnNode usedInsn : usedInsns) {
usedInsn.add(AFlag.DONT_GENERATE);
}
}
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode mth, List<InsnNode> usedInsns) {
MethodInfo callMth = mth.getMethodInfo();
ClassNode cls = mth.getParentClass();
List<RegisterArg> argList = mth.getArgRegs();
ClassNode outerCls = mth.getUseIn().get(0).getParentClass();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(outerCls.getClassInfo().getType())) {
startArg = 1;
}
Map<InsnArg, FieldNode> map = new LinkedHashMap<>();
int argsCount = argList.size();
for (int i = startArg; i < argsCount; i++) {
RegisterArg arg = argList.get(i);
InsnNode useInsn = getParentInsnSkipMove(arg);
if (useInsn == null) {
return Collections.emptyMap();
}
switch (useInsn.getType()) {
case IPUT:
FieldNode fieldNode = cls.searchField((FieldInfo) ((IndexInsnNode) useInsn).getIndex());
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
return Collections.emptyMap();
}
map.put(arg, fieldNode);
usedInsns.add(useInsn);
break;
case CONSTRUCTOR:
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
if (!superConstr.isSuper()) {
return Collections.emptyMap();
}
usedInsns.add(useInsn);
break;
default:
return Collections.emptyMap();
}
}
return map;
}
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar.getUseCount() != 1) {
return null;
}
RegisterArg useArg = sVar.getUseList().get(0);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
return null;
}
if (parentInsn.getType() == InsnType.MOVE) {
return getParentInsnSkipMove(parentInsn.getResult());
}
return parentInsn;
} }
private static boolean isNonDefaultConstructorExists(MethodNode defCtor) { private static boolean isNonDefaultConstructorExists(MethodNode defCtor) {
@@ -137,6 +137,17 @@ public class DeboxingVisitor extends AbstractVisitor {
if (ssaVar.isTypeImmutable()) { if (ssaVar.isTypeImmutable()) {
return false; return false;
} }
InsnNode assignInsn = ssaVar.getAssignInsn();
if (assignInsn == null) {
// method arg
return false;
}
InsnType assignInsnType = assignInsn.getType();
if (assignInsnType == InsnType.CONST || assignInsnType == InsnType.MOVE) {
if (assignInsn.getArg(0).getType().isObject()) {
return false;
}
}
for (RegisterArg useArg : ssaVar.getUseList()) { for (RegisterArg useArg : ssaVar.getUseList()) {
InsnNode parentInsn = useArg.getParentInsn(); InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) { if (parentInsn == null) {
@@ -86,7 +86,7 @@ public class FixAccessModifiers extends AbstractVisitor {
if (!accessFlags.isPublic()) { if (!accessFlags.isPublic()) {
// if class is used in inlinable method => make it public // if class is used in inlinable method => make it public
for (MethodNode useMth : cls.getUseInMth()) { for (MethodNode useMth : cls.getUseInMth()) {
boolean canInline = MarkMethodsForInline.canInline(useMth) || useMth.contains(AType.METHOD_INLINE); boolean canInline = useMth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE) || useMth.contains(AType.METHOD_INLINE);
if (canInline && !useMth.getUseIn().isEmpty()) { if (canInline && !useMth.getUseIn().isEmpty()) {
return AccessFlags.PUBLIC; return AccessFlags.PUBLIC;
} }
@@ -10,7 +10,6 @@ import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
@@ -47,7 +46,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
if (mia != null) { if (mia != null) {
return mia; return mia;
} }
if (canInline(mth)) { if (mth.contains(AFlag.METHOD_CANDIDATE_FOR_INLINE)) {
if (mth.getBasicBlocks() == null) { if (mth.getBasicBlocks() == null) {
return null; return null;
} }
@@ -59,14 +58,6 @@ public class MarkMethodsForInline extends AbstractVisitor {
return MethodInlineAttr.inlineNotNeeded(mth); return MethodInlineAttr.inlineNotNeeded(mth);
} }
public static boolean canInline(MethodNode mth) {
if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) {
return false;
}
AccessInfo accessFlags = mth.getAccessFlags();
return accessFlags.isSynthetic() && accessFlags.isStatic();
}
@Nullable @Nullable
private static MethodInlineAttr inlineMth(MethodNode mth) { private static MethodInlineAttr inlineMth(MethodNode mth) {
List<InsnNode> insns = BlockUtils.collectInsnsWithLimit(mth.getBasicBlocks(), 2); List<InsnNode> insns = BlockUtils.collectInsnsWithLimit(mth.getBasicBlocks(), 2);
@@ -79,7 +70,11 @@ public class MarkMethodsForInline extends AbstractVisitor {
if (insn.getType() == InsnType.RETURN && insn.getArgsCount() == 1) { if (insn.getType() == InsnType.RETURN && insn.getArgsCount() == 1) {
// synthetic field getter // synthetic field getter
// set arg from 'return' instruction // set arg from 'return' instruction
return addInlineAttr(mth, InsnNode.wrapArg(insn.getArg(0))); InsnArg arg = insn.getArg(0);
if (!arg.isInsnWrap()) {
return null;
}
return addInlineAttr(mth, ((InsnWrapArg) arg).getWrapInsn());
} }
// method invoke // method invoke
return addInlineAttr(mth, insn); return addInlineAttr(mth, insn);
@@ -1,7 +1,5 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -19,10 +17,9 @@ import jadx.api.plugins.input.data.attributes.types.AnnotationsAttr;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode; import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ConstClassNode; import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode; import jadx.core.dex.instructions.ConstStringNode;
@@ -46,6 +43,7 @@ import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
@@ -432,96 +430,39 @@ public class ModVisitor extends AbstractVisitor {
return false; return false;
} }
/**
* For args in anonymous constructor invoke apply:
* - forbid inline into constructor call
* - make variables final (compiler require this implicitly)
*/
private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) { private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) {
MethodInfo callMth = co.getCallMth(); IMethodDetails callMthDetails = mth.root().getMethodUtils().getMethodDetails(co);
MethodNode callMthNode = mth.root().resolveMethod(callMth); if (!(callMthDetails instanceof MethodNode)) {
if (callMthNode == null) {
return; return;
} }
MethodNode callMth = (MethodNode) callMthDetails;
ClassNode classNode = callMthNode.getParentClass(); if (!callMth.contains(AFlag.ANONYMOUS_CONSTRUCTOR) || callMth.contains(AFlag.NO_SKIP_ARGS)) {
if (!classNode.isAnonymous()) {
return; return;
} }
if (!mth.getParentClass().getInnerClasses().contains(classNode)) { SkipMethodArgsAttr attr = callMth.get(AType.SKIP_MTH_ARGS);
return; if (attr != null) {
} int argsCount = Math.min(callMth.getArgRegs().size(), co.getArgsCount());
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(callMthNode, co); for (int i = 0; i < argsCount; i++) {
if (argsMap.isEmpty() && !callMthNode.getArgRegs().isEmpty()) { if (attr.isSkip(i)) {
return; anonymousCallArgMod(co.getArg(i));
}
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
FieldNode field = entry.getValue();
if (field == null) {
continue;
}
InsnArg arg = entry.getKey();
field.addAttr(new FieldReplaceAttr(arg));
field.add(AFlag.DONT_GENERATE);
if (arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg;
SSAVar sVar = reg.getSVar();
if (sVar != null) {
sVar.getCodeVar().setFinal(true);
} }
reg.add(AFlag.DONT_INLINE);
reg.add(AFlag.SKIP_ARG);
} }
} else {
// additional info not available apply mods to all args (the safest solution)
co.getArguments().forEach(ModVisitor::anonymousCallArgMod);
} }
} }
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode callMthNode, ConstructorInsn co) { private static void anonymousCallArgMod(InsnArg arg) {
Map<InsnArg, FieldNode> map = new LinkedHashMap<>(); arg.add(AFlag.DONT_INLINE);
MethodInfo callMth = callMthNode.getMethodInfo(); if (arg.isRegister()) {
ClassNode cls = callMthNode.getParentClass(); ((RegisterArg) arg).getSVar().getCodeVar().setFinal(true);
ClassNode parentClass = cls.getParentClass();
List<RegisterArg> argList = callMthNode.getArgRegs();
int startArg = 0;
if (callMth.getArgsCount() != 0 && callMth.getArgumentsTypes().get(0).equals(parentClass.getClassInfo().getType())) {
startArg = 1;
} }
int argsCount = argList.size();
for (int i = startArg; i < argsCount; i++) {
RegisterArg arg = argList.get(i);
InsnNode useInsn = getParentInsnSkipMove(arg);
if (useInsn == null) {
return Collections.emptyMap();
}
FieldNode fieldNode = null;
if (useInsn.getType() == InsnType.IPUT) {
FieldInfo field = (FieldInfo) ((IndexInsnNode) useInsn).getIndex();
fieldNode = cls.searchField(field);
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
return Collections.emptyMap();
}
} else if (useInsn.getType() == InsnType.CONSTRUCTOR) {
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
if (!superConstr.isSuper()) {
return Collections.emptyMap();
}
} else {
return Collections.emptyMap();
}
map.put(co.getArg(i), fieldNode);
}
return map;
}
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar.getUseCount() != 1) {
return null;
}
RegisterArg useArg = sVar.getUseList().get(0);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn == null) {
return null;
}
if (parentInsn.getType() == InsnType.MOVE) {
return getParentInsnSkipMove(parentInsn.getResult());
}
return parentInsn;
} }
/** /**
@@ -1,11 +1,11 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -17,6 +17,7 @@ import jadx.core.clsp.ClspClass;
import jadx.core.clsp.ClspMethod; import jadx.core.clsp.ClspMethod;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodBridgeAttr;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.RenameReasonAttr; import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
@@ -32,6 +33,7 @@ import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@JadxVisitor( @JadxVisitor(
name = "OverrideMethodVisitor", name = "OverrideMethodVisitor",
@@ -45,70 +47,86 @@ public class OverrideMethodVisitor extends AbstractVisitor {
@Override @Override
public boolean visit(ClassNode cls) throws JadxException { public boolean visit(ClassNode cls) throws JadxException {
processCls(cls); SuperTypesData superData = collectSuperTypes(cls);
if (superData != null) {
for (MethodNode mth : cls.getMethods()) {
processMth(mth, superData);
}
}
return true; return true;
} }
private void processCls(ClassNode cls) { private void processMth(MethodNode mth, SuperTypesData superData) {
List<ArgType> superTypes = collectSuperTypes(cls);
if (!superTypes.isEmpty()) {
for (MethodNode mth : cls.getMethods()) {
processMth(cls, superTypes, mth);
}
}
}
private void processMth(ClassNode cls, List<ArgType> superTypes, MethodNode mth) {
if (mth.isConstructor() || mth.getAccessFlags().isStatic() || mth.getAccessFlags().isPrivate()) { if (mth.isConstructor() || mth.getAccessFlags().isStatic() || mth.getAccessFlags().isPrivate()) {
return; return;
} }
MethodOverrideAttr attr = processOverrideMethods(cls, mth, superTypes); MethodOverrideAttr attr = processOverrideMethods(mth, superData);
if (attr != null) { if (attr != null) {
if (attr.getBaseMethods().isEmpty()) {
throw new JadxRuntimeException("No base methods for override attribute: " + attr.getOverrideList());
}
mth.addAttr(attr); mth.addAttr(attr);
IMethodDetails baseMth = Utils.last(attr.getOverrideList()); IMethodDetails baseMth = Utils.getOne(attr.getBaseMethods());
if (baseMth != null) { if (baseMth != null) {
boolean updated = fixMethodReturnType(mth, baseMth, superTypes); boolean updated = fixMethodReturnType(mth, baseMth, superData);
updated |= fixMethodArgTypes(mth, baseMth, superTypes); updated |= fixMethodArgTypes(mth, baseMth, superData);
if (updated && cls.root().getArgs().isRenameValid()) { if (updated) {
// check if new signature cause method collisions // check if new signature cause method collisions
fixMethodSignatureCollisions(mth); checkMethodSignatureCollisions(mth, mth.root().getArgs().isRenameValid());
} }
} }
} }
} }
private MethodOverrideAttr processOverrideMethods(ClassNode cls, MethodNode mth, List<ArgType> superTypes) { private MethodOverrideAttr processOverrideMethods(MethodNode mth, SuperTypesData superData) {
MethodOverrideAttr result = mth.get(AType.METHOD_OVERRIDE); MethodOverrideAttr result = mth.get(AType.METHOD_OVERRIDE);
if (result != null) { if (result != null) {
return result; return result;
} }
ClassNode cls = mth.getParentClass();
String signature = mth.getMethodInfo().makeSignature(false); String signature = mth.getMethodInfo().makeSignature(false);
List<IMethodDetails> overrideList = new ArrayList<>(); List<IMethodDetails> overrideList = new ArrayList<>();
for (ArgType superType : superTypes) { Set<IMethodDetails> baseMethods = new HashSet<>();
ClassNode classNode = cls.root().resolveClass(superType); for (ArgType superType : superData.getSuperTypes()) {
ClassNode classNode = mth.root().resolveClass(superType);
if (classNode != null) { if (classNode != null) {
MethodNode ovrdMth = searchOverriddenMethod(classNode, signature); MethodNode ovrdMth = searchOverriddenMethod(classNode, signature);
if (ovrdMth != null && isMethodVisibleInCls(ovrdMth, cls)) { if (ovrdMth != null) {
overrideList.add(ovrdMth); if (isMethodVisibleInCls(ovrdMth, cls)) {
MethodOverrideAttr attr = ovrdMth.get(AType.METHOD_OVERRIDE); overrideList.add(ovrdMth);
if (attr != null) { MethodOverrideAttr attr = ovrdMth.get(AType.METHOD_OVERRIDE);
return buildOverrideAttr(mth, overrideList, attr); if (attr != null) {
addBaseMethod(superData, overrideList, baseMethods, superType);
return buildOverrideAttr(mth, overrideList, baseMethods, attr);
}
} }
} }
} else { } else {
ClspClass clsDetails = cls.root().getClsp().getClsDetails(superType); ClspClass clsDetails = mth.root().getClsp().getClsDetails(superType);
if (clsDetails != null) { if (clsDetails != null) {
Map<String, ClspMethod> methodsMap = clsDetails.getMethodsMap(); Map<String, ClspMethod> methodsMap = clsDetails.getMethodsMap();
for (Map.Entry<String, ClspMethod> entry : methodsMap.entrySet()) { for (Map.Entry<String, ClspMethod> entry : methodsMap.entrySet()) {
String mthShortId = entry.getKey(); String mthShortId = entry.getKey();
if (mthShortId.startsWith(signature)) { if (mthShortId.startsWith(signature)) {
overrideList.add(entry.getValue()); overrideList.add(entry.getValue());
break;
} }
} }
} }
} }
addBaseMethod(superData, overrideList, baseMethods, superType);
}
return buildOverrideAttr(mth, overrideList, baseMethods, null);
}
private void addBaseMethod(SuperTypesData superData, List<IMethodDetails> overrideList, Set<IMethodDetails> baseMethods,
ArgType superType) {
if (superData.getEndTypes().contains(superType.getObject())) {
IMethodDetails last = Utils.last(overrideList);
if (last != null) {
baseMethods.add(last);
}
} }
return buildOverrideAttr(mth, overrideList, null);
} }
@Nullable @Nullable
@@ -123,22 +141,24 @@ public class OverrideMethodVisitor extends AbstractVisitor {
@Nullable @Nullable
private MethodOverrideAttr buildOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList, private MethodOverrideAttr buildOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList,
@Nullable MethodOverrideAttr attr) { Set<IMethodDetails> baseMethods, @Nullable MethodOverrideAttr attr) {
if (overrideList.isEmpty() && attr == null) { if (overrideList.isEmpty() && attr == null) {
return null; return null;
} }
if (attr == null) { if (attr == null) {
// traced to base method // traced to base method
List<IMethodDetails> cleanOverrideList = overrideList.stream().distinct().collect(Collectors.toList()); List<IMethodDetails> cleanOverrideList = overrideList.stream().distinct().collect(Collectors.toList());
return applyOverrideAttr(mth, cleanOverrideList, false); return applyOverrideAttr(mth, cleanOverrideList, baseMethods, false);
} }
// trace stopped at already processed method -> start merging // trace stopped at already processed method -> start merging
List<IMethodDetails> mergedOverrideList = Utils.mergeLists(overrideList, attr.getOverrideList()); List<IMethodDetails> mergedOverrideList = Utils.mergeLists(overrideList, attr.getOverrideList());
List<IMethodDetails> cleanOverrideList = mergedOverrideList.stream().distinct().collect(Collectors.toList()); List<IMethodDetails> cleanOverrideList = mergedOverrideList.stream().distinct().collect(Collectors.toList());
return applyOverrideAttr(mth, cleanOverrideList, true); Set<IMethodDetails> mergedBaseMethods = Utils.mergeSets(baseMethods, attr.getBaseMethods());
return applyOverrideAttr(mth, cleanOverrideList, mergedBaseMethods, true);
} }
private MethodOverrideAttr applyOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList, boolean update) { private MethodOverrideAttr applyOverrideAttr(MethodNode mth, List<IMethodDetails> overrideList,
Set<IMethodDetails> baseMethods, boolean update) {
// don't rename method if override list contains not resolved method // don't rename method if override list contains not resolved method
boolean dontRename = overrideList.stream().anyMatch(m -> !(m instanceof MethodNode)); boolean dontRename = overrideList.stream().anyMatch(m -> !(m instanceof MethodNode));
SortedSet<MethodNode> relatedMethods = null; SortedSet<MethodNode> relatedMethods = null;
@@ -188,10 +208,10 @@ public class OverrideMethodVisitor extends AbstractVisitor {
continue; continue;
} }
} }
mthNode.addAttr(new MethodOverrideAttr(Utils.listTail(overrideList, depth), relatedMethods)); mthNode.addAttr(new MethodOverrideAttr(Utils.listTail(overrideList, depth), relatedMethods, baseMethods));
depth++; depth++;
} }
return new MethodOverrideAttr(overrideList, relatedMethods); return new MethodOverrideAttr(overrideList, relatedMethods, baseMethods);
} }
@NotNull @NotNull
@@ -221,52 +241,92 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return Objects.equals(superMth.getParentClass().getPackage(), cls.getPackage()); return Objects.equals(superMth.getParentClass().getPackage(), cls.getPackage());
} }
private List<ArgType> collectSuperTypes(ClassNode cls) { private static final class SuperTypesData {
Map<String, ArgType> superTypes = new LinkedHashMap<>(); private final List<ArgType> superTypes;
collectSuperTypes(cls, superTypes); private final Set<String> endTypes;
if (superTypes.isEmpty()) {
return Collections.emptyList(); private SuperTypesData(List<ArgType> superTypes, Set<String> endTypes) {
this.superTypes = superTypes;
this.endTypes = endTypes;
}
public List<ArgType> getSuperTypes() {
return superTypes;
}
public Set<String> getEndTypes() {
return endTypes;
} }
return new ArrayList<>(superTypes.values());
} }
private void collectSuperTypes(ClassNode cls, Map<String, ArgType> superTypes) { @Nullable
private SuperTypesData collectSuperTypes(ClassNode cls) {
List<ArgType> superTypes = new ArrayList<>();
Set<String> endTypes = new HashSet<>();
collectSuperTypes(cls, superTypes, endTypes);
if (superTypes.isEmpty()) {
return null;
}
if (endTypes.isEmpty()) {
throw new JadxRuntimeException("No end types in class hierarchy: " + cls);
}
return new SuperTypesData(superTypes, endTypes);
}
private void collectSuperTypes(ClassNode cls, List<ArgType> superTypes, Set<String> endTypes) {
RootNode root = cls.root(); RootNode root = cls.root();
int k = 0;
ArgType superClass = cls.getSuperClass(); ArgType superClass = cls.getSuperClass();
if (superClass != null && !Objects.equals(superClass, ArgType.OBJECT)) { if (superClass != null) {
addSuperType(root, superTypes, superClass); k += addSuperType(root, superTypes, endTypes, superClass);
} }
for (ArgType iface : cls.getInterfaces()) { for (ArgType iface : cls.getInterfaces()) {
addSuperType(root, superTypes, iface); k += addSuperType(root, superTypes, endTypes, iface);
}
if (k == 0) {
endTypes.add(cls.getType().getObject());
} }
} }
private void addSuperType(RootNode root, Map<String, ArgType> superTypesMap, ArgType superType) { private int addSuperType(RootNode root, List<ArgType> superTypesMap, Set<String> endTypes, ArgType superType) {
superTypesMap.put(superType.getObject(), superType); if (Objects.equals(superType, ArgType.OBJECT)) {
return 0;
}
superTypesMap.add(superType);
ClassNode classNode = root.resolveClass(superType); ClassNode classNode = root.resolveClass(superType);
if (classNode == null) { if (classNode != null) {
for (String superCls : root.getClsp().getSuperTypes(superType.getObject())) { collectSuperTypes(classNode, superTypesMap, endTypes);
ArgType type = ArgType.object(superCls); return 1;
superTypesMap.put(type.getObject(), type);
}
} else {
collectSuperTypes(classNode, superTypesMap);
} }
ClspClass clsDetails = root.getClsp().getClsDetails(superType);
if (clsDetails != null) {
int k = 0;
for (ArgType parentType : clsDetails.getParents()) {
k += addSuperType(root, superTypesMap, endTypes, parentType);
}
if (k == 0) {
endTypes.add(superType.getObject());
}
return 1;
}
// no info found => treat as hierarchy end
endTypes.add(superType.getObject());
return 1;
} }
private boolean fixMethodReturnType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) { private boolean fixMethodReturnType(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData) {
ArgType returnType = mth.getReturnType(); ArgType returnType = mth.getReturnType();
if (returnType == ArgType.VOID) { if (returnType == ArgType.VOID) {
return false; return false;
} }
boolean updated = updateReturnType(mth, baseMth, superTypes); boolean updated = updateReturnType(mth, baseMth, superData);
if (updated) { if (updated) {
mth.addDebugComment("Return type fixed from '" + returnType + "' to match base method"); mth.addDebugComment("Return type fixed from '" + returnType + "' to match base method");
} }
return updated; return updated;
} }
private boolean updateReturnType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) { private boolean updateReturnType(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData) {
ArgType baseReturnType = baseMth.getReturnType(); ArgType baseReturnType = baseMth.getReturnType();
if (mth.getReturnType().equals(baseReturnType)) { if (mth.getReturnType().equals(baseReturnType)) {
return false; return false;
@@ -276,7 +336,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
} }
TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare(); TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare();
ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType(); ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType();
for (ArgType superType : superTypes) { for (ArgType superType : superData.getSuperTypes()) {
TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls); TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls);
if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) { if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) {
ArgType targetRetType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseReturnType); ArgType targetRetType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseReturnType);
@@ -291,7 +351,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return false; return false;
} }
private boolean fixMethodArgTypes(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes) { private boolean fixMethodArgTypes(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData) {
List<ArgType> mthArgTypes = mth.getArgTypes(); List<ArgType> mthArgTypes = mth.getArgTypes();
List<ArgType> baseArgTypes = baseMth.getArgTypes(); List<ArgType> baseArgTypes = baseMth.getArgTypes();
if (mthArgTypes.equals(baseArgTypes)) { if (mthArgTypes.equals(baseArgTypes)) {
@@ -304,7 +364,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
boolean changed = false; boolean changed = false;
List<ArgType> newArgTypes = new ArrayList<>(argCount); List<ArgType> newArgTypes = new ArrayList<>(argCount);
for (int argNum = 0; argNum < argCount; argNum++) { for (int argNum = 0; argNum < argCount; argNum++) {
ArgType newType = updateArgType(mth, baseMth, superTypes, argNum); ArgType newType = updateArgType(mth, baseMth, superData, argNum);
if (newType != null) { if (newType != null) {
changed = true; changed = true;
newArgTypes.add(newType); newArgTypes.add(newType);
@@ -318,7 +378,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return changed; return changed;
} }
private ArgType updateArgType(MethodNode mth, IMethodDetails baseMth, List<ArgType> superTypes, int argNum) { private ArgType updateArgType(MethodNode mth, IMethodDetails baseMth, SuperTypesData superData, int argNum) {
ArgType arg = mth.getArgTypes().get(argNum); ArgType arg = mth.getArgTypes().get(argNum);
ArgType baseArg = baseMth.getArgTypes().get(argNum); ArgType baseArg = baseMth.getArgTypes().get(argNum);
if (arg.equals(baseArg)) { if (arg.equals(baseArg)) {
@@ -329,7 +389,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
} }
TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare(); TypeCompare typeCompare = mth.root().getTypeUpdate().getTypeCompare();
ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType(); ArgType baseCls = baseMth.getMethodInfo().getDeclClass().getType();
for (ArgType superType : superTypes) { for (ArgType superType : superData.getSuperTypes()) {
TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls); TypeCompareEnum compareResult = typeCompare.compareTypes(superType, baseCls);
if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) { if (compareResult == TypeCompareEnum.NARROW_BY_GENERIC) {
ArgType targetArgType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseArg); ArgType targetArgType = mth.root().getTypeUtils().replaceClassGenerics(superType, baseArg);
@@ -343,7 +403,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return null; return null;
} }
private void fixMethodSignatureCollisions(MethodNode mth) { private void checkMethodSignatureCollisions(MethodNode mth, boolean rename) {
String mthName = mth.getMethodInfo().getAlias(); String mthName = mth.getMethodInfo().getAlias();
String newSignature = MethodInfo.makeShortId(mthName, mth.getArgTypes(), null); String newSignature = MethodInfo.makeShortId(mthName, mth.getArgTypes(), null);
for (MethodNode otherMth : mth.getParentClass().getMethods()) { for (MethodNode otherMth : mth.getParentClass().getMethods()) {
@@ -351,12 +411,16 @@ public class OverrideMethodVisitor extends AbstractVisitor {
if (otherMthName.equals(mthName) && otherMth != mth) { if (otherMthName.equals(mthName) && otherMth != mth) {
String otherSignature = otherMth.getMethodInfo().makeSignature(true, false); String otherSignature = otherMth.getMethodInfo().makeSignature(true, false);
if (otherSignature.equals(newSignature)) { if (otherSignature.equals(newSignature)) {
if (otherMth.contains(AFlag.DONT_RENAME) || otherMth.contains(AType.METHOD_OVERRIDE)) { if (rename) {
otherMth.addWarnComment("Can't rename method to resolve collision"); if (otherMth.contains(AFlag.DONT_RENAME) || otherMth.contains(AType.METHOD_OVERRIDE)) {
} else { otherMth.addWarnComment("Can't rename method to resolve collision");
otherMth.getMethodInfo().setAlias(makeNewAlias(otherMth)); } else {
otherMth.addAttr(new RenameReasonAttr("avoid collision after fix types in other method")); otherMth.getMethodInfo().setAlias(makeNewAlias(otherMth));
otherMth.addAttr(new RenameReasonAttr("avoid collision after fix types in other method"));
}
} }
otherMth.addAttr(new MethodBridgeAttr(mth));
return;
} }
} }
} }
@@ -17,6 +17,7 @@ import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp; import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
@@ -69,6 +70,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
checkInline(block); checkInline(block);
removeParenthesis(block); removeParenthesis(block);
modifyArith(block); modifyArith(block);
checkConstUsage(block);
} }
moveConstructorInConstructor(mth); moveConstructorInConstructor(mth);
} }
@@ -122,6 +124,38 @@ public class PrepareForCodeGen extends AbstractVisitor {
} }
} }
/**
* Add explicit type for non int constants
*/
private static void checkConstUsage(BlockNode block) {
for (InsnNode blockInsn : block.getInstructions()) {
blockInsn.visitInsns(insn -> {
if (forbidExplicitType(insn.getType())) {
return;
}
for (InsnArg arg : insn.getArguments()) {
if (arg.isLiteral() && arg.getType() != ArgType.INT) {
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
}
}
});
}
}
private static boolean forbidExplicitType(InsnType type) {
switch (type) {
case CONST:
case CAST:
case IF:
case FILLED_NEW_ARRAY:
case APUT:
case ARITH:
return true;
default:
return false;
}
}
private static void removeParenthesis(BlockNode block) { private static void removeParenthesis(BlockNode block) {
for (InsnNode insn : block.getInstructions()) { for (InsnNode insn : block.getInstructions()) {
removeParenthesis(insn); removeParenthesis(insn);
@@ -1,11 +1,19 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.usage.UsageInfoVisitor; import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
@JadxVisitor( @JadxVisitor(
@@ -34,78 +42,136 @@ public class ProcessAnonymous extends AbstractVisitor {
} }
private static void markAnonymousClass(ClassNode cls) { private static void markAnonymousClass(ClassNode cls) {
if (usedOnlyOnce(cls) || isAnonymous(cls) || isLambdaCls(cls)) { boolean synthetic = cls.getAccessFlags().isSynthetic()
if (isStaticFieldUsedOutside(cls)) { || cls.getClassInfo().getShortName().contains("$")
return; || Character.isDigit(cls.getClassInfo().getShortName().charAt(0));
} if (!synthetic) {
cls.add(AFlag.ANONYMOUS_CLASS); return;
cls.add(AFlag.DONT_GENERATE); }
MethodNode anonymousConstructor = checkUsage(cls);
if (anonymousConstructor == null) {
return;
}
ArgType baseType = getBaseType(cls);
if (baseType == null) {
return;
}
for (MethodNode mth : cls.getMethods()) { cls.add(AFlag.ANONYMOUS_CLASS);
if (mth.isConstructor()) { cls.addAttr(new AnonymousClassBaseAttr(baseType));
mth.add(AFlag.ANONYMOUS_CONSTRUCTOR); cls.add(AFlag.DONT_GENERATE);
}
} anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
// force anonymous class to be processed before outer class,
// actual usage of outer class will be removed at anonymous class process,
// see ModVisitor.processAnonymousConstructor method
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
ClassNode topOuterCls = outerCls.getTopParentClass();
ListUtils.safeRemove(cls.getDependencies(), topOuterCls);
ListUtils.safeRemove(outerCls.getUseIn(), cls);
// move dependency to codegen stage
if (cls.isTopClass()) {
topOuterCls.setDependencies(ListUtils.safeRemoveAndTrim(topOuterCls.getDependencies(), cls));
topOuterCls.setCodegenDeps(ListUtils.safeAdd(topOuterCls.getCodegenDeps(), cls));
} }
} }
private static boolean isStaticFieldUsedOutside(ClassNode cls) { /**
ClassNode topCls = cls.getTopParentClass(); * Checks:
* - class have only one constructor which used only once (allow common code for field init)
* - methods or fields not used outside (allow only nested inner classes with synthetic usage)
*
* @return anonymous constructor method
*/
private static MethodNode checkUsage(ClassNode cls) {
MethodNode ctr = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
if (ctr == null) {
return null;
}
if (ctr.getUseIn().size() != 1) {
// check if used in common field init in all constructors
if (!checkForCommonFieldInit(ctr)) {
return null;
}
}
MethodNode ctrUseMth = ctr.getUseIn().get(0);
ClassNode ctrUseCls = ctrUseMth.getParentClass();
if (ctrUseCls.equals(cls)) {
// exclude self usage
return null;
}
for (MethodNode mth : cls.getMethods()) {
if (mth == ctr) {
continue;
}
for (MethodNode useMth : mth.getUseIn()) {
if (useMth.equals(ctrUseMth)) {
continue;
}
if (badMethodUsage(cls, useMth, mth.getAccessFlags())) {
return null;
}
}
}
for (FieldNode field : cls.getFields()) { for (FieldNode field : cls.getFields()) {
if (field.isStatic()) { for (MethodNode useMth : field.getUseIn()) {
for (MethodNode useMth : field.getUseIn()) { if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
ClassNode useCls = useMth.getParentClass().getTopParentClass(); return null;
if (!useCls.equals(topCls)) {
return true;
}
} }
} }
} }
return false; return ctr;
} }
private static boolean usedOnlyOnce(ClassNode cls) { private static boolean badMethodUsage(ClassNode cls, MethodNode useMth, AccessInfo accessFlags) {
if (cls.getUseIn().size() == 1 && cls.getUseInMth().size() == 1) { ClassNode useCls = useMth.getParentClass();
// used only once if (useCls.equals(cls)) {
boolean synthetic = cls.getAccessFlags().isSynthetic() || cls.getClassInfo().getShortName().contains("$"); return false;
if (synthetic) {
// must have only one constructor which used only once
MethodNode ctr = null;
for (MethodNode mth : cls.getMethods()) {
if (mth.isConstructor()) {
if (ctr != null) {
ctr = null;
break;
}
ctr = mth;
}
}
return ctr != null && ctr.getUseIn().size() == 1;
}
} }
return false; if (accessFlags.isSynthetic()) {
} // allow synthetic usage in inner class
return !useCls.getParentClass().equals(cls);
private static boolean isAnonymous(ClassNode cls) {
return cls.getClassInfo().isInner()
&& Character.isDigit(cls.getClassInfo().getShortName().charAt(0))
&& cls.getMethods().stream().filter(MethodNode::isConstructor).count() == 1;
}
private static boolean isLambdaCls(ClassNode cls) {
return cls.getAccessFlags().isSynthetic()
&& cls.getAccessFlags().isFinal()
&& cls.getClassInfo().getRawName().contains(".-$$Lambda$")
&& countStaticFields(cls) == 0;
}
private static int countStaticFields(ClassNode cls) {
int c = 0;
for (FieldNode field : cls.getFields()) {
if (field.getAccessFlags().isStatic()) {
c++;
}
} }
return c; return true;
}
/**
* Checks:
* + all in constructors
* + all usage in one class
* - same field put (ignored: methods not loaded yet)
*/
private static boolean checkForCommonFieldInit(MethodNode ctrMth) {
List<MethodNode> ctrUse = ctrMth.getUseIn();
if (ctrUse.isEmpty()) {
return false;
}
ClassNode firstUseCls = ctrUse.get(0).getParentClass();
return ListUtils.allMatch(ctrUse, m -> m.isConstructor() && m.getParentClass().equals(firstUseCls));
}
@Nullable
private static ArgType getBaseType(ClassNode cls) {
int interfacesCount = cls.getInterfaces().size();
if (interfacesCount > 1) {
return null;
}
ArgType superCls = cls.getSuperClass();
if (superCls == null || superCls.equals(ArgType.OBJECT)) {
if (interfacesCount == 1) {
return cls.getInterfaces().get(0);
}
return ArgType.OBJECT;
}
if (interfacesCount == 0) {
return superCls;
}
// check if super class already implement that interface (weird case)
ArgType interfaceType = cls.getInterfaces().get(0);
if (cls.root().getClsp().isImplements(superCls.getObject(), interfaceType.getObject())) {
return superCls;
}
return null;
} }
} }
@@ -80,6 +80,13 @@ public class ProcessInstructionsVisitor extends AbstractVisitor {
} }
break; break;
case STR_CONCAT:
// invoke-custom with string concatenation translated directly to STR_CONCAT, merge next move-result
if (insn.getResult() == null) {
mergeMoveResult(insnByOffset, offset, insn, ArgType.STRING);
}
break;
case FILLED_NEW_ARRAY: case FILLED_NEW_ARRAY:
ArgType arrType = ((FilledNewArrayNode) insn).getArrayType(); ArgType arrType = ((FilledNewArrayNode) insn).getArrayType();
mergeMoveResult(insnByOffset, offset, insn, arrType); mergeMoveResult(insnByOffset, offset, insn, arrType);
@@ -0,0 +1,60 @@
package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
name = "ProcessMethodsForInline",
desc = "Mark methods for future inline",
runAfter = {
UsageInfoVisitor.class
}
)
public class ProcessMethodsForInline extends AbstractVisitor {
private boolean inlineMethods;
@Override
public void init(RootNode root) {
inlineMethods = root.getArgs().isInlineMethods();
}
@Override
public boolean visit(ClassNode cls) throws JadxException {
if (!inlineMethods) {
return false;
}
for (MethodNode mth : cls.getMethods()) {
if (canInline(mth)) {
mth.add(AFlag.METHOD_CANDIDATE_FOR_INLINE);
fixClassDependencies(mth);
}
}
return true;
}
private static boolean canInline(MethodNode mth) {
if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) {
return false;
}
AccessInfo accessFlags = mth.getAccessFlags();
boolean isSynthetic = accessFlags.isSynthetic() || mth.getName().contains("$");
return isSynthetic && accessFlags.isStatic();
}
private static void fixClassDependencies(MethodNode mth) {
ClassNode parentClass = mth.getTopParentClass();
for (MethodNode useInMth : mth.getUseIn()) {
// remove possible cross dependency to force class with inline method to be processed before its
// usage
ClassNode useTopCls = useInMth.getTopParentClass();
parentClass.setDependencies(ListUtils.safeRemoveAndTrim(parentClass.getDependencies(), useTopCls));
}
}
}
@@ -129,7 +129,7 @@ public class SignatureProcessor extends AbstractVisitor {
if (checkedArgTypes == null) { if (checkedArgTypes == null) {
return false; return false;
} }
mth.updateTypes(Utils.lockList(checkedArgTypes), retType); mth.updateTypes(Collections.unmodifiableList(checkedArgTypes), retType);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
mth.addWarnComment("Type validation failed for signature: " + sp.getSignature(), e); mth.addWarnComment("Type validation failed for signature: " + sp.getSignature(), e);
@@ -154,7 +154,7 @@ public class SignatureProcessor extends AbstractVisitor {
return newArgTypes; return newArgTypes;
} }
} }
mth.addWarnComment("Incorrect args count in method signature: " + sp.getSignature()); mth.addDebugComment("Incorrect args count in method signature: " + sp.getSignature());
return null; return null;
} }
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
@@ -38,6 +38,7 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnList; import jadx.core.utils.InsnList;
import jadx.core.utils.InsnRemover; import jadx.core.utils.InsnRemover;
@@ -82,7 +83,7 @@ public class SimplifyVisitor extends AbstractVisitor {
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
InsnNode insn = list.get(i); InsnNode insn = list.get(i);
int insnCount = list.size(); int insnCount = list.size();
InsnNode modInsn = simplifyInsn(mth, insn); InsnNode modInsn = simplifyInsn(mth, insn, null);
if (modInsn != null) { if (modInsn != null) {
modInsn.rebindArgs(); modInsn.rebindArgs();
if (i < list.size() && list.get(i) == insn) { if (i < list.size() && list.get(i) == insn) {
@@ -110,7 +111,7 @@ public class SimplifyVisitor extends AbstractVisitor {
for (InsnArg arg : insn.getArguments()) { for (InsnArg arg : insn.getArguments()) {
if (arg.isInsnWrap()) { if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn(); InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
InsnNode replaceInsn = simplifyInsn(mth, wrapInsn); InsnNode replaceInsn = simplifyInsn(mth, wrapInsn, insn);
if (replaceInsn != null) { if (replaceInsn != null) {
arg.wrapInstruction(mth, replaceInsn); arg.wrapInstruction(mth, replaceInsn);
InsnRemover.unbindInsn(mth, wrapInsn); InsnRemover.unbindInsn(mth, wrapInsn);
@@ -123,7 +124,7 @@ public class SimplifyVisitor extends AbstractVisitor {
} }
} }
private InsnNode simplifyInsn(MethodNode mth, InsnNode insn) { private InsnNode simplifyInsn(MethodNode mth, InsnNode insn, @Nullable InsnNode parentInsn) {
if (insn.contains(AFlag.DONT_GENERATE)) { if (insn.contains(AFlag.DONT_GENERATE)) {
return null; return null;
} }
@@ -146,8 +147,9 @@ public class SimplifyVisitor extends AbstractVisitor {
case SPUT: case SPUT:
return convertFieldArith(mth, insn); return convertFieldArith(mth, insn);
case CAST:
case CHECK_CAST: case CHECK_CAST:
return processCast(mth, (IndexInsnNode) insn); return processCast(mth, (IndexInsnNode) insn, parentInsn);
case MOVE: case MOVE:
InsnArg firstArg = insn.getArg(0); InsnArg firstArg = insn.getArg(0);
@@ -212,7 +214,7 @@ public class SimplifyVisitor extends AbstractVisitor {
return null; return null;
} }
private static InsnNode processCast(MethodNode mth, IndexInsnNode castInsn) { private static InsnNode processCast(MethodNode mth, IndexInsnNode castInsn, @Nullable InsnNode parentInsn) {
if (castInsn.contains(AFlag.EXPLICIT_CAST)) { if (castInsn.contains(AFlag.EXPLICIT_CAST)) {
return null; return null;
} }
@@ -229,7 +231,8 @@ public class SimplifyVisitor extends AbstractVisitor {
ArgType castToType = (ArgType) castInsn.getIndex(); ArgType castToType = (ArgType) castInsn.getIndex();
if (!ArgType.isCastNeeded(mth.root(), argType, castToType) if (!ArgType.isCastNeeded(mth.root(), argType, castToType)
|| isCastDuplicate(castInsn)) { || isCastDuplicate(castInsn)
|| shadowedByOuterCast(mth.root(), castToType, parentInsn)) {
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1); InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
insnNode.setOffset(castInsn.getOffset()); insnNode.setOffset(castInsn.getOffset());
insnNode.setResult(castInsn.getResult()); insnNode.setResult(castInsn.getResult());
@@ -254,6 +257,15 @@ public class SimplifyVisitor extends AbstractVisitor {
return false; return false;
} }
private static boolean shadowedByOuterCast(RootNode root, ArgType castType, @Nullable InsnNode parentInsn) {
if (parentInsn != null && parentInsn.getType() == InsnType.CAST) {
ArgType parentCastType = (ArgType) ((IndexInsnNode) parentInsn).getIndex();
TypeCompareEnum result = root.getTypeCompare().compareTypes(parentCastType, castType);
return result.isNarrow();
}
return false;
}
/** /**
* Simplify 'cmp' instruction in if condition * Simplify 'cmp' instruction in if condition
*/ */
@@ -532,32 +544,44 @@ public class SimplifyVisitor extends AbstractVisitor {
if (arith.getArgsCount() != 2) { if (arith.getArgsCount() != 2) {
return null; return null;
} }
InsnArg litArg = null; LiteralArg litArg = null;
InsnArg secondArg = arith.getArg(1); InsnArg secondArg = arith.getArg(1);
if (secondArg.isInsnWrap()) { if (secondArg.isInsnWrap()) {
InsnNode wr = ((InsnWrapArg) secondArg).getWrapInsn(); InsnNode wr = ((InsnWrapArg) secondArg).getWrapInsn();
if (wr.getType() == InsnType.CONST) { if (wr.getType() == InsnType.CONST) {
litArg = wr.getArg(0); InsnArg arg = wr.getArg(0);
if (arg.isLiteral()) {
litArg = (LiteralArg) arg;
}
} }
} else if (secondArg.isLiteral()) { } else if (secondArg.isLiteral()) {
litArg = secondArg; litArg = (LiteralArg) secondArg;
} }
if (litArg != null) { if (litArg == null) {
long lit = ((LiteralArg) litArg).getLiteral(); return null;
// fix 'c + (-1)' => 'c - (1)' }
if (arith.getOp() == ArithOp.ADD && lit < 0) { switch (arith.getOp()) {
return new ArithNode(ArithOp.SUB, case ADD:
arith.getResult(), arith.getArg(0), // fix 'c + (-1)' to 'c - (1)'
InsnArg.lit(-lit, litArg.getType())); if (litArg.isNegative()) {
} LiteralArg negLitArg = litArg.negate();
InsnArg firstArg = arith.getArg(0); if (negLitArg != null) {
if (arith.getOp() == ArithOp.XOR && firstArg.getType() == ArgType.BOOLEAN return new ArithNode(ArithOp.SUB, arith.getResult(), arith.getArg(0), negLitArg);
&& (lit == 0 || lit == 1)) { }
InsnNode node = new InsnNode(lit == 0 ? InsnType.MOVE : InsnType.NOT, 1); }
node.setResult(arith.getResult()); break;
node.addArg(firstArg);
return node; case XOR:
} // simplify xor on boolean
InsnArg firstArg = arith.getArg(0);
long lit = litArg.getLiteral();
if (firstArg.getType() == ArgType.BOOLEAN && (lit == 0 || lit == 1)) {
InsnNode node = new InsnNode(lit == 0 ? InsnType.MOVE : InsnType.NOT, 1);
node.setResult(arith.getResult());
node.addArg(firstArg);
return node;
}
break;
} }
return null; return null;
} }
@@ -49,6 +49,7 @@ public class BlockExceptionHandler {
if (mth.isNoExceptionHandlers()) { if (mth.isNoExceptionHandlers()) {
return false; return false;
} }
BlockProcessor.updateCleanSuccessors(mth);
BlockProcessor.computeDominanceFrontier(mth); BlockProcessor.computeDominanceFrontier(mth);
processCatchAttr(mth); processCatchAttr(mth);
@@ -322,23 +323,14 @@ public class BlockExceptionHandler {
private static boolean wrapBlocksWithTryCatch(MethodNode mth, TryCatchBlockAttr tryCatchBlock) { private static boolean wrapBlocksWithTryCatch(MethodNode mth, TryCatchBlockAttr tryCatchBlock) {
List<BlockNode> blocks = tryCatchBlock.getBlocks(); List<BlockNode> blocks = tryCatchBlock.getBlocks();
BlockNode top = searchTopBlock(mth, blocks); BlockNode top = searchTopBlock(mth, blocks);
if (top.getPredecessors().isEmpty()) { if (top.getPredecessors().isEmpty() && top != mth.getEnterBlock()) {
return false; return false;
} }
BlockNode bottom = searchBottomBlock(mth, blocks); BlockNode bottom = searchBottomBlock(mth, blocks);
if (Consts.DEBUG_EXC_HANDLERS) { if (Consts.DEBUG_EXC_HANDLERS) {
LOG.debug("TryCatch #{} split: top {}, bottom: {}", tryCatchBlock.id(), top, bottom); LOG.debug("TryCatch #{} split: top {}, bottom: {}", tryCatchBlock.id(), top, bottom);
} }
BlockNode topSplitterBlock = getTopSplitterBlock(mth, top);
BlockNode topSplitterBlock;
if (top == mth.getEnterBlock()) {
BlockNode fixedTop = mth.getEnterBlock().getSuccessors().get(0);
topSplitterBlock = BlockSplitter.blockSplitTop(mth, fixedTop);
} else {
BlockNode existTopSplitter = BlockUtils.getBlockWithFlag(top.getPredecessors(), AFlag.EXC_TOP_SPLITTER);
topSplitterBlock = existTopSplitter != null ? existTopSplitter : BlockSplitter.blockSplitTop(mth, top);
}
topSplitterBlock.add(AFlag.EXC_TOP_SPLITTER); topSplitterBlock.add(AFlag.EXC_TOP_SPLITTER);
topSplitterBlock.add(AFlag.SYNTHETIC); topSplitterBlock.add(AFlag.SYNTHETIC);
@@ -355,6 +347,10 @@ public class BlockExceptionHandler {
BlockSplitter.connect(bottom, bottomSplitterBlock); BlockSplitter.connect(bottom, bottomSplitterBlock);
} }
if (Consts.DEBUG_EXC_HANDLERS) {
LOG.debug("TryCatch #{} result splitters: top {}, bottom: {}",
tryCatchBlock.id(), topSplitterBlock, bottomSplitterBlock);
}
connectSplittersAndHandlers(tryCatchBlock, topSplitterBlock, bottomSplitterBlock); connectSplittersAndHandlers(tryCatchBlock, topSplitterBlock, bottomSplitterBlock);
for (BlockNode block : blocks) { for (BlockNode block : blocks) {
@@ -372,6 +368,25 @@ public class BlockExceptionHandler {
return true; return true;
} }
private static BlockNode getTopSplitterBlock(MethodNode mth, BlockNode top) {
if (top == mth.getEnterBlock()) {
BlockNode fixedTop = mth.getEnterBlock().getSuccessors().get(0);
return BlockSplitter.blockSplitTop(mth, fixedTop);
}
BlockNode existPredTopSplitter = BlockUtils.getBlockWithFlag(top.getPredecessors(), AFlag.EXC_TOP_SPLITTER);
if (existPredTopSplitter != null) {
return existPredTopSplitter;
}
// try to reuse exists splitter on empty simple path below top block
if (top.getCleanSuccessors().size() == 1 && top.getInstructions().isEmpty()) {
BlockNode otherTopSplitter = BlockUtils.getBlockWithFlag(top.getCleanSuccessors(), AFlag.EXC_TOP_SPLITTER);
if (otherTopSplitter != null && otherTopSplitter.getPredecessors().size() == 1) {
return otherTopSplitter;
}
}
return BlockSplitter.blockSplitTop(mth, top);
}
private static BlockNode searchTopBlock(MethodNode mth, List<BlockNode> blocks) { private static BlockNode searchTopBlock(MethodNode mth, List<BlockNode> blocks) {
BlockNode top = BlockUtils.getTopBlock(blocks); BlockNode top = BlockUtils.getTopBlock(blocks);
if (top != null) { if (top != null) {
@@ -394,16 +409,32 @@ public class BlockExceptionHandler {
// not found -> blocks don't have same dominator // not found -> blocks don't have same dominator
// try to search common cross block outside input set // try to search common cross block outside input set
// NOTE: bottom block not needed for exit nodes (no data flow from them) // NOTE: bottom block not needed for exit nodes (no data flow from them)
return BlockUtils.getPathCross(mth, blocks); BlockNode pathCross = BlockUtils.getPathCross(mth, blocks);
if (pathCross == null) {
return null;
}
List<BlockNode> preds = new ArrayList<>(pathCross.getPredecessors());
preds.removeAll(blocks);
List<BlockNode> outsidePredecessors = preds.stream()
.filter(p -> !BlockUtils.atLeastOnePathExists(blocks, p))
.collect(Collectors.toList());
if (outsidePredecessors.isEmpty()) {
return pathCross;
}
// some predecessors outside of input set paths -> split block only for input set
BlockNode splitCross = BlockSplitter.blockSplitTop(mth, pathCross);
splitCross.add(AFlag.SYNTHETIC);
for (BlockNode outsidePredecessor : outsidePredecessors) {
// return predecessors to split bottom block (original)
BlockSplitter.replaceConnection(outsidePredecessor, splitCross, pathCross);
}
return splitCross;
} }
private static void connectSplittersAndHandlers(TryCatchBlockAttr tryCatchBlock, BlockNode topSplitterBlock, private static void connectSplittersAndHandlers(TryCatchBlockAttr tryCatchBlock, BlockNode topSplitterBlock,
@Nullable BlockNode bottomSplitterBlock) { @Nullable BlockNode bottomSplitterBlock) {
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) { for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
BlockNode handlerBlock = handler.getHandlerBlock(); BlockNode handlerBlock = handler.getHandlerBlock();
if (handlerBlock == null) {
System.out.println();
}
BlockSplitter.connect(topSplitterBlock, handlerBlock); BlockSplitter.connect(topSplitterBlock, handlerBlock);
if (bottomSplitterBlock != null) { if (bottomSplitterBlock != null) {
BlockSplitter.connect(bottomSplitterBlock, handlerBlock); BlockSplitter.connect(bottomSplitterBlock, handlerBlock);
@@ -76,7 +76,7 @@ public class BlockProcessor extends AbstractVisitor {
mth.finishBasicBlocks(); mth.finishBasicBlocks();
} }
private static void updateCleanSuccessors(MethodNode mth) { static void updateCleanSuccessors(MethodNode mth) {
mth.getBasicBlocks().forEach(BlockNode::updateCleanSuccessors); mth.getBasicBlocks().forEach(BlockNode::updateCleanSuccessors);
} }
@@ -0,0 +1,226 @@
package jadx.core.dex.visitors.kotlin;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs.UseKotlinMethodsForVarNames;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.rename.CodeRenameVisitor;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
name = "ProcessKotlinInternals",
desc = "Use variable names from Kotlin intrinsic1 methods",
runAfter = {
InitCodeVariables.class,
DebugInfoApplyVisitor.class
},
runBefore = {
CodeRenameVisitor.class
}
)
public class ProcessKotlinInternals extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(ProcessKotlinInternals.class);
private static final String KOTLIN_INTERNAL_PKG = "kotlin.jvm.internal.";
private static final String KOTLIN_INTRINSICS_CLS = KOTLIN_INTERNAL_PKG + "Intrinsics";
private static final String KOTLIN_VARNAME_SOURCE_MTH1 = "(Ljava/lang/Object;Ljava/lang/String;)V";
private static final String KOTLIN_VARNAME_SOURCE_MTH2 = "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V";
private @Nullable ClassInfo kotlinIntrinsicsCls;
private Set<MethodInfo> kotlinVarNameSourceMethods;
private boolean hideInsns;
@Override
public void init(RootNode root) throws JadxException {
ClassNode kotlinCls = searchKotlinIntrinsicsClass(root);
if (kotlinCls != null) {
kotlinIntrinsicsCls = kotlinCls.getClassInfo();
kotlinVarNameSourceMethods = collectMethods(kotlinCls);
LOG.debug("Kotlin Intrinsics class: {}, methods: {}", kotlinCls, kotlinVarNameSourceMethods.size());
} else {
kotlinIntrinsicsCls = null;
LOG.debug("Kotlin Intrinsics class not found");
}
hideInsns = root.getArgs().getUseKotlinMethodsForVarNames() == UseKotlinMethodsForVarNames.APPLY_AND_HIDE;
}
@Override
public boolean visit(ClassNode cls) {
if (kotlinIntrinsicsCls == null) {
return false;
}
for (MethodNode mth : cls.getMethods()) {
processMth(mth);
}
return true;
}
private void processMth(MethodNode mth) {
if (mth.isNoCode()) {
return;
}
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
if (insn.getType() == InsnType.INVOKE) {
try {
processInvoke(mth, insn);
} catch (Exception e) {
mth.addWarnComment("Failed to extract var names", e);
}
}
}
}
}
private void processInvoke(MethodNode mth, InsnNode insn) {
int argsCount = insn.getArgsCount();
if (argsCount < 2) {
return;
}
MethodInfo invokeMth = ((InvokeNode) insn).getCallMth();
if (!kotlinVarNameSourceMethods.contains(invokeMth)) {
return;
}
InsnArg firstArg = insn.getArg(0);
if (!firstArg.isRegister()) {
return;
}
RegisterArg varArg = (RegisterArg) firstArg;
boolean renamed = false;
if (argsCount == 2) {
String str = getConstString(mth, insn, 1);
if (str != null) {
renamed = checkAndRename(varArg, str);
}
} else if (argsCount == 3) {
// TODO: use second arg for rename class
String str = getConstString(mth, insn, 2);
if (str != null) {
renamed = checkAndRename(varArg, str);
}
}
if (renamed && hideInsns) {
insn.add(AFlag.DONT_GENERATE);
}
}
private boolean checkAndRename(RegisterArg arg, String str) {
String name = trimName(str);
if (NameMapper.isValidAndPrintable(name)) {
arg.getSVar().getCodeVar().setName(name);
return true;
}
return false;
}
@Nullable
private String getConstString(MethodNode mth, InsnNode insn, int arg) {
InsnArg strArg = insn.getArg(arg);
if (!strArg.isInsnWrap()) {
return null;
}
InsnNode constInsn = ((InsnWrapArg) strArg).getWrapInsn();
InsnType insnType = constInsn.getType();
if (insnType == InsnType.CONST_STR) {
return ((ConstStringNode) constInsn).getString();
}
if (insnType == InsnType.SGET) {
// revert const field inline :(
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) constInsn).getIndex();
FieldNode fieldNode = mth.root().resolveField(fieldInfo);
if (fieldNode != null) {
String str = (String) fieldNode.get(JadxAttrType.CONSTANT_VALUE).getValue();
InsnArg newArg = InsnArg.wrapArg(new ConstStringNode(str));
insn.replaceArg(strArg, newArg);
return str;
}
}
return null;
}
private String trimName(String str) {
if (str.startsWith("$this$")) {
return str.substring(6);
}
if (str.startsWith("$")) {
return str.substring(1);
}
return str;
}
@Nullable
private static ClassNode searchKotlinIntrinsicsClass(RootNode root) {
ClassNode kotlinCls = root.resolveClass(KOTLIN_INTRINSICS_CLS);
if (kotlinCls != null) {
return kotlinCls;
}
List<ClassNode> candidates = new ArrayList<>();
for (ClassNode cls : root.getClasses()) {
if (isKotlinIntrinsicsClass(cls)) {
candidates.add(cls);
}
}
return Utils.getOne(candidates);
}
private static boolean isKotlinIntrinsicsClass(ClassNode cls) {
if (!cls.getClassInfo().getFullName().startsWith(KOTLIN_INTERNAL_PKG)) {
return false;
}
if (cls.getMethods().size() < 5) {
return false;
}
int mthCount = 0;
for (MethodNode mth : cls.getMethods()) {
if (mth.getAccessFlags().isStatic()
&& mth.getMethodInfo().getShortId().endsWith(KOTLIN_VARNAME_SOURCE_MTH1)) {
mthCount++;
}
}
return mthCount > 2;
}
private Set<MethodInfo> collectMethods(ClassNode kotlinCls) {
Set<MethodInfo> set = new HashSet<>();
for (MethodNode mth : kotlinCls.getMethods()) {
if (!mth.getAccessFlags().isStatic()) {
continue;
}
String shortId = mth.getMethodInfo().getShortId();
if (shortId.endsWith(KOTLIN_VARNAME_SOURCE_MTH1) || shortId.endsWith(KOTLIN_VARNAME_SOURCE_MTH2)) {
set.add(mth.getMethodInfo());
}
}
return set;
}
}
@@ -216,7 +216,7 @@ public class RegionMaker {
// invert loop condition if 'then' points to exit // invert loop condition if 'then' points to exit
condInfo = IfInfo.invert(condInfo); condInfo = IfInfo.invert(condInfo);
} }
loopRegion.setCondition(condInfo.getCondition()); loopRegion.updateCondition(condInfo);
exitBlocks.removeAll(condInfo.getMergedBlocks()); exitBlocks.removeAll(condInfo.getMergedBlocks());
if (!exitBlocks.isEmpty()) { if (!exitBlocks.isEmpty()) {
@@ -720,8 +720,7 @@ public class RegionMaker {
confirmMerge(currentIf); confirmMerge(currentIf);
IfRegion ifRegion = new IfRegion(currentRegion); IfRegion ifRegion = new IfRegion(currentRegion);
ifRegion.setCondition(currentIf.getCondition()); ifRegion.updateCondition(currentIf);
ifRegion.setConditionBlocks(currentIf.getMergedBlocks());
currentRegion.getSubBlocks().add(ifRegion); currentRegion.getSubBlocks().add(ifRegion);
BlockNode outBlock = currentIf.getOutBlock(); BlockNode outBlock = currentIf.getOutBlock();
@@ -97,7 +97,7 @@ public class RenameVisitor extends AbstractVisitor {
// check inner classes names // check inner classes names
ClassInfo parentClass = classInfo.getParentClass(); ClassInfo parentClass = classInfo.getParentClass();
while (parentClass != null) { while (parentClass != null) {
if (parentClass.getAliasShortName().equals(clsName)) { if (parentClass.getAliasShortName().equals(newShortName)) {
String clsAlias = deobfuscator.getClsAlias(cls); String clsAlias = deobfuscator.getClsAlias(cls);
classInfo.changeShortName(clsAlias); classInfo.changeShortName(clsAlias);
cls.addAttr(new RenameReasonAttr(cls).append("collision with other inner class name")); cls.addAttr(new RenameReasonAttr(cls).append("collision with other inner class name"));
@@ -98,6 +98,10 @@ public class TypeCompare {
|| secondPrimitiveType == PrimitiveType.BOOLEAN) { || secondPrimitiveType == PrimitiveType.BOOLEAN) {
return CONFLICT; return CONFLICT;
} }
if (swapEquals(firstPrimitiveType, secondPrimitiveType, PrimitiveType.CHAR, PrimitiveType.BYTE)
|| swapEquals(firstPrimitiveType, secondPrimitiveType, PrimitiveType.CHAR, PrimitiveType.SHORT)) {
return CONFLICT;
}
return firstPrimitiveType.compareTo(secondPrimitiveType) > 0 ? WIDER : NARROW; return firstPrimitiveType.compareTo(secondPrimitiveType) > 0 ? WIDER : NARROW;
} }
@@ -105,6 +109,10 @@ public class TypeCompare {
return TypeCompareEnum.CONFLICT; return TypeCompareEnum.CONFLICT;
} }
private boolean swapEquals(PrimitiveType first, PrimitiveType second, PrimitiveType a, PrimitiveType b) {
return (first == a && second == b) || (first == b && second == a);
}
private TypeCompareEnum compareArrayWithOtherType(ArgType array, ArgType other) { private TypeCompareEnum compareArrayWithOtherType(ArgType array, ArgType other) {
if (!other.isTypeKnown()) { if (!other.isTypeKnown()) {
if (other.contains(PrimitiveType.ARRAY)) { if (other.contains(PrimitiveType.ARRAY)) {
@@ -19,6 +19,7 @@ import jadx.core.Consts;
import jadx.core.clsp.ClspGraph; import jadx.core.clsp.ClspGraph;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassBaseAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithNode;
@@ -35,12 +36,15 @@ import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn; import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.nodes.utils.MethodUtils;
import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.AttachMethodDetails; import jadx.core.dex.visitors.AttachMethodDetails;
@@ -273,6 +277,11 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, clsType)); addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, clsType));
break; break;
case CONSTRUCTOR:
ArgType ctrClsType = replaceAnonymousType((ConstructorInsn) insn);
addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, ctrClsType));
break;
case CONST: case CONST:
LiteralArg constLit = (LiteralArg) insn.getArg(0); LiteralArg constLit = (LiteralArg) insn.getArg(0);
addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, constLit.getType())); addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, constLit.getType()));
@@ -308,6 +317,19 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
} }
} }
private ArgType replaceAnonymousType(ConstructorInsn ctr) {
if (ctr.isNewInstance()) {
ClassNode ctrCls = root.resolveClass(ctr.getClassType());
if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) {
AnonymousClassBaseAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS_BASE);
if (baseTypeAttr != null) {
return baseTypeAttr.getBaseType();
}
}
}
return ctr.getClassType().getType();
}
private ITypeBound makeAssignFieldGetBound(IndexInsnNode insn) { private ITypeBound makeAssignFieldGetBound(IndexInsnNode insn) {
ArgType initType = insn.getResult().getInitType(); ArgType initType = insn.getResult().getInitType();
if (initType.containsTypeVariable()) { if (initType.containsTypeVariable()) {
@@ -340,7 +362,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
return null; return null;
} }
if (insn instanceof BaseInvokeNode) { if (insn instanceof BaseInvokeNode) {
TypeBoundInvokeUse invokeUseBound = makeInvokeUseBound(regArg, (BaseInvokeNode) insn); ITypeBound invokeUseBound = makeInvokeUseBound(regArg, (BaseInvokeNode) insn);
if (invokeUseBound != null) { if (invokeUseBound != null) {
return invokeUseBound; return invokeUseBound;
} }
@@ -352,21 +374,32 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
return new TypeBoundConst(BoundEnum.USE, regArg.getInitType(), regArg); return new TypeBoundConst(BoundEnum.USE, regArg.getInitType(), regArg);
} }
private TypeBoundInvokeUse makeInvokeUseBound(RegisterArg regArg, BaseInvokeNode invoke) { private ITypeBound makeInvokeUseBound(RegisterArg regArg, BaseInvokeNode invoke) {
InsnArg instanceArg = invoke.getInstanceArg(); InsnArg instanceArg = invoke.getInstanceArg();
if (instanceArg == null || instanceArg == regArg) { if (instanceArg == null) {
return null; return null;
} }
IMethodDetails methodDetails = root.getMethodUtils().getMethodDetails(invoke); MethodUtils methodUtils = root.getMethodUtils();
IMethodDetails methodDetails = methodUtils.getMethodDetails(invoke);
if (methodDetails == null) { if (methodDetails == null) {
return null; return null;
} }
int argIndex = invoke.getArgIndex(regArg) - invoke.getFirstArgOffset(); if (instanceArg != regArg) {
ArgType argType = methodDetails.getArgTypes().get(argIndex); int argIndex = invoke.getArgIndex(regArg) - invoke.getFirstArgOffset();
if (!argType.containsTypeVariable()) { ArgType argType = methodDetails.getArgTypes().get(argIndex);
return null; if (!argType.containsTypeVariable()) {
return null;
}
return new TypeBoundInvokeUse(root, invoke, regArg, argType);
} }
return new TypeBoundInvokeUse(root, invoke, regArg, argType);
// for override methods use origin declared class as type
if (methodDetails instanceof MethodNode) {
MethodNode callMth = (MethodNode) methodDetails;
ClassInfo declCls = methodUtils.getMethodOriginDeclClass(callMth);
return new TypeBoundConst(BoundEnum.USE, declCls.getType(), regArg);
}
return null;
} }
private boolean tryPossibleTypes(MethodNode mth, SSAVar var, ArgType type) { private boolean tryPossibleTypes(MethodNode mth, SSAVar var, ArgType type) {
@@ -8,7 +8,6 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -24,6 +23,7 @@ import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils; import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.ResContainer; import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.XmlSecurity;
public class ExportGradleProject { public class ExportGradleProject {
@@ -139,7 +139,7 @@ public class ExportGradleProject {
private Document parseXml(String xmlContent) { private Document parseXml(String xmlContent) {
try { try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); DocumentBuilder builder = XmlSecurity.getSecureDbf().newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(xmlContent))); Document document = builder.parse(new InputSource(new StringReader(xmlContent)));
document.getDocumentElement().normalize(); document.getDocumentElement().normalize();
@@ -524,7 +524,22 @@ public class BlockUtils {
return false; return false;
} }
public static boolean isPathExists(Collection<BlockNode> startBlocks, BlockNode end) { /**
* Search at least one path from startBlocks to end
*/
public static boolean atLeastOnePathExists(Collection<BlockNode> startBlocks, BlockNode end) {
for (BlockNode startBlock : startBlocks) {
if (isPathExists(startBlock, end)) {
return true;
}
}
return false;
}
/**
* Check if exist path from every startBlocks to end
*/
public static boolean isAllPathExists(Collection<BlockNode> startBlocks, BlockNode end) {
for (BlockNode startBlock : startBlocks) { for (BlockNode startBlock : startBlocks) {
if (!isPathExists(startBlock, end)) { if (!isPathExists(startBlock, end)) {
return false; return false;
@@ -557,9 +572,9 @@ public class BlockUtils {
return traverseSuccessorsUntil(start, end, new BitSet(), false); return traverseSuccessorsUntil(start, end, new BitSet(), false);
} }
public static BlockNode getTopBlock(Collection<BlockNode> blocks) { public static BlockNode getTopBlock(List<BlockNode> blocks) {
if (blocks.size() == 1) { if (blocks.size() == 1) {
return blocks.iterator().next(); return blocks.get(0);
} }
for (BlockNode from : blocks) { for (BlockNode from : blocks) {
boolean top = true; boolean top = true;
@@ -579,9 +594,9 @@ public class BlockUtils {
/** /**
* Search last block in control flow graph from input set. * Search last block in control flow graph from input set.
*/ */
public static BlockNode getBottomBlock(Collection<BlockNode> blocks) { public static BlockNode getBottomBlock(List<BlockNode> blocks) {
if (blocks.size() == 1) { if (blocks.size() == 1) {
return blocks.iterator().next(); return blocks.get(0);
} }
for (BlockNode bottomCandidate : blocks) { for (BlockNode bottomCandidate : blocks) {
boolean bottom = true; boolean bottom = true;
@@ -18,6 +18,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.ICodeWriter; import jadx.api.ICodeWriter;
import jadx.api.impl.SimpleCodeWriter; import jadx.api.impl.SimpleCodeWriter;
import jadx.core.codegen.ConditionGen;
import jadx.core.codegen.InsnGen; import jadx.core.codegen.InsnGen;
import jadx.core.codegen.MethodGen; import jadx.core.codegen.MethodGen;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
@@ -31,6 +32,8 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.DotGraphVisitor; import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.IDexTreeVisitor;
@@ -53,6 +56,16 @@ public class DebugUtils {
dump(mth, "dump"); dump(mth, "dump");
} }
public static void dumpRaw(MethodNode mth, String desc, Predicate<MethodNode> dumpCondition) {
if (dumpCondition.test(mth)) {
dumpRaw(mth, desc);
}
}
public static void dumpRawTest(MethodNode mth, String desc) {
dumpRaw(mth, desc, method -> method.getName().equals("test"));
}
public static void dumpRaw(MethodNode mth, String desc) { public static void dumpRaw(MethodNode mth, String desc) {
File out = new File("test-graph-" + desc + "-tmp"); File out = new File("test-graph-" + desc + "-tmp");
DotGraphVisitor.dumpRaw().save(out, mth); DotGraphVisitor.dumpRaw().save(out, mth);
@@ -129,6 +142,7 @@ public class DebugUtils {
private static void printRegion(MethodNode mth, IRegion region, ICodeWriter cw, String indent, boolean printInsns) { private static void printRegion(MethodNode mth, IRegion region, ICodeWriter cw, String indent, boolean printInsns) {
printWithAttributes(cw, indent, region.toString(), region); printWithAttributes(cw, indent, region.toString(), region);
indent += "| "; indent += "| ";
printRegionSpecificInfo(cw, indent, mth, region, printInsns);
for (IContainer container : region.getSubBlocks()) { for (IContainer container : region.getSubBlocks()) {
if (container instanceof IRegion) { if (container instanceof IRegion) {
printRegion(mth, (IRegion) container, cw, indent, printInsns); printRegion(mth, (IRegion) container, cw, indent, printInsns);
@@ -142,6 +156,23 @@ public class DebugUtils {
} }
} }
private static void printRegionSpecificInfo(ICodeWriter cw, String indent,
MethodNode mth, IRegion region, boolean printInsns) {
if (region instanceof LoopRegion) {
LoopRegion loop = (LoopRegion) region;
IfCondition condition = loop.getCondition();
if (printInsns && condition != null) {
ConditionGen conditionGen = new ConditionGen(new InsnGen(MethodGen.getFallbackMethodGen(mth), true));
cw.startLine(indent).add("|> ");
try {
conditionGen.add(cw, condition);
} catch (Exception e) {
cw.startLine(indent).add(">!! ").add(condition.toString());
}
}
}
}
private static void printInsns(MethodNode mth, ICodeWriter cw, String indent, IBlock block) { private static void printInsns(MethodNode mth, ICodeWriter cw, String indent, IBlock block) {
for (InsnNode insn : block.getInstructions()) { for (InsnNode insn : block.getInstructions()) {
try { try {
@@ -206,4 +206,16 @@ public class InsnUtils {
insn.add(AFlag.DONT_GENERATE); insn.add(AFlag.DONT_GENERATE);
return true; return true;
} }
public static <T extends InsnArg> boolean containsVar(List<T> list, RegisterArg arg) {
if (list == null || list.isEmpty()) {
return false;
}
for (InsnArg insnArg : list) {
if (insnArg == arg || arg.sameRegAndSVar(insnArg)) {
return true;
}
}
return false;
}
} }
@@ -7,6 +7,7 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -62,8 +63,75 @@ public class ListUtils {
newList.add(newObj); newList.add(newObj);
return newList; return newList;
} }
list.remove(oldObj); int idx = list.indexOf(oldObj);
list.add(newObj); if (idx != -1) {
list.set(idx, newObj);
} else {
list.add(newObj);
}
return list; return list;
} }
public static <T> void safeRemove(List<T> list, T obj) {
if (list != null && !list.isEmpty()) {
list.remove(obj);
}
}
public static <T> List<T> safeRemoveAndTrim(List<T> list, T obj) {
if (list == null || list.isEmpty()) {
return list;
}
if (list.remove(obj)) {
if (list.isEmpty()) {
return Collections.emptyList();
}
}
return list;
}
public static <T> List<T> safeAdd(List<T> list, T obj) {
if (list == null || list.isEmpty()) {
List<T> newList = new ArrayList<>(1);
newList.add(obj);
return newList;
}
list.add(obj);
return list;
}
/**
* Search exactly one element in list by filter
*
* @return null if found not exactly one element (zero or more than one)
*/
@Nullable
public static <T> T filterOnlyOne(List<T> list, Predicate<T> filter) {
if (list == null || list.isEmpty()) {
return null;
}
T found = null;
for (T element : list) {
if (filter.test(element)) {
if (found != null) {
// found second
return null;
}
found = element;
}
}
return found;
}
public static <T> boolean allMatch(List<T> list, Predicate<T> test) {
if (list == null || list.isEmpty()) {
return false;
}
for (T element : list) {
if (!test.test(element)) {
return false;
}
}
return true;
}
} }
@@ -8,10 +8,12 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -284,6 +286,19 @@ public class Utils {
return result; return result;
} }
public static <T> Set<T> mergeSets(Set<T> first, Set<T> second) {
if (isEmpty(first)) {
return second;
}
if (isEmpty(second)) {
return first;
}
Set<T> result = new HashSet<>(first.size() + second.size());
result.addAll(first);
result.addAll(second);
return result;
}
public static Map<String, String> newConstStringMap(String... parameters) { public static Map<String, String> newConstStringMap(String... parameters) {
int len = parameters.length; int len = parameters.length;
if (len == 0) { if (len == 0) {
@@ -315,6 +330,21 @@ public class Utils {
return result; return result;
} }
/**
* Build map from list of values with value to key mapping function
* <br>
* Similar to:
* <br>
* {@code list.stream().collect(Collectors.toMap(mapKey, Function.identity())); }
*/
public static <K, V> Map<K, V> groupBy(List<V> list, Function<V, K> mapKey) {
Map<K, V> map = new HashMap<>(list.size());
for (V v : list) {
map.put(mapKey.apply(v), v);
}
return map;
}
@Nullable @Nullable
public static <T> T getOne(@Nullable List<T> list) { public static <T> T getOne(@Nullable List<T> list) {
if (list == null || list.size() != 1) { if (list == null || list.size() != 1) {
@@ -323,6 +353,14 @@ public class Utils {
return list.get(0); return list.get(0);
} }
@Nullable
public static <T> T getOne(@Nullable Collection<T> collection) {
if (collection == null || collection.size() != 1) {
return null;
}
return collection.iterator().next();
}
@Nullable @Nullable
public static <T> T first(List<T> list) { public static <T> T first(List<T> list) {
if (list.isEmpty()) { if (list.isEmpty()) {
@@ -20,7 +20,7 @@ import java.io.DataInput;
import java.io.IOException; import java.io.IOException;
/** /**
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski "brut.alll@gmail.com"
*/ */
public abstract class DataInputDelegate implements DataInput { public abstract class DataInputDelegate implements DataInput {
protected final DataInput mDelegate; protected final DataInput mDelegate;
@@ -22,7 +22,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
/** /**
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski "brut.alll@gmail.com"
*/ */
public class ExtDataInput extends DataInputDelegate { public class ExtDataInput extends DataInputDelegate {
public ExtDataInput(InputStream in) { public ExtDataInput(InputStream in) {
@@ -27,7 +27,7 @@ import javax.imageio.ImageIO;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
/** /**
* @author Ryszard Wiśniewski <brut.alll@gmail.com> * @author Ryszard Wiśniewski "brut.alll@gmail.com"
*/ */
public class Res9patchStreamDecoder { public class Res9patchStreamDecoder {
@@ -14,14 +14,11 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -75,7 +72,7 @@ public class FileUtils {
public static void makeDirsForFile(Path path) { public static void makeDirsForFile(Path path) {
if (path != null) { if (path != null) {
makeDirs(path.getParent().toFile()); makeDirs(path.toAbsolutePath().getParent().toFile());
} }
} }
@@ -117,7 +114,11 @@ public class FileUtils {
try (Stream<Path> pathStream = Files.walk(dir)) { try (Stream<Path> pathStream = Files.walk(dir)) {
pathStream.sorted(Comparator.reverseOrder()) pathStream.sorted(Comparator.reverseOrder())
.map(Path::toFile) .map(Path::toFile)
.forEach(File::delete); .forEach(file -> {
if (!file.delete()) {
LOG.warn("Failed to remove file: {}", file.getAbsolutePath());
}
});
} catch (Exception e) { } catch (Exception e) {
throw new JadxRuntimeException("Failed to delete directory " + dir, e); throw new JadxRuntimeException("Failed to delete directory " + dir, e);
} }
@@ -259,45 +260,6 @@ public class FileUtils {
return false; return false;
} }
private static List<String> getZipFileList(File file) {
List<String> filesList = new ArrayList<>();
try (ZipFile zipFile = new ZipFile(file)) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
filesList.add(entry.getName());
}
} catch (Exception e) {
LOG.error("Error read zip file '{}'", file.getAbsolutePath(), e);
}
return filesList;
}
public static boolean isApkFile(File file) {
if (!isZipFile(file)) {
return false;
}
List<String> filesList = getZipFileList(file);
return filesList.contains("AndroidManifest.xml")
&& filesList.contains("classes.dex");
}
public static boolean isZipDexFile(File file) {
if (!isZipFile(file) || !isZipFileCanBeOpen(file)) {
return false;
}
List<String> filesList = getZipFileList(file);
return filesList.contains("classes.dex");
}
private static boolean isZipFileCanBeOpen(File file) {
try (ZipFile zipFile = new ZipFile(file)) {
return zipFile.entries().hasMoreElements();
} catch (Exception e) {
return false;
}
}
public static String getPathBaseName(Path file) { public static String getPathBaseName(Path file) {
String fileName = file.getFileName().toString(); String fileName = file.getFileName().toString();
int extEndIndex = fileName.lastIndexOf('.'); int extEndIndex = fileName.lastIndexOf('.');
@@ -56,6 +56,8 @@ public class BinaryXMLParser extends CommonBinaryParser {
private final RootNode rootNode; private final RootNode rootNode;
private String appPackageName; private String appPackageName;
private Map<String, ClassNode> classNameCache;
public BinaryXMLParser(RootNode rootNode) { public BinaryXMLParser(RootNode rootNode) {
this.rootNode = rootNode; this.rootNode = rootNode;
try { try {
@@ -78,7 +80,9 @@ public class BinaryXMLParser extends CommonBinaryParser {
firstElement = true; firstElement = true;
decode(); decode();
nsMap = null; nsMap = null;
return writer.finish(); ICodeInfo codeInfo = writer.finish();
this.classNameCache = null; // reset class name cache
return codeInfo;
} }
private boolean isBinaryXml() throws IOException { private boolean isBinaryXml() throws IOException {
@@ -467,6 +471,9 @@ public class BinaryXMLParser extends CommonBinaryParser {
} }
private void attachClassNode(ICodeWriter writer, String attrName, String clsName) { private void attachClassNode(ICodeWriter writer, String attrName, String clsName) {
if (!writer.isMetadataSupported()) {
return;
}
if (clsName == null || !attrName.equals("name")) { if (clsName == null || !attrName.equals("name")) {
return; return;
} }
@@ -476,7 +483,10 @@ public class BinaryXMLParser extends CommonBinaryParser {
} else { } else {
clsFullName = clsName; clsFullName = clsName;
} }
ClassNode classNode = rootNode.searchClassByFullAlias(clsFullName); if (classNameCache == null) {
classNameCache = rootNode.buildFullAliasClassCache();
}
ClassNode classNode = classNameCache.get(clsFullName);
if (classNode != null) { if (classNode != null) {
writer.attachAnnotation(classNode); writer.attachAnnotation(classNode);
} }
@@ -109,7 +109,7 @@ public class ValuesParser extends ParserConstants {
case TYPE_INT_DEC: case TYPE_INT_DEC:
return Integer.toString(data); return Integer.toString(data);
case TYPE_INT_HEX: case TYPE_INT_HEX:
return Integer.toHexString(data); return "0x" + Integer.toHexString(data);
case TYPE_INT_BOOLEAN: case TYPE_INT_BOOLEAN:
return data == 0 ? "false" : "true"; return data == 0 ? "false" : "true";
case TYPE_FLOAT: case TYPE_FLOAT:
@@ -63,10 +63,15 @@ public class TypeCompareTest {
public void comparePrimitives() { public void comparePrimitives() {
check(INT, UNKNOWN_OBJECT, TypeCompareEnum.CONFLICT); check(INT, UNKNOWN_OBJECT, TypeCompareEnum.CONFLICT);
check(INT, OBJECT, TypeCompareEnum.CONFLICT); check(INT, OBJECT, TypeCompareEnum.CONFLICT);
check(INT, BOOLEAN, TypeCompareEnum.CONFLICT);
check(INT, CHAR, TypeCompareEnum.WIDER); check(INT, CHAR, TypeCompareEnum.WIDER);
check(INT, SHORT, TypeCompareEnum.WIDER); check(INT, SHORT, TypeCompareEnum.WIDER);
check(BOOLEAN, INT, TypeCompareEnum.CONFLICT);
check(BOOLEAN, CHAR, TypeCompareEnum.CONFLICT);
check(CHAR, BYTE, TypeCompareEnum.CONFLICT);
check(CHAR, SHORT, TypeCompareEnum.CONFLICT);
firstIsNarrow(CHAR, NARROW_INTEGRAL); firstIsNarrow(CHAR, NARROW_INTEGRAL);
firstIsNarrow(array(CHAR), UNKNOWN_OBJECT); firstIsNarrow(array(CHAR), UNKNOWN_OBJECT);
} }
@@ -20,6 +20,7 @@ import java.util.concurrent.TimeoutException;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -46,6 +47,7 @@ import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.ResourceStorage; import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry; import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.tests.api.compiler.DynamicCompiler; import jadx.tests.api.compiler.DynamicCompiler;
import jadx.tests.api.compiler.JavaUtils;
import jadx.tests.api.compiler.StaticCompiler; import jadx.tests.api.compiler.StaticCompiler;
import jadx.tests.api.utils.TestUtils; import jadx.tests.api.utils.TestUtils;
@@ -63,8 +65,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
public abstract class IntegrationTest extends TestUtils { public abstract class IntegrationTest extends TestUtils {
private static final Logger LOG = LoggerFactory.getLogger(IntegrationTest.class); private static final Logger LOG = LoggerFactory.getLogger(IntegrationTest.class);
private static final String TEST_DIRECTORY = "src/test/java"; private static final String TEST_DIRECTORY = "src/test/java";
private static final String TEST_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY; private static final String TEST_DIRECTORY2 = "jadx-core/" + TEST_DIRECTORY;
@@ -74,7 +76,7 @@ public abstract class IntegrationTest extends TestUtils {
/** /**
* Set 'TEST_INPUT_PLUGIN' env variable to use 'java' or 'dx' input in tests * Set 'TEST_INPUT_PLUGIN' env variable to use 'java' or 'dx' input in tests
*/ */
private static final boolean USE_JAVA_INPUT = Utils.getOrElse(System.getenv("TEST_INPUT_PLUGIN"), DEFAULT_INPUT_PLUGIN).equals("java"); static final boolean USE_JAVA_INPUT = Utils.getOrElse(System.getenv("TEST_INPUT_PLUGIN"), DEFAULT_INPUT_PLUGIN).equals("java");
/** /**
* Run auto check method if defined: * Run auto check method if defined:
@@ -148,7 +150,7 @@ public abstract class IntegrationTest extends TestUtils {
assertThat("File list is empty", files, not(empty())); assertThat("File list is empty", files, not(empty()));
return getClassNodeFromFiles(files, clazz.getName()); return getClassNodeFromFiles(files, clazz.getName());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); LOG.error("Failed to get class node", e);
fail(e.getMessage()); fail(e.getMessage());
} }
return null; return null;
@@ -184,18 +186,15 @@ public abstract class IntegrationTest extends TestUtils {
protected JadxDecompiler loadFiles(List<File> inputFiles) { protected JadxDecompiler loadFiles(List<File> inputFiles) {
args.setInputFiles(inputFiles); args.setInputFiles(inputFiles);
boolean useDx = !isJavaInput();
LOG.info(useDx ? "Using dex input" : "Using java input");
args.setUseDxInput(useDx);
JadxDecompiler d = new JadxDecompiler(args); JadxDecompiler d = new JadxDecompiler(args);
if (isJavaInput()) {
d.getPluginManager().unload("java-convert");
LOG.info("Using java input");
} else {
d.getPluginManager().unload("java-input");
LOG.info("Using dex input");
}
try { try {
d.load(); d.load();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); LOG.error("Load failed", e);
d.close(); d.close();
fail(e.getMessage()); fail(e.getMessage());
return null; return null;
@@ -333,7 +332,7 @@ public abstract class IntegrationTest extends TestUtils {
runDecompiledAutoCheck(cls); runDecompiledAutoCheck(cls);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); LOG.error("Auto check failed", e);
fail("Auto check exception: " + e.getMessage()); fail("Auto check exception: " + e.getMessage());
} }
} }
@@ -491,7 +490,8 @@ public abstract class IntegrationTest extends TestUtils {
this.useEclipseCompiler = true; this.useEclipseCompiler = true;
} }
protected void useTargetJavaVersion(int version) { public void useTargetJavaVersion(int version) {
Assumptions.assumeTrue(JavaUtils.checkJavaVersion(version), "skip test for higher java version");
this.targetJavaVersion = version; this.targetJavaVersion = version;
} }
@@ -523,11 +523,12 @@ public abstract class IntegrationTest extends TestUtils {
printOffsets = true; printOffsets = true;
} }
protected void useJavaInput() { public void useJavaInput() {
this.useJavaInput = true; this.useJavaInput = true;
} }
protected void useDexInput() { public void useDexInput() {
Assumptions.assumeFalse(USE_JAVA_INPUT, "skip dex input tests");
this.useJavaInput = false; this.useJavaInput = false;
} }
@@ -7,6 +7,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import jadx.api.JadxInternalAccess; import jadx.api.JadxInternalAccess;
@@ -24,6 +25,7 @@ public abstract class SmaliTest extends IntegrationTest {
@BeforeEach @BeforeEach
public void init() { public void init() {
Assumptions.assumeFalse(USE_JAVA_INPUT, "skip smali test for java input tests");
super.init(); super.init();
this.useDexInput(); this.useDexInput();
} }
@@ -0,0 +1,64 @@
package jadx.tests.api.extensions.profiles;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
import org.junit.platform.commons.util.AnnotationUtils;
import org.junit.platform.commons.util.Preconditions;
import jadx.tests.api.IntegrationTest;
import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated;
public class JadxTestProfilesExtension implements TestTemplateInvocationContextProvider {
@Override
public boolean supportsTestTemplate(ExtensionContext context) {
return isAnnotated(context.getTestMethod(), TestWithProfiles.class);
}
@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
Preconditions.condition(IntegrationTest.class.isAssignableFrom(context.getRequiredTestClass()),
"@TestWithProfiles should be used only in IntegrationTest subclasses");
Method testMethod = context.getRequiredTestMethod();
boolean testAnnAdded = AnnotationUtils.findAnnotation(testMethod, Test.class).isPresent();
Preconditions.condition(!testAnnAdded, "@Test annotation should be removed");
TestWithProfiles profilesAnn = AnnotationUtils.findAnnotation(testMethod, TestWithProfiles.class).get();
return Stream.of(profilesAnn.value())
.sorted()
.map(RunWithProfile::new);
}
private static class RunWithProfile implements TestTemplateInvocationContext {
private final TestProfile testProfile;
public RunWithProfile(TestProfile testProfile) {
this.testProfile = testProfile;
}
@Override
public String getDisplayName(int invocationIndex) {
return testProfile.getDescription();
}
@Override
public List<Extension> getAdditionalExtensions() {
return Collections.singletonList(beforeTest());
}
private BeforeTestExecutionCallback beforeTest() {
return execContext -> testProfile.accept((IntegrationTest) execContext.getRequiredTestInstance());
}
}
}
@@ -0,0 +1,41 @@
package jadx.tests.api.extensions.profiles;
import java.util.function.Consumer;
import jadx.tests.api.IntegrationTest;
public enum TestProfile implements Consumer<IntegrationTest> {
DX_J8("dx-java-8", test -> {
test.useTargetJavaVersion(8);
test.useDexInput();
}),
D8_J11("d8-java-11", test -> {
test.useTargetJavaVersion(11);
test.useDexInput();
}),
JAVA8("java-8", test -> {
test.useTargetJavaVersion(8);
test.useJavaInput();
}),
JAVA11("java-11", test -> {
test.useTargetJavaVersion(11);
test.useJavaInput();
});
private final String description;
private final Consumer<IntegrationTest> setup;
TestProfile(String description, Consumer<IntegrationTest> setup) {
this.description = description;
this.setup = setup;
}
@Override
public void accept(IntegrationTest integrationTest) {
this.setup.accept(integrationTest);
}
public String getDescription() {
return description;
}
}
@@ -0,0 +1,18 @@
package jadx.tests.api.extensions.profiles;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
@TestTemplate
@ExtendWith(JadxTestProfilesExtension.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestWithProfiles {
TestProfile[] value();
}
@@ -74,6 +74,13 @@ public class JadxCodeAssertions extends AbstractStringAssert<JadxCodeAssertions>
return newCode; return newCode;
} }
public JadxCodeAssertions removeLineComments() {
String code = actual.replaceAll("//.*(?!$)", "");
JadxCodeAssertions newCode = new JadxCodeAssertions(code);
newCode.print();
return newCode;
}
public JadxCodeAssertions print() { public JadxCodeAssertions print() {
System.out.println("-----------------------------------------------------------"); System.out.println("-----------------------------------------------------------");
System.out.println(actual); System.out.println(actual);
@@ -51,7 +51,6 @@ public abstract class BaseExternalTest extends IntegrationTest {
protected JadxDecompiler decompile(JadxArgs jadxArgs, @Nullable String clsPatternStr, @Nullable String mthPatternStr) { protected JadxDecompiler decompile(JadxArgs jadxArgs, @Nullable String clsPatternStr, @Nullable String mthPatternStr) {
JadxDecompiler jadx = new JadxDecompiler(jadxArgs); JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.getPluginManager().unload("java-convert");
jadx.load(); jadx.load();
if (clsPatternStr == null) { if (clsPatternStr == null) {
@@ -0,0 +1,33 @@
package jadx.tests.integration.arith;
import jadx.tests.api.IntegrationTest;
import jadx.tests.api.extensions.profiles.TestProfile;
import jadx.tests.api.extensions.profiles.TestWithProfiles;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestPrimitivesNegate extends IntegrationTest {
@SuppressWarnings("UnnecessaryUnaryMinus")
public static class TestCls {
public double test() {
double[] arr = new double[5];
arr[0] = -20;
arr[0] += -79;
return arr[0];
}
public void check() {
assertThat(test()).isEqualTo(-99);
}
}
@TestWithProfiles({ TestProfile.DX_J8, TestProfile.JAVA8 })
public void test() {
noDebugInfo();
assertThat(getClassNode(TestCls.class))
.code()
.containsOne("dArr[0] = -20.0d;")
.containsOne("dArr[0] = dArr[0] - 79.0d;");
}
}
@@ -24,6 +24,6 @@ public class TestBooleanToByte extends SmaliTest {
public void test() { public void test() {
assertThat(getClassNodeFromSmali()) assertThat(getClassNodeFromSmali())
.code() .code()
.containsOne("write(this.showConsent ? (byte) 1 : 0);"); .containsOne("write(this.showConsent ? (byte) 1 : (byte) 0);");
} }
} }
@@ -24,6 +24,6 @@ public class TestBooleanToChar extends SmaliTest {
public void test() { public void test() {
assertThat(getClassNodeFromSmali()) assertThat(getClassNodeFromSmali())
.code() .code()
.containsOne("write(this.showConsent ? (char) 1 : 0);"); .containsOne("write(this.showConsent ? (char) 1 : (char) 0);");
} }
} }
@@ -24,6 +24,6 @@ public class TestBooleanToLong extends SmaliTest {
public void test() { public void test() {
assertThat(getClassNodeFromSmali()) assertThat(getClassNodeFromSmali())
.code() .code()
.containsOne("write(this.showConsent ? 1 : 0);"); .containsOne("write(this.showConsent ? 1L : 0L);");
} }
} }
@@ -24,6 +24,6 @@ public class TestBooleanToShort extends SmaliTest {
public void test() { public void test() {
assertThat(getClassNodeFromSmali()) assertThat(getClassNodeFromSmali())
.code() .code()
.containsOne("write(this.showConsent ? (short) 1 : 0);"); .containsOne("write(this.showConsent ? (short) 1 : (short) 0);");
} }
} }

Some files were not shown because too many files have changed in this diff Show More