diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index df81bd32c..f2cc4e754 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; @@ -621,16 +622,16 @@ public class Renderer implements RenderingManager { } writer.append(',').ws(); - List virtualMethods = new ArrayList<>(); + Map virtualMethods = new LinkedHashMap<>(); + collectMethodsToCopyFromInterfaces(classSource.get(cls.getName()), virtualMethods); for (PreparedMethod method : cls.getMethods()) { if (!method.methodHolder.getModifiers().contains(ElementModifier.STATIC) && method.methodHolder.getLevel() != AccessLevel.PRIVATE) { - virtualMethods.add(method.reference); + virtualMethods.put(method.reference.getDescriptor(), method.reference); } } - collectMethodsToCopyFromInterfaces(classSource.get(cls.getName()), virtualMethods); - renderVirtualDeclarations(virtualMethods); + renderVirtualDeclarations(virtualMethods.values()); debugEmitter.emitClass(null); } writer.append("]);").newLine(); @@ -702,7 +703,7 @@ public class Renderer implements RenderingManager { } } - private void collectMethodsToCopyFromInterfaces(ClassReader cls, List targetList) { + private void collectMethodsToCopyFromInterfaces(ClassReader cls, Map target) { Set implementedMethods = new HashSet<>(); ClassReader superclass = cls; while (superclass != null) { @@ -717,35 +718,40 @@ public class Renderer implements RenderingManager { } Set visitedClasses = new HashSet<>(); - for (String ifaceName : cls.getInterfaces()) { - ClassReader iface = classSource.get(ifaceName); - if (iface != null) { - collectMethodsToCopyFromInterfacesImpl(iface, targetList, implementedMethods, visitedClasses); + superclass = cls; + while (superclass != null) { + for (String ifaceName : superclass.getInterfaces()) { + ClassReader iface = classSource.get(ifaceName); + if (iface != null) { + collectMethodsToCopyFromInterfacesImpl(iface, target, implementedMethods, visitedClasses); + } } + superclass = superclass.getParent() != null ? classSource.get(superclass.getParent()) : null; } } - private void collectMethodsToCopyFromInterfacesImpl(ClassReader cls, List targetList, - Set visited, Set visitedClasses) { + private void collectMethodsToCopyFromInterfacesImpl(ClassReader cls, Map target, + Set implementedMethods, Set visitedClasses) { if (!visitedClasses.add(cls.getName())) { return; } + for (String ifaceName : cls.getInterfaces()) { + ClassReader iface = classSource.get(ifaceName); + if (iface != null) { + collectMethodsToCopyFromInterfacesImpl(iface, target, implementedMethods, visitedClasses); + } + } + for (MethodReader method : cls.getMethods()) { if (!method.hasModifier(ElementModifier.STATIC) && !method.hasModifier(ElementModifier.ABSTRACT)) { - if (visited.add(method.getDescriptor())) { - targetList.add(method.getReference()); + MethodDescriptor descriptor = method.getDescriptor(); + if (!implementedMethods.contains(descriptor)) { + target.put(descriptor, method.getReference()); } } } - - for (String ifaceName : cls.getInterfaces()) { - ClassReader iface = classSource.get(ifaceName); - if (iface != null) { - collectMethodsToCopyFromInterfacesImpl(iface, targetList, visited, visitedClasses); - } - } } private static Object getDefaultValue(ValueType type) { diff --git a/tests/src/test/java/org/teavm/vm/VMTest.java b/tests/src/test/java/org/teavm/vm/VMTest.java index b55f287e3..b70475803 100644 --- a/tests/src/test/java/org/teavm/vm/VMTest.java +++ b/tests/src/test/java/org/teavm/vm/VMTest.java @@ -491,8 +491,20 @@ public class VMTest { @Test public void indirectDefaultMethod() { - PathJoint o = new PathJoint(); - assertEquals("SecondPath.foo", o.foo()); + StringBuilder sb = new StringBuilder(); + for (FirstPath o : new FirstPath[] { new PathJoint(), new FirstPathOptimizationPrevention() }) { + sb.append(o.foo()).append(";"); + } + assertEquals("SecondPath.foo;FirstPath.foo;", sb.toString()); + } + + @Test + public void indirectDefaultMethodSubclass() { + StringBuilder sb = new StringBuilder(); + for (FirstPath o : new FirstPath[] { new PathJointSubclass(), new FirstPathOptimizationPrevention() }) { + sb.append(o.foo()).append(";"); + } + assertEquals("SecondPath.foo;FirstPath.foo;", sb.toString()); } interface FirstPath { @@ -511,6 +523,13 @@ public class VMTest { class PathJoint implements FirstPath, SecondPath { } + class PathJointSubclass extends PathJoint implements FirstPath { + } + + class FirstPathOptimizationPrevention implements FirstPath { + // Used to ensure that the implementation of FirstPath.foo() is not optimized away by TeaVM. + } + @Test public void cloneArray() { String[] a = new String[] { "foo" };