diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinWriter.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinWriter.java index ed0e91a525..4716b643d0 100644 --- a/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinWriter.java +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinWriter.java @@ -32,6 +32,7 @@ import org.jetbrains.java.decompiler.struct.gen.generics.GenericClassDescriptor; import org.jetbrains.java.decompiler.struct.gen.generics.GenericFieldDescriptor; import org.jetbrains.java.decompiler.struct.gen.generics.GenericMethodDescriptor; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericsChecker; import org.jetbrains.java.decompiler.util.*; import org.jetbrains.java.decompiler.util.collections.VBStyleCollection; import org.vineflower.kotlin.expr.KAnnotationExprent; @@ -769,6 +770,15 @@ private void writeClassDefinition(ClassNode node, TextBuffer buffer, int indent, } GenericClassDescriptor descriptor = cl.getSignature(); + if (descriptor != null) { + VarType superclass = new VarType(cl.superClass.getString(), true); + List interfaces = Arrays.stream(cl.getInterfaceNames()) + .map(s -> new VarType(s, true)) + .toList(); + + descriptor.verifyTypes(superclass, interfaces); + } + if (descriptor != null && !descriptor.fparameters.isEmpty()) { appendTypeParameters(buffer, descriptor.fparameters, descriptor.fbounds); } @@ -861,6 +871,9 @@ public void writeField(ClassWrapper wrapper, StructClass cl, StructField fd, Tex Map.Entry fieldTypeData = getFieldTypeData(fd); VarType fieldType = fieldTypeData.getKey(); GenericFieldDescriptor descriptor = fieldTypeData.getValue(); + if (descriptor != null) { + descriptor.verifyType(cl.getSignature(), fieldType); + } if (!isEnum) { buffer.append(ExprProcessor.getCastTypeName(descriptor == null ? fieldType : descriptor.type)); @@ -1045,6 +1058,41 @@ public boolean writeMethod(ClassNode node, StructMethod mt, int methodIndex, Tex } GenericMethodDescriptor descriptor = mt.getSignature(); + if (descriptor != null) { + List params = new ArrayList<>(Arrays.asList(mt.methodDescriptor().params)); + if ((node.access & CodeConstants.ACC_ENUM) != 0 && init) { + // Signatures skip enum parameters, the checker must as well + params.remove(0); + params.remove(0); + } + + StructExceptionsAttribute attr = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_EXCEPTIONS); + List exceptions = new ArrayList<>(); + if (attr != null) { + for (int i = 0; i < attr.getThrowsExceptions().size(); i++) { + exceptions.add(new VarType(attr.getExcClassname(i, node.classStruct.getPool()), true)); + } + } + + GenericClassDescriptor classSig = (mt.getAccessFlags() & CodeConstants.ACC_STATIC) == 0 ? node.classStruct.getSignature() : null; + GenericsChecker checker = null; + if (classSig != null) { + checker = classSig.getChecker(); + + ClassNode currentParent = node.parent; + while (currentParent != null) { + GenericClassDescriptor parentSignature = currentParent.classStruct.getSignature(); + if (parentSignature != null) { + checker = checker.copy(parentSignature.getChecker()); + } + + currentParent = currentParent.parent; + } + } + + descriptor.verifyTypes(checker, params, mt.methodDescriptor().ret, exceptions); + } + boolean throwsExceptions = false; int paramCount = 0; diff --git a/src/org/jetbrains/java/decompiler/main/ClassWriter.java b/src/org/jetbrains/java/decompiler/main/ClassWriter.java index 9fe46f3632..9c68fa01d1 100644 --- a/src/org/jetbrains/java/decompiler/main/ClassWriter.java +++ b/src/org/jetbrains/java/decompiler/main/ClassWriter.java @@ -37,6 +37,7 @@ import org.jetbrains.java.decompiler.struct.gen.generics.GenericClassDescriptor; import org.jetbrains.java.decompiler.struct.gen.generics.GenericFieldDescriptor; import org.jetbrains.java.decompiler.struct.gen.generics.GenericMethodDescriptor; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericsChecker; import org.jetbrains.java.decompiler.util.InterpreterUtil; import org.jetbrains.java.decompiler.util.Key; import org.jetbrains.java.decompiler.util.TextBuffer; @@ -704,6 +705,15 @@ else if (components != null) { buffer.appendClass(node.simpleName, true, cl.qualifiedName); GenericClassDescriptor descriptor = cl.getSignature(); + if (descriptor != null) { + VarType superclass = new VarType(cl.superClass.getString(), true); + List interfaces = Arrays.stream(cl.getInterfaceNames()) + .map(s -> new VarType(s, true)) + .toList(); + + descriptor.verifyTypes(superclass, interfaces); + } + if (descriptor != null && !descriptor.fparameters.isEmpty()) { appendTypeParameters(buffer, descriptor.fparameters, descriptor.fbounds); } @@ -834,6 +844,9 @@ public void writeField(ClassWrapper wrapper, StructClass cl, StructField fd, Tex Map.Entry fieldTypeData = getFieldTypeData(fd); VarType fieldType = fieldTypeData.getKey(); GenericFieldDescriptor descriptor = fieldTypeData.getValue(); + if (descriptor != null) { + descriptor.verifyType(cl.getSignature(), fieldType); + } if (!isEnum) { buffer.appendCastTypeName(descriptor == null ? fieldType : descriptor.type); @@ -1120,6 +1133,48 @@ public boolean writeMethod(ClassNode node, StructMethod mt, int methodIndex, Tex } GenericMethodDescriptor descriptor = mt.getSignature(); + if (descriptor != null) { + List params = new ArrayList<>(Arrays.asList(mt.methodDescriptor().params)); + + if (init && node.classStruct.hasModifier(CodeConstants.ACC_ENUM)) { + // Enum name and ordinal parameters need to be explicitly excluded + params.remove(0); + params.remove(0); + } + + // Exclude any parameters that the signature itself won't contain + List mask = methodWrapper.synthParameters; + if (mask != null) { + for (int i = 0, j = 0; i < mask.size(); i++, j++) { + if (mask.get(i) != null) { + params.remove(j--); + } + } + } + + StructExceptionsAttribute attr = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_EXCEPTIONS); + List exceptions = new ArrayList<>(); + if (attr != null) { + for (int i = 0; i < attr.getThrowsExceptions().size(); i++) { + exceptions.add(new VarType(attr.getExcClassname(i, node.classStruct.getPool()), true)); + } + } + + GenericsChecker checker = new GenericsChecker(); + + ClassNode currentNode = node; + while (currentNode != null) { + GenericClassDescriptor parentSignature = currentNode.classStruct.getSignature(); + if (parentSignature != null) { + checker = checker.copy(parentSignature.getChecker()); + } + + currentNode = currentNode.parent; + } + + descriptor.verifyTypes(checker, params, mt.methodDescriptor().ret, exceptions); + } + boolean throwsExceptions = false; int paramCount = 0; diff --git a/src/org/jetbrains/java/decompiler/main/RecordHelper.java b/src/org/jetbrains/java/decompiler/main/RecordHelper.java index eb1b591ff1..cfa73a014e 100644 --- a/src/org/jetbrains/java/decompiler/main/RecordHelper.java +++ b/src/org/jetbrains/java/decompiler/main/RecordHelper.java @@ -3,7 +3,6 @@ import org.jetbrains.java.decompiler.code.CodeConstants; import org.jetbrains.java.decompiler.main.rels.MethodWrapper; import org.jetbrains.java.decompiler.modules.decompiler.exps.*; -import org.jetbrains.java.decompiler.modules.decompiler.flow.DirectGraph; import org.jetbrains.java.decompiler.modules.decompiler.stats.BasicBlockStatement; import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; @@ -201,7 +200,10 @@ private static void recordComponentToJava(TextBuffer buffer, StructClass cl, Str VarType fieldType = new VarType(cd.getDescriptor(), false); GenericFieldDescriptor descriptor = cd.getSignature(); - if (descriptor != null) fieldType = descriptor.type; + if (descriptor != null) { + descriptor.verifyType(cl.getSignature(), fieldType); + fieldType = descriptor.type; + } buffer.appendCastTypeName(varArgComponent ? fieldType.decreaseArrayDim() : fieldType); if (varArgComponent) { diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java index d1a97ba1ff..390e0650e4 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java @@ -19,6 +19,7 @@ import org.jetbrains.java.decompiler.struct.gen.CodeType; import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor; import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericFieldDescriptor; import org.jetbrains.java.decompiler.struct.gen.generics.GenericType; import org.jetbrains.java.decompiler.struct.match.MatchEngine; import org.jetbrains.java.decompiler.struct.match.MatchNode; @@ -78,31 +79,36 @@ public VarType getInferredExprType(VarType upperBound) { cl = cl.superClass == null ? null : DecompilerContext.getStructContext().getClass((String)cl.superClass.value); } - if (ft != null && ft.getSignature() != null) { - VarType ret = ft.getSignature().type.remap(types.getOrDefault(cl.qualifiedName, Collections.emptyMap())); + if (ft != null) { + GenericFieldDescriptor signature = ft.getSignature(); + if (signature != null) { + signature.verifyType(cl.getSignature(), descriptor.type); - if (instance != null && cl.getSignature() != null) { - VarType instType = instance.getInferredExprType(null); + VarType ret = signature.type.remap(types.getOrDefault(cl.qualifiedName, Collections.emptyMap())); - if (instType.isGeneric() && instType.type != CodeType.GENVAR) { - GenericType ginstance = (GenericType)instType; + if (instance != null && cl.getSignature() != null) { + VarType instType = instance.getInferredExprType(null); - cl = DecompilerContext.getStructContext().getClass(instType.value); - if (cl != null && cl.getSignature() != null) { - Map tempMap = new HashMap<>(); - cl.getSignature().genericType.mapGenVarsTo(ginstance, tempMap); - VarType _new = ret.remap(tempMap); + if (instType.isGeneric() && instType.type != CodeType.GENVAR) { + GenericType ginstance = (GenericType) instType; - if (_new != null) { - ret = _new; - } else { - ret = getExprType(); + cl = DecompilerContext.getStructContext().getClass(instType.value); + if (cl != null && cl.getSignature() != null) { + Map tempMap = new HashMap<>(); + cl.getSignature().genericType.mapGenVarsTo(ginstance, tempMap); + VarType _new = ret.remap(tempMap); + + if (_new != null) { + ret = _new; + } else { + ret = getExprType(); + } } } } - } - return ret; + return ret; + } } return getExprType(); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/VarExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/VarExprent.java index 9bf724adfd..6c6ebcb45a 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/VarExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/VarExprent.java @@ -230,6 +230,7 @@ public VarType getDefinitionVarType() { if (lvt.getSignature() != null) { GenericFieldDescriptor descriptor = GenericMain.parseFieldSignature(lvt.getSignature()); if (descriptor != null) { + descriptor.verifyLoosely(lvt.getVarType()); return descriptor.type; } } @@ -245,28 +246,39 @@ public VarType getDefinitionVarType() { } int visibleOffset = bytecode == null ? -1 : bytecode.length(); if (originalIndex != null) { - // first try from signature + // Try loading the type from LVT attributes: + // First try the signature if it's available and matches a known descriptor. + // If signature not available, then try the descriptor. + // If neither are present, fall back to the context-inferred type. + + VarType type = null; + StructLocalVariableTableAttribute lvt = method.methodStruct.getLocalVariableAttr(); + if (lvt != null) { + String descriptor = lvt.getDescriptor(originalIndex, visibleOffset); + if (descriptor != null) { + type = new VarType(descriptor); + } + } + if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) { - StructLocalVariableTypeTableAttribute attr = + StructLocalVariableTypeTableAttribute lvtSignatures = method.methodStruct.getAttribute(StructGeneralAttribute.ATTRIBUTE_LOCAL_VARIABLE_TYPE_TABLE); - if (attr != null) { - String signature = attr.getSignature(originalIndex, visibleOffset); + if (lvtSignatures != null) { + String signature = lvtSignatures.getSignature(originalIndex, visibleOffset); if (signature != null) { GenericFieldDescriptor descriptor = GenericMain.parseFieldSignature(signature); if (descriptor != null) { + if (type != null) { + descriptor.verifyLoosely(type); + } return descriptor.type; } } } } - // then try from descriptor - StructLocalVariableTableAttribute attr = method.methodStruct.getLocalVariableAttr(); - if (attr != null) { - String descriptor = attr.getDescriptor(originalIndex, visibleOffset); - if (descriptor != null) { - return new VarType(descriptor); - } + if (type != null) { + return type; } } } diff --git a/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericClassDescriptor.java b/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericClassDescriptor.java index ae2a3dd908..06f24f94d5 100644 --- a/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericClassDescriptor.java +++ b/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericClassDescriptor.java @@ -2,8 +2,12 @@ package org.jetbrains.java.decompiler.struct.gen.generics; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.struct.gen.VarType; public class GenericClassDescriptor { @@ -17,4 +21,40 @@ public class GenericClassDescriptor { public final List fparameters = new ArrayList<>(); public final List> fbounds = new ArrayList<>(); + + private GenericsChecker checker; + + public GenericsChecker getChecker() { + if (checker == null) { + checker = new GenericsChecker(fparameters, fbounds); + } + + return checker; + } + + public void verifyTypes(VarType actualSuperclass, List actualSuperInterfaces) { + GenericsChecker checker = getChecker(); + + if (superclass != null) { + if (!checker.isProperlyBounded(actualSuperclass, superclass)) { + DecompilerContext.getLogger().writeMessage("Mismatched superclass signature, expected: " + actualSuperclass + ", actual: " + superclass.value, IFernflowerLogger.Severity.WARN); + superclass = actualSuperclass; + } + } + + for (int i = 0; i < superinterfaces.size(); i++) { + VarType actualSuperInterface = i < actualSuperInterfaces.size() ? actualSuperInterfaces.get(i) : null; + VarType superInterface = superinterfaces.get(i); + + if (actualSuperInterface == null) { + DecompilerContext.getLogger().writeMessage("Actual superinterface count is less than expected", IFernflowerLogger.Severity.WARN); + break; + } + + if (!checker.isProperlyBounded(actualSuperInterface, superInterface)) { + DecompilerContext.getLogger().writeMessage("Mismatched superinterface signature, expected: " + actualSuperInterface + ", actual: " + superInterface.value, IFernflowerLogger.Severity.WARN); + superinterfaces.set(i, actualSuperInterface); + } + } + } } diff --git a/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericFieldDescriptor.java b/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericFieldDescriptor.java index 2b0fdf1dad..d53a23e6d5 100644 --- a/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericFieldDescriptor.java +++ b/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericFieldDescriptor.java @@ -1,12 +1,45 @@ // Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.struct.gen.generics; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.struct.gen.CodeType; import org.jetbrains.java.decompiler.struct.gen.VarType; public class GenericFieldDescriptor { - public final VarType type; + public VarType type; public GenericFieldDescriptor(VarType type) { this.type = type; } + + public void verifyType(GenericClassDescriptor containingClassGenerics, VarType realType) { + if (containingClassGenerics == null) { + DecompilerContext.getLogger().writeMessage("Class generics were not found, verifying type loosely", IFernflowerLogger.Severity.INFO); + verifyLoosely(realType); + return; + } + + GenericsChecker checker = containingClassGenerics.getChecker(); + + if (!checker.isProperlyBounded(type, realType)) { + DecompilerContext.getLogger().writeMessage("Mismatched field signature, expected: " + realType.value + ", actual: " + type.value, IFernflowerLogger.Severity.WARN); + type = realType; + } + } + + //FIXME this is necessary because some places don't have other signature information, + // which prevents the ability to check class- or method-provided generic types + public void verifyLoosely(VarType actualType) { + if (actualType.isSuperset(type)) { + return; + } + + if (type.type == CodeType.GENVAR) { + return; // Impossible to verify + } + + DecompilerContext.getLogger().writeMessage("Mismatched field signature, expected: " + type.value + ", actual: " + actualType.value, IFernflowerLogger.Severity.WARN); + type = actualType; + } } \ No newline at end of file diff --git a/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericMethodDescriptor.java b/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericMethodDescriptor.java index a93d250e24..2382252889 100644 --- a/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericMethodDescriptor.java +++ b/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericMethodDescriptor.java @@ -4,13 +4,15 @@ import java.util.Collections; import java.util.List; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.struct.gen.VarType; public class GenericMethodDescriptor { public final List typeParameters; public final List> typeParameterBounds; public final List parameterTypes; - public final VarType returnType; + public VarType returnType; public final List exceptionTypes; public GenericMethodDescriptor(List typeParameters, @@ -28,4 +30,31 @@ public GenericMethodDescriptor(List typeParameters, private static List substitute(List list) { return list.isEmpty() ? Collections.emptyList() : list; } + + public void verifyTypes(GenericsChecker checker, List realParamTypes, VarType realReturnType, List realExceptionTypes) { + checker = checker == null ? new GenericsChecker(typeParameters, typeParameterBounds) : checker.copy(typeParameters, typeParameterBounds); + + for (int i = 0; i < parameterTypes.size(); i++) { + VarType parameterType = parameterTypes.get(i); + VarType realType = realParamTypes.get(i); + + if (!checker.isProperlyBounded(parameterType, realType)) { + DecompilerContext.getLogger().writeMessage("Mismatched method parameter signature, expected: " + realType.value + ", actual: " + parameterType.value, IFernflowerLogger.Severity.WARN); + parameterTypes.set(i, realType); + } + } + + if (!checker.isProperlyBounded(returnType, realReturnType)) { + DecompilerContext.getLogger().writeMessage("Mismatched method return signature, expected: " + realReturnType.value + ", actual: " + returnType.value, IFernflowerLogger.Severity.WARN); + returnType = realReturnType; + } + + for (int i = 0; i < exceptionTypes.size(); i++) { + VarType exceptionType = exceptionTypes.get(i); + VarType realType = realExceptionTypes.get(i); + if (!checker.isProperlyBounded(exceptionType, realType)) { + DecompilerContext.getLogger().writeMessage("Mismatched method exception signature, expected: " + realType.value + ", actual: " + exceptionType.value, IFernflowerLogger.Severity.WARN); + } + } + } } \ No newline at end of file diff --git a/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericsChecker.java b/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericsChecker.java new file mode 100644 index 0000000000..d6df6ac31e --- /dev/null +++ b/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericsChecker.java @@ -0,0 +1,70 @@ +package org.jetbrains.java.decompiler.struct.gen.generics; + +import org.jetbrains.java.decompiler.struct.gen.CodeType; +import org.jetbrains.java.decompiler.struct.gen.VarType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GenericsChecker { + private final Map> boundsMap; + + public GenericsChecker() { + boundsMap = Map.of(); + } + + public GenericsChecker(List typeVariables, List> bounds) { + boundsMap = new HashMap<>(typeVariables.size(), 1); + for (int i = 0; i < typeVariables.size(); i++) { + boundsMap.put(typeVariables.get(i), bounds.get(i)); + } + } + + private GenericsChecker(Map> boundsMap) { + this.boundsMap = boundsMap; + } + + public GenericsChecker copy(List typeVariables, List> bounds) { + HashMap> newBounds = new HashMap<>(boundsMap); + for (int i = 0; i < typeVariables.size(); i++) { + newBounds.put(typeVariables.get(i), bounds.get(i)); + } + + return new GenericsChecker(newBounds); + } + + public GenericsChecker copy(GenericsChecker parent) { + HashMap> newBoundsMap = new HashMap<>(this.boundsMap); + for (Map.Entry> entry : parent.boundsMap.entrySet()) { + if (!newBoundsMap.containsKey(entry.getKey())) { + newBoundsMap.put(entry.getKey(), new ArrayList<>(entry.getValue())); + } + } + + return new GenericsChecker(newBoundsMap); + } + + public boolean isProperlyBounded(VarType type, VarType bound) { + if (type.isSuperset(bound)) { + return true; + } + + // Get base type if array + bound = bound.resizeArrayDim(0); + + if (type.type == CodeType.GENVAR && type instanceof GenericType genericType) { + List typeBounds = boundsMap.get(genericType.value); + if (typeBounds != null) { + for (VarType typeBound : typeBounds) { + if (isProperlyBounded(typeBound, bound)) { + return true; + } + } + } + } + + return false; + } +} diff --git a/test/org/jetbrains/java/decompiler/SingleClassesTest.java b/test/org/jetbrains/java/decompiler/SingleClassesTest.java index e08c0fc374..ed1e934ed5 100644 --- a/test/org/jetbrains/java/decompiler/SingleClassesTest.java +++ b/test/org/jetbrains/java/decompiler/SingleClassesTest.java @@ -731,6 +731,7 @@ private void registerDefault() { register(JAVA_16, "TestSwitchExpressionReturnType"); register(JAVA_8, "TestGenericMapping"); + registerRaw(CUSTOM, "TestCorruptedSignatures").setExpectedFileName("Signatures.java"); } private void registerEntireClassPath() { diff --git a/testData/classes/custom/TestCorruptedSignatures.class b/testData/classes/custom/TestCorruptedSignatures.class new file mode 100644 index 0000000000..49993f8b54 Binary files /dev/null and b/testData/classes/custom/TestCorruptedSignatures.class differ diff --git a/testData/results/TestCorruptedSignatures.dec b/testData/results/TestCorruptedSignatures.dec new file mode 100644 index 0000000000..30e14cb5ec --- /dev/null +++ b/testData/results/TestCorruptedSignatures.dec @@ -0,0 +1,18 @@ +import java.util.ArrayList; +import java.util.List; + +abstract class Signatures implements List { + List field = new ArrayList(); + + void method(List o) { + }// 9 +} + +class 'Signatures' { + method 'method (Ljava/util/List;)V' { + 0 7 + } +} + +Lines mapping: +9 <-> 8