1 /*
2  * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import java.io.IOException;
25 import java.lang.reflect.InvocationHandler;
26 import java.lang.reflect.InvocationTargetException;
27 import java.lang.reflect.Method;
28 import java.lang.reflect.Proxy;
29 import java.util.Arrays;
30 
31 import org.testng.annotations.DataProvider;
32 import org.testng.annotations.Test;
33 import static org.testng.Assert.*;
34 
35 /**
36  * @test
37  * @bug 8159746
38  * @run testng DefaultMethods
39  * @summary Basic tests for Proxy::invokeSuper default method
40  */
41 
42 public class DefaultMethods {
43     public interface I1 {
m()44         default int m() {
45             return 10;
46         }
47     }
48 
49     public interface I2 {
m()50         default int m() {
51             return 20;
52         }
53 
privateMethod()54         private void privateMethod() {
55             throw new Error("should not reach here");
56         }
57     }
58 
59     // I3::m inherits from I2:m
60     public interface I3 extends I2 {
m3(String... s)61         default int m3(String... s) {
62             return Arrays.stream(s).mapToInt(String::length).sum();
63         }
64     }
65 
66     public interface I4 extends I1, I2 {
m()67         default int m() {
68             return 40;
69         }
70 
mix(int a, String b)71         default int mix(int a, String b) {
72             return 0;
73         }
74     }
75 
76     public interface I12 extends I1, I2 {
77         @Override
m()78         int m();
79 
sum(int a, int b)80         default int sum(int a, int b) {
81             return a + b;
82         }
83 
concat(Object first, Object... rest)84         default Object[] concat(Object first, Object... rest) {
85             Object[] result = new Object[1 + rest.length];
86             result[0] = first;
87             System.arraycopy(rest, 0, result, 1, rest.length);
88             return result;
89         }
90     }
91 
92     public interface IX {
doThrow(Throwable exception)93         default void doThrow(Throwable exception) throws Throwable {
94             throw exception;
95         }
96     }
97 
findDefaultMethod(Class<?> refc, Method m)98     private static Method findDefaultMethod(Class<?> refc, Method m) {
99         try {
100             assertTrue(refc.isInterface());
101 
102             Method method = refc.getMethod(m.getName(), m.getParameterTypes());
103             assertTrue(method.isDefault());
104             return method;
105         } catch (NoSuchMethodException e) {
106             throw new RuntimeException(e);
107         }
108     }
109 
110     @Test
test()111     public void test() {
112         ClassLoader loader = DefaultMethods.class.getClassLoader();
113         Object proxy = Proxy.newProxyInstance(loader, new Class<?>[] { I1.class, I2.class},
114                 (o, method, params) -> {
115                     return InvocationHandler.invokeDefault(o, findDefaultMethod(I2.class, method), params);
116                 });
117         I1 i1 = (I1) proxy;
118         assertEquals(i1.m(), 20);
119     }
120 
121     // a default method is declared in one of the proxy interfaces
122     @DataProvider(name = "defaultMethods")
defaultMethods()123     private Object[][] defaultMethods() {
124         return new Object[][]{
125             new Object[]{new Class<?>[]{I1.class, I2.class}, true, 10},
126             new Object[]{new Class<?>[]{I1.class, I3.class}, true, 10},
127             new Object[]{new Class<?>[]{I1.class, I12.class}, true, 10},
128             new Object[]{new Class<?>[]{I2.class, I12.class}, true, 20},
129             new Object[]{new Class<?>[]{I4.class}, true, 40},
130             new Object[]{new Class<?>[]{I4.class, I3.class}, true, 40},
131             new Object[]{new Class<?>[]{I12.class}, false, -1},
132             new Object[]{new Class<?>[]{I12.class, I1.class, I2.class}, false, -1}
133         };
134     }
135 
136     @Test(dataProvider = "defaultMethods")
testDefaultMethod(Class<?>[] intfs, boolean isDefault, int expected)137     public void testDefaultMethod(Class<?>[] intfs, boolean isDefault, int expected) throws Throwable {
138         InvocationHandler ih = (proxy, method, params) -> {
139             System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params));
140             switch (method.getName()) {
141                 case "m":
142                     assertTrue(method.isDefault() == isDefault);
143                     assertTrue(Arrays.stream(proxy.getClass().getInterfaces())
144                                      .anyMatch(intf -> method.getDeclaringClass() == intf),
145                                Arrays.toString(proxy.getClass().getInterfaces()));
146                     if (method.isDefault()) {
147                         return InvocationHandler.invokeDefault(proxy, method, params);
148                     } else {
149                         return -1;
150                     }
151                 default:
152                     throw new UnsupportedOperationException(method.toString());
153             }
154         };
155 
156         Object proxy = Proxy.newProxyInstance(DefaultMethods.class.getClassLoader(), intfs, ih);
157         Method m = proxy.getClass().getMethod("m");
158         int result = (int)m.invoke(proxy);
159         assertEquals(result, expected);
160     }
161 
162     // a default method may be declared in a proxy interface or
163     // inherited from a superinterface of a proxy interface
164     @DataProvider(name = "supers")
supers()165     private Object[][] supers() {
166         return new Object[][]{
167             // invoke "m" implemented in the first proxy interface
168             // same as the method passed to InvocationHandler::invoke
169             new Object[]{new Class<?>[]{I1.class}, I1.class, 10},
170             new Object[]{new Class<?>[]{I2.class}, I2.class, 20},
171             new Object[]{new Class<?>[]{I1.class, I2.class}, I1.class, 10},
172             // "m" is implemented in I2, an indirect superinterface of I3
173             new Object[]{new Class<?>[]{I3.class}, I3.class, 20},
174             // "m" is implemented in I1, I2 and overridden in I4
175             new Object[]{new Class<?>[]{I4.class}, I4.class, 40},
176             // invoke "m" implemented in the second proxy interface
177             // different from the method passed to InvocationHandler::invoke
178             new Object[]{new Class<?>[]{I1.class, I2.class}, I2.class, 20},
179             new Object[]{new Class<?>[]{I1.class, I3.class}, I3.class, 20},
180             // I2::m is implemented in more than one proxy interface directly or indirectly
181             // I3::m resolves to I2::m (indirect superinterface)
182             // I2 is the superinterface of I4 and I4 overrides m
183             // the proxy class can invoke I4::m and I2::m
184             new Object[]{new Class<?>[]{I3.class, I4.class}, I3.class, 20},
185             new Object[]{new Class<?>[]{I3.class, I4.class}, I4.class, 40},
186             new Object[]{new Class<?>[]{I4.class, I3.class}, I3.class, 20},
187             new Object[]{new Class<?>[]{I4.class, I3.class}, I4.class, 40}
188         };
189     }
190 
191     @Test(dataProvider = "supers")
testSuper(Class<?>[] intfs, Class<?> proxyInterface, int expected)192     public void testSuper(Class<?>[] intfs, Class<?> proxyInterface, int expected) throws Throwable {
193         final InvocationHandler ih = (proxy, method, params) -> {
194             switch (method.getName()) {
195                 case "m":
196                     assertTrue(method.isDefault());
197                     return InvocationHandler.invokeDefault(proxy, findDefaultMethod(proxyInterface, method), params);
198                 default:
199                     throw new UnsupportedOperationException(method.toString());
200             }
201         };
202         ClassLoader loader = proxyInterface.getClassLoader();
203         Object proxy = Proxy.newProxyInstance(loader, intfs, ih);
204         if (proxyInterface == I1.class) {
205             I1 i1 = (I1) proxy;
206             assertEquals(i1.m(), expected);
207         } else if (proxyInterface == I2.class) {
208             I2 i2 = (I2) proxy;
209             assertEquals(i2.m(), expected);
210         } else if (proxyInterface == I3.class) {
211             I3 i3 = (I3) proxy;
212             assertEquals(i3.m(), expected);
213         } else if (proxyInterface == I4.class) {
214             I4 i4 = (I4) proxy;
215             assertEquals(i4.m(), expected);
216         } else {
217             throw new UnsupportedOperationException(proxyInterface.toString());
218         }
219         // invoke via InvocationHandler.invokeDefaultMethod directly
220         assertEquals(InvocationHandler.invokeDefault(proxy, proxyInterface.getMethod("m")), expected);
221     }
222 
223     // invoke I12 default methods with parameters and var args
224     @Test
testI12()225     public void testI12() throws Throwable {
226         final InvocationHandler ih = (proxy, method, params) -> {
227             System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params));
228             switch (method.getName()) {
229                 case "sum":
230                 case "concat":
231                     assertTrue(method.isDefault());
232                     return InvocationHandler.invokeDefault(proxy, method, params);
233                 default:
234                     throw new UnsupportedOperationException(method.toString());
235             }
236         };
237         ClassLoader loader = DefaultMethods.class.getClassLoader();
238         I12 i12 = (I12) Proxy.newProxyInstance(loader, new Class<?>[] { I12.class }, ih);
239         assertEquals(i12.sum(1, 2), 3);
240         assertEquals(i12.concat(1, 2, 3, 4), new Object[]{1, 2, 3, 4});
241         Method m = I12.class.getMethod("concat", Object.class, Object[].class);
242         assertTrue(m.isDefault());
243         assertEquals(InvocationHandler.invokeDefault(i12, m, 100, new Object[] {"foo", true, "bar"}),
244                      new Object[] {100, "foo", true, "bar"});
245     }
246 
247     // test a no-arg default method with and without arguments passed in the invocation
248     @Test
testEmptyArgument()249     public void testEmptyArgument() throws Throwable {
250         ClassLoader loader = DefaultMethods.class.getClassLoader();
251         Object proxy = Proxy.newProxyInstance(loader, new Class<?>[]{I4.class}, HANDLER);
252         Method m1 = I4.class.getMethod("m");
253         assertTrue(m1.getDeclaringClass() == I4.class);
254         assertTrue(m1.isDefault());
255         InvocationHandler.invokeDefault(proxy, m1);
256         InvocationHandler.invokeDefault(proxy, m1, new Object[0]);
257 
258         Method m2 = I4.class.getMethod("mix", int.class, String.class);
259         assertTrue(m1.getDeclaringClass() == I4.class);
260         assertTrue(m1.isDefault());
261         InvocationHandler.invokeDefault(proxy, m2, Integer.valueOf(100), "foo");
262     }
263 
264     @Test
testVarArgs()265     public void testVarArgs() throws Throwable {
266         ClassLoader loader = DefaultMethods.class.getClassLoader();
267         I3 proxy = (I3)Proxy.newProxyInstance(loader, new Class<?>[]{I3.class}, HANDLER);
268         Method m = I3.class.getMethod("m3", String[].class);
269         assertTrue(m.isVarArgs() && m.isDefault());
270         assertEquals(proxy.m3("a", "b", "cde"), 5);
271         assertEquals(InvocationHandler.invokeDefault(proxy, m, (Object)new String[] { "a", "bc" }), 3);
272     }
273 
274     /*
275      * Invoke I12::m which is an abstract method
276      */
277     @Test(expectedExceptions = {IllegalArgumentException.class})
invokeAbstractMethod()278     public void invokeAbstractMethod() throws Exception {
279         ClassLoader loader = DefaultMethods.class.getClassLoader();
280         I12 proxy = (I12) Proxy.newProxyInstance(loader, new Class<?>[]{I12.class}, HANDLER);
281         Method method = I12.class.getMethod("m");
282         assertTrue(method.getDeclaringClass() == I12.class);
283         assertFalse(method.isDefault());
284         proxy.m();
285     }
286 
287     /*
288      * Invoke a non proxy (default) method with parameters
289      */
290     @Test(expectedExceptions = {IllegalArgumentException.class})
invokeNonProxyMethod()291     public void invokeNonProxyMethod() throws Throwable {
292         ClassLoader loader = DefaultMethods.class.getClassLoader();
293         I3 proxy = (I3) Proxy.newProxyInstance(loader, new Class<?>[]{I3.class}, HANDLER);
294         Method m = I4.class.getMethod("mix", int.class, String.class);
295         assertTrue(m.isDefault());
296         InvocationHandler.invokeDefault(proxy, m);
297     }
298 
299     // negative cases
300     @DataProvider(name = "negativeCases")
negativeCases()301     private Object[][] negativeCases() {
302         return new Object[][]{
303             // I4::m overrides I1::m and I2::m
304             new Object[] { new Class<?>[]{I4.class}, I1.class, "m" },
305             new Object[] { new Class<?>[]{I4.class}, I2.class, "m" },
306             // I12::m is not a default method
307             new Object[] { new Class<?>[]{I12.class}, I12.class, "m" },
308             // non-proxy default method
309             new Object[] { new Class<?>[]{I3.class}, I1.class, "m" },
310             // not a default method and not a proxy interface
311             new Object[] { new Class<?>[]{I12.class}, DefaultMethods.class, "test" },
312             new Object[] { new Class<?>[]{I12.class}, Runnable.class, "run" },
313             // I2::privateMethod is a private method
314             new Object[] { new Class<?>[]{I3.class}, I2.class, "privateMethod" }
315         };
316     }
317 
318     @Test(dataProvider = "negativeCases", expectedExceptions = {IllegalArgumentException.class})
testNegativeCase(Class<?>[] interfaces, Class<?> defc, String name)319     public void testNegativeCase(Class<?>[] interfaces, Class<?> defc, String name)
320             throws Throwable {
321         ClassLoader loader = DefaultMethods.class.getClassLoader();
322         Object proxy = Proxy.newProxyInstance(loader, interfaces, HANDLER);
323         try {
324             Method method = defc.getDeclaredMethod(name);
325             InvocationHandler.invokeDefault(proxy, method);
326         } catch (Throwable e) {
327             System.out.format("%s method %s::%s exception thrown: %s%n",
328                               Arrays.toString(interfaces), defc.getName(), name, e.getMessage());
329             throw e;
330         }
331     }
332 
333     @DataProvider(name = "illegalArguments")
illegalArguments()334     private Object[][] illegalArguments() {
335         return new Object[][] {
336             new Object[] {},
337             new Object[] { 100 },
338             new Object[] { 100, "foo", 100 },
339             new Object[] { 100L, "foo" },
340             new Object[] { "foo", 100},
341             new Object[] { null, "foo" }
342         };
343     }
344 
345     @Test(dataProvider = "illegalArguments", expectedExceptions = {IllegalArgumentException.class})
testIllegalArgument(Object... args)346     public void testIllegalArgument(Object... args) throws Throwable {
347         ClassLoader loader = DefaultMethods.class.getClassLoader();
348         I4 proxy = (I4)Proxy.newProxyInstance(loader, new Class<?>[]{I4.class}, HANDLER);
349         Method m = I4.class.getMethod("mix", int.class, String.class);
350         assertTrue(m.isDefault());
351         if (args.length == 0) {
352             // substitute empty args with null since @DataProvider doesn't allow null array
353             args = null;
354         }
355         InvocationHandler.invokeDefault(proxy, m, args);
356     }
357 
358     @DataProvider(name = "throwables")
throwables()359     private Object[][] throwables() {
360         return new Object[][] {
361             new Object[] { new IOException() },
362             new Object[] { new IllegalArgumentException() },
363             new Object[] { new ClassCastException() },
364             new Object[] { new NullPointerException() },
365             new Object[] { new AssertionError() },
366             new Object[] { new Throwable() }
367         };
368     }
369 
370     @Test(dataProvider = "throwables")
testInvocationException(Throwable exception)371     public void testInvocationException(Throwable exception) throws Throwable {
372         ClassLoader loader = DefaultMethods.class.getClassLoader();
373         IX proxy = (IX)Proxy.newProxyInstance(loader, new Class<?>[]{IX.class}, HANDLER);
374         Method m = IX.class.getMethod("doThrow", Throwable.class);
375         try {
376             InvocationHandler.invokeDefault(proxy, m, exception);
377         } catch (Throwable e) {
378             assertEquals(e, exception);
379         }
380     }
381 
382     private static final InvocationHandler HANDLER = (proxy, method, params) -> {
383         System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params));
384         return InvocationHandler.invokeDefault(proxy, method, params);
385     };
386 }
387