feat(plugins): allow to set minimum required jadx version in plugin info (#2314)
This commit is contained in:
@@ -1,15 +1,29 @@
|
||||
package jadx.api.plugins;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class JadxPluginInfo {
|
||||
private final String pluginId;
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final String homepage;
|
||||
private String homepage;
|
||||
|
||||
/**
|
||||
* Conflicting plugins should have the same 'provides' property; only one will be loaded
|
||||
*/
|
||||
private final String provides;
|
||||
private String provides;
|
||||
|
||||
/**
|
||||
* Minimum required jadx version to run this plugin.
|
||||
* <br>
|
||||
* Format: "<stable version>, r<revision number of unstable version>".
|
||||
* Example: "1.5.1, r2305"
|
||||
*
|
||||
* @see <a href="https://github.com/skylot/jadx/wiki/Jadx-plugins-guide#required-jadx-version">wiki
|
||||
* page</a>
|
||||
* for details.
|
||||
*/
|
||||
private @Nullable String requiredJadxVersion;
|
||||
|
||||
public JadxPluginInfo(String id, String name, String description) {
|
||||
this(id, name, description, "", id);
|
||||
@@ -43,10 +57,26 @@ public class JadxPluginInfo {
|
||||
return homepage;
|
||||
}
|
||||
|
||||
public void setHomepage(String homepage) {
|
||||
this.homepage = homepage;
|
||||
}
|
||||
|
||||
public String getProvides() {
|
||||
return provides;
|
||||
}
|
||||
|
||||
public void setProvides(String provides) {
|
||||
this.provides = provides;
|
||||
}
|
||||
|
||||
public @Nullable String getRequiredJadxVersion() {
|
||||
return requiredJadxVersion;
|
||||
}
|
||||
|
||||
public void setRequiredJadxVersion(@Nullable String requiredJadxVersion) {
|
||||
this.requiredJadxVersion = requiredJadxVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pluginId + ": " + name + " - '" + description + '\'';
|
||||
|
||||
@@ -4,11 +4,14 @@ import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.plugins.versions.VerifyRequiredVersion;
|
||||
|
||||
public class JadxPluginInfoBuilder {
|
||||
private String pluginId;
|
||||
private String name;
|
||||
private String description;
|
||||
private String homepage = "";
|
||||
private @Nullable String requiredJadxVersion;
|
||||
private @Nullable String provides;
|
||||
|
||||
/**
|
||||
@@ -43,6 +46,11 @@ public class JadxPluginInfoBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public JadxPluginInfoBuilder requiredJadxVersion(String versions) {
|
||||
this.requiredJadxVersion = versions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JadxPluginInfo build() {
|
||||
Objects.requireNonNull(pluginId, "PluginId is required");
|
||||
Objects.requireNonNull(name, "Name is required");
|
||||
@@ -50,6 +58,11 @@ public class JadxPluginInfoBuilder {
|
||||
if (provides == null) {
|
||||
provides = pluginId;
|
||||
}
|
||||
return new JadxPluginInfo(pluginId, name, description, homepage, provides);
|
||||
if (requiredJadxVersion != null) {
|
||||
VerifyRequiredVersion.verify(requiredJadxVersion);
|
||||
}
|
||||
JadxPluginInfo pluginInfo = new JadxPluginInfo(pluginId, name, description, homepage, provides);
|
||||
pluginInfo.setRequiredJadxVersion(requiredJadxVersion);
|
||||
return pluginInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import jadx.api.plugins.input.JadxCodeInput;
|
||||
import jadx.api.plugins.loader.JadxPluginLoader;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.core.plugins.versions.VerifyRequiredVersion;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class JadxPluginManager {
|
||||
@@ -50,15 +51,16 @@ public class JadxPluginManager {
|
||||
|
||||
public void load(JadxPluginLoader pluginLoader) {
|
||||
allPlugins.clear();
|
||||
VerifyRequiredVersion verifyRequiredVersion = new VerifyRequiredVersion();
|
||||
for (JadxPlugin plugin : pluginLoader.load()) {
|
||||
addPlugin(plugin);
|
||||
addPlugin(plugin, verifyRequiredVersion);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
|
||||
public void register(JadxPlugin plugin) {
|
||||
Objects.requireNonNull(plugin);
|
||||
PluginContext addedPlugin = addPlugin(plugin);
|
||||
PluginContext addedPlugin = addPlugin(plugin, new VerifyRequiredVersion());
|
||||
if (addedPlugin == null) {
|
||||
LOG.debug("Can't register plugin, it was disabled: {}", plugin.getPluginInfo().getPluginId());
|
||||
return;
|
||||
@@ -67,11 +69,17 @@ public class JadxPluginManager {
|
||||
resolve();
|
||||
}
|
||||
|
||||
private @Nullable PluginContext addPlugin(JadxPlugin plugin) {
|
||||
private @Nullable PluginContext addPlugin(JadxPlugin plugin, VerifyRequiredVersion verifyRequiredVersion) {
|
||||
PluginContext pluginContext = new PluginContext(decompiler, pluginsData, plugin);
|
||||
if (disabledPlugins.contains(pluginContext.getPluginId())) {
|
||||
return null;
|
||||
}
|
||||
String requiredJadxVersion = pluginContext.getPluginInfo().getRequiredJadxVersion();
|
||||
if (!verifyRequiredVersion.isCompatible(requiredJadxVersion)) {
|
||||
LOG.warn("Plugin '{}' not loaded: requires '{}' jadx version which it is not compatible with current: {}",
|
||||
pluginContext, requiredJadxVersion, verifyRequiredVersion.getJadxVersion());
|
||||
return null;
|
||||
}
|
||||
LOG.debug("Loading plugin: {}", pluginContext);
|
||||
if (!allPlugins.add(pluginContext)) {
|
||||
throw new IllegalArgumentException("Duplicate plugin id: " + pluginContext + ", class " + plugin.getClass());
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package jadx.core.plugins.versions;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.Jadx;
|
||||
|
||||
public class VerifyRequiredVersion {
|
||||
|
||||
public static boolean isJadxCompatible(@Nullable String reqVersionStr) {
|
||||
return new VerifyRequiredVersion().isCompatible(reqVersionStr);
|
||||
}
|
||||
|
||||
public static void verify(String requiredJadxVersion) {
|
||||
try {
|
||||
parse(requiredJadxVersion);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Malformed 'requiredJadxVersion': " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private final String jadxVersion;
|
||||
private final boolean unstable;
|
||||
|
||||
private final boolean dev;
|
||||
|
||||
public VerifyRequiredVersion() {
|
||||
this(Jadx.getVersion());
|
||||
}
|
||||
|
||||
public VerifyRequiredVersion(String jadxVersion) {
|
||||
this.jadxVersion = jadxVersion;
|
||||
this.unstable = jadxVersion.startsWith("r");
|
||||
this.dev = jadxVersion.equals(Jadx.VERSION_DEV);
|
||||
}
|
||||
|
||||
public boolean isCompatible(@Nullable String reqVersionStr) {
|
||||
if (reqVersionStr == null || reqVersionStr.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
RequiredVersionData reqVer = parse(reqVersionStr);
|
||||
if (dev) {
|
||||
// keep version str parsing for verification
|
||||
return true;
|
||||
}
|
||||
if (unstable) {
|
||||
return VersionComparator.checkAndCompare(jadxVersion, reqVer.getUnstableRev()) >= 0;
|
||||
}
|
||||
return VersionComparator.checkAndCompare(jadxVersion, reqVer.getReleaseVer()) >= 0;
|
||||
}
|
||||
|
||||
public String getJadxVersion() {
|
||||
return jadxVersion;
|
||||
}
|
||||
|
||||
private static final Pattern REQ_VER_FORMAT = Pattern.compile("(\\d+\\.\\d+\\.\\d+),\\s+(r\\d+)");
|
||||
|
||||
private static RequiredVersionData parse(String reqVersionStr) {
|
||||
Matcher matcher = REQ_VER_FORMAT.matcher(reqVersionStr);
|
||||
if (!matcher.matches()) {
|
||||
throw new RuntimeException("Expect format: " + REQ_VER_FORMAT + ", got: " + reqVersionStr);
|
||||
}
|
||||
return new RequiredVersionData(matcher.group(1), matcher.group(2));
|
||||
}
|
||||
|
||||
private static final class RequiredVersionData {
|
||||
private final String releaseVer;
|
||||
private final String unstableRev;
|
||||
|
||||
private RequiredVersionData(String releaseVer, String unstableRev) {
|
||||
this.releaseVer = releaseVer;
|
||||
this.unstableRev = unstableRev;
|
||||
}
|
||||
|
||||
public String getReleaseVer() {
|
||||
return releaseVer;
|
||||
}
|
||||
|
||||
public String getUnstableRev() {
|
||||
return unstableRev;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package jadx.core.plugins.versions;
|
||||
|
||||
public class VersionComparator {
|
||||
|
||||
private VersionComparator() {
|
||||
}
|
||||
|
||||
public static int checkAndCompare(String str1, String str2) {
|
||||
return compare(clean(str1), clean(str2));
|
||||
}
|
||||
|
||||
private static String clean(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
String result = str.trim().toLowerCase();
|
||||
if (result.startsWith("jadx-gui-")) {
|
||||
result = result.substring(9);
|
||||
}
|
||||
if (result.startsWith("jadx-")) {
|
||||
result = result.substring(5);
|
||||
}
|
||||
if (result.charAt(0) == 'v') {
|
||||
result = result.substring(1);
|
||||
}
|
||||
if (result.charAt(0) == 'r') {
|
||||
result = result.substring(1);
|
||||
int dot = result.indexOf('.');
|
||||
if (dot != -1) {
|
||||
result = result.substring(0, dot);
|
||||
}
|
||||
}
|
||||
// treat a package version as part of version
|
||||
result = result.replace('-', '.');
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int compare(String str1, String str2) {
|
||||
String[] s1 = str1.split("\\.");
|
||||
int l1 = s1.length;
|
||||
String[] s2 = str2.split("\\.");
|
||||
int l2 = s2.length;
|
||||
|
||||
int i = 0;
|
||||
// skip equals parts
|
||||
while (i < l1 && i < l2) {
|
||||
if (!s1[i].equals(s2[i])) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// compare first non-equal ordinal number
|
||||
if (i < l1 && i < l2) {
|
||||
return Integer.valueOf(s1[i]).compareTo(Integer.valueOf(s2[i]));
|
||||
}
|
||||
boolean checkFirst = l1 > l2;
|
||||
boolean zeroTail = isZeroTail(checkFirst ? s1 : s2, i);
|
||||
if (zeroTail) {
|
||||
return 0;
|
||||
}
|
||||
return checkFirst ? 1 : -1;
|
||||
}
|
||||
|
||||
private static boolean isZeroTail(String[] arr, int pos) {
|
||||
for (int i = pos; i < arr.length; i++) {
|
||||
if (Integer.parseInt(arr[i]) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package jadx.core.plugins.versions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class VerifyRequiredVersionTest {
|
||||
@Test
|
||||
public void test() {
|
||||
isCompatible("1.5.0, r2000", "1.5.1", true);
|
||||
isCompatible("1.5.1, r3000", "1.5.1", true);
|
||||
isCompatible("1.5.1, r3000", "1.6.0", true);
|
||||
isCompatible("1.5.1, r3000", "1.5.0", false);
|
||||
|
||||
isCompatible("1.5.1, r3000", "r3001.417bb7a", true);
|
||||
isCompatible("1.5.1, r3000", "r4000", true);
|
||||
isCompatible("1.5.1, r3000", "r3000", true);
|
||||
isCompatible("1.5.1, r3000", "r2000", false);
|
||||
}
|
||||
|
||||
private static void isCompatible(String requiredVersion, String jadxVersion, boolean result) {
|
||||
assertThat(new VerifyRequiredVersion(jadxVersion).isCompatible(requiredVersion))
|
||||
.as("Expect plugin with required version %s is%s compatible with jadx %s",
|
||||
requiredVersion, result ? "" : " not", jadxVersion)
|
||||
.isEqualTo(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package jadx.core.plugins.versions;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class VersionComparatorTest {
|
||||
|
||||
@Test
|
||||
public void testCompare() {
|
||||
checkCompare("", "", 0);
|
||||
checkCompare("1", "1", 0);
|
||||
checkCompare("1", "2", -1);
|
||||
checkCompare("1.1", "1.1", 0);
|
||||
checkCompare("0.5", "0.5", 0);
|
||||
checkCompare("0.5", "0.5.0", 0);
|
||||
checkCompare("0.5", "0.5.00", 0);
|
||||
checkCompare("0.5", "0.5.0.0", 0);
|
||||
checkCompare("0.5", "0.5.0.1", -1);
|
||||
checkCompare("0.5.0", "0.5.0", 0);
|
||||
checkCompare("0.5.0", "0.5.1", -1);
|
||||
checkCompare("0.5", "0.5.1", -1);
|
||||
checkCompare("0.4.8", "0.5", -1);
|
||||
checkCompare("0.4.8", "0.5.0", -1);
|
||||
checkCompare("0.4.8", "0.6", -1);
|
||||
checkCompare("1.3.3.1", "1.3.3", 1);
|
||||
checkCompare("1.3.3-1", "1.3.3", 1);
|
||||
checkCompare("1.3.3.1-1", "1.3.3", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareUnstable() {
|
||||
checkCompare("r2190.ce527ed", "jadx-r2299.742d30d", -1);
|
||||
}
|
||||
|
||||
private static void checkCompare(String a, String b, int result) {
|
||||
assertThat(VersionComparator.checkAndCompare(a, b))
|
||||
.as("Compare %s and %s expect %d", a, b, result)
|
||||
.isEqualTo(result);
|
||||
assertThat(VersionComparator.checkAndCompare(b, a))
|
||||
.as("Compare %s and %s expect %d", b, a, -result)
|
||||
.isEqualTo(-result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user