未验证 提交 157e702f 编写于 作者: S Skylot

feat: inline lambdas by instance field (#1800)

上级 77892f41
......@@ -91,6 +91,9 @@ public class JadxCLIArgs {
@Parameter(names = { "--no-inline-methods" }, description = "disable methods inline")
protected boolean inlineMethods = true;
@Parameter(names = { "--no-inline-kotlin-lambda" }, description = "disable inline for Kotlin lambdas")
protected boolean allowInlineKotlinLambda = true;
@Parameter(names = "--no-finally", description = "don't extract finally block")
protected boolean extractFinally = true;
......@@ -287,6 +290,7 @@ public class JadxCLIArgs {
args.setInsertDebugLines(addDebugLines);
args.setInlineAnonymousClasses(inlineAnonymousClasses);
args.setInlineMethods(inlineMethods);
args.setAllowInlineKotlinLambda(allowInlineKotlinLambda);
args.setExtractFinally(extractFinally);
args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive);
......@@ -368,6 +372,10 @@ public class JadxCLIArgs {
return inlineMethods;
}
public boolean isAllowInlineKotlinLambda() {
return allowInlineKotlinLambda;
}
public boolean isExtractFinally() {
return extractFinally;
}
......
......@@ -53,6 +53,7 @@ public class JadxArgs {
private boolean extractFinally = true;
private boolean inlineAnonymousClasses = true;
private boolean inlineMethods = true;
private boolean allowInlineKotlinLambda = true;
private boolean skipResources = false;
private boolean skipSources = false;
......@@ -263,6 +264,14 @@ public class JadxArgs {
this.inlineMethods = inlineMethods;
}
public boolean isAllowInlineKotlinLambda() {
return allowInlineKotlinLambda;
}
public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
this.allowInlineKotlinLambda = allowInlineKotlinLambda;
}
public boolean isExtractFinally() {
return extractFinally;
}
......
......@@ -17,6 +17,7 @@ import jadx.api.plugins.input.data.MethodHandleType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitInsnAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
......@@ -210,7 +211,31 @@ public class InsnGen {
}
}
protected void staticField(ICodeWriter code, FieldInfo field) throws CodegenException {
FieldNode fieldNode = root.resolveField(field);
if (fieldNode != null
&& fieldNode.contains(AFlag.INLINE_INSTANCE_FIELD)
&& fieldNode.getParentClass().contains(AType.ANONYMOUS_CLASS)) {
FieldInitInsnAttr initInsnAttr = fieldNode.get(AType.FIELD_INIT_INSN);
if (initInsnAttr != null) {
InsnNode insn = initInsnAttr.getInsn();
if (insn instanceof ConstructorInsn) {
fieldNode.add(AFlag.DONT_GENERATE);
inlineAnonymousConstructor(code, fieldNode.getParentClass(), (ConstructorInsn) insn);
return;
}
}
}
makeStaticFieldAccess(code, field, fieldNode, mgen.getClassGen());
}
public static void makeStaticFieldAccess(ICodeWriter code, FieldInfo field, ClassGen clsGen) {
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
makeStaticFieldAccess(code, field, fieldNode, clsGen);
}
private static void makeStaticFieldAccess(ICodeWriter code,
FieldInfo field, @Nullable FieldNode fieldNode, ClassGen clsGen) {
ClassInfo declClass = field.getDeclClass();
// TODO
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
......@@ -221,7 +246,6 @@ public class InsnGen {
}
code.add('.');
}
FieldNode fieldNode = clsGen.getClassNode().root().resolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
......@@ -232,10 +256,6 @@ public class InsnGen {
}
}
protected void staticField(ICodeWriter code, FieldInfo field) {
makeStaticFieldAccess(code, field, mgen.getClassGen());
}
public void useClass(ICodeWriter code, ArgType type) {
mgen.getClassGen().useClass(code, type);
}
......@@ -695,9 +715,7 @@ public class InsnGen {
private void makeConstructor(ConstructorInsn insn, ICodeWriter code) throws CodegenException {
ClassNode cls = mth.root().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous() && !fallback) {
cls.ensureProcessed();
inlineAnonymousConstructor(code, cls, insn);
mth.getParentClass().addInlinedClass(cls);
return;
}
if (insn.isSelf()) {
......@@ -748,6 +766,7 @@ public class InsnGen {
}
private void inlineAnonymousConstructor(ICodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
cls.ensureProcessed();
if (this.mth.getParentClass() == cls) {
cls.remove(AType.ANONYMOUS_CLASS);
cls.remove(AFlag.DONT_GENERATE);
......@@ -786,6 +805,8 @@ public class InsnGen {
ClassGen classGen = new ClassGen(cls, mgen.getClassGen().getParentGen());
classGen.setOuterNameGen(mgen.getNameGen());
classGen.addClassBody(code, true);
mth.getParentClass().addInlinedClass(cls);
}
private void makeInvoke(InvokeNode insn, ICodeWriter code) throws CodegenException {
......
......@@ -270,7 +270,7 @@ public class RegionGen extends InsnGen {
code.startLine('}');
}
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) {
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) throws CodegenException {
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
......
......@@ -37,7 +37,9 @@ public enum AFlag {
SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call
NO_SKIP_ARGS,
ANONYMOUS_CONSTRUCTOR,
INLINE_INSTANCE_FIELD,
THIS,
SUPER,
......
......@@ -7,12 +7,19 @@ import jadx.core.dex.nodes.ClassNode;
public class AnonymousClassAttr extends PinnedAttribute {
public enum InlineType {
CONSTRUCTOR,
INSTANCE_FIELD,
}
private final ClassNode outerCls;
private final ArgType baseType;
private final InlineType inlineType;
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType) {
public AnonymousClassAttr(ClassNode outerCls, ArgType baseType, InlineType inlineType) {
this.outerCls = outerCls;
this.baseType = baseType;
this.inlineType = inlineType;
}
public ClassNode getOuterCls() {
......@@ -23,6 +30,10 @@ public class AnonymousClassAttr extends PinnedAttribute {
return baseType;
}
public InlineType getInlineType() {
return inlineType;
}
@Override
public AType<AnonymousClassAttr> getAttrType() {
return AType.ANONYMOUS_CLASS;
......@@ -30,6 +41,6 @@ public class AnonymousClassAttr extends PinnedAttribute {
@Override
public String toString() {
return "AnonymousClass{" + outerCls + ", base: " + baseType + '}';
return "AnonymousClass{" + outerCls + ", base: " + baseType + ", inline type: " + inlineType + '}';
}
}
package jadx.core.dex.info;
import org.intellij.lang.annotations.MagicConstant;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts;
import jadx.core.utils.exceptions.JadxRuntimeException;
......@@ -20,10 +22,21 @@ public class AccessInfo {
this.type = type;
}
@MagicConstant(valuesFromClass = AccessFlags.class)
public boolean containsFlag(int flag) {
return (accFlags & flag) != 0;
}
@MagicConstant(valuesFromClass = AccessFlags.class)
public boolean containsFlags(int... flags) {
for (int flag : flags) {
if ((accFlags & flag) == 0) {
return false;
}
}
return true;
}
public AccessInfo remove(int flag) {
if (containsFlag(flag)) {
return new AccessInfo(accFlags & ~flag, type);
......
......@@ -384,8 +384,14 @@ public class ExtractFieldInit extends AbstractVisitor {
return list;
}
private static void addFieldInitAttr(MethodNode mth, FieldNode field, InsnNode insn) {
InsnNode assignInsn = InsnNode.wrapArg(insn.getArg(0));
private static void addFieldInitAttr(MethodNode mth, FieldNode field, IndexInsnNode putInsn) {
InsnNode assignInsn;
InsnArg fldArg = putInsn.getArg(0);
if (fldArg.isInsnWrap()) {
assignInsn = ((InsnWrapArg) fldArg).getWrapInsn();
} else {
assignInsn = InsnNode.wrapArg(fldArg);
}
field.addAttr(new FieldInitInsnAttr(mth, assignInsn));
}
}
......@@ -10,9 +10,11 @@ import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr.InlineType;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
......@@ -30,6 +32,7 @@ import jadx.core.utils.exceptions.JadxException;
UsageInfoVisitor.class
}
)
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public class ProcessAnonymous extends AbstractVisitor {
private boolean inlineAnonymousClasses;
......@@ -64,17 +67,26 @@ public class ProcessAnonymous extends AbstractVisitor {
if (!canBeAnonymous(cls)) {
return;
}
MethodNode anonymousConstructor = checkUsage(cls);
MethodNode anonymousConstructor = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
if (anonymousConstructor == null) {
return;
}
InlineType inlineType = checkUsage(cls, anonymousConstructor);
if (inlineType == null) {
return;
}
ArgType baseType = getBaseType(cls);
if (baseType == null) {
return;
}
ClassNode outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
ClassNode outerCls;
if (inlineType == InlineType.INSTANCE_FIELD) {
outerCls = cls.getUseInMth().get(0).getParentClass();
} else {
outerCls = anonymousConstructor.getUseIn().get(0).getParentClass();
}
outerCls.addInlinedClass(cls);
cls.addAttr(new AnonymousClassAttr(outerCls, baseType));
cls.addAttr(new AnonymousClassAttr(outerCls, baseType, inlineType));
cls.add(AFlag.DONT_GENERATE);
anonymousConstructor.add(AFlag.ANONYMOUS_CONSTRUCTOR);
......@@ -202,14 +214,11 @@ public class ProcessAnonymous extends AbstractVisitor {
* 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)
* - if constructor used only in class init check if possible inline by instance field
*
* @return anonymous constructor method
* @return decided inline type
*/
private static MethodNode checkUsage(ClassNode cls) {
MethodNode ctr = ListUtils.filterOnlyOne(cls.getMethods(), MethodNode::isConstructor);
if (ctr == null) {
return null;
}
private static InlineType checkUsage(ClassNode cls, MethodNode ctr) {
if (ctr.getUseIn().size() != 1) {
// check if used in common field init in all constructors
if (!checkForCommonFieldInit(ctr)) {
......@@ -219,6 +228,9 @@ public class ProcessAnonymous extends AbstractVisitor {
MethodNode ctrUseMth = ctr.getUseIn().get(0);
ClassNode ctrUseCls = ctrUseMth.getParentClass();
if (ctrUseCls.equals(cls)) {
if (checkForInstanceFieldUsage(cls, ctr)) {
return InlineType.INSTANCE_FIELD;
}
// exclude self usage
return null;
}
......@@ -226,6 +238,20 @@ public class ProcessAnonymous extends AbstractVisitor {
// exclude usage inside inner classes
return null;
}
if (!checkMethodsUsage(cls, ctr, ctrUseMth)) {
return null;
}
for (FieldNode field : cls.getFields()) {
for (MethodNode useMth : field.getUseIn()) {
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
return null;
}
}
}
return InlineType.CONSTRUCTOR;
}
private static boolean checkMethodsUsage(ClassNode cls, MethodNode ctr, MethodNode ctrUseMth) {
for (MethodNode mth : cls.getMethods()) {
if (mth == ctr) {
continue;
......@@ -235,18 +261,46 @@ public class ProcessAnonymous extends AbstractVisitor {
continue;
}
if (badMethodUsage(cls, useMth, mth.getAccessFlags())) {
return null;
return false;
}
}
}
return true;
}
private static boolean checkForInstanceFieldUsage(ClassNode cls, MethodNode ctr) {
MethodNode ctrUseMth = ctr.getUseIn().get(0);
if (!ctrUseMth.getMethodInfo().isClassInit()) {
return false;
}
FieldNode instFld = ListUtils.filterOnlyOne(cls.getFields(),
f -> f.getAccessFlags().containsFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
&& f.getFieldInfo().getType().equals(cls.getClassInfo().getType()));
if (instFld == null) {
return false;
}
List<MethodNode> instFldUseIn = instFld.getUseIn();
if (instFldUseIn.size() != 2
|| !instFldUseIn.contains(ctrUseMth) // initialized in class init
|| !instFldUseIn.containsAll(cls.getUseInMth()) // class used only with this field
) {
return false;
}
if (!checkMethodsUsage(cls, ctr, ctrUseMth)) {
return false;
}
for (FieldNode field : cls.getFields()) {
if (field == instFld) {
continue;
}
for (MethodNode useMth : field.getUseIn()) {
if (badMethodUsage(cls, useMth, field.getAccessFlags())) {
return null;
return false;
}
}
}
return ctr;
instFld.add(AFlag.INLINE_INSTANCE_FIELD);
return true;
}
private static boolean badMethodUsage(ClassNode cls, MethodNode useMth, AccessInfo accessFlags) {
......@@ -297,6 +351,13 @@ public class ProcessAnonymous extends AbstractVisitor {
if (cls.root().getClsp().isImplements(superCls.getObject(), interfaceType.getObject())) {
return superCls;
}
if (cls.root().getArgs().isAllowInlineKotlinLambda()) {
if (superCls.getObject().equals("kotlin.jvm.internal.Lambda")) {
// Inline such class with have different semantic: missing 'arity' property.
// For now, it is unclear how it may affect code execution.
return interfaceType;
}
}
return null;
}
}
......@@ -325,7 +325,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
ClassNode ctrCls = root.resolveClass(ctr.getClassType());
if (ctrCls != null && ctrCls.contains(AFlag.DONT_GENERATE)) {
AnonymousClassAttr baseTypeAttr = ctrCls.get(AType.ANONYMOUS_CLASS);
if (baseTypeAttr != null) {
if (baseTypeAttr != null && baseTypeAttr.getInlineType() == AnonymousClassAttr.InlineType.CONSTRUCTOR) {
return baseTypeAttr.getBaseType();
}
}
......
......@@ -3,12 +3,13 @@ package jadx.tests.integration.inline;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import jadx.NotYetImplemented;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.ProcessAnonymous;
import jadx.core.utils.ListUtils;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
......@@ -19,14 +20,17 @@ public class TestInstanceLambda extends SmaliTest {
public static class TestCls {
public <T> Map<T, T> test(List<? extends T> list) {
return toMap(list, Lambda.INSTANCE);
return toMap(list, Lambda$1.INSTANCE);
}
/**
* Smali test missing 'T' definition in 'Lambda<T>'
* Note: use '$1' so class looks like generated by compiler and pass check in
* {@link ProcessAnonymous#canBeAnonymous(ClassNode)}
*/
private static class Lambda<T> implements Function<T, T> {
public static final Lambda INSTANCE = new Lambda();
@SuppressWarnings({ "CheckStyle", "checkstyle:TypeName" })
private static class Lambda$1<T> implements Function<T, T> {
public static final Lambda$1 INSTANCE = new Lambda$1();
@Override
public T apply(T t) {
......@@ -35,12 +39,13 @@ public class TestInstanceLambda extends SmaliTest {
}
private static <T> Map<T, T> toMap(List<? extends T> list, Function<T, T> valueMap) {
return list.stream().collect(Collectors.toMap(k -> k, valueMap));
return null;
}
}
@Test
public void test() {
useJavaInput();
noDebugInfo();
assertThat(getClassNode(TestCls.class))
.code();
......@@ -50,23 +55,23 @@ public class TestInstanceLambda extends SmaliTest {
public void testSmaliDisableInline() {
args.setInlineAnonymousClasses(false);
List<ClassNode> classNodes = loadFromSmaliFiles();
assertThat(searchTestCls(classNodes, "Lambda"))
assertThat(searchTestCls(classNodes, "Lambda$1"))
.code()
.containsOne("class Lambda<T> implements Function<T, T> {");
.containsOne("class Lambda$1<T> implements Function<T, T> {");
assertThat(searchTestCls(classNodes, "TestCls"))
.code()
.containsOne("Lambda.INSTANCE");
.containsOne("Lambda$1.INSTANCE");
}
@NotYetImplemented("Inline lambda by instance field")
@Test
public void testSmali() {
List<ClassNode> classNodes = loadFromSmaliFiles();
assertThat(classNodes)
assertThat(ListUtils.filter(classNodes, c -> !c.contains(AFlag.DONT_GENERATE)))
.describedAs("Expect lambda to be inlined")
.hasSize(1);
assertThat(searchTestCls(classNodes, "TestCls"))
.code()
.doesNotContain("Lambda.INSTANCE");
.doesNotContain("Lambda$1.INSTANCE")
.containsOne("toMap(list, new Function<T, T>() {");
}
}
.class public Linline/Lambda;
.class public Linline/Lambda$1;
.super Ljava/lang/Object;
.implements Ljava/util/function/Function;
......@@ -12,13 +12,13 @@
}
.end annotation
.field public static final INSTANCE:Linline/Lambda;
.field public static final INSTANCE:Linline/Lambda$1;
.method static constructor <clinit>()V
.registers 1
new-instance v0, Linline/Lambda;
invoke-direct {v0}, Linline/Lambda;-><init>()V
sput-object v0, Linline/Lambda;->INSTANCE:Linline/Lambda;
new-instance v0, Linline/Lambda$1;
invoke-direct {v0}, Linline/Lambda$1;-><init>()V
sput-object v0, Linline/Lambda$1;->INSTANCE:Linline/Lambda$1;
return-void
.end method
......
......@@ -15,18 +15,12 @@
}
.end annotation
sget-object v0, Linline/Lambda;->INSTANCE:Linline/Lambda;
sget-object v0, Linline/Lambda$1;->INSTANCE:Linline/Lambda$1;
invoke-static {p1, v0}, Linline/TestCls;->toMap(Ljava/util/List;Ljava/util/function/Function;)Ljava/util/Map;
move-result-object v0
return-object v0
.end method
.method private static synthetic lambda$toMap$0(Ljava/lang/Object;)Ljava/lang/Object;
.registers 1
return-object p0
.end method
.method private static toMap(Ljava/util/List;Ljava/util/function/Function;)Ljava/util/Map;
.registers 4
.annotation system Ldalvik/annotation/Signature;
......@@ -43,14 +37,6 @@
}
.end annotation
invoke-interface {p0}, Ljava/util/List;->stream()Ljava/util/stream/Stream;
move-result-object v0
invoke-custom {}, call_site_0("apply", ()Ljava/util/function/Function;, (Ljava/lang/Object;)Ljava/lang/Object;, invoke-static@Linline/TestCls;->lambda$toMap$0(Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/Object;)Ljava/lang/Object;)@Ljava/lang/invoke/LambdaMetafactory;->metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
move-result-object v1
invoke-static {v1, p1}, Ljava/util/stream/Collectors;->toMap(Ljava/util/function/Function;Ljava/util/function/Function;)Ljava/util/stream/Collector;
move-result-object v1
invoke-interface {v0, v1}, Ljava/util/stream/Stream;->collect(Ljava/util/stream/Collector;)Ljava/lang/Object;
move-result-object v0
check-cast v0, Ljava/util/Map;
const/4 v0, 0x0
return-object v0
.end method
......@@ -401,6 +401,10 @@ public class JadxSettings extends JadxCLIArgs {
this.inlineMethods = inlineMethods;
}
public void setAllowInlineKotlinLambda(boolean allowInlineKotlinLambda) {
this.allowInlineKotlinLambda = allowInlineKotlinLambda;
}
public void setExtractFinally(boolean extractFinally) {
this.extractFinally = extractFinally;
}
......
......@@ -538,6 +538,13 @@ public class JadxSettingsWindow extends JDialog {
needReload();
});
JCheckBox inlineKotlinLambdas = new JCheckBox();
inlineKotlinLambdas.setSelected(settings.isAllowInlineKotlinLambda());
inlineKotlinLambdas.addItemListener(e -> {
settings.setAllowInlineKotlinLambda(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
JCheckBox extractFinally = new JCheckBox();
extractFinally.setSelected(settings.isExtractFinally());
extractFinally.addItemListener(e -> {
......@@ -581,6 +588,7 @@ public class JadxSettingsWindow extends JDialog {
other.addRow(NLS.str("preferences.useDebugInfo"), useDebugInfo);
other.addRow(NLS.str("preferences.inlineAnonymous"), inlineAnonymous);
other.addRow(NLS.str("preferences.inlineMethods"), inlineMethods);
other.addRow(NLS.str("preferences.inlineKotlinLambdas"), inlineKotlinLambdas);
other.addRow(NLS.str("preferences.extractFinally"), extractFinally);
other.addRow(NLS.str("preferences.fsCaseSensitive"), fsCaseSensitive);
other.addRow(NLS.str("preferences.useDx"), useDx);
......
......@@ -158,6 +158,7 @@ preferences.useImports=Import statements generieren
preferences.useDebugInfo=Debug-Infos verwenden
preferences.inlineAnonymous=Anonyme Inline-Klassen
preferences.inlineMethods=Inline-Methoden
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
#preferences.extractFinally=Extract finally block
preferences.fsCaseSensitive=Dateisystem unterscheidet zwischen Groß/Kleinschreibung
preferences.skipResourcesDecode=Keine Ressourcen dekodieren
......
......@@ -158,6 +158,7 @@ preferences.useImports=Use import statements
preferences.useDebugInfo=Use debug info
preferences.inlineAnonymous=Inline anonymous classes
preferences.inlineMethods=Inline methods
preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
preferences.extractFinally=Extract finally block
preferences.fsCaseSensitive=File system is case-sensitive
preferences.skipResourcesDecode=Don't decode resources
......
......@@ -158,6 +158,7 @@ preferences.replaceConsts=Reemplazar constantes
#preferences.useDebugInfo=Use debug info
#preferences.inlineAnonymous=
#preferences.inlineMethods=Inline methods
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
#preferences.extractFinally=Extract finally block
#preferences.fsCaseSensitive=
preferences.skipResourcesDecode=No descodificar recursos
......
......@@ -158,6 +158,7 @@ preferences.useImports=import 문 사용
preferences.useDebugInfo=디버그 정보 사용
preferences.inlineAnonymous=인라인 익명 클래스
preferences.inlineMethods=인라인 메서드
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
preferences.extractFinally=finally 블록 추출
preferences.fsCaseSensitive=파일 시스템 대소문자 구별
preferences.skipResourcesDecode=리소스 디코딩 하지 않기
......
......@@ -158,6 +158,7 @@ preferences.useImports=Utilizar declaração de imports
preferences.useDebugInfo=Utilizar informação de depuração
preferences.inlineAnonymous=Classes anônimas de uma linha
preferences.inlineMethods=Métodos de uma linha
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
preferences.extractFinally=Extrair blocos finally
preferences.fsCaseSensitive=Sistema de arquivo diferencia maiúsculas de minúsculas
preferences.skipResourcesDecode=Não decodificar recursos
......
......@@ -158,6 +158,7 @@ preferences.useImports=Использовать импорты
preferences.useDebugInfo=Отладочная информация
preferences.inlineAnonymous=Объединять анонимные классы
preferences.inlineMethods=Объединять методы
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
preferences.extractFinally=Вычленять finally блоки
preferences.fsCaseSensitive=Учитывать регистр в файловой системе
preferences.skipResourcesDecode=Не декодировать ресурсы
......
......@@ -158,6 +158,7 @@ preferences.useImports=使用 import 语句
preferences.useDebugInfo=启用调试信息
preferences.inlineAnonymous=内联匿名类
preferences.inlineMethods=内联方法
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
preferences.extractFinally=提取finally块
preferences.fsCaseSensitive=文件系统区分大小写
preferences.skipResourcesDecode=不反编译资源文件
......
......@@ -158,6 +158,7 @@ preferences.useImports=使用 import 陳述式
preferences.useDebugInfo=使用除錯資訊
preferences.inlineAnonymous=內嵌匿名類別
preferences.inlineMethods=內嵌方式
#preferences.inlineKotlinLambdas=Allow to inline Kotlin Lambdas
preferences.extractFinally=擷取 finally 區塊
preferences.fsCaseSensitive=檔案系統區分大小寫
preferences.skipResourcesDecode=不要為資源解碼
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册