Bug 55483 - ELException when object has overloaded methods
Summary: ELException when object has overloaded methods
Status: RESOLVED FIXED
Alias: None
Product: Tomcat 8
Classification: Unclassified
Component: EL (show other bugs)
Version: 8.0.x-trunk
Hardware: PC Mac OS X 10.4
: P2 normal (vote)
Target Milestone: ----
Assignee: Tomcat Developers Mailing List
URL:
Keywords:
: 56147 (view as bug list)
Depends on:
Blocks:
 
Reported: 2013-08-26 12:27 UTC by Daniel Mikusa
Modified: 2016-08-04 15:28 UTC (History)
1 user (show)



Attachments
Unit test (1.46 KB, text/plain)
2013-08-26 15:02 UTC, Daniel Mikusa
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Daniel Mikusa 2013-08-26 12:27:34 UTC
Included below are two test cases which fail.  The first calls an overloaded method on an object.  The second calls an overloaded constructor.  More details below.


1.) Here's the first test.

   @Test
   public void test01() {
       ELProcessor processor = new ELProcessor();
       processor.defineBean("sb", new StringBuilder());
       Assert.assertEquals("a", processor.eval("sb.append('a'); sb.toString()"));
   }

This fails with the following stack trace.

javax.el.ELException: Cannot convert a of type class java.lang.String to long
	at org.apache.el.lang.ELSupport.coerceToNumber(ELSupport.java:349)
	at org.apache.el.lang.ELSupport.coerceToNumber(ELSupport.java:328)
	at org.apache.el.lang.ELSupport.coerceToType(ELSupport.java:450)
	at org.apache.el.ExpressionFactoryImpl.coerceToType(ExpressionFactoryImpl.java:48)
	at javax.el.Util.buildParameters(Util.java:351)
	at javax.el.BeanELResolver.invoke(BeanELResolver.java:173)
	at javax.el.CompositeELResolver.invoke(CompositeELResolver.java:84)
	at org.apache.el.parser.AstValue.getValue(AstValue.java:157)
	at org.apache.el.parser.AstSemicolon.getValue(AstSemicolon.java:35)
	at org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:188)
	at javax.el.ELProcessor.getValue(ELProcessor.java:45)
	at javax.el.ELProcessor.eval(ELProcessor.java:38)
	at org.apache.el.parser.TestAstMethodCalls.test01(TestAstMethodCalls.java:32)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

Looking into this, it appears that the EL is having trouble because StringBuilder's append method is overloaded.  It is instructed to call append with the character 'c', but instead is trying to coerce the character 'c' to a long and call append with the long.

This chain of events seems to be kicked off in AstValue.getValue() line #157, where it's calling resolver.invoke(..).  The call to resolver.invoke() is passing null as the paramTypes argument.  This trickles down to BeanELResolver.invoke(), which calls Util.findMethod().  Because paramTypes is null, Util.findMethod() selects the first method it finds with the expected number of arguments.  In the case above, it selects StringBuilder.append(long), which causes the problem above.



2.) Here's the second test.

   @Test
   public void test02() {
       ELProcessor processor = new ELProcessor();
       processor.getELManager().importClass("java.util.Date");
       Date result = (Date) processor.eval("Date(86400)");
       Assert.assertEquals(86400, result.getTime());
   }

This one fails intermittently with the following stack trace.

javax.el.ELException: java.lang.IllegalArgumentException
	at javax.el.StaticFieldELResolver.invoke(StaticFieldELResolver.java:118)
	at javax.el.CompositeELResolver.invoke(CompositeELResolver.java:84)
	at org.apache.el.parser.AstFunction.getValue(AstFunction.java:138)
	at org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:188)
	at javax.el.ELProcessor.getValue(ELProcessor.java:45)
	at javax.el.ELProcessor.eval(ELProcessor.java:38)
	at org.apache.el.parser.TestAstMethodCalls.test02(TestAstMethodCalls.java:39)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.IllegalArgumentException
	at java.util.Date.parse(Date.java:615)
	at java.util.Date.<init>(Date.java:272)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
	at javax.el.StaticFieldELResolver.invoke(StaticFieldELResolver.java:111)
	... 29 more

Looking into this error, it seems similar to #1.  The difference is that AstFunction.getValue() line #138 is calling invoke on the resolver and passing null as the paramTypes.  This trickles down to the StaticFieldELResolver.invoke() method, which calls Util.findConstructor().  Again, because paramTypes is null, Util.findConstructor() searches the available constructors for the one with the same number of arguments.  The reason that this intermittently fails is because on my system, the call to Class.getConstructors() returns the list of constructs in an arbitrary order. So it fails when Date(String) is listed first, but succeeds when Date(long) is listed first.
Comment 1 Daniel Mikusa 2013-08-26 15:02:25 UTC
Created attachment 30764 [details]
Unit test

Attaching a unit test to replicate the problem.
Comment 2 Mark Thomas 2013-08-28 20:50:41 UTC
Thanks for the report and the unit tests. This has been fixed in trunk and will be included in 8.0.0-RC2 onwards.
Comment 3 Mark Thomas 2014-02-21 12:03:33 UTC
*** Bug 56147 has been marked as a duplicate of this bug. ***
Comment 4 Eugene Chung (TmaxSoft) 2014-04-02 04:31:30 UTC
org.apache.el.util.ReflectionUtil#getMethod(java.lang.Object, java.lang.Object, java.lang.Class<?>[], java.lang.Object[])


            // If a method is found where every parameter matches exactly,
            // return it
            if (exactMatch == paramCount) {
                getMethod(base.getClass(), m);
            }


I haven't fully understand the recent patches but I think it should be 

return getMethod(base.getClass(), m);

as the comment says.
Comment 5 Mark Thomas 2014-04-03 07:35:54 UTC
Re-open to review comment #4.
Comment 6 Mark Thomas 2014-04-12 20:59:24 UTC
Thanks for the report.

The missing return didn't change the behaviour, it just meant that the exact match wasn't returned as quickly as it could have been.
Comment 7 Christopher Ng 2014-04-23 10:05:42 UTC
The fix for this has broken one of our EL expressions, we have an overloaded method like so:

doSomething(HttpServletRequest a, String b, String c, String d);
doSomething(String a, String b, String c, String d);

and we end up with an error 'Unable to find unambiguous method'.  It is supposed to invoke the first method.

From what I can tell it's because when it is assessing the suitability of each method, it doesn't find an exact match for either as the actual request object is a sub-class of HttpServletRequest, and can also be coerced into a String.

Perhaps this can be fixed by prioritising sub-class matches over coercions?
Comment 8 Konstantin Kolinko 2014-04-25 22:37:54 UTC
(In reply to Christopher Ng from comment #7)
> The fix for this has broken one of our EL expressions, we have an overloaded method like so:
> 
> doSomething(HttpServletRequest a, String b, String c, String d);
> doSomething(String a, String b, String c, String d);
> ...

This issue has already been reported and is tracked as bug 56425.
(It has been fixed in 8.0.x for 8.0.6 onwards and in 7.0.x for 7.0.54 onwards.)
Comment 9 gangsu_choi 2014-08-20 09:24:29 UTC
This changes added Tomcat 7.0 but It has no unit tests.

Do Anyone have unit tests for Tomcat 7.0?
Comment 10 Hao 2016-08-02 04:01:40 UTC
This change broke our code. I pasted our code here
http://stackoverflow.com/questions/38709068/argument-type-of-parameterized-method-call-in-el

We were able to call ${objectMapper.writeValueAsString(actionItems)} where actionItems is an ArrayList, while writeValueAsString takes an Object argument. 

After this change, method invocation in EL can only find the method with exact match of parameter types. And our method call is broken with NoSuchMethodException.

It makes sense to find the exact match when there are method overloads. But if there is no method overload, it should return the only matched method thus it is compatible with the old code like ours.
Comment 11 Mark Thomas 2016-08-02 17:03:37 UTC
(In reply to Hao from comment #10)
> This change broke our code.

Please open a new issue and attach a minimal test case that demonstrates the issue.
Comment 12 Hao 2016-08-04 15:28:52 UTC
Hi Mark, I created a new issue yesterday
https://bz.apache.org/bugzilla/show_bug.cgi?id=59939

I believe it is related this this issue. Can you please have a look? Thanks.