From dd10071c7d583e4b3a65e99732ea5e4f0adbaecd Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Fri, 12 Dec 2025 16:15:03 +0100 Subject: [PATCH] LDEV-5519 Restore exact match priority for constructor resolution https://luceeserver.atlassian.net/browse/LDEV-5519 --- .../lucee/transformer/dynamic/meta/Clazz.java | 14 ++++- test/tickets/LDEV5519.cfc | 60 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 test/tickets/LDEV5519.cfc diff --git a/core/src/main/java/lucee/transformer/dynamic/meta/Clazz.java b/core/src/main/java/lucee/transformer/dynamic/meta/Clazz.java index c277c8aace..724bdafe12 100644 --- a/core/src/main/java/lucee/transformer/dynamic/meta/Clazz.java +++ b/core/src/main/java/lucee/transformer/dynamic/meta/Clazz.java @@ -261,8 +261,20 @@ public static String getPackagePrefix() { } public static Constructor getConstructor(Class clazz, Constructor[] constructors, Object[] args, boolean convertArgument, boolean convertComparsion, Constructor defaultValue) { - // like Class[] parameterTypes; + + // LDEV-5519: exact match - parameter type must equal argument type exactly + outer: for (Constructor fm: constructors) { + if ((args.length == fm.getArgumentCount()) && clazz.getName().equals(fm.getDeclaringClassName())) { + parameterTypes = fm.getArgumentClasses(); + for (int y = 0; y < parameterTypes.length; y++) { + if (args[y] == null || Reflector.toReferenceClass(parameterTypes[y]) != args[y].getClass()) continue outer; + } + return fm; + } + } + + // like (assignable) - fallback if no exact match outer: for (Constructor fm: constructors) { if ((args.length == fm.getArgumentCount()) && clazz.getName().equals(fm.getDeclaringClassName())) { parameterTypes = fm.getArgumentClasses(); diff --git a/test/tickets/LDEV5519.cfc b/test/tickets/LDEV5519.cfc new file mode 100644 index 0000000000..0c7364fa33 --- /dev/null +++ b/test/tickets/LDEV5519.cfc @@ -0,0 +1,60 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function run( testResults, testBox ) { + describe( title='LDEV-5519 wrong constructor called - exact match should win over assignable', body=function() { + + it( title='JSONObject constructor with String should parse JSON, not treat as bean', body=function() { + var jsonString = '{"name":"test"}'; + var helper = new component javasettings='{ + "maven": ["org.json:json:20240303"] + }' { + import org.json.JSONObject; + function parse( jsonString ) { + var jsonObj = new JSONObject( javacast( "String", jsonString.toString() ) ); + return jsonObj.getString( "name" ); + } + }; + expect( helper.parse( jsonString ) ).toBe( "test" ); + }); + + it( title='StringBuilder constructor with String vs CharSequence', body=function() { + // StringBuilder has StringBuilder(String) and StringBuilder(CharSequence) + // String should match StringBuilder(String) exactly + var sb = createObject( "java", "java.lang.StringBuilder" ).init( "hello" ); + expect( sb.toString() ).toBe( "hello" ); + }); + + it( title='BigDecimal constructor with String vs Object', body=function() { + // BigDecimal(String) should be called, not some other constructor + var bd = createObject( "java", "java.math.BigDecimal" ).init( "123.45" ); + expect( bd.toString() ).toBe( "123.45" ); + }); + + it( title='HashMap constructor with Map argument', body=function() { + // HashMap(Map) should work when passing a LinkedHashMap + var source = createObject( "java", "java.util.LinkedHashMap" ).init(); + source.put( "key", "value" ); + var copy = createObject( "java", "java.util.HashMap" ).init( source ); + expect( copy.get( "key" ) ).toBe( "value" ); + }); + + it( title='ArrayList constructor with Collection argument', body=function() { + // ArrayList(Collection) should work when passing a LinkedList + var source = createObject( "java", "java.util.LinkedList" ).init(); + source.add( "item1" ); + source.add( "item2" ); + var copy = createObject( "java", "java.util.ArrayList" ).init( source ); + expect( copy.size() ).toBe( 2 ); + expect( copy.get( 0 ) ).toBe( "item1" ); + }); + + it( title='Integer constructor with String', body=function() { + // Integer(String) should parse the string + var i = createObject( "java", "java.lang.Integer" ).init( "42" ); + expect( i.intValue() ).toBe( 42 ); + }); + + }); + } + +}